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.
In Asp.net MVC, we can easily apply validation to web application by using Data Annotation attribute classes to model class. Data Annotation attribute classes are present in System.
Model validation is the process of checking whether the user input is suitable for model binding and if not it should provide useful error messages to the user.
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
.
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.
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;
}
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With