1

I'm in the process of learning MVC and I'm coming from have a background in WebForms.

I'm working with the default MVC project template and I don't understand how the textbox knows to set the model's "Email" property.

login.cshtml

<div class="form-group">
    @Html.LabelFor(m => m.Email, new { @class = "col-md-2 control-label" })
    <div class="col-md-10">
        @Html.TextBoxFor(m => m.Email, new { @class = "form-control" })
        @Html.ValidationMessageFor(m => m.Email, "", new { @class = "text-danger" })
    </div>
</div>

So I get that m => m.Email is a lambda expression that creates a Func<LoginViewModel, string> anonymous function (or expression?). To me it looks like a getter function (for rendering the html textbox value from data) that takes a viewmodel and returns a string by accessing it's "Email" property.

Yet the server somehow knows all about the "m.Email" variable. It knows all the attributes and it knows to set it with the value from the text box when it gets posted to the server.

My question is how does it know this? How is the server extracting this information about the "Email" modelview variable and could you give me an example of how I could do it manually given a similar Func<LoginViewModel, string> type?

Slight
  • 1,541
  • 3
  • 19
  • 38

2 Answers2

2

There are many things going on here. Some of which are "hidden" by the Razor rendering engine behind the scenes. For example, at the top of the page when you specify @model Foo this actually is a syntactical shortcut to creating a class for your Page which uses Foo as it's model type. In WebForms this would look like this:

<% @Page Inherits="System.Web.Mvc.ViewPage<Foo>" %>

Razor hides this from you though. In reality, your View actually is a ViewPage<Foo> when you use @model Foo

Now, if you look at ViewPage in the documentation:

https://msdn.microsoft.com/en-us/library/dd470798(v=vs.118).aspx

You will see a lot of interesting things. Among them is the Html property.

This is of type HtmlHelper<TModel> where TModel is Foo if your Model is Foo.

So, when you write (in Razor) @Html.Whatever() this is really an HtmlHeler<Foo>.Whatever().

https://msdn.microsoft.com/en-us/library/dd492619(v=vs.118).aspx

Now, let's look at the actual method you're concerned with. Html.TextBoxFor()

If you look at the link above for HtmlHelper, you can see a number of Extension methods on this, these extension methods are the basis for the HtmlHelpers that MVC provides. Among them is:

https://msdn.microsoft.com/en-us/library/ee703644(v=vs.118).aspx

public static MvcHtmlString TextBoxFor<TModel, TProperty>(
    this HtmlHelper<TModel> htmlHelper,
    Expression<Func<TModel, TProperty>> expression
)

Here, we can see that this is an extension method. It takes the TModel and TProperty generic parameters, and in the function parameters is this HtmlHelper<TModel> htmlHelper as the first parameter. C# hides this from you in extension methods, but passes it to the method when you call it as @Html.TextBoxFor().

Notice that the next parmeter is the expression, which is of type Expression<Func<TModel, TProperty>>, so, this is how the expression (or lambda) knows ultimately what the model is, and knows to return the property type. It boils down to specifying the model type at the top of the page, and having this bubble down through the object graph.

I know this is a lot to absorb, but if you follow the chain of objects, you should get the hang of how this works.

As for posting back to the server, this has little to do with any of this. That is part of the model binding system, and it just looks at the values posted to the server and the type of parameter the action method takes, and then it tries to match them up based on their names.

The only thing that is related is that the HtmlHelpers format the input element names in a manner the model binder can understand.

Erik Funkenbusch
  • 92,674
  • 28
  • 195
  • 291
  • Oh ok this makes perfect sense. So it's Func not Func like I was seeing. For some reason intellisense was showing the latter and it made sense. If I do it with a boolean modelview variable it shows Func, any idea why that is? Otherwise this answer seems correct. – Slight Mar 03 '15 at 19:45
  • @Slight - Intellisense is showing you the type of the property you're currently using... so if it's a Boolean property, TProperty will be bool, if it's a string it will be string. – Erik Funkenbusch Mar 03 '15 at 19:50
  • Ah, I misinterpreted, so it was working as I thought. The only part of your answer that I was looking for then is the bottom part "As for posting back to the server[...]". Could you elaborate on that? How does the postback data even get a name? How would it know to label the data as "Email" or is it just based on the sequential order? – Slight Mar 03 '15 at 20:00
  • @Slight - there are already plenty of questions (and answers) on how model binding works, plus lots of blog posts. I answered the part that was less well documented. However, in general, the helper formats the html input element with a "name='Email'" attribute based on the property name, if you're interested, you can read the source code for the HTML helpers on codeplex. Also, look at the generated HTML in the web page of the view (not the razor code, but the code in the browser) to see the attributes created. – Erik Funkenbusch Mar 03 '15 at 20:04
  • Well thanks for the info Erik,. While I did find your answer informative and upped it, I had to accept the other answer as it was more relevant to what I was asking for. I'm not blaming you or anything, my question probably could've been clearer. – Slight Mar 04 '15 at 01:17
  • @Slight - yes, your question seemed to be asking how the lambda expressions knew what the model was, and how to access the properties, which is what I was answering. The rest of it has so little to do with Expressions or lambdas that it seemed irrelevant. – Erik Funkenbusch Mar 04 '15 at 04:13
1

Congrats on taking the plunge into Mvc.

The lambda function is just for creating the textbox html string, it has nothing to do with it coming back in, it uses that to scan the property for dataannotations for validation, display forms etc etc.

Getting data back in to your actions? That is down to the model binder.

Any post action to a controller will have it's arguments inspected by the model binder and then the form collection keys will be tried against all the parameters to try and match them up by name.

So if you had an action like this

public ActionResult SendMeData(string firstName)

The form collection will be scanned for keys that match firstname (case insensitively) and if there is a match, it will set the argument to it.

In the case of this action signature

public ActionResult SendMeData(Dude dude)

public class Dude {
    public string FirstName {get;set;}
}

It will check Dude for it having a parameterless constructor, if it does, it will create a new instance of that object, and then scan its properties looking for matching names in the form collection and setting appropriately.

So any values that you want to send back to the controller in a post action, just create an input/select/textarea with the name matching the property or argument you want, and all the magic is handled for you.

Slicksim
  • 7,054
  • 28
  • 32
  • Ok, so you're saying there is an implicit connection that the backend tries to establish through reflection. In my code example, how would it know that the textbox is tied to the email field? Nothing there, other than the lambda expression, links it. In the post data, it wouldn't be anything like "Email: value" right? Because the name "Email" doesn't appear anywhere on the view. – Slight Mar 03 '15 at 19:56
  • The result of the helper would be something like that is how it gets back in, that goes into the form collection as a namevaluepair, so the model binder finds it and binds it to any property in can find in the root of an object call Email, or an argument called email – Slicksim Mar 03 '15 at 20:29
  • I just wanted to point out, upon doing more research, that an Expression> (aka m => m.Email in my example) can actually give you the PropertyInfo being accessed in the lambda expression. This is how the system knows exactly what property to use and output. It'd be cool if you could add something like that to your answer for future viewers. (Here's a Q/A that demonsrates this http://stackoverflow.com/questions/491429/how-to-get-the-propertyinfo-of-a-specific-property) – Slight Mar 04 '15 at 01:52