Validar objeto en función de factores externos (es decir, la exclusividad del almacén de datos)

Descripción

Mi solución tiene estos proyectos:

  • DAL = Marco de entidad modificado
  • DTO = Objetos de transferencia de datos que son capaces de validarse
  • BL = Business Layer Services
  • WEB = presentación Asp.net aplicación MVC

DAL, BL y WEB todos DTO de referencia, que es genial.
El proceso generalmente se ejecuta de esta manera:

  1. Se realiza una solicitud web a la WEB
  2. WEB publica DTOs
    • Los DTO se validan automágicamente a través de ActionFilter personalizado
    • los errores de validación se recogen automáticamente
  3. (La validación es correcta) Llamadas WEB a BL proporcionando DTO
  4. BL llama a DAL utilizando DTO (puede pasarlos o simplemente usarlos)

Problema de validación DTO entonces …

Mis DTO son capaces de validarse en función de su propio estado (valores de las propiedades). Pero ahora tengo un problema cuando este no es el caso. Necesito que validen usando BL (y consecuentemente DAL).

Mi ejemplo de la vida real : registros de usuarios y WEB obtiene un DTO de usuario que se valida. La parte problemática es la validación de username . Su singularidad debe verificarse en el almacén de datos.
¿Cómo se supone que haga esto?

Hay información adicional de que todos los DTO implementan una interfaz (es decir, el DTO de User implementa IUser ) para propósitos de IoC y TDD. Ambos son parte del proyecto DTO .

Imposible bash

  1. No puedo hacer referencia a BL en DTO porque obtendré una referencia circular.
    Comstacktion error
  2. No puedo crear un proyecto DTO.Val adicional que haga referencia a clases de DTO parciales e implemente su validación allí (harían referencia a BL + DTO).
    Partial classes can't span assemblies.

Posibles bashs

  1. Cree un ActionFilter especial que valide objetos contra condiciones externas. Este se crearía dentro del proyecto WEB y vería DTO y BL que se usarían aquí.
  2. Coloque los DTO en BL y mantenga las interfaces de DTO como DTO reales referenciadas por otros proyectos y refactorice todo el código para usar interfaces en lugar de clases concretas.
  3. No maneje la validación externa dependiente y permita que las dependencias externas emitan una excepción, probablemente la peor solución a este problema

¿Qué sugieres?

Sugeriría un experimento que solo he estado probando durante la última semana más o menos.

En base a esta inspiración, estoy creando DTO que validan un poco diferente al enfoque de DataAnnotations . Muestra DTO:

 public class Contact : DomainBase, IModelObject { public int ID { get; set; } public string Name { get; set; } public LazyList Details { get; set; } public DateTime Updated { get; set; } protected override void ConfigureRules() { base.AddRule(new ValidationRule() { Properties = new string[] { "name" }, Description = "A Name is required but must not exceed 300 characters in length and some special characters are not allowed", validator = () => this.Name.IsRequired300LenNoSpecial() }); base.AddRule(new ValidationRule() { Properties = new string[] { "updated" }, Description = "required", validator = () => this.Updated.IsRequired() }); } } 

Esto podría parecer más trabajo que DataAnnotations y bueno, eso es porque así es, pero no es enorme. Creo que es más presentable en la clase (ahora tengo algunas clases DTO realmente feas con atributos DataAnnotations , ya no puedes ver las propiedades). Y el poder de los delegates anónimos en esta aplicación es casi válido para libros (por lo que estoy descubriendo).

