Unobtrusive validation on multiple models with same property names - asp.net-mvc

I have a view containing multiple partial views bind to different models.
#model MyApp.ViewModels.ParentViewModel
#Html.Partial("_PartialView1", Model.PartialView1)
#Html.Partial("_PartialView2", Model.PartialView2)
Unobtrusive validation works, problem is, the models for the views have properties with the same name.
public class ClassA
{
public int SomeProperty { get; set; }
}
public class ClassB
{
public int SomeProperty { get; set; }
}
public class ParentViewModel
{
public int ClassA PartialView1 { get; set; }
public int ClassB PartialView2 { get; set; }
}
Since both properties have the same name, their html name attributes are the same too.
If ClassA.SomeProperty has an error, same error is shown in ClassB.SomeProperty.
Is there a way to have proper validation without changing the property names?

Do not use partials (which result in duplicate name attributes without the correct prefix, and which cannot be bound to you model when you submit the form).
The correct approach is to use an EditorTemplate. Rename _PartialView1.cshtml to ClassA.cshtml and move it to the /Views/Shared/EditorTemplates folder (ditto for _PartialView2 which needs to renamed to ClassB.cshtml - i.e. to match the name of the class). Then in the main view its
#model MyApp.ViewModels.ParentViewModel
....
#Html.EditorFor(m => m.PartialView1)
#Html.EditorFor(m => m.PartialView2)
Your html will now generate the correct name attributes
<input name="PartialView1.SomeProperty" .... />
<input name="PartialView2.SomeProperty" .... />
and the associated #Html.ValidationMessageFor() will also match up correctly
Side note: You can also solve this using a partial by passing the prefix as additional ViewData as per this answer, but the correct approach is to use an EditorTemplate

Unless they are in separate forms I don't think it is possible without giving them a different name.

Related

How to persist an object in view model on post back [duplicate]

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!

MVC Razor helpers do not render proper ID and Name attributes for fields of interface derived classes

I have a class which looks like this:
public class ApplicationFormModel
{
protected ApplicationFormModel()
{
CurrentStep = ApplicationSteps.PersonalInfo;
PersonalInfoStep = new PersonalInfo();
}
public PersonalInfo PersonalInfoStep { get; set; }
public IEducationalBackground EducationalBackgroundStep { get; set; }
public IAboutYou AboutYouStep { get; set; }
public IOther OtherStep { get; set; }
}
where IEducationalBackground, IAboutYou, and IOther are interfaces. I do not use this class directly, but I use derived classes of this one which upon instantiation create the proper instances of EducationalBackgroundStep, AboutYouStep, and OtherStep.
In my view, I am using Razor Helpers such as
#Html.TextBoxFor(model => (model.EducationalBackgroundStep as ApplicationFormModels.EducationalBackgroundAA).University, new {#class = "form-control", type = "text", autocomplete = "off"})
The field 'University', for example, is NOT part of the Interface and I therefore need the cast to access it. Everything is fine for properties of the interface itself, but those which I need to cast for do not end up having the correct ID and Name properties.
For example, instead of EducationalBackgroundStep_University as ID, I only get University. This causes the form to not include this value when submitting it.
I did not have this issue before when I used a base class instead of an interface, but then I had to include the EducationalBackgroundStep, AboutYouStep, and OtherStep in each derived class (and have it then of the correct derived type), but that is what I wanted to avoid.
Is there any way around this? Thank you very much!
The issue with the ID generation is because you are using casting (x as y) and the TextBoxFor expression handler can't determine what the original model property was (more to the point, it doesn't make sense to use the original model property as you're not using it any more, you're using the cast property)
Example fiddle: https://dotnetfiddle.net/jQOSZA
public class c1
{
public c2 c2 { get; set; }
}
public class c2
{
public string Name { get; set; }
}
public ActionResult View(string page, bool pre = false)
{
var model = new c1 { c2 = new c2 { Name = "xx" } };
return View(model);
}
View
#model HomeController.c1
#Html.TextBoxFor(x=>Model.c2.Name)
#Html.TextBoxFor(x=>(Model.c2 as HomeController.c2).Name)
The first textboxfor has ID c2_Name while the second has just Name
You have two options:
1) use concrete classes rather than interfaces for your viewmodel
2) don't use TextBoxFor and instead use TextBox and specify the ID manually (but then you'll lose refactoring)
#Html.TextBox("c2_Name", (Model.c2 as HomeController.c2).Name)
This will give you the ID you're expecting, but as #StephenMuecke rightly points out, this might not bind correctly when you do the POST - so you may still be stuck... but at least it answers the question.
#freedomn-m explained to me why my code wouldn't work and he put me on the right track to find a solution, so he gets the accepted answer.
The workaround I used is the following - so I now have the following classes:
public class ApplicationFormViewModel {
public PersonalInfo PersonalInfoStep { get; set; }
// constructors which take the other classes and
// initialize these fields in an appropriate manner
public IEducationalBackground EducationalBackgroundStep { get; set; }
public IAboutYou AboutYouStep { get; set; }
public IOther OtherStep { get; set; }
}
// in our case, XX can be one of 3 values, so we have 3 classes
public class ApplicationFormXX {
public PersonalInfo PersonalInfoStep { get; set; }
// constructor which take the ApplicationFormViewModel and
// initialize these fields in an appropriate manner
public EducationalBackgroundXX EducationalBackgroundStep { get; set; }
public AboutYouXX AboutYouStep { get; set; }
public OtherXX OtherStep { get; set; }
}
To the main View I send the ApplicationFormViewModel and for each of the fields, I call a separate Partial View.
The Partial views render the common fields which are present in the Interfaces and then, depending on the type of the object held by the interface, it calls a different partial view which accepts the correct Model.
Example:
In the main View I have (NOTE: The actions return a partial view):
#model Applications.Models.ApplicationFormModels.ApplicationFormViewModel
// CODE, CODE, CODE
#Html.Action("RenderEducationalBackgroundStep", "ApplicationFormsLogic", routeValues: new {model = Model})
In the Partial View of for the EducationalBackgroundStep, I have:
#model ApplicationFormModels.ApplicationFormViewModel
// CODE, CODE, CODE
#{
var educationalBackgroundType = Model.EducationalBackgroundStep.GetType();
if (educationalBackgroundType == typeof(EducationalBackgroundXX))
{
<text>#Html.Partial("~\\Views\\Partials\\ApplicationForm\\Partials\\ApplicationSteps\\EducationalBackground\\_EducationalBackgroundXX.cshtml", new ApplicationFormModels.ApplicationFormModelXX { EducationalBackgroundStep = Model.EducationalBackgroundStep as EducationalBackgroundXX })</text>
}
// OTHER ELSE IF CASES
}
And then, the _EducationalBackgroundXX.cshtml partial view expects a model like this:
#model ApplicationFormModels.ApplicationFormModelXX
This way, no casting is required and everything works fine with the ModelBinder. Again, thank you #freedomn-m for setting me on the right track.
NOTE: In practice I need more fields than the ones presented here (for navigation and some custom logic), so actually all of these classes inherit an abstract base class (this makes it redundant to have the PersonalInfoStep declared in each of the classes, for example, because it can be inherited from the abstract base class). But for the intents and purposes of this method, what's present here suffices.

