I have an Edit action/view for my User object, but only want a few fields to be editable.
I've set up the view to bind to the User object, and am using Html.EditorFor() for the few fields that I want to be editable.
I noticed in my User object on Post:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult EditUser(Mynamespace.User user)
{ }
that only the fields that I provided .EditorFor() controls for actually have any data.
I tried using Html.Hidden(Model.ID) for one of the fields that i didn't want to be editable, but it is null in the new User object created from model binding.
So, my question- How do I bind where only a couple of the fields should be editable?
Thanks!
It sounds like you probably want to start thinking about using a View Model that is specific to the form/input that you're dealing with. But in the short term, ....
You could bind to a FormCollection parameter instead and copy the values manually, OR...
you can use the TryUpdateModel method to populate this existing user object with the new data.
Here's the documentation for TryUpdateModel:
http://msdn.microsoft.com/en-us/library/dd470756.aspx
It's still possible for malicious users to send phony form-values that map to real properties on your model, so to protect against this (like an employee changing his salary property with a simple form hack) you can introduce an interface that contains the white list properties that you allow.
Here's an example:
public interface IUserEditableFields
{
string Username {get;set;}
string Email {get;set;}
}
//... in the controller action
if(TryUpdateModel<IUserEditableFields>(user)) {
//validation passed
//only Username and Email were editable
}
This is a good resource on how to do this:
http://css.dzone.com/news/aspnet-mvc-think-before-you-bi
Are you using the strongly-type helper for the hidden field or is it exactly like you've typed. If you've got it exactly as typed, then the name of the hidden field is the value of the id, not the name of the property on the model (ID). You might want to change it to:
<%= Html.Hidden( "ID" ) %>
or (if using strongly-typed helpers)
<%= Html.HiddenFor( m => m.ID ) %>
Ben's answer is largely correct, in that a ViewModel might be more appropriate, and short of that, TryUpdateModel can be used. However, I add that in that case, rather than requiring the domain object to implement a new interface, you use the overload TryUpdateModel<T>(T, string[]), which allows you to whitelist the updateable properties in a string array by name.
Related
I am working on an MVC 2.0 C# web Applciation. In one of my form, i am using LabelFor() html helper.
I am using the following syntax:
<%=Html.LabelFor(model=>model.Addedon)%>
Here, for this label i would like to associate a initial value that is DateTime.Now
I tried some thing like this:
<%=Html.LabelFor(model=>model.Addedon,new{value=DateTime.Now})%>
But, i am getting an error saying that there is no over load for this helper taking two arguments.Please help
UPDATED:
The form is create form/ add form which makes an insert operation. So, i am building a model and updating that model to the database.
In that model, i have a field called createdby. So, i need to associate this value with the username logged in and doing the insert operation.
So, how to associate this username value with the model field and i need to display as label so that it will be read only field.
Hope this makes clear..
LabelFor is only for, you guessed it, rendering a <label> element.
It also uses the [Display] and [DisplayName] attributes, so you can have a strongly-typed label with custom name.
What you're after is probably this:
<div>
<%= Html.LabelFor(model => model.Addeon) %>
</div>
<div>
<%= Html.DisplayFor(model => model.Addeon) %>
</div>
So the LabelFor will generate the property name description (e.g. 'Addeon'), while the DisplayFor will render the property value. DisplayFor can use the [DisplayFormat] attribute if you need custom formatting. You can set the default property value in the view model's constructor:
public class ViewModel
{
[Display(Name = "My awesome date")]
public DateTime Addeon {get;set;}
public ViewModel()
{
Addeon = DateTime.Now;
}
}
[EDIT]
Actually, your edit would make for a good second question instead of putting it here. Anyway, in your situation I'd create a dedicated view model that would hold the properties you need (e.g. user name) and would be filled in controller. Everything else would be conceptually the same - view would bind to the view model.
I have an entity with, amongst others, the fields: CreatedBy, CreatedAt, ChangedAt
Now in the view, I do not want the user to fill these fields.
My approach was to fill them on the HTTP POST Action, before ModelState.IsValid check is made and before saving the data to the database.
However, ModelState.IsValid keeps returning false no matter what. What is the right way to implement this? Should I take the validation (ModelState.IsValid) from the POST action?
One problem, many solutions (from best to worst, in my opinion).
First solution
The best way would be to use a ViewModel (a class containing only the fields which must be edited by user and / or validated).
In this case, the CreatedBy, CreatedAt, ChangedAt fields would not appear in your ViewModel class.
Second solution
If you don't want ViewModel, you can put the "not mutable by user" fields as hidden fields in your view
#Html.HiddenFor(m => m.CreatedAt)
of course, hidden fields can be changed by user, so it's always an open door to unwanted datas...
Third solution
Clean ModelState from undesired errors before checking if it's valid (should become really boring if you've many fields concerned).
if (ModelState.ContainsKey("CreatedAt")) {
ModelState["CreatedAt"].Errors.Clear();
}
//then test for ModelState.IsValid()
Other solutions
WhiteList, BlackList for model binding, or... anything I forgot !
Many projects like to separate the View Model from the Domain Model. This allows you to create a View-Model class specifically tailored for the data you want to render and/or receive in a certain action while keeping the domain model correct/consistent.
In your View-Model class you would either not define any such property as the created date (since it is not supposed to be posted but is determined in the action). Or if you use one and the same View-Model for rendering and posting, and you want to render the date, you can make the date nullable (see Alexey's answer) on the View-Model while keeping it mandatory on the domain model.
DateTime is not nullable. If you want to keep those fields as null during model binding, if there is no values for them use:
Nullable<DateTime>
or shortcut:
DateTime?
I have a FormViewModel that handles different fields. Many of them have not to be presented to the user (such as modified_date, current_user_id) so I am using hidden fields to respect the FormViewModel structure. When submitted they are passed to the controller's action and correctly saved to the DB but I'm asking: is it the best way to do in ASPNET MVC? I would have preferred to define them in FormViewModel and using only the fields to be modified instead of showing also the non-modifiable as hidden fields.
Is there a better way to do it?
If these fields are not being touched by the user than I would do this;
Create a FormViewModel with only the fields that are relevant. Also the primary key.
The primary key still needs to be on the page me thinks.
Then in the controller you accept the FormViewModel as the argument, you then load the actual model and update, validate fields as required and save the model.
The above is simplistic and you'll have more layers but you should get the idea
I think you can do a few things to make your life a little easier:
Let the URL (and the routing mechanism) give you the id (the primary key of whatever you are trying to edit)
You can have a URL like '/Student/Edit/1' Routing will ensure that your Action method gets the id value directly.
Have 2 action methods to handle your request. One decorated with [HttpGet] to render the initial form to the user (where you just retrieve your object from the repository and pass it on to your View) and a [HttpPost] one to actually handle the post back from the user.
The second method could look something like:
[HttpPost]
[ActionName("Edit")]
public ActionResult EditPost(int id) {
...your code here...
}
Retrieve the actual record from the repository/store based on the id passed in.
Use the UpdateModel function to apply the changes to the database record and pass on the record back to your repository layer to store it back in the database.
However, in a real world application, you will probably want separation of concerns and decoupling between your repository and your view layer (ASP.NET MVC.)
If they are part of the model, the method you are using is perfectly fine. You even have a helper method in HtmlHelper.HiddenFor to output the hidden field for you. However, if the values are something like modified date or current user, you'd might be better suited passing those along from your controller to a DTO for your data layer. I'm making some assumptions about what you're doing for data access, though.
The risk with storing data which shouldn't be modified in hidden fields is that it can be modified using a browsers built in/extension developer tools. Upon post these changes will be saved to your database (if that's how you're handling the action).
To protect hidden fields you can use the MVC Security Extensions project https://mvcsecurity.codeplex.com.
Say the field you want to protect is Id...
On you controller post method add:
[ValidateAntiModelInjection("Id")]
Within your view add:
#Html.AntiModelInjectionFor(m => m.Id)
#Html.HiddenFor(m => m.Id)
On post your Id field will be validated.
Create a FormViewModel with only the fields that are relevant. Also the primary key.
The primary key still needs to be on the page me thinks.
Then in the controller you accept the FormViewModel as the argument, you then load the actual model and update, validate fields as required and save the model.
The above is simplistic and you'll have more layers but you should get the idea
I have a controller with two actions:
[AcceptVerbs("GET")]
public ActionResult Add()
{
PrepareViewDataForAddAction();
return View();
}
[AcceptVerbs("POST")]
public ActionResult Add([GigBinderAttribute]Gig gig, FormCollection formCollection)
{
if (ViewData.ModelState.IsValid)
{
GigManager.Save(gig);
return RedirectToAction("Index", gig.ID);
}
PrepareViewDataForAddAction();
return View(gig);
}
As you can see, when the form posts its data, the Add action uses a GigBinder (An implemenation of IModelBinder)
In this binder I have:
if (int.TryParse(bindingContext.HttpContext.Request.Form["StartDate.Hour"], out hour))
{
gig.StartDate.Hour = hour;
}
else
{
bindingContext.ModelState.AddModelError("Doors", "You need to tell us when the doors open");
}
The form contains a text box with id "StartDate.Hour".
As you can see above, the GigBinder tests to see that the user has typed in an integer into the textbox with id "StartDate.Hour". If not, a model error is added to the modelstate using AddModelError.
Since the gigs property gigs.StartDate.Hour is strongly typed, I cannot set its value to, for example, "TEST" if the user has typed this into the forms textbox.
Hence, I cant set the value of gigs.StartDate.Hour since the user has entered a string rather than an integer.
Since the Add Action returns the view and passes the model (return View(gig);) if the modelstate is invalid, when the form is re-displayed with validation mssages, the value "TEST" is not displayed in the textbox. Instead, it will be the default value of gig.StartDate.Hour.
How do I get round this problem? I really stuck!
I think the problem is that your ViewModel does not match closely enough with your View. It's really important in MVC that your ViewModel matches your View as closely as possible.
In your ViewModel you're assuming an integer, but in your View you're using a TextBox to render the property, which will allow any kind of text. There's a mismatch here and the difficulties you are experiencing trying to map them is a symptom of the mismatch.
I think you should either:
1. Change the type of the ViewModel property to string and then do validation in your controller to ensure the string entered is actually a number or:
2. Change the control that the View renders to a control that will only allow a number to be entered via a custom control or Javascript validation (as #Qun Wang recommends)
Personally, I'd recommend option 1. That way the ViewModel is not dependent on the View implementation.
Could you do this in your PrepareViewDataForAddAction method?..
if (!ViewData.ModelState.IsValid)
{
ViewData["StartDate.Hour"] = "Error";
}
The other fields on the form will still populate based on the properties of the Gig object.
I think you need to do some basic client side validation first.
don't allow it to post to the server.
Short: how does modelbinding pass objects from view to controller?
Long:
First, based on the parameters given by the user through a search form, some objects are retrieved from the database.
These objects are given meta data that are visible(but not defining) to the customer (e.g: naming and pricing of the objects differ from region to region).
Later on in the site, the user can click links that should show details of these objects.
Because these meta data are important for displaying, but not defining, I need to get the previously altered object back in the controller.
When I use the default asp.net mvc modelbinding, the .ToString() method is used. This off course doesn't return a relevant string for recreating the complete object.
I would have figured the ISerializable interface would be involved, but this is not so.
How should I go about to get the desired effect? I can't imagine I'm the first one to be faced with this question, so I guess I'm missing something somewhere...
The default model binding takes form parameters by name and matches them up with the properties of the type specified in the argument list. For example, your model has properties "Price" and "Name", then the form would need to contain inputs with ids/names "Price" and "Name" (I suspect it does a case insensitive match). The binder uses reflection to convert the form values associated with these keys into the appropriate type and assigns it to the properties of a newly created object of the type specified by the parameter (again derived by reflection).
You can actually look at (and download) the source for this at http://www.codeplex.com/aspnet, although you'll have to drill down into the MVC source from there. I'd give a link to the DefaultModelBinder source, but the way they are constructed, I believe the link changes as revisions are introduced.
So, to answer your question, you need to have parameters (could be hidden) on your form that correspond to the properties of the object that you want to recreate. When you POST the form (in the view) to the controller, the binder should reconstitute an object of the specified type using the form parameters. If you need to do translation from the values in the form parameter to the object properties, you'll probably need to implement your own custom model binder.
[EDIT] In response to your second post:
Let's say that we want to have a link back to an action that uses a customized object. We can store the customized object in TempData (or the Session if we need it to last more through more than one postback) with a particular key. We can then construct the action link and provide the key of the object as value to the ActionLink in an anonymous class. This will pass back the key as a Request parameter. In our action we can use the key from this parameter to retrieve the object from TempData.
<%= Html.ActionLink( ViewData["CustomObject1",
"Select",
new { TempDataKey = ViewData["CustomObject1_Key"] }
) %>
public ActionResult Select()
{
Entity custObj = null;
string objKey = Request.Params["TempDataKey"];
if (!string.IsNullOrEmpty(objKey))
{
custObj = (Entity)TempData[objKey];
}
... continue processing
}
#tvanfosson
Thanks for your explanation, but what about links? (no forms involved)
Currently the Html.ActionLink(c=>c.Action(parameter), "label") takes objects as parameter. These have to be translated into URL parts. For this, MVC ALWAYS goes to the .ToString() method. I don't want to serialize my object in the ToString method.
Shouldn't I be able to somehow help the framework serialize my object? Say through the ISerialize interface or something?