Category: poorManDisplayErrors

Poor man display errors–part 3–Display

In part 2 I have exposed the errors via the API to the world. Now I should show in a Blazor/ Razor page . It is enough to read with an HTTP call and display data.

Display data in a table

Because
– the API that gives error can change , however it will be an array of objects
– I do not want to use a custom grid

I took the decision to create the display of errors as a table that has dynamic columns based on the array .

This is the code for javascript to create a table from an array ( it assumes that a div with id = mytable exists)

var table= null;
function displayData(a){
console.log(a);
arrayToTable(JSON.parse(a));
};

function removeTableCreated(){
var parent =document.getElementById("myTable");
while (parent.lastChild) {
parent.removeChild(parent.lastChild);
}
}

function arrayToTable(data) {
removeTableCreated();
if((!Array.isArray(data)) || data.length == 0){
window.alert(‘ no data ‘);
return;
}

// Create table element
table = document.createElement(‘table’);
table.border = ‘1’;

// Create table header row
const headerRow = document.createElement(‘tr’);
Object.keys(data[0]).forEach(key => {
const th = document.createElement(‘th’);
th.textContent = key;
headerRow.appendChild(th);
});
table.appendChild(headerRow);

// Create table rows
data.forEach(item => {
const row = document.createElement(‘tr’);
Object.values(item).forEach(value => {
const td = document.createElement(‘td’);
td.textContent = value;
row.appendChild(td);
});
table.appendChild(row);
});

// Append table to the body (or any other container)
//document.body.appendChild(table);
document.getElementById("myTable").appendChild(table);
}

Obtain Data

Now the blazor razor page is simple

<h3>PoorManErrors</h3>
<FluentButton Appearance="Appearance.Accent" @onclick="async()=> await GenerateError()">Generate Error</FluentButton>

<FluentButton Appearance="Appearance.Accent" @onclick="async()=> await SeeErrors()">Refresh</FluentButton>

<FluentButton Appearance="Appearance.Accent" @onclick="async()=> await ClearErrors()">Clear</FluentButton>
<div id="myTable"></div>

And the code for retrieving

    [Inject(Key = Program.apiNameConfig)]
    private HttpClient? httpClient { get; set; }
    [Inject]
    private IJSRuntime? JSRuntimeData { get; set; }

    private async Task GenerateError()
    {

        ArgumentNullException.ThrowIfNull(httpClient);
        var r = new ReadFromAPI(httpClient);

        var res = await r.ReadDataGet("api/usefull/error/WithILogger");

        await SeeErrors();
        StateHasChanged();

    }
    protected override async Task OnInitializedAsync()
    {
        await SeeErrors();
    }
    private async Task ClearErrors()
    {
        ArgumentNullException.ThrowIfNull(httpClient);
        var r = new ReadFromAPI(httpClient);
        var res = await r.ReadDataGet("nlog/Memory/stringData/clear");
        await SeeErrors();
    }
    private async Task SeeErrors()
    {
        ArgumentNullException.ThrowIfNull(httpClient);
        var r = new ReadFromAPI(httpClient);


        var res = await r.ReadDataGet("nlog/Memory/stringData/list");
        if (string.IsNullOrEmpty(res))
        {
            return;
        }
        ;
        if (res != null)
        {
            ArgumentNullException.ThrowIfNull(JSRuntimeData);
            await JSRuntimeData.InvokeVoidAsync("displayData", res);

        }
    }

Side note 1 : the button GenerateErrors take advantage of package NetCoreUsefullEndpoints that registers an endpoint that logs an error.

Side note 2: See MainLayout for tag ErrorContent . You may wanto to modify this one too, to go to the error page

Side note 3: You may want to add a “copy to clipboard” button or “send email”

That’s all, folks!

Poor man display errors–part 2 – API

The idea of previous post was that I want to display the errors from a WebApplication – composed by a backend ( WebAPI .NET Core )and frontend( Blazor )

In this post I will show what modifications I must do in the API – code in .NET Core . There are 3 steps

Step1 : Intercept Errors

First I should intercept ( via a middleware ) all errors that can occur in an WebAPI.



using NLog.Web;

namespace XP.API;

public class LogErrorsMiddleware : IMiddleware
{
    static LogErrorsMiddleware()
    {
        logger = NLog.LogManager.GetCurrentClassLogger();
    }
    private static NLog.Logger logger;

    public LogErrorsMiddleware()
    {
        
    }
    public Task InvokeAsync(HttpContext context, RequestDelegate next)
    {
        try
        {
            logger.Debug("Starting "+ context.Request.Path);
            return next(context);

        }
        catch(Exception ex)
        {
            // Log the exception
            logger.Error(ex, "An error occurred while processing the request.");
            
            throw;
        }
        finally
        {
            logger.Debug("Ending " + context.Request.Path);
        }
    }
}

And I register in program.cs

builder.Services.AddTransient&lt;LogErrorsMiddleware&gt;();
//other statemtents
app.UseMiddleware&lt;XP.API.LogErrorsMiddleware&gt;();

Step2 : Add errors to memory

