Code Monkey home page Code Monkey logo

middleware's Introduction

Middleware

Various ASP.NET Core middleware. Mostly for use in APIs.

ProblemDetails NuGet

Install-Package Hellang.Middleware.ProblemDetails

Configure in Startup class. See examples:

Other packages that integrate with ProblemDetails middleware:

Logo

corrupted file by Rflor from the Noun Project

SpaFallback NuGet

Install-Package Hellang.Middleware.SpaFallback

The SpaFallback middleware is designed to make your client-side SPA routing work seamlessly with your server-side routing.

When a request for a client-side route hits the server, chances are there's no middleware that will handle it. This means it will reach the end of the pipeline, the response status code will be set to 404 and the response will bubble back up the pipeline and be returned to the client. This is probably not what you want to happen.

Whenever a request can't be handled on the server, you usually want to fall back to your SPA and delegate the routing to the client. This is what the SpaFallback middleware enables.

The middleware works by passing all requests through the pipeline and let other middleware try to handle it. When the response comes back, it will perform a series of checks (outlined below) and optionally re-execute the pipeline, using the configured fallback path. This defaults to /index.html. This should bootstrap your SPA and let the client-side routing take over.

The following rules are verified before a fallback occurs:

  1. The method is GET.
  2. The status code is 404.
  3. The response hasn't started yet.
  4. The requested path does not have a file extension.
  5. The request actually reached the end of the pipeline.

The middleware tries to be as smart as possible when determining whether a fallback should happen or not:

If the request path has a file extension, i.e. /public/image.png, the client probably wanted an actual file (typically served by StaticFiles), but it was missing from disk, so we let the 404 response through to the client.

In addition, we check that the response wasn't handled by other middleware (but still ended up with a 404 status code). This is useful if you want to prevent disclosing the existence of a resource that the client don't have access to. In order to achieve this, AddSpaFallback will automatically inject a "marker middleware" at the end of the pipeline. If the request reaches this middleware, it will set the response status code to 404 and add a tag to the HttpContext.Items dictionary. This tag is then checked in the fallback middleware to verify a "hard" 404.

Usage

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddSpaFallback();
        services.AddMvc();
    }

    public void Configure(IApplicationBuilder app)
    {
        app.UseSpaFallback();
        app.UseStaticFiles();
        app.UseMvc();
    }
}

Authentication.JwtBearer.Google NuGet

Install-Package Hellang.Authentication.JwtBearer.Google

Makes it straight-forward to hook up authentication with Google identity tokens, using Microsoft's existing Microsoft.AspNetCore.Authentication.JwtBearer for parsing and validating the tokens.

Usage

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(x => x.UseGoogle(
        clientId: "<google-client-id>",
        hostedDomain: "<optional-g-suite-domain>"));

middleware's People

Contributors

angularsen avatar bopazyn avatar christianacca avatar dependabot-support avatar dependabot[bot] avatar dglozano avatar fernetowac avatar janrucka avatar jonassamuelsson avatar khellang avatar lonix1 avatar mabead avatar maxsenft avatar perbrage avatar shatl avatar tibitoth avatar timothymakkison avatar tu-ob-gw avatar yaevh avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

middleware's Issues

Improve rethrow logic

I am thinking of introducing two new methods for ProblemDetailsOptions class:

  • Rethrow<T>() where T:Exception
  • Rethrow<T>(Func<HttpContext, Boolean> predicate) where T:Exception

The logic would run before ShouldRethrowException.

IncludeExceptionDetails prints path "C:\Users\khe1\..."

Hi there! First of all I would like to thank you for this amazing package :)

Not sure if this is expected behavior, but with IncludeExceptionDetails, there's a few of: "C:\\Users\\khe1\\Code\\Middleware\\src\\ProblemDetails\\ProblemDetailsMiddleware.cs"

Full response (redacted sensitive info with [...]):

