public class AxoDialogBaseView<T> : RenderableComplexComponentBase<T>, IComponent, IHandleEvent, IHandleAfterRender, IRenderableComponent, IDisposable, IRenderableComplexComponentBase where T : AxoDialogBase
Unique ID of dialog, which is used to synchronize dialogs across clients. Make sure you pass unique value, otherwise inconsistencies may occur.
+When no value provided, URI is used as a ID.
public class CSVDataExporter<TPlain, TOnline> : IDataExporter<TPlain, TOnline> where TPlain : IAxoDataEntity, new() where TOnline : IAxoDataEntity
+
public class CSVDataExporter<TPlain, TOnline> : BaseDataExporter<TPlain, TOnline>, IDataExporter<TPlain, TOnline> where TPlain : IAxoDataEntity, new() where TOnline : IAxoDataEntity
public class TXTDataExporter<TPlain, TOnline> : BaseDataExporter<TPlain, TOnline>, IDataExporter<TPlain, TOnline> where TPlain : IAxoDataEntity, new() where TOnline : IAxoDataEntity
Show method with a possibility to close dialog externally by setting a signal.WARNING: This is experimental implementation of possibility to close dialogs externally. More testing need to be done.
Provides basic momentary on function.To get the actual state of the toggle task, '''IsSwitchedOn()''', '''IsSwitchedOff()''' AND '''GetState()''' methods are available.
Thanks to having AXOpen.Security implemented, we are able to identify users using our application. The same user can be logged in on multiple clients at the same time and it is desirable to be able to have an account of which clients belong to which user. This article explains how this can be achieved and how we can send messages to specific clients in Blazor. This app is built on SignalR, an open-source library that simplifies adding real-time web functionality to apps.
+
Prerequisities:
+
+
Microsoft.AspNetCore.SignalR.Client NuGet package
+
+
SignalR hub
+
Creating a hub
+
To create a new SignalR hub, we need to create a class that inherits from the Hub class located in the Microsoft.AspNetCore.SignalR namespace. It is responsible for handling messages from clients and connection management. A simple demo of a SignalR hub can be found
+in ConnectionHub.cs. The ConnectionHub class has a number of methods that can be overridden and methods specified by the user. E.g.:
+
+
OnConnectedAsync() - called when a new client connects to the hub
+
OnDisconnectedAsync() - called when a client disconnects from the hub
+
SendMessage() - custom method that can be called by the client
+
+
Hub set up in Blazor
+
To use the hub across all components in Blazor we need to create a service that will provide the Hub connection. See HubConnectionProvider.cs. The service is registered in the Program.cs file in the service configuration:
To use the hub we need to first inject the HubConnectionProvider service into the component we want to use the hub in. To listen for messages from the hub we need to register a handler (a method that will be called when a message is received) using the On method. In Index.razor.cs e.g.:
This will register a handler that will be called when a message with the name ReceiveMessage is received. The handler will be called with two parameters - sender and message. The types of the parameters need to be specified in the On method. This ReceiveMessage is called from the ConnectionHub in a SendMessage method:
+
public async Task SendMessage(string receiver, string message)
+{
+ ...
+ // sends message to all clients regardless of the receiver
+ await Clients.All.SendAsync("ReceiveMessage", sender, message);
+}
+
+
To trigger the SendMessage method from the client we can use the InvokeAsync method in a code behind of a component:
Sequence diagram of the communication between the clients and the hub:
+
sequenceDiagram
+ participant Client 1
+ participant Client 2
+ participant Client 3
+ participant Hub
+
+ loop Communication
+ Client 3->>Hub: HubConnection.SendAsync("SendMessage", receiver, message)
+ Note over Hub: SendMessage(string receiver, string message)<br/>is called
+ Hub-->>Client 1: Clients.All.SendAsync("ReceiveMessage", sender, message);
+ Hub-->>Client 2: Clients.All.SendAsync("ReceiveMessage", sender, message);
+ Hub-->>Client 3: Clients.All.SendAsync("ReceiveMessage", sender, message);
+ Note over Client 2: HubConnection.On("ReceiveMessage")<br/>listener is triggered on all clients
+ end
+
+
How to identify clients
+
To be able to access currently logged in user in ConnectionHub we need to obtain the .AspNetCore.Identity.Application cookie used for identification. This is done in the Host.cshtml file:
+
var cookie = HttpContext.Request.Cookies[".AspNetCore.Identity.Application"];
+
+
The cookie is then passed to the App.razor component as a parameter. In the code behind of the App.razor component, a cookie object is created and added to the HubConnection as a cookie container under options.Cookies:
+
var cookieContainer = new CookieContainer();
+var cookie = new Cookie()
+{
+ Name = ".AspNetCore.Identity.Application",
+ Domain = NavigationManager.ToAbsoluteUri("/").Host,
+ Value = IdentityCookie
+};
+cookieContainer.Add(cookie);
+
+HubConnectionProvider.HubConnection = new HubConnectionBuilder()
+ .WithUrl(NavigationManager.ToAbsoluteUri("/connectionHub"), options =>
+ {
+ options.Cookies = cookieContainer;
+ })
+ .Build();
+
+
By providing the cookie to the HubConnection we are now able to access the currently logged in user in the ConnectionHub:
+
string name = Context.User.Identity.Name; // name of the currently logged in user
+
+
Mapping client connection ids to user names
+
To by able to send messages to only those clients on which the specific user we want to send the message to is logged in, we need to map the client connection ids to the client's logged in user. The ConnectionHub contains a static variable _connections of type ConnectionMapping<string> that maps the client connection ids to the user names. The ConnectionMapping class is a simple dictionary that allows multiple values to be mapped to a single key. The ConnectionMapping class is defined in ConnectionMapping.cs.
+
When a new client connects to the hub, the OnConnectedAsync() method is called. Each connection has a unique id which we can add to the _connections dictionary along with the user name of the currently logged in user:
As it is implemented currently, when a user on a client is not logged in, the connection is not added to the mapping.
+
When a client disconnects from the hub, the OnDisconnectedAsync() method is called. We can then remove the connection id from the _connections dictionary:
To send a message to those clients on which the specific user is logged in, we need to obtain the connection ids of those clients. This is done by getting the values from the _connections dictionary using the user name as a key:
To send a message to all clients, we can use the Clients.All.SendAsync() method. if we want to send a message only to the caller (the client that called a SendAsyncto the hub), we can use the Clients.Caller.SendAsync() method.
Please note that the IP address corresponds to the IP address of your network adapter.
+
2. Add rules to the firewall
+
Follow these steps to add rules for the desired ports in the Windows Defender Firewall:
+
+
Go to Control Panel > Windows Defender Firewall > Advanced Settings
+
+
In the Inbound Rules section, add the rules for the ports you wish to use.
+
+
+
If you are using Eset, you should perform the following steps:
+
+
Navigate to Eset > Setup > Network > Click on settings next to Firewall > Configure.
+
+
Check the option `Also evaluate rules from Windows Firewall`` or add the rule directly in Eset.
+If you using Eset you need to: Eset > Setup > Network > click on settings next to Firewall > Configure
+
+
+
Warning
+
If you intend to use HTTPS with a self-signed SSL certificate, make sure to adjust the DeveloperSettings.BypassSSLCertificate attribute in Program.cs to true, before start your application. Here's an example of how to do it:
The AlertDialog class provides a notification mechanism in application in form of toasts.
+
+
In-app usage
+
Alerts dialogs can be simply called anywhere from application by injecting IAlertDialogService and calling AddAlertDialog(type, title, message, time) method.
+
+
Note
+
IAlertDialogService is a scoped service, therefore alerts are unique to each client and are not synchronized.
+
+
+
Make sure your Blazor application references axopen_core_blazor project and AxoCore services are added to builder in Program.cs file.
+
+
builder.Services.AddAxoCoreServices();
+
+
+
Add AxoAlertToast instance to MainLayout.razor file.
AxoDialogs provide capability to interact with the user by rising dialogs directly from the PLC program.
+
Example
+
VAR PUBLIC
+ _dialog : AXOpen.Core.AxoDialog;
+END_VAR
+//----------------------------------------------
+
+IF(_dialog.Show(THIS)
+ .WithOk()
+ .WithType(eDialogType#Success)
+ .WithCaption('What`s next?')
+ .WithText('To continue click OK?').Answer() = eDialogAnswer#OK) THEN
+
+ //if answer is ok, move next in sequence
+ THIS.MoveNext();
+
+END_IF;
+
+
+
Getting started
+
+
Make sure your Blazor application references axopen_core_blazor project and AxoCore services are added to builder in Program.cs file. Also, map dialoghub which is needed for dialog synchronization using SignalR technology.
Go to your page, where you wish to have dialogs and include AxoDialogLocator component at the end of that page.
+
+
Provide list of ObservedObjects, on which you want to observe dialogs. You can also provide DialogId, which serves for synchronization of dialogs between multiple clients. If DialogId is not provided, the current URI is used as an id.
+
+
Important
+
Make sure, that each page has only one instance of AxoDialogLocator and that provided DialogId is unique across the application! If you wish to observe multiple objects, add them into ObservedObjects list.
Now, when dialog is invoked in PLC, it will show on all clients and pages, where AxoDialogLocator is present with corresponding observed objects. The answers are synchronized across multiple clients.
+
AxoDialog types
+
AxoDialogs contains currently 3 types of predefined dialogs:
+
+
Okay dialog
+
YesNo dialog
+
YesNoCancel dialog
+
+
+
Also, the visual type of corresponding dialog can be adjusted with eDialogType enum, which is defined as follows:
Answers of dialogs are synchronized across multiple clients with the SignalR technology.
+
+
Closing a dialog with external signal
+
External signals can be provided to dialog instance within a ShowWithExternalClose method, which can be then used to close dialog externally (for example from other page of application, or by pressing a hardware button...).
+
4 different signals can be monitored in ShowWithExternalClose method:
+
+
inOkAnswerSignal
+
inYesAnswerSignal
+
inNoAnswerSignal
+
inCancelAnswerSignal
+
+
Below is an example of closing dialog with _externalCloseOkSignal bool variable, which is set in other part of application:
+
VAR PUBLIC
+ _dialog : AXOpen.Core.AxoDialog;
+ _externalCloseOkSignal : BOOL;
+ _dialogAnswer : eDialogAnswer;
+END_VAR
+
+//----------------------------------------------
+_dialogAnswer := _dialog.ShowWithExternalClose(THIS, _externalCloseOkSignal)
+.WithOK()
+.WithType(eDialogType#Info)
+.WithCaption('Hello world!')
+.WithText('You can also close me externally!').Answer();
+
+IF(_dialog3Answer = eDialogAnswer#Ok) THEN
+ // if answer is provided, move next
+ THIS.MoveNext();
+
+END_IF;
+
+
+
Creation of own modal dialog
+
PLC side
+
+
Create own PLC instance of dialog, which extends AxoDialogBase.
+
+
Define dialog structure and corresponding show method, which will initialize and invoke remote task needed for dialog creation.
+
+
+
Blazor side
+
+
Define Blazor view of modal dialog, which is then generated by RenderableContentControl according to presentation pipeline.
+For example, when Dialog plc type is MyCustomModal, the view must by named MyCustomModalDialogView, because implementation is using Dialog presentation type.
+
The Blazor view must inherits from @AxoDialogBaseView<MyCustomModal>, where correct generic type of dialog from PLC must be passed. The opening/closing of dialog is managed in base class by virtual methods, which can be overridden if needed.
+
It is recommended to use provided ModalDialog Blazor component, which can be customized by user needs and is fully compatible with closing/opening synchronization approach provided in base class. Otherwise, the open/close virtual methods from base class must be overridden and accordingly adapted.
Now you can use the AlertDialog wherever needed. To utilize the AlertDialog in your views or code-behind file, you must inject the 'IAlertDialogService' service:
type: represents the visualization type - Info, Success, Danger, Warning
-
title: Refers to the header of your alert
-
message: Corresponds to the text in your alert
-
time: Specifies the duration in seconds for which the alert will be displayed
-
-
RenderableContentControl
-
To use AlertDialog in a RenderableComponentBase, you need to add the 'AlertDialogService' property with the current AlertDialogService to the 'RenderableContentControl'. You can obtain the AlertDialogService from the injected service.
RenderableComponentBase has the AlertDialogService property, so in any class that inherits from RenderableComponentBase, you can use the AlertDialogService, for example:
With this option, buttons for export and import data will appear. After clicking on the export button, the .zip file will be created, which contains all existing records. If you want to import data, you must upload .zip file with an equal data structure as we get in the export file.
+
Custom export
+
You have the option to customize the exported files according to your preferences. This includes selecting specific columns and rows, choosing the desired file type, and specifying the separator. It's important to note that if you don't select all columns for export, importing the files may not be done correctly.
+
During the importing process, it is crucial to enter the same separator that was used during the export. If the default separator was used during the export, there is no need to make any changes.
+
You also can create own exporter. To do this, you must create a class that implements IDataExporter<TPlain, TOnline> interface. This interface requires you to implement the Export, Import and GetName method. Once you've done this, your custom exporter will be displayed in the custom export and import modal view. Users will be able to choose the exported file type through this view.
For a better user experience, it is strongly recommended to clean the Temp directory when starting the application. The best way to do this is to add the following lines to the "Program.cs" file:
With this option, buttons for export and import data will appear. After clicking on the export button, the .zip file will be created, which contains all existing records. If you want to import data, you must upload .zip file with an equal data structure as we get in the export file.
+
Custom export
+
You have the option to customize the exported files according to your preferences. This includes selecting specific columns and rows, choosing the desired file type, and specifying the separator. It's important to note that if you don't select all columns for export, importing the files may not be done correctly.
+
During the importing process, it is crucial to enter the same separator that was used during the export. If the default separator was used during the export, there is no need to make any changes.
+
You also can create own exporter. To do this, you must create a class that implements IDataExporter<TPlain, TOnline> interface. This interface requires you to implement the Export, Import and GetName method. Once you've done this, your custom exporter will be displayed in the custom export and import modal view. Users will be able to choose the exported file type through this view.
For a better user experience, it is strongly recommended to clean the Temp directory when starting the application. The best way to do this is to add the following lines to the "Program.cs" file:
With this option, buttons for export and import data will appear. After clicking on the export button, the .zip file will be created, which contains all existing records. If you want to import data, you must upload .zip file with an equal data structure as we get in the export file.
+
Custom export
+
You have the option to customize the exported files according to your preferences. This includes selecting specific columns and rows, choosing the desired file type, and specifying the separator. It's important to note that if you don't select all columns for export, importing the files may not be done correctly.
+
During the importing process, it is crucial to enter the same separator that was used during the export. If the default separator was used during the export, there is no need to make any changes.
+
You also can create own exporter. To do this, you must create a class that implements IDataExporter<TPlain, TOnline> interface. This interface requires you to implement the Export, Import and GetName method. Once you've done this, your custom exporter will be displayed in the custom export and import modal view. Users will be able to choose the exported file type through this view.
For a better user experience, it is strongly recommended to clean the Temp directory when starting the application. The best way to do this is to add the following lines to the "Program.cs" file:
With this option, buttons for export and import data will appear. After clicking on the export button, the .zip file will be created, which contains all existing records. If you want to import data, you must upload .zip file with an equal data structure as we get in the export file.
+
Custom export
+
You have the option to customize the exported files according to your preferences. This includes selecting specific columns and rows, choosing the desired file type, and specifying the separator. It's important to note that if you don't select all columns for export, importing the files may not be done correctly.
+
During the importing process, it is crucial to enter the same separator that was used during the export. If the default separator was used during the export, there is no need to make any changes.
+
You also can create own exporter. To do this, you must create a class that implements IDataExporter<TPlain, TOnline> interface. This interface requires you to implement the Export, Import and GetName method. Once you've done this, your custom exporter will be displayed in the custom export and import modal view. Users will be able to choose the exported file type through this view.
For a better user experience, it is strongly recommended to clean the Temp directory when starting the application. The best way to do this is to add the following lines to the "Program.cs" file:
Localization is a useful feature of any application. It allows you to translate the application into different languages. This guide will show you how localization is achieved in our template Blazor application - templates.simple.
+
Prerequisites
+
+
Microsoft.Extensions.Localization NuGet package
+
+
Localization in Blazor
+
To make use of localization in Blazor, make sure that:
+
+
Localization services are added in Program.cs:
+
builder.Services.AddLocalization();
+
+
+
Localization middleware with supported languages is added in the correct order to the middleware pipeline in Program.cs:
+
var supportedCultures = new[] { "en-US", "sk-SK", "es-ES"};
+var localizationOptions = new RequestLocalizationOptions()
+ .AddSupportedCultures(supportedCultures)
+ .AddSupportedUICultures(supportedCultures);
+
+app.UseRequestLocalization(localizationOptions);
+
+
+
In _Imports.razor the following @using directives are added:
For more information on localization in Blazor visit Microsoft Docs.
+
Adding support for a new language
+
In order to add a new language support to the application, a resource file (.resx) needs to be created. Resource file are in the forefront of localization in .NET. They are used to store app data (in our case strings), that can be easily accessed and changed without recompiling the app.
+
In our template application, resource files are located in the Resources folder. Create a new resource file for the language you want to add. The name of the file should be in the following format: ResourceName.culture.resx, where culture is the culture code of the language. E.g. ResourceName.de.resx would be a resource file for German language.
+
If you want to make resource files easier to work with, check out ResXManager extension for Visual Studio.
+
In _Imports.razor make sure that the @using directive for the newly created resource file is added and inject the IStringLocalizer service of the resource file. E.g.:
To change the language dynamically, add a new CultureInfo object to the supportedCultures array in the code section of Index.razor. E.g.:
+
private CultureInfo[] supportedCultures = new[]
+{
+ new CultureInfo("en-US"),
+ new CultureInfo("sk-SK"),
+ new CultureInfo("es-ES"),
+ new CultureInfo("de-DE") // newly added language
+};
+
+
When selecting a language from the <select> menu in Index.razor, a cookie with selected language is created by ChangeCulture method of CultureController.
+
Using localized strings
+
To use localized strings, simply use Localizer service previously injected in _Imports.razor E.g.:
+
<h1>@Localizer["Hello World!"]</h1>
+
+
If the string is not found in the resource file, the key is returned instead. If it is found, however, the localized string is returned.
The StartDequeuing method is now called with two parameters. The first parameter AxoApplication.Current.Logger refers to the instance of the logger that was created and configured in the previous step. The second parameter is 250. This starts a loop that dequeues log messages from the AxoLogger's message queue every 250 milliseconds, passing them to the configured sinks—in our case, the console window.
The StartDequeuing method is now called with two parameters. The first parameter AxoApplication.Current.Logger refers to the instance of the logger that was created and configured in the previous step. The second parameter is 250. This starts a loop that dequeues log messages from the AxoLogger's message queue every 250 milliseconds, passing them to the configured sinks—in our case, the console window.
The custom compiled bootstrap files are stored in the wwwroot\css\custom folder. Add your custom compiled bootstrap to this folder. In order to be able to switch to your newly created theme, you need to add the name of the theme to the supportedThemes array in the Index.razor file:
This method creates a cookie with the name theme and the value of the selected theme. The cookie is then used to determine which stylesheet to use. The cookie expires after the browser session ends.
+
In the _Host.cshtml file, the css file of the selected theme is loaded based on the value of the theme cookie:
Make sure that the string name of your theme in supportedThemes array in Index.razor file matches with the correct case string in the switch statement in the _Host.cshtml file. In case of an unknown theme name from the theme cookie or when the app is opened for the first time (the cookie has not been created yet), the default bootstrap theme is loaded.