Validación dinámica usando reglas personalizadas

Validación dinámica usando reglas personalizadas

La mejor manera de indicar las reglas comerciales es en un xml. Para aprovechar al máximo esta notación, debe comenzar definiendo la estructura del modelo de datos del motor de reglas, es decir, responder a estas preguntas.

  1. ¿Cuáles son las reglas?
  2. ¿Se pueden categorizar las reglas?
  3. ¿Las reglas contienen propiedades comunes (atributos) como valores permitidos, formato, etc.?

Una vez hecho esto, cree un xml de reglas ficticio y luego derive un esquema xml basado en este xml. La herramienta xsd.exe puede ayudarlo a crear el esquema. Es más fácil crear el esquema si puede utilizar herramientas como Altova XmlSpy.

En cuanto a las respuestas a sus preguntas específicas,

Una vez que tenga el esquema en su lugar, Visual Studio brinda un amplio soporte para crear el xml (incluidos intellisense y validación).

No es necesario, la clase XmlSerializer proporciona lógica para la serialización/deserialización, es decir, para convertir las reglas xml en el modelo de datos de reglas y viceversa.

Bueno, este es un punto parcialmente válido en comparación con las reglas codificadas (reglas que están integradas en su ensamblaje como clases), pero la flexibilidad de este enfoque supera con creces cualquier desventaja de rendimiento. No necesita reconstruir la solución en caso de que haya un cambio en las reglas. En la mayoría de los casos, el impacto en el rendimiento es mínimo.

A menos que tenga un criterio de rendimiento estricto, el enfoque xml es la forma preferida de implementar el motor de reglas. Recuerde que cuanto menos acoplada esté su arquitectura, mayor será la flexibilidad en el tiempo de ejecución, pero habrá un impacto negativo en el rendimiento.

Regla de ejemplo

<RulesEngine>
  <Rules>
    <Rule Id="Rule1">
      <Function>
        <Equals>
          <Property name="Property1" classId="MyClassId"/>
            <Sum>
              <Property name="Property2" classId="MyClassId"/>
              <Constant type="UInt16" value="1"/>
            </Sum>
          </Equals>
        </Function>
      </Rule>
    </Rules>
    <Classes>
    <Class name="MyNamespace.MyClass" Id="MyClassId">
      <Property name="Property1" type="UInt16"/>
      <Property name="Property2" type="UInt16"/>
    </Class>
  </Classes>
</RulesEngine>

El motor de reglas necesita interpretar esta regla y deducir el significado en consecuencia.


Echa un vistazo a FluentValidation. Utiliza expresiones y puede crear validaciones condicionales (por ejemplo, validar estos propiedades si eso uno cumple con algunos criterios). FV tal vez no sea tan dinámico fuera de la caja, pero obtienes Intellisense, expresividad y seguridad de tipo. Su naturaleza genérica significa que se ejecuta razonablemente rápido. Puede inyectar parte de la dinámica del tiempo de ejecución pasando delegados de validación o validadores personalizados que pueden hacer casi cualquier cosa que se le ocurra.

Significa que tendría que volver a compilar, pero podría colocar los validadores en un ensamblaje separado. Y tiene sentido para el validador no estar en/en la clase, porque a menudo encuentras que la validación se realiza en contexto . Por ejemplo, un coche puede ser válido si tiene todas sus ruedas. Pero, si estás tratando de conducirlo y no tiene gasolina batería, entonces es "inválida" para conducir. Dicho esto, ubicaría las reglas "cercanas" a lo que están validando, ya que son parte de su dominio.

Si necesita una regla para una propiedad que depende de una o más propiedades (incluida ella misma) y un mensaje personalizado si no se cumplen los criterios de la regla, puede hacerlo. Considere esto:

RuleFor(x => x.P1)
    .Must(x => x.P1 > x.P2)
    .Message("P1 must be one more than P2. P1 was {0}; P2 was {1}", x=>x.P1, x=>x.P2);

da una comparación simple, pero podrías hacer algo mucho más complejo.