Usando MVC y Nhibernate fluido, ¿cómo valido campos únicos en mi ViewModel antes de vincularlos a mi Objeto de dominio y guardarlos?

StackOverflow https://stackoverflow.com/questions/1440440

Pregunta

Tengo un sitio web donde permito a los usuarios crear nuevos registros de piezas. Estoy tratando de descubrir la mejor manera de validar campos específicos para la unicidad. Quiero asegurarme de que alguien no intente agregar una Parte con PartNumber 1234 si ese PartNumber ya existe en una Parte diferente.

La aplicación web está utilizando Asp.net MVC con nHibernate fluido para asignar mis objetos a la base de datos. Estoy usando la validación de Castle en mis modelos de vista para cosas como ValidateNonEmpty, ValidateRange, etc. ¿Debo usar el Método ValidateSelf para consultar el repositorio para ver si ese número de parte ya existe? Algo no se siente bien al usar mi Repositorio en ViewModel.

¿Sería mejor para mí colocar esa lógica en la acción del controlador? Eso no parece correcto porque espero que mi ViewModel ya esté validado en el momento (durante ModelBind).

O tal vez no es ninguno de los anteriores. Gracias por cualquier ayuda en este caso.

ACTUALIZACIÓN Ok, no estoy seguro de si esto ayudará, pero así es como se ve mi acción Guardar para una acción Crear típica en mi proyecto:

public ActionResult Create(PartViewModel viewModel)
{
 //I think I'd like to know if its Valid by this point, not on _repository.Save
 if(ModelState.IsValid)
 {
    try
    {
        var part = _partCreateViewModelMap.MapToEntity(viewModel);

        _repository.Save(part);
        return Redirect("~/Part/Details/" + part.Id);
    }
    catch (Exception e)
    {
        // skip on down...
    }
 }

 // return view to edit 
 return View(viewModel);
}
¿Fue útil?

Solución

Me han hecho esta pregunta muchas veces. A mis amigos les preocupaba si podían acceder a los datos desde el código del validador. La respuesta es simple. Si necesita hacer esto, debe hacerlo. Por lo general, necesitamos hacer tales controles en cada nivel de abstracción. Y después de todas las comprobaciones, debe estar listo para detectar una excepción, causada por una violación de restricción única.

Otros consejos

Si define una restricción única dentro de la base de datos, ¿por qué no delegar la responsabilidad de verificar si ya existe un valor único en la base de datos? Con NHibernate, puede usar la interfaz NHibernate.Exceptions.ISQLExceptionConverter para capturar y transformar errores conocidos relacionados con infracciones de restricciones. También puede usar implementadores NHibernate.Exceptions.IViolatedConstraintNameExtracter (consulte NHibernate.Exceptions.TemplatedViolatedConstraintNameExtracter ) para obtener los detalles sucios de la excepción de su base de datos y transformarlo en un usuario. mensaje amigable, reempaquetado como una excepción de validación de su elección y póngalo en el controlador correspondiente.

Ejemplo de un convertidor de excepciones rápido, muy específico, rápido y sucio de uno de mis proyectos:


Imports NHibernate
Imports NHibernate.Exceptions
Imports System.Data.SqlClient
Imports System.Data.Common

Namespace NHibernate

    Public Class ConstraintViolationExceptionConverter
        Implements ISQLExceptionConverter

        Public Function Convert(ByVal adoExceptionContextInfo As Global.NHibernate.Exceptions.AdoExceptionContextInfo) As System.Exception Implements Global.NHibernate.Exceptions.ISQLExceptionConverter.Convert

            Dim dbEx As DbException = ADOExceptionHelper.ExtractDbException(adoExceptionContextInfo.SqlException)

            If TypeOf dbEx Is SqlException Then
                Dim sqlError As SqlException = DirectCast(dbEx, SqlException)

                Select Case sqlError.Number
                    Case 547
                        Return New ConstraintViolationException(adoExceptionContextInfo.Message, adoExceptionContextInfo.SqlException)

                End Select

            End If

            Return SQLStateConverter.HandledNonSpecificException(adoExceptionContextInfo.SqlException, adoExceptionContextInfo.Message, adoExceptionContextInfo.Sql)

        End Function


    End Class

