Multiple dynamic Submitbuttons in MVC - c#

I am currently developing a Framework to generate dynamic Views in MVC, the idea is based on this tutorial.
The next step is adding the possibility to generate multiple submit buttons but I cant get it to work. I did some research and found this approach. However, since i want to generate those buttons dynamically, this does not work yet.
What I tried is to modify this attribute code here:
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class MultipleButtonAttribute : ActionNameSelectorAttribute
{
public string Name { get; set; }
public string Argument { get; set; }
public override bool IsValidName(ControllerContext controllerContext, string actionName, MethodInfo methodInfo)
{
var isValidName = false;
var keyValue = string.Format("{0}:{1}", Name, Argument);
var value = controllerContext.Controller.ValueProvider.GetValue(keyValue);
if (value != null)
{
controllerContext.Controller.ControllerContext.RouteData.Values[Name] = Argument;
isValidName = true;
}
return isValidName;
}
}
While debugging I digged down the ValueProvider and found out that the JQueryFormValueProvider in the Collection of Valueproviders actually contains the Name of the clicked submitbutton.
The button is generated like this:
<input type="submit" name="buttonClick:#Model.Id" value="#Model.Text" />
Unfortunately the ValueProviders do not let me Iterate through the Keys so I dont know how to get the value I circled red in the above screenshot.
It doesnt need to be this approach, all what counts is, to find out which button was clicked.

Ok i found the solution by modifying the Attribute-code like this:
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class NameToRouteDataAttribute : ActionNameSelectorAttribute
{
/// <Summary>Name of the Value you want to grab from the control.</Summary>
public string Name { get; set; }
public override bool IsValidName(ControllerContext controllerContext, string actionName, MethodInfo methodInfo)
{
ValueProviderCollection providers = controllerContext.Controller.ValueProvider as ValueProviderCollection;
if (providers != null)
{
// We need this value-Provider as it contains the Data from the Form
JQueryFormValueProvider formProvider = providers.OfType<JQueryFormValueProvider>().FirstOrDefault();
if (formProvider != null)
{
// now look for the specified value-prefix.
var kvp = formProvider.GetKeysFromPrefix(Name).FirstOrDefault();
if (kvp.Key != null)
controllerContext.Controller.ControllerContext.RouteData.Values[Name] = kvp.Key;
}
}
return true;
}
}
Contollercode:
[HttpPost]
[NameToRouteDataAttribute(Name = "buttonClick")]
public ActionResult Show(FormViewModel form)
{
string buttonId = RouteData.GetRequiredString("buttonClick");
...
}
Buttoncode
#model MvcForms.Controls.ButtonViewModel
<input type="submit" name="buttonClick.#Model.Id" value="#Model.Text" />
The important thing here is, that the name of the Button contains a dot. This makes the JQueryFormValueProvider.GetKeysFromPrefix work the way I need it to.

Related

How to use an alternative auto-generated identifier upon rendering multiple input elements with the same property name?

