Remove Global Cleanup
Mit Framework Studio 4.7 möchten wir uns von der Konstruktor-Injection des Globals lösen.
Der Umbau sieht vor, dass das Global nicht mehr im Konstruktor übergeben wird, sondern über ein sogenanntes Ambient Pattern/AsyncLocal, in FS GlobalContext, genannt, bereitgestellt wird.
Dieser GlobalContext ermöglicht weiterhin den Zugriff auf this.Global.
Beim Einstieg in einen Request setzt Framework Studio das Global und räumt dieses nach einem Request wieder ab.
Vorteil des Umbaus ist, dass das Global von den Instanzen gelöst wird.
Remove Global Cleanup - Maintenance Routine
Im Login-Dialog muss der Maintenance-Mode aktiviert werden, damit sie verfügbar ist. Die Routine kann in der IDE über das Menü Update / FS 4.7 / (3) Remove Global Cleanup aufgerufen werden.
Änderungen:
In den Konstruktoren und Factory.Create()-Methoden vom Components wird kein Global mehr übergeben.
- cdCustomer oCustomer = new cdCustomer(this.Global); + cdCustomer oCustomer = new cdCustomer(); - IcdArticle oArticle = cdArticleFactory.Create(this.Global); + IcdArticle oArticle = cdArticleFactory.Create();StaticCache.Get()-Methode wird kein Global mehr übergeben.
- return cdArticleGroupCache.Get(this.Global).FillObject(This,sArticleGroupIDP); + return cdArticleGroupCache.Get().FillObject(This,sArticleGroupIDP);Von Framework Studio erzeugte Service-Proxies erwarten im Konstruktor kein Global mehr
- MISRemoteServiceProxy proxy = new MISRemoteServiceProxy(this.Global, "*", sUrlP); + MISRemoteServiceProxy proxy = new MISRemoteServiceProxy("*", sUrlP);TextCollFactory-Methoden erwarten kein Global mehr
- FSstring sLogLevel = ctLogLevelFactory.GetText(this.Global, this.shtLevel); + FSstring sLogLevel = ctLogLevelFactory.GetText(this.shtLevel);Die Methoden
GetMLText(),FormatMLText(),GetMLKeyText()undFormatMLKeyText()sind vom Global in die KlasseMLUtilgewandert:- throw new FrameworkApplicationException(this.Global.GetMLText(MLKeys.MSG_93a1ea2873804fcbb771d2b2a5305f85), 0); + throw new FrameworkApplicationException(MLUtil.GetMLText(MLKeys.MSG_93a1ea2873804fcbb771d2b2a5305f85), 0); - this.Global.FormatMLKeyText(MLKeys.MSG_ddb19cf4d65b4b98a178855bdbcb9112, oOfflineScannerParameterP.sIP, sTunnelString) + MLUtil.FormatMLKeyText(MLKeys.MSG_ddb19cf4d65b4b98a178855bdbcb9112, oOfflineScannerParameterP.sIP, sTunnelString)
Manuelle Nacharbeiten
In 99% der Anwendungsfälle deckt der neu eingführte GlobalContext, die bisherige Verwendung von this.Global ab.
Das bedeutet, dass nach Durchführung der Maintenance-Routine das Global aus den Konstruktoren entfernt wurde und keine weiteren Änderungen vorgenommen werden müssen.
Zudem kann this.Global weiterhin verwendet werden, um auf das aktuelle Global zuzugreifen.
In 1% der Fälle, in denen ein eigenes Global erstellt und in Methoden übergeben wird, reicht der von FS autom. bereitgestellte Context nicht aus.
Diese Stellen werden zwar von der Maintenace-Routine erkannt, aber können nicht generisch korrigiert werden.
Aus diesem Grund wird der ursprüngliche Code auskommentiert und mit dem Zusatz WARNINGGLOBAL markiert.
Warning
Der Code ist nach der Maintenance-Routine compilefähig, aber ggf. in seiner Funktion nicht mehr korrekt. Diese Codestellen müssen nachgearbeitet werden.
Beispiel für einen auskommentierten Code, welcher nachgearbeitet werden muss:
return new tcdCall_to_dcCall2(/*global WARNINGGLOBAL*/).Transform(oCall);
Wird einer Instanz ein eigenes Global übergeben, hat diese Instanz dieses Global gespeichert und arbeitet auch mit diesem. Mit dem Entfernen der Globals aus dem Konstruktor ist dieses Verhalten nicht mehr gegeben. Vor allem im Service-Bereich wird öfter mit eigenen Globals gearbeitet. (Anwendungsfälle sind meist sehr speziell, z.B.: Wenn mit fremden Mandanten im InterCompany-Bereich gearbeitet wird.) Hier muss nun explizit ein GlobalContext aufgespannt werden, welcher das eigens erstellte Global beinhaltet. Innerhalb des Contexts greifen dann neue Instanzen auf dieses erstellte Global zu.
Alter Dummy-Code mit auskommentiertem Global und WARNINGGLOBAL:
public virtual dcCall2 GetCallById(string token, long callId)
{
// Instanz eines selbst erstellten Globals
IFSGlobalObjects global = AppAccessHost.CreateGlobal(token);
try
{
// eigenes Global wird übergeben
IcAppAccessHelper oHelper = cAppAccessHelperFactory.Create(/*global WARNINGGLOBAL*/);
//oHelper-Instanz arbeitet mit diesem übergebenen Global
var oCall = oHelper.GetCallById(callId);
// weiterer Code
return new tcdCall_to_dcCall2(/*global WARNINGGLOBAL*/).Transform(oCall);
}
finally {
// ...
}
}
Korrekt umgebauter Dummy-Code unter Verwendung des GlobalContexts:
public virtual dcCall2 GetCallById(string token, long callId)
{
// Instanz eines selbst erstellten Globals
IFSGlobalObjects global = (IFSGlobalObjects)FS.Hosting.Broker.Base.GlobalObjectManager.CreateGlobalObject(guid.NewGuid().Value);
// Der GlobalContext übernimmt nun das Setzen des Globals am Anfang des Requests und räumt das Global am Ende des Requests wieder ab.
// Im Fall, dass ich ein Global erstelle, muss ich mich um diesen GlobalContext kümmern.
// Sobald der GlobalContext mit dem using() aufgespannt wurde, können die auskommentierten Globals entfernt werden.
using(GlobalContext.Context(global))
{
try
{
IcAppAccessHelper oHelper = cAppAccessHelperFactory.Create();
// oHelper-Instanz arbeitet nun mit dem Global aus dem GlobalContext
var oCall = oHelper.GetCallById(callId);
// ... weiterer Code
return new tcdCall_to_dcCall2().Transform(oCall);
}
finally
{
// ...
}
}
}
Servicemethoden mit GlobalObject
Bei Servicemethoden mit der aktiven Option "Generate GlobalObject", kümmert sich FS um das Aufspannen des GlobalContexts. Es kann weiterhin mit this.Global auf das Global zugegriffen werden.
Der generierte Code sieht dann wie folgt aus:
public virtual void Operation()
{
try
{
IFSGlobalObjects Global = (IFSGlobalObjects)FS.Hosting.Broker.Base.GlobalObjectManager.CreateGlobalObject(guid.NewGuid().Value);
try
{
using(GlobalContext.Context(Global))
{
// geschriebener Code vom User
}
}
finally {
((GlobalObjects)Global).Dispose();
}
}
catch(Exception ex)
{
// ... Exception-Handling
}
}