Programmatically invoking the ASP.NET Core request pipeline
Asked Answered
M

1

10

Question

Is there a way to programmatically invoke the ASP.NET Core request pipeline from within my own application, given I have a HTTP verb, the route, headers and body payload?

Background

There are use-cases where the WebAPI of our ASP.NET Core application is not accessible because the application is running behind a firewall or is otherwise not reachable.

To provide a solution for this scenario we want our application to poll some other service for "work-items" which then translate into API calls in our application.

Approaches I considered

  • I could probably just ask DI to give me an instance of a controller and then invoke methods on it. Problems with this approach:
    • Authorization attributes are not enforced. But it is important in our use-case to have the bearer token validated. So here the question would be: How to invoke programmatically the Authorization middleware?
    • I would have to route the incoming work-items to the correct controller/method myself.
  • Using the Microsoft.AspNetCore.TestHost package I could create a TestClient which allows me to make requests to myself (see here). But there are a couple of uncertainties here:
    • The intended use-case of this TestHost is for integration testing. Is it safe to use this in a production environment?
    • Is it even possible to have such a TestServer running alongside the regular hosting?
    • What about thread-safety? Can I create multiple TestClients from a single TestServer instance and use them from different threads?

So I'm sure there must be a cleaner and more direct way to programmatically invoke the request pipeline from within my own application...

Mewl answered 18/5, 2018 at 9:11 Comment(5)
I fear I don't get your issue. When you are having issues with the firewall you can't hit the controller in the first place, how would you then make it call itself directly? Am I missing something here?Turgor
Could you use HttpClient to make the new request? That is how you would make a request to an 'external' website e.g. githubAyrshire
@SimplyGed, looks like I overlooked the most obvious solution - also seems a bit hackish, to be honest. I also digged into the TestServer source code and I discovered the IHttpApplication interface which has a ProcessRequestAsync method which apparently allows to push a request through the request pipeline. I will probably have a closer look at this and otherwise just use HttpClient to invoke my own API, as you suggested.Mewl
@RobertHegner, did this solution work for you? If so, I wonder if you could share what your implementation ended up looking like, the ProcessRequestAsync approach looks pretty interesting. I have a similar use case and recently asked for some guidance #56207615.Thaw
@user991985 I ended up using Simply Ged's suggestion (using a HtttpClient to invoke my own endpoints).Mewl
W
10

Yes, it is actually fairly easy. You can get a reference to the request pipeline at the end of your Startup class Configure method. Save it in a static field / singleton service / etc.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
  // ... usual configuration code

  AClass.PipelineStaticField = app.Build();
}

Then, in the method you wish to inject the request, you have to build a HttpContext to pass into the pipeline.

var ctx = new DefaultHttpContext();

// setup a DI scope for scoped services in your controllers etc.
using var scope = _provider.CreateScope();
ctx.RequestServices = scope.ServiceProvider;

// prepare the request as needed
ctx.Request.Body = new MemoryStream(...);
ctx.Request.ContentType = "application/json";
ctx.Request.ContentLength = 1234;
ctx.Request.Method = "POST";
ctx.Request.Path = PathString.FromUriComponent("/mycontroller/action");

// you only need this if you are hosting in IIS (.UseIISIntegration())
ctx.Request.Headers["MS-ASPNETCORE-TOKEN"] = Environment.GetEnvironmentVariable("ASPNETCORE_TOKEN");

// setup a place to hold the response body
ctx.Response.Body = new MemoryStream();

// execute the request
await AClass.PipelineStaticField(ctx);

// interpret the result as needed, e.g. parse the body
ctx.Response.Body.Seek(0, SeekOrigin.Begin);
using var reader = new StreamReader(ctx.Response.Body);
string body = await reader.ReadToEndAsync();

That way your request will traverse the whole pipeline including all the middleware such as authentication and authorization.

Watering answered 3/12, 2020 at 15:33 Comment(3)
I didn't test it because I'm no longer working on that project (the question is from 2018). But it looks good and I'm sure it will help others. Thanks for your answer!Mewl
Worked for me like a charm. I use this to inject Web Requests via WebSocket messages. The API may not be available to the public due to firewall configuration so we optionally provide a proxy, that proxy incoming requests via the WebSocket pipeline. By injecting the request, we can use the exactly same code as for instances where the API is available directlyHorace
It might be a bad practice since it's hard to find other posts about this approach, but it worked exactly how I needed it to. Thanks for sharing.Petromilli

© 2022 - 2024 — McMap. All rights reserved.