ModelBinding with Steve Sandersons BeginCollectionItem - c#

I'm using Steve Sandersons BeginCollectionItem extension to help with binding lists of items. This works fine for primitive types. The problem I'm having is that for a custom model binder that I've written I can't see how to generate the full name and index of the item that I'm binding to.
Currently my model binder looks like this:
public class MoneyModelBinder : DefaultModelBinder
{
protected override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var valueResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName + ".Amount");
if (valueResult != null)
{
var value = valueResult.AttemptedValue;
var currencyCode = bindingContext.ValueProvider.GetValue(bindingContext.ModelName + ".Iso3LetterCode").AttemptedValue;
var money = (Money) bindingContext.Model;
Money parsedValue;
if (String.IsNullOrEmpty(value))
{
money.Amount = null;
return;
}
var currency = Currency.FromIso3LetterCode(currencyCode);
if (!Money.TryParse(value, currency, out parsedValue))
{
bindingContext.ModelState.AddModelError("Amount", string.Format("Unable to parse {0} as money", value));
}
else
{
money.Amount = parsedValue.Amount;
money.Currency = parsedValue.Currency;
}
}
else
{
base.OnModelUpdated(controllerContext, bindingContext);
}
}
}
My ViewModel Lokks like this (some propertis omitted for clarity):
public class EditFeeEarningCapacityViewModel
{
public List<FeeEarner> FeeEarners { get; set; }
public class FeeEarner
{
public Money AverageChargeOutRate { get; set; }
}
}
My Edit Template for the Money type looks like this:
#model Core.Money
#{
int decimalPlaces;
if(!int.TryParse(string.Format("{0}", ViewData["DecimalPlaces"]), out decimalPlaces))
{
decimalPlaces = 0;
}
}
<div class="input-prepend">
<span class="add-on">#Model.Currency.Symbol</span>#Html.TextBoxFor(m => m.Amount,
new
{
placeholder = string.Format("{0}", Model.Currency),
#class = "input-mini",
Value = String.Format("{0:n" + decimalPlaces + "}", Model.Amount)
})
</div>
#Html.HiddenFor(x => x.Iso3LetterCode)
For a form that has post values like this:
FeeEarners.index 3fa91d09-0617-4bea-ae3f-d84862be8c04
FeeEarners[3fa91d09-0617-4bea-ae3f-d84862be8c04].feeEarner.AverageChargeOutRate.Amount 500
FeeEarners[3fa91d09-0617-4bea-ae3f-d84862be8c04].feeEarner.AverageChargeOutRate.Iso3LetterCode GBP
I can't see how to detect the index of the item or the property name that I'm binding to. So essentially, how do I find the index of the item I'm trying to bind to and the name of the property that I'm trying to bind the data from?

I am not fimilar with that Helper but for collection i am doing a bit different trick.
define key
var key = "EditModel[{0}].{1}";
var index = 0;
then build form
foreach(var fee in Model.FeeEarners){
#Html.TextBox(string.Format(key, index, "PropertyNameFromYourFeeClass"));
//It will build text box and set value
}
On Controller side
create action with input parameter
public ActionResult Save(EditFeeEarningCapacityViewModel editModel){
...your code here
}

Related

Using [JsonProperty("name")] in ModelState.Errors

