Post-Conversion

WebMAP: WinForms to Web is a tool designed to convert .NET Framework apps based on C# and Windows Forms to a modern web architecture. Post-Conversion documentation for WebMAP WinForms.

With a brand new WebMAP: WinForms to Web converted application, let's dive into the necessary steps to have your application up and running.

Compile and Run the Converted Application

Compiling and running your converted application is the highlight of the whole process, but before we get right into it, you need to make sure your computer compiles the following requirements.

System Requirements

Before you start running the converted you will need:

  • Node.js LTS

  • Visual Studio 2019, Build Tools for Visual Studio 2019.

    Your installation of Visual Studio, must include the following settings:

    • Go to Workloads tab and turn on ASP.Net and web development, see the following example:

Output Folder Contents

The output folder for the converted app should be similar to the HelloWorld one from the following image:

HelloWord demo folder content

Using HelloWorld as an example, the structure of the folder consists of:

  • helloworld folder: the main project folder.

    • helloworld-angular folder: contains all the code related to the UI, for more information visit Workspace and project file structure.

      • src folder.

        • app folder.

          • components folder: contains all the angular components generated from the converted UI.

          • app.component.css: it is the stylesheet used by the app.component

          • app.component.html: it is the HTML associated with the app.component

          • app.component.ts: this is the startup angular component of every migrated application

          • app.module.ts: this module includes all the sub-modules of all the application. e.g. You have two project (csproj): project1.csproj and project2.csproj. This will yield two sub-modules: project1.module.ts and project2.module.ts

          • hello-world.module.ts: this module includes all the components of the hello-world csproj.

        • assets folder: contains all the images of the application.

        • index.html: it is the page that launches the angular startup component: app.component <app-root></app-root>

        • package.json: this is a json file that contains the list of javascript packages required by the application.

  • Logs folder: you'll find the logs related to the conversion and any error that might have occurred, for more information visit Error and Troubleshooting.

  • Stubs folder: any element that was not converted will be generated as a stub, for more information of what a stub is visit Error and Troubleshooting.

  • HelloWorld.sln: la solución del proyecto.

  • nuget.config: contains the package sources for the app.

  • PageObject.xml: file with the summary of all the elements contained by each of the forms of the application.

Converted Application Request and Response

Using the HelloWorld example application, let's follow the process that occurs when you click the button Click Me to understand the communication between the FrontEnd and BackEnd of your converted application.

HelloWorld Click Me

The Click of the button will trigger a sequence of steps that can be summarized in the following

Now, let go into detail for each step.

Step 1: Send the Request

When you click the Click Me button to start the whole process it will:

  1. Call WebMAPService to perform the following actions:

    1. Gather the Changed models to be synchronized with the back-end's observable models.

    2. Create and send the HTTP POST event with the gathered deltas.

  2. The WebMAPService creates the following JSON and send it to the back-end.

{
"Changed" : [],
"Actions" : [{
"Arguments" : null,
"Name" : "Click",
"ReceiverId" : "a1c4236c-ca1e-4550-9789-2c17d12e6f50"
}]
}

Changed array: since there are no models that were modified, the array is empty. Actions array: contains the list of actions that should be processed on the back-end. ReceiverId is the identification of the component interacted with.

Step 2: Process the Request and Create Response

The BackEnd will:

  1. Receives the JSON request: decodes this information and creates a request object call Data Transfer Object (DTO), this will contain the list Changed of the click.

  2. Process the DTO request: applies the changes defined in the DTO and keeps track of the component state. In this case, it will change the state of the label with the new text.

  3. Create the response: creates a response based on the changes made to the components, transforms it into a JSON, that can be interpreted by the Front-End, and sends the response to the UI.

For a more in-depth explanation of all the components of the Back-End, take a look into the section:

Step 3: Process the Response

Now, that the response has arrived back to the client-side.

{
"Added" : [],
"Changed" : [
{
"Text" : "Hello World",
"Visible" : true,
"Id" : "a2fb1871-2576-4dbe-9203-17dc4b13427a",
"MapperId" : "lbl",
"References" : {}
}
],
"Removed" : [],
"Actions" : []
}

The WebMAPService will process the JSON above, and synchronize the Front-Fnd's observable models with each of the models in theChangedarray. And finally, the components should be refreshed and show the new Hello World text.

Compile and Run the Converted Application