Clase base:

 public partial class DomainBase : IDataErrorInfo { private IList _rules = new List(); public DomainBase() { // populate the _rules collection this.ConfigureRules(); } protected virtual void ConfigureRules() { // no rules if not overridden } protected void AddRule(ValidationRule rule) { this._rules.Add(rule); } #region IDataErrorInfo Members public string Error { get { return String.Empty; } // Validation should call the indexer so return "" here } // ..we dont need to support this property. public string this[string columnName] { get { // get all the rules that apply to the property being validated var rulesThatApply = this._rules .Where(r => r.Properties.Contains(columnName)); // get a list of error messages from the rules StringBuilder errorMessages = new StringBuilder(); foreach (ValidationRule rule in rulesThatApply) if (!rule.validator.Invoke()) // if validator returns false then the rule is broken if (errorMessages.ToString() == String.Empty) errorMessages.Append(rule.Description); else errorMessages.AppendFormat("\r\n{0}", rule.Description); return errorMessages.ToString(); } } #endregion } 

ValidationRule y mis funciones de validación:

 public class ValidationRule { public string[] Properties { get; set; } public string Description { get; set; } public Func validator { get; set; } } ///  /// These extention methods return true if the validation condition is met. ///  public static class ValidationFunctions { #region IsRequired public static bool IsRequired(this String str) { return !str.IsNullOrTrimEmpty(); } public static bool IsRequired(this int num) { return num != 0; } public static bool IsRequired(this long num) { return num != 0; } public static bool IsRequired(this double num) { return num != 0; } public static bool IsRequired(this Decimal num) { return num != 0; } public static bool IsRequired(this DateTime date) { return date != DateTime.MinValue; } #endregion #region String Lengths public static bool IsLengthLessThanOrEqual(this String str, int length) { return str.Length <= length; } public static bool IsRequiredWithLengthLessThanOrEqual(this String str, int length) { return !str.IsNullOrTrimEmpty() && (str.Length <= length); } public static bool IsRequired300LenNoSpecial(this String str) { return !str.IsNullOrTrimEmpty() && str.RegexMatch(@"^[- \r\n\\\.!:*,@$%&""?\(\)\w']{1,300}$", RegexOptions.Multiline) == str; } #endregion } 

Si mi código parece desordenado, es porque solo he estado trabajando en este enfoque de validación durante los últimos días. Necesito esta idea para cumplir algunos requisitos:

  • Necesito apoyar la interfaz IDataErrorInfo para que mi capa MVC valide automáticamente
  • Necesito ser capaz de soportar escenarios complejos de validación (creo que todo el punto de su pregunta): quiero poder validar contra múltiples propiedades en el mismo objeto (es decir, StartDate y FinishDate); propiedades de objetos diferentes / múltiples / asociados como lo tendría en un gráfico de objetos; e incluso otras cosas que no he pensado aún.
  • Necesito apoyar la idea de un error que se aplica a más de una propiedad
  • Como parte de mi recorrido TDD y DDD, quiero que mis objetos de dominio describan más mi 'dominio' que mis métodos de capa de servicio, por lo que poner estas complejas condiciones en los objetos modelo (no DTO) parece lograr esto

Este enfoque creo que me dará lo que quiero, y tal vez a ti también.

Me imagino que si te subes a bordo conmigo en esto, seríamos bastante 'solos', pero valdría la pena. Estaba leyendo acerca de las nuevas capacidades de validación en MVC 2, pero todavía no cumple con la lista de deseos anterior sin modificación personalizada.

Espero que esto ayude.

La architecture S # arp tiene un identificador de método [DomainSignature] que se usa con el validador de nivel de clase [HasUniqueDomainSignature] hará el trabajo. Vea el código de muestra a continuación:

 [HasUniqueDomainSignature] public class User : Entity { public User() { } public User(string login, string email) : this() { Login = login; Email = email; } [DomainSignature] [NotNullNotEmpty] public virtual string Login { get; set; } [DomainSignature] public virtual string Email { get; set; } 

}

Eche un vistazo más de cerca a http://www.sharparchitecture.net/

Tuve exactamente el mismo problema y, después de tratar de encontrar una solución durante días, días y días, terminé fusionando mi DTO, DAL y BL en una sola biblioteca. Mantuve mi capa de presentación separada. No estoy seguro si esa es una opción para usted o no. Para mí, pensé que mis posibilidades de cambiar alguna vez la tienda de datos eran muy pequeñas, por lo que el nivel separado no era realmente necesario.

También implementé el Bloque de aplicaciones de validación de Microsoft para todas mis validaciones de DTO. Tienen un método de “autovalidación” que le permite realizar validaciones complejas.

Solución resultante

Terminé usando un filtro de acción de controlador que podía validar objetos contra factores externos que no se pueden obtener del objeto en sí.

Creé el filtro que toma el nombre del parámetro de acción para verificar y el tipo de validador que validará ese parámetro en particular. Por supuesto, este validador tiene que implementar cierta interfaz para que todo sea reutilizable.

 [ValidateExternalFactors("user", typeof(UserExternalValidator))] public ActionResult Create(User user) 

El validador necesita implementar esta interfaz simple

 public interface IExternalValidator { bool IsValid(T instance); } 

Es una solución simple y efectiva para un problema aparentemente complejo.