Alyio.AspNetCore.ApiMessages 8.0.0

dotnet add package Alyio.AspNetCore.ApiMessages --version 8.0.0
NuGet\Install-Package Alyio.AspNetCore.ApiMessages -Version 8.0.0
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Alyio.AspNetCore.ApiMessages" Version="8.0.0" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Alyio.AspNetCore.ApiMessages --version 8.0.0
#r "nuget: Alyio.AspNetCore.ApiMessages, 8.0.0"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install Alyio.AspNetCore.ApiMessages as a Cake Addin
#addin nuget:?package=Alyio.AspNetCore.ApiMessages&version=8.0.0

// Install Alyio.AspNetCore.ApiMessages as a Cake Tool
#tool nuget:?package=Alyio.AspNetCore.ApiMessages&version=8.0.0

Alyio.AspNetCore.ApiMessages

Build Status

The Alyio.AspNetCore.ApiMessages provides the mechanism to process unhandled exception occured during a HTTP context and writes machine-readable format for specifying errors in HTTP API responses based on rfc7807.

You can throw any exception during a HTTP context if you want, and if the IApiMessage has been implemented by the exception, Alyio.AspNetCore.ApiMessages will produce a consistent response corresponding to it.

dotnet add package Alyio.AspNetCore.ApiMessages

To use Alyio.AspNetCore.ApiMessage, just call app.UseApiMessageHandler in Startup.Configure as below.

#if NET8_0
builder.Services.AddExceptionHandler<InternalServerErrorMessageExceptionHandler>();
#endif

var app = builder.Build();

#if NET8_0
app.UseExceptionHandler("/Error");
#else
app.UseExceptionHandler(new ExceptionHandlerOptions { ExceptionHandler = ExceptionHandler.WriteUnhandledMessageAsync });
#endif
app.UseApiMessageHandler();

// . . .

app.Run();
  • To handle unknown exception during a HTTP context, configure the ExceptionHandlerOptions.ExceptionHandler with Alyio.AspNetCore.ApiMessages.ExceptionHandler.WriteUnhandledMessageAsync.
  • For .NET 8.0, you can also use the IServiceCollection.AddExceptionHandler<T> to handle errors in ASP.NET Core.

NOTE: You can also use the exception filter using MvcOptions.Filters, instead of the middleware as below:

builder.Services
    .AddControllers(options =>
    {
        options.Filters.Add(typeof(ApiMessageFilterAttribute));
        options.Conventions.Add(new RouteTokenTransformerConvention(new SlugifyParameterTransformer()));
    }).ConfigureApiBehaviorOptions(o =>
    {
        // Suppress the default model state validator
        o.SuppressModelStateInvalidFilter = true;
    });

400 Bad Request

Automatic HTTP 400 responses

The [ApiController] attribute makes model validation errors automatically trigger an HTTP 400 response.> Consequently, the following code is unnecessary in an action method:

if (!ModelState.IsValid)
{
    return BadRequest(ModelState);
}

ASP.NET Core MVC uses the ModelStateInvalidFilter action filter to do the preceding check.

. . .

To disable the automatic 400 behavior, set the SuppressModelStateInvalidFilter property to true. Add the following code:

builder.Services.AddControllers()
    .ConfigureApiBehaviorOptions(options =>
    {
        // options.SuppressConsumesConstraintForFormFileParameters = true;
        // options.SuppressInferBindingSourcesForParameters = true;
        options.SuppressModelStateInvalidFilter = true;
        // options.SuppressMapClientErrors = true;
        // options.ClientErrorMapping[StatusCodes.Status404NotFound].Link = "https://httpstatuses.com/404";
    });

