How To Implement Plug-In On C# Blazor For Native JavaScript Application. Open API Scheme Diagram For Draw.io

How To Implement Plug-In On C# Blazor For Native JavaScript Application, Open Api Schema Diagram For Draw.io
 
Blazor .NET is a quite new, actively developing technology from Microsoft for building rich web applications. This technology is based on web assembly and Mono. Blazor allows us to write a client code on C# language and to create a layout with the help of a razor syntax. In this way, C# code, which is usually used in the development of the back-end part, can be easily reused in a front-end part implementation. What if Blazor .NET and C# can be used for building the plug-in for the existing native JavaScript web or Electron application? This article is devoted to creating the Open API scheme visualization plug-in on C# language for the diagram web-editor Draw.io implemented on JavaScript.
 
Draw.io is a powerful online tool for creating diagrams. It is available as a web application and also as a desktop. Draw.io is a mature project. It is implemented by pure JavaScript. Furthermore, it's UI is implemented by using a standard JavaScript document API. The diagram rendering engine is the open source library mxGraph, which is developed by the same team as the main application.
 
How To Implement Plug-In On C# Blazor For Native JavaScript Application, Open Api Schema Diagram For Draw.io
 
Draw.io can use a plug-in script. An example of the SQL scheme plug-in can be found on github. The code listing below describes the public interface, which is necessary for the implementation of building plug-in.
  1. Draw.loadPlugin(function (ui)   
  2.    // plugin code    
  3. });    
The following steps should be done for setting up the plug-in in draw.io application:
  1. Navigate to main menu Extras -> Plugins 
  2. Setup the javascript file URL in the opened dialog form
  3. Restart electron application or reload draw.io web site to apply changes
Draw.io creates diagrams in a special XML format. An example below shows how it appears.
  1. <mxGraphModel dx="1662" dy="794" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">    
  2.   <root>    
  3.     <mxCell id="0"/>    
  4.     <mxCell id="1" parent="0"/>    
  5.     <mxCell id="HnHs_ySxEpce5QuuIPva-3" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="HnHs_ySxEpce5QuuIPva-1" target="HnHs_ySxEpce5QuuIPva-2">    
  6.       <mxGeometry relative="1" as="geometry"/>    
  7.     </mxCell>    
  8.     <mxCell id="HnHs_ySxEpce5QuuIPva-1" value="" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">    
  9.       <mxGeometry x="170" y="220" width="120" height="60" as="geometry"/>    
  10.     </mxCell>    
  11.     <mxCell id="HnHs_ySxEpce5QuuIPva-2" value="" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">    
  12.       <mxGeometry x="440" y="220" width="120" height="60" as="geometry"/>    
  13.     </mxCell>    
  14.   </root>    
  15. </mxGraphModel>  
 
There are root XML element with mxcell collections inside. Every XML mxcell element describes a visual element on the diagram. There are two types of elements. It depends on the values of the special attributes:
  • edge="1" - connector, line, arrow and so on
  • vertex="1" - vertex, ellipse, rectange and so on
Draw.io provides API, which allows rendering a diagram from the XML content.
This is a brief description of draw.io application and its basic features. Let's go to the description of an example of a plug-in task.
 

C# plug-in example task

 
The main goal of this article is to show, how it is possible to integrate any existing .NET library with Draw.io. So, for example,  let's get from NuGet the library NSwag.Core. This library allows working with Open API specification - Swagger. Then let's implement plug-in using NSwag.Core, which will allow creating a draw.io XML diagram from the swagger scheme JSON file.   
How To Implement Plug-In On C# Blazor For Native JavaScript Application, Open Api Schema Diagram For Draw.io
 

Creating .NET Blazor project

 
First of all, we need to create a project .NET Blazor client side rendering type..NET 3.0 contains this type of project, but it is available only from the command line now:
  1. dotnet new blazorwasm –n NewPluginProject  
The new project contains a few demo Blazor components. We need the only one root App component for the plug-in. Thi component will integrate .NET and JavaScript execution contexts. It is better to create the code behind file “App.razor.cs”, and then to move logic from the razor layout to the C# class for the main plug-in App component. All other non-used demo components can be removed from the project now. Solution explorer will be looking as below after preparing project:
 
 How To Implement Plug-In On C# Blazor For Native JavaScript Application, Open Api Schema Diagram For Draw.io
 