MVC ignores DataType on overriden model property when DisplayFor is used

I'm wondering if anyone can confirm this behavior or if I've done something wrong.
Normally when you specify the DataType(DataType.MultilineText) attribute, and do something like #Html.DisplayFor(m => m.Body) MVC will use the MultilineText.cshtml in the DisplayTemplates folder. That does not seem to work when the DataType attribute is applied to an overriden property as in the code below. Now if I move the attribute to the property in the abstract class it MVC does use the MultilineText.cshtml display template.
public abstract class PostBase
{
[Required]
public virtual string Body { get; set; }
}
public class MessagePost : PostBase
{
[StringLength(500), DataType(DataType.MultilineText)]
public override string Body
{
get { return base.Body; }
set { base.Body = value; }
}
}
What's the Model in declared in your view? The abstract or child?
It uses reflection to read the attribute based on the model declared so:
#model PostBase
#Html.DisplayFor(m => m.Body)
Will work differently to
#model MessagePost
#Html.DisplayFor(m => m.Body)
the first of these will apply the [Required] only. It's bound to a PostBase model (doesn't know or care what the child class), so when it reflects the PostBase class; this only has [Required] on that property. So it never looks for the MultilineText.cshtml, why would it? It's not got MultilineText on it.
The second one will apply [StringLength(500), DataType(DataType.MultilineText)] and [Required]. The attributes are combined for inherited classes so when it reflects the class it'll see both attributes.
This view should use the template as required. I'm guessing this doesn't work for you though as I'm presuming the inheritance is there for a reason?

Asp.net mvc how to use htmlhelper to generate complex type?