{
    "errors": [
        {
            "message": "External component has thrown an exception.",
            "type": "System.Runtime.InteropServices.ExternalException",
            "raw": " [...]  at Hellang.Middleware.ProblemDetails.ProblemDetailsMiddleware.Invoke(HttpContext context) in C:\\Users\\khe1\\Code\\Middleware\\src\\ProblemDetails\\ProblemDetailsMiddleware.cs:line 52",
            "stackFrames": [
                {"filePath":"C:\\Users\\MyUser\\[...]\\Controllers\\ErrorController.cs","fileName":"ErrorController.cs","function":"[...].ErrorController.GetException()","line":50,"preContextLine":44,"preContextCode":["        public IActionResult Get500() => StatusCode(500,","            new ExceptionProblemDetails(new ExternalException(\"Internal Exception Message\"), 500));","","        ///  Produz erro 500 - Internal Server Error ","        [HttpGet(\"Exception\")]","        [ProducesResponseType(StatusCodes.Status500InternalServerError)]"],"contextCode":["        public IActionResult GetException() => throw new ExternalException();"],"postContextCode":["    }","}"]},
                {"filePath":null,"fileName":null,"function":"lambda_method(Closure , object , object[] )","line":null,"preContextLine":null,"preContextCode":null,"contextCode":null,"postContextCode":null},
                {"filePath":null,"fileName":null,"function":"Microsoft.Extensions.Internal.ObjectMethodExecutor.Execute(object target, object[] parameters)","line":null,"preContextLine":null,"preContextCode":null,"contextCode":null,"postContextCode":null},
                {"filePath":null,"fileName":null,"function":"Microsoft.AspNetCore.Mvc.Internal.ActionMethodExecutor+SyncActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, object controller, object[] arguments)","line":null,"preContextLine":null,"preContextCode":null,"contextCode":null,"postContextCode":null},
                {"filePath":null,"fileName":null,"function":"Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeActionMethodAsync()","line":null,"preContextLine":null,"preContextCode":null,"contextCode":null,"postContextCode":null},
                {"filePath":null,"fileName":null,"function":"Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeNextActionFilterAsync()","line":null,"preContextLine":null,"preContextCode":null,"contextCode":null,"postContextCode":null},
                {"filePath":null,"fileName":null,"function":"Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Rethrow(ActionExecutedContext context)","line":null,"preContextLine":null,"preContextCode":null,"contextCode":null,"postContextCode":null},
                {"filePath":null,"fileName":null,"function":"Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted)","line":null,"preContextLine":null,"preContextCode":null,"contextCode":null,"postContextCode":null},
                {"filePath":null,"fileName":null,"function":"Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeInnerFilterAsync()","line":null,"preContextLine":null,"preContextCode":null,"contextCode":null,"postContextCode":null},
                {"filePath":null,"fileName":null,"function":"Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextResourceFilter()","line":null,"preContextLine":null,"preContextCode":null,"contextCode":null,"postContextCode":null},
                {"filePath":null,"fileName":null,"function":"Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow(ResourceExecutedContext context)","line":null,"preContextLine":null,"preContextCode":null,"contextCode":null,"postContextCode":null},
                {"filePath":null,"fileName":null,"function":"Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted)","line":null,"preContextLine":null,"preContextCode":null,"contextCode":null,"postContextCode":null},
                {"filePath":null,"fileName":null,"function":"Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeFilterPipelineAsync()","line":null,"preContextLine":null,"preContextCode":null,"contextCode":null,"postContextCode":null},
                {"filePath":null,"fileName":null,"function":"Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeAsync()","line":null,"preContextLine":null,"preContextCode":null,"contextCode":null,"postContextCode":null},
                {"filePath":null,"fileName":null,"function":"Microsoft.AspNetCore.Routing.EndpointMiddleware.Invoke(HttpContext httpContext)","line":null,"preContextLine":null,"preContextCode":null,"contextCode":null,"postContextCode":null},
                {"filePath":null,"fileName":null,"function":"Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware.Invoke(HttpContext httpContext)","line":null,"preContextLine":null,"preContextCode":null,"contextCode":null,"postContextCode":null},
                {"filePath":null,"fileName":null,"function":"Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)","line":null,"preContextLine":null,"preContextCode":null,"contextCode":null,"postContextCode":null},
                {"filePath":null,"fileName":null,"function":"Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware.Invoke(HttpContext context)","line":null,"preContextLine":null,"preContextCode":null,"contextCode":null,"postContextCode":null},
                {"filePath":null,"fileName":null,"function":"Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)","line":null,"preContextLine":null,"preContextCode":null,"contextCode":null,"postContextCode":null},
                {"filePath":null,"fileName":null,"function":"Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)","line":null,"preContextLine":null,"preContextCode":null,"contextCode":null,"postContextCode":null},

                {"filePath":"C:\\Users\\khe1\\Code\\Middleware\\src\\ProblemDetails\\ProblemDetailsMiddleware.cs","fileName":"ProblemDetailsMiddleware.cs","function":"Hellang.Middleware.ProblemDetails.ProblemDetailsMiddleware.Invoke(HttpContext context)","line":52,"preContextLine":null,"preContextCode":null,"contextCode":null,"postContextCode":null}
            ]
        }
    ],
    "type": "https://httpstatuses.com/500",
    "title": "Internal Server Error",
    "status": 500,
    "detail": "External component has thrown an exception.",
    "traceId": "0HLKUGQA6V1TJ:00000003"
}

ProblemDetails middleware when Response has started

Hi @khellang

I have one scenario that I would like to support with ProblemDetails. I usually create a unit of work middleware to make request transactional and consistent:

public class UnitOfWorkMiddleware
{
    private readonly RequestDelegate next;

    public UnitOfWorkMiddleware(RequestDelegate next)
    {
        this.next = next;
    }

    public async Task Invoke(HttpContext context)
    {
        await next(context);
        var requestMethod = context.Request.Method;
        var isSafeMethod = requestMethod == HttpMethods.Get || requestMethod == HttpMethods.Head;
        var dbContext = context.RequestServices.GetService(typeof(EFDbContext)) as EFDbContext;
        if (IsSuccessStatusCode(context.Response) && !isSafeMethod)
        {
            await dbContext.SaveChangesAsync();
        }
    }

    private bool IsSuccessStatusCode(HttpResponse response)
    {
        return response.StatusCode >= 200 && response.StatusCode <= 299;
    }
}

In case that SaveChanges() fail, problems details detect the exception but the response has started and rethrow the exception, but in this case I would like to continue using Problem Details.

One question related with this line

if (context.Response.HasStarted)

Why check if response has been started and not catch all exceptions? Can you tell me some scenario when you have to re throw the exception and not send a problems details to the client?

Actually I don't know to manage my scenario in the right way.

Regards!

ValidationErrors with ProblemDetails