Let's assume that I have a view model like this
public class ExampleVM
{
[Display(Name = "Foo")]
public Nullable<decimal> FooInternal { get; set; }
}
My view looks like this (also a form tag which I omitted in this post)
#model ExampleVM
....
<input asp-for="FooInternal" class="form-control" type="number" />
This results in a rendered text box with FooInternal as id-attribute.
In my scenario, I also have a modal dialog with another form with another view model which shares a property with the same name. I know that the asp-for taghelper renders the id-attribute either a manually from a specified id or infers the id from the property name.
In my backend code, I want to be able to name my properties how I seem fit given the view model context. I don't want to rename my properties to make them globally unique.
I try to avoid two things:
To manually specify the id in the view / the input element. I'd much rather use an autogenerated id that I can set via another attribute in the backend.
Given that I use the view model with [FromBody] in a post, I can't exactly rename the property as it would be with [FromRoute(Name="MyFoo")]. I don't want to map a manually entered id back to my property.
Basically, I'm looking for something like this:
public class ExampleVM
{
[Display(Name = "Foo")]
[HtmlId(Name = "MyUniqueFooName")]
public Nullable<decimal> FooInternal { get; set; }
}
where HtmlId would be an attribute that interacts with the tag-helper for rendering and also for rebinding the view model as parameter from a [HttpPost] method.
Maybe another approach is also valid since avoiding multiple input elements (with the same identifier) in multiple forms on the same page seems like a common situation to me.
According to your description, if you want to achieve your requirement, you should write custom modelbinding and custom input tag helper to achieve your requirement.
Since the asp.net core modelbinding will bind the data according to the post back's form data, you should firstly write the custom input tag helper to render the input name property to use HtmlId value.
Then you should write a custom model binding in your project to bind the model according to the HtmlId attribute.
About how to re-write the custom input tag helper, you could refer to below steps:
Notice: Since the input tag helper has multiple type "file, radio,checkbox and else", you should write all the logic based on the source codes.
According to the input taghelper source codes, you could find the tag helper will call the Generator.GenerateTextBox method to generate the input tag html content.
The Generator.GenerateTextBox has five parameters, the third parameter expression is used to generate the input textbox's for attribute.
Generator.GenerateTextBox(
ViewContext,
modelExplorer,
For.Name,
modelExplorer.Model,
format,
htmlAttributes);
If you want to show the HtmlId value as the name for the for attribute, you should create a custom input taghelper.
You should firstly create a custom attribute:
[System.AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)]
public class HtmlId : Attribute
{
public string _Id;
public HtmlId(string Id) {
_Id = Id;
}
public string Id
{
get { return _Id; }
}
}
Then you could use var re = ((Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.DefaultModelMetadata)For.ModelExplorer.Metadata).Attributes.PropertyAttributes.Where(x => x.GetType() == typeof(HtmlId)).FirstOrDefault(); to get the htmlid in the input tag helper's GenerateTextBox method.
Details, you could refer to below custom input tag helper codes:
using Microsoft.AspNetCore.Mvc.TagHelpers;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc.Rendering;
namespace SecurityRelatedIssue
{
[HtmlTargetElement("input", Attributes = ForAttributeName, TagStructure = TagStructure.WithoutEndTag)]
public class CustomInputTagHelper: InputTagHelper
{
private const string ForAttributeName = "asp-for";
private const string FormatAttributeName = "asp-format";
public override int Order => -10000;
public CustomInputTagHelper(IHtmlGenerator generator)
: base(generator)
{
}
public override void Process(TagHelperContext context, TagHelperOutput output)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (output == null)
{
throw new ArgumentNullException(nameof(output));
}
// Pass through attributes that are also well-known HTML attributes. Must be done prior to any copying
// from a TagBuilder.
if (InputTypeName != null)
{
output.CopyHtmlAttribute("type", context);
}
if (Name != null)
{
output.CopyHtmlAttribute(nameof(Name), context);
}
if (Value != null)
{
output.CopyHtmlAttribute(nameof(Value), context);
}
// Note null or empty For.Name is allowed because TemplateInfo.HtmlFieldPrefix may be sufficient.
// IHtmlGenerator will enforce name requirements.
var metadata = For.Metadata;
var modelExplorer = For.ModelExplorer;
if (metadata == null)
{
throw new InvalidOperationException();
}
string inputType;
string inputTypeHint;
if (string.IsNullOrEmpty(InputTypeName))
{
// Note GetInputType never returns null.
inputType = GetInputType(modelExplorer, out inputTypeHint);
}
else
{
inputType = InputTypeName.ToLowerInvariant();
inputTypeHint = null;
}
// inputType may be more specific than default the generator chooses below.
if (!output.Attributes.ContainsName("type"))
{
output.Attributes.SetAttribute("type", inputType);
}
// Ensure Generator does not throw due to empty "fullName" if user provided a name attribute.
IDictionary<string, object> htmlAttributes = null;
if (string.IsNullOrEmpty(For.Name) &&
string.IsNullOrEmpty(ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix) &&
!string.IsNullOrEmpty(Name))
{
htmlAttributes = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase)
{
{ "name", Name },
};
}
TagBuilder tagBuilder;
switch (inputType)
{
//case "hidden":
// tagBuilder = GenerateHidden(modelExplorer, htmlAttributes);
// break;
//case "checkbox":
// tagBuilder = GenerateCheckBox(modelExplorer, output, htmlAttributes);
// break;
//case "password":
// tagBuilder = Generator.GeneratePassword(
// ViewContext,
// modelExplorer,
// For.Name,
// value: null,
// htmlAttributes: htmlAttributes);
// break;
//case "radio":
// tagBuilder = GenerateRadio(modelExplorer, htmlAttributes);
// break;
default:
tagBuilder = GenerateTextBox(modelExplorer, inputTypeHint, inputType, htmlAttributes);
break;
}
if (tagBuilder != null)
{
// This TagBuilder contains the one <input/> element of interest.
output.MergeAttributes(tagBuilder);
if (tagBuilder.HasInnerHtml)
{
// Since this is not the "checkbox" special-case, no guarantee that output is a self-closing
// element. A later tag helper targeting this element may change output.TagMode.
output.Content.AppendHtml(tagBuilder.InnerHtml);
}
}
}
private TagBuilder GenerateTextBox(
ModelExplorer modelExplorer,
string inputTypeHint,
string inputType,
IDictionary<string, object> htmlAttributes)
{
var format = Format;
if (string.IsNullOrEmpty(format))
{
if (!modelExplorer.Metadata.HasNonDefaultEditFormat &&
string.Equals("week", inputType, StringComparison.OrdinalIgnoreCase) &&
(modelExplorer.Model is DateTime || modelExplorer.Model is DateTimeOffset))
{
// modelExplorer = modelExplorer.GetExplorerForModel(FormatWeekHelper.GetFormattedWeek(modelExplorer));
}
else
{
//format = GetFormat(modelExplorer, inputTypeHint, inputType);
}
}
if (htmlAttributes == null)
{
htmlAttributes = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
}
htmlAttributes["type"] = inputType;
if (string.Equals(inputType, "file"))
{
htmlAttributes["multiple"] = "multiple";
}
var re = ((Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.DefaultModelMetadata)For.ModelExplorer.Metadata).Attributes.PropertyAttributes.Where(x => x.GetType() == typeof(HtmlId)).FirstOrDefault();
return Generator.GenerateTextBox(
ViewContext,
modelExplorer,
((HtmlId)re).Id,
modelExplorer.Model,
format,
htmlAttributes);
}
}
}
Improt this taghelper in _ViewImports.cshtml
#addTagHelper *,[yournamespace]
Model exmaple:
[Display(Name = "Foo")]
[HtmlId("test")]
public string str { get; set; }
Result:
Then you could write a custom model binding for the model to bind the data according to the htmlid. About how to use custom model binding, you could refer to this article.