Here is an example about how to generate 400 Bad Request messages:

    [HttpPut("{id}")]
    public async Task PutWeatherForecastAsync([FromRoute] int id, [FromBody] WeatherForecast weather)
    {
        if (id != weather.Id)
        {
            // http://localhost:5000/weather-forecast-api-message/10> put -c "{}"
            // HTTP / 1.1 400 Bad Request
            // Cache - Control: no - cache
            // Content - Type: application / problem + json; charset = utf - 8
            // Date: Tue, 24 Oct 2023 08:31:51 GMT
            // Expires: -1
            // Pragma: no - cache
            // Server: Kestrel
            // Transfer - Encoding: chunked
            // 
            // {
            //   "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
            //   "title": "ValidationFailed",
            //   "status": 400,
            //   "traceId": "00-dcb9bbe9d2d19a1c13b1990131d82e39-8f17233259d979ec-00"
            // }
            throw new BadRequestMessage();
        }

        // NOTE: The [ApiController] attribute makes model validation errors automatically trigger an HTTP 400 response. 
        if (!ModelState.IsValid)
        {
            // http://localhost:5000/weather-forecast-api-message/10> put -c "{"id": 10}"
            // HTTP/1.1 400 Bad Request
            // Cache-Control: no-cache
            // Content-Type: application/problem+json; charset=utf-8
            // Date: Tue, 24 Oct 2023 08:33:21 GMT
            // Expires: -1
            // Pragma: no-cache
            // Server: Kestrel
            // Transfer-Encoding: chunked
            // 
            // {
            //   "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
            //   "title": "ValidationFailed",
            //   "status": 400,
            //   "traceId": "00-8c2871328b938115780f42e1c383a41f-3deddb83dfb02b25-00",
            //   "errors": {
            //     "Summary": [
            //       "The Summary field is required."
            //     ]
            //   }
            // }
            throw new BadRequestMessage(ModelState);
        }

        _ = _context.WeatherForecasts ?? throw new NotFoundMessage();

        _context.WeatherForecasts.Update(weather);

        try
        {
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException) when (!WeatherForecastExists(id))
        {
            // http://localhost:5000/weather-forecast-api-message/11> put -c "{"id": 11, "summary": "coool"}"
            // HTTP/1.1 404 Not Found
            // Cache-Control: no-cache
            // Content-Type: application/problem+json; charset=utf-8
            // Date: Tue, 24 Oct 2023 08:36:04 GMT
            // Expires: -1
            // Pragma: no-cache
            // Server: Kestrel
            // Transfer-Encoding: chunked
            // 
            // {
            //   "type": "https://tools.ietf.org/html/rfc7231#section-6.5.4",
            //   "title": "Not Found",
            //   "status": 404,
            //   "traceId": "00-76dc706d91e46ebbb42cfcc34b9f45ab-420dc463c9979a83-00"
            // }
            throw new NotFoundMessage();
        }
    }

401 Unauthorized

    [Route("unauthorized")]
    public void UnauthorizedMessage()
    {
        // http://localhost:5000/weather-forecast-api-message> post unauthorized -c "{}"
        // HTTP/1.1 401 Unauthorized
        // Cache-Control: no-cache
        // Content-Type: application/problem+json; charset=utf-8
        // Date: Tue, 24 Oct 2023 08:46:53 GMT
        // Expires: -1
        // Pragma: no-cache
        // Server: Kestrel
        // Transfer-Encoding: chunked
        // 
        // {
        //   "type": "https://datatracker.ietf.org/doc/html/rfc7235#section-3.1",
        //   "title": "Unauthorized",
        //   "status": 401,
        //   "detail": "Oops, something wrong.",
        //   "traceId": "00-15af5b9926042f4768e5e3146ffc0e79-d5358830ba9105c1-00"
        // }
        throw new UnauthorizedMessage("Oops, something wrong.");
    }

