4
votes

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...

1
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?Tseng
Could you use HttpClient to make the new request? That is how you would make a request to an 'external' website e.g. githubSimply Ged
@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.Robert Hegner
@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 stackoverflow.com/questions/56207115/….bbrownd
@user991985 I ended up using Simply Ged's suggestion (using a HtttpClient to invoke my own endpoints).Robert Hegner

1 Answers

4
votes

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.