2014-05-21 8 views
5

Sto avendo una cosa terribile a questo nonostante aver fatto qualcosa di simile in un'applicazione Windows. Sto lavorando a un'applicazione WPF (Prism, Unity, MVVM) e ho appena completato la vista di accesso. Una volta che le credenziali dell'utente sono stati convalidati contro una tabella in SQL Server faccio questo:Persistenti credenziali utente in WPF con Unity e MVVM

Thread.CurrentPrincipal = user.GenericPrincipal(); 

La classe utente è definito come tale:

public class ApplicationIdentity : GenericIdentity, IApplicationIdentity 
{ 
    public string UserName { get; set; } 
    public bool Authenticated { get; set; } 

    public ICollection<IModuleProperties> Modules { get; set; } 
    public ICollection<IViewProperties> Views { get; set; } 

    public ApplicationIdentity(string userName, IAuthenticatedUser authenticatedUser) 
     : base(userName) 
    { 
     UserName = userName; 
     Authenticated = authenticatedUser.Authenticated; 
     Modules = authenticatedUser.Modules; 
     Views = authenticatedUser.Views; 
    } 

    public IPrincipal GenericPrincipal() 
    { 
     return new GenericPrincipal(this, null); 
    } 
} 

Il problema che sto funzionando in è che quasi subito dopo la schermata di login è respinto faccio presente invito:

var currentUser = (IApplicationIdentity)Thread.CurrentPrincipal.Identity; 

che genera un'eccezione che l'identità non può essere gettato a digitare IApplicationIdentity, e non sono sicuro che cosa manco . Tutti gli articoli SO/Google che ho letto finora hanno risolto questo problema autenticando l'utente con AD, ma questo non funziona nel mio scenario.

Il problema che sto cercando di risolvere qui è semplicemente quello di mantenere l'utente attualmente connesso e di quali moduli e viste dovrebbero avere accesso. Se esiste un modo migliore per raggiungere questo obiettivo al di fuori dell'impostazione di CurrentPrincipal, sono completamente aperto ad altre soluzioni. Grazie per l'aiuto che potresti essere in grado di fornire!

EDIT (soluzione):

voglio solo chiudere il ciclo su ciò che la soluzione era. Quello che segue è un po 'di tutorial, quindi è un po' lungo, ma dovrebbe essere utile a chiunque vi si imbattesse. La risposta accettata suggeriva di iniettare il contenitore Unity, registrare un'istanza del mio oggetto e lavorare con esso da lì. Sono d'accordo che questo è probabilmente l'approccio corretto, ma mi ha richiesto di "hackerare" sul mio Bootstrapper un po '. Prima di arrivare alla logica di Bootstrapper, dovrei probabilmente andare oltre un po 'del lavoro di preparazione in questione.

Nel mio approccio originale mio avviso Accesso non è stato registrato con il mio contenitore, perché la vista Accesso è stato istanziato prima del mio programma di avvio automatico in esecuzione (App.xaml aperta la finestra di login.)

La prima cosa che ho fatto è stato fare la mia vista e ViewModel ala iniettabile:

public Login(ILoginViewModel viewModel) 
    { 
     InitializeComponent(); 
     DataContext = viewModel; 
    } 

    public LoginViewModel(IUnityContainer container) 
    { 
     Container = container; 
    } 

Ancora una volta: questo dovrebbe essere noto a chiunque che ha lavorato con Unity (o IOC in generale). Poi ho bisogno di registrare il mio oggetto utente corrente con Unity, una volta che un utente è stato autenticato:

private void Login(object obj) 
    { 
     ... 
     if (user.Authenticated) 
     { 
      Container.RegisterInstance("CurrentUser", user); 
     } 
     ... 
    } 

Qualcuno che ha seguito qualsiasi degli articoli/MVVM prisma/Unity che abbondano su Internet è probabilmente familiarità con il seguente metodo :

protected override IModuleCatalog CreateModuleCatalog() 
    { 
     var catalog = new ModuleCatalog(); 
     catalog.AddModule(typeof (CoreModule)); 
     catalog.AddModule(typeof (CoreModule2)); 
     catalog.AddModule(typeof (CoreModule3)); 
     return catalog; 
    } 

Questo metodo è abbastanza semplice, ma in un vero e proprio scenario mondiale i moduli che un utente ha accesso a probabilmente essere dinamico piuttosto che statico CreateModuleCatalog() è chiamato nel metodo di avvio automatico Run() (o una combinazione di entrambi.) prima di InitializeShell(). Nel mio caso ho ancora un modulo statico a cui tutti gli utenti avrebbero accesso (indipendentemente dal livello di autorizzazione), ma (a mio parere) si sentirà "anti-patternish" per istanziare la vista Login da questo metodo (per non parlare del registro i tipi con Unity.) Così la mia CreateModuleCatalog() diventato:

protected override IModuleCatalog CreateModuleCatalog() 
    { 
     var catalog = new ModuleCatalog(); 
     catalog.AddModule(typeof(CoreModule)); 
     return catalog; 
    } 

