Multiple subscribers to the OnSendingHeaders event in an owin middleware - asp.net-web-api

I have two middlewares similar to the ones below which are both subscribing to the OnSendingHeaders event. One sets some headers and the other one removes some headers:
public class Middleware1 : OwinMiddleware
{
public Middleware1(OwinMiddleware next) : base(next)
{
}
public override async Task Invoke(IOwinContext context)
{
context.Response.OnSendingHeaders(state =>
{
var res = (OwinResponse)state;
// im removing some headers
}, context.Response);
await Next.Invoke(context);
}
}
public class Middleware2 : OwinMiddleware
{
public Middleware2(OwinMiddleware next) : base(next)
{
}
public override async Task Invoke(IOwinContext context)
{
context.Response.OnSendingHeaders(state =>
{
var res = (OwinResponse)state;
// im setting some headers
}, context.Response);
await Next.Invoke(context);
}
}
I also have web api controllers that creates excel and pdf-documents and returns an IHttpActionResult. That piece of code is working as intended.
However, when I issue a request to a controller that returns a pdf/excel-document, the response gets aborted in my browser after the headers are sent. The response is sent back as a http status code 200 and the correct Content-Length header is sent, but the file to be downloaded gets aborted.
This error seems to be tied to the subscribing of the OnSendingHeaders event. Because if I remove the middlewares or set/remove the headers without subscribing to the event (calling context.Response.Headers directly), everything works fine.
Is this event not supposed to be subscribed to multiple times or what could otherwise be causing this issue?

Related

ASP.NET Core API response headers not in expected place

I have an ASP.NET Core API that adds two headers to its response callback_uri and redirect_uri.
The strange thing (to me) is that in my AJAX call to the service, the headers are part of the JSON data, as a headers array, rather than the request object itself. I cannot use jqxhr.getResponseHeader(...) and therefore must interrogate the headers array manually within the response data.
Because the StatusCode is also part of the data it means my AJAX success callback is always called, even when I'm testing for a 400 bad request response, which makes testing less simple.
Web API controller action:
[HttpGet, Route("Authenticate")]
public HttpResponseMessage Authenticate(string applicationId)
{
HttpResponseMessage response;
if(!_security.IsApplicationIdValid(applicationId))
{
response = new HttpResponseMessage(System.Net.HttpStatusCode.BadRequest);
response.ReasonPhrase = ErrorMessages.INVALID_APPLICATION_ID;
return response;
}
IAuthenticationProvider authProvider = _security.GetAuthenticationProvider();
response = new HttpResponseMessage(System.Net.HttpStatusCode.Redirect);
response.Headers.Add(HeaderKeyNames.CALLBACK_URI_KEY_NAME, authProvider.GetCallbackUrl());
response.Headers.Add(HeaderKeyNames.AUTHENTICATION_SERVICE_REDIRECT_URI_KEY_NAME, authProvider.GetUrl());
return response;
}
AJAX code:
var settings = {
data: { "applicationId": applicationId },
success: successCallback, // at the moment just writes to console
error: errorCallback, // at the moment just writes to console
method: "GET"
};
$.ajax(url, settings);
Am I doing something wrong on the server-side?
You can use a combination of ResultFilters and ServiceFilterAttribute to add your custom headers. This is particularly useful because:
ServiceFilter enables you to have DI access in your ResultFilter.
You can apply it as an Attribute in the actions you want
You can test it.
Putting all together:
Create the custom result filter class
public class CustomHeadersResultFilter : IResultFilter
{
private readonly IMyService _myService;
public CustomHeadersResultFilter(IMyService myService)
{
_myService = myService;
}
public void OnResultExecuting(ResultExecutingContext context)
{
context.HttpContext.Response.Headers.Add("my-header", _myService.GetData());
// if under CORS, this need to be added otherwise you can't read the headers using xhr.getResponseHeader('my-header')
context.HttpContext.Response.Headers.Add("Access-Control-Expose-Headers", "my-header");
}
public void OnResultExecuted(ResultExecutedContext context)
{
// can't add headers here, since it's too late in the pipeline
}
}
Register it in your Startup.ConfigureServices
services.AddTransient<IMyService, MyService>();
// our custom result filter
services.AddTransient<CustomHeadersResultFilter>();
Apply the attribute in the action you want to return the custom headers
[HttpGet("{id}")]
[ServiceFilter(typeof(CustomHeadersResultFilter))]
public ActionResult Get(string id)
{
if (id == "something-bad")
{
return BadRequest("invalid application id");
}
// return a 200 Ok. Check the other types if you want something different
return Ok();
}
Testing all of this with a separate web application, doing an ajax request to the API, you can access the headers:
<script>
var settings = { method: "GET" };
$.ajax('http://localhost:61284/api/values/test', settings)
.done(function (data, textStatus, xhr) {
alert(xhr.getResponseHeader('my-header'));
})
.fail(function () {
alert("error");
});
</script>
Add headers like this: (ofc change the type if needed or define your own)
response.Content.Headers.ContentType = new MediaTypeHeaderValue("text/plain");
What you're doing is creating a HttpResponseMessage object, serializing it to json and then returning it.
This is why the headers are in the json content, instead of the http response.
What you can do is someting like this:
[HttpGet, Route("Authenticate")]
public IActionResult Authenticate(string applicationId)
{
if(!_security.IsApplicationIdValid(applicationId))
{
return BadRequest(ErrorMessages.INVALID_APPLICATION_ID);
}
IAuthenticationProvider authProvider = _security.GetAuthenticationProvider();
this.Response.Headers.Add(HeaderKeyNames.CALLBACK_URI_KEY_NAME, authProvider.GetCallbackUrl());
this.Response.Headers.Add(HeaderKeyNames.AUTHENTICATION_SERVICE_REDIRECT_URI_KEY_NAME, authProvider.GetUrl());
return StatusCode(302);
}

