Wie würde ich eine Web-API-Anforderung über Dependency Injection übergeben?

9

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:

  1. Jede Methode, die ein 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
  2. Injizieren Sie das IPrincipal in den ctor des Service, indem Sie bei jeder Anfrage einen neuen Eintrag des Objektgraphen erzeugen, indem Sie DependencyOverride in Unity verwenden: 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?

    
Michael Stum 18.06.2014, 21:55
quelle

4 Antworten

2

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:

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):

Noseratio 20.06.2014 02:49
quelle
2

Die ultimative Antwort ist, dass unsere IoC-Container geändert werden müssen, um async zu unterstützen / besser zu warten.

Hintergrund:

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.

Antwort:

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.

    
Jeff Walker Code Ranger 19.06.2014 14:15
quelle
0

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.

    
MaGnumX 15.07.2017 01:10
quelle
-1
  

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:

%Vor%

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

auseinandersetzen     
Khanh TO 19.06.2014 11:42
quelle