I have the below code in my OWIN Startup class:
myiapbuilder.Map("/something/something", doit =>
{
doit.Use<pipepart1>();
doit.Use<pipepart2>();
doit.Use<piprpart3>();
});
If a condition occurs that I don't like in pipepart1, I would like to write a custom text/plain response to the caller within that Middleware, and do not fire pipepart2 or pipepart3. The BranchingPipelines sample on CodePlex shows lots of stuff, but not that.
Is it possible to short-circut a flow or otherwise stop OWIN processing of Middleware based on an earlier Middleware evaluation?
if you plan to respond directly to the client from pipepart1, then you could avoid calling other middlewares in the pipeline. Following is an example. Is this something you had in mind?
Here based on some condition (in my case if querystring has a particular key), I decide to either respond directly to the client or call onto next middleware.
appBuilder.Map("/something/something", doit =>
{
doit.Use<Pipepart1>();
doit.Use<Pipepart2>();
});
public class Pipepart1 : OwinMiddleware
{
public Pipepart1(OwinMiddleware next) : base(next) { }
public override Task Invoke(IOwinContext context)
{
if (context.Request.Uri.Query.Contains("shortcircuit"))
{
return context.Response.WriteAsync("Hello from Pipepart1");
}
return Next.Invoke(context);
}
}
public class Pipepart2 : OwinMiddleware
{
public Pipepart2(OwinMiddleware next) : base(next) { }
public override Task Invoke(IOwinContext context)
{
return context.Response.WriteAsync("Hello from Pipepart2");
}
}
Related
I have a generic message interface like this:
public interface IMyMessage
{
int EventCode {get;}
}
Now I have multiple consumers handling this message:
public class MyConsumer1: IConsumer<IMyMessage>{...}
public class MyConsumer2: IConsumer<IMyMessage>{...}
I want MyConsumer1 to handle only those messages where EventCode==1, and have MyConsumer2 handle all messages where EventCode==2.
I know that I can do an if statement in the Consume method, but want to know if there is a better way like some routing filter?
My preferred way would be to create an Attribute ie. HandlesEventCodeAttribute(1) and apply it on the Consumers.
I also use Autofac container integration with the MassTransit.
Please help.
Thanks
Before I give any input on the actual question, I would ask why you'd use the same message type with a property to determine which consumers actually consume the message. There are better (more efficient) methods available, such as using DIRECT exchanges with RabbitMQ.
You could create your own attribute, and create a middleware filter that would look at the consumer, see if it has the custom attribute, and then use the value from that attribute to check the message and filter it if the consumer is not interested in it.
A full working sample is shown below:
First, create the attribute.
class EventCodeAttribute :
Attribute
{
public int EventCode { get; }
public EventCodeAttribute(int eventCode)
{
EventCode = eventCode;
}
}
And the message type:
interface IEventMessage
{
int EventCode { get; }
}
The middleware filter:
class EventCodeFilter<TConsumer> :
IFilter<ConsumerConsumeContext<TConsumer, IEventMessage>>
where TConsumer : class
{
readonly int _eventCode;
public EventCodeFilter()
{
var attribute = typeof(TConsumer).GetCustomAttribute<EventCodeAttribute>();
if (attribute == null)
throw new ArgumentException("Message does not have the attribute required");
_eventCode = attribute.EventCode;
}
public async Task Send(ConsumerConsumeContext<TConsumer, IEventMessage> context, IPipe<ConsumerConsumeContext<TConsumer, IEventMessage>> next)
{
if (context.Message.EventCode.Equals(_eventCode))
{
await next.Send(context);
}
}
public void Probe(ProbeContext context)
{
var scope = context.CreateFilterScope("eventCode");
scope.Add("code", _eventCode);
}
}
The sample consumer:
[EventCode(27)]
class EventCodeConsumer :
IConsumer<IEventMessage>
{
public async Task Consume(ConsumeContext<IEventMessage> context)
{
}
}
Finally, configure the consumer to use the filter:
builder.AddMassTransit(cfg =>
{
cfg.AddConsumer<EventCodeConsumer>(x =>
x.ConsumerMessage<IEventMessage>(p => p.UseFilter(new EventCodeFilter<EventCodeConsumer>())));
});
I have wrote a Middleware which checks if Authorization Token is included in the header and based on that request are executed or returns error if token is missing. Now it is working fine for other Controllers.
But What should I do for Login/Registration Controller which don't required Authorization headers. How can I configure my Middleware to ignore these.
Current Implementation of MiddleWare to Check Headers for Authorization Token.
public class AuthorizationHeaderValidator
{
private readonly RequestDelegate _next;
private readonly ILogger<AuthorizationHeaderValidator> _logger;
public AuthorizationHeaderValidator(RequestDelegate next, ILogger<AuthorizationHeaderValidator> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
StringValues authorizationHeader;
Console.WriteLine(context.Request.Path.Value);
if (context.Request.Headers.TryGetValue("Authorization", out authorizationHeader))
{
await _next(context);
}
else
{
_logger.LogError("Request Failed: Authorization Header missing!!!");
context.Response.StatusCode = 403;
var failureResponse = new FailureResponseModel()
{
Result = false,
ResultDetails = "Authorization header not present in request",
Uri = context.Request.Path.ToUriComponent().ToString(),
Timestamp = DateTime.Now.ToString("s", CultureInfo.InvariantCulture),
Error = new Error()
{
Code = 108,
Description = "Authorization header not present in request",
Resolve = "Send Request with authorization header to avoid this error."
}
};
string responseString = JsonConvert.SerializeObject(failureResponse);
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(responseString);
return;
}
}
}
This is not a complete answer but only directions. Please post your code once you finish this task for next generations.
It seems you need a Filter and not Middlware as Middleware don't have access to rout data. Create new authorization filter by inheriting from Attribute and implementing IAuthorizationFilter or IAsyncAuthorizationFilter. There is only one method to implement
public void OnAuthorization(AuthorizationFilterContext context)
{
}
or
public Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
}
Decorate controllers and/or actions that you want to exclude from this logic with AllowAnonymousAttribute. Inside your OnAuthorization method check if current action or controller has AllowAnonymousAttribute and if it is return without setting Result on AuthorizationFilterContext. Otherwise execute the logic from you original Middleware and set Result property. Setting Result will short-circuit the remainder of the filter pipeline.
Then register your filter globally:
services.AddMvc(options =>
{
options.Filters.Add(new CustomAuthorizeFilter());
});
Not sure why you need middleware to validate if the Authorization header is present. It's difficult to exclude the controllers this way as all requests will go through this middleware before they hit the MVC pipeline.
[Authorize] attribute will do the job for you, given that you have some form of authentication integrated. If you need to exclude the controllers which don't require authorization, you can simply add [AllowAnonymous] at the controller level or at the action method level. Please see the code snippet below from the Microsoft Docs
[Authorize]
public class AccountController : Controller
{
[AllowAnonymous]
public ActionResult Login()
{
}
public ActionResult Logout()
{
}
}
If you must use a middleware, you can consider using it as an MVC filter, which means that it will be scoped to the MVC pipeline. For more details, please see this link. However, that will still not solve the problem to exclude the controllers without adding some convoluted logic, which can be quite complicated.
I have solved my problem by Implementing PipeLine
public class AuthorizationMiddlewarePipeline
{
public void Configure(IApplicationBuilder applicationBuilder)
{
applicationBuilder.UseMiddleware<AuthorizationHeaderValidator>();
}
}
And than I am using it like this on either Controller Scope or Method scope
[MiddlewareFilter(typeof(AuthorizationMiddlewarePipeline))]
I like the Automatic HTTP 400 responses functionality new to ASP.NET Core 2.1 and it's working out really well for most cases.
However, in one action I need to do a bit of pre-processing before validation the payload. I have a custom validator that requires two values in the model to perform validation. One of those values is in the path so I would like to set that value on the model from the path then validate.
I don't want to switch the functionality off for all actions with:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<ApiBehaviorOptions>(options =>
{
options.SuppressModelStateInvalidFilter = true;
});
}
Is there any way I could switch it off just for an individual action?
Edit:
I tried modifying the InvalidModelStateResponseFactory but it didn't solve my problem because I still need to get into the controller action:
services.Configure<ApiBehaviorOptions>(options =>
{
options.InvalidModelStateResponseFactory = actionContext =>
{
var ignore = actionContext.ActionDescriptor.FilterDescriptors.Any(fd => fd.Filter is SuppressModelStateInvalidFilterAttribute);
if (ignore)
{
// Can only return IActionResult so doesn't enter the controller action.
}
return new BadRequestObjectResult(actionContext.ModelState);
};
});
[AttributeUsage(AttributeTargets.Method)]
public class SuppressModelStateInvalidFilterAttribute : FormatFilterAttribute
{
}
Edit:
Here's a link to an issue I raised on the asp.net core repo in case I get anywhere with that - https://github.com/aspnet/Mvc/issues/8575
Update: you can just use the following code in ConfigureServices in Startup.cs:
services.Configure<ApiBehaviorOptions>(apiBehaviorOptions => {
apiBehaviorOptions.SuppressModelStateInvalidFilter = true;
});
Based on Simon Vane's answer, I had to modify the attribute for ASP.Net Core 2.2 as follows:
/// <summary>
/// Suppresses the default ApiController behaviour of automatically creating error 400 responses
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public class SuppressModelStateInvalidFilterAttribute : Attribute, IActionModelConvention {
private static readonly Type ModelStateInvalidFilterFactory = typeof(ModelStateInvalidFilter).Assembly.GetType("Microsoft.AspNetCore.Mvc.Infrastructure.ModelStateInvalidFilterFactory");
public void Apply(ActionModel action) {
for (var i = 0; i < action.Filters.Count; i++) {
if (action.Filters[i] is ModelStateInvalidFilter || action.Filters[i].GetType() == ModelStateInvalidFilterFactory) {
action.Filters.RemoveAt(i);
break;
}
}
}
}
I had a response from Microsoft - https://github.com/aspnet/Mvc/issues/8575
The following worked a charm.
[AttributeUsage(AttributeTargets.Method)]
public class SuppressModelStateInvalidFilterAttribute : Attribute, IActionModelConvention
{
public void Apply(ActionModel action)
{
for (var i = 0; i < action.Filters.Count; i++)
{
if (action.Filters[i] is ModelStateInvalidFilter)
{
action.Filters.RemoveAt(i);
break;
}
}
}
}
In my controller I could then make changes to the model before re-validating it (note the ModelState.Clear(), TryValidateModel add to existing model state):
if (model == null)
{
return BadRequest(ModelState);
}
model.Property = valueFromPath;
ModelState.Clear();
if (TryValidateModel(model) == false)
{
return BadRequest(ModelState);
}
You could play with ApiBehaviorOptions.InvalidModelStateResponseFactory property to handle specific cases based on actionContext details:
services.Configure<ApiBehaviorOptions>(options =>
{
options.InvalidModelStateResponseFactory = actionContext =>
{
// Do what you need here for specific cases with `actionContext`
// I believe you can cehck the action attributes
// if you'd like to make mark / handle specific cases by action attributes.
return new BadRequestObjectResult(context.ModelState);
}
});
This could probably be solved by implementing your own validator for your specific case. It is covered quite well in the documentation.
https://learn.microsoft.com/en-us/aspnet/core/mvc/models/validation?view=aspnetcore-2.1#custom-validation
Either that or possibly a custom model binder to create your model with all the preprocessing done before it is validated.
I encountered similar problem and came up with this solution.
public class SuppressModelStateInvalidFilterAttribute : ActionFilterAttribute
{
public SuppressModelStateInvalidFilterAttribute()
{
Order = -2500;
}
public override Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
context.ModelState.Clear();
return next.Invoke();
}
}
public class MyMiddleware
{
RequestDelegate _next;
public MyMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
//await context.Response.WriteAsync("Hello!");
await _next(context);
context.Response.Headers.Add("X-ElapsedTime", new[] { "bla" });
}
}
As soon as I add something like a header. I cannot receive any more the response from my Web API controller.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseMyMiddleware();
app.UseMvc();
}
Do I need to read first the answer the following middleware "UseMvc" produced?
I just have a very simple Controller method:
// GET: api/values
[HttpGet]
public IEnumerable<string> Get()
{
//Task t = new Task(() => Thread.Sleep(2000));
//t.Start();
return new string[] { "value1", "value2" };
}
I think I found a solution, but it is not actually a full answer:
public class MyMiddleware
{
RequestDelegate _next;
HttpContext _context;
public MyMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
_context = context;
context.Response.OnStarting(OnStartingCallback, state: this);
await _next(context);
}
public Task OnStartingCallback(object state)
{
_context.Response.Headers.Set("x-bla", "bla");
return Task.FromResult(0);
}
}
I found a reference to: https://github.com/aspnet/Session/blob/master/src/Microsoft.AspNet.Session/SessionMiddleware.cs and tried to build my code according to it.
Anyway this code feels not very safe. Is it really thread safe.
Basically what's happening is that you can't set any headers after anything has been written to the response body.
It is because the headers are being sent before the body and as soon as any body content is set.
To get around it in a proper way would be to buffer the response and don't send anything until all middlewares have been executed so they have a chance to modify the headers. This behavior should be a responsibility of the web server. Unfortunately, I didn't find any useful information how to configure buffering on ASP.NET 5 (IIS or Kestrel).
Your solution seems to be ok, but it's not thread safe. Middlewares are singletons and holding context in a class field may introduce race conditions when multiple concurrent request may hit your server.
You can make it thread safe by passing the context as a state object.
context.Response.OnStarting(OnStartingCallback, state: context);
and then retrieve it in callback by casting object state to HttpContext.
I would like to capture and save in a log file all the requests that my WebAPI should handle.
Just tried to save the Request.Content from the controller constructor but unfortunately,
the request object is null from the controller constructor scope.
Hope to learn an efficient way to do it.
I would just hook into web api tracing...
http://www.asp.net/web-api/overview/testing-and-debugging/tracing-in-aspnet-web-api
From the above article, you can implement ITraceWriter like so. This example uses System.Diagnostics.Trace.WriteLine, but you could plug in writing to a file here as well.
public class SimpleTracer : ITraceWriter
{
public void Trace(HttpRequestMessage request, string category, TraceLevel level,
Action<TraceRecord> traceAction)
{
TraceRecord rec = new TraceRecord(request, category, level);
traceAction(rec);
WriteTrace(rec);
}
protected void WriteTrace(TraceRecord rec)
{
var message = string.Format("{0};{1};{2}",
rec.Operator, rec.Operation, rec.Message);
System.Diagnostics.Trace.WriteLine(message, rec.Category);
}
}
As you can see from the Trace method, you get access to the HttpRequestMessage here.
I ended up implementing middleware to deal with it.
public class GlobalRequestLogger : OwinMiddleware
{
public override Task Invoke(IOwinContext context)
{
// Implement logging code here
}
}
Then in your Startup.cs:
app.Use<GlobalRequestLogger>();