Adding Observers to already running MassTransit system

I am trying to add a microservice to a system that contains a MassTransit observer, which will observe request response or publish messages already being used in the system. I cannot redeploy the existing services easily so would prefer to avoid it if possible.
The following code only executes when the service starts, it does not execute when a message is sent.
BusControl = Bus.Factory.CreateUsingRabbitMq(cfg =>
{
var host = cfg.Host(new Uri($"{settings.Protocol}://{settings.RabbitMqHost}/"), h =>
{
h.Username(settings.RabbitMqConsumerUser);
h.Password(settings.RabbitMqConsumerPassword);
});
cfg.ReceiveEndpoint(host, "pub_sub_flo", ec => { });
host.ConnectSendObserver(new RequestObserver());
host.ConnectPublishObserver(new RequestObserver());
});
Observers:
public class RequestObserver : ISendObserver, IPublishObserver
{
public Task PreSend<T>(SendContext<T> context) where T : class
{
return Task.CompletedTask;
}
public Task PostSend<T>(SendContext<T> context) where T : class
{
var proxy = new StoreProxyFactory().CreateProxy("fabric:/MessagePatterns");
proxy.AddEvent(new ConsumerEvent()
{
Id = Guid.NewGuid(),
ConsumerId = Guid.NewGuid(),
Message = "AMQPRequestResponse",
Date = DateTimeOffset.Now,
Type = "Observer"
}).Wait();
return Task.CompletedTask;
}
public Task SendFault<T>(SendContext<T> context, Exception exception) where T : class
{
return Task.CompletedTask;
}
public Task PrePublish<T>(PublishContext<T> context) where T : class
{
return Task.CompletedTask;
}
public Task PostPublish<T>(PublishContext<T> context) where T : class
{
var proxy = new StoreProxyFactory().CreateProxy("fabric:/MessagePatterns");
proxy.AddEvent(new ConsumerEvent()
{
Id = Guid.NewGuid(),
ConsumerId = Guid.NewGuid(),
Message = "AMQPRequestResponse",
Date = DateTimeOffset.Now,
Type = "Observer"
}).Wait();
return Task.CompletedTask;
}
public Task PublishFault<T>(PublishContext<T> context, Exception exception) where T : class
{
return Task.CompletedTask;
}
}
Can anyone help?
Many thanks in advance.
The observers are only called for messages sent, published, etc. on the bus instance to which they are attached. They will not observe messages sent or received by other bus instances.
If you want to observe those messages, you could create an observer queue and bind that queue to your service exchanges so that copies of the request messages are sent to your service. The replies, however, would not be easy to get since they're sent directly to the client queues via temporary exchanges.
cfg.ReceiveEndpoint(host, "service-observer", e =>
{
e.Consumer<SomeConsumer>(...);
e.Bind("service-endpoint");
});
This will bind the service endpoint exchange to your receive endpoint queue, so that copies of the messages are sent to your consumer.
This is commonly referred to as a wire tap.