The next part of the article won't cover many details about Blazor .NET, instead we will focus on integration between JavaScript application and C# plug-in. If you want to find more details about Blazor .NET technology, please refer to the official guide. Also, code listings below contain only the main part of the source code with description.   
 

Create a plug-in startup script and load Blazor component

 
It is impossible to create a draw.io plug-in without any lines of JavaScript code. Above all, we need to implement a draw.io interface to setup plug-in and load Blazor .NET web assembly script. So, let’s add new file OpenApiDocumentSchemaPlugin.js  to the ‘wwwroot\js’ project directory. The new file will contain the following logic on JavaScript:
  • define base URL for loading other plug-in scripts
  • add new menu element in draw.io
  • create dom-element inside draw.io document body for loading plug-in with Blazor .NET web assembly component
  • load script for Blazor .NET web assembly
  1. // Base URL for related plug-in scripts loading  
  2. var OpenApiDocumentSchemaPluginLocation = 'http://localhost:6930/';      
  3.       
  4. Draw.loadPlugin(function (ui) {      
  5.       
  6.     const loadScript = (path, action) => {      
  7.         //load javascript file to editor and execute action after   
  8.     };      
  9.          
  10.     //Load .NET Blazor startup script    
  11.     loadScript(OpenApiDocumentSchemaPluginLocation + 'js/blazor.webassembly.js', () => {      
  12.         //Create element in DOM for Blazor web assembly component 
  13.         const pluginDomElement = 'OpenApiDocumentSchemaPluginApp';      
  14.         document.getElementsByTagName("body")[0].appendChild(document.createElement(pluginDomElement));      
  15.     });      
  16.       
  17.     //Add new menu item in draw.io    
  18. });      
So far, so good, we can load Blazor .NET component in draw.io application now. Let's implement the logic for the new swagger scheme diagram plug-in.
 