ho scelto di usare la mia forzatura dei InitializeShell() per registrare i miei tipi di accesso, visualizzare la vista di accesso, ecc fine ho liquidata con questo per un'implementazione:

protected override void InitializeShell() 
    { 
     base.InitializeShell(); 
     Container.RegisterType(typeof (Login), "LoginView"); 
     Container.RegisterType<ILoginViewModel, LoginViewModel>(); 

     Application.Current.ShutdownMode = ShutdownMode.OnExplicitShutdown; 
     ShowLogOn(); 

     Application.Current.MainWindow = (Window)Shell; 
     Application.Current.MainWindow.Show(); 
    } 

ShowLogOn() Arresta l'applicazione se l'utente alla fine preme il pulsante Annulla nella vista di accesso, quindi avrei probabilmente inserito tutto il codice da ShowLogOn() prima della chiamata a base.InitializeShell() in modo che non venga eseguito codice inutile. ShowLogOn() sembra esattamente come ci si potrebbe aspettare a guardare:

private void ShowLogOn() 
    { 
     var login = Container.Resolve<Login>(); 
     var dialogResult = login.ShowDialog(); 

     if (!dialogResult.Value) 
     { 
      Application.Current.Shutdown(1); 
     } 
     else 
     { 
      LoadAuthorizedModules(); 
      Application.Current.MainWindow = null; 
      Application.Current.ShutdownMode = ShutdownMode.OnMainWindowClose; 
     } 
    } 

Se l'utente annulla fuori dalla mia vista Accesso il risultato finestra sarà falso e l'applicazione viene arrestato. Se sono stati autenticati correttamente, ora dobbiamo caricare i moduli che sono autorizzati a vedere. Questo è anche abbastanza semplice:

private void LoadAuthorizedModules() 
    { 
     var currentUser = Container.Resolve<IApplicationIdentity>("CurrentUser"); 
     foreach (var module in currentUser.Modules) 
     { 
      var moduleAssembly = Assembly.Load(module.AssemblyName); 
      var loadingModule = moduleAssembly.GetType(module.Type); 

      ModuleCatalog.AddModule(new ModuleInfo 
       { 
        ModuleName = loadingModule.Name, 
        ModuleType = loadingModule.AssemblyQualifiedName 
       }); 
     } 
    } 

Questo metodo garantisce un po 'più spiegazione. Per prima cosa dai un'occhiata a questa riga perché potrebbe essere fonte di confusione: var currentUser = Container.Resolve<IApplicationIdentity>("CurrentUser"); avviso che non ho registrato esplicitamente il tipo IApplicationIdentity ovunque nel mio codice! Tuttavia, è stato registrato implicitamente quando ho fatto questo: Container.RegisterInstance("CurrentUser", user); tecnicamente avrei potuto scrivere la dichiarazione precedente come: Container.RegisterInstance<IApplicationIdentity>("CurrentUser", user); ma questo è ridondante per me, quindi fai ciò che ti senti più a tuo agio.

La proprietà Moduli di IApplicationIdentity contiene una raccolta di oggetti che contengono informazioni sui moduli a cui l'utente corrente ha accesso. module.AssemblyName è il nome dell'assembly fisico in cui risiede il mio tipo di modulo personalizzato e module.Type è il tipo del modulo.

Come si può vedere: una volta che il tipo è stato caricato attraverso la potenza della riflessione, è necessario aggiungerlo alla proprietà Unity Bootstrappers ModuleCatalog che è diretta. Supponendo che tutto sia andato a buon fine, l'esecuzione dovrebbe tornare al metodo InitializeShell e ora Shell dovrebbe avviarsi.

Questo è stato piuttosto lungo, ma spero che qualcuno lo trovi utile. Inoltre, sarei interessato a qualsiasi opinione su come rendere questo codice "migliore". Grazie!

+0

Non sarà possibile eseguire il cast di un oggetto Identity framework sul proprio tipo personalizzato. Probabilmente dovresti cambiare il tuo tipo ApplicationIdentity per avere un costruttore che accetta invece un oggetto Identity come parametro. – Geo242

risposta

1

È possibile controllare questa domanda se si desidera archiviarla nella discussione - https://stackoverflow.com/a/6699699/1798889. Ma se vuoi solo avere accesso a IApplicationIdentity. Ti suggerirei di registrare quell'istanza con l'unità.

// IUnityContainer can be injected by unity 
IUnityContainer container; 

// Register this instance each time now you call for IApplicationIdentity this object will be returned 
container.RegisterInstance(typeof (IApplicationIdentity), user.GenericPrincipal()); 

Con questo ora è possibile iniettare IApplicationIdentity e l'unità soddisferà ogni volta che si ha bisogno. Non devi preoccuparti dei thread.

+0

Ciao Charles. Ho guardato il link, ma Windows Identity non è quello che rappresenta il mio oggetto utente, quindi non funzionerà. Comunque, hai fatto un buon punto sull'iniezione del contenitore che alla fine mi ha portato alla mia risposta. Grazie per essere una cassa di risonanza! – dparsons