2009-07-19 9 views
44

Alcuni giorni fa ho riscontrato questo problema con il threading ASP.Net. Volevo avere un oggetto singleton per richiesta web. In realtà ho bisogno di questo per la mia unità di lavoro. Volevo istanziare un'unità di lavoro per ogni richiesta web in modo che la mappa delle identità fosse valida attraverso la richiesta. In questo modo ho potuto utilizzare un IoC per iniettare il mio IUnitOfWork nelle mie classi di repository in modo trasparente e utilizzare la stessa istanza per interrogare e aggiornare le mie entità.Contesto Singleton Per Call (richiesta Web) in Unity

Dal momento che sto usando Unity, ho erroneamente utilizzato PerThreadLifeTimeManager. Mi sono presto reso conto che il modello di threading ASP.Net non supporta ciò che voglio ottenere. Fondamentalmente usa un theadpool e ricicla i thread, e ciò significa che ottengo un UnitOfWork per thread !! Tuttavia, quello che volevo era una unità di lavoro per richiesta web.

Un po 'di googling mi ha dato this great post. Era esattamente quello che volevo; tranne per la parte di unità che era abbastanza facile da acheive.

Questa è la mia implementazione per PerCallContextLifeTimeManager per l'unità:

public class PerCallContextLifeTimeManager : LifetimeManager 
{ 
    private const string Key = "SingletonPerCallContext"; 

    public override object GetValue() 
    { 
     return CallContext.GetData(Key); 
    } 

    public override void SetValue(object newValue) 
    { 
     CallContext.SetData(Key, newValue); 
    } 

    public override void RemoveValue() 
    { 
    } 
} 

E naturalmente Io lo uso per registrare il mio unità di lavoro con un codice simile a questo:

unityContainer 
      .RegisterType<IUnitOfWork, MyDataContext>(
      new PerCallContextLifeTimeManager(), 
      new InjectionConstructor()); 

Speranza che salva qualcuno un po 'di tempo.

+0

Soluzione piacevole. Se posso, ti consiglio di rinominarlo in "CallContextLifetimeManager" poiché le richieste Web sono probabilmente solo una delle potenziali applicazioni. –

+0

Vero, ho aggiornato il testo e il codice per riflettere questo. Grazie. –

+0

+1 Molto utile. – MrDustpan

risposta

25

soluzione Ordinato, ma ogni istanza di LifetimeManager dovrebbe usare una chiave univoca piuttosto che una costante:

private string _key = string.Format("PerCallContextLifeTimeManager_{0}", Guid.NewGuid()); 

caso contrario, se si dispone di più di un oggetto registrato con PerCallContextLifeTimeManager, stanno condividendo la stessa chiave di accesso CallContext e non recupererai l'oggetto previsto.

Anche la pena attuare RemoveValue per assicurare gli oggetti vengono eliminati:

public override void RemoveValue() 
{ 
    CallContext.FreeNamedDataSlot(_key); 
} 
+0

-1 CallContext viene ricreato su ogni richiesta. La chiave non deve essere univoca tra diverse istanze del singleton per richiesta. –

+8

+1 In realtà, deve essere proprio come ha detto sei occhi. Se non si assegnano chiavi univoche, tutti gli oggetti sono registrati sotto chiave singola e le cose si incasinano. –

+0

-1 Vedere la risposta di Steven Robbins e il commento di Micah Zoltu alla domanda: sotto carico 'CallContext' non viene conservato per una singola richiesta. –

20

Anche se questo va bene di chiamare questo PerCallContextLifeTimeManager, sono abbastanza sicuro che questo è non "sicuro" per essere considerato un ASP.Net LifeTimeManager per richiesta.

Se ASP.Net fa il suo filo-swap allora l'unica cosa portato attraverso al nuovo thread attraverso CallContext è il HttpContext corrente - qualsiasi altra cosa si memorizzano in CallContext sarà andato. Questo significa che sotto il carico pesante il codice sopra potrebbe avere risultati non previsti - e immagino che sarebbe un vero dolore rintracciare il perché!

L'unico modo "sicuro" per fare questo è con HttpContext.Current.Items, o fare qualcosa di simile:

public class PerCallContextOrRequestLifeTimeManager : LifetimeManager 
{ 
    private string _key = string.Format("PerCallContextOrRequestLifeTimeManager_{0}", Guid.NewGuid()); 

    public override object GetValue() 
    { 
     if(HttpContext.Current != null) 
     return GetFromHttpContext(); 
     else 
     return GetFromCallContext(); 
    } 

    public override void SetValue(object newValue) 
    { 
     if(HttpContext.Current != null) 
     return SetInHttpContext(); 
     else 
     return SetInCallContext(); 
    } 

    public override void RemoveValue() 
    { 
    } 
} 

Questo significa, ovviamente, prendendo le dipendenze su System.Web :-(

Molto più informazioni su questo sito:

http://piers7.blogspot.com/2005/11/threadstatic-callcontext-and_02.html

3

Grazie per il tuo contributo,

Ma la domanda proposta di attuazione presenta due inconvenienti, uno dei quali è un grave bug come già detto da Steven Robbins in his answer e Micah Zoltu in a comment.

  1. contesto di chiamata non è t garantito di essere conservato da asp.net per una singola richiesta. Sotto carico, può passare a un altro, causando l'interruzione proposta di implementazione.
  2. Non gestisce il rilascio di dipendenze alla fine della richiesta.

Attualmente, il pacchetto Unity.Mvc Nuget fornisce un PerRequestLifetimeManager per eseguire il lavoro. Non dimenticare di registrare il codice UnityPerRequestHttpModule associato nel codice di avvio, altrimenti non verrà gestito nemmeno il rilascio delle dipendenze.

Uso bootstrapping

DynamicModuleUtility.RegisterModule(typeof(UnityPerRequestHttpModule)); 

o in web.config system.webServer/modules

<add name="UnityPerRequestHttpModule" type="Microsoft.Practices.Unity.Mvc.UnityPerRequestHttpModule, Microsoft.Practices.Unity.Mvc" preCondition="managedHandler" /> 

Risulta sua implementazione corrente è adatto anche per moduli web. E non dipende nemmeno da MVC. Sfortunatamente, il suo assemblaggio fa, causa di alcune altre classi che contiene.

Attenzione, nel caso in cui si utilizzi un modulo http personalizzato utilizzando le dipendenze risolte, è possibile che siano già disponibili nel modulo EndRequest. Dipende dall'ordine di esecuzione del modulo.