Polly and Blazor, Part 3 – Dependency Injection

Full source code available here.

This post continues to build on the two previous on using Polly with Blazor. The first was a simple example of using a Wait and Retry while updating the display as the policy retried, the second did the same but made use of the Polly Context. The Polly Context is something I have not made a lot of use of in the past, but I have a feeling that it will be very helpful with Blazor applications.

I’m keeping the examples similar, deliberately changing a single thing in each. In this post I will define the Wait and Retry Policy in the Startup, add it to the service collection and inject it into the Razor page.

One big difference to note is that I can no longer update the display from inside the OnRetry delegate of the Policy. Gone is the call to InvokeAsync(StateHasChanged) because this is dependent on the the ComponentBase.

In Startup.cs add the following, it defines the Wait and Retry policy, and its OnRetry delegate –

public void ConfigureServices(IServiceCollection services)
    {
    IAsyncPolicy<HttpResponseMessage> _httpRequestPolicy = Policy.HandleResult<HttpResponseMessage>(
    r => !r.IsSuccessStatusCode)
    .WaitAndRetryAsync(3,
        retryAttempt => TimeSpan.FromSeconds(retryAttempt), onRetry: (response, retryDelay, retryCount, context) =>
        {
            context["message"] = context["message"] + $"\nReceived: {response.Result.StatusCode}, retryCount: {retryCount}, delaying: {retryDelay.Seconds} seconds";
        });

    services.AddSingleton<IAsyncPolicy<HttpResponseMessage>>(_httpRequestPolicy);
    // snip...

In the Razor section of the Index.razor page add this –

@inject IAsyncPolicy<HttpResponseMessage> _httpRequestPolicy

The policy will now be injected into the Razor page.

Using it is simple, see line 16 below –

@code {
   HttpClient httpClient = new HttpClient() {
        BaseAddress =  new Uri("http://localhost:5000")
    };

    private string status;
    private Context context = new Polly.Context("", new Dictionary<string, object>() {{"message",""}}); 
    private string responseCode;
    private string number;

    private async Task CallRemoteServiceAsync()
    {
        status = "Calling remote service...";
        string requestEndpoint = "faulty/";

        HttpResponseMessage httpResponse = await _httpRequestPolicy.ExecuteAsync(ctx => httpClient.GetAsync(requestEndpoint), context);
        responseCode = httpResponse.StatusCode.ToString();
        number = await httpResponse.Content.ReadAsStringAsync();
        status = "Finished";
    }
}

Instead of injecting a Polly Policy you could inject a Polly Registry with more than one policy. I have written about the registry a few times on this blog.

Full source code available here.

Polly and Blazor, Part 2 – Using the Context

Full source code available here.

This post is a short follow up to the one where I used Polly Wait and Retry in Blazor. In that post I used variables defined in my C# method to pass information back to the calling code, to display on the screen. But here I’m going to show how the Polly Context can be used instead.

The setup and almost all the code is the same, so please take a look at that post.

Using the Context
The Razor section references the message in the Polly context rather than a defined variable for the message –

<p>Polly Summary : @context["message"]</p>

In the code block add a Polly Context and initialize the dictionary with a message, this is important because the Razor block references this message when the page loads.

@code {
    //snip...

    private Context context = new Polly.Context("", new Dictionary<string, object>() {{"message",""}});
    //snip... 

Then in the rest of the code block, update the message in the context with the information to display to the user.

private async Task CallRemoteServiceAsync()
{
    IAsyncPolicy<HttpResponseMessage> _httpRequestPolicy = Policy.HandleResult<HttpResponseMessage>(
        r => !r.IsSuccessStatusCode)
        .WaitAndRetryAsync(3,
            retryAttempt => TimeSpan.FromSeconds(retryAttempt * 2), onRetry: (response, retryDelay, retryCount, context) => {
                context["message"] = $"Recieved: {response.Result.StatusCode}, retryCount: {retryCount}, delaying: {retryDelay.Seconds} seconds\n";
                Console.WriteLine(context["message"]);
                InvokeAsync(StateHasChanged);
    });
    status = "Calling remote service...";
    string requestEndpoint = "faulty/";
    HttpResponseMessage httpResponse = await _httpRequestPolicy.ExecuteAsync(ctx => httpClient.GetAsync(requestEndpoint), context);
    responseCode = httpResponse.StatusCode.ToString();
    number = await httpResponse.Content.ReadAsStringAsync();
    status = "Finished";
    context["message"] = "";
}

That’s it, another way to do the same thing.

In the next post I’ll show how to use a Polly Policy defined within the OnInitializedAsync block.

Full source code available here.

Logging to DataDog with Serliog and .Net 5

Full source code available here.

This is a quick post to showing how to setup a .Net 5 Web Api application with logging to DataDog from your local computer.

Getting Datadog up and running

First step is to create an account with Datadog and then install the Datadog agent on your computer, in my case I am using Ubuntu.

There are a few small configuration steps to get the agent up and running.

In the datadog.yaml file look for an entry called logs_enabled, make sure this is set to true.

##################################
## Log collection Configuration ##
##################################

## @param logs_enabled - boolean - optional - default: false
## Enable Datadog Agent log collection by setting logs_enabled to true.
#
logs_enabled: true

In the /etc/datadog-agent/conf.d/ create a directory called mydotnet5app.d.

Inside that directory create a file called conf.yaml and paste in the below.

#Log section
logs:

    # - type : (mandatory) type of log input source (tcp / udp / file)
    #   port / path : (mandatory) Set port if type is tcp or udp. Set path if type is file
    #   service : (mandatory) name of the service owning the log
    #   source : (mandatory) attribute that defines which integration is sending the log
    #   sourcecategory : (optional) Multiple value attribute. Can be used to refine the source attribute
    #   tags: (optional) add tags to each log collected

  - type: file
    path: /path/to/my/logfile/application.log
    service: csharp
    source: csharp

The .Net 5 Application

I have a simple .Net 5 Web Api application. I added Serilog to log to a file at the location specified in the conf.yaml above.

In Program.cs I add the below. Note the JsonFormatter on line 4, that is important for structured logging in DataDog –

public static void Main(string[] args)
{
    Log.Logger = new LoggerConfiguration()
        .WriteTo.File(new JsonFormatter(renderMessage: true), "webapi.log")
        .CreateLogger();

    try
    {
        Log.Information("Starting up...");
        CreateHostBuilder(args).Build().Run();
    }
    catch (Exception ex)
    {
        Log.Fatal(ex, "Application start-up failed");
    }
    finally
    {
        Log.CloseAndFlush();
    }
}

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .UseSerilog()
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.UseStartup<Startup>();
        });
}

