Is there a way to create a strongly typed controller action? For example:
In a Controller I use:
aClientLink = Url.Action("MethodName", "ControllerName", new { Params... });
I would like to use:
aClientLink = Url.Action(Controller.MethodName,ControllerName);
I do not want to re-invent the wheel. I am sure someone has some clever solution. This would allow me to add compile time checking to controller methods.
You could create your own HtmlHelper extension methods that uses expressions (the same way they are used to reference properties of a model).
#* An expression used to indicate which property of the model should be
examined. It may or may not actually be executed. *#
#Html.IdFor( o => o.FirstName )
#* Don't actually evaluate the expression, just parse it for the method name *#
#Url.ActionFor( o => o.ControllerMethod() )
You can look at the MVC source for examples of helper methods which take expressions as input values, and you can see my answer here for how to retrieve metadata about an object property from an expression.
However...
Used in a view, I would argue that such an approach ties your view far too tightly to the controller. I dislike magic strings but they do provide complete decoupling.
It's not clear if you want to use such methods inside a controller, in which case the separation of concerns becomes less of a problem. You would still lose certain abilities, such as the ability to alias an action name.
Related
In the following code of the example application of VS2015:
#model LoginViewModel
<p>
#Html.DisplayNameFor(m => m.Name)
</p>
I want to know how the compiler knows that m refers to the LoginViewModel if I never said that m is an alias for LoginViewModel.
And I would like help understanding the lambda expression of DisplayNameFor. Why it requires a lambda if we could just pass the string m.Name? How is it used here?
The given Razor template will be compiled, so the Razor view compiler can do some magic here.
The compiler knows the type of your model because of the #model directive (without the #model directive the compiler falls back to dynamic).
If you look at the #Html.DisplayNameFor directive, then the Html instance is an object of the type HtmlHelper<TModel> where TModel is the type given by the #model directive. In your case is the concrete type HtmlHelper<LoginViewModel>.
Now the HtmlHelper<LoginViewModel>.DisplayNameFor method is stongly typed and the compiler can figure that 'm' (which is only a parameter name) is of type LoginViewModel and that the lamdba expression returns a value from the model.
During runtime the DisplayNameFor method is executed by providing your model object as parameter 'm' the expression returns the object of the model member (or the object the expression returns) and the MVC framework can inspect the object (Type, Validation Attributes, etc.) and produces the appropriate html based on internal or custom templates.
If you would just pass a string, then MVC would not be able to get the needed type and validation annotations (and much more information).
Your first question: It passes LogonViewModel to #Html.DislplayNameFor method since you have define it on you first line as your model (#model LoginViewModel)
Aslo as it mentioned here:
What is the #Html.DisplayFor syntax for?
Html.DisplayFor() will render the DisplayTemplate that matches the
property's type.
If it can't find any, I suppose it invokes .ToString().
DisplayNameFor is a strongly typed HTML helper. These were first introduced in ASP.NET MVC 2. The purpose of them (as per the linked article) is to...
...provide a nice way to get better type-safety within your view templates. This enables better compile-time checking of your views (allowing you to find errors at build-time instead of at runtime), and also supports richer intellisense when editing your view templates within Visual Studio.
If you want to understand the innards of how they work, the source code is available on GitHub.
I'm designing an alternative MVC framework for ASP.Net. Part of my goals for the framework is to have as little "magic" as possible. The only bit of reflection I have is for binding things like form, query string, etc to a plain ol' class(with some optional attributes for ignore, conversion, etc). As such, I do absolutely no class/method detection. Everything must be very explicit. I've gone through about 3 iterations of API "shape". The first two achieved my goal of having no magic, but it was very verbose and not easy to read.. and controllers usually had to do the heavy lifting the MVC framework should do.
So, now in this third iteration I'm trying really hard to get it right. One slightly controversial thing I do differently is the routing code. Because everything is explicit and reflection is discouraged, I can't search for some attribute in the controller to resolve a route. Everything must be specified at the route level. In the first iteration this wasn't done, but it made for extremely cumbersome and verbose controllers...
Right now, I have this fluent API for specifying routes. It has gone a bit beyond what I first imagined though and now functions as a sort of way to specify what a controller's method is capable of and what it should accept.
On to the actual code. The implementation is irrelevant. The only thing you really need to know is that there is a LOT of generic types involved. So, here is a quick sample of some routing:
var router=new Router(...);
var blog=router.Controller(() => new BlogController());
blog.Handles("/blog/index").With((ctrl) => ctrl.Index());
blog.Handles("/blog/{id}").With((ctrl,model) => ctrl.View(model["id"])).WhereRouteLike((r) => r["id"].IsInteger()); //model defaults to creating a dictionary from route parameters
blog.Handles("/blog/new").UsingFormModel(() => new BlogPost()).With((ctrl, model) => ctrl.NewPost(model)); //here model would be of type BlogPost. Also, could substitue UsingRouteModel, UsingQueryStringModel, etc
There are also some other methods that could be implemented such as WhereModelIsLike or some such that does verification on the model. However, does this kind of "specification" belong in the routing layer? What are the limits that should be specified in the routing layer? What should be left to the controller to validate?
Am I making the routing layer worry about too much?
I think the routing is way too verbose. I wouldn't want to write that kind of code for 20 controllers. Especially, because it is really repetetive.
The problem I see here that even default cases require verbose declarations. Those verbose declarations only should be needed for special cases.
It is expressive and readable, but you might want to consider packaging advanced features up.
Have a look at the following specification. And that's just for a single action in a single controller:
blog.Handles("/blog/new")
.UsingFormModel(() => new BlogPost())
.With((ctrl, model) => ctrl.NewPost(model))
.WhereModelIsLike(m => m.Status == PostStatus.New);
One way to only slightly reducing the amount of code would be to allow the specification of a root folder:
var blog=router.Controller(() => new BlogController(), "/blog");
blog.Handles("index").Wi..
blog.Handles("{id}").Wit..
blog.Handles("new").Usin..
Another idea to reduce the code for default cases would be to introduce one interface per default action. The controller needs to implement the interfaces for supported actions:
Something like this maybe:
public interface ISupportIndex
{
void Index();
}
public interface ISupportSingleItem
{
void View(int id);
}
Now, you could provide methods like blog.HandlesIndex();, blog.HandlesSingleItem();.
Those methods return the same thing as your existing methods, so the result can be further refined.
They could be designed as extension methods that are only available if the controller actually implements the interface. To achieve this, the return type of router.Controller would need to be a covariant interface with the controller as generic parameter, i.e. something like this:
IControllerRoute<out TController>
For example, the extension method HandlesIndex would be implemented like this:
public static IRouteHandler HandlesIndex(
this IControllerRoute<ISupportIndex> route)
{
// note: This makes use of the "root" as suggested above:
// It only specifies "index", not "/someroot/index".
return route.Handles("index").With(x => x.Index);
}
work on IControllerRoute<ISupportIndex>, to be only displayed in cases the controller actually supports it.
The route for the blog controller could look like this:
blog.HandlesIndex();
blog.HandlesSingleItem();
// Uses short version for models with default constructor:
blog.HandlesNew<BlogPost>().UsingFormModel();
// The version for models without default constructor could look like this:
//blog.HandlesNew<BlogPost>().UsingFormModel(() => new BlogPost(myDependency));
Adding validation rules could be done a little bit more concise, too:
blog.HandlesNew<BlogPost>().UsingFormModel()
.When(m => m.Status == PostStatus.New);
If the specification is more complex, it could be packaged in its own class that implements IModelValidation. That class is now used:
blog.HandlesNew<BlogPost>().UsingFormModel()
.WithValidator<NewBlogPostValidation>();
All of my suggestions are just ways to make your current approach easier to handle, so I guess up till now, it doesn't really answer your actual question. I do that now:
I like my controllers as clean as possible. Putting validation rules on the route is something that looks very good to me, because the controller action now can assume that it only is called with valid data. I would continue with this approach.
Yes, IMHO, routing should not contain logic about the model, or even the view.
If you look at light-weight Web frameworks out there now (Nancy etc.), the routing concept does not include things like view link generation. It is entirely about mapping the URI template to the controller. This takes a lot of the "magic" out of the ASP.NET implementation.
https://github.com/NancyFx/Nancy/wiki/Defining-routes
However, the Nancy approach still requires some "framework" code to understand which routes are available. So, it doesn't fit your requirements exactly.
Imagine a common scenario, this is a simpler version of what I'm coming across. I actually have a couple of layers of further nesting on mine....
But this is the scenario
Theme contains List
Category contains List
Product contains List
My Controller provides a fully populated Theme, with all the Categories for that theme, the Products within this categories and the their orders.
The orders collection has a property called Quantity (amongst many others) that needs to be editable.
#model ViewModels.MyViewModels.Theme
#Html.LabelFor(Model.Theme.name)
#foreach (var category in Model.Theme)
{
#Html.LabelFor(category.name)
#foreach(var product in theme.Products)
{
#Html.LabelFor(product.name)
#foreach(var order in product.Orders)
{
#Html.TextBoxFor(order.Quantity)
#Html.TextAreaFor(order.Note)
#Html.EditorFor(order.DateRequestedDeliveryFor)
}
}
}
If I use lambda instead then then I only seem to get a reference to the top Model object, "Theme" not those within the foreach loop.
Is what I'm trying to do there even possible or have I overestimated or misunderstood what is possible?
With the above I get an error on the TextboxFor, EditorFor, etc
CS0411: The type arguments for method
'System.Web.Mvc.Html.InputExtensions.TextBoxFor(System.Web.Mvc.HtmlHelper,
System.Linq.Expressions.Expression>)'
cannot be inferred from the usage. Try specifying the type arguments
explicitly.
Thanks.
The quick answer is to use a for() loop in place of your foreach() loops. Something like:
#for(var themeIndex = 0; themeIndex < Model.Theme.Count(); themeIndex++)
{
#Html.LabelFor(model => model.Theme[themeIndex])
#for(var productIndex=0; productIndex < Model.Theme[themeIndex].Products.Count(); productIndex++)
{
#Html.LabelFor(model=>model.Theme[themeIndex].Products[productIndex].name)
#for(var orderIndex=0; orderIndex < Model.Theme[themeIndex].Products[productIndex].Orders; orderIndex++)
{
#Html.TextBoxFor(model => model.Theme[themeIndex].Products[productIndex].Orders[orderIndex].Quantity)
#Html.TextAreaFor(model => model.Theme[themeIndex].Products[productIndex].Orders[orderIndex].Note)
#Html.EditorFor(model => model.Theme[themeIndex].Products[productIndex].Orders[orderIndex].DateRequestedDeliveryFor)
}
}
}
But this glosses over why this fixes the problem.
There are three things that you have at least a cursory understanding before you can resolve this issue. I have
to admit that I cargo-culted this
for a long time when I started working with the framework. And it took me quite a while
to really get what was going on.
Those three things are:
How do the LabelFor and other ...For helpers work in MVC?
What is an Expression Tree?
How does the Model Binder work?
All three of these concepts link together to get an answer.
How do the LabelFor and other ...For helpers work in MVC?
So, you've used the HtmlHelper<T> extensions for LabelFor and TextBoxFor and others, and
you probably noticed that when you invoke them, you pass them a lambda and it magically generates
some html. But how?
So the first thing to notice is the signature for these helpers. Lets look at the simplest overload for
TextBoxFor
public static MvcHtmlString TextBoxFor<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression
)
First, this is an extension method for a strongly typed HtmlHelper, of type <TModel>. So, to simply
state what happens behind the scenes, when razor renders this view it generates a class.
Inside of this class is an instance of HtmlHelper<TModel> (as the property Html, which is why you can use #Html...),
where TModel is the type defined in your #model statement. So in your case, when you are looking at this view TModel
will always be of the type ViewModels.MyViewModels.Theme.
Now, the next argument is a bit tricky. So lets look at an invocation
#Html.TextBoxFor(model=>model.SomeProperty);
It looks like we have a little lambda, And if one were to guess the signature, one might think that the type for
this argument would simply be a Func<TModel, TProperty>, where TModel is the type of the view model and TProperty
is inferred as the type of the property.
But thats not quite right, if you look at the actual type of the argument its Expression<Func<TModel, TProperty>>.
So when you normally generate a lambda, the compiler takes the lambda and compiles it down into MSIL, just like any other
function (which is why you can use delegates, method groups, and lambdas more or less interchangeably, because they are just
code references.)
However, when the compiler sees that the type is an Expression<>, it doesn't immediately compile the lambda down to MSIL, instead it generates an
Expression Tree!
What is an Expression Tree?
So, what the heck is an expression tree. Well, it's not complicated but its not a walk in the park either. To quote ms:
| Expression trees represent code in a tree-like data structure, where each node is an expression, for example, a method call or a binary operation such as x < y.
Simply put, an expression tree is a representation of a function as a collection of "actions".
In the case of model=>model.SomeProperty, the expression tree would have a node in it that says: "Get 'Some Property' from a 'model'"
This expression tree can be compiled into a function that can be invoked, but as long as it's an expression tree, it's just a collection of nodes.
So what is that good for?
So Func<> or Action<>, once you have them, they are pretty much atomic. All you can really do is Invoke() them, aka tell them to
do the work they are supposed to do.
Expression<Func<>> on the other hand, represents a collection of actions, which can be appended, manipulated, visited, or compiled and invoked.
So why are you telling me all this?
So with that understanding of what an Expression<> is, we can go back to Html.TextBoxFor. When it renders a textbox, it needs
to generate a few things about the property that you are giving it. Things like attributes on the property for validation, and specifically
in this case it needs to figure out what to name the <input> tag.
It does this by "walking" the expression tree and building a name. So for an expression like model=>model.SomeProperty, it walks the expression
gathering the properties that you are asking for and builds <input name='SomeProperty'>.
For a more complicated example, like model=>model.Foo.Bar.Baz.FooBar, it might generate <input name="Foo.Bar.Baz.FooBar" value="[whatever FooBar is]" />
Make sense? It is not just the work that the Func<> does, but how it does its work is important here.
(Note other frameworks like LINQ to SQL do similar things by walking an expression tree and building a different grammar, that this case a SQL query)
How does the Model Binder work?
So once you get that, we have to briefly talk about the model binder. When the form gets posted, it's simply like a flat
Dictionary<string, string>, we have lost the hierarchical structure our nested view model may have had. It's the
model binder's job to take this key-value pair combo and attempt to rehydrate an object with some properties. How does it do
this? You guessed it, by using the "key" or name of the input that got posted.
So if the form post looks like
Foo.Bar.Baz.FooBar = Hello
And you are posting to a model called SomeViewModel, then it does the reverse of what the helper did in the first place. It looks for
a property called "Foo". Then it looks for a property called "Bar" off of "Foo", then it looks for "Baz"... and so on...
Finally it tries to parse the value into the type of "FooBar" and assign it to "FooBar".
PHEW!!!
And voila, you have your model. The instance the Model Binder just constructed gets handed into requested Action.
So your solution doesn't work because the Html.[Type]For() helpers need an expression. And you are just giving them a value. It has no idea
what the context is for that value, and it doesn't know what to do with it.
Now some people suggested using partials to render. Now this in theory will work, but probably not the way that you expect. When you render a partial, you are changing the type of TModel, because you are in a different view context. This means that you can describe
your property with a shorter expression. It also means when the helper generates the name for your expression, it will be shallow. It
will only generate based on the expression it's given (not the entire context).
So lets say you had a partial that just rendered "Baz" (from our example before). Inside that partial you could just say:
#Html.TextBoxFor(model=>model.FooBar)
Rather than
#Html.TextBoxFor(model=>model.Foo.Bar.Baz.FooBar)
That means that it will generate an input tag like this:
<input name="FooBar" />
Which, if you are posting this form to an action that is expecting a large deeply nested ViewModel, then it will try to hydrate a property
called FooBar off of TModel. Which at best isn't there, and at worst is something else entirely. If you were posting to a specific action that was accepting a Baz, rather than the root model, then this would work great! In fact, partials are a good way to change your view context, for example if you had a page with multiple forms that all post to different actions, then rendering a partial for each one would be a great idea.
Now once you get all of this, you can start to do really interesting things with Expression<>, by programatically extending them and doing
other neat things with them. I won't get into any of that. But, hopefully, this will
give you a better understanding of what is going on behind the scenes and why things are acting the way that they are.
You can simply use EditorTemplates to do that, you need to create a directory named "EditorTemplates" in your controller's view folder and place a seperate view for each of your nested entities (named as entity class name)
Main view :
#model ViewModels.MyViewModels.Theme
#Html.LabelFor(Model.Theme.name)
#Html.EditorFor(Model.Theme.Categories)
Category view (/MyController/EditorTemplates/Category.cshtml) :
#model ViewModels.MyViewModels.Category
#Html.LabelFor(Model.Name)
#Html.EditorFor(Model.Products)
Product view (/MyController/EditorTemplates/Product.cshtml) :
#model ViewModels.MyViewModels.Product
#Html.LabelFor(Model.Name)
#Html.EditorFor(Model.Orders)
and so on
this way Html.EditorFor helper will generate element's names in an ordered manner and therefore you won't have any further problem for retrieving the posted Theme entity as a whole
You could add a Category partial and a Product partial, each would take a smaller part of the main model as it's own model, i.e. Category's model type might be an IEnumerable, you would pass in Model.Theme to it. The Product's partial might be an IEnumerable that you pass Model.Products into (from within the Category partial).
I'm not sure if that would be the right way forward, but would be interested in knowing.
EDIT
Since posting this answer, I've used EditorTemplates and find this the easiest way to handle repeating input groups or items. It handles all your validation message problems and form submission/model binding woes automatically.
When you are using foreach loop within view for binded model ...
Your model is supposed to be in listed format.
i.e
#model IEnumerable<ViewModels.MyViewModels>
#{
if (Model.Count() > 0)
{
#Html.DisplayFor(modelItem => Model.Theme.FirstOrDefault().name)
#foreach (var theme in Model.Theme)
{
#Html.DisplayFor(modelItem => theme.name)
#foreach(var product in theme.Products)
{
#Html.DisplayFor(modelItem => product.name)
#foreach(var order in product.Orders)
{
#Html.TextBoxFor(modelItem => order.Quantity)
#Html.TextAreaFor(modelItem => order.Note)
#Html.EditorFor(modelItem => order.DateRequestedDeliveryFor)
}
}
}
}else{
<span>No Theam avaiable</span>
}
}
It is clear from the error.
The HtmlHelpers appended with "For" expects lambda expression as a parameter.
If you are passing the value directly, better use Normal one.
e.g.
Instead of TextboxFor(....) use Textbox()
syntax for TextboxFor will be like Html.TextBoxFor(m=>m.Property)
In your scenario you can use basic for loop, as it will give you index to use.
#for(int i=0;i<Model.Theme.Count;i++)
{
#Html.LabelFor(m=>m.Theme[i].name)
#for(int j=0;j<Model.Theme[i].Products.Count;j++) )
{
#Html.LabelFor(m=>m.Theme[i].Products[j].name)
#for(int k=0;k<Model.Theme[i].Products[j].Orders.Count;k++)
{
#Html.TextBoxFor(m=>Model.Theme[i].Products[j].Orders[k].Quantity)
#Html.TextAreaFor(m=>Model.Theme[i].Products[j].Orders[k].Note)
#Html.EditorFor(m=>Model.Theme[i].Products[j].Orders[k].DateRequestedDeliveryFor)
}
}
}
Another much simpler possibility is that one of your property names is wrong (probably one you just changed in the class). This is what it was for me in RazorPages .NET Core 3.
All my controllers are based off of a BaseController, to share properties between them and override OnActionExecuting to set some values based on the route.
I'm creating a BaseViewData class to do the same for all my view data.
At the moment I'm populating the view data like so (C#):
var viewData = new BaseViewData
{
Name = "someName",
Language = "aLanguage",
Category = "aCategoryName"
};
I do this in every action that requires the view data. Some of the properties are common, need to be set throughout every action. Is there a way to set some of the properties on a more global scale?
If I instantiate the BaseViewData class in the OnActionExecuting method on the BaseController, how do I access the BaseViewData properties from the action in the regular controllers (derived from the BaseController)?
Update in response to Dennis Palmer:
I'm essentially doing this because of a nagging issue I'm having with ViewData["lang"] not being populated randomly on some requests. ViewData["lang"] contains "en" if the language is English, and "ja" if it is Japanese (well, it's supposed to anyway). I populate ViewData["lang"] inside OnActionExecuting on the BaseController.
In my view, I make a call to some partial views based on the language:
<% Html.RenderPartial(ViewData["lang"] + "/SiteMenu"); %>
But I'm randomly getting errors thrown that state "Cannot find /SiteMenu", which points to the fact that ViewData["lang"] has no value. I just cannot find any reason why ViewData["lang"] would not get populated. So, I'm rewriting the site to use ONLY strongly typed view data (and setting some hard defaults). But if another method is better, I'll go that way.
Thank you!
I'm not sure I follow exactly what you're trying to do, but if your view is using values in the route to display certain information, it seems like adding your own extension methods for HtmlHelper would be a better way to go.
Are Name, Language and Category contained in your routes? If so, then HtmlHelper will have access to the route info and can determine what to display via the extension methods. What is the correlation between your routes and what your views need to know?
Update: Is lang part of your route? If so, then I would still contend that you could write an HtmlHelper extension method that looks at the route data directly and determines which partial view to render. That way your controller wouldn't even need to worry about setting the ViewData["lang"]. The view would always know how to render based on the route.
Update 2: I think dismissing use of an HtmlHelper extension method because it re-evaluates the route data might be a case of premature optimization. Your controller inheritance scheme sounds overly complex and you asked the question because the way you were setting ViewData was unreliable. I doubt that pulling the value from route data would be much, if any, less efficient than setting and reading from ViewData.
From your comment:
In the controller I use the lang value
to determine which view to show as
well.
That only makes me think that there are more pieces of your system that I'd need to see in order to give better advice. If you have separate views for each language then why does the view need to be told which language to use?
Another alternative to consider would be using nested master pages. You could have a single master page for your site layout and then a nested master page for each language that just contains a hard coded lang value.
Perhaps instead of this inheritance scheme you have, you can just use action filters to add the data you need.
I'm writing a view helper based on the ideas about partial requests from this blog post: http://blog.codeville.net/2008/10/14/partial-requests-in-aspnet-mvc/
In the controller action I prepare a widget by running:
AddWidget<Someontroller>(x => x.OtherActionName(new List<int>()));
Then in my view I can run the action and render the view output by doing some form of:
Html.RenderWidget...
And here comes my question, what compiler checked syntax would you choose from the following to use in the view:
Html.RenderWidget<SomeController, List<int>>(x => x.OtherActionName);
Html.RenderWidget<SomeController>(x => x.OtherActionName(null));
Html.RenderWidget<SomeController>(x => x.OtherActionName(It.IsAny<List<int>>);
Can anyone name some pros and cons? Or is it better to go with strings as the original Partial Request implementation does?
PS. Don't take the naming of It.IsAny> to litteraly, I just thought it was best described using the Moq naming.
Using the strings is significantly faster than using so-called strongly typed helpers (really; it's like 10 times faster), unless you implement some kind of caching for your Expression parsing. Note, though, that MVC 2 may have something along these lines RSN. So one option, if you can, is to just wait and see what's in the next preview drop. At the very least, you'll want to look like the rest of MVC, and the MVC team may end up doing your work for you.
("So called" because under the covers they're going to end up as strings in a RouteValueDictionary anyway.)