403 Forbidden

    [Route("forbidden")]
    public void ForbiddenMessage()
    {
        // http://localhost:5000/weather-forecast-api-message> get forbidden
        // HTTP/1.1 403 Forbidden
        // Cache-Control: no-cache
        // Content-Type: application/problem+json; charset=utf-8
        // Date: Tue, 24 Oct 2023 08:46:33 GMT
        // Expires: -1
        // Pragma: no-cache
        // Server: Kestrel
        // Transfer-Encoding: chunked
        // 
        // {
        //   "type": "https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.3",
        //   "title": "Forbidden",
        //   "status": 403,
        //   "detail": "Oops, something wrong.",
        //   "traceId": "00-0ac73e5f90fbd604cf5a984afea1c61c-1e4f7eb6bcfb025f-00"
        // }
        throw new ForbiddenMessage("Oops, something wrong.");
    }

201 Created

For 201 Created message,

    [HttpPost]
    public async Task<CreatedMessage> PostWeatherForecastAsync([FromBody] WeatherForecast weather)
    {
        // NOTE: The [ApiController] attribute makes model validation errors automatically trigger an HTTP 400 response. 
        if (!ModelState.IsValid)
        {
            throw new BadRequestMessage(ModelState);
        }

        _ = _context.WeatherForecasts ?? throw new InternalServerErrorMessage("Entity set 'WeatherForecastDbContext.WeatherForecasts'  is null.");

        await _context.WeatherForecasts.AddAsync(weather);
        await _context.SaveChangesAsync();

        // http://localhost:5000/weather-forecast-api-message> post -c "{"summary": "cooooooooooool"}"
        // HTTP/1.1 201 Created
        // Content-Type: application/json; charset=utf-8
        // Date: Tue, 24 Oct 2023 08:38:45 GMT
        // Location: /weather-forecast-api-message/11
        // Server: Kestrel
        // Transfer-Encoding: chunked
        // 
        // {
        //   "id": "11",
        //   "links": [
        //     {
        //       "href": "/weather-forecast-api-message/11",
        //       "rel": "self"
        //     }
        //   ]
        // }
        return this.CreatedMessageAtAction(nameof(GetWeatherForecastAsync), new { id = weather.Id }, weather.Id.ToString())!;
    }

500 Internal Server Error

You can throw any exception during a HTTP context at anytime and anywhere. The ExceptionHandler.WriteUnhandledMessageAsync will intercept the exception and write an a 500 Internal Server Error message.

    [HttpGet("oops")]
    public void Oops()
    {
        // http://localhost:5000/weather-forecast-api-message> get oops
        // HTTP/1.1 500 Internal Server Error
        // Cache-Control: no-store, no-cache
        // Content-Type: application/problem+json; charset=utf-8
        // Date: Tue, 24 Oct 2023 08:45:05 GMT
        // Expires: -1
        // Pragma: no-cache
        // Server: Kestrel
        // Transfer-Encoding: chunked
        // 
        // {
        //   "type": "https://datatracker.ietf.org/doc/html/rfc7231#section-6.6.1",
        //   "title": "Internal Server Error",
        //   "status": 500,
        //   "detail": "System.InvalidOperationException: Oops, something wrong.
        //    at WebApiMessages.Samples.Controllers.WeatherForecastApiMessageController.Oops() . . .",
        //   "exceptionType": "System.InvalidOperationException",
        //   "traceId": "00-9e1f69c7a4935df87f24b569518a66d3-463ec76137670a25-00"
        // }
        throw new InvalidOperationException("Oops, something wrong.");
    }

Samples

You can also run the sample at test/Samples/WebApiMessages.Samples/:

$ dotnet run
Building...
. . .
info: Microsoft.EntityFrameworkCore.Update[30100]
      Saved 10 entities to in-memory store.
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://localhost:5000
. . .

And open another terminal, run httprepl:

To install the HttpRepl, run the following command:

dotnet tool install -g Microsoft.dotnet-httprepl

$ httprepl http://localhost:5000
(Disconnected)> connect http://localhost:5000
Using a base address of http://localhost:5000/
Using OpenAPI description at http://localhost:5000/swagger/v1/swagger.json
For detailed tool info, see https://aka.ms/http-repl-doc