Hi!

Hopefully the right place to ask for advice. I added ProblemDetails Nuget and thought I configured it correctly. Apparently not and it also appears I don't see the problem myself.

The problem is that I can't get the validation errors come through properly.

So, after adding the library I configure it what I think is fairly normal way and add by just copying the example piece of code from the repository. I also add options.OnBeforeWriteDetails = (ctx, details) => to change some details. The configuration happens at https://github.com/veikkoeeva/orleans/blob/8a44d947660ebc0d3579d175736946b8e113e3be/Samples/OneBoxDeployment/src/OneBoxDeployment.Api/Startup.cs#L387.

Though when I start the service and once it has been opened to the Swagger page and I call https://github.com/veikkoeeva/orleans/blob/update-oneboxdeployment-to-core3/Samples/OneBoxDeployment/src/OneBoxDeployment.Api/Controllers/BigSampleController.cs#L55 with int changed to string to get a validation error, the lambda isn't called, and the details haven't been changed. And I don't know what gives!

I have also a test that checks some of the contents at https://github.com/veikkoeeva/orleans/blob/update-oneboxdeployment-to-core3/Samples/OneBoxDeployment/test/OneBoxDeployment.IntegrationTests/Tests/FaultInjectionTests.cs#L50.

I also noticed that if [Produces("application/json", "application/problem+json")] is provided on the controller level, the return content-type will application/json whereas if nothing is given, it will be application/problem+json. Is this on purpose? See at https://github.com/veikkoeeva/orleans/blob/update-oneboxdeployment-to-core3/Samples/OneBoxDeployment/src/OneBoxDeployment.Api/Controllers/BigSampleController.cs#L18.

I'm updating the sample to Orleans repo, credit naturally given to those with advice (or commits). :)

WithExceptionDetails feature request

Hi there!
Thanks for this helpful package!
I have integrated it into my project but there are some behaviour that are not exactly as expected for me.

I was looking for something that you did in branch aspnetcore 2.2:

ex.WithExceptionDetails(details)

but more generic, for all kind of problem details, not only ExceptionProblemDetails.

The point is, if I want to map the exception to a custom class, I will not have the details added to that..

    private MvcProblemDetails GetDetails(HttpContext context, Exception error)
    {
        var statusCode = context.Response.StatusCode;

        if (error == null)
        {
            return Options.MapStatusCode(context, statusCode);
        }

        var result = GetProblemDetails(context, error);

        // We don't want to leak exception details unless it's configured,
        // even if the user mapped the exception into ExceptionProblemDetails.
        if (result is ExceptionProblemDetails ex)  // <------------------------------ Here
        {
            if (Options.IncludeExceptionDetails(context))
            {
                try
                {
                    var details = DetailsProvider.GetDetails(ex.Error);
                    return ex.WithExceptionDetails(details);
                }
                catch (Exception e)
                {
                    // Failed to get exception details for the specific exception.
                    // Fall back to generic status code problem details below.
                    Logger.ProblemDetailsMiddlewareException(e);
                }
            }

            return Options.MapStatusCode(context, ex.Status ?? statusCode);
        }

        return result;
    }

In practice, when I use a custom ProblemDetailsException, there is no Stack Trace added to that..

private MvcProblemDetails GetProblemDetails(HttpContext context, Exception error)
{
	if (error is ProblemDetailsException problem)
	{
		// The user has already provided a valid problem details object.
		return problem.Details;
	}

	if (Options.TryMapProblemDetails(context, error, out var result))
	{
		// The user has set up a mapping for the specific exception type.
		return result;
	}

	// Fall back to the generic exception problem details.
	return new ExceptionProblemDetails(error);
}

Do you accept PR on aspnetcore Branch?
Or is it possible to have a similar feature, extended for all problem details, also on main branch?

Thanks!

Can't pass through Detail field on mapped exception in Production

I have a Web API in which I'm trying to pass a somewhat more detailed message in production to the caller (so they can relay to the user) when an attempt to email to a user fails. In this particular request, the email send must be successful for the API operation to successfully complete (i.e. don't generate and change user's password if we can't email them the new password). When the email fails, I need to tell the caller that we can't generate the password and let them know to check the email config rather than just what it does currently which is:

{
    "type": "https://httpstatuses.com/400",
    "title": "Bad Request",
    "status": 400
}

I have a map set up as follows, where the exception passed has the message I want to convey:

// This will map ArgumentException to the 400 BadRequest status code.
options.Map<ArgumentException>(ex => new ExceptionProblemDetails(ex, StatusCodes.Status400BadRequest) { Detail = ex.Message });

I'm expecting that Detail will now contain the message I've passed "Could not send email - check email server configuration"

But the response still does not include the Detail field. Only if I set options.IncludeExceptionDetails to true do I see it, but then of course we are sending all the other exception information that we don't want to leak.

This is what I am trying to achieve:

{
    "type": "https://httpstatuses.com/400",
    "title": "Bad Request",
    "detail": "Could not send email - check email server configuration",
    "status": 400
}

Is there a clean way to do this?

ProblemDetails: support derived types in Map<TException>

It's not currently possible to create a map for an exception type and all its derived types.

options.Map<Exception>(e => /* only runs if typeof(e) is exactly Exception */);

This means that you can't map an abstract exception class. You can map each individual derived exception type, but you can't pre-configure maps for exception types that haven't been invented yet.