Plug-in architecture: interaction between JavaScript and C# contexts

 
The main plug-in behavior will be:
  • open and load JSON file;
  • then parse JSON string to the OpenApiDocument object (NSwag.Core lib);
  • then create XML diagram from OpenApiDocument (custom C# code);
  • and then render a diagram in the Draw.io.
Hence, there are will be the two types of interactions between C# and JavaScript code:
  • JavaScript part will send JSON string for processing to C# part
  • C# part will send XML diagram string for rendering to JavaScript part
Blazor .NET supports necessary features for the interactions with JavaScript (Interop). But there are the following nuances:
  • C# code can call a JavaScript function, which is defined in the global context. Also, C# code can call a function from instances of JavaScript classes, but these instances should be defined in the global context as well
  • JavaScript can call C# static methods. Also, JavaScript can call C# instance methods, but a reference on this instance should be provided to the JavaScript context firstly
These Blazor .NET features allow implementing the following architecture interactions between JavaScript application and .NET Blazor plug-in on C#.
The following conventions using on the diagram below:
  • JS Context — draw.io application context
  • .NET Context —context web assembly Blazor component (C# code).
 
There are two special code files OpenApiDocumentSchemaDotNetContract.js and OpenApiDocumentSchemaJsContract.js on JavaScript and two special code files OpenApiDocumentSchemaDotNetContract.cs and OpenApiDocumentSchemaJsContract.cs on C#. 
 
How To Implement Plug-In On C# Blazor For Native JavaScript Application, Open Api Schema Diagram For Draw.io
 
Code on JavaScript implements the following behavior:
  • OpenApiDocumentSchemaDotNetContract.js encapsulates reference on the object in .NET context and calls methods in the .Net Blazor component context (Proxy)
  • OpenApiDocumentSchemaJsContract.js handles calls from C# context (Handler)
Code on C# implements the following behavior:
  • OpenApiDocumentSchemaDotNetContract.cs handles calls from JavaScript context (Handler)
  • OpenApiDocumentSchemaJsContract.cs using base JsRuntime object and calls JavaScript functions (Proxy)
 So, every context contains two types of classes - Handler and Proxy. This decision is made according to the SRP (Single Responsibility Principle).
 

Implementation of the contract classes in JavaScript part

 
OpenApiDocumentSchemaJsContract.js file contains OpenApiDocumentSchemaJsContract class with methods for working with UI and rendering the XML diagram. The code below shows a short description.
  1. var OpenApiDocumentSchemaJsContract = (function () {  
  2.     return {  
  3.   
  4.         setEditorUi: function (ui) {  
  5.             this.ui = ui;  
  6.         },  
  7.   
  8.         openFile: function () {  
  9.             //Open file from device  
  10.         },  
  11.   
  12.         loadXml: function (xmlContent) {  
  13.            //render XML layout  
  14. this.ui.editor.setGraphXml(mxUtils.parseXml(xmlContent).documentElement);  
  15.         }  
  16.     };  
  17. }());  
OpenApiDocumentSchemaDotNetContract.js file contains OpenApiDocumentSchemaDotNetContract class, which redirects all calls to the C# context. 
  1. var OpenApiDocumentSchemaDotNetContract = (function () {    
  2.     return {    
  3.         setDotNetReference: function (dotnetContractReference) {    
  4.             // Set reference to the DotNet Object in JavaScript context    
  5.             this.dotnetContractReference = dotnetContractReference;    
  6.         },    
  7.         onMenuClick: function () {    
  8.             // Redirect On Menu item method call to C# context    
  9.             this.dotnetContractReference.invokeMethodAsync('OnMenuClick');    
  10.         },    
  11.         onLoadFile: function (content) {    
  12.             // Redirect On Load file content method call to C# context    
  13.             this.dotnetContractReference.invokeMethodAsync('OnLoadFile', content);    
  14.         }    
  15.     };    
  16. }());   

Implementation of the contract classes in .NET plug-in part

 
OpenApiDocumentSchemaDotNetContract class contains methods with JSInvokable attributes to provide access from JavasScript context.
  1. public class OpenApiDocumentSchemaDotNetContract : IDotNetInteropContract    
  2. {    
  3.     [JSInvokable("OnMenuClick")]    
  4.     public async Task OnMenuClick()    
  5.     {    
  6.         await _jsContract.OpenFile().ConfigureAwait(false);    
  7.     }    
  8.     
  9.     [JSInvokable("OnLoadFile")]    
  10.     public async Task OnLoadFile(string content)    
  11.     {    
  12.         //handle event loading file    
  13.     }    
  14. }    
OpenApiDocumentSchemaJsContract class wraps IJSRuntime service call, which allows calling the JavaScript context from C# code.
  1. public async Task LoadXml(string xmlContent)    
  2. {    
  3.       await _jsService.RunJsAction<T>(nameof(LoadXml), xmlContent);    
  4. }    
  5.     
  6. public async Task OpenFile()    
  7. {    
  8.      await _jsService.RunJsAction<T>(nameof(OpenFile));    
  9. }    
For building diagrams,  XML layout uses custom interface IDiagramXmlBuilder and its implementation.
  1. public interface IDiagramXmlBuilder    
  2. {    
  3.         string AddVertex(string value);    
  4.     
  5.         string AddEllipse(int x, int y, int size, string value);    
  6.     
  7.         string AddEdge(string value, string sourceId, string targetId);    
  8.     
  9.         string GetDiagramXml();    
  10. }    
OpenApiDocumentSchemaDotNetContract class contains the main logic:
  • handles event of the file content loading
  • creates OpenApiDocument by NSwag.Core library API 
  • creates a diagram's XML layout. 
After all, it sends a diagram's XML layout to the editor for rendering. 
  1. [JSInvokable("OnLoadFile")]    
  2. public async Task OnLoadFile(string content)    
  3. {    
  4.     try    
  5.     {    
  6.         var openApiDocument = await OpenApiDocument.FromJsonAsync(content);    
  7.         var openApiDiagramBuilder = new OpenApiDiagramBuilder(openApiDocument, new DiagramXmlBuilder());    
  8.         await _jsContract.LoadXml(openApiDiagramBuilder.BuildDiagram());    
  9.     }    
  10.     catch(Exception ex)    
  11.     {    
  12.         await _jsContract.ShowError(ex.Message);    
  13.     }    
  14. }    

Implementation startup part of the Blazor component 


The startup point of the Blazor .NET part is App.razor.cs class and App razor component. It is registered in Startup.cs file, also there is configured a link between App component and Html element in DOM — OpenApiDocumentSchemaPluginApp.
  1. app.AddComponent<App>(“OpenApiDocumentSchemaPluginApp”);
App.razor.cs overrides the initialization method of the Blazor component. In this method initialized reference in JavaScript context to the object in the .Net context.
  1. protected override Task OnInitializedAsync()  
  2. {  
  3.      // Set reference to the JavaScript class  
  4.      _ = JsContractInteropService.SetReferenceInJsContext(DotNetContract);  
  5.      return base.OnInitializedAsync();  
  6. }  
So, this is a high level description of the main parts of the plug-in. So, let's try to run the project and test plug-in in the application.
 

Testing plug-in in the Draw.io application

 
The source code of the example project can be loaded from Github. It is better to use Visual Studio 2019 for working with the code. Open DrawIoDotNetPlugins.sln file and select OpenApiDocumentSchemaPlugin as startup project. After that, run the project locally with IIS Express (Ctrl + F5). Blazor .NET plug-in component will be launched locally with URL http://localhost:6930/.
 
How To Implement Plug-In On C# Blazor For Native JavaScript Application, Open Api Schema Diagram For Draw.io
 
Then open draw.io web site in the new browser tab and navigate menu "Extras -> Plugins …." to setup plug-in.  
 
How To Implement Plug-In On C# Blazor For Native JavaScript Application, Open Api Schema Diagram For Draw.io
 
Input plug-in startup script URL value in the opened dialog: http://localhost:6930/js/OpenApiDocumentSchemaPlugin.js
 
How To Implement Plug-In On C# Blazor For Native JavaScript Application, Open Api Schema Diagram For Draw.io

Reload draw.io web site page and confirm loading plug-in component at the beginng. So, now plug-in is loaded in the application and ready for testing.
 
Navigate to the menu "File -> Open from…" and find new element "Open Api Document". The source code repository contains example swagger document file: \src\OpenApiDocumentSchemaPlugin\Sample\test-swagger.json
 
Select json file in the open file dialog and editor will show the new diagram. 
 
How To Implement Plug-In On C# Blazor For Native JavaScript Application, Open Api Schema Diagram For Draw.io

After loading the diagram all blocks will have a position in zero points by default. The diagram will look ugly. Use the "Auto layout" feature to make it better. Navigate menu "Arrange -> Layout -> Horizontal Flow" and the diagram will be automatically transformed in a good view.
 

Some notes after Blazor and JavaScript integration

  • It is required to set up web.config file in the Blazor .NET component's project with appropriate Http-headers for correct loading scripts by the draw.io web site from the localhost web site. Otherwise, the CORS issue will be during scripts loading in the browser.
  1. <add name=”Access-Control-Allow-Origin” value=”https://www.draw.io" />    
  2. <add name=”Access-Control-Allow-Headers” value=”Content-Type” />    
  3. <add name=”Access-Control-Allow-Credentials” value=”true” />    
  • Blazor .NET creates the special script blazor.webassembly.js during the building. This script contains code for loading web assembly libraries and mono. It uses the relative domain path of the web site, which is loading blazor.webassembly.js script. So, it will have ‘draw.io’ path value. But the plug-in is hosted on the ‘localhost’ address. Hence, it is required to override all the paths in this file on appropriate value OpenApiDocumentSchemaPluginLocation + “_framework/*”. This modification allows loading components from the localhost URL. OpenApiDocumentSchemaPluginLocation variable is configured in the OpenApiDocumentSchemaPlugin.js file and can be set up to any web site URL value or also can be mapped to the local directory. The last option is useful when testing the plug-in solution in the Electron application.

  • Blazor .NET builds and publish the project as a package, which contains the two directories with a many "*.dll", "*.wasm' files. The package for this simple plug-in example project is 7,42 Mb. This is a very large size for the simple client library package.
     
    How To Implement Plug-In On C# Blazor For Native JavaScript Application, Open Api Schema Diagram For Draw.io
  • Also, there is some delays after the first access to the plug-in in the menu element. The application will be not responsive about 10–20 sec. This is the time of the first building code inside CLR.

Conclusion

 
Blazor .NET is an actively developing framework with powerful features. Integration of Blazor .NET components with JavaScript applications allows reusing existing code on C# language. This is a good case for rapid prototyping and probably for internal tools development. But at the same time, the Blazor .NET components package has a large size and is required to host many directories and files. A simple JavaScript library may be delivered as a single "js" file, which can be uglified and minified. It can be simply hosted on Github or npm.
 
The next part of the article will describe the process of integration between.Net Blazor component and application on the Electron platform.