Enable CORS for Web Api 2 and OWIN token authentication

I have an ASP.NET MVC 5 webproject (localhost:81) that calls functions from my WebApi 2 project (localhost:82) using Knockoutjs, to make the communication between the two projects I enable CORS. Everything works so far until I tried to implement OWIN token authentication to the WebApi.
To use the /token endpoint on the WebApi, I also need to enable CORS on the endpoint but after hours of trying and searching for solutions it is still now working and the api/token still results in:
XMLHttpRequest cannot load http://localhost:82/token. No 'Access-Control-Allow-Origin' header is present on the requested resource.
public void Configuration(IAppBuilder app)
{
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
TokenConfig.ConfigureOAuth(app);
...
}
TokenConfig
public static void ConfigureOAuth(IAppBuilder app)
{
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<AppUserManager>(AppUserManager.Create);
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = new SimpleAuthorizationServerProvider()
};
app.UseOAuthAuthorizationServer(OAuthServerOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}
AuthorizationProvider
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
var appUserManager = context.OwinContext.GetUserManager<AppUserManager>();
IdentityUser user = await appUserManager.FindAsync(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
... claims
}
IdentityConfig
public static AppUserManager Create(IdentityFactoryOptions<AppUserManager> options, IOwinContext context)
{
// Tried to enable it again without success.
//context.Response.Headers.Add("Access-Control-Allow-Origin", new[] {"*"});
var manager = new AppUserManager(new UserStore<AppUser>(context.Get<ApplicationDbContect>()));
...
var dataProtectionProvider = options.DataProtectionProvider;
if (dataProtectionProvider != null)
{
manager.UserTokenProvider =
new DataProtectorTokenProvider<AppUser>(dataProtectionProvider.Create("ASP.NET Identity"));
}
return manager;
}
EDIT:
1. Important note is that opening the endpoint directly (localhost:82/token) works.
2. Calling the Api (localhost:82/api/..) from the webproject also works, so the CORS is enabled for WebApi.
I know your issue was solved inside comments, but I believe is important to understand what was causing it and how to resolve this entire class of problems.
Looking at your code I can see you are setting the Access-Control-Allow-Origin header more than once for the Token endpoint:
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
And inside GrantResourceOwnerCredentials method:
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
This, looking at the CORS specifications, is itself an issue because:
If the response includes zero or more than one Access-Control-Allow-Origin header values, return fail and terminate this algorithm.
In your scenario, the framework is setting this header two times, and understanding how CORS must be implemented, this will result in the header removed in certain circumstances (possibly client-related).
This is also confirmed by the following question answer: Duplicate Access-Control-Allow-Origin: * causing COR error?
For this reason moving the call to app.UseCors after the call to ConfigureOAuth allows your CORS header to be set only once (because the owin pipeline is interrupted at the OAuth middleware, and never reaches the Microsoft CORS middleware for the Token endpoint) and makes your Ajax call working.
For a better and global solution you may try to put again app.UseCors before the OAuth middleware call, and remove the second Access-Control-Allow-Origin insertion inside GrantResourceOwnerCredentials.
Follow below steps and you will have your API working:
Remove any code like config.EnableCors(), [EnableCors(header:"*"....)] from your API.
Go to startup.cs and add below line
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
before
ConfigureAuth(app);
Uou will also need to install Microsoft.owin.cors package to use this functionality
Solving the problem without using app.UseCors()
I had the same problem. I used a Vue.Js client with axois to access my REST-API with cross-corps. On my Owin-Api-Server I was not able to add Microsoft.Owin.Cors nuget due to version conflicts with other 3rd party components. So I couldn't use app.UseCors() method but I solved it by using the middleware pipeline.
private IDisposable _webServer = null;
public void Start(ClientCredentials credentials)
{
...
_webServer = WebApp.Start(BaseAddress, (x) => Configuration(x));
...
}
public void Configuration(IAppBuilder app)
{
...
// added middleware insted of app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
app.Use<MyOwinMiddleware>();
app.UseWebApi(config);
...
}
public class MyOwinMiddleware : OwinMiddleware
{
public MyOwinMiddleware(OwinMiddleware next) :
base(next)
{ }
public override async Task Invoke(IOwinContext context)
{
var request = context.Request;
var response = context.Response;
response.OnSendingHeaders(state =>
{
var resp = (IOwinResponse)state;
// without this headers -> client apps will be blocked to consume data from this api
if (!resp.Headers.ContainsKey("Access-Control-Allow-Origin"))
resp.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
if (!resp.Headers.ContainsKey("Access-Control-Allow-Headers"))
resp.Headers.Add("Access-Control-Allow-Headers", new[] { "*" });
if (!resp.Headers.ContainsKey("Access-Control-Allow-Methods"))
resp.Headers.Add("Access-Control-Allow-Methods", new[] { "*" });
// by default owin is blocking options not from same origin with MethodNotAllowed
if (resp.StatusCode == (int)HttpStatusCode.MethodNotAllowed &&
HttpMethod.Options == new HttpMethod(request.Method))
{
resp.StatusCode = (int)HttpStatusCode.OK;
resp.ReasonPhrase = HttpStatusCode.OK.ToString();
}
}, response);
await Next.Invoke(context);
}
}
So I created my own middleware and manipulated the response. GET calls only needed the Access-Control-Allow headers whereas for OPTIONS calls I also needed to manipulate the StatusCode because axois.post() is calling first with OPTIONS-method before sending the POST. If OPTIONS return StatusCode 405, the POST will never be sent.
This solved my problem. Maybe this can help somebody too.