how to redirect to different view when we click on page numbers in mvc asp.net

i have implemented one web grid with pagination enabled using webgridextension.cs file. Same page have multiple buttons to perform different actions like search, get excel, get pdf. in order to perform different form submits i used multiplebutton with action selector attribute. when i click on page number down to the grid it was navigating to default action index.
Here i want to navigate to different action GetRFQData with model (already loaded) in the page..
can you please help me on this.
WebGrid grid = new WebGrid(null, rowsPerPage: 25, canPage: true, defaultSort: "RFQID");
grid.Bind(Model != null ? Model.RFQSearchResults != null ? Model.RFQSearchResults
: new List<Shipsurance.Model.RFQ>()
: new List<Shipsurance.Model.RFQ>(), rowCount: Model != null ? Model.TotalRowsCount :25, autoSortAndPage: false);
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class MultipleButtonAttribute : ActionNameSelectorAttribute
{
public string Name { get; set; }
public string Argument { get; set; }
public override bool IsValidName(ControllerContext controllerContext, string actionName, MethodInfo methodInfo)
{
var isValidName = false;
var keyValue = string.Format("{0}:{1}", Name, Argument);
var value = controllerContext.Controller.ValueProvider.GetValue(keyValue);
if (value != null)
{
controllerContext.Controller.ControllerContext.RouteData.Values[Name] = Argument;
isValidName = true;
}
return isValidName;
}
}
[MultipleButton(Name = "action", Argument = "GetRFQData")]
public ActionResult GetData(Shipsurance.Model.RFQ rfqModelData)
{
SerachRFQ getRFQDetails = new SerachRFQ();
return View("Index", getDetails.getResults(rfqModelData));
}
You can use RedirectToAction() method.
return RedirectToAction("Your action", model);

