ASP.NET MVC 3 - Bind model to a List<> that contains different types - asp.net-mvc-3

I'm trying to work out how to bind a model that contains a list of multiple object types to a postback action in ASP.NET MVC 3. I've got the following classes that define a list a vehicle types:
public enum VehicleType
{
Car,
Plane.
Boat
}
public class BaseVehicle
{
public VehicleType VehicleType { get; set; }
public string Name { get; set; }
public int Passengers { get; set; }
}
public class Plane : BaseVehicle
{
public int WingSpan { get; set; }
// -- etc --
}
// Properties omitted
public class Car : BaseVehicle {}
public class Boat : BaseVehicle {}
public class VehiclesViewModel
{
public string Notes { get; set; }
public List<BaseVehicle> Vehicles { get; set; }
}
The above classes are displayed by these views:
<!-- VehiclesView.cshtml - loaded by the controller -->
#model Mvc3Test.Models.VehiclesViewModel
<h2>Vehicles</h2>
#Html.EditorFor(m => m.Notes)
<hr />
#Html.EditorFor(m => m.Vehicles)
<!-- BaseVehicle.cshtml -->
#model BaseVehicle
#using Mvc3Test.Data
#Html.HiddenFor(m => m.VehicleType)
#{
if (Model.VehicleType == VehicleType.Car)
{
Html.RenderPartial("Car", (Car)Model);
}
else if (Model.VehicleType == VehicleType.Plane)
{
Html.RenderPartial("Plane", (Plane)Model);
}
// etc..
}
<-- Plane.cshtml -->
#model Mvc3Test.Data.Plane
<h2>Plane</h2>
<p>Name: #Html.EditorFor(m => m.Name)</p>
<p>Passengers: #Html.EditorFor(m => m.Passengers)</p>
<p>Wingspan: #Html.EditorFor(m => m.WingSpan) metres</p>
<!-- Car.cshtml omitted -->
I don't know it the above is the best way to handle the displaying (especially the if statement within a view), but it works for now. The problem is how to bind back to the viewmodel class. I've tried replacing the Html.TextBorFor() with Html.TextBox() so I can add binding prefixes ("Vehicles.Car" etc..) but there does not seem to be a way of getting the default model binders to determine what kind of class is being represented in the html so the right type can be instantiated.
I think I'll have to write a custom model binder to work it out - is this the right method to use or is there another way I've missed?
Thanks for any help

First of all
if (Model.VehicleType == VehicleType.Car)
{
Html.RenderPartial("Car", (Car)Model);
}
else if (Model.VehicleType == VehicleType.Plane)
{
Html.RenderPartial("Plane", (Plane)Model);
}
Can be omitted with EditorFor.Works just fine.
As for binding back - IMO best way is to create custom model binder.
Example:
public class DerivedTypeModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var typeName = GetVehicleType(bindingContext);
if (typeName != null)
{
var modelType = Type.GetType(typeName);
var targetTypeInstance = Activator.CreateInstance(modelType);
bindingContext = new ModelBindingContext
{
ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => targetTypeInstance, modelType),
ModelState = bindingContext.ModelState,
FallbackToEmptyPrefix = bindingContext.FallbackToEmptyPrefix,
ModelName = bindingContext.FallbackToEmptyPrefix ? string.Empty : bindingContext.ModelName,
ValueProvider = bindingContext.ValueProvider,
};
}
return base.BindModel(controllerContext, bindingContext);
}
private string GetVehicleType(ModelBindingContext bindingContext)
{
var valueResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName + "." + "VehicleType");
if (valueResult == null && bindingContext.FallbackToEmptyPrefix)
{
valueResult = bindingContext.ValueProvider.GetValue("VehicleType");
}
return valueResult == null ? null : valueResult.AttemptedValue;
}
}

Related

can I use AdditionalMetadata with editor template in mvc3?

