Utilizzando MVC e Nhibernate fluente, come posso convalidare campi univoci sul mio ViewModel prima di collegarli al mio oggetto dominio e salvarli?

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

Domanda

Ho un sito Web in cui consento agli utenti di creare nuovi record di parti. Sto cercando di capire il modo migliore per convalidare campi specifici per l'unicità. Voglio assicurarmi che qualcuno non tenti di aggiungere una parte con PartNumber 1234 se tale PartNumber esiste già su una parte diversa.

L'applicazione Web utilizza Asp.net MVC con fluente nHibernate per mappare i miei oggetti sul database. Sto usando la convalida Castle sui miei modelli di visualizzazione per cose come ValidateNonEmpty, ValidateRange, ecc. Dovrei usare il metodo ValidateSelf per interrogare il repository per vedere se quel numero parte esiste già? Qualcosa non va bene sull'uso del mio repository sul ViewModel.

Sarebbe meglio per me posizionare quella logica sull'azione del controller? Non sembra giusto perché mi aspetto che il mio ViewModel sia già convalidato al momento (durante ModelBind).

O forse non è nessuno dei precedenti. Grazie per l'aiuto su questo.

Aggiorna Ok, non sono sicuro che questo possa aiutare, ma ecco come appare la mia azione Salva per una tipica azione Crea nel mio progetto:

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);
}
È stato utile?

Soluzione

Mi è stata posta questa domanda molte volte. I miei amici erano preoccupati se potevano eseguire l'accesso ai dati dal codice validatore. La risposta è semplice Se devi farlo, dovresti farlo. Di solito abbiamo bisogno di fare tali controlli ad ogni livello di astrazione. E dopo tutti i controlli dovresti essere pronto a rilevare un'eccezione, causata da una violazione univoca dei vincoli.

Altri suggerimenti

Se si definisce un vincolo univoco all'interno del database, perché non delegare la responsabilità di verificare se esiste già un valore univoco nel database? Utilizzando NHibernate, è possibile utilizzare l'interfaccia NHibernate.Exceptions.ISQLExceptionConverter per acquisire e trasformare errori noti relativi a violazioni di vincoli. Puoi anche utilizzare gli implementatori NHibernate.Exceptions.IViolatedConstraintNameExtracter (vedi NHibernate.Exceptions.TemplatedViolatedConstraintNameExtracter ) per ottenere i dettagli sporchi dell'eccezione del tuo database e trasformarlo in un utente- messaggio amichevole, riconfezionamento come eccezione di convalida della scelta e prenderlo nel relativo controller.

Esempio di un convertitore di eccezioni rapido, molto specifico, rapido e sporco da uno dei miei progetti:


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

Configurato tramite l'elemento della proprietà web.config / nhibernate-configuration / session-factory :


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

Modifica: probabilmente dovresti menzionare che l'interfaccia del convertitore è cambiata nelle ultime versioni di NHibernate, l'interfaccia di questo esempio proviene da NHibernate.dll v2.1.0.4000

In genere inserisco un livello di servizio tra i miei controller e repository.
Il livello di servizio gestirà quindi la convalida e le chiamate al repository.

Quindi, se si verifica un errore di convalida nel livello di servizio, lancio un'eccezione personalizzata, la cattura nel controller e inserisco gli errori nello stato del modello.

Non ho una risposta per la tua domanda ma puoi consultare il sito sharparchitecture.net. Contiene alcune best practice per asp.net mvc e nhibernate. Inoltre, posso consigliarti di controllare il progetto xval e le esercitazioni sulla convalida con validatori di annotazioni di dati

Ho trovato che la soluzione che funziona per me è

1.) Chiedi se l'entità è valida per eseguire il tuo lavoro di validazione.
2.) Al termine, dovresti avere qualcosa sul tuo oggetto per dimostrarne la validità o meno (nel mio caso uso un concetto simile a CSLA di "regole infrante").
3.) Se hai qualcosa del genere puoi verificare che l'oggetto sia valido prima che NHibernate tenti di persistere come mostrato di seguito.

L'unico problema con questo approccio è che è necessario implementare un'interfaccia su ogni entità che richiede la convalida. Se riesci a convivere con questo, NHibernate impedirà che persistano le modifiche di un oggetto non valido secondo le tue regole.

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();
            }
        }
    }
}

Direi che questo è importante per la tua architettura. Con le app MVC che ho fatto in passato, allontaniamo le cose del dominio dalle cose del web e naturalmente usiamo l'iniezione delle dipendenze per evitare forti dipendenze.

Quando si tratta di convalidare il modello quando si è in procinto di legarlo, sì, è possibile utilizzare facilmente il servizio, il repository o qualsiasi altra cosa successiva nell'architettura in un metodo ValidateSelf. Penso che sorga la domanda su cosa sia quella dipendenza.

Se ricordo correttamente, puoi creare il tuo raccoglitore personalizzato che utilizzerà il tuo framework di iniezione di dipendenza per collegare tutti i servizi di cui il tuo modello ha bisogno per la convalida quando lo crei, chiama il raccoglitore predefinito di MVC per compilare l'oggetto, quindi chiama in Il framework di Castle Validation per eseguire la validazione. Questa non è una soluzione completamente pensata, ma speriamo che provochi alcune idee.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top