I have a custom validation attribute
Lets say I have a HelloWorld class that implements ValidationAttribute. I then apply this attribute to a field within my API.
[HelloWorld]
public string FirstName { get; set; }
When I generate the Swagger UI, I get a JSON OpenAPI spec and the model displays the properties of each field like below:
If I add a Required tag, a asterisk is displayed
If I use attributes like RegularExpression/Range/StringLength, text appears to specify this.
However, I would like to make someone aware of the custom validation, with my own description.
Is it possible?
Any help would be much appreciated. I've spent all day looking at DocumentFilter/SchemaFilter/OperationFilter, but can't find any good documentation or examples.
You can do it like this:
Create custom validation attribute, for example UniqueAttribute
public class UniqueAttribute : ValidationAttribute { ... }
Apply this attribute to a model property
[Unique]
public string Name { get; set; }
Implement ISchemaFilter interface extension
public class AddUniquenessDescriptionFilter : ISchemaFilter
{
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
var attr = context.MemberInfo?.CustomAttributes.Where(x =>
x.AttributeType.Name == nameof(UniqueAttribute))
.FirstOrDefault();
if (attr is not null)
{
schema.Extensions.Add("isUnique", new OpenApiBoolean(true));
}
}
}
And use extension on startup
builder.Services.AddSwaggerGen(options =>
{
options.SchemaFilter<AddUniquenessDescriptionFilter>();
});
The results look like this:
Swagger documentation
Here is a full example: Custom ValidationAttribute
I was trying to use OperationFilter, but it wasn't supported on .Net Framework 4.1.
I would have to use the filters to amend the documentation (I believe this is the was x-extensions is utilised)
Related
We used DB-first approach to generate models in a .NET core application. DataAnnotations were put in a "buddy" metadata class so as to avoid writing in an autogenerated file. When controller calls TryValidateModel, all works well, Name property is required.
public partial class User
{
public string Name { get; set; }
}
[ModelMetadataType(typeof(UserMetaData))]
public partial class User : IValidatableObject
{
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) { }
}
public class UserMetaData
{
[Required]
public string Name { get; set; }
}
On a service layer of the app, we want to implement additional validation, that also checks if objects are valid in regards to data annotations. This is done via
Validator.TryValidateObject()
which successfully calls Validate method, but disregards data annotations - user is valid, even with an empty name.
TL;DR:
MVC (web project) knows how to consider data annotations put in a "buddy" class via ModelMetadataType attribute, service layer project does not.
I thought i have found the answer here, but it seems that
TypeDescriptor.AddProviderTransparent
does not work for .net core apps.
Any ideas would be greatly appreciated.
I really hoped for a one line solution :)
I abused ashrafs answer to his own question like so:
var metadataAttr = typeof(T).GetCustomAttributes(typeof(ModelMetadataTypeAttribute), true).OfType<ModelMetadataTypeAttribute>().FirstOrDefault();
if (metadataAttr != null)
{
var metadataClassProperties = TypeDescriptor.GetProperties(metadataAttr.MetadataType).Cast<PropertyDescriptor>();
var modelClassProperties = TypeDescriptor.GetProperties(typeof(T)).Cast<PropertyDescriptor>();
var errs =
from metaProp in metadataClassProperties
join modelProp in modelClassProperties
on metaProp.Name equals modelProp.Name
from attribute in metaProp.Attributes.OfType<ValidationAttribute>()
where !attribute.IsValid(modelProp.GetValue(model))
select new ValidationResult(attribute.FormatErrorMessage(Reflector.GetPropertyDisplayName<T>(metaProp.Name)), new[] { metaProp.Name });
validationResults.AddRange(errs);
}
I am looking to validate a particular request depending on values in a database. It's a complex scenario, but I will try to simplify it in an example.
Say I have the following model:
public class CustomerModel
{
public int AgencyId { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
When a POST request comes in, I need to make a call to get certain requirements for the AgencyId being passed.
var requirements = _repository.GetRequirementsForAgency(model.AgencyId);
The information I would get back from the database would tell me which properties are required, which may be different for each agency. For instance, one agency might require Name and Age where as another one might only require Name. The requirements object would look something like this:
public class Requirement
{
public string PropertyName { get; set; }
public bool IsRequired { get; set; }
}
So, my question is what would be the best way to validate this model before it gets submitted to the database? Ideally, I would like to give the Agency the ability to change these requirements, therefore, I would like to avoid hard coding validation if possible.
My first thought was to call a list of requirements and then do a foreach over each requirement searching by PropertyName and then checking to see if there was a value or not, but I wasn't sure if this was the best way.
I then looked into Data Annotations, but did not find a way to add attributes at run time.
You can use Fluent Validation library and implement custom validator
public class CustomerModelValidator : AbstractValidator<CustomerModel>
{
private readonly IRepository _repository;
public RegisterModelValidator(IRepository repository)
{
this._repository= repository;
RuleFor(x => x.AgencyId).GreaterThan(0).WithMessage("Invalid AgencyId");
RuleFor(x => x.Age).GreaterThan(0).WithMessage("Invalid Age");
Custom(c =>
{
var requirements = _repository.GetRequirementsForAgency(model.AgencyId);
\\validate each property according to requirements object.
\\if (Validation fails for some property)
return new ValidationFailure("property", "message");
\\else
return null;
});
}
}
If you use dependency injection in your project (which i strongly advice), you will have to inject relevant IRepository into an attribute. Otherwise you can just create/use a specific repository in your attribute.
A really nice thing is when you properly register your validator you will be able to validate you model with default if (ModelState.IsValid) check
I have a ViewModel that I can decorate with the [Required] attribute (see below). I've come to the point where I need to let the client control which fields are required or not. They can configure this trough XML and all this info is stored in the Model when it's first created. Now I have fields that are not decorated with [Required] but still need to get validated (as per "user settings") before submitting (for example the Phone field).
public class MyBusinessObjectViewModel
{
[Required]
public string Email { get; set; } //compulsory
public string Phone { get; set; } //not (yet) compulsory, but might become
}
If the user will not enter the Phone number, the data will still get posted. Wanting not to mess with custom validators, I just add the "data-val" and "data-val-required" attributes to the Html, like this:
Dictionary<string, object> dict = new Dictionary<string, object>();
dict.Add("data-val", "true");
dict.Add("data-val-required", "This field is required.");
#Html.TextBoxFor(x => x, dict);
This forces the client side validation for all the properties that are dynamically set as required. Is this good practice? What kind of side effects can I expect?
You should look into extending the meta model framework with your own metadata provider to do the actual binding between your site's configuration and the model metadata. You can actually set the required property flag to true on the property model metadata during the metadata creation process. I can't remember for sure whether this causes the built in editor templates to generate the attribute, but I think it does. Worst case scenario you can actually create and attach a new RequiredAttribute to the property, which is a tad bit kluggy, but works pretty well in certain scenarios.
You could also do this with IMetadataAware attributes, especially if Required is the only metadata aspect your users can customize, but the implementation really depends on what you're trying to do.
One major advantage of using a custom ModelMetadataProvider in your specific case is that you can use dependency injection (via ModelMetadataProviders) to get your customer settings persistence mechanism into scope, whereas with the data attribute you only get to write an isolated method that runs immediately after the metadata model is created.
Here is a sample implementation of a custom model metadata provider, you'd just have to change the client settings to whatever you wanted to use.
UPDATED but not tested at all
public class ClientSettingsProvider
{
public ClientSettingsProvider(/* db info */) { /* init */ }
public bool IsPropertyRequired(string propertyIdentifier)
{
// check the property identifier here and return status
}
}
public ClientRequiredAttribute : Attribute
{
string _identifier;
public string Identifier { get { return _identifer; } }
public ClientRequiredAttribute(string identifier)
{ _identifier = identifier; }
}
public class RequiredModelMetadataProvider : DataAnnotationsModelMetadataProvider
{
ClientSettings _clientSettings;
public RequiredModelMetadataProvider(ClientSettings clientSettings)
{
_clientSettings = clientSettings;
}
protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
{
// alternatively here is where you could 'inject' a RequiredAttribute into the attributes list
var clientRequiredAttribute = attributes.OfType<ClientRequiredAttribute>().SingleOrDefault();
if(clientRequiredAttribute != null && _clientSettings.IsPropertyRequired(clientRequiredAttribute.Identifier))
{
// By injecting the Required attribute here it will seem to
// the base provider we are extending as if the property was
// marked with [Required]. Your data validation attributes should
// be added, provide you are using the default editor templates in
// you view.
attributes = attributes.Union(new [] { new RequiredAttribute() });
}
var metadata = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
// REMOVED, this is another way but I'm not 100% sure it will add your attributes
// Use whatever attributes you need here as parameters...
//if (_clientSettings.IsPropertyRequired(containerType, propertyName))
//{
// metadata.IsRequired = true;
//}
return metadata;
}
}
USAGE
public class MyModel
{
[ClientRequired("CompanyName")]
public string Company { get; set; }
}
public class MyOtherModel
{
[ClientRequired("CompanyName")]
public string Name { get; set; }
public string Address { get; set; }
}
Both of these models would validate the string "CompanyName" against your client settings provider.
Not wanting to mess with custom validators, so you messed in the View obfuscating the logic of your validation by removing it from the place where it is expected to be found.
Really, don't be afraid of creating a custom attribute validator. What you are doing right now is getting a technical debt.
I have my ADO entity generated in MVC 2 and I know that if I want to put custom validation on an object I can do something like this.
[MetadataType(typeof(MyEntity_Validation))]
public partial class MyEntity
{
private sealed class MyEntity_Validation
{
[Required]
[RegularExpression("[A-Za-z][0-9]{5}")]
public string SomeField{ get; set; }
}
}
But I don't know why that works.
How does that work? Some sort of convention?
Metadata is a convention, yes. See http://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.aspx. You can add attributes to fields to enforce validation, display, concurrency, all sorts of common usefulness. Hope this helps.
ASP.NET MVC 2 will support validation based on DataAnnotation attributes like this:
public class User
{
[Required]
[StringLength(200)]
public string Name { get; set; }
}
How can I check that a current model state is valid using only pure .NET (not using MVC binding, controller methods, etc.)?
Ideally, it would be a single method:
bool IsValid(object model);
This code sample is from Steve Sanderson's blog about xVal (which uses the DataAnnotationsAttribute to validate properties). Basically, you just need to enumerate the attibutes using reflection and check IsValid():.
internal static class DataAnnotationsValidationRunner
{
public static IEnumerable<ErrorInfo> GetErrors(object instance)
{
return from prop in TypeDescriptor.GetProperties(instance).Cast<PropertyDescriptor>()
from attribute in prop.Attributes.OfType<ValidationAttribute>()
where !attribute.IsValid(prop.GetValue(instance))
select new ErrorInfo(prop.Name, attribute.FormatErrorMessage(string.Empty), instance);
}
}