Dependency Injection und Konfiguration
Framework Studio stellt eine Reihe von Extension Methods bereit, über die ein REST Service die FS-Infrastruktur einbinden, seine Konfiguration laden und Dependency Injection nutzen kann. Dieses Kapitel beschreibt alle Methoden und die zugehörige Middleware im Detail.
Bereitgestellt werden die Extension Methods durch die Klasse FSRestServiceExtensions:
UseFSConfiguration()— lädt die JSON-Konfiguration des Services aus den übergebenenargs.UseFSHosting()— registriert alle für das FS-Hosting relevanten Services im DI-Container.FSAppStartup()— initialisiert denFSServiceProviderund führt die Startup-Hooks aus.BuildFSHost()— Kombination ausUseFSHosting()+Build()+FSAppStartup().
Ergänzend dazu ist die Middleware FSServiceProviderScopeMiddleware in der Startup-Klasse zu registrieren, damit FSServiceProvider.Current pro Request einen eigenen Scope erhält.
Konfiguration
Über den RunWizard bzw. den Publish Wizard muss für den REST Service eine Konfiguration übergeben werden.
Diese Konfiguration muss im JSON-Format hinterlegt werden. Der Pfad zur Konfigurationsdatei wird über das args-Array übergeben.
Mit args[1] kann der Konfigurationspfad dann an den Service übergeben werden.
.UseConfiguration(new ConfigurationBuilder()
.AddJsonFile(args[1], optional: false, reloadOnChange: true)
.Build());
UseFSConfiguration(string[] args, bool optional = true)
Es kann auch die von Framework Studio bereitgestellte UseFSConfiguration() Methode verwendet werden. Diese lädt die JSON-Konfiguration automatisch aus args[1]. Es muss das args-Array übergeben werden.
return WebHost.CreateDefaultBuilder(args)
.UseFSConfiguration(args);
Beispiel JSON-Konfiguration
{
"Kestrel": {
"Endpoints": {
"Http": {
"Url": "http://localhost:5100"
},
"Https": {
"Url": "https://localhost:5101"
}
}
},
"Logging": {
"LogLevel": {
"Default": "Information"
}
}
}
Tip
Die URL/Port-Konfiguration sollte über die JSON-Konfiguration erfolgen und nicht fest im Code hinterlegt werden (UseUrls()), da sonst Customizing und Konfigurationsänderungen nicht mehr wirksam sind.
Dependency Injection
UseFSHosting()
UseFSHosting() registriert alle für das FS-Hosting relevanten Services im DI-Container des IWebHostBuilder.
Dazu gehören unter anderem Datenbankverbindungen, Authentifizierung, Autorisierung, Lizenzierung und der GlobalContext.
WebHost.CreateDefaultBuilder(args)
.UseFSHosting() // <-- FS-Services registrieren
.UseStartup<Startup>()
.Build();
FSAppStartup()
FSAppStartup() führt die notwendigen Initialisierungsroutinen auf dem fertig gebauten IWebHost aus.
Dabei wird der FSServiceProvider mit dem Root-ServiceProvider initialisiert und die registrierten Startup-Hooks ausgeführt.
CreateWebHostBuilder(args)
.Build()
.FSAppStartup() // <-- FS-Initialisierung
.Run();
Important
FSAppStartup() muss nach Build() und vor Run() aufgerufen werden.
Ohne diesen Aufruf ist der FSServiceProvider nicht initialisiert und FSServiceProvider.Current liefert eine InvalidOperationException.
Zusammenspiel UseFSHosting() und FSAppStartup()
Beide Methoden werden zusammen in der Run-Methode des REST Services verwendet:
public int Run(string[] args)
{
CreateWebHostBuilder(args)
.UseFSHosting()
.Build()
.FSAppStartup()
.Run();
return 0;
}
Kombination BuildFSHost()
Die beiden Methoden UseFSHosting() und FSAppStartup() können auch durch die Methode BuildFSHost() ersetzt werden. Diese kombiniert die beiden Aufrufe.
public int Run(string[] args)
{
CreateWebHostBuilder(args)
.BuildFSHost()
.Run();
return 0;
}
FSServiceProviderScopeMiddleware
Damit FSServiceProvider.Current innerhalb von Controller-Methoden und weiteren Komponenten des REST Services zur Verfügung steht, muss die FSServiceProviderScopeMiddleware in der Startup-Klasse registriert werden.
Die Middleware legt um jeden HTTP-Request einen eigenen DI-Scope.
Registrierung
In der Configure-Methode der Startup-Klasse muss die Middleware vor den anderen Middlewares registriert werden:
public void Configure(IApplicationBuilder app)
{
app.UseMiddleware<FSServiceProviderScopeMiddleware>();
// ... weitere Middlewares und Endpoints
app.UseMvc();
}
Verwendung
Nach der Registrierung steht FSServiceProvider.Current in allen Komponenten innerhalb der Request-Pipeline zur Verfügung:
[Route("api/[controller]")]
public class WeatherController : Controller
{
[HttpGet]
public IActionResult Get()
{
var authorization = FSServiceProvider.Current.GetRequiredService<IFSAuthorization>();
if (!authorization.Granted(myAccessUnit))
{
return Unauthorized();
}
// ...
return Ok(result);
}
}
Caution
Ohne die Middleware ist FSServiceProvider.Current im REST Service null.
Wird auf Services zugegriffen ohne dass die Middleware registriert ist, kommt es zu einer NullReferenceException.
Important
Die Middleware erzeugt mit FSServiceProvider.CreateScope() einen eigenen DI-Scope pro Request.
Es gelten dieselben Regeln wie beim eigenen Scope:
Innerhalb des Requests dürfen keine Tasks oder Threads gestartet werden, die über die Lebensdauer des Requests hinaus aktiv sind.
Beispielcode für einen Controller mit eigenem GlobalObject
In WCF-Services wurde über das Property GenerateGlobalObject automatisch ein using (FSServiceProvider.CreateScope()) um die Servicemethode gelegt. In REST Services muss dies selbst implementiert werden.
[Route("api/[controller]")]
public class TokenController : Controller
{
[HttpGet("globals")]
public async Task<IActionResult> GetGlobalTokens()
{
string result = "";
var localGlobal = FS.Hosting.Broker.Base.GlobalObjectManager.CreateGlobalObject(
FS.Shared.BaseObjects.guid.NewGuid().Value);
using (FSServiceProvider.CreateScope(localGlobal))
{
// ...
}
// Für diesen Zugriff wird die FSServiceProviderScopeMiddleware benötigt
result = FSServiceProvider.Current
.GetRequiredService<IFSAuthenticationService>()
.IsAuthenticationEnabled.ToString();
return Ok(result);
}
}
Important
In den WCF-Services wurde über das Property GenerateGlobalObject automatisch ein using (FSServiceProvider.CreateScope()) um die Servicemethode gelegt. Dies muss in den REST Services selbst implementiert werden!
Minimales Beispiel
Der folgende Code zeigt eine minimale IFSRestService-Implementierung mit JSON-Konfiguration über UseFSConfiguration() — ohne weitere FS-Services:
public class WeatherService : FS.Hosting.Shared.IFSRestService
{
public int Run(string[] args)
{
try
{
CreateWebHostBuilder(args).Build().Run();
return 0;
}
catch
{
return -1;
}
}
public virtual IWebHostBuilder CreateWebHostBuilder(string[] args)
{
return WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.UseFSConfiguration(args);
}
}
Vollständiges Beispiel mit DI
Das folgende Beispiel zeigt einen kompletten REST Service mit Dependency Injection, Middleware und Konfiguration:
// REST Service - IFSRestService Implementierung
public class WeatherService : FS.Hosting.Shared.IFSRestService
{
public int Run(string[] args)
{
if (args[7] == "True")
{
System.Diagnostics.Debugger.Launch();
}
CreateWebHostBuilder(args)
.BuildFSHost() // UseFSHosting() + Build() + FSAppStartup()
.Run();
return 0;
}
public virtual IWebHostBuilder CreateWebHostBuilder(string[] args)
{
return WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.UseFSConfiguration(args);
}
}
// Startup-Klasse
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
// Eigene Services registrieren
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// FSServiceProviderScopeMiddleware als erstes registrieren!
app.UseMiddleware<FSServiceProviderScopeMiddleware>();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseMvc();
}
}