MVC 6 WebAPI returning html error page instead of json version of exception object

I am calling an api endpoint in an MVC 6 WebAPI:
POST http://localhost:57287/mytestapi/testentity/ HTTP/1.1
Accept: application/json
X-APIKey: 00000000-0000-0000-0000-000000000000
Content-Type: application/json; charset=utf-8
Host: localhost:57287
Content-Length: 1837
Expect: 100-continue
Connection: Keep-Alive
In the body I have json serialized test entity.
I have a bug in my entity controller code and the api is returning a 500 response 'Server Error' I know what the bug is an will fix it, however the issue I need some help with is that the API is returning HTML instead of the json serialized exception object - Json is what I expect: it's what the old webapi would return. I have ported the coded from an old test project that I know works.
So why is MVC 6 WebAPI returning html rather than json? Is there some configuration I need to do?
EDIT:
I added Accept: application/json to headers as suggested by #danludwig, however this did not resolve the issue, I still got an html error page back.
I looked at my StartUp.cs and found:
if (env.IsDevelopment())
{
//app.UseBrowserLink();
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
in the ConfigureApp method. I tested with app.UseDeveloperExceptionPage(); commented out. This prevented the return of the html error page in the api response body, however I am still not getting the json serialised exception object.
The ExceptionHandlerMiddleware configured when using UseExceptionHandler("Home/Error") does not include any support for JSON. It will just return the error html page. The same can be said when using UseDeveloperExceptionPage.
As far as I know you will need to add yourself some piece of code that will handle errors and return a json.
One option is to use an exception filter and add it either globally or on selected controllers, although this approach would only cover exceptions coming from the controller action methods. For example the following filter will return a json object only when the request accept was application/json (Otherwise it would let the exception pass through which for example could be handled by the global error page):
public class CustomJSONExceptionFilter : ExceptionFilterAttribute
{
public override void OnException(ExceptionContext context)
{
if (context.HttpContext.Request.GetTypedHeaders().Accept.Any(header => header.MediaType == "application/json"))
{
var jsonResult = new JsonResult(new { error = context.Exception.Message });
jsonResult.StatusCode = (int)System.Net.HttpStatusCode.InternalServerError;
context.Result = jsonResult;
}
}
}
services.AddMvc(opts =>
{
//Here it is being added globally.
//Could be used as attribute on selected controllers instead
opts.Filters.Add(new CustomJSONExceptionFilter());
});
Another option is to add your own exception handler middleware using the app.UseExceptionHandler overload that lets you specify the behavior of the alternative pipeline that will process the exception. I have quickly wrote a similar example using an inline middleware, which will return a json object only when the request accept was application/json:
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseExceptionHandler(appBuilder =>
{
appBuilder.Use(async (context, next) =>
{
var excHandler = context.Features.Get<IExceptionHandlerFeature>();
if (context.Request.GetTypedHeaders().Accept.Any(header => header.MediaType == "application/json"))
{
var jsonString = string.Format("{{\"error\":\"{0}\"}}", excHandler.Error.Message);
context.Response.ContentType = new MediaTypeHeaderValue("application/json").ToString();
await context.Response.WriteAsync(jsonString, Encoding.UTF8);
}
else
{
//I haven't figured out a better way of signally ExceptionHandlerMiddleware that we can't handle the exception
//But this will do the trick of letting the other error handlers to intervene
//as the ExceptionHandlerMiddleware class will swallow this exception and rethrow the original one
throw excHandler.Error;
}
});
});
Both approaches will let you have other error handlers that maybe provide html pages for non json requests (Another idea would be to either return a json or an html page from your custom error handler).
PS. If using the second approach, you most likely want to put that logic into its own middleware class and use a different approach to generate the json response. In that case take a look at what JsonResultExecutor does
I found a cheap hack to get what I want by adding this to the Startup Configure method:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
// Simple error page to avoid a repo dependency.
app.Use(async (context, next) =>
{
try
{
await next();
}
catch (Exception ex)
{
if (context.Response.HasStarted)
{
throw;
}
context.Response.StatusCode = 500;
context.Response.ContentType = "application/json";
var json = JToken.FromObject(ex);
await context.Response.WriteAsync(json.ToString());
}
});
//Rest of configure method omitted for brevity.
}

