I like Owin's lightweight code based configuration registration. Is this the correct way to create an endpoint in Owin?
Right now the below code is broken, It executes (the code in the if-block runs), but I then get a 404.
public class HelloOkEndpoint : OwinMiddleware
{
public HelloOkEndpoint(OwinMiddleware next)
: base(next)
{
}
public override Task Invoke(IOwinContext context)
{
IOwinRequest request = context.Request;
IOwinResponse response = context.Response;
if (request.Path.Value.ToLower().Contains("hello.ashx"))
{
response.Body = new MemoryStream(System.Text.Encoding.UTF8.GetBytes("Ok!"));
response.StatusCode = 200;
}
return Next.Invoke(context);
}
}
I would normally implement this as an ashx
Okay, I've solved it, leaving as a reference since googling ashx, and hander and owin almost entirely deals with middleware and not "endpoint-ware"
public class HelloOkEndpoint : OwinMiddleware
{
public HelloOkEndpoint(OwinMiddleware next)
: base(next)
{
}
public override Task Invoke(IOwinContext context)
{
IOwinRequest request = context.Request;
IOwinResponse response = context.Response;
if (request.Path.Value.ToLower().Contains("hello.ashx"))
{
response.StatusCode = 200;
//This is the magic, using response.WriteAsync will end the Respose.
return response.WriteAsync(System.Text.Encoding.UTF8.GetBytes("Ok!"));
}
return Next.Invoke(context);
}
}
Related
I have an API-controller that calls a service class. Inside the service class I want to throw an exception so the API-controller can catch it, and return a Http-BadRequest response.
But what exception is equal to Bad Request? And what is best practise for this situation?
I used this pattern for throwing exceptions in the application layer and the api layer would recognize the http status code:
The exceptions definition:
public class BadRequestException : Exception
{
public BadRequestException(string message = null)
: base(message == null ? "Bad Request" : message)
{ }
}
public class ActionInputIsNotValidException : BadRequestException
{
public ActionInputIsNotValidException()
: base("Action input is not valid")
{ }
}
An Action Filter to handle exceptions in api layer:
public class ExceptionActionFilter : ExceptionFilterAttribute
{
public ExceptionActionFilter()
{
}
public override void OnException(ExceptionContext context)
{
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
context.HttpContext.Response.ContentType = "application/json";
if (isTypeOf(context.Exception, typeof(Exceptions.BadRequestException)))
{
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest;
}
context.Result = new JsonResult(new
{
Message = context.Exception.Message,
});
}
private bool isTypeOf(Exception exception, Type baseType)
{
return exception.GetType() == baseType || exception.GetType().IsSubclassOf(baseType);
}
}
Then in the application layer we can throw exceptions and the result of api call will be a json containing error message with http 400 status code:
throw new ActionInputIsNotValidException();
I have implemented a subclass of AuthenticationHandler. It returns AuthenticationResult.Fail("This is why you can't log in");
I would have expected this message to end up in the body, or at least in the HTTP status text, but instead I get a blank 401 response.
Is there any way to provide additional information for failed authentication attempts in ASP.NET core?
Override HandleChallengeAsync:
In the example below the failReason is a private field in my implementation of AuthenticationHandler.
I don't know if this is the best way to pass the reason for failure. But the AuthenticationProperties on the AuthenticateResult.Fail method did not make it through to HandleChallengeAsync in my test.
public class CustomAuthenticationHandler<TOptions> : AuthenticationHandler<TOptions> where TOptions : AuthenticationSchemeOptions, new()
{
private string failReason;
public CustomAuthenticationHandler(IOptionsMonitor<TOptions> options
, ILoggerFactory logger
, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock) { }
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
failReason = "Reason for auth fail";
return AuthenticateResult.Fail(failReason);
}
protected override Task HandleChallengeAsync(AuthenticationProperties properties)
{
Response.StatusCode = 401;
if (failReason != null)
{
Response.HttpContext.Features.Get<IHttpResponseFeature>().ReasonPhrase = failReason;
}
return Task.CompletedTask;
}
}
From the docs: https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.authentication.authenticationhandler-1?view=aspnetcore-2.2
Override this method to deal with 401 challenge concerns, if an authentication scheme in question deals an authentication interaction as part of it's request flow. (like adding a response header, or changing the 401 result to 302 of a login page or external sign-in location.)
Source:
https://github.com/aspnet/Security/blob/master/src/Microsoft.AspNetCore.Authentication/AuthenticationHandler.cs#L201
I used this code in my custom Middleware to return problemDetails response.
public async Task Invoke(HttpContext httpContext)
{
await this.Next(httpContext);
if (httpContext.Response.StatusCode == StatusCodes.Status401Unauthorized)
{
var authenticateResult = await httpContext.AuthenticateAsync();
if (authenticateResult.Failure != null)
{
var routeData = httpContext.GetRouteData() ?? new RouteData();
var actionContext = new ActionContext(httpContext, routeData, new ActionDescriptor());
var problemDetails = this.ProblemDetailsFactory.CreateProblemDetails(httpContext,
statusCode: httpContext.Response.StatusCode,
detail: authenticateResult.Failure.Message);
var result = new ObjectResult(problemDetails)
{
ContentTypes = new MediaTypeCollection(),
StatusCode = problemDetails.Status,
DeclaredType = problemDetails.GetType()
};
await this.Executor.ExecuteAsync(actionContext, result);
}
}
}
For changing the body or Http status, you could try Context.Response.
Here is a demo code:
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
namespace TestIdentity
{
public class CustomAuthenticationHandler<TOptions> : AuthenticationHandler<TOptions> where TOptions : AuthenticationSchemeOptions, new()
{
public CustomAuthenticationHandler(IOptionsMonitor<TOptions> options
, ILoggerFactory logger
, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock)
{
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
await Context.Response.WriteAsync("This is why you can't log in");
return AuthenticateResult.Fail("This is why you can't log in");
}
}
}
I am running WebAPI with just one Middleware and not able to set response HTTP status code.
I am using OnSendingHeaders() and able to add headers and set response body, but status code is not getting set and response always has it set as 200 OK.
I am able to set response status code in ValidateUrl(context) though. Difference is ValidateUrl(context) is called synchronously and OnSendingHeaders() would be called asynchronously after ProcessIncomingRequest() is executed.
Is HTTP status line being sent even before OnSendingHeaders() gets called?
How/where should I set response HTTP status code when incoming request is being processed asynchronously?
public class Startup
{
public void Configuration(IAppBuilder appBuilder)
{
appBuilder.Use(typeof(SampleMiddleware));
}
}
public class SampleMiddleware : OwinMiddleware
{
private string _responseBody = null;
public SampleMiddleware(OwinMiddleware next) : base(next)
{
}
public async override Task Invoke(IOwinContext context)
{
if (!ValidateUrl(context))
{
return;
}
context.Response.OnSendingHeaders(state =>
{
var cntxt = (IOwinContext)state;
SetResponseMessage(cntxt);
}, context);
await ProcessIncomingRequest();
await Next.Invoke(context);
}
private void SetResponseMessage(IOwinContext context)
{
//Setting status code
context.Response.StatusCode = 201;
//Setting headers
context.Response.Headers.Add("XYZ", new[] { "Value-1" });
context.Response.Headers.Add("ABC", new[] { "Value-2" });
//Setting response body
context.Response.Write(_responseBody);
}
private async Task ProcessIncomingRequest()
{
// Process request
//await ProcessingFunc();
// Set response body
_responseBody = "Response body based on above processing";
}
private bool ValidateUrl(IOwinContext context)
{
if (context.Request.Host.ToString().Equals("xyx"))
{
return true;
}
else
{
context.Response.StatusCode = 400;
context.Response.Write("Bad Request");
return false;
}
}
}
I am really stuck on this as I have searched far and wide for a solution for an asyncpost using Web API but couldnt find anything. Essentially, its got to make a POST call using HttpClient to the relevant controller class AddMenuItem using Web API but it just doesn't work. It simply throws an error of a 404 Error and cannot see the controller method. Any reasons why and solution for this would be very helpful!
// Async Post Call
public static async void asyncPost()
{
using (var client = new HttpClient())
{
try
{
var values = new System.Collections.Generic.Dictionary<string, string>();
values.Add("ItemName", "Pepperoni Pizza");
var content = new FormUrlEncodedContent(values);
string baseAddress = "http://localhost:9000/";
HttpResponseMessage response3 = await client.PostAsync(baseAddress + "api/values/AddMenuItem", content);
if (response3.StatusCode == System.Net.HttpStatusCode.OK)
{
// Do something...
}
}
catch (OperationCanceledException) { }
}
}
// POST api/values
public void AddMenuItem([FromBody]string itemName)
{
//Should go in here when PostAync is called
}
Don't use async void; use async Task instead.
public static async Task PostAsync()
Then your controller can call it with await:
public async Task AddMenuItem([FromBody]string itemName)
{
await PostAsync(..);
}
I'm writing a Nancy endpoint and I want to do something that I think should be really simple. I want to support returning the content in either json or xml but when html or any other type is requested to return a 406 Not supported. I can easily force either XML or JSON only, and I guess I could do and if (accept is html) return 406 but I would assume that there is some support for this in the content Negotiation support.
Can anybody shed any light?
Implement your own IResponseProcessor, Nancy will pick it up and hook in the engine.
public sealed class NoJsonOrXmlProcessor : IResponseProcessor
{
public ProcessorMatch CanProcess(MediaRange requestedMediaRange, dynamic model, NancyContext context)
{
if (requestedMediaRange.Matches("application/json") || requestedMediaRange.Matches("aaplication/xml"))
{
//pass on, so the real processors can handle
return new ProcessorMatch{ModelResult = MatchResult.NoMatch, RequestedContentTypeResult = MatchResult.NoMatch};
}
return new ProcessorMatch{ModelResult = MatchResult.ExactMatch, RequestedContentTypeResult = MatchResult.ExactMatch};
}
public Response Process(MediaRange requestedMediaRange, dynamic model, NancyContext context)
{
return new Response{StatusCode = HttpStatusCode.NotAcceptable};
}
public IEnumerable<Tuple<string, MediaRange>> ExtensionMappings { get; private set; }
}
We avoided the use of ResponseProcessor for the whole reason that the request was still being run all the way through our authentication layer, domain layer, etc. We wanted a way to quickly kill the request as soon as possible.
What we ended up doing was performing the check inside our own Boostrapper
public class Boostrapper : DefaultNancyBootstrapper
{
protected override void RequestStartup(TinyIoCContainer requestContainer, IPipelines pipelines, NancyContext context)
{
base.RequestStartup(requestContainer, pipelines, context);
pipelines.BeforeRequest += nancyContext =>
{
RequestHeaders headers = nancyContext.Request.Headers
if (!IsAcceptHeadersAllowed(headers.Accept))
{
return new Response() {StatusCode = HttpStatusCode.NotAcceptable};
}
return null;
}
}
private bool IsAcceptHeadersAllowed(IEnumerable<Tuple<string, decimal>> acceptTypes)
{
return acceptTypes.Any(tuple =>
{
var accept = new MediaRange(tuple.Item1);
return accept.Matches("application/json") || accept.Matches("application/xml");
});
}
}