I have a complex type License as a view model.
public class License
{
public string Name { get; set; }
// Other Properties
public List<Function> Functions { get; set; }
}
public class Function
{
public string Name { get; set; }
// Other Properties
public List<Unit> Units { get; set; }
}
public class Unit
{
public string Name { get; set; }
// Other Properties
}
Both the Function's view template and Unit's view template are dynamiclly rendered. So the html looks like this:
<!-- LicenseView -->
#model License
#Html.TextBoxFor(m => m.Name) // this is OK
#for(int i=0; i<Model.Functions.Count; i++)
{
#Html.Partial(Model.Functions[i].Name, Model.Functions[i])
}
and the FunctionView may look like this
#model Function
#Html.TextBoxFor(m => m.Name) // the generated html element's name is just 'Name'
#for(int i=0; i < Model.Units.Count; i++)
{
#Html.Partial(Model.Units[i].Name, Model.Units[i])
}
and this is UnitView
#model Unit
#Html.TextBoxFor(m => m.Name) // the generated html element's name is just 'Name'
So my question is, what should I do the make the Name attribute correct?
Thanks a lot
The only change you need to make in the above code is to use Editor instead of partial view.
So basically all you code will look similar to the following
#model License
#Html.TextBoxFor(m => m.Name)
// Editor will take care of the repetition and u don't need to explicitly pass in the name
// Since the model already have the attribute
#Html.EditorFor(Model.Functions)
Then create your editor template folder, "EditorTemplates", under "Shared" folder and name your view file as "Function"
Do the same for Unit class and you will get what you want.
As #Jack said... you can do this using Editors instead of PartialViews.
BUT... if you really want to use PartialViews, you can do it, but the model to pass should be the top one (License). This way is similar of what David Jessee proposed, but splitting the one view in several.
Pardon me for guessing at the problem, but are you asking for the DisplayName attribute?
It will define how the html helpers display your field lables
public class License
{
[DisplayName("License Name")]
public string Name { get; set; }
// Other Properties
public List<Function> Functions { get; set; }
}
public class Function
{
[DisplayName("Fun Name")]
public string Name { get; set; }
// Other Properties
public List<Unit> Units { get; set; }
}
public class Unit
{
[DisplayName("Unit Name")]
public string Name { get; set; }
// Other Properties
}
be sure to have
using System.ComponentModel;
in your model code.
If you want to be able to create all of the inputs for a complex object graph and have the entire graph be reconstituted by the model binder, the easiest way to approach it is to create a single view or partial view that renders the entire graph:
#for(int i=0;i<Functions.Length;i++){
#for(int j=0;j<Units.Length;j++){
#Html.EditorFor(Functions[i].Length[j].Unit)
}
}
The other option would be to find a way to pass the index of your element to the partial views for each leaf on your object graph.
Granted, a lot of people dont like the idea of rendering a complex model inside of a single view. However, your other option is to make the smaller child views for Units, etc. be dependent on having additional data either injected or provided by the context. 6 of one, half dozen of the other. Just about every time I've done the "academically correct" approach of making exactly one view or partial view for each type in an object graph, I ended up with a whole bunch of views that were not reusable to begin with and the only advantage I got was the ability to say, "Look! Lots of small files.....that are totally dependent on each other...why did I do that?"

Templated Helpers and SelectLists in the View Model don't mix: true?

It seems like there's a disconnect in the advised best practices when it comes to including the data source of a select list in a view model. For example, many best practices blog posts will recommend something along these lines:
ViewModel:
public class InvoiceViewModel
{
[UIHint("SelectInvoiceType")]
public int idInvoiceType { get; set; }
/* snip */
/* I'll use this in the view to generate a SelectList */
public List<invoicetype> InvoiceTypes { get; set; }
}
But then when we get to the editor template, the Model object will be just the int, with no knowledge of the containing view model:
SelectInvoiceType.cshtml
#model int
#{
Layout = "~/Views/Shared/_EditorFormItem.cshtml";
List<SelectListItem> selList = /* nothing to say here, really */;
}
#section DataContent {
#Html.DropDownListFor(m => Model, selList, null)
}
So, unless I'm missing something, these two "best practices" -- templated view helpers and strongly-typed list data in the view model -- just can't be used together. You have to resort to stuffing your list data into the ViewBag. Sound about right?
Sorry to sound incredulous, but I feel like I must be missing something.
You have to resort to stuffing your list data into the ViewBag. Sound about right?
No. To me stuffing things in ViewBag/ViewData doesn't sound right. You should not use int as a model type to an editor template that is supposed to generate a dropdownlist. A dropdownlist consists of two properties: a scalar type to bind the selected value to and a collection to be used to generate the different options in this ddl.
So a more correct way would be the following:
public class MyViewModel
{
[UIHint("SelectInvoiceType")]
public InvoiceTypesViewModel Invoice { get; set; }
... some other properties specific to the view
}
where InvoiceTypesViewModel is a view model and doesn't contain any reference to domain models such as invoicetype in the list in your example:
public class InvoiceTypesViewModel
{
public int SelectedInvoiceType { get; set; }
public SelectList InvoiceTypes { get; set; }
}
then in your main view:
#model MyViewModel
...
#Html.EditorFor(x => x.Invoice)
and the editor template:
#model InvoiceViewModel
#Html.DropDownListFor(m => m.SelectedInvoiceType, Model.InvoiceTypes)

Resources