I need to create a editor template for diferent type of data for example: for string I need a EditorTemplate for largeString and for shortstring
I see that the best way for me is using editor template. so can I use AdditionalMetadata? for something like this?
[UIHint("StringLarge")]
[AdditionalMetadata("width", "50px")]
public DateTime Date { get; set; }
My editor Template StringLarge.cshtml
#inherits System.Web.Mvc.WebViewPage<System.String>
if("have AdditionalMetadata"){
#Html.TextBox("", Model, new { #class = "StringLarge" })
}
else
{
#Html.TextBox("", Model, new { #class = "StringShort" })
}
Can I do that or just create separtes EditorTemplate for stringLarge and StringShort?
You can do that by writing a custom attribute implementing the IMetadataAware interface:
public class MyStringsAttribute : Attribute, IMetadataAware
{
private readonly string _value;
public MyStringsAttribute(string value)
{
_value = value;
}
public void OnMetadataCreated(ModelMetadata metadata)
{
metadata.TemplateHint = "Strings";
metadata.AdditionalValues["someKey"] = _value;
}
}
and then:
[MyStrings("somevalue")]
public DateTime Date { get; set; }
and finally inside your custom editor template (~/Views/Shared/EditorTemplates/Strings.cshtml) you could check for the presence of this additional metadata:
#{
var additionalMetadata = (string)ViewData.ModelMetadata.AdditionalValues["someKey"];
}
#if (string.Equals(additionalMetadata, "somevalue"))
{
...
}
else
{
...
}
using [AdditionalMetadata]
ViewModel:
[UIHint("StringCorto")]
[AdditionalMetadata("style", "width:100px")]
public string Nit { get; set; }
Editor Template:
#inherits System.Web.Mvc.WebViewPage<System.String>
#{
this.ViewData.ModelMetadata.AdditionalValues.Add("class", "StringCorto");
}
#Html.TextBox(string.Empty, ViewContext.ViewData.TemplateInfo.FormattedModelValue, this.ViewData.ModelMetadata.AdditionalValues)

Copying ModelMetaData to Child Property's ModelMetaData

I've created a wrapper model which contains an inner property, Value, which is the property to be shown in the view. In the outermost model, I create a property which is the type of the wrapper and I apply some attributes and validations to that property. When the wrapper view is rendering, I want to copy all validations and attributes from the wrapper to the inner Value property so that when I finally call EditorFor(m => m.Value), the view that renders the child property also renders the validators, etc. related to the container property.
HomeController.cs
namespace MvcApplication1.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
return View(new IndexModel());
}
}
}
IndexModel.cs
namespace MvcApplication1.Models.Home
{
public class IndexModel
{
[Display(Name = "Test Edit Field")]
[MaxLength(10)]
[AdditionalMetadata("ViewName", "String")]
[TemplateVisibility(false, true)]
public BatchEditField<string> TestEditField { get; set; }
public IndexModel()
{
this.TestEditField = new BatchEditField<string>("TEST");
}
}
}
EditField.cs (The "wrapper" model.)
namespace MvcApplication1.Models.Home
{
public class EditField
{
public string RawValue { get; set; }
public object Value { get; set; }
public string Description { get; set; }
public EditField() { }
public EditField(string rawValue, object value, string description = null)
{
this.RawValue = rawValue;
this.Value = value;
this.Description = (description ?? Convert.ToString(value));
}
public override string ToString()
{
return this.Description;
}
}
public class EditField<T> : BatchEditField
{
public new T Value { get { return (T)base.Value; } set { base.Value = value; } }
public EditField() { }
public EditField(string defaultValue) : base(null, defaultValue, null) { }
public EditField(string rawValue, T value, string description = null)
: base(rawValue, value, description)
{
}
}
}
EditField.cshtml (The "wrapper" view.)
#using S3.Common.Extensions
#model MvcApplication1.Models.Home.EditField
#{
//Attempting to copy AdditionalValues from the wrapper property to the child Value property.
ModelMetadata property = this.ViewData.ModelMetadata.Properties.Single(o => "Value".Equals(o.PropertyName));
foreach (var item in this.ViewData.ModelMetadata.AdditionalValues)
{
property.AdditionalValues[item.Key] = item.Value;
}
}
#Html.EditorFor(m => m.Value, Convert.ToString(this.ViewData.ModelMetadata.AdditionalValues.ValueOrDefault("ViewName")))
#if (this.Model != null && !this.Model.RawValue.IsNullOrEmpty() && !this.Model.RawValue.Trim().IsNullOrEmpty())
{
<p class="RawValue#(this.ViewData.ModelMetadata.AdditionalValues.ContainsKey("Style") ? " " + this.ViewData.ModelMetadata.AdditionalValues["Style"] : "")">#(this.Model.RawValue.IsNullOrEmpty() ? "(Not Specified)" : this.Model.RawValue)</p>
}
String.cshtml (The child view.)
#* Inspecting this.ViewData.ModelMetaData.AdditionalValues shows that it is empty; the parent AdditionalValues that were copied in the wrapper view did not make it through. *#
#Html.TextBox(string.Empty, this.ViewData.TemplateInfo.FormattedModelValue)
#Html.ValidationMessage(string.Empty)
Summary
Finally, when the page is rendered, no validation elements, etc. are included because they don't exist in the ModelMetaData when String.cshtml is rendered.

