I am building an MVC 5 app and have come to the point where I need to validate user input.
I would like to apply a [Required] attribute to a class that is not a built-in data type. Here is a snippet of my view model:
public class GraffitiViewModel : EformBase
{
[Required(ErrorMessage = "Please select yes or no")]
public RadioButtonList<YesNoType> GraffitiOffensive { get; set; }
[Required(ErrorMessage = "Please select yes or no")]
public RadioButtonList<YesNoType> GraffitiTag { get; set; }
// ... more stuff here
}
The RadioButtonList is a class that emits HTML markup for corresponding C# radio button definitions. The [Required] attribute is not working in this context. Is there a way I can extend either my RadioButtonList class, or the [Required] attribute, so I don't have to modify my ViewModel?
I am thinking along the lines of a custom attribute that will perform this validation or a method in my RadioButtonList that will return a bool indicating whether or not the validation succeeded.
Looking forward to your responses!
M
The [Required] attribute should fire if:
the property is null
OR
the property is a string type, which is either empty or whitespace
See MSDN for more details.
Alternatively you can use the code here to create a custom attribute which fires on whatever conditions you decide.
Related
I am using NancyFX for my API Gateway and I have a model such as the following:
public class CreatePerson
{
[Required]
public string FirstName {get;set;}
[Required]
public string LastName {get;set;}
[Required]
[Phone]
public string Phone {get;set;}
[Required]
[MyCustomValidationRule]
public string ImagePath {get;set;}
}
It uses both a custom MyCustomValidationRule attribute as well as the provided Phone and Required attributes.
In my module I have the following:
public class PersonModule
{
public PersonModule()
{
Post["/",true] = async (context,cancel)=>
{
var request = this.BindAndValidate<CreatePerson>();
if(!ModelValidationResult.IsValid)
{
//THIS NEVER HITS
}
}
}
}
The [Required] attributes are working and if I omit any of the properties, it works fine. However, if I pass in an invalid phone (such as sdfsdgsdg or I do something that clearly breaks MyCustomValidationRule attribute, it is not caught. Furthermore, I have placed a breakpoint in the constructor of the MyCustomValidationRule attribute, and it never hits.
Why is this not hitting?
#TheJediCowboy. I had the same problem in my project.
I can't check my theory now, but I think you should add ValidationAdapter for each kind of attribute, example you can find on github
I think only 4 validation attribute will work in default package(Range, Regex, Required, StringLength), please check list of validation adapters github
For custom attributes required add custom adapter, I think.
UPDATED
This solution checked and works good.
For validation attribute (except Range, Regex, Required and StringLength) required add validation adapter (or override exiting). Nancy use all validation adapters that are in the project.
I have a partial view that displays a number of inputs based on a view model. In some situations, some of those inputs are not rendered by the partial view, but are still decorated with [Required] attributes in the view model. As a result, when the form is posted back to my controller, ModelState.IsValid returns false. Is there a way to bypass this?
You can use Foolproof to validate your fields conditionally. This way, they'll be required only when they need to, as you can see in the example of the link.
private class Person
{
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
public bool Married { get; set; }
[RequiredIfTrue("Married")]
public string MaidenName { get; set; }
}
In this example, MaidenName will only change your ModelState.IsValid to false if Married == true
I'd recommend separating your validation from your base model.
public class MyModel
{
public string MyString { get; set; }
public string MyHiddenField { get; set; }
}
public interface IMyModel_ValidateMystringOnly
{
[Required]
string MyString { get; set; }
}
[MetadataType(TypeOf(IMyModel_ValidateMystringOnly))]
public class MyModel_ValidateMystringOnly : MyModel
This allows you to create any number of validation types, and only validate what you want when you want.
public ActionResult ShowMyModel()
{
var model = new MyModel(); // or Respository.GetMyModel() whatever..
View(model);
}
public ActionResult ValidateModel(MyModel_ValidateMystringOnly model)
{
if (ModelState.IsValid)
{
// Hey Validation!
}
// MyModel_ValidateMyStringOnly is a MyModel
// so it can be passed to the same view!
return View("ShowMyModel", model);
}
This is just an example, but should be clear on how-to reuse the same model with or without validation.
I have used method at times where the form changes slightly based on specific DropDown or Radio Button selections.
Inside your Action method before you check ModelState.IsValid you can do something like ModelState.Remove("Object.PropertyName")
Note: The property name should be the same as the ID rendered to the client. Use a "." for any underscores.
If isSomeCondition Then
ModelState.Remove("Property1")
ModelState.Remove("Property2")
End If
If ModelState.IsValid() Then
...
End If
You should always separate your VIEW model from your DOMAIN model. There is a very good reason for this and it has to do with security. When you use your domain models as your view models you are vulnerable to an overposting and/or underposting attacks. You can read more about it on these pages:
http://odetocode.com/blogs/scott/archive/2012/03/12/complete-guide-to-mass-assignment-in-asp-net-mvc.aspx
http://blogs.msdn.com/b/rickandy/archive/2012/03/23/securing-your-asp-net-mvc-4-app-and-the-new-allowanonymous-attribute.aspx
https://hendryluk.wordpress.com/tag/asp-net-mvc/
In short if you don't need a field then it should not be in your view model. You should convert - map your view models to domain models. Although it can be tedious it makes your application much more secure. There are libraries you can use to help you with mapping such as Automapper.
EDIT: Since my original answer, I have come to a conclusion that the easiest way to deal with this type of scenario is to have your view model implement IValidatableObject interface and then write your validation logic inside the Validate method. It does not give you client side validation but it is the most effective and clean way to accomplish custom/scenario based validation without writing your own custom filters.
You can read more about it here: http://weblogs.asp.net/scottgu/class-level-model-validation-with-ef-code-first-and-asp-net-mvc-3
The title says it all, but I'll add a bit of background here.
Until recently, I've been using MVC's already-written CompareAttribute to compare two values, in this case a password and its confirmation. It's worked well, except this attribute does not display the display name, set by the [Display(Name = "Name")] attribute of the property being compared.
Here are the two properties being compared:
[Required]
[Display(Name = "New Password")]
public string New { get; set; }
[Compare("New")]
[Display(Name = "Confirm Password")]
public string ConfirmPassword { get; set; }
The validation message reads as follows:
'Confirm Password' and 'New' do not match.
This works, but it's obviously not as good as it should be. The New should read as New Password, as specified by the Display attribute.
I have gotten this working, although not completely. The following implementation (for some reason) fixes the issue of not getting the specified name of the property, but I'm not sure why:
public class CompareWithDisplayNameAttribute : CompareAttribute
{
public CompareWithDisplayNameAttribute(string otherProperty)
: base(otherProperty)
{
}
}
Now, even though this works, client-side validation does not work. I've received an answer in another question that suggests using something like this
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(CompareWithDisplayName), typeof(CompareAttributeAdapter))
in my Global.asax, however the CompareAttributeAdapter doesn't actually exist.
So here I am. I've got the Display attribute being used properly by my custom CompareWithDisplayName attribute, but client-side validation missing altogether.
How can I make client-side validation work with this solution in the cleanest way possible?
If you want your custom compare attribute to work with clientside validation you will need to implement IClientValidatable. This has GetValidationRules which is where you can do any custom validation you might wish.
Example
public class CompareWithDisplayNameAttribute : CompareAttribute, IClientValidatable
{
public CompareWithDisplayNameAttribute(string otherProperty)
: base(otherProperty)
{
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(
ModelMetadata metadata, ControllerContext context)
{
// Custom validation goes here.
yield return new ModelClientValidationRule();
}
}
I have a viewmodel with two other models in it. Both have fields that are required(done with entity framework).
public class featureModel
{
public FEATURE FEATURE { get; set; }
public REQUIREMENTS REQUIREMENTS { get; set; }
}
On my page I have a dropdownlist, which is populated like this
ViewBag.FEATURE_ID = new SelectList(db.FEATURE, "FEATURE_ID", "Name_");
on view
#Html.DropDownList("FEATURE_ID", "ADD FEATURE")
Which will be a dropdownlist of all the features found, and on the top a ADD FEATURE option(when this is selected, i have some js to show the fields the user should input). IF this option is selected, then the user would need to input certain fields, if not they should not(and the fields are hidden). When my page validates, it requires that this needs to be populated, which makes sense in terms of validation.
Is there a way that I can set a condition to ignore this validation if the dropdownlist is on a certain option?
I guess you want to ignore the jquery validation for hidden fields. You can try this.
var validator = $("#formId").data('validator');
validator.settings.ignore = ":hidden";
At the server-side clear the errors from the ModelState as said by #Forty-Two
You implement System.ComponentModel.DataAnnotations.IValidatableObject on your ViewModel and perform validation there:
public class featureModel : IValidatableObject
{
public FEATURE FEATURE { get; set; }
public REQUIREMENTS REQUIREMENTS { get; set; }
public bool FeaturesRequired { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (featuresRequired)
{
// do your feature check here
if (FEATURE == null)
{
yield return new ValidationResult("You must enter a feature.");
}
}
}
}
Have a hidden field on your view that provides a value for FeaturesRequired (you could do this with javascript so onchange of your drop down) which can then be checked on model validation.
IValidatable with unobtrusive ajax (client-side validation)
Have a look at this resource for validating IValidatableObject on client-side.
If you are referring to ModelState errors in your controller when you say "when my page validates", then you can use
ModelState.Clear()
to clear all errors or
ModelState.Remove(string key)
to clear specific errors. You can check the value of the dropdown list and then decide which model errors (if any) to remove.
EDIT
ignore jquery validation
$("#formId").validate({ //Your form id
ignore: "#ignoreFieldId" //you can use any selector you want here: class, etc
})
If I understand the [ScaffoldColumn(false)] attribute correctly, I should be able to decorate a variable with this and then, when I create a strongly-typed Edit view, that field will show up as hidden text and NOT a Label/Textbox pair.
I am using entity framework and then adding a partial class with an inner metadata class like so:
[MetadataType(typeof(AlumniInterest_Metadata))]
public partial class AlumniInterest
{
private class AlumniInterest_Metadata
{
[ScaffoldColumn(false)]
[DisplayName("Person Id")]
[StringLength(8)]
public object person_id { get; set; }
[DisplayName("Interest")]
[StringLength(35)]
public string interest_desc { get; set; }
}
}
This partial is in the same namespace as the EF generated class and the DisplayName attribute IS being picked up so I think things are wired correctly. I tried changing the type from string to object (based on some google search results) but that did nothing.
Anyone else run into this problem? Have I made a newb error?
The MVC tooling does not reason about ScaffoldColumnAttribute. This attribute is only used when you invoke the Html.DisplayForModel or Html.EditorForModel methods.
If you wanted the Add View dialog to honor ScaffoldColumnAttribute you could edit the T4 template file that's used to generate a View.
The [ScaffoldColumn(false)] does not seem to work as you would expected. You will need to set
Html.HiddenFor(model => model.person_id)
in your view manually.