Given this POCO class that was automatically generated by an EntityFramework T4 template (has not and can not be manually edited in any way):
public partial class Customer
{
[Required]
[StringLength(20, ErrorMessage = "Customer Number - Please enter no more than 20 characters.")]
[DisplayName("Customer Number")]
public virtual string CustomerNumber { get;set; }
[Required]
[StringLength(10, ErrorMessage = "ACNumber - Please enter no more than 10 characters.")]
[DisplayName("ACNumber")]
public virtual string ACNumber{ get;set; }
}
Note that "ACNumber" is a badly named database field, so the autogenerator is unable to generate the correct display name and error message which should be "Account Number".
So we manually create this buddy class to add custom attributes that could not be automatically generated:
[MetadataType(typeof(CustomerAnnotations))]
public partial class Customer { }
public class CustomerAnnotations
{
[NumberCode] // This line does not work
public virtual string CustomerNumber { get;set; }
[StringLength(10, ErrorMessage = "Account Number - Please enter no more than 10 characters.")]
[DisplayName("Account Number")]
public virtual string ACNumber { get;set; }
}
Where [NumberCode] is a simple regex based attribute that allows only digits and hyphens:
[AttributeUsage(AttributeTargets.Property)]
public class NumberCodeAttribute: RegularExpressionAttribute
{
private const string REGX = #"^[0-9-]+$";
public NumberCodeAttribute() : base(REGX) { }
}
NOW, when I load the page, the DisplayName attribute works correctly - it shows the display name from the buddy class not the generated class.
The StringLength attribute does not work correctly - it shows the error message from the generated class ("ACNumber" instead of "Account Number").
BUT the [NumberCode] attribute in the buddy class does not even get applied to the AccountNumber property:
foreach (ValidationAttribute attrib in prop.Attributes.OfType<ValidationAttribute>())
{
// This collection correctly contains all the [Required], [StringLength] attributes
// BUT does not contain the [NumberCode] attribute
ApplyValidation(generator, attrib);
}
Why does the prop.Attributes.OfType<ValidationAttribute>() collection not contain the [NumberCode] attribute? NumberCode inherits RegularExpressionAttribute which inherits ValidationAttribute so it should be there.
If I manually move the [NumberCode] attribute to the autogenerated class, then it is included in the prop.Attributes.OfType<ValidationAttribute>() collection.
So what I don't understand is why this particular attribute does not work in when in the buddy class, when other attributes in the buddy class do work. And why this attribute works in the autogenerated class, but not in the buddy. Any ideas?
Also why does DisplayName get overriden by the buddy, when StringLength does not?
I noticed that your NumberCodeAttribute doesn't specify AllowMultiple=True in the AttributeUsage attribute. The default for that parameter (if it isn't specified) is false. Try adding that on and it should appear.
I recreated your code using VS2008 and MVC2 and it worked fine for me.
Related
In my ASP.NET MVC 5 (C#) application, I have a class called Name:
public class Name
{
public virtual string? First { get; set; }
public virtual string? Middle { get; set; }
public virtual string? Last { get; set; }
public virtual string? Suffix { get; set; }
}
I also have several models containing a property (of type name) called NameFull:
[RequiredProps(nameof(Name.First), nameof(Name.Last))] // *** More about this in a minute. ***
public Name? NameFull { get; set; }
These model properties get used by a custom tag helper like this: <field asp-for="NameFull" />. There's logic inside the custom tag helper detects that the model property is a complex type and generates a custom HTML helper since HTML helpers can handle complex objects unlike tag helpers. So basically this code <field asp-for="NameFull" /> generates multiple input elements. That's the background, here's my problem...
I want to be able to make some of those inputs required on a case-by-case basis. So maybe in one model first and last name are required. In another model, none of them are required. So I created a custom attribute named RequiredPropsAttribute (see model example above). It inherits from ValidationAttribute and implements IClientModelValidator AttributeAdapter to apply client-side validation like so:
public sealed class RequiredPropsAttribute : ValidationAttribute, IClientModelValidator
{
//most of the logic has been omitted for brevity...
public void AddValidation(ClientModelValidationContext context)
{
var properties = context.ModelMetadata.Properties;
if (!context.Attributes.ContainsKey("data-val"))
{
context.Attributes.Add("data-val", "true");
}
if (!context.Attributes.ContainsKey("data-val-required"))
{
context.Attributes.Add("data-val-required", $"The {string.Join(", ", properties)} fields are all required.");
}
}
}
This does allow me to add validation to the parent property (Name), but what I want is to apply the validation to its nested children. In short, I want something similar to the above code that injects data-val="true" and data-val-required="This field is required" into the nested child inputs (Name.First, Name.Middle, Name.Last, Name.Suffix). I've tried accessing the children using context.ModelMetadata.Properties, but the collection isn't set up in such a way to where I can write code like this: property.Attributes.Add("data-val", "true"). Which leads me to my question...
QUESTION: How do I write logic inside AddValidation that lets me basically say: loop through the child properties of Name and add attributes to each of them so each child <input> gets data- validation attributes. Is this possible? And if not, is there any other way I can conditionally add a required attribute to the name properties based on attributes applied to name, e.g. add a RequiredIf to First that says "if Name has a RequiredProps attribute containing First, then make first required, etc.?
Thanks in advance for your help, everyone!
I have a generated service contract class, that I frequently refresh, where I want to include [Required] and [DefaultValue("abc")] attributes on some of the contract methods.
My solution, to prevent my code from getting blown away each refresh, is to use [ModelMetadataType] attribute. It appears to be working, but the web front-end isn't showing the red * to indicate required?
You can see aaa gets defaulted, but the required indicator does not. If I leave the field blank and run it, it has a validation error.
Generated contract class:
[System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.2")]
[System.Diagnostics.DebuggerStepThroughAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://schemas.microsoft.com/dynamics/2010/01/datacontracts")]
public partial class CallContext
{
public string Company {get; set;}
}
My metadata class:
[ModelMetadataType(typeof(MyCallContextMetadata))]
public partial class CallContext
{
}
internal class MyCallContextMetadata
{
[Required(AllowEmptyStrings =false)]
[DefaultValue("aaa")]
public string Company { get; set; }
}
I can't seems to find anything related to this issue on Google.
Please help !!
SCENARIO:
Mainly I have a WebAPI server with a controller method that expects a simple type as parameter.
That API looks like this:
public HttpResponseMessage Foo([FromBody] LoginModel form)
{
// ...some code
return this.Request.CreateResponse(HttpStatusCode.OK);
}
and the LoginType class looks like this:
public class LoginModel
{
[Required]
[EmailAddress(ErrorMessage = "Please have a Email address format")]
public string Email;
[Required]
[StringLength(20, MinimumLength = 6, ErrorMessage = "Password must be between 6 and 20 characters")]
public string Password;
}
Problem occurs when the Client tries to run the API method. I pass a json that looks like this
{ "Email" : "xxx#xxx.com" , "Password" : "oooooo" }
....I get the following exception
EXCEPTION MESSAGE:
"Field 'Email' on type 'XXXX.Models.Login' is attributed with one or more validation attributes. Validation attributes on fields are not supported. Consider using a public property for validation instead.
The same happens when I ran the api call from Fiddler !!
NOTE:
If I remove the various Attributes like [Required], it works smoothly. The client call never gets to the method when the Attributes are in place.
Help is truly appreciated !!!
Like it says:
Validation attributes on fields are not supported. Consider using a public property for validation instead.
So use properties;
public string Email { get; set; }
I've got a model for a phone number notification (users get notified of emergencies via phone numbers that they associate with their account, and they can set what order these are called in). Most of the time, the phone number part of the model is required, but there's a special case during creation of a new user where we don't want to force it.
I made a very simple child object UserCreationPhoneNotificationModel that inherits from the above described UserPhoneNotificationModel. There's a couple other small changes, but the relevant one here is overriding the PhoneNumber field so that it's no longer required.
In the parent model it's
[Required]
public virtual string PhoneNumber { get; set; }
And in the child model it's just
public override string PhoneNumber { get; set; }
I figured this would do the trick, but apparently not. I thought the issue would be that RequiredAttribute would have Inherited = true on it, but it doesn't so I'm not entirely sure why it's being inherited into the subclass.
I did double check to make sure, and removing Required from the field in the parent also made the field in the subclass not required, so it's definitely some kind of inheritance thing.
It can be done by Hiding the existing Property by a new property of the same name.
So, if the property is like this in the parent class:
[Required]
public virtual string PhoneNumber { get; set; }
You can define the new property in the child class like this:
public new string PhoneNumber { get; set; }
This will hide the PhoneNumber Property along with its Attributes in the child class. Now you can use any other attributes with it. For example, you can add [PhoneNumber] attribute to the property in the child class.
Using inheritance to share behaviour when the relationship you're modelling doesn't fit can be problematic. You do not typically get any benefit from using inheritance to share behaviour between ViewModels, while you can (and, as in your case, do) encounter problems.
Your best bet is to use a different model class for each use-case. If you genuinely need to share behaviour between ViewModels, you can do so with composition.
below example may be help you.
public class SigninModel
{
[Required]
[EmailAddress]
public virtual string email { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
public string password { get; set; }
}
This is Signin Model and i have inherit to another model but email is not required it's code as below:
public class ExternalLoginModel : SigninModel
{
public override string email { get; set; }
public string LoginProvider { get; set; }
public string ProviderKey { get; set; }
}
I have override the email property in another model
If Using C# 4.0,
How about writing the 'new' Constructor.
public new string PhoneNumber { get; set; }
I'm not sure that it works good.
It makes no sense to not inherit an attribute.
Removing the [Required] attribute when overriding essentially violates LSP.
If NotRequiredDerived is a subtype of RequiredBase, then objects of type RequiredBase may be replaced with objects of type NotRequiredDerived (i.e. an object of type RequiredBase may be substituted with any object of a subtype NotRequiredDerived) without altering any of the desirable properties of the program.
To put it in simple code:
var requiredPhoneNumber = new RequiredBase() { PhoneNumber = "123456789" };
HandleRequiredBaseObject(requiredPhoneNumber); //Works
var optionalPhoneNumber = new NotRequiredDerived() { PhoneNumber = null };
HandleRequiredBaseObject(optionalPhoneNumber); //Fails
HandleRequiredBaseObject(RequiredBase obj) inherently assumes that the PhoneNumber is required (as per the definition of the RequiredBase class); and it's not expecting to received a derived object which lacks this constraint! This is going to result in runtime exceptions.
The only way to not violate LSP would be to ensure that the [Required] constraint is not violated on derived classes; which means that there's no point to trying to remove the [Required] annotation in the first place.
I can think of one theoretical case where not inheriting the attribute would make sense: if the attribute expands the range of valid options, as opposed to limiting it.
Suppose we make a class PositiveNumber and an attribute that sets it to also allow negative numbers:
public class Base
{
[NegativesAllowed]
public PositiveNumber Number { get; set; }
}
public class Derived : Base
{
public PositiveNumber Number { get; set; }
}
If Base allows all numbers, and Derived only allows positive numbers, then it doesn't inherently violate LSP.
However, this seems like a very forced situation. Using a limited type which is then broadened by an attribute is not something you're going to realistically encounter.
For your case, you simply shouldn't be inheriting one viewmodel from another.
The fact that there are different attributes required is the main reason why you shouldn't be inheriting these classes! They are supposed to work differently from one another, and therefore should not pretend like one is a derivation of the other.
I am working on asp.net mvc 2 web application.
I have model with 3 properties:
[IsCityInCountry("CountryID", "CityID"]
public class UserInfo
{
[Required]
public int UserID { get; set; }
[Required]
public int CountryID { get; set; }
[Required]
public int CityID { get; set; }
}
I have one "required" property attribute, and one attribute on class level :
using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
public class IsCityInCountry : ValidationAttribute
{
public IsCityInCountry(string countryIDProperty, string cityIDProperty)
{
CountryIDProperty = countryIDProperty;
CityIDProperty = cityIDProperty;
}
public string CountryIDProperty { get; set; }
public string CityIDProperty { get; set; }
public override bool IsValid(object value)
{
var properties = TypeDescriptor.GetProperties(value);
var countryID = properties.Find(CountryIDProperty, true).GetValue(value);
var cityID = properties.Find(CityIDProperty , true).GetValue(value);
int countryIDInt;
int.TryParse(countryID.ToString(), out countryIDInt);
int cityIDInt;
int.TryParse(cityID.ToString(), out cityIDInt);
if (CountryBusiness.IsCityInCountry(countryIDInt, cityIDInt))
{
return true;
}
return false;
}
}
When I post the form on my view, and CountryID is not entered, in ModelState dictionary there's an error about that issue. Other attribute is ignored ("IsCityInCountry"). When I choose CountryID and CityID, which is not in selected country, I get appropriate validation message about that, and ModelState has another key (which is ""). I understand that advantage have property attributes and then class attributes. My question; is there any way to get all validation messages at the same time, no matter what kind of attributes are involved (class or property attributes)? Thanks in advance.
ASP.NET MVC won't perform class level validation if there are property level validation errors. Brad Wilson explains this in his blog post:
Earlier today, we committed a change to MVC 2 that converted the
validation system from Input Validation to Model Validation.
What this means is that we will always run all validators on an
object, if that object had at least one value bound into it during
model binding. We run the property-level validators first, and if all
of those succeed, we'll run the model-level validators.
I would recommend you to go ahead and checkout FluentValidation.NET if you want to perform some more advanced validation in an ASP.NET MVC application. Declarative validation simply doesn't fit the bill in advanced validation scenarios.