Continuing on from last time, let’s see how to return a status message from our ASP.NET Core API endpoints, and by convention, display those results in our application as Bootstrap Alerts!

Just joining us? Check out part 1, where we added a Bootstrap alert message to our view and redirect results. We’ll be building on that solution in this post.

The Problem We’re Solving

100% of web apps I work on these days make calls from JavaScript code to JSON-serving API endpoints. And almost all of them follow this pattern:

service.makeRequest('api/some/uri').then(resp => {
    //Do something interesting with the response..
    
    //... then show a 'success' message!
}).catch(err => {
    //Show an 'error' message!
})

That pattern ends up copy-pasted everywhere in the project. All so we can display some basic feedback to the user:

What if, instead of copy-pasting this pattern everywhere, we could capture this pattern once, and have it apply to all of our API calls?

And what if the exact message we show could be driven server-side by code in our API?

That’s exactly what we’re going to build in this post.

We won’t be using Angular or React or anything like that (yet). We’ll keep it fairly generic, but the approach can be applied to the framework of your choice.

Decorating API Results

The first thing we need to do is decorate our ASP.NET Core API results.

The extension methods we wrote last time already does this just-fine, and we can already write code like this:

public IActionResult Success()
{
    return Ok(DateTime.UtcNow.ToString()).WithSuccess("Success!", "The API call worked!");
}

public IActionResult Error()
{
    return BadRequest().WithDanger("Failed!", "The API didn't like your request...");
}

Unfortunately, the actual implementation of our decorator won’t do what we want. That’s because it uses TempData, and TempData doesn’t make a lot of sense for API responses.

What can we do instead? How can we add additional metadata to our response?

Updating the Decorator

We’ll extend the AlertDecoratorResult that we made last time so that it can handle API and non-API results.

Here’s the new ExecuteResultAsync method:

public async Task ExecuteResultAsync(ActionContext context)
{
    if (Result is StatusCodeResult || Result is OkObjectResult)
    {
        AddAlertMessageToApiResult(context);
    }
    else
    {
        AddAlertMessageToMvcResult(context);
    }

    await Result.ExecuteResultAsync(context);
}

Depending on the type of result, we do one of two things.

For API results:

private void AddAlertMessageToApiResult(ActionContext context)
{
    context.HttpContext.Response.Headers.Add("x-alert-type", Type);
    context.HttpContext.Response.Headers.Add("x-alert-title", Title);
    context.HttpContext.Response.Headers.Add("x-alert-body", Body);
}

We just tack on a few custom response headers that our client-side code will eventually know how to leverage.

For normal/MVC results, we continue to use TempData, just like we did in the first post:

private void AddAlertMessageToMvcResult(ActionContext context)
{
    var factory = context.HttpContext.RequestServices.GetService<ITempDataDictionaryFactory>();

    var tempData = factory.GetTempData(context.HttpContext);
    tempData["_alert.type"] = Type;
    tempData["_alert.title"] = Title;
    tempData["_alert.body"] = Body;
}

Displaying Status Messages Client-Side

That takes care of the server-side code, so let’s take a look at what needs to happen client-side.

Regardless of client-side framework, the high-level approach here is the same:

  1. Register a "global" response handler.
  2. In the handler, check for the custom alert headers.
  3. If the headers are present, extract their values, and display the alert.

We’re going to keep things really simple and use jQuery. I KNOW, I KNOW, no one uses jQuery, I should be using Angular/Vue/React/Aurelia/Next/Watwat. That’s what I’d do in a full-fledged app, and I’ll show that in a future post! Just bear with me for now as we focus on the pattern! ?

Ok, first, we need to define a placeholder where we’ll display our alerts. Over in _StatusMessage.cshtml, we’ll just wrap our old code with a simple div:

<div class="alert-container">
@if (!string.IsNullOrEmpty(type))
{
    //Same code as last time...
}
</div>

At runtime, we’ll grab a reference to that div and append an alert to it dynamically.

Let’s define our global handler, which is done using jQuery’s ajaxComplete. I put mine in script.js, which is part of the standard ASP.NET Core project template.

(function() {
    function showAlert(alert) {
        // Show an alert!
    }

    $(document).ajaxComplete((event, xhr) => {
        // Do something with the response!
    });
})();

Holy cow, that’s an IIFE!! It’s been a while since I’ve had to write one of those…

We’ve just stubbed things out so far, but you can see where we’re going. Let’s go ahead and flesh out our showAlert helper:

    function showAlert(alert) {
        const alertContainer = $('.alert-container');

        const alertElement = $(`<div class="alert alert-${alert.type} alert-dismissible" role="alert">` +
            '<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>' +
            `<strong>${alert.title}</strong> ${alert.body}` +
            '</div>');

        alertContainer.append(alertElement);
        alertElement.alert();
    }

This is fairly simple jQuery 101 here: we grab a reference to our alert container, make a new alert element with the correct class, title, and body, then append it. For good measure, we use Bootstrap’s jQuery alert component to wire up the close button attribute.

Now we can hook up our ajaxComplete handler:

    $(document).ajaxComplete((event, xhr) => {
        if (xhr.getResponseHeader('x-alert-type')) {
            const alert = {
                type: xhr.getResponseHeader('x-alert-type'),
                title: xhr.getResponseHeader('x-alert-title'),
                body: xhr.getResponseHeader('x-alert-body')
            }

            showAlert(alert);
        }
    });

There’s not much going on here: if the response contains an x-alert-type header, we build up an alert, and call showAlert.

The net result:

Wrapping Up

With these changes in place, we should be able to attach an alert to any response we hand back from one of our controllers.

Do note that this isn’t meant to be a replacement for all of your local error (or success!) handling!

Sometimes you’ll need to take some additional actions, like retrying an action. Or maybe attaching validation errors to a form.

But what we don’t have to do anymore is sprinkle our code with boilerplate success and error handling. That sort of thing can (and should) be handled globally!

Full source code for this example is available over at the Github repo.

Coming Soon

In a future post, I’ll show you how to adapt this technique to an Angular application, and how to use a library other than Bootstrap for displaying them. Stay tuned!