Asp.net MVC5 async action result executed, but response not sent till web server shutdown

I have an async action, which is supposed to return a JSON message to the browser after awaiting some task. Though the ActionResult is built and executed successfully(I'm using my own JsonResult, so I confirmed this by stepping into the source), the browser still gets no response(confirmed by Fiddler).
Meanwhile, it works if I'm awaiting for Task.Delay(), and returning a dummy message.
Strangely, if I rebuild my projects with VS2013 while the IIS Express running my website, all the sudden the browser receives the message that was supposed to be sent several minutes ago! I think it's shutting down the web server makes this happen, however I can't figure out how exactly this is happening.
I've been debugging for a day, disabled everything that I thought could have been related, with no luck. So any help about what could be the cause to this strange behavior is welcome. Thanks, here is the code:
[HttpPost]
public async Task<JsonResult> Update(string token)
{
try
{
//This works
//await Task.Delay(TimeSpan.FromSeconds(1));
//return Json(new { error = "testing." });
//This won't work
var feedback = await ServerConnectionKeeper.UpdateStation(token);
return feedback.Success
? Json(new { redirect = Url.Action("Index", "Home") })
: Json(new { error = feedback.Error });
}
catch (Exception ex)
{
return Json(new { error = ex.Message });
}
}
It turns out that I called an async void method, which made some strange unknown(by me) things happen. Here is the some conceptual code:
[HttpPost]
public async Task<JsonResult> Data()
{
await SomeTask();
return Json(new { message = "Testing" });
}
private async Task SomeTask()
{
FireAndForget();
await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);
}
private async void FireAndForget()
{
await Task.Delay(TimeSpan.FromSeconds(100)).ConfigureAwait(false);
}
With above code, I thought the response would come 1 second after requesting. But it does not, not even 100 seconds after. In some cases I get a InvalidOperationException, in other cases I get nothing.
If I change the async void to async Task, either ConfigureAwait(false) or not, every thing works:
[HttpPost]
public async Task<JsonResult> Data()
{
await SomeTask();
return Json(new { message = "Testing 4" });
}
private async Task SomeTask()
{
FireAndForget();
await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);
}
private async Task FireAndForget()
{
await Task.Delay(TimeSpan.FromSeconds(100)).ConfigureAwait(false);
}
The reason responses are sent when web server shutting down in my production code, is because my FireAndForget() method there is an async loop reading network messages, and never returns untill the connection closed, and shutting down the web server closes the connection. It's still strange to me that I didn't get InvalidOperationException there though.

Resources