Ich habe eine Web-API-Anwendung, in die der Controller über Dependency Injection (Unity) Services / Repositories usw. injiziert hat. Nehmen wir an, ich habe eine IStuffService
, die die IPrincipal
der aktuellen Anfrage (oder einen Wrapper darum) benötigt.
Das Problem mit der Web-API scheint zu sein, dass die einzige zuverlässige Quelle der aktuellen Anfrage / des aktuellen Benutzers die Request
-Eigenschaft auf der -Instanz von ApiController
ist. . Alles, was statisch ist (sei es HttpContext.Current
, CallContext.Get/SetData
oder Thread.Get/SetData
), ist aufgrund der Synchronisationseigenschaft der Web API nicht garantiert auf demselben Thread zu liegen.
Wie vergewissere ich mich zuverlässig, dass der anforderungsspezifische Kontext durch Abhängigkeiten geleitet wird und, was noch wichtiger ist, dass die Operation den korrekten IPrincipal
während der gesamten Operation beibehält?
Zwei Optionen:
IPrincipal
benötigt, hat sie als Argument für die Methode - das ist der zuverlässigste Weg, aber es erfordert auch, dass ich dieses Ding in jeder Methodensignatur habe container.Resolve(opType, new DependencyOverride(typeof(IPrincipal), principal))
Option 2 bedeutet, dass meine Methodensignaturen sauber sind, aber es bedeutet auch, dass ich sicherstellen muss, dass alle Abhängigkeiten den TransientLifetimeManager verwenden, nicht einen Singleton oder sogar einen pro-Thread.
Gibt es eine bessere Lösung, als ich nicht sehe?
Aus den Kommentaren:
@MichaelStum, ich glaube, HttpContext.User sollte korrekt fließen über async / await (innerhalb derselben HTTP-Anfrage). Ist es nicht für dich? - Naseratio vor 17 Stunden
@Noseratio Siehe die anderen Antworten - in .net 4.0, es ist an die gebunden aktueller Thread und wurde nicht ordnungsgemäß gepflegt. Es scheint, dass in 4.5, Dies könnte behoben werden. Das heißt, HttpContext.Current ist immer noch nicht so geeignet in Web-API, weil auf selbst gehosteten es keine gibt HttpContext.Current.
AFAIK, es gibt sowieso keine angemessene Unterstützung für async/await
in ASP.NET 4.0 (Sie können wahrscheinlich Microsoft.Bcl.Async
für die Sprachunterstützung verwenden, aber es gibt keine ASP.NET-Laufzeitunterstützung, also müssten Sie greifen Sie auf AsyncManager
zurück, um das TAP-Muster zu implementieren.
Das heißt, ich bin zu 99% sicher, dass Thread.CurrentPrincipal
immer noch korrekt über await
continuations in ASP.NET 4.0 fließen würde. Das liegt daran, dass es als Teil von ExecutionContext
flow statt als Synchronisationskontext fließt. Bezüglich HtttContext.Current.User
bin ich nicht sicher, ob es in ASP.NET 4.0 korrekt fließen würde (obwohl es in ASP.NET 4.5 sicherlich funktioniert).
Ich habe Ihre Frage erneut gelesen, konnte jedoch eine explizite Beschwerde darüber erhalten, dass Thread.CurrentPrincipal
nicht korrekt übertragen wurde. Tritt dieses Problem im vorhandenen Code auf (wenn dies der Fall ist, handelt es sich wahrscheinlich um einen Fehler in ASP.NET)?
Hier ist eine Liste verwandter Fragen, die von Stephen Cleary mit einigen großartigen Einsichten beantwortet wurden:
Warum muss "Task.Yield ()" erwartet werden, damit Thread.CurrentPrincipal korrekt ausgeführt wird?
Mit der ASP.NET-Web-API fließt mein ExecutionContext nicht in asynchronen Aktionen
Dieser Blogpost von Scott Hanselman ist auch verwandt, obwohl er über WebForms spricht:
Wenn Sie sich Sorgen um Self-Hosting-Szenarien machen , glaube ich, dass Thread.CurrentPrincipal
dort immer noch korrekt fließen wird (sobald eine korrekte Identität festgelegt wurde). Wenn Sie andere Eigenschaften (außer diejenigen, die automatisch mit ExecutionContext
fließen) fließen lassen möchten, können Sie Ihre eigener Synchronisationskontext. Eine weitere Option (nicht so nett, IMO) ist benutzerdefinierte awariters zu verwenden.
Wenn Sie in einer Situation, in der tatsächlich Thread-Affinität über await
continuation benötigt wird (ähnlich wie in einer clientseitigen UI-Anwendung), haben Sie auch diese Option (wiederum mit einem benutzerdefinierten Synchronisationskontext):
Die ultimative Antwort ist, dass unsere IoC-Container geändert werden müssen, um async zu unterstützen / besser zu warten.
Das Verhalten von async / apply hat sich zwischen .NET 4 und .NET 4.5 geändert. In .NET 4.5 wurde SynchronizationContext
eingeführt und es wird HttpContext.Current
korrekt wiederhergestellt (siehe Ссылка ). Es ist jedoch häufig eine bewährte Methode, .ConfigureAwait(false)
zu verwenden (siehe "Kontext konfigurieren" in Ссылка ) und fordert ausdrücklich, dass der Kontext nicht erhalten bleibt. In diesem Fall hätten Sie immer noch das Problem, das Sie beschreiben.
Die beste Antwort, die ich in meinem eigenen Code finden konnte, ist, die Abhängigkeit, die von HttpContext.Current
(in Ihrem Fall IPrincipal
) kommt, früh in der Webanfrage anzufordern, damit sie geladen wird in den Behälter.
Ich habe keine Erfahrung mit Unity, aber in Ninject würde das etwa so aussehen:
%Vor% Dann würde ich sicher sein, dass IPrincipal
früh in der Webanfrage geladen wurde, bevor Sie den Kontext verloren haben. Entweder in BeginRequest oder als Abhängigkeit vom Controller. Dadurch wird die IPrincipal
in den Container für diese Anforderung geladen.
Hinweis: Es gibt immer noch Situationen, in denen dies nicht funktioniert. Ich weiß nicht, ob Unity dieses Problem hat, aber ich weiß, Ninject tut es. Es verwendet tatsächlich den HttpContext.Current, um festzustellen, welche Anfrage aktiv ist. Wenn Sie also später versuchen, etwas aus dem Container aufzulösen, z. B. einen Service-Locator oder eine Factory, kann es möglicherweise nicht aufgelöst werden.
Ich weiß, das ist eine alte Frage, die ich mit der ersten Option (in der Frage) bearbeitet habe und es funktioniert.
UPDATE: Ich habe meine Antwort gelöscht, als ich merkte, dass ich etwas gepostet habe, das nicht funktioniert. Entschuldigung für etwaige Unannehmlichkeiten.
Wie kann ich zuverlässig sicherstellen, dass der anforderungsspezifische Kontext übergeben wird? durch Abhängigkeiten und noch wichtiger, dass die Operation beibehalten wird das korrekte IPrincipal den ganzen Weg durch die Operation?
Ich denke nicht, dass Sie das tun sollten. Ihr Service ist eine untere Ebene als Ihr Api-Controller. Ihr Dienst sollte nicht von Klassen abhängig sein, die sich auf die höhere Ebene beziehen , andernfalls könnte Ihr Dienst nicht wiederverwendet werden , z. B. wenn Sie eine win forms-Anwendung erstellen müssen der vorhandenen Dienste.
IPrincipal
ist nicht geeignet, um in unsere Dienste eingefügt zu werden, da es web-bezogen ist . Wenn wir diese Informationen an die unteren Ebenen (Dienst) weiterleiten, sollten wir unsere neutralen Klassen oder nur eine userId an entkoppeln von unseren Diensten weitergeben die Anwendung, die es verwendet.
Sie sollten eigene Klassen für Benutzer und alle anforderungsbezogenen -Klassen definieren, die in unserer Servicesbene verwendet werden sollen, da sie domänenbezogen sind. Damit ist Ihre Service-Ebene Application-Layer (Web, Win Forms, Konsole, ..) agnostic:
%Vor% Dann können Sie IAppPrincipal
als pro-Request-Bereich in Ihrer Webanwendung registrieren und alle Eigenschaften mit Ihrem IPrincipal
füllen. Das wird Ihre IAppPrincipal
für Ihr gesamtes Objektdiagramm vor jedem await/async
Aufruf initialisieren. Beispielcode mit Unity:
Der Schlüssel hier ist, dass wir unsere Serviceebene bereits vom Web entkoppeln. Wenn Sie Ihre Serviceebene zum Erstellen eines Windows-Formulars oder einer Konsolenanwendung erneut verwenden müssen, könnten Sie IAppPrincipal
als Singleton registrieren und sie anders ausfüllen.
Wir müssen uns nicht mit plattformbezogenen Problemen wie async/await
Tags und Links asp.net-web-api .net c# asp.net async-await