That’s all you need to configure logging, all that is left is to add a few logging statements.

Here is a simple controller that lets you do division from, giving the opportunity to generate and log divide by zero exceptions.

[ApiController]
[Route("[controller]")]
public class MathController : ControllerBase
{
    private readonly ILogger<MathController> _logger;

    public MathController(ILogger<MathController> logger)
    {
        _logger = logger;
    }

    [HttpGet("division")]
    public ActionResult Get(int a, int b)
    {
        _logger.LogInformation("Dividing {numerator} by {denominator}", a, b); // note how I can change the names of the attributes that DataDog logs to numerator and denominator.

        int answer;
        try
        {
            answer = a / b;
        }
        catch (DivideByZeroException ex)
        {
            _logger.LogError(ex, "SomeId: {someId}. ErrorCode: {errorCode} Division didn't work, was dividing {numerator} by {denominator}", Guid.NewGuid(), -99, a, b);

            return BadRequest("Dividing by zero is not allowed");
        }
        _logger.LogInformation("Returning {result}", answer); // again the name of the attribute logged is different from the variable name in the code.
        return Ok(answer);
    }
}

You can also log more complex objects in DataDog, like a Person with an id, first name, last name and date of birth. The full code for this is in the attached zip in the PersonController.cs file, but it is as simple as –

public ActionResult Post(Person person)
{
    _logger.LogDebug("Received new person: {@person}",person);

And here is what it looks like in DataDog.

A successful request –

And one that generates an exception –

And one that logs a complex object –

Now, there is a huge variety of things you can do within DataDog to search, alert, monitor, etc on the data but I’m not going into that here.

Full source code available here.