End Namespace

Configurado a través del elemento de propiedad web.config / nhibernate-configuration / session-factory :


<property name="sql_exception_converter">csl.NHibernate.ConstraintViolationExceptionConverter, csl</property>

Editar: probablemente debería mencionar que la interfaz del convertidor ha cambiado en versiones recientes de NHibernate, la interfaz de este ejemplo es de NHibernate.dll v2.1.0.4000

Normalmente pongo una capa de Servicio entre mis controladores y repositorios.
La capa de servicio manejaría la validación y las llamadas al repositorio.

Entonces, si hay un error de validación en la capa de servicio, lanzo una excepción personalizada, la atrapo en el controlador e inyecto los errores en el estado del modelo.

No tengo respuesta para su pregunta, pero puede consultar el sitio sharparchitecture.net. Contiene algunas mejores prácticas para asp.net mvc y nhibernate. También puedo recomendarle que consulte el proyecto xval y los tutoriales sobre Validación con validadores de anotación de datos

He encontrado que la solución que me funciona es

1.) Pregunte si la entidad es válida para ejecutar su trabajo de validación.
2.) Después de completar esto, debe tener algo en su objeto para mostrar que es válido o no (en mi caso, uso un concepto similar a CSLA de "reglas rotas").
3.) Si tiene algo como esto, puede verificar que el objeto sea válido antes de que NHibernate intente conservarlo como se muestra a continuación.

El único problema con este enfoque es que es necesario implementar una interfaz en cada entidad que requiera validación. Si puede vivir con esto, evitará que NHibernate persista en los cambios de un objeto que no es válido de acuerdo con sus reglas.

using System;
using NHibernate;
using NHibernate.Event;
using Validation.Entities.Interfaces;
using Persistence.SessionBuilder;

namespace Persistence.Validation
{
    public class ValidationEventListener : IPreInsertEventListener, IPreUpdateEventListener
    {

        public bool OnPreInsert(NHibernate.Event.PreInsertEvent @event)
        {
            var entityToInsert = @event.Entity as IBusinessBase;

            if (entityToInsert != null)
            {
                if (entityToInsert.BrokenRules != null)
                {
                    RollbackTransactionBecauseTheEntityHasBrokenRules();
                }
            }

            return false;
        }

        public bool OnPreUpdate(NHibernate.Event.PreUpdateEvent @event)
        {
            var entityToUpdate = @event.Entity as IBusinessBase;

            if (entityToUpdate != null)
            {
                if (entityToUpdate.BrokenRules != null)
                {
                    RollbackTransactionBecauseTheEntityHasBrokenRules();
                }
            }

            return false;
        }

        private void RollbackTransactionBecauseTheEntityHasBrokenRules()
        {
            try
            {
                ISession session = SessionBuilderFactory.GetBuilder().CurrentSession;

                if (session != null)
                {
                    session.Transaction.Rollback();
                }
            }
            catch (Exception ex)
            {
                //this will force a rollback if we don't have a session bound to the current context 
                throw new NotImplementedException();
            }
        }
    }
}

Diría que esto es importante en su arquitectura. Con las aplicaciones MVC que he hecho en el pasado, abstraemos las cosas del dominio de las cosas de la web y, naturalmente, usamos la inyección de dependencias para evitar dependencias difíciles.

Cuando se trata de validar el modelo cuando está en el acto de vincularlo, sí, podría usar fácilmente el servicio, el repositorio o lo que sea que tenga a continuación en su arquitectura en un método ValidateSelf. Creo que surge la pregunta de qué pasa con esa dependencia.

Si no recuerdo mal, puede crear su propio cuaderno personalizado que usará su marco de inyección de dependencia para conectar cualquier servicio que su modelo necesite para validación cuando lo cree, llame al cuaderno predeterminado de MVC para completar el objeto, luego llame al Marco de Validación del Castillo para hacer la validación. Esta no es una solución completamente pensada, pero con suerte provoca algunas ideas.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top