7

Sto costruendo un'applicazione ASP.NET MVC che utilizza un approccio DDD (Domain Driven Design) con accesso al database gestito da NHibernate. Ho classe del modello di dominio (Administrator) che voglio di iniettare una dipendenza in tramite un CIO contenitore come il Castello di Windsor, qualcosa di simile:Iniezione di dipendenze nelle classi del modello di dominio con Nhibernate (ASP.NET MVC + IOC)

public class Administrator 
{ 
    public virtual int Id { get; set; } 

    //.. snip ..// 

    public virtual string HashedPassword { get; protected set; } 

    public void SetPassword(string plainTextPassword) 
    { 
     IHashingService hasher = IocContainer.Resolve<IHashingService>(); 

     this.HashedPassword = hasher.Hash(plainTextPassword); 
    } 
} 

Io fondamentalmente voglio iniettare IHashingService per il metodo SetPassword senza chiamare il CIO Container direttamente (perché si suppone che sia un pattern anti-IOC). Ma non sono sicuro di come farlo. Il mio oggetto Administrator viene istanziato tramite new Administrator(); o viene caricato tramite NHibernate, quindi come faccio a inserire IHashingService nella classe Administrator?

Ripensandoci, sto andando su questo nel modo giusto? Speravo di evitare di avere la mia base di codice disseminato ...

currentAdmin.Password = HashUtils.Hash(password, Algorithm.Sha512); 

... e invece ottenere il modello di dominio in sé per prendersi cura di hashing e ordinatamente incapsulare via. Posso immaginare che un altro sviluppatore accidentalmente scelga l'algoritmo sbagliato e abbia alcune password come Sha512 e alcune come MD5, alcune con una salina, altre con un sale diverso ecc ecc. Invece se gli sviluppatori scrivono ...

currentAdmin.SetPassword(password); 

... allora questo nasconderebbe quei dettagli e si prenderà cura di quei problemi sopra elencati non sarebbe?

risposta

1

C'è un motivo per cui non è possibile passare lo IHashingService nel costruttore per la classe Administrator? Ecco come risolverei la dipendenza.

public class Administrator 
{ 
    private readonly IHashingService _hashingService; 

    public Administrator(IHashingService hashingService) 
    { 
     _hashingService = hashingService; 
    } 

    // <snip> 

    public void SetPassword(string plainTextPassword) 
    { 
     this.HashedPassword = _hashingService.Hash(plainTextPassword); 
    } 
} 

Modifica # 1

Se tirando da un modello, provare a utilizzare l'iniezione a livello di metodo.

public void SetPassword(string plainText, IHashingService hasher) 
{ 
    if (hasher == null) throw new ArgumentNullException("hasher"); 
    this.HashedPassword = hasher.Hash(plainText); 
} 

Modifica # 2

Inoltre, perché non rendono facile su te stesso e solo fare un'estensione su stringa?

public static class ExtensionsOfString 
{ 
    public static string Hash(this string s) 
    { 
     // hash with SHA256 
     return hashedString; 
    } 
} 

Mentre mi rendo conto che c'è un "sostituibile" aspetto il codice di usare l'iniezione di dipendenza, questo non è esattamente un grosso problema per questo esempio. Non hai davvero bisogno di un IPasswordEncryptionService nello stesso modo in cui avresti bisogno, ad esempio, di ICreditCardAuthorizationService. Se, un giorno, modifichi l'algoritmo di hashing da SHA256 a SHA512, ora avrai invalidato ogni password nel tuo database.

+2

Poiché l'amministratore può ottenere un'istanza da Nhibernate (ad esempio se carica un oggetto Amministratore dal database) .Come farò in modo che NHibernate inietti il ​​servizio? m bloccato su –

+1

Sono d'accordo con la seconda modifica di Jarrett.In questo caso si inietta una dipendenza che non dovrebbe essere lì – jfar

+1

Sono d'accordo in questo caso, forse l'iniezione di dipendenza non è l'approccio migliore, ma potrei potenzialmente avere altro casi in cui è necessario come un ITaxCalculatorService, quindi sarebbe utile sapere come fare questo per riferimento futuro –

2

È necessario ricordare come si è hashing. In questo modo è possibile eseguire una stringa hash in futuro per verificare se è la loro password, confrontandola con il valore hash. Ciò significa che è necessario memorizzare un enum o qualche altro campo nell'oggetto che indica il meccanismo di hashing utilizzato nel database.

Altrimenti, se si modifica l'implementazione di hashing predefinita, tutte le vecchie password con hash non sono più valide e gli utenti non vedranno più il motivo per cui le loro password non funzionano più e finiranno per un'interfaccia IHashingService che non fornisce alcuna flessibilità (poiché l'implementazione dell'hash non può essere modificata senza aggiungere regole strane come "utilizzare questo hash per gli amministratori creati prima del 2010-01-12"), esistente senza una vera buona ragione.

A tal fine, vorrei aggiungere il campo appropriato (un enum, una stringa restituita dalla IHashingService interfaccia, qualcosa) e hanno o NHibernate istanziare il servizio di hashing per me tramite un IUserType implementazione, o mi piacerebbe usare una fabbrica modello in cui le istanze concrete sono state fornite alla fabbrica dal contenitore IoC. Ciò combinerebbe l'iniezione a livello di metodo di Jarrett con una soluzione che consente agli oggetti ri-idratati di trovare le loro implementazioni di hashing senza dipendere dal contenitore IoC.

Buona fortuna!

1

O hash la password in una facciata di applicazione (se si utilizza qualsiasi) o fornire l'implementazione IHashingService ad ogni chiamata a Administrator.SetPassword(..). Penso che sia stato chiamato doppia spedizione?!

Se ti ostini a DI-in-entità soluzione, ho fatto qualcosa di simile con PostSharp AOP e PostSharp4Spring dichiarando [Configurable] attributo sull'entità, ma la soluzione è per Spring.Net. Puoi guardare here per maggiori informazioni. Inoltre, se si sta configurando NHibernate dal contenitore DI, è possibile ricorrere a una ricorsione per provare a DI un'entità prima che il contenitore abbia terminato la configurazione. È necessaria una semplice classe statica con metodo per sopprimere DI sulla costruzione dell'entità durante l'inizializzazione del contenitore. Non è possibile fornire un esempio al momento però :(

Problemi correlati