Custom validation attribute calling another validation attribute

I want to create a custom validation attribute that calls other validation attributes.
For example I want to create an attribute called PasswordValidationAttribute. I want it to decorate the property it is defined on with RequiredAttribute, RegularExpressionAttribute and StringLengthAttribute with various parameters defined (such as the regular expression for a password and a maximum and minimum string length).
I'm struggling on where to begin, ascertain how much work is involved and determine if it is at all possible. Once this attribute is applied to a property I would like it to process the ValidationMessageFor HtmlHelper correctly and do a serverside call. I'm hoping I don't need to redefine them (otherwise it will be too much work).
For .net 4 it could look like:
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
public class MyValidationAttribute : ValidationAttribute
{
private readonly bool isRequired;
public string Regex { get; set; }
public int StringLength { get; set; }
public MyValidationAttribute(bool isRequired)
{
this.isRequired = isRequired;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var composedAttributes = ConstructAttributes().ToArray();
if (composedAttributes.Length == 0) return ValidationResult.Success;
var errorMsgBuilder = new StringBuilder();
foreach (var attribute in composedAttributes)
{
var valRes = attribute.GetValidationResult(value, validationContext);
if (valRes != null && !string.IsNullOrWhiteSpace(valRes.ErrorMessage))
errorMsgBuilder.AppendLine(valRes.ErrorMessage);
}
var msg = errorMsgBuilder.ToString();
if (string.IsNullOrWhiteSpace(msg))
return ValidationResult.Success;
return new ValidationResult(msg);
}
private IEnumerable<ValidationAttribute> ConstructAttributes()
{
if (isRequired)
yield return new RequiredAttribute();
if (Regex != null)
yield return new RegularExpressionAttribute(Regex);
if (StringLength > 0)
yield return new StringLengthAttribute(StringLength);
}
}
Usage:
[MyValidationAttribute(true, Regex = "[a-z]*", StringLength = 3)]
public string Name { get; set; }
In .net 3.5 there is a limitation, that you cannot dynamically construct the message value from underlying attributes (at least I was not able get to through it). You can set only one message per whole attribute.
Everything changed is inside method IsValid.
public override bool IsValid(object value)
{
var composedAttributes = ConstructAttributes().ToArray();
if (composedAttributes.Length == 0) return true;
return composedAttributes.All(a => a.IsValid(value));
}
Note to ErrorMessage:
Return value of IsValid method of ValidationAttribute in .net 3.5 is not ValidationResult but bool. When I tried to set the ErrorMessage, I got the error that value can be set only once.

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

Validation of objects in ViewModel not adding CSS validation classes

