Reputation: 20585
I have a very simple Model, that needs to get validated from Database
public class UserAddress
{
public string CityCode {get;set;}
}
CityCode
can have values that are only available in my database table.
I know i can do something like.
[HttpPost]
public ActionResult Address(UserAddress model)
{
var connection = ; // create connection
var cityRepository = new CityRepository(connection);
if (!cityRepository.IsValidCityCode(model.CityCode))
{
// Added Model error
}
}
This seems very WET
as I have to use this model at lot of placed and adding the same logic each place seems like i am not using MVC Architecture properly.
So, what is the best pattern to validate model from Database ?
NOTE:
Most of the validation are single field lookup from Database, other validation may include combination of field. But right now I am happy with single field lookup validation, as long as it is DRY
and is not using too much reflection it is acceptable.
NO CLIENT SIDE VALIDATION : To anyone who is answering in terms of client side validation, I do not need any such validation, most of my validation are server sided, and I need the same, please do not answer with Client Side Validation methods.
P.S. If any one can give me hint how to do a attribute based validation from Database, will be extremely greatful.
Upvotes: 27
Views: 18985
Reputation: 1504
Update 2022 asp.net core 6
With asp.net core 7 should be much more compact because of Generic Attributes. My idea is to make an abstract class for getting service through generics. And an interface for the validator. It is not tested yet.
Attribute class:
From ValidationContext is possible to get your service.
namespace Cart.Validation;
using System.ComponentModel.DataAnnotations;
public class ProductExistsAttribute : ValidationAttribute
{
protected override ValidationResult? IsValid(object? value, ValidationContext validationContext)
{
var validator = (ProductValidator?)validationContext.GetService(typeof(ProductValidator));
if (validator == null)
throw new Exception("The validator is null. Did you register it?");
if (String.IsNullOrEmpty((string?)value))
return null;
if (validator.IsProductExistsAsync((string)value).Result)
return ValidationResult.Success;
return new ValidationResult(ErrorMessage);
}
}
Entity:
namespace Cart.Entity;
using Cart.Validation;
using System.ComponentModel.DataAnnotations;
public partial class AddProductEshop
{
[Required(ErrorMessage = "Product code missing")]
[ProductEshopExists(ErrorMessage = "Product does not exist")]
public string Code { get; set; } = null!;
...
}
Program.cs:
builder.Services.AddScoped<ProductValidator>();
Upvotes: 0
Reputation: 1166
Andrew Lock has an elegant solution for doing this. By creating a custom validation attribute and then getting your external service from the validation context.
public class CustomValidationAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var service = (IExternalService) validationContext
.GetService(typeof(IExternalService));
// ... validation logic
}
}
More detail here
https://andrewlock.net/injecting-services-into-validationattributes-in-asp-net-core/
Upvotes: 0
Reputation: 6948
I'd offer a very simple solution for server-side validation of fields that can only have values existing in database. First of all we'll need a validation attribute:
public class ExistAttribute : ValidationAttribute
{
//we can inject another error message or use one from resources
//aint doing it here to keep it simple
private const string DefaultErrorMessage = "The value has invalid value";
//use it for validation purpose
private readonly ExistRepository _repository;
private readonly string _tableName;
private readonly string _field;
/// <summary>
/// constructor
/// </summary>
/// <param name="tableName">Lookup table</param>
/// <param name="field">Lookup field</param>
public ExistAttribute(string tableName, string field) : this(tableName, field, DependencyResolver.Current.GetService<ExistRepository>())
{
}
/// <summary>
/// same thing
/// </summary>
/// <param name="tableName"></param>
/// <param name="field"></param>
/// <param name="repository">but we also inject validation repository here</param>
public ExistAttribute(string tableName, string field, ExistRepository repository) : base(DefaultErrorMessage)
{
_tableName = tableName;
_field = field;
_repository = repository;
}
/// <summary>
/// checking for existing object
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public override bool IsValid(object value)
{
return _repository.Exists(_tableName, _field, value);
}
}
Validation repository itself looks pretty simple as well:
public class ExistRepository : Repository
{
public ExistRepository(string connectionString) : base(connectionString)
{
}
public bool Exists(string tableName, string fieldName, object value)
{
//just check if value exists
var query = string.Format("SELECT TOP 1 1 FROM {0} l WHERE {1} = @value", tableName, fieldName);
var parameters = new DynamicParameters();
parameters.Add("@value", value);
//i use dapper here, and "GetConnection" is inherited from base repository
var result = GetConnection(c => c.ExecuteScalar<int>(query, parameters, commandType: CommandType.Text)) > 0;
return result;
}
}
Here's base Repository
class:
public class Repository
{
private readonly string _connectionString;
public Repository(string connectionString)
{
_connectionString = connectionString;
}
protected T GetConnection<T>(Func<IDbConnection, T> getData)
{
var connectionString = _connectionString;
using (var connection = new SqlConnection(connectionString))
{
connection.Open();
return getData(connection);
}
}
}
And now, what you need to do in model is mark your fields with ExistAttribute
, specifying table name and field name for lookup:
public class UserAddress
{
[Exist("dbo.Cities", "city_id")]
public int CityCode { get; set; }
[Exist("dbo.Countries", "country_id")]
public int CountryCode { get; set; }
}
Controller action:
[HttpPost]
public ActionResult UserAddress(UserAddress model)
{
if (ModelState.IsValid) //you'll get false here if CityCode or ContryCode don't exist in Db
{
//do stuff
}
return View("UserAddress", model);
}
Upvotes: 1
Reputation: 17182
Please check the EDIT from attached from the middle of this answer, for more elaborate and generic solution.
Following is my solution to do a simple Attribute based Validation. Create an attribute -
public class Unique : ValidationAttribute
{
public Type ObjectType { get; private set; }
public Unique(Type type)
{
ObjectType = type;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (ObjectType == typeof(Email))
{
// Here goes the code for creating DbContext, For testing I created List<string>
// DbContext db = new DbContext();
var emails = new List<string>();
emails.Add("[email protected]");
emails.Add("[email protected]");
var email = emails.FirstOrDefault(u => u.Contains(((Email)value).EmailId));
if (String.IsNullOrEmpty(email))
return ValidationResult.Success;
else
return new ValidationResult("Mail already exists");
}
return new ValidationResult("Generic Validation Fail");
}
}
I created a simple model to test -
public class Person
{
[Required]
[Unique(typeof(Email))]
public Email PersonEmail { get; set; }
[Required]
public GenderType Gender { get; set; }
}
public class Email
{
public string EmailId { get; set; }
}
Then I created following View -
@model WebApplication1.Controllers.Person
@using WebApplication1.Controllers;
<script src="~/Scripts/jquery-1.10.2.min.js"></script>
<script src="~/Scripts/jquery.validate.min.js"></script>
<script src="~/Scripts/jquery.validate.unobtrusive.min.js"></script>
@using (Html.BeginForm("CreatePersonPost", "Sale"))
{
@Html.EditorFor(m => m.PersonEmail)
@Html.RadioButtonFor(m => m.Gender, GenderType.Male) @GenderType.Male.ToString()
@Html.RadioButtonFor(m => m.Gender, GenderType.Female) @GenderType.Female.ToString()
@Html.ValidationMessageFor(m => m.Gender)
<input type="submit" value="click" />
}
Now When I enter the same Email - [email protected]
and click on Submit button I can get errors in my POST
action, as shown below.
EDIT Here goes more generic and detailed answer.
Create IValidatorCommand
-
public interface IValidatorCommand
{
object Input { get; set; }
CustomValidationResult Execute();
}
public class CustomValidationResult
{
public bool IsValid { get; set; }
public string ErrorMessage { get; set; }
}
Lets assume we have our Repository
and UnitOfWork
defined in following way -
public interface IRepository<TEntity> where TEntity : class
{
List<TEntity> GetAll();
TEntity FindById(object id);
TEntity FindByName(object name);
}
public interface IUnitOfWork
{
void Dispose();
void Save();
IRepository<TEntity> Repository<TEntity>() where TEntity : class;
}
Now lets created our own Validator Commands
-
public interface IUniqueEmailCommand : IValidatorCommand { }
public interface IEmailFormatCommand : IValidatorCommand { }
public class UniqueEmail : IUniqueEmailCommand
{
private readonly IUnitOfWork _unitOfWork;
public UniqueEmail(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
public object Input { get; set; }
public CustomValidationResult Execute()
{
// Access Repository from Unit Of work here and perform your validation based on Input
return new CustomValidationResult { IsValid = false, ErrorMessage = "Email not unique" };
}
}
public class EmailFormat : IEmailFormatCommand
{
private readonly IUnitOfWork _unitOfWork;
public EmailFormat(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
public object Input { get; set; }
public CustomValidationResult Execute()
{
// Access Repository from Unit Of work here and perform your validation based on Input
return new CustomValidationResult { IsValid = false, ErrorMessage = "Email format not matched" };
}
}
Create our Validator Factory
which will give us a particular command based on Type.
public interface IValidatorFactory
{
Dictionary<Type,IValidatorCommand> Commands { get; }
}
public class ValidatorFactory : IValidatorFactory
{
private static Dictionary<Type,IValidatorCommand> _commands = new Dictionary<Type, IValidatorCommand>();
public ValidatorFactory() { }
public Dictionary<Type, IValidatorCommand> Commands
{
get
{
return _commands;
}
}
private static void LoadCommand()
{
// Here we need to use little Dependency Injection principles and
// populate our implementations from a XML File dynamically
// at runtime. For demo, I am passing null in place of UnitOfWork
_commands.Add(typeof(IUniqueEmailCommand), new UniqueEmail(null));
_commands.Add(typeof(IEmailFormatCommand), new EmailFormat(null));
}
public static IValidatorCommand GetCommand(Type validatetype)
{
if (_commands.Count == 0)
LoadCommand();
var command = _commands.FirstOrDefault(p => p.Key == validatetype);
return command.Value ?? null;
}
}
And the renovated Validation Attribute -
public class MyValidateAttribute : ValidationAttribute
{
public Type ValidateType { get; private set; }
private IValidatorCommand _command;
public MyValidateAttribute(Type type)
{
ValidateType = type;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
_command = ValidatorFactory.GetCommand(ValidateType);
_command.Input = value;
var result = _command.Execute();
if (result.IsValid)
return ValidationResult.Success;
else
return new ValidationResult(result.ErrorMessage);
}
}
Finally we can use our attribute as follows -
public class Person
{
[Required]
[MyValidate(typeof(IUniqueEmailCommand))]
public string Email { get; set; }
[Required]
public GenderType Gender { get; set; }
}
Output as follows -
EDIT Detailed explanation to make this solution more generic.
Lets say I have a property Email
where I need to do following validations -
In that case we can create IEmailCommand
inherited from IValidatorCommand
. Then inherit IEmailFormatCommand
, IEmailLengthCommand
and IEmailUniqueCommand
from IEmailCommand
.
Our ValidatorFactory
will hold the pool of all three command implementations in Dictionary<Type, IValidatorCommand> Commands
.
Now instead of decorating our Email
property with three commands, we can decorate it with IEmailCommand
.
In this case, our ValidatorFactory.GetCommand()
method needs to be changed. Instead of returning one command every time, it should return all matching commands for a particular type. So basically its signature should be List<IValidatorCommand> GetCommand(Type validatetype)
.
Now as we can get all the commands associated with a property we can loop the commands and get the Validation results in our ValidatorAttribute
.
Upvotes: 38
Reputation: 595
In Model:
public class UserAddress
{
public string CityCode {get;set;}
}
In Controller: First Create Single Function for Validate for single Connection
public dynamic GetCity(string cityCode)
{
var connection = ; // create connection
var cityRepository = new CityRepository(connection);
if (!cityRepository.IsValidCityCode(model.CityCode))
{
// Added Model error
}
return(error);
}
Function call from another controller like:
var error = controllername.GetCity(citycode);
Other Method for Many connection
public dynamic GetCity(string cityCode,string connection)
{
var cityRepository = new CityRepository(connection);
if (!cityRepository.IsValidCityCode(model.CityCode))
{
// Added Model error
}
return(error);
}
Function call from another controller like:
var error = controllername.GetCity(citycode,connection);
Upvotes: 0
Reputation: 9759
Here is my attempt -
To start with, to identify what validation we need to perform on the property, we can have an enum as identifier.
public enum ValidationType
{
City,
//Add more for different validations
}
Next define our custom validation attribute something as follows, wherein the enum type is declared as attribute parameter -
public class ValidateLookupAttribute : ValidationAttribute
{
//Use this to identify what validation needs to be performed
public ValidationType ValidationType { get; private set; }
public ValidateLookupAttribute(ValidationType validationType)
{
ValidationType = validationType;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
//Use the validation factory to get the validator associated
//with the validator type
ValidatorFactory validatorFactory = new ValidatorFactory();
var Validator = validatorFactory.GetValidator(ValidationType);
//Execute the validator
bool isValid = Validator.Validate(value);
//Validation is successful, return ValidationResult.Succes
if (isValid)
return ValidationResult.Success;
else //Return validation error
return new ValidationResult(Validator.ErrorMessage);
}
}
Going further if you need to add more validations, the attribute class need not to be changed.
And now simply decorate your property with this attribute
[ValidateLookup(ValidationType.City)]
public int CityId { get; set; }
Here are the other connecting parts of the solution-
Validator Interface. All validators will implement this interface. It has just a method to validate the object coming in and validator specific error message when validation fails.
public interface IValidator
{
bool Validate(object value);
string ErrorMessage { get; set; }
}
CityValidator Class (Of course you can improve this class with use of DI etc., its is just for ref purpose).
public class CityValidator : IValidator
{
public bool Validate(object value)
{
//Validate your city here
var connection = ; // create connection
var cityRepository = new CityRepository(connection);
if (!cityRepository.IsValidCityCode((int)value))
{
// Added Model error
this.ErrorMessage = "City already exists";
}
return true;
}
public ErrorMessage { get; set; }
}
Validator Factory, this is responsibel for providing correct validator associated with the validation type
public class ValidatorFactory
{
private Dictionary<ValidationType, IValidator> validators = new Dictionary<ValidationType, IValidator>();
public ValidatorFactory()
{
validators.Add(ValidationType.City, new CityValidator());
}
public IValidator GetValidator(ValidationType validationType)
{
return this.validators[validationType];
}
}
Based on the design of your system and conventions, the actual implementation may vary slightly, but at high level it should address the problems nicely. Hope that helps
Upvotes: 0
Reputation: 5573
I've done this in the past and it worked for me:
public interface IValidation
{
void AddError(string key, string errorMessage);
bool IsValid { get; }
}
public class MVCValidation : IValidation
{
private ModelStateDictionary _modelStateDictionary;
public MVCValidation(ModelStateDictionary modelStateDictionary)
{
_modelStateDictionary = modelStateDictionary;
}
public void AddError(string key, string errorMessage)
{
_modelStateDictionary.AddModelError(key, errorMessage);
}
public bool IsValid
{
get
{
return _modelStateDictionary.IsValid;
}
}
}
At your Business Layer level do something like this:
public class UserBLL
{
private IValidation _validator;
private CityRepository _cityRepository;
public class UserBLL(IValidation validator, CityRepository cityRep)
{
_validator = validator;
_cityRepository = cityRep;
}
//other stuff...
public bool IsCityCodeValid(CityCode cityCode)
{
if (!cityRepository.IsValidCityCode(model.CityCode))
{
_validator.AddError("Error", "Message.");
}
return _validator.IsValid;
}
}
And now at the controller level user your favorite IoC to register and instance of the this.ModelState
into your UserBLL
:
public class MyController
{
private UserBLL _userBll;
public MyController(UserBLL userBll)
{
_userBll = userBll;
}
[HttpPost]
public ActionResult Address(UserAddress model)
{
if(userBll.IsCityCodeValid(model.CityCode))
{
//do whatever
}
return View();//modelState already has errors in it so it will display in the view
}
}
Upvotes: 1
Reputation: 9759
I would have used RemoteValidation
. I've found this simplest for scenarios like validations against database.
Decorate your property with Remote attribute -
[Remote("IsCityCodeValid","controller")]
public string CityCode { get; set; }
Now, "IsCityCodeValid" will be a action method, that will return JsonResult and take the property name that you want to validate as parameter and "controller" is the name of controller in which your method will be placed. Make sure that the parameter name is same as property name.
Do your validations in the method, and in case it is valid return json true and false otherwise. Simple and quick!
public JsonResult IsCityCodeValid(string CityCode)
{
//Do you DB validations here
if (!cityRepository.IsValidCityCode(cityCode))
{
//Invalid
return Json(false, JsonRequestBehavior.AllowGet);
}
else
{
//Valid
return Json(true, JsonRequestBehavior.AllowGet);
}
}
And you are done!!. MVC framework will take care of the rest.
And of course based on you requirement, you can use different overloads of remote attribute. You can also include other dependent properties, defining custome error message etc. You can pass even pass the model class as parameter to Json result action method MSDN Ref.
Upvotes: 5
Reputation: 43
if you really want to validate from database here are some techniques you can follow 1.using System.ComponentModel.DataAnnotations add reference to class
public int StudentID { get; set; }
[StringLength(50)]
public string LastName { get; set; }
[StringLength(50)]
public string FirstName { get; set; }
public Nullable<System.DateTime> EnrollmentDate { get; set; }
[StringLength(50)]
public string MiddleName { get; set; }
here string lenght is defined i.e 50 and datetime can be nullable etc EF Database First with ASP.NET MVC: Enhancing Data Validation
Upvotes: 1
Reputation: 45
I think you should use a custom validation
public class UserAddress
{
[CustomValidation(typeof(UserAddress), "ValidateCityCode")]
public string CityCode {get;set;}
}
public static ValidationResult ValidateCityCode(string pNewName, ValidationContext pValidationContext)
{
bool IsNotValid = true // should implement here the database validation logic
if (IsNotValid)
return new ValidationResult("CityCode not recognized", new List<string> { "CityCode" });
return ValidationResult.Success;
}
Upvotes: 2