Pass data serialized with Json.net library to the View and bind it with knockout

I am trying to send some serialized data to the view and bind it to the knockout code. I am using json.net library for serialization because I want to pass the constants of an enum to the view ( and not the underlying integers.) I am not sure how my controller returning Json data should look like. Here is the sample code:
My view model that will be serialized:
public class FranchiseInfoViewModel
{
public string FolderName { get; set; }
[JsonConverter(typeof(StringEnumConverter))]
public LobbyTemplateOptions LobbyTemplate { get; set; }
public List<LobbyTemplateOptions> LobbyTemplates { get; set; }
public void Initialize()
{
FolderName = "Test";
LobbyTemplate = LobbyTemplateOptions.G;
LobbyTemplates = new List<LobbyTemplateOptions>
{
LobbyTemplateOptions.G,
LobbyTemplateOptions.H,
LobbyTemplateOptions.I
};
Enum:
[JsonConverter(typeof(StringEnumConverter))]
public enum LobbyTemplateOptions
{
G = 7,
H = 8,
I = 9
}
My knockout code:
$(function () {
omega.FranchiseInfo = (function () {
var FolderName = ko.observable();
var LobbyTemplates = ko.observableArray([]);
$.getJSON("FranchiseData", function (data) {
FolderName(data.FolderName);
for (var i = 0; i < data.LobbyTemplate.length; i++) {
LobbyTemplates.push(data.LobbyTemplate[i]);
}
});
return {
folderName: FolderName,
lobbyTemplates: LobbyTemplates
}
} ());
ko.applyBindings(omega.FranchiseInfo);
})
}
I am wondering how my controller that passes serialized Json data to the view should look like as I have not used json.net and I am relatively new to programming:
Controller passing the Json data to the view:
public JsonResult FranchiseData()
{
FranchiseInfoViewModel franchiseInfoViewModel = new FranchiseInfoViewModel();
franchiseInfoViewModel.MapFranchiseInfoToFranchiseInfoViewModel();
string json = JsonConvert.SerializeObject(franchiseInfoViewModel);
// this is how I do it with the default Json serializer
// return Json(franchiseInfoViewModel, JsonRequestBehavior.AllowGet);
}
I would be very gratefull if somebody can post a working example of my controller. Thank You!
You need to implement JsonNetResult.
public class JsonNetResult : ActionResult
{
public Encoding ContentEncoding { get; set; }
public string ContentType { get; set; }
public object Data { get; set; }
public JsonSerializerSettings SerializerSettings { get; set; }
public Formatting Formatting { get; set; }
public JsonNetResult()
{
SerializerSettings = new JsonSerializerSettings();
}
public override void ExecuteResult(ControllerContext context)
{
if (context == null)
throw new ArgumentNullException("context");
HttpResponseBase response = context.HttpContext.Response;
response.ContentType = !string.IsNullOrEmpty(ContentType)
? ContentType
: "application/json";
if (ContentEncoding != null)
response.ContentEncoding = ContentEncoding;
if (Data != null)
{
JsonTextWriter writer = new JsonTextWriter(response.Output) { Formatting = Formatting };
JsonSerializer serializer = JsonSerializer.Create(SerializerSettings);
serializer.Serialize(writer, Data);
writer.Flush();
}
}
}
To use it, in your case you need to rewrite controller method in this way:
public ActionResult FranchiseData()
{
FranchiseInfoViewModel franchiseInfoViewModel = new FranchiseInfoViewModel();
franchiseInfoViewModel.MapFranchiseInfoToFranchiseInfoViewModel();
JsonNetResult jsonNetResult = new JsonNetResult();
jsonNetResult.Formatting = Formatting.Indented;
jsonNetResult.Data = franchiseInfoViewModel;
return jsonNetResult;
}
(implementation of JsonNetResult above was taken this blog post
http://james.newtonking.com/archive/2008/10/16/asp-net-mvc-and-json-net.aspx )

RequiredIf Conditional Validation Attribute

I was looking for some advice on the best way to go about implementing a validation attribute that does the following.
Model
public class MyInputModel
{
[Required]
public int Id {get;set;}
public string MyProperty1 {get;set;}
public string MyProperty2 {get;set;}
public bool MyProperty3 {get;set;}
}
I want to have atleast prop1 prop2 prop3 with a value and if prop3 is the only value filled it it should not equal false.
How would i go about writing a validation attribute(s?) for this?
Thanks for any help!
I had the same problem yesterday, but I did it in a very clean way which works for both client side and server side validation.
Condition: Based on the value of other property in the model, you want to make another property required. Here is the code:
public class RequiredIfAttribute : RequiredAttribute
{
private String PropertyName { get; set; }
private Object DesiredValue { get; set; }
public RequiredIfAttribute(String propertyName, Object desiredvalue)
{
PropertyName = propertyName;
DesiredValue = desiredvalue;
}
protected override ValidationResult IsValid(object value, ValidationContext context)
{
Object instance = context.ObjectInstance;
Type type = instance.GetType();
Object proprtyvalue = type.GetProperty(PropertyName).GetValue(instance, null);
if (proprtyvalue.ToString() == DesiredValue.ToString())
{
ValidationResult result = base.IsValid(value, context);
return result;
}
return ValidationResult.Success;
}
}
PropertyName is the property on which you want to make your condition
DesiredValue is the particular value of the PropertyName (property) for which your other property has to be validated for required
Say you have the following:
public enum UserType
{
Admin,
Regular
}
public class User
{
public UserType UserType {get;set;}
[RequiredIf("UserType",UserType.Admin,
ErrorMessageResourceName="PasswordRequired",
ErrorMessageResourceType = typeof(ResourceString))]
public string Password { get; set; }
}
At last but not the least, register adapter for your attribute so that it can do client side validation (I put it in global.asax, Application_Start)
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(RequiredIfAttribute),
typeof(RequiredAttributeAdapter));
EDITED
Some people have reported issues that the client side fires no matter what or it does not work. So I modified the above code to do conditional client side validation with Javascript as well. For this case you don't need to register adapter
public class RequiredIfAttribute : ValidationAttribute, IClientValidatable
{
private String PropertyName { get; set; }
private Object DesiredValue { get; set; }
private readonly RequiredAttribute _innerAttribute;
public RequiredIfAttribute(String propertyName, Object desiredvalue)
{
PropertyName = propertyName;
DesiredValue = desiredvalue;
_innerAttribute = new RequiredAttribute();
}
protected override ValidationResult IsValid(object value, ValidationContext context)
{
var dependentValue = context.ObjectInstance.GetType().GetProperty(PropertyName).GetValue(context.ObjectInstance, null);
if (dependentValue.ToString() == DesiredValue.ToString())
{
if (!_innerAttribute.IsValid(value))
{
return new ValidationResult(FormatErrorMessage(context.DisplayName), new[] { context.MemberName });
}
}
return ValidationResult.Success;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule
{
ErrorMessage = ErrorMessageString,
ValidationType = "requiredif",
};
rule.ValidationParameters["dependentproperty"] = (context as ViewContext).ViewData.TemplateInfo.GetFullHtmlFieldId(PropertyName);
rule.ValidationParameters["desiredvalue"] = DesiredValue is bool ? DesiredValue.ToString().ToLower() : DesiredValue;
yield return rule;
}
}
And finally the javascript ( bundle it and renderit...put it in its own script file)
$.validator.unobtrusive.adapters.add('requiredif', ['dependentproperty', 'desiredvalue'], function (options) {
options.rules['requiredif'] = options.params;
options.messages['requiredif'] = options.message;
});
$.validator.addMethod('requiredif', function (value, element, parameters) {
var desiredvalue = parameters.desiredvalue;
desiredvalue = (desiredvalue == null ? '' : desiredvalue).toString();
var controlType = $("input[id$='" + parameters.dependentproperty + "']").attr("type");
var actualvalue = {}
if (controlType == "checkbox" || controlType == "radio") {
var control = $("input[id$='" + parameters.dependentproperty + "']:checked");
actualvalue = control.val();
} else {
actualvalue = $("#" + parameters.dependentproperty).val();
}
if ($.trim(desiredvalue).toLowerCase() === $.trim(actualvalue).toLocaleLowerCase()) {
var isValid = $.validator.methods.required.call(this, value, element, parameters);
return isValid;
}
return true;
});
You need obviously the unobstrusive validate jQuery to be included as requirement
I know the topic was asked some time ago, but recently I had faced similar issue and found yet another, but in my opinion a more complete solution. I decided to implement mechanism which provides conditional attributes to calculate validation results based on other properties values and relations between them, which are defined in logical expressions.
Using it you are able to achieve the result you asked about in the following manner:
[RequiredIf("MyProperty2 == null && MyProperty3 == false")]
public string MyProperty1 { get; set; }
[RequiredIf("MyProperty1 == null && MyProperty3 == false")]
public string MyProperty2 { get; set; }
[AssertThat("MyProperty1 != null || MyProperty2 != null || MyProperty3 == true")]
public bool MyProperty3 { get; set; }
More information about ExpressiveAnnotations library can be found here. It should simplify many declarative validation cases without the necessity of writing additional case-specific attributes or using imperative way of validation inside controllers.
I got it to work on ASP.NET MVC 5
I saw many people interested in and suffering from this code and i know it's really confusing and disrupting for the first time.
Notes
worked on MVC 5 on both server and client side :D
I didn't install "ExpressiveAnnotations" library at all.
I'm taking about the Original code from "#Dan Hunex", Find him above
Tips To Fix This Error
"The type System.Web.Mvc.RequiredAttributeAdapter must have a public constructor which accepts three parameters of types System.Web.Mvc.ModelMetadata, System.Web.Mvc.ControllerContext, and ExpressiveAnnotations.Attributes.RequiredIfAttribute Parameter name: adapterType"
Tip #1: make sure that you're inheriting from 'ValidationAttribute' not from 'RequiredAttribute'
public class RequiredIfAttribute : ValidationAttribute, IClientValidatable { ...}
Tip #2: OR remove this entire line from 'Global.asax', It is not needed at all in the newer version of the code (after edit by #Dan_Hunex), and yes this line was a must in the old version ...
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(RequiredIfAttribute), typeof(RequiredAttributeAdapter));
Tips To Get The Javascript Code Part Work
1- put the code in a new js file (ex:requiredIfValidator.js)
2- warp the code inside a $(document).ready(function(){........});
3- include our js file after including the JQuery validation libraries, So it look like this now :
#Scripts.Render("~/bundles/jqueryval")
<script src="~/Content/JS/requiredIfValidator.js"></script>
4- Edit the C# code
from
rule.ValidationParameters["dependentproperty"] = (context as ViewContext).ViewData.TemplateInfo.GetFullHtmlFieldId(PropertyName);
to
rule.ValidationParameters["dependentproperty"] = PropertyName;
and from
if (dependentValue.ToString() == DesiredValue.ToString())
to
if (dependentValue != null && dependentValue.ToString() == DesiredValue.ToString())
My Entire Code Up and Running
Global.asax
Nothing to add here, keep it clean
requiredIfValidator.js
create this file in ~/content or in ~/scripts folder
$.validator.unobtrusive.adapters.add('requiredif', ['dependentproperty', 'desiredvalue'], function (options)
{
options.rules['requiredif'] = options.params;
options.messages['requiredif'] = options.message;
});
$(document).ready(function ()
{
$.validator.addMethod('requiredif', function (value, element, parameters) {
var desiredvalue = parameters.desiredvalue;
desiredvalue = (desiredvalue == null ? '' : desiredvalue).toString();
var controlType = $("input[id$='" + parameters.dependentproperty + "']").attr("type");
var actualvalue = {}
if (controlType == "checkbox" || controlType == "radio") {
var control = $("input[id$='" + parameters.dependentproperty + "']:checked");
actualvalue = control.val();
} else {
actualvalue = $("#" + parameters.dependentproperty).val();
}
if ($.trim(desiredvalue).toLowerCase() === $.trim(actualvalue).toLocaleLowerCase()) {
var isValid = $.validator.methods.required.call(this, value, element, parameters);
return isValid;
}
return true;
});
});
_Layout.cshtml or the View
#Scripts.Render("~/bundles/jqueryval")
<script src="~/Content/JS/requiredIfValidator.js"></script>
RequiredIfAttribute.cs Class
create it some where in your project, For example in ~/models/customValidation/
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
namespace Your_Project_Name.Models.CustomValidation
{
public class RequiredIfAttribute : ValidationAttribute, IClientValidatable
{
private String PropertyName { get; set; }
private Object DesiredValue { get; set; }
private readonly RequiredAttribute _innerAttribute;
public RequiredIfAttribute(String propertyName, Object desiredvalue)
{
PropertyName = propertyName;
DesiredValue = desiredvalue;
_innerAttribute = new RequiredAttribute();
}
protected override ValidationResult IsValid(object value, ValidationContext context)
{
var dependentValue = context.ObjectInstance.GetType().GetProperty(PropertyName).GetValue(context.ObjectInstance, null);
if (dependentValue != null && dependentValue.ToString() == DesiredValue.ToString())
{
if (!_innerAttribute.IsValid(value))
{
return new ValidationResult(FormatErrorMessage(context.DisplayName), new[] { context.MemberName });
}
}
return ValidationResult.Success;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule
{
ErrorMessage = ErrorMessageString,
ValidationType = "requiredif",
};
rule.ValidationParameters["dependentproperty"] = PropertyName;
rule.ValidationParameters["desiredvalue"] = DesiredValue is bool ? DesiredValue.ToString().ToLower() : DesiredValue;
yield return rule;
}
}
}
The Model
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Web;
using Your_Project_Name.Models.CustomValidation;
namespace Your_Project_Name.Models.ViewModels
{
public class CreateOpenActivity
{
public Nullable<int> ORG_BY_CD { get; set; }
[RequiredIf("ORG_BY_CD", "5", ErrorMessage = "Coordinator ID is required")] // This means: IF 'ORG_BY_CD' is equal 5 (for the example) > make 'COR_CI_ID_NUM' required and apply its all validation / data annotations
[RegularExpression("[0-9]+", ErrorMessage = "Enter Numbers Only")]
[MaxLength(9, ErrorMessage = "Enter a valid ID Number")]
[MinLength(9, ErrorMessage = "Enter a valid ID Number")]
public string COR_CI_ID_NUM { get; set; }
}
}
The View
Nothing to note here actually ...
#model Your_Project_Name.Models.ViewModels.CreateOpenActivity
#{
ViewBag.Title = "Testing";
}
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>CreateOpenActivity</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.ORG_BY_CD, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.ORG_BY_CD, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.ORG_BY_CD, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.COR_CI_ID_NUM, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.COR_CI_ID_NUM, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.COR_CI_ID_NUM, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
I may upload a project sample for this later ...
Hope this was helpful
Thank You
If you try to use "ModelState.Remove" or "ModelState["Prop"].Errors.Clear()" the "ModelState.IsValid" stil returns false.
Why not just removing the default "Required" Annotation from Model and make your custom validation before the "ModelState.IsValid" on Controller 'Post' action? Like this:
if (!String.IsNullOrEmpty(yourClass.Property1) && String.IsNullOrEmpty(yourClass.dependantProperty))
ModelState.AddModelError("dependantProperty", "It´s necessary to select some 'dependant'.");
Expanding on the notes from Adel Mourad and Dan Hunex, I amended the code to provide an example that only accepts values that do not match the given value.
I also found that I didn't need the JavaScript.
I added the following class to my Models folder:
public class RequiredIfNotAttribute : ValidationAttribute, IClientValidatable
{
private String PropertyName { get; set; }
private Object InvalidValue { get; set; }
private readonly RequiredAttribute _innerAttribute;
public RequiredIfNotAttribute(String propertyName, Object invalidValue)
{
PropertyName = propertyName;
InvalidValue = invalidValue;
_innerAttribute = new RequiredAttribute();
}
protected override ValidationResult IsValid(object value, ValidationContext context)
{
var dependentValue = context.ObjectInstance.GetType().GetProperty(PropertyName).GetValue(context.ObjectInstance, null);
if (dependentValue.ToString() != InvalidValue.ToString())
{
if (!_innerAttribute.IsValid(value))
{
return new ValidationResult(FormatErrorMessage(context.DisplayName), new[] { context.MemberName });
}
}
return ValidationResult.Success;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule
{
ErrorMessage = ErrorMessageString,
ValidationType = "requiredifnot",
};
rule.ValidationParameters["dependentproperty"] = (context as ViewContext).ViewData.TemplateInfo.GetFullHtmlFieldId(PropertyName);
rule.ValidationParameters["invalidvalue"] = InvalidValue is bool ? InvalidValue.ToString().ToLower() : InvalidValue;
yield return rule;
}
I didn't need to make any changes to my view, but did make a change to the properties of my model:
[RequiredIfNot("Id", 0, ErrorMessage = "Please select a Source")]
public string TemplateGTSource { get; set; }
public string TemplateGTMedium
{
get
{
return "Email";
}
}
[RequiredIfNot("Id", 0, ErrorMessage = "Please enter a Campaign")]
public string TemplateGTCampaign { get; set; }
[RequiredIfNot("Id", 0, ErrorMessage = "Please enter a Term")]
public string TemplateGTTerm { get; set; }
Hope this helps!
The main difference from other solutions here is that this one reuses logic in RequiredAttribute on the server side, and uses required's validation method depends property on the client side:
public class RequiredIf : RequiredAttribute, IClientValidatable
{
public string OtherProperty { get; private set; }
public object OtherPropertyValue { get; private set; }
public RequiredIf(string otherProperty, object otherPropertyValue)
{
OtherProperty = otherProperty;
OtherPropertyValue = otherPropertyValue;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
PropertyInfo otherPropertyInfo = validationContext.ObjectType.GetProperty(OtherProperty);
if (otherPropertyInfo == null)
{
return new ValidationResult($"Unknown property {OtherProperty}");
}
object otherValue = otherPropertyInfo.GetValue(validationContext.ObjectInstance, null);
if (Equals(OtherPropertyValue, otherValue)) // if other property has the configured value
return base.IsValid(value, validationContext);
return null;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule();
rule.ErrorMessage = FormatErrorMessage(metadata.GetDisplayName());
rule.ValidationType = "requiredif"; // data-val-requiredif
rule.ValidationParameters.Add("other", OtherProperty); // data-val-requiredif-other
rule.ValidationParameters.Add("otherval", OtherPropertyValue); // data-val-requiredif-otherval
yield return rule;
}
}
$.validator.unobtrusive.adapters.add("requiredif", ["other", "otherval"], function (options) {
var value = {
depends: function () {
var element = $(options.form).find(":input[name='" + options.params.other + "']")[0];
return element && $(element).val() == options.params.otherval;
}
}
options.rules["required"] = value;
options.messages["required"] = options.message;
});
I think using IValidatableObject is a good choice.
public class MyInputModel : IValidateObject
{
[Required]
public int Id {get;set;}
public string MyProperty1 {get;set;}
public string MyProperty2 {get;set;}
public bool MyProperty3 {get;set;}
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (MyProperty1==null&&MyProperty2==null&&MyPropterty3!=false) //whatever condition
{
yield return new ValidationResult(
"Custom complex error");
}
}
}

