AUHelper und die Dependency Injection in FS
Mit Version 4.8 unterstützen Framework-Studio Anwendungen die Nutzung von Dependency-Injection.
Viele zentrale Komponenten, die bisher direkt über das GlobalObject oder den AUHelper erreichbar waren, wurden in eigenständige, per DI auflösbare Interfaces und Services überführt.
Dies betrifft insbesondere folgende Bereiche:
- Lizenzprüfung
- Printing-API
- DB Connections
- Global Context
- Authentifizierung eines Users - siehe
IFSAuthentication - Zugriff auf Runtime-Konfigurationsdatei - siehe
IRuntimeConfigProvider
Die folgenden Abschnitte erläutern die wichtigsten Änderungen und zeigen, wie die neuen DI-basierten APIs verwendet werden können.
Update auf FS 4.8
Zusammengefasst bedeutet die Überarbeitung des AUHelpers und des GlobalObjects, dass viele Aufrufe von Methoden und Properties am AUHelper nun auf Fehler laufen oder obsolete Warnings produzieren. Die entsprechenden Funktionen wurden in andere Klassen ausgelagert. Der Zugriff erfolgt nun über das zugehörige Interface.
Tip
Ein großer Teil des AUHelpers kann über eine Maintenance Routine korrigiert werden. Siehe Kapitel 'AUHelper - Code Replace Cleanup'
Manuelle Anpassungen sind für folgende Themen notwendig:
Lizenzprüfung
Prüfung der Lizenz und der Rollen, die dem User zugeteilt wurden
Früher: AUHelper.Granted(..)
Jetzt: IFSAuthorization.IsGranted(AccessUnit) oder IFSAuthorization.IsGranted(AccessUnit, AUPermissionType)
Prüfung der Lizenz, die zugeteilten Rollen werden nicht berücksichtigt
Früher: AUHelper.LicenseGranted(..)
Jetzt: IRuntimeLicensePermissionEvaluator.IsGranted(AccessUnit) oder IRuntimeLicensePermissionEvaluator.IsGranted(AccessUnit, AUPermissionType)
Prüfung, ob die Lizenz-Datei den Zugriff auf die übergebene AccessUnit erlaubt
Früher: AUHelper.LicenseGrantedInFile()
Jetzt: IRuntimeLicenseVerifier.IsGranted(AccessUnit, AUPermissionType)
Printing
Die API des Printings hat sich grundlegend verändert. Die Zuständigkeiten teilen sich wie folgt auf:
Datenklassen:
- PrintServer - Beschreibt einen PrintServer
- Printer - Beschreibt einen Printer
- PrintJob - Beinhaltet alle für den Druck erforderlichen Daten
- ReportDocument - Erzeugt nur noch einen PrintJob
Alle Methoden für den Druck befinden sich nun im injectable IPrintServerApi.
Veränderungen am ReportDocument:
reportDocument.PrintToPrinter(..)ist nun als ExtensionMethod am ReportDocument verfügbar: PrintToPrinter- Der Inhalt der Methode
reportDocument.PrintToTransaction(..)wurde gesplittet in- Erstellung des PrintJobs ->
reportDocument.CreatePrintJob(..)und - printJob.PrintToTransaction ->
PrintServerAPI.EnqueuePrintJob().
- Erstellung des PrintJobs ->
Der Code für einen Print sieht nun wie folgt aus:
var job = reportDocument.CreatePrintJob();
job.PrinterId = printerId;
var transactionId = _printServerApi.CreateTransaction(printServerId);
_printServerApi.EnqueuePrintJob(job, transactionId);
_printServerApi.CommitTransaction(transactionId);
oder abgekürzt über die ExtensionMethod am ReportDocument
reportDocument.PrintToPrinter(IPrintServerApi printServerApi, int copies, bool isCollated, int startPage, int endPage, int orderID, guid printServerId, guid printerId)
DB Connections
Neue DI DBConnectionFactory
Bisher wurden neue FrameworkDataConnections über DBDatasourceBase.CreateConnection() aufgebaut.
using (FrameworkDataConnection dataConnection = DBDatasourceBase.CreateConnection(dbDescConn.CGName))
{
// ...
}
Zukünftig muss hier über den ServiceProvider auf die IDBConnectionFactory zugegriffen werden. Mit CreateConnection(IDBConnectionSourceBase) kann die Connection aufgebaut werden.
using ( FrameworkDataConnection dataConnection = FSServiceProvider.Current.GetRequiredService<IDBConnectionFactory>().CreateConnection(new DBDatasourceBase(groupId, groupName)))
{
// ....
}
Alternativ kann auch direkt über den ConnectionGroupName eine Connection aufgebaut werden.
Zu beachten ist hierbei, dass die Methode nur bei einer in der RuntimeConfig konfigurierten Connection funktioniert.
using ( FrameworkDataConnection dataConnection = FSServiceProvider.Current.GetRequiredService<IDBConnectionFactory>().CreateConnectionFromGroup(dbDescConn.CGName))
{
// ....
}
Warning
Um das Aufräumen der Connection mit einem using muss sich auch weiterhin selbst gekümmert werden.
Neuer DI DBConnectionScope
Außerdem gibt es nun einen IDBConnectionScope, der die Verwaltung von Connections innerhalb eines Scopes übernimmt. Auf diesen kann auch mittels ServiceProvider zugegriffen werden. Mit der GetConnection kann auf die im Scope/Request verwalteten Connections zugegriffen werden.
Caution
Die Connections, die über den IDBConnectionScope geholt werden, dürfen nicht in ein using gesetzt oder anderweitig Disposed werden.
FrameworkDataConnection dataConnection = FSServiceProvider.Current.GetRequiredService<IDBConnectionScope>().GetConnection(new DBDatasourceBase(groupId, groupName));
Alternativ gibt es auch eine Extension-Method, der nur die GruppenId und der Gruppenname übergeben werden muss.
FrameworkDataConnection dataConnection = FSServiceProvider.Current.GetRequiredService<IDBConnectionScope>().GetConnection(groupId, groupName);
Important
Für die gleiche ConnectionGroup wird immer dieselbe Connection zurückgegeben.
Note
Die Connections im IDBConnectionScope werden automatisch nach dem Request aufgeräumt.
Neue DBPerformanceCounter Logik
Jede FrameworkDataConnection, die mit einem Global initialisiert worden ist, hat automatisch über die DBPerformanceCounter Logik Zugriffe und Zeiten für die Connection gemessen und am Global gespeichert.
Durch den Umbau der DB Connections ist dies so nicht mehr möglich, unter anderem, weil die Connections kein Global mehr haben.
Die Verwaltung der neuen IDBPerformanceCounter übernimmt nun der IDBPerformanceCounterStore, der Zugriff erfolgt dabei über die TryGetDBPerformanceCounter.
Connections, die über IDBConnectionScope.GetConnection genutzt werden, kümmern sich selbst darum, dass die IDBPerformanceCounter in den IDBPerformanceCounterStore übertragen werden.
Für Connections, die über die IDBConnectionFactory oder new erzeugt wurden, müssen die DBPerformanceCounter selbst in den IDBPerformanceCounterStore übertragen werden, sofern dies gewünscht ist.
Das muss allerdings noch im using-Block erfolgen, da die Connection sonst bereits disposed ist.
using (var connection = new FrameworkDataConnection(connectionString))
// Oder
using (var connection = FSServiceProvider.Current.GetRequiredService<IDBConnectionFactory>().CreateConnectionFromGroup(connectionGroupName))
{
// Code to execute
connection.Close();
// SessionId mit Global
string sessionId = this.Global.Token;
// SessionId ohne Global
string sessionId = FSServiceProvider.Current.GetRequiredService<ISessionScope>().GetCurrentSessionId();
IDBPerformanceCounter counter = DBPerformanceCounter.FromConnection(connection);
FSServiceProvider.Current.GetRequiredService<IDBPerformanceCounterStore>().AddDBPerformanceCounter(sessionId, counter);
}
Global Context
Mit der Einführung von Dependency-Injection müssen die bisherigen Aufrufe des GlobalContext angepasst werden. Nachfolgend werden die wichtigsten Änderungen und deren Auswirkungen erläutert:
Verwendung von FSServiceProvider.CreateScope()
- Anstelle des bisherigen
GlobalContextwird nunFSServiceProvider.CreateScope(global)verwendet, um einen Scope zu simulieren, der einem Request entspricht. - Der ServiceProvider-Scope sorgt ebenfalls für die Isolierung aller Ressourcen und Zustände vom Code außerhalb des Scopes.
- Beim Beenden des Scopes wird dieser automatisch disposed. Dadurch werden alle temporären Informationen und Ressourcen, die im Scope angelegt wurden, bereinigt.
- Datenbankverbindungen, die innerhalb des Scopes erstellt werden, werden ebenfalls verwaltet und am Ende des Scopes automatisch geschlossen.
Weitere Informationen zum FSServiceProvider.
Alter vs. neuer Code
Alter Code:
using (GlobalContext.Context(localGlobal))
{
// ... eigener Code ...
GlobalContext.Current.ocGlobal...();
// Schließen der Connections - wie am Ende eines Requests.
localGlobal.CloseConnections();
}
Hinweis:
GlobalContext.Context() kann keinen ServiceProvider-Scope automatisch erzeugen, da:
- Heute nicht in allen Fällen ein
CloseConnections()ausgeführt wird. - Asynchroner Code, z.B. mit
Task.Run()oderThread.Start(), nicht korrekt funktioniert.
Neuer Code:
using(FSServiceProvider.CreateScope(localGlobal))
{
// ... eigener Code ...
GlobalContext.Current.ocGlobal...();
// eine Connection verwenden
FSServiceProvider.Current.GetRequiredService<IDBConnectionScope>().GetConnection(...);
}
Die Methode FSServiceProvider.CreateScope(global) erzeugt neben dem Service-Provider-Scope auch einen neuen GlobalContext.
Dadurch wird sichergestellt, dass alle relevanten Ressourcen und Zustände korrekt verwaltet und am Ende des Scopes bereinigt werden.
Warning
Keine Async-Operationen im Scope ausführen!
Der Scope darf keine asynchronen Operationen (z.B. Task.Run(), Thread.Start()) enthalten.
Beim Dispose am Ende des using-Blocks wird der Scope bereinigt, wodurch darüber hinaus laufende Tasks oder Threads auf ungültige Ressourcen zugreifen und Fehler verursachen können.
Ein Refactoring ist erforderlich, um asynchrone Abläufe außerhalb des Scopes zu organisieren.
AUHelper-Änderungen mit notwendiger manueller Nacharbeit
Die folgenden Funktionen sind nicht durch einen allgemeinen Code Replace lösbar und müssen aus diesem Grund manuell nachgearbeitet werden. Wir empfehlen zunächst die Code Replaces durchzuführen und nach einem anschließenden Compile alle manuellen Änderungen vorzunehmen.
AUHelper.PackageInfo
Die Klasse PackageInfo, welche sich zuvor im AUHelper befand, wurde verschoben.
AUHelper IdentifyUnit(string)
Diese Methode wurde umbenannt in SwitchUnit und ist über das injectable Interface IFSAuthentication erreichbar.
AUHelper ChangePassword()
Die beiden Methoden ChangePasswordChecked() wurden entfernt.
Ersetzt wurden die Methoden durch ChangePassword.
FSServiceProvider.Current.GetRequiredService<IFSAuthenticationService>().ChangePassword(username, oldPassword, newPassword);
GlobalObjects AddPackageInfo()/ RegisterPackage()
Die beiden genannten Methoden stehen nicht mehr zur Verfügung. Für die Auskunft über Packages kann GetPackageInfosWithoutSystemPackage() verwendet werden.
AUHelper GetRoleCaption(string roleName) und GetRoleDescription(string roleName)
Gibt es in dieser Form nicht mehr. Die Information kann über GetApplicationRoles() abgerufen werden. Die Liste der Roles werden nicht gecached. Bei jedem Aufruf wird also am AuthenticationService die Informationen neu angefordert. Das Caching muss der User übernehmen.
AUHelper GetAccessUnitInfos()
Hier hat sich das zurückgegebene DTO geändert.
Zukünftig wird statt einem IEnumerable<AccessUnitInfo> ein IEnumerable<AccessUnitDefinition> zurückgegeben.
AUHelper RegisterUnits()
Kann in dieser Form nicht mehr aufgerufen werden.
Registrierung neuer Units:
ModularComponent muss Interface IRegisterUnitsProvider implementieren.
Folgende Methode muss anschließend programmiert werden:
CollectUnits()public virtual IEnumerable<(string Key, string Name)> CollectUnits() { yield return(key, name); // ... }Diese Methode wird dann mittels IFSAuthentication.RefreshUnits automatisch beim Start der Applikation ausgeführt und die Units werden am AuthenticationService registriert.
IFSAuthentication.RefreshUnits()kann ebenfalls über den FSServiceProvider ausgeführt werden und registriert die überCollectUnits()registrierten Units beim AuthenticationService.
AUHelper - Code Replacement Cleanup
Viele der Änderungen können über einfache Replacements korrigiert werden. Wir haben hierfür verschiedene Regular Expressions formuliert, welche bei der Bereinigung der obsolete Warnings helfen können.
Hierfür muss der Maintenance Mode in FS aktiviert und folgende Routine geöffnet werden: Code Replacement Cleanup
Wir empfehlen hier eine schrittweise Vorgehensweise. Zunächst also das komplette Package kompilieren, anschließend eine Warning herauspicken und für diese den Regex ausführen. Anschließend erneuter Compile und erst dann den CheckIn durchführen.
Granted/LicenseGranted/LicenseGrantedInFile
== FS - Code Replace Cleanup ==
CheckIn Comment: Code Replace AUHelper.LicenseGranted(..)
Search Regex: (?<![a-zA-Z0-9_])(?:this\s*\.\s*)?Global\s*\.\s*AUHelper\s*\.LicenseGranted\s*\(
Replace Text: FSServiceProvider.Current.GetRequiredService<IRuntimeLicensePermissionEvaluator>().IsGranted(
== FS - Code Replace Cleanup ==
CheckIn Comment: Code Replace AUHelper.LicenseGrantedInFile()
Search Regex: (?<![a-zA-Z0-9_])(?:this\s*\.\s*)?Global\s*\.\s*AUHelper\s*\.LicenseGrantedInFile\s*\(\s*
Replace Text: FSServiceProvider.Current.GetRequiredService<IRuntimeLicenseVerifier>().IsGranted(
== FS - Code Replace Cleanup ==
CheckIn Comment: Code Replace AUHelper.Granted(...)
Search Regex: (?<![a-zA-Z0-9_])(?:this\s*\.\s*)?Global\s*\.\s*AUHelper\s*\.Granted\s*\(\s*
Replace Text: FSServiceProvider.Current.GetRequiredService<IFSAuthorization>().IsGranted(
AUHelper.Username
== FS - Code Replace Cleanup ==
CheckIn Comment: Code Replace AUHelper.Username
Search Regex: (?<![a-zA-Z0-9_])(?:this\s*\.\s*)?Global\s*\.\s*AUHelper\s*\.Username
Replace Text: FSServiceProvider.Current.GetRequiredService<IFSAuthenticationScope>().UserTokenInfo.Username
AUHelper.AuthenticationEnabled
== FS - Code Replace Cleanup ==
CheckIn Comment: Code Replace AUHelper.AuthenticationEnabled
Search Regex: (?<![a-zA-Z0-9_])(?:this\s*\.\s*)?Global\s*\.\s*AUHelper\s*\.AuthenticationEnabled
Replace Text: FSServiceProvider.Current.GetRequiredService<IFSAuthenticationService>().IsAuthenticationEnabled
AUHelper.AuthenticationEnabledInConfig
== FS - Code Replace Cleanup ==
CheckIn Comment: Code Replace AUHelper.AuthenticationEnabledInConfig
Search Regex: (?<![a-zA-Z0-9_])(?:this\s*\.\s*)?Global\s*\.\s*AUHelper\s*\.AuthenticationEnabledInConfig
Replace Text: FSServiceProvider.Current.GetRequiredService<IRuntimeConfigProvider>().IsAuthenticationEnabled
AUHelper.GetUserRoles
== FS - Code Replace Cleanup ==
CheckIn Comment: Code Replace AUHelper.GetUserRoles()
Search Regex: (?<![a-zA-Z0-9_])(?:this\s*\.\s*)?Global\s*\.\s*AUHelper\s*\.GetUserRoles\s*\(
Replace Text: FSServiceProvider.Current.GetRequiredService<IFSAuthenticationService>().GetUserRoles(FSServiceProvider.Current.GetRequiredService<IFSAuthenticationScope>().UserTokenBase64
AUHelper.GetApplicationRoles
== FS - Code Replace Cleanup ==
CheckIn Comment: Code Replace AUHelper.GetApplicationRoles()
Search Regex: (?<![a-zA-Z0-9_])(?:this\s*\.\s*)?Global\s*\.\s*AUHelper\s*\.GetApplicationRoles\s*\([^)]*\)
Replace Text: FSServiceProvider.Current.GetRequiredService<IFSAuthenticationService>().GetApplicationRoles()
AUHelper.IsDefaultUser
== FS - Code Replace Cleanup ==
CheckIn Comment: Code Replace AUHelper.IsDefaultUser
Search Regex: (?<![a-zA-Z0-9_])(?:this\s*\.\s*)?Global\s*\.\s*AUHelper\s*\.IsDefaultUser
Replace Text: FSServiceProvider.Current.GetRequiredService<IFSAuthenticationScope>().UserTokenInfo.IsDefaultUser
AUHelper.GetUserUnits
== FS - Code Replace Cleanup ==
CheckIn Comment: Code Replace AUHelper.GetUserUnits()
Search Regex: (?<![a-zA-Z0-9_])(?:this\s*\.\s*)?Global\s*\.\s*AUHelper\s*\.GetUserUnits\s*\(
Replace Text: FSServiceProvider.Current.GetRequiredService<IFSAuthenticationService>().GetUserUnits(FSServiceProvider.Current.GetRequiredService<IFSAuthenticationScope>().UserTokenBase64
AUHelper.IdentifyUnit
== FS - Code Replace Cleanup ==
CheckIn Comment: Code Replace AUHelper.IdentifyUnit()
Search Regex: (?<![a-zA-Z0-9_])(?:this\s*\.\s*)?Global\s*\.\s*AUHelper\s*\.IdentifyUnit\s*\(
Replace Text: FSServiceProvider.Current.GetRequiredService<IFSAuthentication>().SwitchUnit(
AUHelper.GetRuntimeLicense
== FS - Code Replace Cleanup ==
CheckIn Comment: Code Replace AUHelper.GetLicense()
Search Regex: (?<![a-zA-Z0-9_])(?:this\s*\.\s*)?Global\s*\.\s*AUHelper\s*\.GetLicense\s*\(
Replace Text: FSServiceProvider.Current.GetRequiredService<IRuntimeLicenseVerifier>().GetRuntimeLicense(
AUHelper.GetPackages
== FS - Code Replace Cleanup ==
CheckIn Comment: Code Replace AUHelper.GetPackages()
Search Regex: (?<![a-zA-Z0-9_])(?:this\s*\.\s*)?Global\s*\.\s*AUHelper\s*\.GetPackages\s*\(\)
Replace Text: FSServiceProvider.Current.GetRequiredService<IPackageInfoResolver>().GetPackageInfosWithoutSystemPackage().ToList()