I just simply am not understanding the a couple of concepts of this simple MVC View:
#model Models.Company
<h2>Index</h2>
#Html.TextBoxFor(m => m.CompanyName)
<br />
#Html.DropDownListFor(m => m.Departments, new SelectList(Model.Departments, "Id", "Name"), "Select Department")
Note that Model.Departments is a property of the Company object that returns a List object.
I understand that the Model of the View is the Company object. What I'm not understanding in the least bit is why LINQ is used to populate the name of the TextBox, why it is used as the name of the DropDownList and why Model Dot Departments is used for the SelectList.
Why not just use Model.CompanyName and Model.Departments? Why bring in LINQ at all?
Do you mean this?:
#Html.TextBoxFor(m.CompanyName)
instead of this?:
#Html.TextBoxFor(m => m.CompanyName)
Because those mean two very different things. The first one (the one you would like to use) doesn't tell TextBoxFor() anything about the model. It just passes a string value. So the resulting text box would have only that string value to use, and nothing about the property or the model which held that property. No model annotations, no validation, nothing. It would be exactly the same as this:
#Html.TextBoxFor("My Company Name")
Heck, it wouldn't even be able to tell the input what to use for the name attribute, which means there'd be no way to bind the model when posting the form back to a server action.
The actual version being used in the code doesn't pass the value of the property, it passes an expression which references the property itself:
m => m.CompanyName
So TextBoxFor() knows the model type, property type, annotations therein, etc. That way it can use any additional information useful to the framework such as validation, helpful messages, type checking, etc.
Related
How can I display Date Humanizely inside DisplayFor?
I tried like this : #Html.DisplayFor(model =>model.EndDate.Humanize())
but editor shows error like:
an expression tree may not contain a call or invocation that uses
optional arguments
Html.DisplayFor is a templated helper, which just means it returns the value of the model expression using a template. The default template is simply calling ToString() on the property referenced by the model expression. As such, what you pass into it must be a model expression referencing a particular property; you cannot do something like call Humanize on it.
However, you can define your own template. This entails creating a view in Views\Shared\DisplayTemplates, which conforms to one of these naming conventions:
It's named after the type it should be used for, e.g. DateTime.cshtml will be applied to any property that is of type DateTime.
It's named after one of the members of the DataType enum, in which case, it's utilized when that DataType is applied to a particular property, e.g. Date.cshtml will be utilized when you apply the attribute [DataType(DataType.Date)].
It's named whatever you like, but that name is explicitly specified for the property using the UIHint attribute, e.g. if you apply an attribute like [UIHint("MyAwesomeDateTime")] and associated MyAwesomeDateTime.cshtml view will be used.
As such, if you were to create a view like Views\Shared\DisplayTemplates\DateTime.cshtml with the contents:
#model DateTime
#Model.Humanize()
Then, simply calling #Html.DisplayFor(m => m.EndDate) will return the humanized date you're looking for. Since EndDate is a DateTime, DisplayFor will utilize your DateTime.cshtml template, which outputs the model (your DateTime with Humanized chained.
An alternative way to specify a display template is to pass it in directly. This is useful when you have a one-off situation and you don't want the template to apply to everything of a particular type. For example, if you just wanted EndDate humanized, but other dates to display as normal, that could be achieved by doing something like:
#Html.DisplayFor(m => m.EndDate, "HumanizedDate")
You would then of course need an associated HumanizedDate.cshtml. You can also employ UIHint on your property as described above here, if you'd prefer to keep this out of the view, and on your model instead.
All that said, display templates are best utilized when there's complex constructions involved. As Stephen pointed out in the comments below your question, you could easily just do: #Model.EndDate.Humanize(), which not only is more terse and explicit than #Html.DisplayFor(m => m.EndDate), but also doesn't require adding custom views or attributes to your model properties.
I have a use case where I used different models for the GET and POST actions in my controller. This works great for my view, because most of the data goes into labels. The model for the GET method contains 10 properties, but the model for the POST method only needs 3.
This GET view renders a form, which only needs 3 of these properties, not all 10. Thus, the model for the POST method accepts a model class which contains only these 3 properties. Therefore, the ASP.Net MVC model binder populates the model class parameter on my POST method with only these 3 necessary properties, and all is well.
Here's the question: When I encounter some business rule violation in the POST method, and want to use ModelState.AddModelError, and re-display the original view, I no longer have the 7 properties that were not POSTed, as they were not part of the form, and are not part of the model class which this method takes as its parameter.
Currently, I'm calling into a builder to return an instance of the model class for the POST method, and have the GET method itself delegating to the same builder. So, in these cases, when there is some business rule violation in the POST method, I return a View("OriginalGetView", originalGetModel). How can I use ModelState.AddModelError in this case, in the POST method, if I want to send custom messages back to the view, using a completely different model class?
It seemed way too lazy to use the same model class for both the GET and POST methods, given that their needs were so different. What is the best practice here? I see a lot of people recommending to use the same model for both methods, and to POST all of the fields back from hidden form fields, but that just seems like a waste of bandwidth in the majority of cases, and it feels ugly to be sending things like "VendorName" back to the server, when I already have "VendorId".
I may be misunderstanding what you are trying to do, but make sure you aren't being penny-wise and pound foolish. I see you may only want to post the identifiers and not necessarily the descriptors. But it sounds like you have to re-display the view after posting...if so you can just access the model properties if you post the same model that is in the get. If you only post the identifiers, you have to spend time re-accessing the database to get the description values(i.e. vendorname as you describe) using the vendor id no? Wouldn't that also be extra processing? Like I said, I could be misunderstanding your post, but for consistency using the same view model for your view to get and post makes the most sense to me.
Hidden Inputs maybe the best solution here still I think, even on 2g you shouldn't create any lag unless unless the values of your Model properties are long strings or something encrypted or xml.
So your .cshtml would have this in it for the 4 properties not currently included in the form:
<form>
#Html.HiddenFor(m => m.Property1)
#Html.HiddenFor(m => m.Property2)
#Html.HiddenFor(m => m.Property3)
#Html.HiddenFor(m => m.Property4)
But you could also get the model state errors from the original posted model and recreate the ModelError state in your response model to get around using hidden inputs.
I just found this guide (not the answer with Green Checkmark but the highest upped Answer: ASP.NET MVC - How to Preserve ModelState Errors Across RedirectToAction?
Note: if you need to copy model properties from Model to another Model (of the same type or different type), in a cleaner way, check out AutoMapper.
Perhaps this could help with what you were trying to achieve - 'Model' level errors - which wouldn't need to attach to a specific field/property - but can be displayed in a Global area.
https://stackoverflow.com/a/53716648/10257093
The end goal is to render an editor template, for a model property, known only at run time, that will not take it's value from the ViewBag.
Html.Editor will use the ViewBag value if the property names match. I don't want this and I hate this "feature".
I hope that this is possible somehow:
var propName = "MyProperty";
var expression = GiveMeTheExpression();
#Html.EditorFor(expression,"MyEditorTemplate")
If not this then some way of rendering an editor template without the viewbag values being used instead of the model's values. I'm totally fine with doing this, IF I CAN IGNORE THE VIEWBAG VALUES SOMEHOW:
#Html.Editor(propName, other, arguments)
You'll probably have to use Html.Partial with a custom ViewDataDictionary.
object knownAtRuntime = ViewBag.ObjectName; // Adapt to your solution
string templateName = String.Concat("EditorTemplates/", knownAtRuntime.GetType().Name);
#Html.Partial(templateName, knownAtRuntime, new ViewDataDictionary(knownAtRuntime));
Note: I made this example simple to illustrate the core concept but you can of course extend it to read UIHintAttribute etc if you like.
Note 2: You may also want to copy values from Html.ViewData to the new ViewDataDictionary to keep your modelstate etc.
Why not try this ?
#Html.EditorFor(x=>x.SomePropertyName)
Assuming SomePropertyName is the name of a Property of your Model which is strongly typed to your view.
Shyju taking about strongly typed object, if I understand you correctly, you need something like: asp.net mvc 3 and dynamic view generation
My solution was to use the #Html.Editor() method with property name as string and to use ViewBag keys that are very unlikely to be found on the model object.
Not ideal, but should work well.
a few newbie questions here:
In a strongly typed view, why:
#model MyProj.Models.User
Why do we use lambdas: ? What exactly does that do? Why not just model.Email?
1. #Html.DisplayNameFor(model => model.Email)
Why doesn't this work? Didn't we call the variable model? (I know I should use #html.() but why isn't model recognisable?
2. users name is: #model.Name
Why does it work with an uppercase 'M'? Didnt we name it with a lower case?
3. users name is: Model.Name
Thanks!
Why do we use lambdas: ? What exactly does that do? Why not just
model.Email?
The DisplayNameFor as well as all the other *For helper methods are taking advantage of the ability of lambda expressions to participate in Expression Trees.
By itself this lambda expression is simply taking in a model type and returning a String. But there is more information needed to get the display name.
In pseudo code, that method is doing:
1.) Treat the lambda as an Expression
2.) Parse the expression to get the name of the property
3.) Use reflection to get the DisplayNameAttribute for that property
4.) Extract the value if it exists, otherwise use the name of the property
5.) Generate a label for that property using either the display name, or property name
Without using Expression trees, you would end up having to pass the property name in as a string... and that just sucks. This provides a strongly typed way of using reflection without magic strings.
Why doesn't this work? Didn't we call the variable model? (I know I
should use #html.() but why isn't model recognisable?
#model is a special directive that is treated differently by Razor. It's like a reserved keyword that isn't any different than class or int.
Why does it work with an uppercase 'M'? Didnt we name it with a lower
case?
Model is a property on your view that has the type you defined using the #model directive, and references your model. Without using the #model directive it would have a type of dynamic.
Im starting with MVC2 and i have a simple question:
If i have a typed view with a form inside, and this textbox created with lambda expressions:
<%: Html.TextBoxFor(e => e.Name)%>
When i submit this form the default model binder take the request form, takes the model typed to the view, serialize the data posted (as a this model) and pass it to an action of my controller.
To try to explain myself better, lets imagine that i have a url like localhost/edittestmodel/ID/1 and i have in my controller action the following code:
public ActionResult Edit(int id)
{
TestModel testmodel=new TestModel();
testmodel.Name="texttorenderintotextbox";
//whats the class that place the testmodel properties into the view?
return View(testmodel);
}
What's the responsable class for place the Name property of my testmodel object into the textbox
<%: Html.TextBoxFor(e => e.Name)%>
Thanks in advance.
Best Regards.
Jose.
It's the TextBoxFor helper method that's responsible for generating the input field from the lambda expression.
View's don't have anything to do in POST request and model binding
When you have strong type views, model type is barely used to have the simplicity of code intellisense in view's code (so you can use lambda expressions like in your example).
But when you POST data back, nothing gets checked against the view. It gets checked against controller action parameters though. And if there's a parameter with a certain custom model binder type, that particular model binder is used to process incoming data.
But to answer your question: TextBoxFor checks your strong type view's model and generates particular textbox with correct name attribute. So data from it will be posted back under the correct form field name.
To go even deeper. It's the view engine that parses view's ASPX code and runs all server side scripts including Html.TextBoxFor() call.