In .NET 6 Minimal API, how to return a 403 with html/text content

729 Views Asked by At

I am using .NET 6 minimal API. I have following simplified code which may return a file, HTTP 304 (not modified), or HTTP 403 (forbidden).

app.MapGet("/download", (HttpContext httpContext) =>
{
    if (DateTime.Now.Hour > 0)
    {
        var problemDetails = new ProblemDetails
        {
            Title = "403 Forbidden",
            Detail = "You are forbidden.  File can only be downloaded at midnight!",
            Status = StatusCodes.Status403Forbidden,
            Instance = httpContext.Request.GetDisplayUrl()
        };
        return Results.Problem(problemDetails);
    }

    string eTag = "my-etag";
    if (httpContext.Request.Headers.TryGetValue("If-None-Match", out var cachedEtag) && cachedEtag.Equals(eTag))
    {
        return Results.StatusCode(StatusCodes.Status304NotModified);
    }

    var entityTag = new EntityTagHeaderValue('"' + eTag + '"');
    return Results.File(@"c:\my.pdf", "application/pdf", entityTag: entityTag);

});

The above code works great. However, how can I return a 403 response with text/HTML rather than JSON?

Current 403 response:

{
    "type": "https://tools.ietf.org/html/rfc7231#section-6.5.3",
    "title": "403 Forbidden",
    "status": 403,
    "detail": "You are forbidden.  File can only be downloaded at midnight!",
    "instance": "https://localhost:7052/test"
}

Wanted 403 response:

<html>
  <body>
      You are forbidden.  File can only be downloaded at midnight!
  </body>
</html>

When I replace return Results.Problem() with following code,

return new ContentResult
{
    StatusCode = 403,
    ContentType = "text/html",
    Content = "You are forbidden.  File can only be downloaded at midnight!"
};

Compiler gives me following error.

  • Error CS0266 Cannot implicitly convert type 'Microsoft.AspNetCore.Http.IResult' to 'System.Threading.Tasks.Task'. An explicit conversion exists (are you missing a cast?)

  • Error CS1662 Cannot convert lambda expression to intended delegate type because some of the return types in the block are not implicitly convertible to the delegate return type

2

There are 2 best solutions below

0
On BEST ANSWER

Yes, it is possible. Create a custom class that implements IResult like this:

class CustomHTMLResult : IResult
{
    private readonly string _htmlContent;
    public CustomHTMLResult(string htmlContent)
    {
        _htmlContent = htmlContent;
    }
    public async Task ExecuteAsync(HttpContext httpContext)
    {
        httpContext.Response.StatusCode = StatusCodes.Status403Forbidden;
        httpContext.Response.ContentType = MediaTypeNames.Text.Html;
        httpContext.Response.ContentLength = Encoding.UTF8.GetByteCount(_htmlContent);
        await httpContext.Response.WriteAsync(_htmlContent);
     }
}

and then just do:

return new CustomHTMLResult("<html>\r\n<body>\r\n <b>You are forbidden.</b>  File can only be downloaded at midnight!\r\n</body>\r\n</html>");
0
On

Alternatively if you can upgrade to .NET 7+ then you can use Results.Text:

app.MapGet("/customres", (HttpContext httpContext, CancellationToken ct) =>
    Results.Text(content: "You are forbidden.  File can only be downloaded at midnight!",
        contentType: "text/html",
        statusCode: (int?)HttpStatusCode.Forbidden));

Or TypedResults.Content:

app.MapGet("/customres", (HttpContext httpContext, CancellationToken ct) => 
  TypedResults.Content(content: "You are forbidden.  File can only be downloaded at midnight!",
    contentType: "text/html",
    statusCode: (int?)HttpStatusCode.Forbidden));