I have the following view models:
public class Search {
public int Id { get; set; }
[Required(ErrorMessage = "Please choose a name.")]
public string Name { get; set; }
[ValidGroup(ErrorMessage = "Please create a new group or choose an existing one.")]
public Group Group { get; set; }
}
public class Group {
public int Id { get; set; }
public string Name { get; set; }
}
I have defined a custom validation attribute as follows:
public class ValidGroupAttribute : ValidationAttribute {
public override bool IsValid(object value) {
if (value == null)
return false;
Group group = (Group)value;
return !(string.IsNullOrEmpty(group.Name) && group.Id == 0);
}
}
I have the following view (omitted some for brevity):
#Html.ValidationSummary()
<p>
<!-- These are custom HTML helper extensions. -->
#Html.RadioButtonForBool(m => m.NewGroup, true, "New", new { #class = "formRadioSearch", id = "NewGroup" })
#Html.RadioButtonForBool(m => m.NewGroup, false, "Existing", new { #class = "formRadioSearch", id = "ExistingGroup" })
</p>
<p>
<label>Group</label>
#if (Model.Group != null && Model.Group.Id == 0) {
#Html.TextBoxFor(m => m.Group.Name)
}
else {
#Html.DropDownListFor(m => m.Group.Id, Model.Groups)
}
</p>
The issue I'm having is the validation class input-validation-error does not get applied to the Group input. I assume this is because the framework is trying to find a field with id="Group" and the markup that is being generated has either id="Group_Id" or id=Group_Name. Is there a way I can get the class applied?
http://f.cl.ly/items/0Y3R0W3Z193s3d1h3518/Capture.PNG
Update
I've tried implementing IValidatableObject on the Group view model instead of using a validation attribute but I still can't get the CSS class to apply:
public class Group : IValidatableObject
{
public int Id { get; set; }
public string Name { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
if (string.IsNullOrEmpty(Name) && Id == 0) {
yield return new ValidationResult("Please create a new group or select an existing one.", new[] { "Group.Name" });
}
}
}
Update 2
Self validation doesn't work. I think this is because the second parameter in the ValidationResult constructor isn't used in the MVC framework.
From: http://www.devtrends.co.uk/blog/the-complete-guide-to-validation-in-asp.net-mvc-3-part-2
In some situations, you might be tempted to use the second constructor overload of ValidationResult that takes in an IEnumerable of member names. For example, you may decide that you want to display the error message on both fields being compared, so you change the code to this:
return new ValidationResult(
FormatErrorMessage(validationContext.DisplayName), new[] { validationContext.MemberName, OtherProperty });
If you run your code, you will find absolutely no difference. This is because although this overload is present and presumably used elsewhere in the .NET framework, the MVC framework completely ignores ValidationResult.MemberNames.
I've come up with a solution that works but is clearly a work around.
I've removed the validation attribute and created a custom model binder instead which manually adds an error to the ModelState dictionary for the property Group.Name.
public class SearchBinder : DefaultModelBinder {
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext,
PropertyDescriptor propertyDescriptor) {
if (propertyDescriptor.Name == "Group" &&
bindingContext.ValueProvider.GetValue("Group.Name") != null &&
bindingContext.ValueProvider.GetValue("Group.Name").AttemptedValue == "") {
ModelState modelState = new ModelState { Value = bindingContext.ValueProvider.GetValue("Group.Name") };
modelState.Errors.Add("Please create a new group or choose an existing one.");
bindingContext.ModelState.Add("Group.Name", modelState);
}
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
}
}
// Register custom model binders in Application_Start()
ModelBinders.Binders.Add(typeof(SearchViewModel), new SearchBinder());
With ModelState["Group.Name"] now having an error entry, the CSS class is being rendered in the markup.
I would much prefer if there was a way to do this with idiomatic validation in MVC though.
Solved!
Found a proper way to do this. I was specifying the wrong property name in the self validating class, so the key that was being added to the ModelState dictionary was Group.Group.Name. All I had to do was change the returned ValidationResult.
yield return new ValidationResult("Please create a new group or select an existing one.", new[] { "Name" });

Categories

Resources