Alyio.AspNetCore.ApiMessages 7.2.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package Alyio.AspNetCore.ApiMessages --version 7.2.0                
NuGet\Install-Package Alyio.AspNetCore.ApiMessages -Version 7.2.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="7.2.0" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Alyio.AspNetCore.ApiMessages --version 7.2.0                
#r "nuget: Alyio.AspNetCore.ApiMessages, 7.2.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=7.2.0

// Install Alyio.AspNetCore.ApiMessages as a Cake Tool
#tool nuget:?package=Alyio.AspNetCore.ApiMessages&version=7.2.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 https://tools.ietf.org/html/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 --version 7.2.0

To use Alyio.AspNetCore.ApiMessage, just call app.UseApiMessageHandler in Startup.Configure as below. To handle unknown exception during a HTTP context, configure the ExceptionHandlerOptions.ExceptionHandler with Alyio.AspNetCore.ApiMessages.ExceptionHandler.WriteUnhandledMessageAsync.

// . . .

var builder = WebApplication.CreateBuilder(args);

// . . .

var app = builder.Build();

//if (app.Environment.IsDevelopment())
//{
//    app.UseDeveloperExceptionPage();
//}
//else
//{
app.UseExceptionHandler(new ExceptionHandlerOptions { ExceptionHandler = ExceptionHandler.WriteUnhandledMessageAsync });
app.UseApiMessageHandler();
//}

// . . .

app.Run();

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 highlighted 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 net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  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 was computed.  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 was computed.  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. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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 619 11/24/2023
7.2.0 605 10/24/2023
7.1.0 525 10/20/2023
7.0.3 1,024 6/8/2022
7.0.1 970 6/4/2022
2.1.0 2,104 11/10/2018
2.0.2 2,792 1/11/2018
2.0.0 1,967 8/22/2017
1.1.3 1,815 8/11/2017
1.1.2 1,837 8/4/2017
1.1.1 1,786 7/18/2017
1.1.0 1,691 6/16/2017
1.0.6 1,688 5/5/2017
1.0.5 1,663 5/4/2017
1.0.4 1,833 4/11/2017
1.0.2 2,254 12/26/2016
1.0.1 2,113 12/19/2016

Trim the 'Async' suffix of action name for async methods.