À l'aide de MVC et de Nhibernate, comment valider des champs uniques sur mon ViewModel avant de les lier à mon objet Domain et de les enregistrer?

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

Question

J'ai un site Web sur lequel j'autorise les utilisateurs à créer de nouveaux enregistrements de partie. J'essaie de trouver le meilleur moyen de valider des champs spécifiques en vue d'unicité. Je veux m'assurer que quelqu'un n'essaye pas d'ajouter une partie avec le numéro de pièce 1234 si ce numéro de pièce existe déjà dans une autre partie.

L'application Web utilise Asp.net MVC avec nHibernate couramment pour mapper mes objets sur la base de données. J'utilise Castle validation sur mes modèles d'affichage pour des choses comme ValidateNonEmpty, ValidateRange, etc. Devrais-je utiliser l'élément ValidateSelf pour interroger le référentiel pour voir si ce numéro de pièce existe déjà? Quelque chose ne va pas à propos de l'utilisation de mon référentiel sur le ViewModel.

Serait-il préférable pour moi de placer cette logique sur l'action du contrôleur? Cela ne semble pas correct car je m'attends à ce que mon ViewModel soit déjà validé au point (pendant ModelBind).

Ou peut-être que rien de tout cela. Merci pour toute aide sur celui-ci.

MISE À JOUR Ok, je ne suis pas sûr que cela puisse aider, mais voici à quoi ressemble mon action Enregistrer pour une action Créer typique dans mon projet:

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);
}
Était-ce utile?

La solution

On m'a posé cette question plusieurs fois. Mes amis se demandaient s'ils pouvaient accéder aux données à partir du code du validateur. La réponse est simple Si vous avez besoin de le faire, vous devriez le faire. Habituellement, nous devons effectuer de tels contrôles à chaque niveau d'abstraction. Et après toutes les vérifications, vous devriez être prêt à intercepter une exception, provoquée par une violation de contrainte unique.

Autres conseils

Si vous définissez une contrainte unique dans la base de données, pourquoi ne pas déléguer la responsabilité de vérifier si une valeur unique existe déjà dans la base de données? Avec NHibernate, vous pouvez utiliser l'interface NHibernate.Exceptions.ISQLExceptionConverter pour capturer et transformer les erreurs connues liées aux violations de contrainte. Vous pouvez également utiliser NHibernate.Exceptions.IViolatedConstraintNameExtracter pour implémenteurs (voir NHibernate.Exceptions.TemplatedViolatedConstraintNameExtracter ) pour obtenir les détails désordonnés de votre exception de base de données et les transformer en utilisateur. message amical, remballez comme une exception de validation de votre choix et attrapez-le dans le contrôleur approprié.

Exemple de convertisseur d'exception rapide et spécifique très spécifique issu de l'un de mes projets:


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

Configuré via l'élément de propriété web.config / nhibernate-configuration / session-factory :


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

Modifier: Si vous devez probablement mentionner que l'interface du convertisseur a été modifiée dans les versions récentes de NHibernate, l'interface de cet exemple provient de NHibernate.dll v2.1.0.4000

Je place généralement une couche de service entre mes contrôleurs et mes référentiels.
La couche service gérerait alors la validation et les appels au référentiel.

Ensuite, s'il y a une erreur de validation dans la couche service, je lève une exception personnalisée, la récupère dans le contrôleur et injecte les erreurs dans l'état du modèle.

Je n'ai pas de réponse à votre question, mais vous pouvez consulter le site sharparchitecture.net. Il contient quelques meilleures pratiques pour asp.net mvc et nhibernate. Je peux également vous recommander de vérifier le projet xval et les tutoriels sur la validation avec les validateurs d'annotation de données

J'ai trouvé la solution qui fonctionne pour moi:

1.) Demandez si l'entité est valide pour exécuter votre travail de validation.
2.) Une fois cette opération terminée, vous devez indiquer quelque chose sur votre objet pour lui indiquer qu'il est valide ou non (dans mon cas, j'utilise un concept similaire à celui des "règles brisées").
3.) Si vous avez quelque chose comme cela, vous pouvez vérifier que l’objet est valide avant que NHibernate ne l’essaye de le conserver comme indiqué ci-dessous.

Le seul problème avec cette approche est que vous devez implémenter une interface sur chaque entité nécessitant une validation. Si vous parvenez à vivre avec cela, NHibernate sera empêché de conserver les modifications d’un objet qui n’est pas valide selon vos règles.

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

Je dirais que cela compte pour votre architecture. Avec les applications MVC que j'ai réalisées par le passé, nous extrayons les éléments de domaine du Web et nous utilisons naturellement l'injection de dépendances pour éviter les dépendances dures.

En ce qui concerne la validation du modèle lorsque vous êtes en train de le lier, vous pouvez facilement utiliser le service, le référentiel ou ce que vous avez ensuite dans votre architecture dans une méthode ValidateSelf. Je pense que la question se pose de savoir quoi de cette dépendance.

Si je me souviens bien, vous pouvez créer votre propre classeur personnalisé qui utilisera votre infrastructure d'injection de dépendance pour connecter les services dont le modèle a besoin pour validation lors de sa création. Appelez le classeur par défaut de MVC pour renseigner l'objet, puis appelez-le. Le cadre de validation de Castle pour effectuer la validation. Ce n’est pas une solution à part entière, mais nous espérons que cela suscitera quelques idées.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top