I have the following setup in my model:
namespace QuickTest.Models
{
public class Person
{
[Required]
[Display(Name = "Full name")]
public string FullName { get; set; }
[Display(Name = "Address Line 1")]
public virtual string Address1 { get; set; }
}
public class Sender : Person
{
[Required]
public override string Address1 { get; set; }
}
public class Receiver : Person
{
}
}
and in my view:
@model QuickTest.Models.Person
@{
ViewBag.Title = "Edit";
}
<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
@using (Html.BeginForm()) {
<fieldset>
<legend>Person</legend>
<div class="editor-label">
@Html.LabelFor(model => model.FullName)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.FullName)
@Html.ValidationMessageFor(model => model.FullName)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.Address1)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Address1)
@Html.ValidationMessageFor(model => model.Address1)
</div>
<div class="errors">
@Html.ValidationSummary(true)
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
Client-side validation is enabled. However, if I send an object of type Sender to the View, client-side validation does not detect that the Address1 field is required. Is there any way of making the client validation work in this scenario?
PS: I discovered that client validation works if I use the following to display the Address1 field in the view:
<div class="editor-field">
@Html.Editor("Address1", Model.Address1)
@Html.ValidationMessageFor(model => model.Address1)
</div>
You can customize the validators and the metadata to come from your concrete class, but the solution has several moving parts, including two custom metadata providers.
First, create a custom
Attribute
to decorate each property of the base class. This is necessary as a flag for our custom providers, to indicate when further analysis is needed. This is the attribute:Next, create a custom
ModelMetadataProvider
inheriting fromDataAnnotationsModelMetadataProvider
:Then, create a custom
ModelValidatorProvider
inheriting fromDataAnnotationsModelValidatorProvider
:After that, register both custom providers in
Application_Start
in Global.asax.cs:Now, change your models like so:
Note that the base class has a new property,
ConcreteType
. This will be used to indicate which inheriting class has instantiated this base class. Whenever an inheriting class has metadata which overrides the metadata in the base class, the inheriting class' constructor should set the base class ConcreteType property.Now, even though your view uses the base class, the attributes specific to any concrete inheriting class will appear in your view, and will affect the validation of the model.
In addition, you should be able to turn the View into a template for the Person type, and use the template for any instance using the base class or inheriting from it.
Hmm, this is a tricky one since the
HtmlHelper<T>.EditorFor
method uses the generic parameter of theHtmlHelper<T>
to figure out which validation attributes are required.I would suggest writing your own EditorFor extension method that delegates calls to the non-generic HtmlHelper.Editor method.
Have you considered creating your own EditorTemplate for Person, Sender and Receiver? The EditorFor and DisplayFor look for a custom template that match the type of the object.
The internal method will look for a template that matches the type of the object. It will then look for a template that matches the base class and then on up the chain of inheritance.