We have a couple of models that override the name via JsonProperty, but this causes an issue when we get validation errors through ModelState. For example:
class MyModel
{
[JsonProperty("id")]
[Required]
public string MyModelId {get;set;}
}
class MyModelController
{
public IHttpActionResult Post([FromBody] MyModel model)
{
if (!ModelState.IsValid)
{
return HttpBadRequest(ModelState);
}
/* etc... */
}
}
The above Post will return the error The MyModelId field is required. which isn't accurate. We'd like this to say The id field is required.. We've attempted using [DataMember(Name="id")] but get the same result.
Question 1: Is there a way we can get ModelState errors to show the JSON property name rather than the C# property name aside from providing our own error messages on every [Required] attribute?
-- Update --
I've been playing around with this and found a "do-it-yourself" method for re-creating the error messages using custom property names. I'm really hoping there's a built-in way to do this, but this seems to do the job...
https://gist.github.com/Blackbaud-JasonTremper/b64dc6ddb460afa1698daa6d075857e4
Question 2: Can ModelState.Key be assumed to match the <parameterName>.<reflectedProperty> syntax or are there cases where this might not be true?
Question 3: Is there an easier way to determine what the JSON parameter name is expected to be rather than searching via reflection on [DataMember] or [JsonProperty] attributes?
Did you try using DisplayName attribute?
displayname attribute vs display attribute
Also, you can assign an error message to [Required] attribute.
[Required(ErrorMessage = "Name is required")]
I also faced this problem, I modified some code from your link to fit my WebAPI. modelState will also store the old key which is the variable name of the model, plus the Json Property names.
First, create the filter ValidateModelStateFilter
Add [ValidateModelStateFilter] above controller method
The filter source code:
public class ValidateModelStateFilter : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
var descriptor = actionContext.ActionDescriptor;
var modelState = actionContext.ModelState;
if (descriptor != null)
{
var parameters = descriptor.GetParameters();
var subParameterIssues = modelState.Keys
.Where(s => s.Contains("."))
.Where(s => modelState[s].Errors.Any())
.GroupBy(s => s.Substring(0, s.IndexOf('.')))
.ToDictionary(g => g.Key, g => g.ToArray());
foreach (var parameter in parameters)
{
var argument = actionContext.ActionArguments[parameter.ParameterName];
if (subParameterIssues.ContainsKey(parameter.ParameterName))
{
var subProperties = subParameterIssues[parameter.ParameterName];
foreach (var subProperty in subProperties)
{
var propName = subProperty.Substring(subProperty.IndexOf('.') + 1);
var property = parameter.ParameterType.GetProperty(propName);
var validationAttributes = property.GetCustomAttributes(typeof(ValidationAttribute), true);
var value = property.GetValue(argument);
modelState[subProperty].Errors.Clear();
foreach (var validationAttribute in validationAttributes)
{
var attr = (ValidationAttribute)validationAttribute;
if (!attr.IsValid(value))
{
var parameterName = GetParameterName(property);
// modelState.AddModelError(subProperty, attr.FormatErrorMessage(parameterName));
modelState.AddModelError(parameterName, attr.FormatErrorMessage(parameterName));
}
}
}
}
}
}
}
private string GetParameterName(PropertyInfo property)
{
var dataMemberAttribute = property.GetCustomAttributes<DataMemberAttribute>().FirstOrDefault();
if (dataMemberAttribute?.Name != null)
{
return dataMemberAttribute.Name;
}
var jsonProperty = property.GetCustomAttributes<JsonPropertyAttribute>().FirstOrDefault();
if (jsonProperty?.PropertyName != null)
{
return jsonProperty.PropertyName;
}
return property.Name;
}
}
You can access the parameter type, get the json name, and then replace the property name with the json name. Something like this:
var invalidParameters = (from m in actionContext.ModelState
where m.Value.Errors.Count > 0
select new InvalidParameter
{
ParameterName = m.Key,
ConstraintViolations = (from msg in m.Value.Errors select msg.ErrorMessage).ToArray()
}).AsEnumerable().ToArray();
if (actionContext.ActionDescriptor.Parameters.Count == 1)
{
var nameMapper = new Dictionary<string, string>();
foreach (var property in actionContext.ActionDescriptor.Parameters[0].ParameterType.GetProperties())
{
object[] attributes = property.GetCustomAttributes(typeof(JsonPropertyNameAttribute), false);
if (attributes.Length != 1) continue;
nameMapper.Add(property.Name, ((JsonPropertyNameAttribute) attributes[0]).Name);
}
var modifiedInvalidParameters = new List<InvalidParameter>();
foreach (var invalidParameter in invalidParameters)
{
if(invalidParameter.ParameterName != null && nameMapper.TryGetValue(invalidParameter.ParameterName, out var mappedName))
{
var modifiedConstraintViolations = new List<string>();
foreach (var constraintViolation in invalidParameter.ConstraintViolations ?? Enumerable.Empty<string>())
{
modifiedConstraintViolations.Add(constraintViolation.Replace(invalidParameter.ParameterName, mappedName));
}
modifiedInvalidParameters.Add(new InvalidParameter
{
ParameterName = mappedName,
ConstraintViolations = modifiedConstraintViolations.ToArray()
});
}
else
{
modifiedInvalidParameters.Add(invalidParameter);
}
}
invalidParameters = modifiedInvalidParameters.ToArray();
}
public struct InvalidParameter
{
[JsonPropertyName("parameter_name")]
public string? ParameterName { get; set; }
[JsonPropertyName("constraint_violations")]
public string[]? ConstraintViolations { get; set; }
}

