c# - for - MVC Model Validation de la base de datos
mvc required field validation (9)
Aquí está mi intento -
Para empezar, para identificar qué validación debemos realizar en la propiedad, podemos tener una enumeración como identificador.
public enum ValidationType
{
City,
//Add more for different validations
}
A continuación, defina nuestro atributo de validación personalizado de la siguiente manera, en el que el tipo enum se declara como parámetro de atributo:
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);
}
}
Yendo más allá si necesita agregar más validaciones, la clase de atributo no necesita ser cambiada.
Y ahora simplemente decore su propiedad con este atributo
[ValidateLookup(ValidationType.City)]
public int CityId { get; set; }
Aquí están las otras partes de conexión de la solución-
Interfaz del Validador Todos los validadores implementarán esta interfaz. Tiene solo un método para validar el ingreso del objeto y un mensaje de error específico del validador cuando falla la validación.
public interface IValidator
{
bool Validate(object value);
string ErrorMessage { get; set; }
}
Clase CityValidator (Por supuesto, puede mejorar esta clase con el uso de DI, es solo para fines de referencia).
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, este es responsable de proporcionar el validador correcto asociado con el tipo de validación
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];
}
}
Según el diseño de su sistema y convenciones, la implementación real puede variar levemente, pero a un nivel alto debería abordar los problemas de forma adecuada. Espero que ayude
Tengo un modelo muy simple, que necesita ser validado desde la base de datos
public class UserAddress
{
public string CityCode {get;set;}
}
CityCode
puede tener valores que solo están disponibles en la tabla de mi base de datos.
Sé que puedo hacer algo como eso.
[HttpPost]
public ActionResult Address(UserAddress model)
{
var connection = ; // create connection
var cityRepository = new CityRepository(connection);
if (!cityRepository.IsValidCityCode(model.CityCode))
{
// Added Model error
}
}
Esto parece muy WET
ya que tengo que usar este modelo en muchos lugares y agregar la misma lógica en cada lugar parece que no estoy usando MVC Architecture correctamente.
Entonces, ¿cuál es el mejor patrón para validar el modelo de la base de datos?
NOTA: la mayor parte de la validación es búsqueda de campo único desde la base de datos, otra validación puede incluir una combinación de campo. Pero en este momento estoy contento con la validación de búsqueda de campo único, siempre y cuando sea DRY
y no utilice demasiada reflexión, es aceptable.
SIN VALIDACIÓN DEL LADO DEL CLIENTE: Para cualquiera que esté respondiendo en términos de validación del lado del cliente, no necesito dicha validación, la mayoría de mi validación es del lado del servidor, y necesito lo mismo, por favor no responda con los métodos de Validación del lado del cliente.
PD. Si alguien me puede dar una pista sobre cómo hacer una validación basada en atributos desde la base de datos, será extremadamente agradecido.
Creo que deberías usar una validación personalizada
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;
}
He hecho esto en el pasado y funcionó para mí:
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;
}
}
}
En su nivel Business Layer, haga algo como esto:
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;
}
}
Y ahora, en el nivel de controlador, this.ModelState
su IoC favorito para registrarse y la instancia de this.ModelState
en su 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
}
}
Hubiera usado RemoteValidation
. He encontrado esto más simple para escenarios como validaciones contra la base de datos.
Decora tu propiedad con el atributo Remoto -
[Remote("IsCityCodeValid","controller")]
public string CityCode { get; set; }
Ahora, "IsCityCodeValid" será un método de acción, que devolverá JsonResult y tomará el nombre de propiedad que desea validar como parámetro y "controller" es el nombre del controlador en el que se colocará su método. Asegúrese de que el nombre del parámetro sea el mismo que el nombre de la propiedad.
Haga sus validaciones en el método, y en caso de que sea válido, devuelva json verdadero y falso en caso contrario. Simple y rápido!
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);
}
}
¡Y terminaste! MVC framework se encargará del resto.
Y, por supuesto, según su requerimiento, puede usar diferentes sobrecargas de atributos remotos. También puede incluir otras propiedades dependientes, definir el mensaje de error del servidor, etc. Puede pasar incluso pasar la clase de modelo como parámetro al método de acción de resultado Json Ref MSDN.
Ofrecería una solución muy simple para la validación del lado del servidor de los campos que solo pueden tener valores existentes en la base de datos. En primer lugar, necesitaremos un atributo de validación:
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);
}
}
El repositorio de validación también se ve muy simple:
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;
}
}
Aquí está la clase base de Repository
:
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);
}
}
}
Y ahora, lo que necesita hacer en el modelo es marcar sus campos con ExistAttribute
, especificando el nombre de la tabla y el nombre del campo para la búsqueda:
public class UserAddress
{
[Exist("dbo.Cities", "city_id")]
public int CityCode { get; set; }
[Exist("dbo.Countries", "country_id")]
public int CountryCode { get; set; }
}
Acción del controlador:
[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);
}
Por favor, compruebe el EDIT desde el adjunto desde el medio de esta respuesta, para una solución más elaborada y genérica.
A continuación está mi solución para hacer una validación simple basada en atributos. Crear un atributo -
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");
}
}
Creé un modelo simple para probar -
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; }
}
Luego creé la siguiente Vista -
@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" />
}
Ahora cuando ingreso el mismo correo electrónico - [email protected]
y [email protected]
clic en el botón Enviar, puedo obtener errores en mi acción POST
, como se muestra a continuación.
EDITAR Aquí va una respuesta más genérica y detallada.
Crear IValidatorCommand
-
public interface IValidatorCommand
{
object Input { get; set; }
CustomValidationResult Execute();
}
public class CustomValidationResult
{
public bool IsValid { get; set; }
public string ErrorMessage { get; set; }
}
Supongamos que tenemos nuestro Repository
y UnitOfWork
definidos de la siguiente manera:
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;
}
Ahora permitamos crear nuestros propios 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" };
}
}
Crea nuestra Validator Factory
que nos dará un comando particular basado en Tipo.
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;
}
}
Y el Atributo de Validación renovado -
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);
}
}
Finalmente podemos usar nuestro atributo de la siguiente manera:
public class Person
{
[Required]
[MyValidate(typeof(IUniqueEmailCommand))]
public string Email { get; set; }
[Required]
public GenderType Gender { get; set; }
}
Salida de la siguiente manera:
EDITAR Explicación detallada para hacer que esta solución sea más genérica.
Digamos que tengo un Email
propiedad donde tengo que hacer las siguientes validaciones:
- Formato
- Longitud
- Único
En ese caso, podemos crear IEmailCommand
heredado de IValidatorCommand
. A continuación, herede IEmailFormatCommand
, IEmailLengthCommand
y IEmailUniqueCommand
de IEmailCommand
.
Nuestro ValidatorFactory
contendrá el conjunto de las tres implementaciones de comando en los comandos de Dictionary<Type, IValidatorCommand> Commands
.
Ahora, en lugar de decorar nuestra propiedad de Email
con tres comandos, podemos decorarlo con IEmailCommand
.
En este caso, nuestro método ValidatorFactory.GetCommand()
necesita ser cambiado. En lugar de devolver un comando cada vez, debe devolver todos los comandos coincidentes para un tipo particular. Así que, básicamente, su firma debe ser List<IValidatorCommand> GetCommand(Type validatetype)
.
Ahora, como podemos obtener todos los comandos asociados con una propiedad, podemos repetir los comandos y obtener los resultados de Validación en nuestro ValidatorAttribute
.
si realmente quiere validar desde la base de datos aquí hay algunas técnicas que puede seguir 1.using System.ComponentModel.DataAnnotations agregar referencia a la clase
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; }
aquí se define la longitud de la cadena, es decir, 50 y datetime pueden ser anulables, etc. EF Database First con ASP.NET MVC: mejora de la validación de datos
Me he enfrentado a situaciones como esta . If User already taken EmailId. i will be Show the Error Message In UI...
He utilizado la validación remota de atributos
También mi implementación para validación de correo electrónico
Propiedad modelo
[Required(ErrorMessage = "{0} required")]
[DataType(DataType.EmailAddress)]
[Display(Name = "Email")]
[RegularExpression(@"/w+([-+.'']/w+)*@/w+([-.]/w+)*/./w+([-.]/w+)*",
ErrorMessage = "Not Valid Email..Enter Again")]
[Remote("CheckEmailId", "Email", ErrorMessage = "Already in use!")]
[StringLength(50, ErrorMessage = "{0}: email addres should be more than 50 charcters")]
public string Email { get; set; }
Aquí CheckEmailId es la acción y el correo electrónico es el nombre del controlador
Veamos el método completo que puede devolver resultado en formato json
public JsonResult CheckEmailId(string Email)
{
myDbcontext db = new myDbcontext();
//if email Id already in database table returns true otherwise false
var result = db.tblEmail.Any(x=>x.PrimaryEmail== Email);
return Json(result, JsonRequestBehavior.AllowGet);
}
Buena suerte ... feliz codificación ...
Lea también MSDN Validación de atributo remoto
- Hola ... Creo que esto es útil para tu pregunta.
- Yo uso ese método para
- llamando a una sola función en varias ubicaciones. Voy a explicar detalladamente a continuación.
En el modelo:
public class UserAddress
{
public string CityCode {get;set;}
}
En el controlador: Primero, cree una sola función para validar para una sola conexión
public dynamic GetCity(string cityCode)
{
var connection = ; // create connection
var cityRepository = new CityRepository(connection);
if (!cityRepository.IsValidCityCode(model.CityCode))
{
// Added Model error
}
return(error);
}
Llamada de función de otro controlador como:
var error = controllername.GetCity(citycode);
Otro método para muchas conexiones
public dynamic GetCity(string cityCode,string connection)
{
var cityRepository = new CityRepository(connection);
if (!cityRepository.IsValidCityCode(model.CityCode))
{
// Added Model error
}
return(error);
}
Llamada de función de otro controlador como:
var error = controllername.GetCity(citycode,connection);