Skip to content

EmptyHttpResult is not straightforward to be used in type checking #45063

@Arclight3

Description

@Arclight3

Is there an existing issue for this?

  • I have searched the existing issues

Is your feature request related to a problem? Please describe the problem.

I created a generic endpoint filter

public class EndpointRequestResponseLoggingFilter<TRequest, TResponse> : IEndpointFilter

which has the purpose to log the request and response objects of an endpoint.

As a side note, I know that .NET 7 offers some similar functionality but it's not good enough for my needs (because it doesn't allow me to mark sensitive properties on the request/response object and filter them).

I made some extension methods to simplify the registration of the endpoint filter:

public static RouteHandlerBuilder AddRequestResponseLoggingFilter<TRequest, TResponse>(this RouteHandlerBuilder builder)
{
    builder.AddEndpointFilter<EndpointRequestResponseLoggingFilter<TRequest, TResponse>>();
    return builder;
}

public static RouteHandlerBuilder AddRequestLoggingFilter<TRequest>(this RouteHandlerBuilder builder)
{
    builder.AddEndpointFilter<EndpointRequestResponseLoggingFilter<TRequest, EmptyHttpResult>>();
    return builder;
}

public static RouteHandlerBuilder AddResponseLoggingFilter<TResponse>(this RouteHandlerBuilder builder)
{
    builder.AddEndpointFilter<EndpointRequestResponseLoggingFilter<EmptyHttpRequest, TResponse>>();
    return builder;
}

and I use them to register the filter on some endpoints:

app.MapPost("/myEndpoint", (MyRequest myRequest) => new MyResponse(1, "Message"))
    .AddRequestResponseLoggingFilter<MyRequest, MyResponse>();

app.MapPost("/myEndpointWithNoResponse", (MyRequest myRequest) => { })
    .AddRequestLoggingFilter<MyRequest>();

app.MapPost("/myEndpointWithNoRequest", () => new MyResponse(1, "abc"))
    .AddResponseLoggingFilter<MyResponse>();

Inside the filter I have this logic:

public virtual async ValueTask<object?> InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next)
{
    // Log request
    if (context.Arguments.Count > 0)
    {
        var request = context.Arguments[0];

        if (request is TRequest)
            // Filter sensitive properties on the request and log the rest of the object
        else if (request is null)
            _logger.LogError("Request: null");
        else
        {
            _logger.LogError("Expected request type '{providedRequestType}' but found type '{actualRequestType}'.", typeof(TRequest).FullName, request.GetType().FullName);
            // Filter sensitive properties on the request and log the rest of the object
        }
    }
    else if (typeof(TRequest) != typeof(EmptyHttpRequest))
        _logger.LogError("Expected request type '{providedRequestType}' but no request object was received.", typeof(TRequest).FullName);
    else
        _logger.LogInformation("Request: No request");

    var response = await next(context);

    // Log response
    if (response is TResponse)
        // Filter sensitive properties on the response and log the rest of the object
    else
    {
        if (response is null)
            _logger.LogError("Response: null");
        else
        {
            if (response.GetType().Name is nameof(EmptyHttpResult))
            {
                if (typeof(TResponse) != typeof(EmptyHttpResult))
                    _logger.LogError("Expected response type '{providedResponseType}' but no response was returned.", typeof(TResponse).FullName);
                else
                    _logger.LogInformation("Response: No response");
            }
           else
            {
                _logger.LogError("Expected response type '{providedResponseType}' but found type '{actualResponseType}'.", typeof(TResponse).FullName, response.GetType().FullName);
                // Filter sensitive properties on the response and log the rest of the object
            }
        }
    }

    return response;
}

As you can see, I am also doing some validations and type checking to make let the developer know that the type he registered on the endpoint filter is not the same as the actual object type.

The EmptyHttpRequest is a record I created to represent the situation when an endpoint doesn't have a request object.
The EmptyHttpResult is what I saw that the framework returns on the response when the endpoint has no response (e.g.: "myEndpointWithNoResponse"). So when I have an endpoint with no response I register the filter like this builder.AddEndpointFilter<EndpointRequestResponseLoggingFilter<TRequest, EmptyHttpResult>>()

The problem though is that I have to do the comparison like this: if (response.GetType().Name is nameof(EmptyHttpResult)) because the public typed exposed by the framework is Microsoft.AspNetCore.Http.HttpResults.EmptyHttpResult while the type of the response object is Microsoft.AspNetCore.Http.RequestDelegateFactory+EmptyHttpResult.

Describe the solution you'd like

I would like the response object to have the type Microsoft.AspNetCore.Http.HttpResults.EmptyHttpResult so I can do the comparison in a much simpler way: if (response is EmptyHttpResult).

Additional context

I am using the dotnet version: 7.0.100

Metadata

Metadata

Assignees

Labels

old-area-web-frameworks-do-not-use*DEPRECATED* This label is deprecated in favor of the area-mvc and area-minimal labels

Type

No type

Projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions