1

Ich habe eine Reihe von ASP.NET 4-Projekten, die in einer MVC (3 RC2) -App gipfeln. Die Lösung verwendet Unity und EntLib Validation für die übergreifende Abhängigkeitsinjektion und -validierung. Beide eignen sich hervorragend zum Implementieren von Repository- und Service-Layer-Implementierungen.So führen Sie eine doppelte Schlüsselvalidierung mithilfe von Entlib- (oder DataAnnotations), MVC- und Repository-Mustern durch

Ich kann jedoch nicht herausfinden, wie die doppelte Schlüsselüberprüfung durchgeführt wird. Wenn sich ein Benutzer beispielsweise anmeldet, möchten wir sicherstellen, dass er keine Benutzer-ID auswählt, die bereits von einem anderen Benutzer verwendet wird. Für diese Art der Validierung muss das validierende Objekt eine Repository-Referenz haben ... oder eine andere Möglichkeit, einen IQueryable/IEnumerable-Verweis auf andere Zeilen im DB zu überprüfen.

Was ich habe, ist eine UserMetadata-Klasse, die alle Eigenschaften-Setter und Getter für einen Benutzer zusammen mit allen entsprechenden DataAnnotations und EntLib-Validierungsattributen hat. Es gibt auch eine UserEntity-Klasse, die mithilfe von EF4 POCO Entity Generator-Vorlagen implementiert wird. Die UserEntity hängt von UserMetadata ab, da sie über ein MetadataTypeAttribute verfügt. Ich habe auch eine UserViewModel-Klasse, die dasselbe genaue MetadataType-Attribut hat. Auf diese Weise kann ich die gleichen Validierungsregeln über Attribute sowohl auf die Entity als auch auf das Viewmodel anwenden.

Es gibt keine konkreten Verweise auf die Repository-Klassen. Alle Repositories werden mit Unity injiziert. Es gibt auch eine Service-Schicht, die die Abhängigkeitsinjektion erhält. Im MVC-Projekt werden Service-Layer-Implementierungsklassen in die Controller-Klassen eingefügt (die Controller-Klassen enthalten nur Service-Layer-Schnittstellenreferenzen). Unity fügt dann die Repository-Implementierungen in die Service-Layer-Klassen ein (Serviceklassen enthalten auch nur Schnittstellenreferenzen).

Ich habe mit dem DataAnnotations CustomValidationAttribute in der Metadatenklasse experimentiert. Das Problem dabei ist, dass die Validierungsmethode statisch sein muss und die Methode eine Repository-Implementierung nicht direkt instanziieren kann. Meine Repository-Schnittstelle ist IRepository <T>, und ich habe nur eine einzige Repository-Implementierungsklasse definiert als EntityRepository <T> für alle Domain-Objekte. Um ein Repository explizit zu instantiieren, müsste ich das neue EntityRepository <UserEntity>() sagen, was zu einem zirkulären Abhängigkeitsdiagramm führen würde: UserMetadata [hängt ab] DuplicateUserIDValidator [hängt ab] UserEntity [hängt ab von] UserMetadata.

Ich habe auch versucht, einen benutzerdefinierten EntLib Validator <T> zusammen mit einem benutzerdefinierten Validierungsattribut zu erstellen. Hier habe ich nicht das gleiche Problem mit einer statischen Methode. Ich denke, ich könnte das zum Laufen bringen, wenn ich herausfinden könnte, wie Unity mein EntityRepository in die Validator-Klasse injizieren kann ... was ich nicht kann. Im Moment befindet sich der gesamte Validierungscode in meiner Metadata-Klassenbibliothek, da das benutzerdefinierte Validierungsattribut dort hingehört.

Haben Sie Ideen, wie Sie Validierungen durchführen, die mit dem aktuellen Repository-Status verglichen werden müssen? Kann Unity verwendet werden, um eine Abhängigkeit in eine Klassenbibliothek der unteren Ebene einzufügen?

+0

Werfen Sie einen Blick auf diesen Artikel: http://www.cuttingedge.it/blogs/steven/pivot/entry.php?id=47. – Steven

Antwort

0

Okay, ich habe eine bessere Lösung gefunden, mit der ich meine frühere Bibliotheksarchitektur verwenden kann.

Ich konnte die UserMetadata-Klasse in einer separaten Bibliothek von UserEntity beibehalten. Hier ist, was die Abhängigkeitsgraphen wie folgt aussieht:

UserMetadata [hängt von] DuplicateUserValidationAttribute [hängt von] IDuplicateUserValidator

Die tatsächliche DuplicateUserValidator Implementierungsklasse ist in meiner Dienstschicht und sieht etwa so aus:

public class DuplicateUserValidator : Validator<string>, IDuplicateUserValidator 
{ 
    public DuplicateUserValidator() 
     : base(null, null) 
    { 
    } 

    [Dependency] 
    public IRepository<UserEntity> UserRepository { get; set; } 

    protected override string DefaultMessageTemplate 
    { 
     get { throw new NotImplementedException(); } 
    } 

    protected override void DoValidate(string stringToValidate, object currentTarget, string key, ValidationResults validationResults) 
    { 
     if (this.UserRepository == null) 
      throw new InvalidOperationException("This should not happen"); 

     // actual code to perform validation... 
    } 
} 

Der Trick ist, die DuplicateUserValidatorAttribute immer nicht auf DuplicateUserValidator abzuhängen:

public class DuplicateUserValidatorAttribute : ValidatorAttribute 
{ 
    protected override Validator DoCreateValidator(Type targetType) 
    { 
     var validator = Unity.Container.Resolve<IDuplicateUserValidator>() as Validator; 
     validator.Tag = Tag; 
     validator.MessageTemplate = GetMessageTemplate(); 
     return validator; 
    } 

} 

Alles, was ich von dort aus tun musste, war eine Zuordnung zur Unity-Konfiguration hinzuzufügen. Wenn der Singleton den IDuplicateUserValidator auflöst, injiziert er meine Implementierungsklasse aus der Service-Schicht, wobei er das [Dependency] -Attribut der UserRepository-Eigenschaft berücksichtigt.

0

Tun Sie es in der Datenbank. Das ist die einzige Lösung, die wirklich multi-user sicher sein kann.

+0

Ich verstehe nicht, wie dies in der DB ist die einzige Multi-User-Safe-Option. Meine Repositorys arbeiten mit einem UnitOfWork-Muster, das einen ObjectContext mit EntityTransaction umschließt. Ich möchte die Validierung auf Db-Ebene vermeiden, weil 1) diese Anforderung weniger testbar wird, 2) eine SQLException abgefangen wird und 3) es keine Möglichkeit gibt, den Validator vom Client aus aufzurufen, ohne die Datenbank zu treffen. – danludwig

+0

@olivehour, nur die DB kann außerhalb ihrer eigenen Transaktion sehen. Eine eindeutige Einschränkung für den DB * wird nicht fehlschlagen, Punkt *, auch wenn der widersprüchliche, festgeschriebene Datensatz in einer Transaktion ist, die für Ihren nicht sichtbar ist. Im App-Server ist folgendes möglich: 1) Benutzer A prüft auf Konflikte, findet keine, 2) Benutzer B prüft auf Konflikte, findet keine, 3) Benutzer A legt "Bob" als Namen fest, 4) Benutzer "B" bestätigt "Bob" als Namen. Hoppla. Sie müssen keine Features testen, die in Ihren DB-Server integriert sind. Das ist nicht dein Code. Sie können dies jedoch vom Client validieren. –

+0

OK, Sie sprechen also von Nebenläufigkeit. Ich habe eine Timestamp-Spalte in der db-Tabelle, und die App wird die Zeilenversion vor dem Ausführen von Commits (optimistische Concurrency-Implementierung) überprüfen. Wenn Benutzer B "Bob" festschreibt, stimmen die Zeitstempelwerte nicht überein und die App weiß, dass ein Parallelkonflikt vorliegt. Ich kann der betreffenden Spalte keine eindeutige Einschränkung auferlegen, da die DB auch eine IsCurrentVersion-Spalte hat. Alle alten Zeilen werden in der Datenbank gespeichert. Es kann also viele Benutzer in der Datenbank mit der gleichen Benutzer-ID geben, aber nur eine wird IsCurrentVersion == true haben. Ein Auslöser wäre stattdessen erforderlich. – danludwig

0

Ich fand eine Lösung, immer noch nicht sicher, ob es die beste ist. Scheint so etwas wie rohe Gewalt für mich.

Das erste, was ich tat, war meine Metadata-Klassen in das gleiche Projekt/Bibliothek wie meine Entity-Klassen zu kombinieren. Da meine Repository-Schnittstelle IRepository <T> ist, musste das Repository als IRepository <UserEntity> deklariert werden. Dies löste die zirkuläre Abhängigkeit, von der ich sprach.Jetzt habe ich UserEntity [hängt ab] UserMetadata [hängt ab] DuplicateUserValidationAttribute [hängt ab] DuplicateUserValidator [hängt ab] UserEntity. Mit all diesen Klassen in der gleichen Bibliothek gibt es, obwohl es immer noch Zirkularität gibt, keine zirkulären Assemblyabhängigkeiten.

Die zweite Sache, die ich gemacht habe, war, meinen IUnityContainer in einen Singleton zu verpacken und ihn in meine übergreifende Utilities-Bibliothek zu verschieben (bevor sie nur im MVC 3-Projekt existierte).

Dann in meinem DuplicateUserValidator Konstruktor, konnte ich folgendes tun:

public class DuplicateUserValidator : Micrsoft.Practices.EnterpriseLibrary.Validation.Validator<string> 
{ 
    public IRepository<UserEntity> UserRepository { get; set; } 

    public DuplicateUserValidator(string tag) 
     : base(string.Empty, tag) 
    { 
     IUnityContainer unity = UnityContainerSingleton.Get(); 
     this.UserRepository = unity.Resolve<IRepository<UserEntity>>() as IRepository<UserEntity>; 
    } 
} 

Jetzt habe ich ein Repository Abhängigkeit in die EntLib Validator Implementierung kundenspezifische injiziert. Ich wünschte, ich könnte die Einheit bekommen, um sie automatisch in den Konstruktor zu injizieren, vielleicht werde ich mich als nächstes damit befassen.