Compiling and running your converted application is the highlight of the whole process, but before we get right into it, you need to make sure your computer complies with the following requirements.

System Requirements

Before you start running the converted you will need:

Step 1: How to Build the UI Code

Install the Angular, if you haven't already. Please open Command Line or PowerShell as an administrator and type the following command.

npm install -g @angular/[email protected]

Update the npm configuration's registry key. This is required to resolve Mobilize's packages and should be done only once.

npm config set "@mobilize:registry" "https://packages.mobilize.net/npm/mobilizenet-npm/"
npm config set registry https://registry.npmjs.org/

Go to the converted application's folder, then go to the folder of the main project and then to the folder with angular in the name. Open the CommandLine pointing to this location. It should look as in the case of HelloWorldDemo.

C:\HelloWorld\helloworld\helloworld-angular>_

Run the following command to install all the packages and their dependencies to run the UI. This step should only be done once.

npm install

The last command will build the UI and should be run after any change done to a component inside the angular folder.

npm run build

Now the user interface is ready!

Angular Project

As an additional option for building the UI you have the Angular Project, this is an option selected before the conversion under Conversion Settings > Conversion Results. This feature is exclusive for projects whose the Target Framework is .NET Core.

This generates an new csproj file located in the angular folder of the converted solution. This new adds the option of building the UI directly in Visual Studio without the need to writing the previous commands in console.

To build the UI directly from the Visual Studio solution right click the new angular project and select Publish.

Then click the Publish button and set up the bin folder.

This will execute the compilation steps found in the project's definition.

After the Web Publish is successful the UI will be ready!

The user can verify the build was successful manually as well by checking the wwwroot folder at the base directory or the node modules inside the angular folder.

Step 2: How to Build the Business Logic Code

Open the solution of the app using Visual Studio 2019. Compile your solution and resolve any errors if necessary.

Before compiling the code, please make sure you have installed .NET Core and the necessary .NET Framework versions in order to compile the code.

Step 3: How to Run the Application

For the final stride open the solution in Visual Studio, right click the main project select Set as Startup Project and click Run button in visual studio.

Set Startup Project

Your first WebMAP: WinForms to Web application is up and running!

Converted HelloWord running

Extend or Modify the Converted Application

For this section, we'll be following a series of common changes that developers may have to do on a daily basis. All the changes will be explained using the converted HelloWorld demo.

Add Component

Say you have to work on the following user story:

As a user, when I navigate the startup page of HelloWorld, I want to be able to click on a Button with the text 'Spanish', and the Label's text should be changed to 'Hola Mundo'. Finally, the screen should look like the following image:

HelloWorld with translate button

Business Logic

In order to complete this user story, you have to add some lines of code on the business logic and UI. Let's start with the logic first.

Open the Form1.Designer.cs file. and declare the new button.

[Intercepted]
private Mobilize.Web.Button button2 { get; set; }

Note: Remember to add the Intercepted attribute in order to let the properties state persist over a request. Only the properties with the Intercepted attribute will be sent to the user interface.

In the same file, instantiate the button within the method InitializeComponents.

//
// button2
//
this.button2 = new Mobilize.Web.Button();
this.button2.Font = new Mobilize.Web.Font("Arial", 14.25F, Mobilize.Web.FontStyle.Regular, Mobilize.Web.GraphicsUnit.Point, ((byte)(0)));
this.button2.Location = new System.Drawing.Point(134, 78);
this.button2.Name = "button2";
this.button2.Text = "Spanish";

Next to the last lines, subscribe click event to the button2.

this.button2.Click += new System.EventHandler(this.button2_Click);

Then, open the Form1.cs file and add a method called button2_Click and any functionality you want to do when someone clicks the button2. Following our user story, it will change the text of the label to Spanish.

private void button2_Click(object sender, System.EventArgs e)
{
this.label1.Text = "Hola Mundo";
}

Now the remaining changes are related to the UI components.

User interface

Enter the helloworld-angular\src\app\components\hello-world\form1 folder, open the file form1.component.html and add the wm-button tag of the button2.

<div *ngIf="model">
<wm-window [model]="model" id="Form1" class="HelloWorld_Form1">
<ng-template let-model>
<div class="Form1">
<wm-label id="label1" [model]="model.label1" class="label1" tabindex="2">My First Sample</wm-label>
<wm-button id="button1" [model]="model.button1" class="button1" tabindex="1"></wm-button>
<wm-button id="button2" class="button2" [model]="model.button2"></wm-button>
</div>
</ng-template>
</wm-window>
</div>