Now we should register the errors from the logging framework into the memory . NLog has a target for registering in memory
( https://github.com/NLog/NLog/wiki/Memory-target ) . ( Serilog has https://github.com/serilog-contrib/SerilogSinksInMemory)

So this is the nlog xml


&lt;?xml version="1.0" encoding="utf-8" ?&gt;
&lt;nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" autoReload="true" throwConfigExceptions="true" internalLogLevel="Info" internalLogFile="c:\temp\internal-nlog-AspNetCore.txt"&gt;

	&lt;!-- enable asp.net core layout renderers --&gt;
	&lt;extensions&gt;
		&lt;add assembly="NLog.Web.AspNetCore"/&gt;
	&lt;/extensions&gt;

	&lt;!-- the targets to write to --&gt;
	&lt;targets&gt;
		&lt;target xsi:type="Memory" name="stringData" MaxLogsCount="200" layout="${longdate}|${aspnet-user-isauthenticated}|${aspnet-user-identity}|${level:uppercase=true}|${logger}|${message:withexception=true}" /&gt;
		&lt;!-- File Target for all log messages with basic details --&gt;
		&lt;target xsi:type="File" name="allfile" fileName="c:\temp\nlog-AspNetCore-all-${shortdate}.log" layout="${longdate}|${event-properties:item=EventId:whenEmpty=0}|${level:uppercase=true}|${logger}|${message} ${exception:format=tostring}" /&gt;

		&lt;!-- File Target for own log messages with extra web details using some ASP.NET core renderers --&gt;
		&lt;target xsi:type="File" name="ownFile-web" fileName="c:\temp\nlog-AspNetCore-own-${shortdate}.log" layout="${longdate}|${event-properties:item=EventId:whenEmpty=0}|${level:uppercase=true}|${logger}|${message} ${exception:format=tostring}|url: ${aspnet-request-url}|action: ${aspnet-mvc-action}" /&gt;

		&lt;!--Console Target for hosting lifetime messages to improve Docker / Visual Studio startup detection --&gt;
		&lt;target xsi:type="Console" name="lifetimeConsole" layout="${MicrosoftConsoleLayout}" /&gt;
	&lt;/targets&gt;

	&lt;!-- rules to map from logger name to target --&gt;
	&lt;rules&gt;
		&lt;!-- All logs, including from Microsoft --&gt;
		&lt;!--&lt;logger name="*" minlevel="Trace" writeTo="allfile" /&gt;--&gt;

		&lt;!-- Suppress output from Microsoft framework when non-critical --&gt;
		&lt;logger name="System.*" finalMinLevel="Warn" /&gt;
		&lt;logger name="Microsoft.*" finalMinLevel="Warn" /&gt;
		&lt;!-- Keep output from Microsoft.Hosting.Lifetime to console for fast startup detection --&gt;
		&lt;logger name="Microsoft.Hosting.Lifetime*" finalMinLevel="Info" writeTo="lifetimeConsole" /&gt;
		&lt;logger name="*" minLevel="Trace" writeTo="lifetimeConsole" /&gt;
		&lt;logger name="*" minLevel="Error" writeTo="stringData" /&gt;
		&lt;!--&lt;logger name="*" minLevel="Trace" writeTo="ownFile-web" /&gt;--&gt;
	&lt;/rules&gt;
&lt;/nlog&gt;

Step3 : Expose Errors

Now we should expose the API to the outside world. I have considered 2 API, one for retrieving and the other for clearing

app.MapGet("/nlog/memory/{name:alpha}/list", (string name) =&gt;
{
    var target = LogManager.Configuration.FindTargetByName&lt;MemoryTarget&gt;(name);
    var logEvents = target.Logs;
    return logEvents.Select((line, nr) =&gt; new
    {
        nr = (nr+1),
        line,

    }).ToArray();
});
    app.MapGet("/nlog/memory/{name}/clear", (string name) =&gt;
    {
        var target = LogManager.Configuration.FindTargetByName&lt;MemoryTarget&gt;(name);
        var logEvents = target.Logs;
        var nr = logEvents.Count;
        logEvents.Clear();
        return (nr.ToString());
    })
    //.ShortCircuit() 
    .WithTags("andrei");

In part three, we’ll explore how to call these endpoints from your Blazor frontend and display errors in a user-friendly way.

Poor Man Display Errors–part 1 – idea

Let’s be honest, debugging can be a pain. You build your beautiful new web app (built with a snazzy backend API like .NET Core and a Blazor frontend – you know the drill!), everything seems great…until it isn’t. Suddenly, you are hitting errors, but you’re stuck hunting for clues in endless log files.

Sound familiar?

Wouldn’t it be amazing to see those errors pop up in real-time, right on your web interface? Imagine knowing exactly what went wrong without leaving the comfort of your application!

So let’s assume you make an Web Application composed , as usually now , from a backend WebAPI (.NET Core) and a frontend (Blazor ). ( What I will show to you could be adapted easy to any technology / framework )

Why not see the errors in real time on the web interface .

How to do this ?

Well, a solution will be to keep the errors in memory on the backend and display on the frontend .

Now, take your favorite logging framework (nlog/log4net/ serilog / others )  and see if it supports this .

I will show the code for Nlog in the next blog post

Andrei Ignat weekly software news(mostly .NET)

* indicates required

Please select all the ways you would like to hear from me:

You can unsubscribe at any time by clicking the link in the footer of our emails. For information about our privacy practices, please visit our website.

We use Mailchimp as our marketing platform. By clicking below to subscribe, you acknowledge that your information will be transferred to Mailchimp for processing. Learn more about Mailchimp's privacy practices here.