I have two ViewModels (simplified):
public class ParentViewModel
{
public ParentViewModel
{
Content = new ChildViewModel();
}
public ChildViewModel Content { get; set, }
}
public class ChildViewModel
{
[Required]
public string Name1 { get; set, }
[Required]
public string Name2 { get; set, }
}
And the following controller post action:
[HttpPost]
public ActionResult Create(ParentViewModel viewModel)
{
if (ModelState.IsValid)
{
// process viewModel -> write something into database
return RedirectToAction("Index");
}
return View(viewModel);
}
Now I am sending the following form values in a post request body to the URL corresponding to that action (manually in Fiddler Request Builder):
Content.Name1=X
This works fine, the Name1 property is filled in viewModel.Content, Name2 is null and the model state is invalid because Name2 is required. So, validation fails as expected.
Xontent.Name1=X or Name1=X or whatever so that nothing gets bound to the viewModel
Now viewModel.Content is not null (because I'm instantiating it in the constructor) but all properties Name1 and Name2 are null. This is expected. What I did not expect is that the model state is valid, so it passes the validation (leading to DB exceptions later because there are non-nullable columns).
How can I improve this code so that validation also works in the second case?
I did three experiments:
I have removed the instantiation of Content in the ParentViewModel constructor, then Content is null in the second example above, but validation still passes.
I have added a [Required] attribute to the Content property (but didn't remove the instantiation of Content in the ParentViewModel constructor). This has no effect at all, the described behaviour of the two tests above is the same.
I have added a [Required] attribute to the Content property and removed the instantiation of Content in the ParentViewModel constructor. This seems to work as I want: In the second test Content is null and validation fails due to the [Required] attribute. It would look like this:
public class ParentViewModel
{
[Required]
public ChildViewModel Content { get; set, }
}
public class ChildViewModel
{
[Required]
public string Name1 { get; set, }
[Required]
public string Name2 { get; set, }
}
I would conclude now that instantiating the Content child property in the ParentViewModel constructor is the source of the problem and that the model binder itself must instantiate the child properties (or not, if there are no matching form fields in the request) in order to have a properly working server side validation.
I have child property instantiation in several other view model constructors and didn't notice this problem until now. So, is this generally a bad practice? Are there other ways to solve the problem?
ModelState.IsValid tells you if any model errors have been added to ModelState.
The default model binder will add some errors for basic type conversion issues such as passing a non-number for something which is an "int". You can populate ModelState more fully based on whatever validation system you're using. I would suggest looking into data annotations to validate the ViewModels since it works well.
This syntax may be wrong or old. ModelState.AddModelError("key", Exception)
paraphrased from
What is ModelState.IsValid valid for in ASP.NET MVC in NerdDinner?
The third solution is fine:
public class ParentViewModel
{
[Required]
public ChildViewModel Content { get; set, }
}
public class ChildViewModel
{
[Required]
public string Name1 { get; set, }
[Required]
public string Name2 { get; set, }
}
I'm using it now at several places and didn't notice any problems.
Related
I have a ViewModel that has a complex object as one of its members. The complex object has 4 properties (all strings). I'm trying to create a re-usable partial view where I can pass in the complex object and have it generate the html with html helpers for its properties. That's all working great. However, when I submit the form, the model binder isn't mapping the values back to the ViewModel's member so I don't get anything back on the server side. How can I read the values a user types into the html helpers for the complex object.
ViewModel
public class MyViewModel
{
public string SomeProperty { get; set; }
public MyComplexModel ComplexModel { get; set; }
}
MyComplexModel
public class MyComplexModel
{
public int id { get; set; }
public string Name { get; set; }
public string Address { get; set; }
....
}
Controller
public class MyController : Controller
{
public ActionResult Index()
{
MyViewModel model = new MyViewModel();
model.ComplexModel = new MyComplexModel();
model.ComplexModel.id = 15;
return View(model);
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
// model here never has my nested model populated in the partial view
return View(model);
}
}
View
#using(Html.BeginForm("Index", "MyController", FormMethod.Post))
{
....
#Html.Partial("MyPartialView", Model.ComplexModel)
}
Partial View
#model my.path.to.namespace.MyComplexModel
#Html.TextBoxFor(m => m.Name)
...
how can I bind this data on form submission so that the parent model contains the data entered on the web form from the partial view?
thanks
EDIT: I've figured out that I need to prepend "ComplexModel." to all of my control's names in the partial view (textboxes) so that it maps to the nested object, but I can't pass the ViewModel type to the partial view to get that extra layer because it needs to be generic to accept several ViewModel types. I could just rewrite the name attribute with javascript, but that seems overly ghetto to me. How else can I do this?
EDIT 2: I can statically set the name attribute with new { Name="ComplexModel.Name" } so I think I'm in business unless someone has a better method?
You can pass the prefix to the partial using
#Html.Partial("MyPartialView", Model.ComplexModel,
new ViewDataDictionary { TemplateInfo = new TemplateInfo { HtmlFieldPrefix = "ComplexModel" }})
which will perpend the prefix to you controls name attribute so that <input name="Name" ../> will become <input name="ComplexModel.Name" ../> and correctly bind to typeof MyViewModel on post back
Edit
To make it a little easier, you can encapsulate this in a html helper
public static MvcHtmlString PartialFor<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression, string partialViewName)
{
string name = ExpressionHelper.GetExpressionText(expression);
object model = ModelMetadata.FromLambdaExpression(expression, helper.ViewData).Model;
var viewData = new ViewDataDictionary(helper.ViewData)
{
TemplateInfo = new System.Web.Mvc.TemplateInfo
{
HtmlFieldPrefix = string.IsNullOrEmpty(helper.ViewData.TemplateInfo.HtmlFieldPrefix) ?
name : $"{helper.ViewData.TemplateInfo.HtmlFieldPrefix}.{name}"
}
};
return helper.Partial(partialViewName, model, viewData);
}
and use it as
#Html.PartialFor(m => m.ComplexModel, "MyPartialView")
If you use tag helpers, the partial tag helper accepts a for attribute, which does what you expect.
<partial name="MyPartialView" for="ComplexModel" />
Using the for attribute, rather than the typical model attribute, will cause all of the form fields within the partial to be named with the ComplexModel. prefix.
You can try passing the ViewModel to the partial.
#model my.path.to.namespace.MyViewModel
#Html.TextBoxFor(m => m.ComplexModel.Name)
Edit
You can create a base model and push the complex model in there and pass the based model to the partial.
public class MyViewModel :BaseModel
{
public string SomeProperty { get; set; }
}
public class MyViewModel2 :BaseModel
{
public string SomeProperty2 { get; set; }
}
public class BaseModel
{
public MyComplexModel ComplexModel { get; set; }
}
public class MyComplexModel
{
public int id { get; set; }
public string Name { get; set; }
...
}
Then your partial will be like below :
#model my.path.to.namespace.BaseModel
#Html.TextBoxFor(m => m.ComplexModel.Name)
If this is not an acceptable solution, you may have to think in terms of overriding the model binder. You can read about that here.
I came across the same situation and with the help of such informative posts changed my partial code to have prefix on generated in input elements generated by partial view
I have used Html.partial helper giving partialview name and object of ModelType and an instance of ViewDataDictionary object with Html Field Prefix to constructor of Html.partial.
This results in GET request of "xyz url" of "Main view" and rendering partial view inside it with input elements generated with prefix e.g. earlier Name="Title" now becomes Name="MySubType.Title" in respective HTML element and same for rest of the form input elements.
The problem occurred when POST request is made to "xyz url", expecting the Form which is filled in gets saved in to my database. But the MVC Modelbinder didn't bind my POSTed model data with form values filled in and also ModelState is also lost. The model in viewdata was also coming to null.
Finally I tried to update model data in Posted form using TryUppdateModel method which takes model instance and html prefix which was passed earlier to partial view,and can see now model is bound with values and model state is also present.
Please let me know if this approach is fine or bit diversified!
I'm learning asp.net mvc and wonder when we need to use BindAttribute.
The first case (using Bind):
Model:
public class Book
{
public string Id { get; set; }
public string Name { get; set; }
public string Author { get; set; }
}
Controller:
public IActionResult Create([Bind(nameof(Book.Name), nameof(Book.Author))] Book model)
{
return Ok();
}
The book Id would be generated on server side. So, client side has nothing to do with it, every actions try to change/make the id is prevented.
The second case (not using Bind):
Model:
public class BookViewModel
{
public string Name { get; set; }
public string Author { get; set; }
}
Controller:
public IActionResult Create(BookViewModel model)
{
return Ok();
}
Because the second model doesn't contain Id property, we don't need to prevent from creating or changing.
I prefer the second. It's easy to manage model.
Is there a case we must use Bind attribute?
We use bind when we want that some properties of complex property are ignored when received on server. It could be for safety or other reasons.
When this action is executed the MVC model binder will use the request parameters to populate the user parameter's properties, as you may already know. However, the Bind attribute tells the model binder to only populate properties with names specified.
So in this case only the Username, FullName and Email properties will be populated. All others will be ignored.
See here for more details: http://ittecture.wordpress.com/2009/05/01/tip-of-the-day-199-asp-net-mvc-defining-model-binding-explicitly/
If you have situation when you only have to ignore one parametar from binding you could use Exclude property:
[Exclude]
public Entity Name {get; set;}
Bind is used to increase security and unauthorized data to be posted on server . In your model class , suppose you have content property also. if the content property is not needed in the future. Then it would be difficult for you to remove all the occurrences of that property. Here you can use bind property like this
[Bind(exclude="content")]
or you can bind only selected properties to be posted on server by including the properties like this
public ActionResult create([Bind(Include = "Name,Author")] Modelclass modelclass)
{
//Do something here
}
You can learn more about it here
Second approach is more suitable instead writing all the properties and bind them but there are some situations where you must bind user like you have a roles property or IsAdmin property in your model then you might not want that user somehow posts the IsAdmin or roles properties to the server . That's where you can use Bind attribute
Obviously there are a number of ways to do this, but I thought I'd ask for a little feedback on benefits and drawbacks of the approaches.
First of all, the NerdDinner tutorial's Edit Action is in the form (say Form A):
[HttpPost]
public ActionResult Edit(int id, FormCollection collection) {
It seems to me that if you shape your ViewModels well to match your views, that the approach Form B:
[HttpPost]
public ActionResult Edit(MyViewModel mvm) {
just seems like a better, cleaner approach. I then just map the VM properties to the Model properties and save. However, if this ViewModel has other entities embedded in it that are initialized via the constructor (for example in the nerddinner tutorial), then this edit action fails if there is no default constructor and you'd have to use the first approach.
So, the first question is do you agree that generally Form B is usually better? Are there drawbacks?
Secondly, it seems then if Form B is used, the decorator type validation would need to be in the ViewModel. Are there advantages of embedding entities in ViewModels and keeping the validation at the entity level only?
This is a pretty general SO question.
the first question is do you agree that generally Form B is usually better?
The only time I do not use Form B is when I upload files. Otherwise, I don't believe anyone should ever need to use Form A. The reason I think people use Form A is a lack of understanding of the abilities of ASP.Net's version of MVC.
Secondly, it seems then if Form B is used, the decorator type validation would need to be in the ViewModel.
Sort of / it Depends. I'll give you an example:
public IValidateUserName
{
[Required]
string UserName { get; set; }
}
public UserModel
{
string UserName { get; set; }
}
[MetadataType(typeof(IValidateUserName))]
public UserValiationModel : UserModel
{
}
The validation decorator is in an interface. I'm using the MetadataType on a derived class to validate the derived type. I personally like this practice because it allows reusable validation and the MetadataType/Validation is NOT part of the ASP.NET core functionality, so it can be used outside of ASP.Net (MVC) application.
Are there advantages of embedding entities in ViewModels ..
Yes, I do my absolute best to never pass a basic model to the view. This is an example of what I don't do:
public class person { public Color FavoriteColor { get; set; } }
ActionResult Details()
{
Person model = new Person();
return this.View(model);
}
What happens when you want to pass more data to your view (for partials or layout data)? That information is not Person relevant most of the time so adding it to the Person model makes no sense. Instead, my models typically look like:
public class DetailsPersonViewModel()
{
public Person Person { get; set; }
}
public ActionResult Details()
{
DetailsPersonViewModel model = new DetailsPersonViewModel();
model.Person = new Person();
return this.View(model);
}
Now I can add required data the DetailsPersonViewModel that view needs beyond what a Person knows. For example, lets say this is going to display a for with all the colors for the Person to pick a favorite. All the possible colors aren't part of a person and shouldn't be part of the person Model, so I'd add them to the DetailPersonViewModel.
public class DetailsPersonViewModel()
{
public Person Person { get; set; }
public IEnumerable<Color> Colors { get; set; }
}
.. and keeping the validation at the entity level only?
System.ComponentModel.DataAnnotations weren't designed to validate properties' properties, so doing something like:
public class DetailsPersonViewModel()
{
[Required(property="FavoriteColor")]
public Person Person { get; set; }
}
Doesn't exist and doesn't make sense. Why ViewModel shouldn't contain the validation for the entity that needs validation.
this edit action fails if there is no default constructor and you'd have to use the first approach.
Correct, but why would a ViewModel or a Entity in a ViewModel not have a parameterless constructor? Sounds like a bad design and even if there is some requirement for this, it's easily solved by ModelBinding. Here's an example:
// Lets say that this person class requires
// a Guid for a constructor for some reason
public class Person
{
public Person(Guid id){ }
public FirstName { get; set; }
}
public class PersonEditViewModel
{
public Person Person { get; set; }
}
public ActionResult Edit()
{
PersonEditViewModel model = new PersonEditViewModel();
model.Person = new Person(guidFromSomeWhere);
return this.View(PersonEditViewModel);
}
//View
#Html.EditFor(m => m.Person.FirstName)
//Generated Html
<input type="Text" name="Person.FirstName" />
Now we have a form that a user can enter a new first name. How do we get back the values in this constructor? Simple, the ModelBinder does NOT care what model it is binding to, it just binds HTTP values to matching class properties.
[MetadataType(typeof(IPersonValidation))]
public class UpdatePerson
{
public FirstName { get; set; }
}
public class PersonUpdateViewModel
{
public UpdatePerson Person { get; set; }
}
[HttpPost]
public ActionResult Edit(PersonUpdateViewModel model)
{
// the model contains a .Person with a .FirstName of the input Text box
// the ModelBinder is simply populating the parameter with the values
// pass via Query, Forms, etc
// Validate Model
// AutoMap it or or whatever
// return a view
}
I have not yet taken a look at the NerDinner project, however, I generally try to avoid having a ViewModel in the POST of an action and instead, only have the elements of the "form" submitted.
For instance, if the ViewModel has a Dictionary that is used in some kind of dropdown, the entire dropdown will not be submitted, only the selected value.
My general approach is:
[HttpGet]
public ActionResult Edit(int id)
{
var form = _service.GetForm(id);
var pageViewModel = BuildViewModel(form);
return View(pageViewModel);
}
[HttpPost]
public ActionResult Edit(int id, MyCustomForm form)
{
var isSuccess = _service.ProcessForm(id);
if(isSuccess){
//redirect
}
//There was an error. Show the form again, but preserve the input values
var pageViewModel = BuildViewModel(form);
return View(pageViewModel);
}
private MyViewModel BuildViewModel(MyCustomForm form)
{
var viewModel = new MyViewModel();
viewModel.Form = form;
viewModel.StateList = _service.GetStateList();
return viewModel;
}
I'm working with ASP.NET MVC 4, but I on't think that matters for the purpose of this question.
I have a relatively complex model for my edit view. Like this:
public class Recipe_model
{
public string Name { get; set; }
public List<Recipe_Ingredient_model> Ingredients { get; set; }
}
where Ingredients is
public class Recipe_Ingredient_model
{
public int RecipeID { get; set; }
public int? UnitID { get; set; }
public double? Quantity { get; set; }
public Ingredient_model Ingredient { get; set; }
}
which itself contains the Ingredient model.
When I make a form for this, the built-in Html.EditorFor() doesn't work for anything past the properties of the Recipe_model, so I'm using partial views to display the editor for each of the sub-models.
That works fine as far the interface goes, but when I submit the form to the controller and try to bind to the Recipe_model automatically using
[HttpPost]
public ActionResult Edit(Recipe_model model)
{
return View(model);
}
it fails because the ids of the input elements in the partial views do not conform to the correct pattern (I think ParentModel_Property).
Short from hard-coding the ids in the partial view or binding manually from the FormCollection in the controller, is there some way to get the correct ids generated in the partial view so that the model will bind automatically on submit?
This is common problem. Instead of simple partials, use EditorTemplates (special folder for models) and binding will work automatically.
For example look at this question: Updating multiple items within same view
in addition to the answer given by #WebDeveloper
you can also try and create a custom model binder though a little more complex but will add to the ease of posting and binding form value to the objects in long run
have a look here http://patrickdesjardins.com/blog/asp-net-mvc-model-binding
you will have to manually take all the form values and bind them to the model once and then you will be able to use the #HtmlFrom methods on the razor to do anything and you will get all the value inside the objects inside the action methods as you like.
I have an experience in work with ASP.NET forms, but new to MVC.
How can I get data from shared views on postback?
In ASP.NET Forms I can write something like this:
ASP.NET Forms:
Model code:
public class MyModelItem
{
// Just TextBox is enough for editing this
public string SimpleProperty { get; set; }
// For this property separate NestedItemEditor.ascx is required
public MyModelNestedItem ComplexProperty { get; set; }
}
public class MyModelNestedItem
{
public string FirstProperty { get; set; }
public string SecondProperty { get; set; }
}
Behavior:
Control for editing MyModelNestedItem is separate ASCX control NestedItemEditor.ascx
This is just for example, MyModelNestedItem can be much more complex, I just want to give idea what I mean.
Now when I showing this item for editing, I'm showing one asp:TextBox and one NestedItemEditor.ascx. On page postback I'm gathering data from both and that's it.
Problem with MVC:
When I'm trying to implement this scenario with MVC, I'm using customized EditorFor (through using UIHint and creating shared view). So this shared view Views\Shared\EditorTemplates\MyModelNestedItem.cshtml can now display data that is already in MyModelNestedItem property but I have no idea how to make it return new entered data.
When parent controller recieves a post request, data seems to be in Request.Form, but which is civilized way to reach it? Sure, the best solution will be if data will fetch automatically into the MyModelItem.ComplexProperty.
The action which is called on post needs to be something like:
[HttpPost]
public ActionResult Index(MyViewModel mdl)
Then all the properties of the model which have input controls (or hidden inputs) on the form will have the data which was entered on the form (or passed to it or modified by javascript, in the case of hidden inputs).
This assumes that MyViewModel is the model referenced in your view.
Writing an ActionResult method in the controller with the complex type simply worked for me:
public class Topic
{
public Topic()
{
}
public DetailsClass Details
{
get;
set;
}
}
public class DetailsClass
{
public string TopicDetails
{
get;
set;
}
}
The view:
#modelTopic
#using (Html.BeginForm("Submit","Default"))
{
#Html.EditorFor(m=>m.Details)
#:<input type="submit" />
}
The controller:
public ActionResult Index()
{
Topic topic = new Topic();
return View( topic);
}
public ActionResult Submit(Topic t)
{
return View(t);
}
When submited, the Topic t contains the value i ented within the editor (Assuming You have a custom editor for the complex type, DetailsClass in my sample)