Consider adding a flag to enable mapping derived types.

public void Map<TException>(Func<TException, MvcProblemDetails> mapping, bool includeDerived = false) where TException : Exception

Use ILoggerFactory and create Logger with current action name

Currently the ILogger category is the name of the Middleware.
Can it be changed so that the CategoryName is set to be the Action of the initial request?
E.g.:
var logger = loggerFactory.CreateLogger(httpContext.ActionDescriptor.DisplayName);

Can't build Master or Core 2.2 branch

During package restore I receive the following error:

The package Microsoft.AspNetCore.Authentication.Abstractions.2.2.0 sha512 validation failed. The package is different than the last restore. ProblemDetails ProblemDetails.csproj

I have been unable to track it down to successfully build.

ProblemDetails in 2.2

ASP.NET Core 2.2 has ProblemDetails support built in and turned on by default if you use the API Controller attribute. What does this project provide if anything over and above the new built-in features?

AddNewtonsoftJson and ProblemDetails, .Net Core 3

I am having problems using Newtonsoft as the json serializer (using AddNewtonsoftJson in startup) with problem details.
When this package returns the problem details the Extensions property is serialzied as "Extensions": {...} and not to the parent.
If I in a controller use return Problem(detail: "test"); the Extensions property is serialized correctly.

The Microsoft.AspNetCore.Mvc.ProblemDetails has a JsonExtensionDataAttribute on the property, but since it is .Net Core 3.0 it is in the namespace System.Text.Json.Serialization.

This issue might not be specific for this package but I need to start somewhere.

Throw ProblemDetailsException in client when server responds with application/problem+json

I use your library to automatically return a application/problem+json responses from my services when something goes wrong. The next thing that I would like to do is to be able to call this service (the server) from another remote piece of code (the client) using an HttpClient + AutoRest generated client. So when the server responds with a application/problem+json response, I would like my the call made on the client to throw a ProblemDetailsException.

Is this something that you have in your library or something that you intend to implement? I quickly looked in the code but I couldn't find anything.

I think that this is typically done using a DelegatingHandler that can be provided to AutoRest.

map my custom exception to ExceptionProblemDetails

Hi dears,
at first, I read your sample in the GitHub, run it, and fine worked.

but in my real application, I have a Custom exception class that used in the domain layer. this class derived from System.Exception. and has multiple extra property(errorCode, errorMsg, StatusCode).

now I want to map my exception class to ExceptionProblemDetails class when configure the problem detail in ConfigureServices() in the startup.

I know also you map with this code:

options.Map<MyCustomException>(ex => new ExceptionProblemDetails(ex, (int)ex.HttpStatusCode));

can I map my properties to the property of the problem detail class before write response by the ProblemDetailsMiddleware. for example(mapping):

new ProblemDetails()
{
Title = myCustomException.Code.ToString(),
Detail = myCustomException.Message,
Status = (int?)myCustomException.HttpStatusCode
})

thanks.

ProblemDetails: make httpstatuses problem types optional

I just installed the ProblemDetails middleware, which was really easy by the way, and this is my first feedback.

The first thing I tried after installation was doing a GET for a non-existent API which would normally result in a 404 and an empty body. Instead I got a 404 and a problem detail document. Great!

{
    "type": "https://httpstatuses.com/404",
    "title": "Not Found",
    "status": 404,
    "detail": null,
    "instance": null
}

What I didn't expect was the problem URI that links to httpstatuses.com. I checked the RFC and it seems to agree with me that you shouldn't have a custom problem URI for generic errors.

(...) truly generic problems -- i.e., conditions that could
potentially apply to any resource on the Web -- are usually better
expressed as plain status codes. For example, a "write access
disallowed" problem is probably unnecessary, since a 403 Forbidden
status code in response to a PUT request is self-explanatory.

https://tools.ietf.org/html/rfc7807#section-4

This specification reserves the use of one URI as a problem type:

The "about:blank" URI [RFC6694], when used as a problem type,
indicates that the problem has no additional semantics beyond that of
the HTTP status code.

https://tools.ietf.org/html/rfc7807#section-4.2

So my recommendation is to just leave off the problem type and let it default to about:blank for generic errors. At least make it configurable using ProblemDetailsOptions.

{
    "title": "Not Found",
    "status": 404,
    "detail": null,
    "instance": null
}

Content-Type for BadRequest with ProblemDetails-object

Hi and thanks for your middlware for ProblemDetails! ๐Ÿ‘

This is more a question and not an issue (?):

When you return BadRequest with an object that is or derives from ProblemDetails the Content-Type will be application/json.

var problem = new OutOfCreditProblemDetails
{
     ...
};

return BadRequest(problem);

But when you return BadRequest without any object the Content-Type will be application/problem+json.

return BadRequest();

Is that by design?

Regards
Niklas

ProblemDetails middleware ignore errors from ActionFilterAttribute

Hi,

I usually try to manage model validation in an ActionFilterAttribute:

public class ValidateModelStateFilter : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        if (context.ModelState.IsValid)
        {
            return;
        }

        var validation = new ValidationProblemDetails(context.ModelState);

        context.Result = new BadRequestObjectResult(validation);
    }
}

But ProblemDetails middleware ignore errors from this ActionFilterAttribute:

error

I understand the first two conditionals in the method, but not the logic that decides the last:

if (context.Response.ContentLength.HasValue)
{
    return false;
}

if (string.IsNullOrEmpty(context.Response.ContentType))
{
    return true;
}

Regards!

log just Message Exception

Hi Dear,
about problem Detail Middle-ware;
when throwed Unhandled exception, logs all stack trace of the exception in my log file.
I want to log just the message of my exception. can I do that?

thanks

Middleware not working for 400 BadRequest

simply
dotnet new webapi
Add middleware as describved in readme
[HttpGet] public IActionResult Get() { return BadRequest("wadwadwd"); }

Does not return a ProblemDetail response. First frsutrated because I thought middleware doesn't support 400s, but it seems that's just broken now..

Btw:
options.IsProblem = context => { return context.Response.StatusCode >= 400; };

doesn't fix it..

Include details in ProblemDetailsException

As shown here, the ProblemDetailsException does not initialize the Exception.Message and does not overwrite Exception.ToString(). As a result, it is very hard to log these exceptions in a generic way.

For example, we have a generic middleware that logs all exception. It simply logs them using the standard ASP.NET Core logger:

_logger.LogDebug(ex, $"Unexpected exception: {ex.Message});

Unfortunately, for ProblemDetailsException it only prints this:

Unexpected exctpion: Exception of type 'Hellang.Middleware.ProblemDetails.ProblemDetailsException' was thrown.`

And we don't have any details.

What do you think about initializing the Message property using the type and title of the problem details and including all the details in the ToString() function? Would you be willing to accept a PR that does that?

Problem details middleware to aggressive with exception mapping

When mapping exceptions and configuring additional problem details using an approach similar to the following:

problemOptions.Map<MyException>(ex => new ExceptionProblemDetails(ex, StatusCodes.Status400BadRequest)
{
    Detail = ex.Message,
    Instance = "http://example.com/foo/..."
});

the aggressive handling by the middleware removes the opportunity to at least provide the contextual data, not just the exception details. The default handler then writes a bare status code problem details.

// We don't want to leak exception details unless it's configured,
// even if the user mapped the exception into ExceptionProblemDetails.
if (result is ExceptionProblemDetails ex)
{
if (Options.IncludeExceptionDetails(context))
{
try
{
var details = DetailsProvider.GetDetails(ex.Error);
return new DeveloperProblemDetails(ex, details);
}
catch (Exception e)
{
// Failed to get exception details for the specific exception.
// Fall back to generic status code problem details below.
Logger.ProblemDetailsMiddlewareException(e);
}
}
return Options.MapStatusCode(context, ex.Status ?? statusCode);
}

It would be far more convenient to facilitate this in order to remove the boilerplate necessary to catch the exceptions and rethrow/return a problem details object at the call site.

unable to find package Microsoft.DiaSymReader.Pdb2Pdb on local build

When I build locally I get an error saying Unable to find package Microsoft.DiaSymReader.Pdb2Pdb.
The build is still reported as successful & I can run all test but something must be wrong. Does anyone else see the same thing or is it just me. I get it on two different machines.

StatusCodeValidationProblemDetails

In a project I'm working on I introduced the following a class similar to the StatusCodeProblemDetails but then inheriting from ValidationProblemDetails.

public class StatusCodeValidationProblemDetails : ValidationProblemDetails
{
	public StatusCodeValidationProblemDetails(int statusCode)
	{
		Status = statusCode;
		Type = $"https://httpstatuses.com/{statusCode}";
		Title = ReasonPhrases.GetReasonPhrase(statusCode);
	}
}

The reason I did this had to do with the return type of a BadRequest. On the actions of my controllers where I return a BadRequest I set the following attribute.

[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(ValidationProblemDetails))]

This helps the NSwag/Swagger/Open API 3 generation in creating the correct specification.

But sometimes additional validation is performed in the controller/action code after the model validation. It is possible to return a BadRequest at that stage in the code.
To produce a correct ProblemDetail it should be based on ValidationProblemDetails and not on ProblemDetails.

But when I do that I can no longer use StatusCodeProblemDetails, which has the nice feature of filling in Status, Type & Title.

Maybe others face the same issue and would it be nice to have the StatusCodeValidationProblemDetails class part of this library.

Additionally I create my custom ProblemDetails so that I can extra properties for this specific action. I than change the attribute to:

[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(MyCustomProblemDetails))]

The MyCustomProblemDetails class inherits from ValidationProblemDetails because that's the only way that my OpenAPI 3 spec is still correct when the ASP.NET Core model validation kicks in and returns a ValidationProblemDetails.
To make it myself easier the MyCustomProblemDetails inherits from StatusCodeValidationProblemDetails so that Status, Type & Title are always set in the same manner.

Prevent conversion of 401 "Challenge" response to ProblemDetails

Hi

I was running into some issues with the "ProblemDetailsMiddleware", since it converts 401 Challenge respones into a ProblemDetails responses and thereby removing the "WWW-Authenticate" header in the process.

I have solved it by adding the following check to the "IsProblem" method in "ProblemDetailsMiddleware.cs":
if (context.Response.Headers.ContainsKey("WWW-Authenticate")) { return false; }

Would you be open to implement this, or maybe make some kind of option available to extend the "IsProblem" check?

ProblemDetailsMiddleware not working at all with CompatibilityVersion.Version_2_2

Hi!

I've found a bug? with compatibility set to version 2.2
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
I have 2.2.6 installed.

Response is started before Invoke method from ProblemDetailsMiddleware

public async Task Invoke(HttpContext context)
{
    try
    {
        await Next(context);

        if (Options.IsProblem(context))
        {
            if (context.Response.HasStarted)
            {
                Logger.ResponseStarted();
                return;
            }
            
            ClearResponse(context, context.Response.StatusCode);

            var details = GetDetails(context, error: null);

            await WriteProblemDetails(context, details);
        }
    }
    // ...
}

IsProblem also always resturns false because ContentType is already set (what is funny to application/problem+json)

private static bool IsProblem(HttpContext context)
{
    if (context.Response.StatusCode < 400)
    {
        return false;
    }

    if (context.Response.StatusCode >= 600)
    {
        return false;
    }

    if (context.Response.ContentLength.HasValue)
    {
        return false;
    }

    if (string.IsNullOrEmpty(context.Response.ContentType))
    {
        return true;
    }

    return false;
}

Maybe for 2.2 there is no need to set headers etc. but only improve returned by framework:

{
  "type": "about:blank",
  "status": 500
}

to than what was befor:

{
  "type": "https://httpstatuses.com/500",
  "title": "Internal Server Error",
  "status": 500,
  "traceId": "0HLOHLQQH7O9E:00000002"
}

Best regards,
Mateusz

Exception in DeveloperProblemDetails.GetErrors() while executing the problem details middleware

I sometimes get the following exception in my server log:

An exception was thrown attempting to execute the problem details middleware.
System.BadImageFormatException: Invalid directory size.
   bei System.Reflection.PortableExecutable.PEReader.ReadDebugDirectory()
   bei Microsoft.Extensions.StackTrace.Sources.PortablePdbReader.GetPdbPath(String assemblyPath)
   bei Microsoft.Extensions.StackTrace.Sources.PortablePdbReader.GetMetadataReader(String assemblyPath)
   bei Microsoft.Extensions.StackTrace.Sources.PortablePdbReader.PopulateStackFrame(StackFrameInfo frameInfo, MethodBase method, Int32 IlOffset)
   bei Microsoft.Extensions.StackTrace.Sources.StackTraceHelper.GetFrames(Exception exception)
   bei Microsoft.Extensions.StackTrace.Sources.ExceptionDetailsProvider.<GetDetails>d__3.MoveNext()
   bei Hellang.Middleware.ProblemDetails.DeveloperProblemDetails.<GetErrors>d__4.MoveNext()
   bei System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
   bei System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
   bei Hellang.Middleware.ProblemDetails.DeveloperProblemDetails..ctor(ExceptionProblemDetails problem, IEnumerable`1 details)
   bei Hellang.Middleware.ProblemDetails.ProblemDetailsMiddleware.GetDetails(HttpContext context, Exception error)