MVC3 Validation with Lightspeed

My ORM (LightSpeed) generates this for Animals table, with Name and Age. Using MVC3 and Razor
[Serializable]
[System.CodeDom.Compiler.GeneratedCode("LightSpeedModelGenerator", "1.0.0.0")]
[System.ComponentModel.DataObject]
[Table(IdColumnName="AnimalID", IdentityMethod=IdentityMethod.IdentityColumn)]
public partial class Animal : Entity<int>
{
[ValidatePresence]
[ValidateLength(0, 50)]
private string _name;
[ValidateComparison(ComparisonOperator.GreaterThan, 0)]
private int _age;
public const string NameField = "Name";
public const string AgeField = "Age";
[System.Diagnostics.DebuggerNonUserCode]
[Required] // ****I put this in manually to get Name required working
public string Name
{
get { return Get(ref _name, "Name"); }
set { Set(ref _name, value, "Name"); }
}
[System.Diagnostics.DebuggerNonUserCode]
public int Age
{
get { return Get(ref _age, "Age"); }
set { Set(ref _age, value, "Age"); }
}
With [Required] attribute added:
With no [Required] attribute added: (notice LightSpeed strange rendering of validation)
With name filled in:
In images above - the validation at the top is LightSpeed (put into ValidationSummary) and at the side is MVC3 (put into ValidationMessageFor)
Am only using Server Side validation currently.
Question: How do I get LightSpeed validation working well in MVC3?
I think it is something in this area http://www.mindscapehq.com/staff/jeremy/index.php/2009/03/aspnet-mvc-part4/
For the server side validation - you will want to use a custom model binder which emits the errors from LightSpeed validation more precisely rather than the leveraging the DefaultModelBinder behavior. Have a look at either directly using or adapting the EntityModelBinder from the community code library for Mvc
http://www.mindscapehq.com/forums/Thread.aspx?PostID=12051
See link http://www.mindscapehq.com/forums/Thread.aspx?ThreadID=4093
Jeremys answer (Mindscape have great support!)
public class EntityModelBinder2 : DefaultModelBinder
{
public static void Register(Assembly assembly)
{
ModelBinders.Binders.Add(typeof(Entity), new EntityModelBinder2());
foreach (Type type in assembly.GetTypes())
{
if (typeof(Entity).IsAssignableFrom(type))
{
ModelBinders.Binders.Add(type, new EntityModelBinder2());
}
}
}
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
object result = base.BindModel(controllerContext, bindingContext);
if (typeof(Entity).IsAssignableFrom(bindingContext.ModelType))
{
Entity entity = (Entity)result;
if (!entity.IsValid)
{
foreach (var state in bindingContext.ModelState.Where(s => s.Value.Errors.Count > 0))
{
state.Value.Errors.Clear();
}
foreach (var error in entity.Errors)
{
if (error.ErrorMessage.EndsWith("is invalid")) continue;
bindingContext.ModelState.AddModelError(error.PropertyName ?? "Custom", error.ErrorMessage);
}
}
}
return result;
}
}
and in Global.asax register using:
EntityModelBinder2.Register(typeof(MyEntity).Assembly);
The Register call sets up the model binder to be used for each entity type in your model assembly so modify as required.
You can get client side validation working with Lightspeed nightly builds from 04/04/2011 onwards.
Create a validator provider as follows:
public class LightspeedModelValidatorProvider : DataAnnotationsModelValidatorProvider
{
private string GetDisplayName(string name)
{
return name; // go whatever processing is required, eg decamelise, replace "_" with " " etc
}
protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes)
{
if(typeof(Entity).IsAssignableFrom(metadata.ContainerType))
{
List<Attribute> newAttributes = new List<Attribute>(attributes);
var attr = DataAnnotationBuilder.GetDataAnnotations(metadata.ContainerType, metadata.PropertyName, GetDisplayName(metadata.PropertyName));
newAttributes.AddRange(attr);
return base.GetValidators(metadata, context, newAttributes);
}
return base.GetValidators(metadata, context, attributes);
}
}
Then in Application_Start() add
ModelValidatorProviders.Providers.Clear();
ModelValidatorProviders.Providers.Add(new LightspeedModelValidatorProvider());

Resources