In the same folder, open the file form1.component.css and add the styles you wish for the class or id set as button2 previously.

.HelloWorld_Form1 .button2 {
font-family: "Arial";
font-size: 17.1px;
left: 134px;
top: 78px;
position: absolute;
width: 116px;
height: 50px;
padding: 0px 0px 0px 0px;
display: table-cell;
vertical-align: middle;
display: table-cell;
}

The petition was completed and now we have our modified WebMAP: WinForms to Web app!

HelloWorld with new button

Switch CSS Themes

Say you have to work and the following happens:

Your boss comes along and asks you to change ALL the user interface to look for a more general look instead of the default red.

Thanks to the technologies implemented for WebMAP: WinForms to Web UI this change can be made with the following phew steps:

Currently, Kendo provides the following themes by default:

For this example, we'll pick the Kendo UI Bootstrap theme.

Go to the helloworld-angular folder, Open a Command Prompt from that folder and run the command:

npm install --save @progress/kendo-theme-bootstrap

In the same folder helloworld-angular, open the file angular.json and you'll see this:

{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"angular": {
...
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
...
"styles": [
"src/styles.css",
"node_modules/@progress/kendo-theme-default/dist/all.css",
"node_modules/@angular/material/prebuilt-themes/purple-green.css",
"node_modules/@mobilize/winforms-components/styles/jQueryStyles.css",
"node_modules/@mobilize/winforms-components/styles/styles.css",
"node_modules/material-icons/iconfont/material-icons.css"
],
...
}

Remove the line.

"node_modules/@progress/kendo-theme-default/dist/all.css",

And add a new reference in the same place.

"node_modules/@progress/kendo-theme-bootstrap/dist/all.css",

Finally, run in the opened CommandProm the command:

npm run build

Run the application, and now you have a happy boss with the minimum work and changes done!

HelloWorld with bootstrap them

Add New Window

Say you have to work on the following user story:

As a user, when I navigate to the startup page of HelloWorld, I want to be able to click on a button with the text 'Login', after I click the button, a Login screen should be shown.

Business Logic

The procedure to create the new button Login, to display the new window, is exactly the same as in the section "Add Component", the only thing that differs is the business logic of the button'sclick event.

Open Form1.Designer.cs file and subscribe the click event to the button3.

this.button3.Click += new System.EventHandler(this.button3_Click);

Then, open the Form1.cs file, add a method called button3_Click as the following:

private void button3_Click(object sender, System.EventArgs e)
{
var loginForm = new Form2();
loginForm.ShowDialog();
}

Create a class call Form2.cs, add theObservableattribute, to the class in order to keep the class within the WebMAP's life cycle, and make the class extend from Mobilize.Web.Form in order to inherit all the necessary properties and functionalities that are needed in any new screen.

using Mobilize.WebMap.Common.Attributes;
namespace HelloWorld
{
[Observable]
public partial class Form2 : Mobilize.Web.Form
{
public Form2()
{
this.InitializeComponent();
}
}
}

Then, create the partial class Form2.Designer.cs.

using Mobilize.WebMap.Common.Attributes;
namespace HelloWorld
{
public partial class Form2
{
[Mobilize.WebMap.Common.Attributes.Designer]
private void InitializeComponent()
{
this.textBox1 = new Mobilize.Web.TextBox();
this.textBox2 = new Mobilize.Web.TextBox();
this.label1 = new Mobilize.Web.Label();
this.label2 = new Mobilize.Web.Label();
this.label3 = new Mobilize.Web.Label();
this.button1 = new Mobilize.Web.Button();
this.panel1 = new Mobilize.Web.Panel();
this.textBox1.Name = "textBox1";
this.textBox2.Name = "textBox2";
this.label1.Name = "label1";
this.label1.Text = "Username";
this.label2.Name = "label2";
this.label2.Text = "Password";
this.label3.Name = "label3";
this.label3.Text = "Login";
this.button1.Name = "button1";
this.button1.Text = "Login";
this.panel1.Controls.Add(this.textBox2);
this.panel1.Controls.Add(this.label2);
this.panel1.Controls.Add(this.textBox1);
this.panel1.Controls.Add(this.button1);
this.panel1.Controls.Add(this.label1);
this.panel1.Name = "panel1";
this.Controls.Add(this.label3);
this.Controls.Add(this.panel1);
this.Name = "HelloWorld.Form2";
this.Text = "Form2";
}
[Intercepted]
private Mobilize.Web.TextBox textBox1 { get; set; }
[Intercepted]
private Mobilize.Web.TextBox textBox2 { get; set; }
[Intercepted]
private Mobilize.Web.Label label1 { get; set; }
[Intercepted]
private Mobilize.Web.Label label2 { get; set; }
[Intercepted]
private Mobilize.Web.Label label3 { get; set; }
[Intercepted]
private Mobilize.Web.Button button1 { get; set; }
[Intercepted]
private Mobilize.Web.Panel panel1 { get; set; }
}
}

User interface

Create a new folder for Form2 that will contain the typescript angular component, the HTML and the CSS for the form. The folder structure would be as below:

  • helloworld-angular

    • src

      • app

        • components

          • hello-world

            • Form1

              • ...

            • Form2

              • ...

In the Form2 folder, create the angular typescript component form2.component.ts:

import { Component, ChangeDetectorRef, ElementRef, Output, Renderer2, ViewEncapsulation} from "@angular/core";
import { EventData, dataTransfer} from "@mobilize/base-components";
import { FormComponent} from "@mobilize/winforms-components";
import { WebMapService} from "@mobilize/angularclient";
@Component({
selector: "hello-world-form2",
styleUrls: ["./form2.component.css"],
templateUrl: "./form2.component.html",
encapsulation: ViewEncapsulation.None
})
@dataTransfer(["frmHelloWorldForm2"])
export class Form2Component extends FormComponent {
protected webServices : WebMapService;
constructor (wmservice : WebMapService, changeDetector : ChangeDetectorRef, render2 : Renderer2, elem : ElementRef) {
super(wmservice, changeDetector, render2, elem);
}
}

Every newly created component should import the following modules:

  1. FormComponent: import this if you want to inherit the properties and functionalities of any screen.

  2. WebMAPService: import this for delta synchronization and sending requests to WebMAP Back-end.

  3. dataTransfer: import this for registering the types of components that are going to be displayed dynamically.

In the same folder, create the HTML file form2.component.html, this will helps us to bind with the new components of the Form2.cs class. The standard to create any new HTML screen is:

<div *ngIf="model">
<wm-window [model]="model" id="..." class="...">
<ng-template let-model>
...
</ng-template>
</wm-window>
</div>

wm-window is a Mobilize Front-end generic component for supporting the System.Widows.Forms.

Now, add the HTML tags wm-label, wm-button, wm-textbox, wm-panel.

<div *ngIf="model">
<wm-window [model]="model" id="Form2" class="HelloWorld_Form2">
<ng-template let-model>
<div class="Form2">
<wm-label id="label3" tabindex="11" class="label3" [model]="model.label3">Login</wm-label>
<wm-panel id="panel1" class="panel1" [model]="model.panel1">
<wm-label id="label1" tabindex="5" class="label1" [model]="model.label1">Username</wm-label>
<wm-textbox id="textBox1" tabindex="3" class="textBox1" [model]="model.textBox1"></wm-textbox>
<wm-label id="label2" tabindex="8" class="label2" [model]="model.label2">Password</wm-label>
<wm-textbox id="textBox2" tabindex="5" class="textBox2" [model]="model.textBox2"></wm-textbox>
<wm-button id="button1" tabindex="2" class="button1" [model]="model.button1"></wm-button>
</wm-panel>
</div>
</ng-template>
</wm-window>
</div>

NOTE: You can add any other HTML tags here to extend functionalities as you want. Remember you will need to bind them with some angular component.

In the Form2 folder, create a HTML file form2.component.css.

.HelloWorld_Form2 {
left: -1px;
top: -1px;
}
.HelloWorld_Form2 .Form2 {
width: 330px;
height: 178px;
overflow: hidden;
}
.HelloWorld_Form2 .label3 {
white-space: nowrap;
overflow: hidden;
font-family: "Arial";
font-size: 26.1px;
font-weight: bold;
left: 125px;
top: 9px;
position: absolute;
width: auto;
height: auto;
}
.HelloWorld_Form2 .panel1 {
background-color: rgba(153, 180, 209, 1);
left: 12px;
top: 51px;
position: absolute;
width: 306px;
height: 115px;
overflow: hidden;
}
.HelloWorld_Form2 .label1 {
white-space: nowrap;
overflow: hidden;
font-family: "Microsoft Sans Serif";
font-size: 14.4px;
left: 7px;
top: 14px;
position: absolute;
width: auto;
height: auto;
}
.HelloWorld_Form2 .textBox1 {
left: 109px;
top: 16px;
position: absolute;
width: 177px;
height: 20px;
}
.HelloWorld_Form2 .label2 {
white-space: nowrap;
overflow: hidden;
font-family: "Microsoft Sans Serif";
font-size: 14.4px;
left: 7px;
top: 45px;
position: absolute;
width: auto;
height: auto;
}
.HelloWorld_Form2 .textBox2 {
left: 109px;
top: 47px;
position: absolute;
width: 177px;
height: 20px;
}
.HelloWorld_Form2 .button1 {
color: black;
font-family: "Microsoft Sans Serif";
font-size: 17.1px;
left: 11px;
top: 73px;
position: absolute;
width: 275px;
height: 36px;
padding: 0px 0px 0px 0px;
display: table-cell;
vertical-align: middle;
display: table-cell;
}
.HelloWorld_Form2 .button1:active {
background-color:DarkBlue;
color:white;
}

To export the new component, please modify the following files:

  1. index.ts

  2. hello-world.module.ts

Also in the helloworld-angular folder, find the index.ts file and add:

//...
import { Form2Component } from './form2/form2.component';
//...
export { Form2Component };

For the hello-world.module.ts file add:

//...
@NgModule({
imports: [
//...
],
exports: [
//...
HelloWorld.Form2Component,
],
declarations: [
//...
HelloWorld.Form2Component
],
bootstrap: [
//...
HelloWorld.Form2Component,
],
providers: [WebMapService],
schemas: [ CUSTOM_ELEMENTS_SCHEMA ]
})
export class HelloWorldModule { }

Build both, the business logic and UI code, and run the application.

HelloWorld with Login
Login window

Stub Filter

This is a customization file that allows the user to exclude certain assemblies, classes and namespaces from the mapping process, which involves taking a given class and transforming it into a custom class that implements its logic in the new converted environment, and avoid transforming those references and creating stubs if they were unsupported.

The reason behind this feature is so the user has control over what is being mapped, if there's a namespace you don't want converted, or a third party library that does not require transformation because it's gonna stay the same, the user can modify this file and specified which ones wants to filter out. Keep in mind unsupported libraries will generate stubs, an empty class that helps the converted solution compile without errors, meaning if the user wants to add a new filter those stub classes wont be generated and the reference to that filter will stay the same as it was in the source code.

To actually allow the user to customize the stub filter the following tags can be added to the XML file to generate the desired outcome of the conversion.

  • Assembly, Class and Namespace: These are the main tags the user will be working with and they specified a given namespace, class or assembly to exclude from the mapping process, the user might want to do this for a variety of reasons, the main one is to exclude a reference to a library that doesn't require transformation cause it will work the same way.

    The assembly tag for instance will filter all the contents of a given assembly, while the class and namespace are more specific, the namespace tag will filter all the classes under the namespace, not taking into account embedded namespaces.

    The tags should look like this for the each of them:

    "<Assembly Name="System.Runtime.InteropServices"/>"
    "<Namespace Name="System.ComponentModel"/>"
    "<Class Name="System.Drawing.Point"/>"
  • Assembly tag with the attribute AssemblyReferencedIn, indicates that an assembly references will not be added to the converted projects when it is defined with the specified Framework. The value of attribute should be a list of .Net Target Framework (net461 or netcoreapp3.1) separated using a comma. "<Assembly Name ="System.Data" AssemblyReferencedIn="net461,netcoreapp3.1"/>"

  • Force Include: This works the opposite way of the example above, instead of filtering a class so is not transformed you can use this to generate an exception to an already establish filter. There are two ways to specify the inclusion, a specific class name for a given namespace, or using simple regex asterisk to specified all the classes inside the namespace.

    "<ForceInclude Namespace="System.ComponentModel" Class="IContainer"/>"
    "<ForceInclude Namespace="System.Media" Class="*"/>"
  • Assembly Exception: This tag works to ignore libraries that don't require conversion, and stay the same in the converted solution, it is used only for the Assessment mode of and should not have an impact in the converted solution, since it's use is to generate the assessment report and the metrics inside of it.

    "<AssemblyException Name="System.Xml.*"/>"