I'm not sure how to reproduce it, but from reading the stack trace I have an idea what might happen:

The top methods of the exception call stack indicate that it crashes while reading a PDB file. The problem I see here is that that class name indicates that it tries to read a portable PDB. But my application is running ASP.NET Core 2.2 on the full .NET Framework 4.7.1 since some used libraries are not available as .NET Core or .NET Standard builds. Those libraries also use non-portable PDB files. So I assume the crash happens because it tries to read a non-portable PDB file assuming it is a portable one.

I'm using ProblemDetails package version 3.1.0.

Changelog/Release Notes?

When I update NuGets i go through each package and i go to GitHub and try to find a changelog or release notes.

This way, I can see if the package updates will break my code, or if a feature has been introduced that I can take advantage of to simplify my code.

Going through the commits takes a ton of time. Is there a Changelog.md? Why are there no notes on the releases?

Refresh Token

Hi,
I have been using your library for validating token issued by Google Authentication and it is been working great. However, the token seems to expire pretty soon, probably one hour. I am using Aspnetcore API with Angular.

How should I refresh the token? Is this supposed to be done at the client side or server side? Any pointers in the right direction would help.

Thanks,

ProblemDetails and Rethrow Exception

Is it possible to rethrow handled Exception?

Imagine having this middlewares structure:

 public void Configure(IApplicationBuilder app)
 {
    app.UseAllElasticApm(Configuration);
    app.UseProblemDetails();
    app.UseMvc();
}

Can ProblemDetailsMiddlware transform response into RFC 7807 standard and also rethrow exception that ElasticApmMiddleware could handle it?

Add TraceId to ProblemDetails

On StackOverflow I found the following topic about ProblemDetails, before finding this solution.

What I miss in this solution is the traceId. I think it would be nice addition.

"No output formatter was found for content type '' to write the response."

WriteProblemDetails never updates the context.Response.ContentType so if you use .AddMvcOptions(options => options ReturnHttpNotAcceptable = true ... like me then you end up with the error above and the caller gets a 406.

Maybe if we are responding with an obvious MvcProblemDetails we should be setting the response content?

Google token validation with local authentication

Hey

First of all I should mention that you're library is very good and makes it easier to validate Google token. As I am new to .net core a question raised in my mind.

I am currently having a .net core React application that is configured with local authentication with JWT.

            services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) 
                .AddJwtBearer(options => {
                                       options.TokenValidationParameters = new TokenValidationParameters
                                       {
                                           ValidateIssuer = true,
                                           ValidateAudience = true,
                                           ValidateLifetime = true,
                                           ValidateIssuerSigningKey = true,
                                           ValidIssuer = "yourdomain.com",
                                           ValidAudience = "yourdomain.com",
                                           IssuerSigningKey = new SymmetricSecurityKey(
                                               Encoding.UTF8.GetBytes("secret key"))
                                       };
                                   });

