How to validate against a Model's metadata through a custom ViewModel? - asp.net-mvc

I'm working on a webapp using ASP MVC. I'm building a page to edit a user's data (model USER, view ModifyUser).
I have a model with validations in this manner:
[MetadataType(typeof(USERS_Metadata))]
public partial class USER
{
public class USERS_Metadata
{
[Required(ErrorMessage = "FALTA NOMBRE")]
[StringLength(30, ErrorMessage = "Nombre entre 3 y 30 caracteres", MinimumLength = 3)]
[RegularExpression(#"^[a-zA-Z''-'\s]{1,40}$", ErrorMessage = "Error en el formato del nombre.")]
public string NAME { get; set; }
I then use a view that automagically validates user inputs:
<div class="editor-label">
<%: Html.LabelFor(model => model.SURNAME) %>
</div>
<div class="editor-field">
<%: Html.EditorFor(model => model.SURNAME) %>
<%: Html.ValidationMessageFor(model => model.SURNAME) %>
</div>
The problem is my view is also gonna need to access some other entities with their own models, like USERCATEGORY, which makes using an strongly typed view a bit more uncomfortable.
Aditionally, and may be even more important, I don't want my view to have to deal with, and even knowing about, properties such as the user's session ID, which currently I handle like this (and i hate it):
<%: Html.HiddenFor(model => model.SESSIONID) %>
Unless I'm utterly mistaken, the most sane option is to build a custom ViewModel. I decided to build my ModifyUserViewModel to match those fields in USER I'm gonna need in my view, and add a few fields from the other models... But I have no idea of how to propagate the metadata in USER, that I use for field validation, to the new ViewModel. This metadata is automatically built from the database, and copypasting it would make me feel dead inside, even if it works.
What is the canonical, most maintenable, easiest way to validate from the View like I am currently doing, but with a ViewModel?

Try to build your view model as an aggregate of several domain models:
public class MyViewModel {
public USER User { get; set; }
public USERCATETORY Category { get; set; }
}
See a great article here.

Related

ASP.NET MVC 5 model validation for non-nullable types (Int32)

I'm working on an ASP.NET MVC 5 application and the project owner is concerned about "under-posting" issues caused by validating non-nullable types (as mentioned in http://bradwilson.typepad.com/blog/2010/01/input-validation-vs-model-validation-in-aspnet-mvc.html and http://www.asp.net/web-api/overview/formats-and-model-binding/model-validation-in-aspnet-web-api).
I created a test case to replicate this issue in ASP.NET MVC 5 but without luck.
Model:
public class ContactModel
{
[Required]
public Int32 data1 { get; set; }
public Int32 data2 { get; set; }
}
View:
<div class="form-group">
#Html.LabelFor(model => model.data1)
<div>
#Html.EditorFor(model => model.data1)
</div>
</div>
<div>
#Html.LabelFor(model => model.data2)
<div>
#Html.EditorFor(model => model.data2)
</div>
</div>
Controller:
public ActionResult Index(Models.ContactModel contact)
{
if (ModelState.IsValid)
{
Response.Write("modelstate is valid<br>");
return View();
}
else
{
Response.Write("modelstate is invalid<br>");
return View();
}
}
It seems that when data1 and data2 are null in the post, their values in the model (contact) will be 0. However, ModelState.IsValid will also be false (instead of true as shown in the two articles).
What I have:
What the second article showed:
I couldn't find any information regarding changes on how model validation works in ASP.NET MVC, so I'm guessing I did something wrong with my test case. Any thought and suggestion are appreciated.
The reason your ModelState is false is because the post is providing form values from each property in your model. Essentially the Model binding system is checking the validity of both data1 and data2 fields as you have #Html.EditorFor helpers explicitly written for both properties in your view (so no underposting is actually going on).
I did successfully replicate the under-posting concerns from the articles. Simply remove one of the EditorFor helpers in your view, so you're actually underposting. With both helpers present, there's no underposting going on. So the view looks like this now (note I added the validation helper for both properties to get feedback in the view on what's going on):
View:
<div class="form-group">
#Html.LabelFor(model => model.data1)
<div>
#Html.EditorFor(model => model.data1)
#Html.ValidationMessageFor(model => model.data1)
#Html.ValidationMessageFor(model => model.data2)
</div>
</div>
Make sure to leave the #Html.EditorFor helper completely off for the data2 property. Now fill in zero in the form field (you'll only one form field in the view of course now), and post to your action.
ModelState will come back as true in this scenario, even though only one form field is being posted. Not a good result if someone does underpost! So here's the (slightly modified) original model class where underposting issues will occur in the case a form field is left off of your form (note the Required attributes don't make any difference in this situation as both properties are value types):
//You could add the Required attribute or not, doesn't matter at this point.
//The concern here is that the Modelstate will still come back as Valid
//in the case of a form field being left off of your form (or someone underposts).
//So to replicate underposting issues, make sure to comment or delete
//at least one Html.EditorFor helper in the view.
//[Required] Underposting will occur regardless if this is marked required or not,
//so be careful if someone does underpost your form.
public Int32 data1 { get; set; }
//[Required]
public Int32 data2 { get; set; }
Now the solution if you want to solve the underposting issue:
Simply mark both properties as required and make them nullable as mentioned in the articles you provided, like so:
[Required]
public Int32? data1 { get; set; }
[Required]
public Int32? data2 { get; set; }
Now when the view is posted with a missing #Html.EditorFor helper or a missing form field, the ModelState Validation will come back as false, and you're protected from underposting issues.

Can a Razor view have more than one model without using a ViewModel object?

#model MyMVC.Models.MyMVC.MyModel
#{
ViewBag.Title = "Index";
}
The reason why I ask this question is in MVC we can have more than 1 form, so I would like to have a model for each form. But when I was trying to add another model at the top of the view, it displays an error.
I know we can use ViewModel which has model1 and model2 inside, but it's not the question I am asking.
I just simply want to know is there any way that we can put in 2 models into 1 view.
Update:
For example, I want to have 2 forms in my view, so for each form I'd like to have a separated model.
Update 2
Thank you all for your replies. I now know that MVC only supports one single model on one view. Do you guys consider this a disadvantage of MVC? Why or Why not?(No one has answered it yet.... why?)
Your answers are similar, so I don't even know which one should be set as an answer.
You should use some prtialviews for other models. You have your main view with main model. Inside of that view, you could have some partialviews with their models and their validations.
--------------------------------------Edit:
As others said it is better to use View Models and combine the models with each others. But as you wanted I created a sample project for you on my Github account:
https://github.com/bahman616/ASPNET_MVC_multiple_models_in_a_view_with_partialview.git
Here is the simplified code of that project:
Here are two models:
public partial class Person
{
public int ID { get; set; }
[Required]
public string Name { get; set; }
}
public partial class Company
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
}
I want to have both create views in one view, so this is the parent view:
#model ASP_NET_MVC_Multiple_Models_In_A_View.Models.Person
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#using (Html.BeginForm("Create","Person")) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Person</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#{Html.RenderAction("_CompanyCreate", "Company");}
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
And this is the partial view:
#model ASP_NET_MVC_Multiple_Models_In_A_View.Models.Company
<script src="~/Scripts/jquery-1.7.1.min.js"></script>
<script src="~/Scripts/jquery.validate.min.js"></script>
<script src="~/Scripts/jquery.validate.unobtrusive.min.js"></script>
#using (Html.BeginForm("_CompanyCreate","Company")) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Company</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
Simple answer:
No, you can't have two models.
Details
There is only one way to declare a Model to be used by a view:
#model [namespace].[path].[ViewModelName]
There is no way to specify multiple of these above.
In addition to that, you can only send one object to a view from the Controller, which would be the model:
public ActionResult WhateverAction()
{
var model = new MyViewModel();
return View(model);
}
Theoretically you cant do it because it binds one model to one view. But you can create somethink like a model helper which combines two other models to one
public class model1
{
string var1 {get; set;}
string var2 {get; set;}
}
public class model2
{
string var3 {get; set;}
string var4 {get; set;}
}
public class modelofBoth
{
model1 mod1 {get; set;}
model2 mod2 {get; set;}
}
If you cannot/don't want to modify the model passed to the view then you could pass the additional object in via the ViewBag object. It feels like a hack to me but it works:
Controller:
ViewBag["Model2"] = yourObject
View:
#{var Model2 = ViewBag["Model2"] as yourObjectType;}
Technically, you could write your own view engine (derived from the RazorViewEngine?) using a custom WebViewPage and have it do whatever you want. The view derives from WebViewPage<T> where T is the type of the model for the page. To support multiple models, the view base class would need to have multiple generic types. Using the baked in RazorViewEngine, however, you're limited to a single model type per view and much of the controller framework presumes a single model per view so you'd have to rewrite that, too. There doesn't seem to be much point in that, however, as you could simply have your models be properties of the (single) view model, then use partial views for each of the forms providing the property as the model for the partial.
For more information see http://haacked.com/archive/2011/02/21/changing-base-type-of-a-razor-view.aspx
Answer is NO. Thats why ViewModel pattern Exists. because the razor engine is totally relied on the model you passed, the model is used for ModelBinding for the basic CRUD Operations.
You can easily have 1 model per form. For each form, you just have to do something like this:
var model = new MyModel();
#Html.TextBoxFor(m => model.Title)
#Html.ValidationMessageFor(m => model.Title)
The validation works like a charm.
This way, you can keep your model to display information as usual and have 1 model per form to submit data (for example newsletter subscription)
if your models are similar,
Make a new model contains references to all models you want, and use it.
if models are not similar, you can fill new model with similar fields you need from all your models.
but if you wants use totally different models, you should use Partial views.

Can't figure out why model is null on postback?

I'm new to ASP.NET MVC and I'm trying to create a very simple blog type site as a means of learning how everything works. But, I'm having a problem when posting from a comment form to a model which is null and I can't tell why.
On a blog post page, I have an "add comment" link which calls some JQuery to render a partial view that is strongly typed to the CommentModel. The link passes in the ID of the blog post as well and the partial is coded like:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Blog.Models.CommentModel>" %>
<% using (Html.BeginForm())
{ %>
<%: Html.HiddenFor(x => x.Post.ID) %>
<%: Html.HiddenFor(x => x.CommentID) %>
<%: Html.TextBoxFor(x => x.Name) %><br />
<%: Html.TextBoxFor(x => x.Email) %><br />
<%: Html.TextBoxFor(x => x.Website) %><br />
<%: Html.TextAreaFor(x => x.Comment) %><br />
<input type="submit" value="submit" />
<% } %>
The CommentsModel is simple and looks like this (I haven't applied any validation or anything yet):
public class CommentModel
{
public BlogPost Post { get; set; }
public int CommentID { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public string Website { get; set; }
public string Comment { get; set; }
}
This is then supposed to post to a simple controller action which will add the comment to the database and return the user to the page. For the sake of simplicity, I've stripped out most of the code but it looks similar to:
[HttpPost]
public ActionResult CommentForm(CommentModel model)
{
if (ModelState.IsValid)
{
}
else
{
}
}
Everything works as expected, except that when posting the comment form, the comment model is null. I can't figure out why this is null. When I view the source of the rendered partial view, I can see that the "Post.ID" is populated with the correct ID, but this is lost when the form is submitted.
Am I missing something obvious here? I've set up forms similar to this in the past and it's worked fine, I can't understand why its not now. Thanks in advance.
Later Edit:
I had typed the code incorrectly and changed the public ActionResult CommentForm(CommentModel model) from public ActionResult CommentForm(CommentModel comment) which was causing the problem.
Thanks for the help.
Similar kind of question has been answered yesterday. Check out : MVC3 - Insert using ViewModel - Object reference not set to an instance of an object
The problem I can see is , when the form is posted, the Post.ID and CommentID are passed, whereas your action is expecting a full blown object of type "CommentModel". The model binder is unable to map the post data, into the corresponding model object.
Add:
public int PostID {get; set;}
...to your model, and populate that in your controller as a hidden input. The Post object is not going to parse easily.

Client Side Validation fails when moving to viewmodel

This seems to a common question for lots of reasons that do not seem to apply to this situation. I have create page using ASP.NET MVC 2 and I was using a strongly typed view to a class generated from DataEnities framework.
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<MVC_EDI.Models.wysCustomerEndPoint>"" %>
I made validation class that I bound back to the the data class.
[MetadataType(typeof(EndPointValidation))]
public partial class wysCustomerEndPoint
{
}
[Bind()]
public class EndPointValidation
{
[Required(ErrorMessage = "Please enter the end point name")]
public string CustName { get; set; }
And I was able to use client side validation on my create page. I had a requirement to add a dropdown list box on the create page, so I switched my view to use a viewmodel instead of the data class I was using.
public class CreateEditCustomerEndPointsViewModel
{
public wysCustomerEndPoint CustomerEndPoint {get; set;}
public List<SelectListItem> DefaultLocationList { get; set; }
}
and here is the view header using the new viewmodel.
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<MVC_EDI.ViewModles.CreateEditCustomerEndPointsViewModel>" %>
But now when this view gets loaded I am getting an error that my formElement is null when it tries to set a value ? I error out here inteh MicrosofyMvcValidation.js file and the formElement array is null.
formElement['__MVC_FormValidation'] = this
I suspect I need to add some sort of data annotation or attribute to either my view model or something like that. But I am not sure where? And surprisingly it seems to work out just fine in FireFox 5 but bombs in IE9?
Edit: thanks for the reply. Yes I believe I am instantiating the object before adding to the ViewModel and using the Html.Helper objects? Here is the code.
wysCustomerEndPoint ep = new wysCustomerEndPoint();
ep.BuyerID = id;
var viewModel = new CreateEditCustomerEndPointsViewModel()
{
CustomerEndPoint = ep
};
return View(viewModel);
and in the view
<div class="editor-label">
<%: Html.Label("Name") %>
</div>
<div class="editor-field">
<%: Html.TextBoxFor(model => model.CustomerEndPoint.CustName) %>
<%: Html.ValidationMessageFor(model => model.CustomerEndPoint.CustName) %>
</div>
cheers
bob
You're maybe doing this, but without seeing the relevant bits of code I don't want to assume. So, make sure you are instantiating the wysCustomerEndPoint object and sending to your view from the Controller method. Also that you are using the Html Helpers for the input elements that you are validating on. Eg.
Html.TextboxFor(model => model.wysCustomerEndPoint.CustName)

MVC 3 - Scaffolding drop down list

I am playing with the Scaffolding that is in Asp.net mvc
I have a property on my view model for countries
public IEnumerable<SelectListItem> Countries { get; set; }
Yet, when I create a view and specify the viewmodel it doesn't scaffold a drop down list as I expected it to. In fact it get completely ignored.
I have compiled the project before doing it
I also tried adding a property like this
public int CountryId { get; set; }
As this article suggested there is a convention at work
http://blog.stevensanderson.com/2011/01/28/mvcscaffolding-one-to-many-relationships/
I am using the Add view option you have when right clicking in a controller action method
Any ideas what is wrong?
In my current project, i faced this problem and couldn't find a quick way to scaffold the Dropdown list of a one-many relation inside one of my Entities.
What I ended up doing was like the following:
1- Create the normal AddView=>Create way.
2- If i had a ID property in my model class, the defaul;t template will generate something like this to represent this property in my view:
<div class="editor-label">
#Html.LabelFor(model => model.CityID)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.CityID)
#Html.ValidationMessageFor(model => model.CityID)
</div>
3- Now I need to replace this default template with a working one, so I wrote this code in the CREATE method:
IEnumerable<SelectListItem> cityItems = dataContext.Cities.AsEnumerable().Select(c => new SelectListItem()
{
Text = c.Name,
Value = c.ID.ToString(),
Selected = false,
});
SelectList cityList = new SelectList(cityItems, "Value", "Text");
ViewBag.CityList = cityList;
this will fetch the Cities table and create a Selectlist that i can pass to my view and work with it to provide the DrobDown with it's items.
4- replace the default code in my View by one like the following:
<div class="Post-label">
<div class="editor-label">
#Html.LabelFor(model => model.CityID)
</div>
<div class="editor-field">
#Html.DropDownListFor(m => m.CityID, ViewData["CityList"] as SelectList)
#Html.ValidationMessageFor(model => model.CityID)
</div>
</div>
The reason I've used ViewData["CityList"] instead of ViewBag.CityList is that this one worked but the other not.
5- now my view is working find and is fetching the City data just like what I expected, and using the same model inside my Edit view resulted in a working one too.
Give it a try and let me know what happened, Thanks.
I have noticed that for a given model, the "Create" scaffold-generated code when creating a new controller is different than if you right-click in an existing controller and say "Add View" and choose the "Create" scaffolding template. In the first case, given you have the correct properties on the child class
public Country Country {get;set;}
public int CountryID {get;set;}
then this case (adding controller with MVC scaffolding read/write and appropriate Model class) WILL generate a #Html.DropDownList for the parent relationship, whereas right-clicking within the controller Create method will not scaffold the drop-down but will instead create an #Html.EditorFor for the relationship.
So the answer if you want scaffolding code to generate the drop-down may be to delete and re-create your controller if possible, otherwise manually add in the appropriate code.
In order to have the option to choose a country with a dropdown the property in your Model should be:
public Country Country{ get; set; } Navigation property used by EF, doesn't involve the database
with
public Country CountryId{ get; set; }
Create the foreign key on the Person table
Each instance/record of a person is associated with a country: the relation is defined with the navigation property via code and with the CountryID for the database.
The scaffholding template will then generate the edit/create methods and views using :
ViewBag.PossibleCountries
Here's a similar scenario.

Resources