Adds an endpoint to any .NET Core service that opens a customizable web view to monitor the service's log activities and resource usage in real-time.
dotnet add package DotNetify.Pulse
using DotNetify;
using DotNetify.Pulse;
...
public void ConfigureServices(IServiceCollection services)
{
services.AddSignalR();
services.AddDotNetify();
services.AddDotNetifyPulse();
}
public void Configure(IApplicationBuilder app)
{
app.UseWebSockets();
app.UseDotNetify();
app.UseDotNetifyPulse();
// .NET Core 2.x only:
app.UseSignalR(config => config.MapDotNetifyHub());
// .NET Core 3.x only:
app.UseRouting();
app.UseEndpoints(endpoints => endpoints.MapHub<DotNetifyHub>("/dotnetify"));
}
Internet connection is required for loading the UI scripts from public CDN.
- copy the folder 'pulse-ui' from the build output to your project.
- set the build action of the files in the folder to
Content
andCopy if newer
.
Before you proceed, let's first do a bit of a dive on how this thing works.
This library uses:
- SignalR to push data from your service to the web browser.
- DotNetify to write the code using MVVM + Reactive programming.
- DotNetify-Elements to provide HTML5 web components for the view.
There is a dotNetify view model in this repo named PulseVM
. This class is the one that pushes data to the browser view, and it only does that when the page is opened.
When it's instantiated, it will look for service objects that implements IPulseDataProvider and passes its own instance to the interface's Configure
method so that service object can add properties for the data stream. The view model then regularly checks for data updates on those properties and push them to the browser.
On the browser side, when it sends the /pulse
HTTP request, this library's middleware intercepts it and returns index.html
. You can find it and other static files in your service's output directory under pulse-ui
folder. The HTML markup uses highly specialized web components from dotNetify-Elements to display data grid and charts and for layout. These components are designed so that they can be configured from the server-side view model and maintain connection with the data properties to auto-update, without requiring client-side scripting.
For example, let's create a simple clock provider:
- Use
AddProperty
to add a new observable property named "Clock" to the Pulse view model, with an initial value. - Create a timer to emit new value every second.
using DotNetify.Pulse;
using System.Reactive.Linq;
...
public class ClockProvider : IPulseDataProvider
{
public IDisposable Configure(PulseVM pulseVM, out OnPushUpdate onPushUpdate)
{
var clockProperty = pulseVM.AddProperty<string>("Clock", DateTime.Now.ToString("hh:mm:ss"));
onPushUpdate = _ => { }; // No op.
return Observable
.Interval(TimeSpan.FromSeconds(1))
.Subscribe(_ => clockProperty.OnNext(DateTime.Now.ToString("hh:mm:ss")));
}
}
services.TryAddEnumerable(ServiceDescriptor.Singleton<IPulseDataProvider, ClockProvider>());
To do this, you will override the default HTML fragment file called "section.html". Notice that when you build your service, the library creates in your project a folder called "pulse-ui" which contains "section_template.html".
- Copy and paste this folder to a new one and name it "custom-pulse-ui".
- Rename "section_template.html" to "section.html".
- Right-click on "section.html", select Properties, and set the "Copy to Output Directory" to "Copy if newer".
- Edit the html file and insert the following:
...
<d-frame>
<div class="card" style="width: 200px; font-size: 40px">
<d-element id="Clock"></d-element>
</div>
...
Read the dotNetify-Elements documentation for info on all the available web components.
app.UseDotNetifyPulse(config => config.UIPath = Directory.GetCurrentDirectory() + "\\custom-pulse-ui");
If you want to pass application settings through appsettings.json
, you can include your custom configuration in the "DotNetifyPulse" configuration section, under "Providers"
. For example:
{
"DotNetifyPulse": {
"Providers": {
"ClockProvider": {
"TimeFormat": "hh:mm:ss"
}
}
}
}
To read the settings, inject PulseConfiguration
type in your constructor, and use the GetProvider
method:
internal class ClockSettings
{
public string TimeFormat { get; set; }
}
public ClockProvider(PulseConfiguration config)
{
var settings = config.GetProvider<ClockSettings>("ClockProvider");
}