Now I want to add Google token validation and I was wondering to ask, will I be able to use your NuGet package and have my local authentication flow?

If I can would that be something like this?

            services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) 
                .AddJwtBearer(options => {
                                       options.TokenValidationParameters = new TokenValidationParameters
                                       {
                                           ValidateIssuer = true,
                                           ValidateAudience = true,
                                           ValidateLifetime = true,
                                           ValidateIssuerSigningKey = true,
                                           ValidIssuer = "yourdomain.com",
                                           ValidAudience = "yourdomain.com",
                                           IssuerSigningKey = new SymmetricSecurityKey(
                                               Encoding.UTF8.GetBytes("secret key"))
                                       };
                                   })
                .AddJwtBearer(x => x.UseGoogle(
                     clientId: "<google-client-id>",
                     hostedDomain: "<optional-g-suite-domain>"));

Cheers

ProblemDetails: support resolving ProblemDetailsOptions from service collection

A popular way to use IOptions is to register them with the IServiceCollection and let DI take care of injecting options into middleware. ProblemDetailsOptions does not support this pattern right now because UseProblemDetails() always provides its own instance of IOptions.

To support DI, there should be a parameterless UseProblemDetails() that doesn't call Options.Create()

public static IApplicationBuilder UseProblemDetails(this IApplicationBuilder app)
{
-     var options = new ProblemDetailsOptions();

-     ConfigureDefaults(options);

-    return app.UseMiddleware<ProblemDetailsMiddleware>(Options.Create(options));
+    return app.UseMiddleware<ProblemDetailsMiddleware>();
}

It should be immediately obvious that this change creates a new issue: it's now impossible to do ConfigureDefaults inside the extension method.

One possible solution is to move ConfigureDefaults to an extension for IServiceCollection

public static AddProblemDetails(this IServiceCollection services)
{
    services.Configure<ProblemDetailsOptions>(ConfigureDefaults); 
}

public static AddProblemDetails(this IServiceCollection services, Action<ProblemDetailsOptions> configure)
{
    services.Configure<ProblemDetailsOptions>(configure); 
    services.PostConfigure<ProblemDetailsOptions>(ConfigureDefaults);
}

TraceId

ProblemDetail not contains 'traceId'

`return BadRequest();` vs `return BadRequest(something);`.

This is probably more of a .NET team question, but you seem to be the expert. :)

I'm curious why return BadRequest(); returns problem details, but return BadRequest(something); returns whatever is passed in. Obviously, you can derive something from ProblemDetails and pass that in, but that seems overly complicated. I wish return BadRequest(something); would always return problem details.

Thoughts?

Inner Exception in middleware

I'm getting following error when I use Problem Detail Middleware.

Error: "An exception was thrown attempting to execute the problem details middleware." System.ArgumentException: The stream must support writing. Parameter name: stream at Microsoft.AspNetCore.WebUtilities.HttpResponseStreamWriter..ctor(Stream stream, Encoding encoding, Int32 bufferSize, ArrayPool1 bytePool, ArrayPool1 charPool) at Microsoft.AspNetCore.Mvc.Internal.MemoryPoolHttpResponseStreamWriterFactory.CreateWriter(Stream stream, Encoding encoding) at Microsoft.AspNetCore.Mvc.Formatters.JsonOutputFormatter.WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding) at Hellang.Middleware.ProblemDetails.ProblemDetailsMiddleware.Invoke(HttpContext context) in D:\Samples\Middleware\src\ProblemDetails\ProblemDetailsMiddleware.cs:line 88

expose httpContextAccessor in options

Hi dear;
I want to pass the path of current request to Instance property of Problem Details.
for example:

