Copying ModelMetaData to Child Property's ModelMetaData - asp.net-mvc-3

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.

Related

Access to a property with Interface cast

ActionBase, ActionA, ActionB and ActionC are Entities (from a database). ActionA, ActionB and ActionC are derived type of ActionBase.
ActionB and ActionC implements ISpecialAction with a SpecialProperty.
ex :
public interface ISpecialAction
{
Guid SpecialProperty { get; }
}
public partial class ActionBase
{
public objectX OnePropertyBase { get; set; }
}
public partial class ActionA : ActionBase
{
public objectY OnePropertyA { get; set; }
}
public partial class ActionB:ActionBase,ISpecialAction
{
public objectZ OnePropertyB { get; set; }
public Guid SpecialProperty
{
get
{
return OnePropertyB.ID;
}
}
}
public partial class ActionC : ActionBase ,ISpecialAction
{
public objectW OnePropertyC { get; set; }
public Guid SpecialProperty
{
get
{
return OnePropertyC.ID;
}
}
}
My problem is that SpecialProperty is build from other Properties of the objects (ActionB or ActionC) and when the cast (to ISpecialAction) is done, OtherProperty and OtherProperty2 are null.
I tried :
GetActionBase().ToList().Where(x=>x is ISpecialAction && ((dynamic) x).SpecialProperty== p_SpecialProperty);
GetActionBase().ToList().Where(x=>x is ISpecialAction && ((ISpecialAction) x).SpecialProperty== p_SpecialProperty);
GetActionBase().ToList().OfType<ISpecialAction>().Where(x => x.SpecialProperty== p_SpecialProperty).Cast<ActionBase>();
return GetActionOnGoing().ToList().OfType<ICityAction>().Cast<ActionBase>().Where(x => ((dynamic)x).CityId == p_CityId);
remark : OfType<> doesn't works with an Interface in Linq to entities but is ok in Linq to object
How do I access my property interface without knowing the type of the object?
I might missed something but this is Ok with the code you provided :
public class objectX
{
}
public class objectY
{
}
public class objectZ
{
public Guid ID { get { return Guid.NewGuid();} }
}
public class objectW
{
public Guid ID { get { return new Guid(); } }
}
class Program
{
private static Guid p_SpecialProperty;
static void Main(string[] args)
{
var result = GetActionBase().ToList().Where(x => x is ISpecialAction && ((dynamic)x).SpecialProperty == p_SpecialProperty).FirstOrDefault();
var result1 = GetActionBase().ToList().Where(x => x is ISpecialAction && ((ISpecialAction)x).SpecialProperty == p_SpecialProperty).FirstOrDefault();
var result2 = GetActionBase().ToList().OfType<ISpecialAction>().Where(x => x.SpecialProperty == p_SpecialProperty).Cast<ActionBase>().FirstOrDefault();
}
private static IEnumerable<ActionBase> GetActionBase()
{
return new List<ActionBase> {new ActionA{OnePropertyA= new objectY()}, new ActionB{OnePropertyB=new objectZ()},new ActionC{OnePropertyC=new objectW()} };
}
}
Not sure if I exactly understand your question, but could you try using an intermediate interface, such as:
public interface ISpecialActionB : ISpecialAction
{
objectZ OnePropertyB { get; set; }
}
public class ActionB : ActionBase, ISpecialActionB
{
//same stuff
}
and casting to that instead.
var b = new ActionB{OnePropertyB = new Whatever()};
var bAsSpecial = b as ISpecialActionB;
var whatever = b.OnePropertyB; // should not be null
It' ok.
Your example run very well without problem so I searched in a other way : AutoMapper.
l_List.Actions = Mapper.Map<List<ActionBase>, Action[]>(l_ActionManagement.GetActionBySpecialId(l_Special.ID).ToList());
The problem was not interfaces or Linq queries but it was that automapper need an empty constructor and in this constructor, I need to initialize OnePropertyB and OnePropertyC to compute SpecialProperty.
Thanks

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)

How to load a view in a particular region dynamically in Prism

In Prism Silverlight5, I have a shell which is divided into two vertical regions(leftRegion,rightRegion) & there are 2 views in Module1 i.e. (View1,View2). In leftRegion I have a View1 loaded which has a button. I want to dynamically load View2 on rightRegion using ViewModel & MEF.ViewModel code is :
[Export(typeof(LeftViewViewModel))]
public class LeftViewViewModel:ViewModelBase,IViewModel
{
[Import]
public IRegionManager CullingRegion { get; set; }
[ImportingConstructor]
public LeftViewViewModel(LeftView view)
:base(view)
{
LoadCommand = new DelegateCommand(LoadControl,CanLoadControl);
}
private void LoadControl()
{
CullingRegion.RegisterViewWithRegion("RightRegion", typeof(RightView));
}
protected bool CanLoadControl()
{
return true;
}
public DelegateCommand LoadCommand { get; set; }
}
LeftView.xaml.cs is :
[Import]
public ViewModels.IViewModel ViewModel
{
get { return (IViewModel) DataContext; }
set { DataContext = value; }
}
IModule implementation is :
[ModuleExport(typeof(CullingModuleModule1))]
public class CullingModuleModule1:IModule
{
[Import]
public IRegionManager CullingRegion { get; set; }
public void Initialize()
{
CullingRegion.RegisterViewWithRegion("ShellContentRegion", typeof (Container));
CullingRegion.RegisterViewWithRegion("LeftRegion", typeof(LeftView));
}
}
First of,I think your ViewModel should not be referenced by a View.
You may need to review View Injection with MEF.
As I've seen in multiple posts :
[Export]
public class YourViewClassName : UserControl
{
public YourViewClassName()
{
}
[Import]
public ILeftViewModel
{
get { return (ILeftViewModel )DataContext; }
set { DataContext = value; }
}
}
[Export(typeof(LeftViewViewModel))]
public class LeftViewViewModel : ILeftViewModel //ILeftViewModel inherits from IViewModel
{
public LeftViewViewModel()
{
}
}
Inside Module Initializer :
CullingRegion.Regions[YourRegionName].Add(ServiceLocator.Current.GetInstance<YourViewClassName>());
Hope it helps

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

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;
}
}

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");
}
}
}

Resources