8

Sto utilizzando ASP.NET MVC 5 e Identity Framework. Quando chiamo UserManager.UpdateAsync (...) verranno eseguiti i miei eventhandlers su ApplicationDbContext() SaveChanges. Qui sto usando HttpContext.Current per diversi scopi (logging e auditing) quindi devo dire all'utente corrente. Tuttavia, l'intero metodo viene eseguito in un thread di lavoro e HttpContext.Current è null.HttpContext.Current è nullo nei metodi di Identity Framework

Il problema più grande che i metodi di "sincronizzazione" di UserManager sono solo wrapper attorno alla versione asincrona, quindi le chiamate sono serializzate, ma i metodi (e gli eventhandler) continuano a essere eseguiti in un thread di lavoro diverso.

Si prega di notare che questo problema non ha nulla a che fare con il contesto async/await. Nel controller dopo l'attesa (o chiamando la versione 'sync') ho indietro il HttpContext corretto, anche il metodo del controller sta continuando in un altro thread. Va bene.

Quindi il problema è all'interno del lavoratore asincrono che verrà eseguito in entrambe le versioni "sync" e async. Penso di capire i fenomeni (ma non sono contento delle false versioni del metodo "sync", i veri metodi di sincronizzazione non mostrerebbero questo problema). Solo che non so come gestirlo/risolverlo.

[btw: Non sarebbe più naturale implementare le operar di UserManager come semplici versioni di pura sincronizzazione, quindi avvolgerle in wrapper multithreading asincroni ?. Se continuiamo questa moda asincrona senza pensare, inventeremo presto l'operatore di assegnazione asincrona. Mi costa dozzine di ore (solo questo numero) e costa miliardi di dollari in tutto il mondo, sono sicuro in molti casi meno ritorno del suo prezzo.]

Bonus: Stiamo parlando di UserManager che ha un impatto piuttosto marginale, ma lo stesso principi e problemi possono applicare qualsiasi libreria fuori dalla scatola (scatola nera per te) che gli autori non implementano le versioni di sincronizzazione e o non si preoccupano del contesto del thread del controller. Che dire di EF, non è così marginale ... e che dire dell'infrastruttura di istanziazione di contenitori DI come "ambito di richiesta" o "ambito di sessione". Sicuramente si comportano male se la risoluzione si verifica in una discussione senza HttpContext.Current. Recentemente ho aggiornato SendGrid NuGet e (come una modifica sostanziale) il metodo Deliver() è andato, e ora solo DeliverAsync() esiste ...

Mi piacerebbe avere un modo sicuro affidabile, come posso accedere a HttpContext all'interno di questo lavoratore per scopi di registrazione e controllo.

codice di esempio, il controller versione 'sync':

[AcceptVerbs(HttpVerbs.Post)] 
public virtual ActionResult Edit(ApplicationUser user) 
{ 
    // validation etc 
    // Update() seems to be only a poor wrapper around the async version, still uses a worker thread. 
    var result = UserManager.Update(user); 
    // Note: HttpContext is correct here so it is not an async/await problem 

    // error handling, creating ActionResult etc. 
} 

codice di esempio, la versione del controller asincrono:

[AcceptVerbs(HttpVerbs.Post)] 
public virtual async Task<ActionResult> Edit(ApplicationUser user) 
{ 
    // validation etc 
    var result = await UserManager.UpdateAsync(user); 
    // Note: HttpContext is correct here so it is not an async/await problem 

    // error handling, creating ActionResult etc. 
} 

e il gestore di eventi in cui HttpContext è nullo:

public ApplicationDbContext() : base("DefaultConnection", false) 
{ 
    InitializeAudit(); 
} 

private void InitializeAudit() 
{ 
    var octx = ((IObjectContextAdapter) this).ObjectContext; 

    octx.SavingChanges += 
     (sender, args) => 
     { 
      // HttpContext.Current is null here 
     }; 
} 

Qualche idea?

+0

Considerare la visualizzazione del codice ... –

+0

Puoi mostrare il codice specifico che stai chiedendo? – i3arnon

+1

Che ne dici di memorizzare 'HttpContext.Current' in una variabile locale prima di creare qualsiasi attività e passarla in qualche modo a task/threads. – EZI

risposta

4

Come hai detto, questo si verifica a causa della filettatura. Il delegato viene eseguito in un thread diverso, rendendo inaccessibile HttpContext.

È possibile spostare la variabile all'esterno del delegato, rendendola una chiusura.

private void InitializeAudit() 
{ 
    var octx = ((IObjectContextAdapter) this).ObjectContext; 
    HttpContext context = HttpContext.Current; 

    octx.SavingChanges += 
     (sender, args) => 
     { 
      // context is not null 
     }; 
} 
+1

Grazie ho pensato qualcosa di simile. Tuttavia, non sono io a creare l'istanza ApplicationDbContext che è anche l'Identity Framework, quindi non è garantito che venga creato nel thread del controller, quindi nel suo costruttore non è necessario avere HttpContext. –

1

Si utilizza identità asp.net attraverso Owin, quindi un'istanza del DbContext è creato per ogni richiesta, e si può ottenere questo riferimento da qualsiasi punto della tubazione richiesta.

nb. questo è utile, ma penso che il dbcontext non dovrebbe essere accessibile al di fuori del gestore. Nella progettazione dell'identità asp.net, solo il manager deve essere a conoscenza del negozio. Credo che il dbcontext sia esposto perché diversi middleware di identità di asp.net hanno una dipendenza su di esso.

Ma, potrebbe aiutare a risolvere il problema:

Consentire la vostra abitudine DbContext gestore da impostare al di fuori della classe:

public EventHandler SavingChangesEventHandler 
     { 
      set 
      { 
       (((System.Data.Entity.Infrastructure.IObjectContextAdapter)this).ObjectContext).SavingChanges += value; 
      } 
     } 

dichiarare una classe ActionFilter personalizzato e registrarlo, allora ignorare OnActionExecuting:

Filtering in ASP.NET MVC https://msdn.microsoft.com/en-us/library/gg416513(VS.98).aspx

public class CustomizeAppDbcontextFilter : ActionFilterAttribute 
{ 
    public override void OnActionExecuting(HttpActionContext actionContext) 
    { 
     var dbcontext = HttpContext.GetOwinContext().Get<ApplicationDbContext>();  
     var currentuser = HttpContext.Current.User; 

     dbcontext.SavingChangesEventHandler = (sender, args) => 
      { 
       // use currentuser 
      }; 
    }  
} 

potrebbe essere bisogno di queste dichiarazioni utilizzando per essere in grado di chiamare i metodi di estensione identity.owin:

utilizzando Microsoft.AspNet.Identity;

utilizzando Microsoft.AspNet.Identity.Owin;

Si dovrebbe essere nel thread del controller perché OnActionExecuting sta eseguendo il wrapping dell'azione del controller.

Non l'ho provato, quindi potrebbe essere necessario un po 'di lucidatura ma il concetto dovrebbe funzionare.

Problemi correlati