services.AddProblemDetails(options =>
{
options.Map(ex => new ProblemDetails()
{
Title = ex.Code.ToString(),
Detail = ex.Message,
Status = (int?)ex.HttpStatusCode,
Instance= httpContextAccessor.HttpContext.Request.Path
}));

Can I access to httpContextAccessor?

ProblemDetails Xml Response not supported

I'm getting an InvalidOperationException when attempting to return a ProblemDetail as text/xml response.

2019-09-04 20:58:40.6396|WARN|Microsoft.AspNetCore.Mvc.Formatters.XmlSerializerOutputFormatter|An error occurred while trying to create an XmlSerializer for the type 'Hellang.Middleware.ProblemDetails.StatusCodeProblemDetails'.
System.InvalidOperationException: Hellang.Middleware.ProblemDetails.StatusCodeProblemDetails cannot be serialized because it does not have a parameterless constructor.
at System.Xml.Serialization.TypeDesc.CheckSupported()
at System.Xml.Serialization.TypeScope.GetTypeDesc(Type type, MemberInfo source, Boolean directReference, Boolean throwOnError)
at System.Xml.Serialization.ModelScope.GetTypeModel(Type type, Boolean directReference)
at System.Xml.Serialization.XmlReflectionImporter.ImportTypeMapping(Type type, XmlRootAttribute root, String defaultNamespace)
at System.Xml.Serialization.XmlSerializer..ctor(Type type, String defaultNamespace)
at System.Xml.Serialization.XmlSerializer..ctor(Type type)
at Microsoft.AspNetCore.Mvc.Formatters.XmlSerializerOutputFormatter.CreateSerializer(Type type)

Looks like StatusCodeProblemDetails would need to provide a default constructor that calls the base ProblemDetails constructor. Is this something that can be supported?

How does this work in conjunction with Problem Details being the default return for 4xx errors in Core 2.2?

https://docs.microsoft.com/en-us/aspnet/core/release-notes/aspnetcore-2.2?view=aspnetcore-2.2#problem-details-support

I am currently encountering an issue where I am throwing my own custom exception and catching it in my own custom middleware. The annoying problem right now is since updating from 2.1 to 2.2 , by using the default MVC stuff like return NotFound() returns problemdetails but throwing MyCustomException does not. This is what led me here to your library.

My worry is, if I choose to use this library, I will diverge from the default handling of the ProblemDetails stuff. I want to lean into the MVC default implementation of ProbleDetails. Not a different take on it, or a custom implementation. I am hoping this library does that and can help me.

Configure output of 'Request already started'

Hi Kristian,

We are using your problem details middleware together with aspnetcore's built in model validation, which is activated implicitly by adding the ApiControllerAttribute to our web api controller.

When sending a request, that causes model validation to fail, it seems asp cancels the request before reaching the ProblemDetailsMiddleware (which is fine, because we want to return 422 Unprocessable Entity in that case). Your code checks this and outputs "The response has already started" into the log.

Because our web api is part of quite a large eco system with multiple services, and the api is supposed to serve as an interface to third party applications, I'm afraid this might spam the logs and cause confusion to customers reading the log files.

We use a custom Filter to influence the response format, but even if we disable the filter, this 'problem' still occurs.

Is it possible, to configure/disable this "response already started" message, or do you have any other workaround?

Thanks in advance,
Markus

Pass HttpContext to Map() and MapStatusCode() as optional parameters

There are things in the request that I"d like to base the exception on so it would be handy if that was passed in whenever an exception should be created. One thing is the RequestServices to get a hold of scoped dependencies since we have no way to do that right now.

The parameter could be nullable/optional to not break backwards compatibility.

Thanks for your contribution

Not all exceptions should be logged as unhandled

An exception thrown by an API shouldn't always be treated as unhandled and logged as such.

However, ProblemDetailsMiddleware does just that.

Instead, the logging decision should be based on the status code of final response. A reasonable default: only status code >= 500 should be logged as unhandled.

Happy to make a pull request with the change

Example - mapping a DBConcurrencyException to a 409 response

Take a scenario where I have an API endpoint that calls some database library. This library code throws a DBConcurrencyException.

In this case, the exception is expected and should be mapped to a Problem Detail with a status code of 409. It is NOT an unhandled exception.

So I implement this handling centrally in one place using ProblemDetailsOptions:

options.Map<DBConcurrencyException>(ex =>
    new ExceptionProblemDetails(ex, StatusCodes.Status409Conflict));

The issue now is that the logs show a logged exception. Tools and people are hard wired to read this as something bad happened.

ProblemDetails middleware is ignored when returning StatusCode(409, error);

Hi ๐Ÿ‘‹

NOTE: this issue is very similar to #2 .

When trying to return a StatusCode result, the middleware isn't invoked iff I provide a model (in this case, a ProblemDetails instance).

โœ… Works.. (this code taken from your samples-code)..

[HttpGet("status/{statusCode}")]
public IActionResult Status([FromRoute] int statusCode)
{
    return StatusCode(statusCode);
}
  • Content-Type: "application/problem+json"

โŒ This code fails to call the middleware.

[HttpGet("conflict")]
public IActionResult ConflictCheck()
{
    var error = new ProblemDetails
    {
        Type = "https://httpstatuses.com/409",
        Title = "Agent was already modified.",
        Status = StatusCodes.Status409Conflict,
        Instance = "/test/conflict",
        Detail = "agent was already modified after you retrieved the latest data. So you would then override the most recent copy. As such, you will need to refresh the page (to get the latest data) then modify that, if required."
    };

    return StatusCode(409, error);
}
  • Content-Type: "application/json"

The reason (which is why this is similar to #2) is because, for the 2nd (not working) example ... when it calls if (Options.IsProblem(context)) (ref: your source code) that returns false. The reason why it returns false is this:

image

My guess is that, in the first (working) example, there is no model .. so there is nothing to write ... so no content-type was set. Where-as in the second example there is a model provided, so it must write the content-type out before the ProblemDetails middeware is invoked?

I'm not sure what the best solution here, is?

Invalid TraceId field in the ProblemDetails response

Hi!

I'm using ASP.NET Core 3.0 with the last version of Hellang.Middleware.ProblemDetails (3.1.0). In my Startup.ConfigureServices method I have added the following line:

services.AddProblemDetails();

And the corresponding

app.UseProblemDetails();

At the beginning of the Configure method.

With this setup, I have noticed that the output is something like this:

{
  "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
  "title": "Bad Request",
  "status": 400,
  "traceId": "|ce3cd73b-4af1db1e8a67d40f."
}

In particular, you can note that the traceId field seems to be invalid. Can you please investigate on this behavior?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.