Knockout not populating DateTime? when initial is null

I'm using knockout to bind my view model to my view. Multiple properties in my view model are nullable, such as DateTime?s. Here's an example:
public class ViewModel
{
public int ID { get; set; }
public string Name { get; set; }
public DateTime? CreationDate { get; set;}
}
As you can see, the property CreationDate is a nullable DateTime.
I'm binding the property with a custom datepicker binder:
ko.bindingHandlers.datepicker = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
try {
var jsonDate = ko.utils.unwrapObservable(valueAccessor());
var value = parseJsonDateString(jsonDate);
var strDate = value.getMonth() + 1 + "/"
+ value.getDate() + "/"
+ value.getFullYear();
element.setAttribute('value', strDate);
}
catch (exc) {
}
$(element).change(function () {
var value = valueAccessor();
value(element.getAttribute('value'));
});
},
update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var val = valueAccessor();
val(element.getAttribute('value'));
}
};
var jsonDateRE = /^\/Date\((-?\d+)(\+|-)?(\d+)?\)\/$/;
var parseJsonDateString = function (value) {
var arr = value && jsonDateRE.exec(value);
if (arr) {
return new Date(parseInt(arr[1]));
}
return value;
};
This enables me to bind my property in the view like so:
<input type="text" data-bind="datepicker: CreationDate" />
Problem
Here's the problem. Sometimes this property is already null when it enters the view. A JSON example could look like this:
{
"Id": 2004,
"Name": "Test",
"CreationDate": null
}
If this is the case, and I change this value to some random value from the datepicker, and send an ajax POST to my controller, I can see that models CreationDate still is equal to null.
So if the DateTime is null as the model enters the view, how do I populate the models property?
Found the solution on my own
I managed to solve my issue by simply changing my binding to the following:
ko.bindingHandlers.datepicker = {
init: function (element, valueAccessor, allBindingsAccessor) {
//initialize datepicker with some optional options
var options = allBindingsAccessor().datepickerOptions || {};
$(element).datepicker(options);
//handle the field changing
ko.utils.registerEventHandler(element, "change", function () {
var observable = valueAccessor();
var value = $(element).val();
// if the input field is empty, the value is falsy and therefore the observable should be = null
if(!value){
observable(null);
} else {
var date = new Date(value);
observable(date);
}
});
//handle disposal (if KO removes by the template binding)
ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
$(element).datepicker("destroy");
});
},
//update the control when the view model changes
update: function (element, valueAccessor) {
var value = ko.unwrap(valueAccessor());
//if the value received is null, we should display nothing in the input field
if (value === null) {
$(element).val(null);
} else {
//we need to manipulate the data to show something user friendly to the user
var date = parseJsonDateString(value);
var strDate = date.getMonth() + 1 + "/"
+ date.getDate() + "/"
+ date.getFullYear();
$(element).val(strDate);
}
}
};
So basically if the value I'm getting in the update function, simply set $(element).val(null). This way, nullable properties are handled correctly.

MVC setting up Html.DropdownList on ModelState.IsValid = false

