Loading...
Loading...
Loading...
Loading...
Loading...
This document helps to understand the SolutionGeneration task, starting from the ProjectGeneration to the WFNetConversionTool call and settings to execute the Generation service.
First thing to understand is what ProjectGeneration does. ProjectGeneration contains the services and templates that generate the output files for a project, using some configurations. In this file, we will be focusing on the SolutionGeneration for Blazor.
We need to start talking about the Interfaces of the Abstractions. This interfaces define the properties and methods needed to execute the generation of the project.
We can find the interfaces in the Mobilize.ProjectGeneration.Abstraction project under the SolutionGenerator repository.
Go to IProjectGenerator, we can find some properties. You can see this properties as parameters that the Generate task needs.
Pay special attention to this method definition, because this will be called inside the Task to call the generator you need.
Now that we see this Interface, we need to understand… Why is this needed? Well, let’s jump on it.
Under the same project, we have a class called ProjectGeneratorBase.
This class implements the IProjectGenerator interface. If you navigate a bit in this class, you will find the GenerateProject implementation.
Now, we need to use the template used by the respective GenerateProject. In this case, we should go into the SolutionTemplate.tt.
Avoid touching the SolutionTemplate.cs
You can modify the template as we did here.
And save the file. When you save, a popup is showing. Press Yes. This is to synchronize the tt with the cs(the cs is generated dynamically by handlebars).
To test the changes we can pack the projects locally, or, upload the changes to a branch and wait for the build to generate an alpha version.
To pack locally just run the next command under the build folder. Change the path for every csproj
Once you pack the changes, you need to install the package in the WFNetConversionTool. If you pack it locally, add the build folder to the package sources in the Nuget Package Manager of Visual Studio.
Or install the alpha version if you commit the changes to a branch.
Now, is necessary to add the BlazorSolutionGenerationTask and the BlazorSolutionGenerator
Here we have our BlazorSolutionGenerator, that will set the params and config to call the GenerateProject from the ProjectGeneration.
Here in the Run method, we need to pass the config to the SolutionGenerationParams
And now, we can call the BlazorSolutionGenerator from the Task.
Implementation of the ImageList control
Working on the implementation of the ImageList, we encounter the issue of management of resources through the ImageListStreamer. The ImageListStreamer is basically a serializer of all the images that are stored inside the ImageList component.
This ImageListStreamer is initialized by the .resx file associated with the .designer in which the ImageList is created. The .rex is the one that created the instance of the ImageListStreamer, serialized in base64 all the images and associated the data with a specific public token and the System.Windows.Forms.ImageListStreamer class.
This makes it very difficult to use this data to serialize out of the implementation of the WindowsForm component because it always needs the .dlls hashes to validate the information. This means that any custom implementation of the ImageListStreamer out of WindowsForm will fail when it tries to convert or deserialize the information storage in the .resx file.
As a way to interpret this mechanism through the resource, we manage to find a work-around that uses the same resource to get every image and storage in the ImageList array. Basically, the ImageList creates an instance of the ImageListStreamer and inside this, creates a method that takes the resource as a parameter and, having stored the keys for every image, gets the object Image associated with the key.
The problem with this solution is that it needs changes in the migrated code like the call of this method that fills the ImageCollection or adds every Image manually to the .resx file and associates that image name to the key so the resource can find it when it is searched.
A possible way to manage this issue is to create a pre-serializer that changes the values that reference the System.Windows.Form.ImageListStreamer for our implementation. That might let us create an instance that references the ImageStream in the .resx file and serialize normal inside the Blazor.ImageListStreamer.
Another solution is to somehow decode the images inside the ImageStream and storage separate in the .resx files of the migration project. This will help us with the work-around to minimize the manual changes in case the first solution does not work.
Technical explanation of service implementation.
One important challenge in the upgrade from Desktop to Web application is the handle of the static variables, in this scenario, the Desktop application only have one scope, if you execute multiple instances of the same application each one will have its own context, in Web application there are different scenarios.
For example, in Blazor WebAssembly deploy it will create one scope per tab or SignalR connection, so it won’t require any change given is a similar execution mode.
In Blazor Server we only have a server-client architecture so, It'll be the same server handling different connections, the scope of the static variables are global, so if you as a user open different tabs, they’ll share the same static variables and values, not per session/tab as expected. As we can see in the following diagram the life cycle of a static variable will persist during the whole server execution.
After some research of different approaches a StaticService is implemented, this service is global and will store the value of the static variable per session, handling by itself the session and the storage. It will give us the following methods:
Get which will return the value stored in the service for this static variable.
Set will set the value into the service for this static variable.
InitializeData, when we have a static property auto-implemented in the source code with a default value, we need to keep the value, but C# doesn’t allow non-auto-implemented properties to be initialized, so this method will set the corresponding value to the property in the first call on Get.
This service will involve changes on the conversion of the static variables that we’ll review in the following section.
Conversion Changes
This feature implies that we need changes in the converted code, principally on the getters and setters, each one will require to call the methods. One important decision we made during the design of this feature is to keep the code visible instead of using some attribute in order to increase the visibility and the maintainability of the converted code. Here we have a basic example with no initialization:
Winforms:
Blazor:
There is another example when an initialization is set on the static variable.
Winforms:
Blazor:
Service Setup
The StaticService requires some configuration in the program of our new application, is necessary to add the Session, DistribuitedMemoryCache service, HttpContextAccessor and initialize the services as we can see in the following code:
These services are required to be provided to the StaticServices to handle the different sessions and identify which one is the current session that is being consulted.