DynamicViewDataDictionary support for ViewBag
rnorbauer opened this issue · 8 comments
In MVC, ViewBags inherit from DynamicViewDataDictionary
, which means that attempting to call a dynamic property on a ViewBag where that property doesn't exist returns null
rather than throwing a RuntimeBinderException
exception.
This allows for very simple syntax in templates like:
<title>@( ViewBag.Title ?? "My default title" )</title>
Is there any way this functionality can be replicated in Razor Machine?
I am currently writing the equivalent code as the following monstrosity:
<title>@( (ViewBag as System.Dynamic.DynamicObject).GetDynamicMemberNames().Contains("Title") ? ViewBag.Title : "My default title")</title>
Perhaps I'm missing something?
PS: the reason one would want this functionality is to optionally bubble up a Title for a page from a template to its containing layout, a fairly common scenario.
I would advise you against putting such logic in the views. It should be in the model layer which feeds the views.
The views should be kept out of logic as much as possible, and stay clear for editing html.
I'm all for separation of concerns, but I'm not sure that I agree with you in this particular case.
Firstly, I don't think that checking whether a value is present is too much work to perform in a view; you see that kind of thing in idiomatic Rails code all the time. But, more importantly, I'm building a static site generator (like Github's Jekyll, or Graze, which is built on Antaris RazorEngine), so most of my HTML pages don't even have their own unique model in the first place (I'm feeding in a model as an ExpandoObject that I read from a global configuration file that specifies certain site details shared across all pages.) The presence of a page in the resulting site isn't determined by anything that happens at a model or controller layer; it is determined by the presence of templates in folders defined by a conventional naming scheme.
How else would you propose doing what I describe? I need to set a variable in my template that sets the title of the HTML page. The actual <title>
element is in the containing layout template. Sometimes it will be set by me in the HTML page, and in other cases I simply want it to use a default. If it weren't for the use of a layout to share repeated code across templates, the HTML page is right where I would set the <title>
along with the vast majority of the other HTML. I don't think it would make sense to move this into the model merely as a way of getting around the fact that the data structure for sharing variables between templates is hard to work with. And in my case it would be difficult at any rate, since my individual pages are specified at the template level and not at the code level.
Given that RazorMachine does such an admirable job of replicating the use of Razor in MVC outside of MVC, it seems like supporting ViewBag behavior that corresponds to that in MVC would be consistent with the existing project approach.
While it isn't ideal, I did find a workaround.
In your _viewstart.cshtml file add:
ViewBag.Title = null;
Since that gets merged into every razor file before processing it will give the Title a default and you won't get the compile error but individual pages can override the value. Of course you could also just set your default value there instead of null if you wanted to drop the logic from your views. Obviously it requires your layout to contain every ViewBag property you want to be optional in your views. Since it's probably just the properties you use in the _Layout then that might not be too big an annoyance.
I'm not saying this is as good as having full support for ViewBag but hopefully it will at least spare you the messy code.
Can you provide a URL for Graze? I did some searching and couldn't find it. I made a razormachine based processor to build static sites with (similar to DocPad) so I'm curious if I should just use Graze or continue to use mine.
@SteveHiner: You can find info on Graze at https://github.com/mikoskinen/graze. I'll probably eventually get around to publishing my static site generator on GitHub. It's done, built on RazorMachine, watches files and auto-reloads the browser on updates, etc., but I need to rewrite my unit tests after a major refactoring effort I just completed, so it's not quite ready to be published.
@rnorbauer Did you ever publish your static site generator? I never built a file watcher into mine and reloading the browser on changes could be really handy.
It will be a very late response, but i needed same behaviour from my ViewBag dynamic object because of the same reason.
I just expanded the Xipton.Razor.Core.DynamicData class and overrided TryGetValue method as in the following:
public class ViewBagDynamic : Xipton.Razor.Core.DynamicData
{
public override bool TryGetMember(System.Dynamic.GetMemberBinder binder, out object result)
{
if (!base.TryGetMember(binder, out result))
{
result = null;
}
return true;
}
}
If you return true allways and set the result
outgoing parameter as null when base.TryGetValue result returns false, you get the MVC like behaviour basically.
Actually, there is a shortened version of the implementation:
public class ViewBagDynamic : Xipton.Razor.Core.DynamicData
{
public override bool TryGetMember(System.Dynamic.GetMemberBinder binder, out object result)
{
base.TryGetMember(binder, out result);
return true;
}
}
You don't need to check if invocation of base.TryGetMember(binder, out result) returned true or false. If it returns false then it sets result
parameter as null
. Then you just need to return true
.