This is something that has always puzzled me as to the best way round, while keeping maintainable code. The below code sets up a list of months and years for a payment gateway form, before assigning these to a variable of type List<SelectListItem>.
Intial Action
PayNowViewModel paymentGateway = new PayNowViewModel();
List<SelectListItem> paymentGatewayMonthsList = new List<SelectListItem>();
List<SelectListItem> paymentGatewayYearsList = new List<SelectListItem>();
for (int i = 1; i <= 12; i++)
{
SelectListItem selectListItem = new SelectListItem();
selectListItem.Value = i.ToString();
selectListItem.Text = i.ToString("00");
paymentGatewayMonthsList.Add(selectListItem);
}
int year = DateTime.Now.Year;
for (int i = year; i <= year + 10; i++)
{
SelectListItem selectListItem = new SelectListItem();
selectListItem.Value = i.ToString();
selectListItem.Text = i.ToString("00");
paymentGatewayYearsList.Add(selectListItem);
}
paymentGateway.ExpiryMonth = paymentGatewayMonthsList;
paymentGateway.ExpiryYear = paymentGatewayYearsList;
return View(paymentGateway);
It's a fair bit of code, and I find myself repeating this code, in similar formats to re-setup the dropdown lists options should the ModelState.IsValid be false and I want to return back to the view for the user to correct there mistakes.
HttpPost Action - Code
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult ConfirmPayment(PayNowViewModel paymentGatewayForm, FormCollection form)
{
if (ModelState.IsValid)
{
// Post processing actions...
return View();
}
else
{
for (int i = 1; i <= 12; i++)
{
SelectListItem selectListItem = new SelectListItem();
selectListItem.Value = i.ToString();
selectListItem.Text = i.ToString("00");
paymentGatewayMonthsList.Add(selectListItem);
}
int year = DateTime.Now.Year;
for (int i = year; i <= year + 10; i++)
{
SelectListItem selectListItem = new SelectListItem();
selectListItem.Value = i.ToString();
selectListItem.Text = i.ToString("00");
paymentGatewayYearsList.Add(selectListItem);
}
form.ExpiryMonth = paymentGatewayMonthsList;
form.ExpiryYear = paymentGatewayYearsList;
return View("MakePayment", form);
}
}
What's the best way to centralise this dropdown setup code so its only in one place? At present you'll see a large proportion (the for loops), is exactly repeated twice. A base controller with function? Or is it better to re-setup like the above?
Any advice appreciated!
Mike.
Add a private method to your controller (the following code assumes your ExpiryMonth and ExpiryYear properties are IEnumerable<SelectListItem> which is all that the DropDownListFor() method requires)
private void ConfigureViewModel(PayNowViewModel model)
{
model.ExpiryMonth = Enumerable.Range(1, 12).Select(m => new SelectListItem
{
Value = m.ToString(),
Text = m.ToString("00")
});
model.ExpiryYear = Enumerable.Range(DateTime.Today.Year, 10).Select(y => new SelectListItem
{
Value = y.ToString(),
Text = y.ToString("00")
});
}
and then in the GET method
public ActionResult ConfirmPayment()
{
PayNowViewModel model = new PayNowViewModel();
ConfigureViewModel(model);
return View(model);
}
and in the POST method
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult ConfirmPayment(PayNowViewModel model)
{
if (!ModelState.IsValid)
{
ConfigureViewModel(model);
return View(model);
}
.... // save and redirect (should not be returning the view here)
}
If the set of your dropdown options is fixed (or recompilation is OK after the potential options change), you can use an enum to store your options.
public enum Month {
// if the dropdown is not required, add default value 0
Optional = 0,
[Display(Name = #"Month_January")]
January = 1,
[Display(Name = #"Month_February")]
February = 2,
// etc ..
}
To render this as a dropdown use an EditorTemplate Enum.cshtml:
#model Enum
#{
var enumType = ViewData.ModelMetadata.ModelType;
var allValues = Enum.GetValues(enumType).Cast<object>().ToSelectList(Model);
// read any attributes like [Required] from ViewData and ModelMetadata ...
var attributes = new Dictionary<string, object>();
}
#Html.DropDownListFor(m => m, allValues, attributes)
The ToSelectList extension method loops over all enum values and converts them to SelectListItems:
public static IList<SelectListItem> ToSelectList<T>(this IEnumerable<T> list) {
return ToSelectList<T>(list, list.FirstOrDefault());
}
public static IList<SelectListItem> ToSelectList<T>(this IEnumerable<T> list, T selectedItem) {
var items = new List<SelectListItem>();
var displayAttributeType = typeof(DisplayAttribute);
foreach (var item in list) {
string displayName;
// multi-language:
// assume item is an enum value
var field = item.GetType().GetField(item.ToString());
try {
// read [Display(Name = #"someKey")] attribute
var attrs = (DisplayAttribute)field.GetCustomAttributes(displayAttributeType, false).First();
// lookup translation for someKey in the Resource file
displayName = Resources.ResourceManager.GetString(attrs.Name);
} catch {
// no attribute -> display enum value name
displayName = item.ToString();
}
// keep selected value after postback:
// assume selectedItem is the Model passed from MVC
var isSelected = false;
if (selectedItem != null) {
isSelected = (selectedItem.ToString() == item.ToString());
}
items.Add(new SelectListItem {
Selected = isSelected,
Text = displayName,
Value = item.ToString()
});
}
return items;
}
To support multiple languages, add translations for the display name keys, e.g. "Month_January", to the Resource file.
Now that the setup code has been abstracted away using some reflection magic, creating a new viewmodel is a breeze :>
public class PayNowViewModel {
// SelectListItems are only generated if this gets rendered
public Month ExpiryMonth { get; set; }
}
// Intial Action
var paymentGateway = new PayNowViewModel();
return View(paymentGateway);
// Razor View: call the EditorTemplate
#Html.EditorFor(m => m.ExpiryMonth)
Note that in the EditorTemplate, Model is passed as the selected item to ToSelectList. After postback, Model will hold the currently selected value. Therefore it stays selected, even if you just return the model after an error in the controller:
// HttpPost Action
if (!ModelState.IsValid) {
return View("MakePayment", paymentGatewayForm);
}
Took us some time to come up with this solution, credits go to the Saratiba team.

Range validation for textbox not working in MVC3

I am trying to raise validation error in an ASP.Net MVC 3 application.
When the user enters a number that is greater than 1000 an error message should be displayed. with the following code on a view model it doesn't seem to be working.
What do i need to change?
[Range(0, 1000, ErrorMessage = "Total number of rows to display must be between 0 to 1000")]
public int DisplayTop { get; set; }
cshtml :
#model Calc.Models.CountingVM
#{
ViewBag.Title = "Reports";
Layout = "~/Views/Shared/_reportsLayout.cshtml";
}
#using (Html.BeginForm("Reports", "Calc", FormMethod.Post, new { #id = "frmReport" }))
{
.........
#Html.TextBoxFor(c => c.DisplayTop, "1000")
#Html.ValidationMessageFor(c => c.DisplayTop)
}
Action Method :
public ActionResult Reports(string ReportName, CalcCriteria criteria)
{
if ((criteria == null) || (criteria.nId == null))
{
criteria = TempData["criteria"] as CalcCriteria;
TempData["criteria"] = criteria; // For reload, without this reloading the page causes null criteria.
}
ReportType c = (ReportType)Enum.Parse(typeof(ReportType), ReportName, true);
JavaScriptSerializer serializer = new JavaScriptSerializer();
string vmJson = string.Empty;
switch (c)
{
.....
int displayTop;
..........
case ReportType.Inventory_Counts_Report:
..............
displayTop = Convert.ToInt32(Request["DisplayTop"]);
........
return View("Counting", CountingVM);
default:
return View();
}
return View(); }
Thanks
BB
You also need to display the validation message:
#Html.ValidationMessageFor(c => c.DisplayTop)
Try
#Html.EditorFor(c => c.DisplayTop, "1000")
I guess, that the problem occures, becase your property is of type int, and the range is for type int, byt you are showing it in a textbox.
Also you need to add ModelState.IsValid in your controller. For example:
[HttpPost]
public ActionResult Create(YourModel model)
{
if(ModelState.IsValid)
{
// Put your logic here
}
return View(create)
}

Dynamic validation in ASP MVC

For a project at work I'm trying to create a process that lets a user dynamically create a form that other users could then fill out the values for. I'm having trouble figuring out how to go about getting this to play nice with the built in model binding and validation with ASP MVC 3, though.
Our view model is set up something like this. Please note that I've over simplified the example code:
public class Form
{
public FieldValue[] FieldValues { get; set; }
}
public class Field
{
public bool IsRequired { get; set; }
}
public class FieldValue
{
public Field Field { get; set; }
public string Value { get; set; }
}
And our view looks something like:
#model Form
#using (Html.BeginForm("Create", "Form", FormMethod.Post))
{
#for(var i = 0; i < Model.Fields.Count(); i++)
{
#Html.TextBoxFor(_ => #Model.Fields[i].Value)
}
<input type="submit" value="Save" name="Submit" />
}
I was hoping that we'd be able to create a custom ModelValidatorProvider or ModelMetadataProvider class that would be able to analyze a FieldValue instance, determine if its Field.IsRequired property is true, and then add a RequiredFieldValidator to that specific instance's validators. I'm having no luck with this, though. It seems that with ModelValidatorProvider(and ModelMetadataProvider) you can't access the parent container's value(ie: GetValidators() will be called for FieldValue.Value, but there's no way from there to get the FieldValue object).
Things I've tried:
In the ModelValidatorProvider, I've tried using
ControllerContext.Controller.ViewData.Model, but that doesn't work if
you have nested types. If I'm trying to figure out the validators
Form.FieldValues[3], I have no idea which FieldValue to use.
I tried using a custom ModelMetadata that tries to use the internal
modelAccessor's Target property to get the parent, but this also
doesn't work if you have a nested type. Somewhere internal to MVC, an
expression like the one in my example will result in the Target being
the Model's type(Form), not FieldValue. So I get the same problem as
above where I have no idea what instance of FieldValue to compare
against.
A class-level validation attribute that I could put on the FieldValue
class itself, but this only gets called during server validation. I
need client-side validation, too.
Is what I'm trying to do even possible in MVC? Or is there something I'm missing entirely?
One possibility is to use a custom validation attribute.
But before getting into the implementation I would like to point out a potential flaw in your scenario. The IsRequired property is part of your model. This means that when the form is submitted its value must be known so that we conditionally apply the required rule to the corresponding property. But for this value to be known when the form is submitted this means that it must be either part of the form (as a hidden or standard input field) or must be retrieved from somewhere (datastore, ...). The problem with the first approach is obvious => hidden field means that the user can set whatever value he likes, so it's no longer a real validation because it is the user that decides which field is required.
This warning being said, let's suppose that you trust your users and decide to take the hidden field approach for storing the IsRequired value. Let's see how a sample implementation:
Model:
public class Form
{
public FieldValue[] Fields { get; set; }
}
public class FieldValue
{
public Field Field { get; set; }
[ConditionalRequired("Field")]
public string Value { get; set; }
}
public class Field
{
public bool IsRequired { get; set; }
}
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new Form
{
Fields = new[]
{
new FieldValue { Field = new Field { IsRequired = true }, Value = "" },
new FieldValue { Field = new Field { IsRequired = true }, Value = "" },
new FieldValue { Field = new Field { IsRequired = false }, Value = "value 3" },
}
};
return View(model);
}
[HttpPost]
public ActionResult Index(Form model)
{
return View(model);
}
}
View:
#model Form
#using (Html.BeginForm())
{
#Html.EditorFor(x => x.Fields)
<input type="submit" value="Save" name="Submit" />
}
ConditionalRequiredAttribute:
public class ConditionalRequiredAttribute : ValidationAttribute, IClientValidatable
{
private RequiredAttribute _innerAttribute = new RequiredAttribute();
private readonly string _fieldProperty;
public ConditionalRequiredAttribute(string fieldProperty)
{
_fieldProperty = fieldProperty;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var containerType = validationContext.ObjectInstance.GetType();
var field = containerType.GetProperty(_fieldProperty);
if (field == null)
{
return new ValidationResult(string.Format("Unknown property {0}", _fieldProperty));
}
var fieldValue = (Field)field.GetValue(validationContext.ObjectInstance, null);
if (fieldValue == null)
{
return new ValidationResult(string.Format("The property {0} was null", _fieldProperty));
}
if (fieldValue.IsRequired && !_innerAttribute.IsValid(value))
{
return new ValidationResult(this.ErrorMessage, new[] { validationContext.MemberName });
}
return ValidationResult.Success;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule()
{
ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
ValidationType = "conditionalrequired",
};
rule.ValidationParameters.Add("iserquiredproperty", _fieldProperty + ".IsRequired");
yield return rule;
}
}
Associated unobtrusive adapter:
(function ($) {
$.validator.unobtrusive.adapters.add('conditionalrequired', ['iserquiredproperty'], function (options) {
options.rules['conditionalrequired'] = options.params;
if (options.message) {
options.messages['conditionalrequired'] = options.message;
}
});
$.validator.addMethod('conditionalrequired', function (value, element, parameters) {
var name = $(element).attr('name'),
prefix = name.substr(0, name.lastIndexOf('.') + 1),
isRequiredFiledName = prefix + parameters.iserquiredproperty,
requiredElement = $(':hidden[name="' + isRequiredFiledName + '"]'),
isRequired = requiredElement.val().toLowerCase() === 'true';
if (!isRequired) {
return true;
}
return value && value !== '';
});
})(jQuery);

Categories

Resources