http://localhost:5000/> ls
.                              []
weather-forecast               [GET|POST]
weather-forecast-api-message   [GET|POST]

http://localhost:5000/> cd weather-forecast-api-message
/weather-forecast-api-message    [GET|POST]

http://localhost:5000/weather-forecast-api-message> get 11
HTTP/1.1 404 Not Found
Cache-Control: no-cache
Content-Type: application/problem+json; charset=utf-8
Date: Tue, 24 Oct 2023 09:10:05 GMT
Expires: -1
Pragma: no-cache
Server: Kestrel
Transfer-Encoding: chunked

{
  "type": "https://tools.ietf.org/html/rfc7231#section-6.5.4",
  "title": "Not Found",
  "status": 404,
  "traceId": "00-b3e64c172a13c1f90edca65413707114-33402b26d1fe8050-00"
}


http://localhost:5000/weather-forecast-api-message> post -c "{}"
HTTP/1.1 400 Bad Request
Cache-Control: no-cache
Content-Type: application/problem+json; charset=utf-8
Date: Tue, 24 Oct 2023 09:10:24 GMT
Expires: -1
Pragma: no-cache
Server: Kestrel
Transfer-Encoding: chunked

{
  "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
  "title": "ValidationFailed",
  "status": 400,
  "traceId": "00-a3a20de07e19661eaa279e12af433abb-14632dd52e784606-00",
  "errors": {
    "Summary": [
      "The Summary field is required."
    ]
  }
}


http://localhost:5000/weather-forecast-api-message> post -c "{"summary": "coooool"}"
HTTP/1.1 201 Created
Content-Type: application/json; charset=utf-8
Date: Tue, 24 Oct 2023 09:10:37 GMT
Location: /weather-forecast-api-message/11
Server: Kestrel
Transfer-Encoding: chunked

{
  "id": "11",
  "links": [
    {
      "href": "/weather-forecast-api-message/11",
      "rel": "self"
    }
  ]
}


http://localhost:5000/weather-forecast-api-message> get 11
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Tue, 24 Oct 2023 09:10:51 GMT
Server: Kestrel
Transfer-Encoding: chunked

{
  "id": 11,
  "date": null,
  "temperatureC": 0,
  "temperatureF": 32,
  "summary": "coooool"
}


http://localhost:5000/weather-forecast-api-message> delete 12
HTTP/1.1 404 Not Found
Cache-Control: no-cache
Content-Type: application/problem+json; charset=utf-8
Date: Tue, 24 Oct 2023 09:11:03 GMT
Expires: -1
Pragma: no-cache
Server: Kestrel
Transfer-Encoding: chunked

{
  "type": "https://tools.ietf.org/html/rfc7231#section-6.5.4",
  "title": "Not Found",
  "status": 404,
  "traceId": "00-4bee59139386ee3ed6c4d9eecce23e34-5c0e3bf4433e921e-00"
}

Product Compatible and additional computed target framework versions.
.NET net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 is compatible.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net6.0

    • No dependencies.
  • net7.0

    • No dependencies.
  • net8.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
8.0.0 574 11/24/2023
7.2.0 573 10/24/2023
7.1.0 498 10/20/2023
7.0.3 996 6/8/2022
7.0.1 938 6/4/2022
2.1.0 2,072 11/10/2018
2.0.2 2,746 1/11/2018
2.0.0 1,917 8/22/2017
1.1.3 1,788 8/11/2017
1.1.2 1,791 8/4/2017
1.1.1 1,755 7/18/2017
1.1.0 1,665 6/16/2017
1.0.6 1,663 5/5/2017
1.0.5 1,635 5/4/2017
1.0.4 1,791 4/11/2017
1.0.2 2,220 12/26/2016
1.0.1 2,082 12/19/2016