Table of Contents

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:

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:

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().

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 GlobalContext wird nun FSServiceProvider.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() oder Thread.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:

  1. ModularComponent muss Interface IRegisterUnitsProvider implementieren.

  2. 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 über CollectUnits() 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()