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...
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.
Related
As a follow-up to this question, I wanted to understand how my invoking of a Service manually can be improved. This became longer than I wanted, but I feel the background info is needed.
When doing a pub/sub (broadcast), the normal sequence and flow in the Messaging API isn't used, and I instead get a callback when a pub/sub message is received, using IRedisClient, IRedisSubscription:
_subscription.OnMessage = (channel, msg) =>
{
onMessageReceived(ParseJsonMsgToPoco(msg));
};
The Action onMessageReceived will then, in turn, invoke a normal .NET/C# Event, like so:
protected override void OnMessageReceived(MyRequest request)
{
OnMyEvent?.Invoke(this, new RequestEventArgs(request));
}
This works, I get my request and all that, however, I would like it to be streamlined into the other flow, the flow in the Messaging API, meaning, the request finds its way into a Service class implementation, and that all normal boilerplate and dependency injection takes place as it would have using Messaging API.
So, in my Event handler, I manually invoke the Service:
private void Instance_OnMyEvent(object sender, RequestEventArgs e)
{
using (var myRequestService = HostContext.ResolveService<MyRequestService>(new BasicRequest()))
{
myRequestService.Any(e.Request);
}
}
and the MyRequestService is indeed found and Any called, and dependency injection works for the Service.
Question 1:
Methods such as OnBeforeExecute, OnAfterExecute etc, are not called, unless I manually call them, like: myRequestService.OnBeforeExecute(e) etc. What parts of the pipeline is lost? Can it be reinstated in some easy way, so I don't have to call each of them, in order, manually?
Question 2:
I think I am messing up the DI system when I do this:
using (var myRequestService = HostContext.ResolveService<MyRequestService>(new BasicRequest()))
{
myRequestService.OnBeforeExecute(e.Request);
myRequestService.Any(e.Request);
myRequestService.OnAfterExecute(e.Request);
}
The effect I see is that the injected dependencies that I have registered with container.AddScoped, isn't scoped, but seems static. I see this because I have a Guid inside the injected class, and that Guid is always the same in this case, when it should be different for each request.
container.AddScoped<IRedisCache, RedisCache>();
and the OnBeforeExecute (in a descendant to Service) is like:
public override void OnBeforeExecute(object requestDto)
{
base.OnBeforeExecute(requestDto);
IRedisCache cache = TryResolve<IRedisCache>();
cache?.SetGuid(Guid.NewGuid());
}
So, the IRedisCache Guid should be different each time, but it isn't. This however works fine when I use the Messaging API "from start to finish". It seems that if I call the TryResolve in the AppHostBase descendant, the AddScoped is ignored, and an instance is placed in the container, and then never removed.
What parts of the pipeline is lost?
None of the request pipeline is executed:
myRequestService.Any(e.Request);
Is physically only invoking the Any C# method of your MyRequestService class, it doesn't (nor cannot) do anything else.
The recommended way for invoking other Services during a Service Request is to use the Service Gateway.
But if you want to invoke a Service outside of a HTTP Request you can use the RPC Gateway for executing non-trusted services as it invokes the full Request Pipeline & converts HTTP Error responses into Typed Error Responses:
HostContext.AppHost.RpcGateway.ExecuteAsync()
For executing internal/trusted Services outside of a Service Request you can use HostContext.AppHost.ExecuteMessage as used by ServiceStack MQ which applies Message Request Request/Response Filters, Service Action Filters & Events.
I have registered with container.AddScoped
Do not use Request Scoped dependencies outside of a HTTP Request, use Singleton if the dependencies are ThreadSafe, otherwise register them as Transient. If you need to pass per-request storage pass them in IRequest.Items.
I am experimenting with a gRPC service and client using proto files. The advice is to use gRPC client factory integration in .NET Core (https://learn.microsoft.com/en-us/aspnet/core/grpc/clientfactory?view=aspnetcore-3.1). To do this you register the client derived from Grpc.Core.ClientBase that is generated by the Grpc.Tools package, like this:
Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.AddGrpcClient<MyGrpcClientType>(o =>
{
o.Address = new Uri("https://localhost:5001");
});
})
My understanding is that MyGrpcClientType is registered with DI as a transient client, meaning a new one is created each time it is injected, but that the client is integrated with the HttpClientFactory, allowing the channel to be reused rather than be created each time.
Now, I would like to use protobuf-net.grpc to generate the client from an interface, which appears to be done like this:
GrpcClientFactory.AllowUnencryptedHttp2 = true;
using var http = GrpcChannel.ForAddress("http://localhost:10042");
var calculator = http.CreateGrpcService<ICalculator>();
If I am correct in thinking that channels are expensive to create, but clients are cheap, how do I achieve integration with the HttpClientFactory (and so reuse of the underlying channel) using protobuf-net.grpc? The above appears to create a GrpcChannel each time I want a client, so what is the correct approach to reusing channels?
Similarly, is it possible to register the protobuf-net.grpc generated service class with the below code in ASP.Net Core?
endpoints.MapGrpcService<MyGrpcServiceType>();
(Please correct any misunderstandings in the above)
Note that you don't need the AllowUnencryptedHttp2 - that's just if you aren't using https, but: you seem to be using https.
On the "similarly"; that should already work - the only bit you might be missing is the call to services.AddCodeFirstGrpc() (usually in Startup.cs, via ConfigureServices).
As for the AddGrpcClient; I would have to investigate. That isn't something that I've explored in the integrations so far. It might be a new piece is needed.
The Client Factory support not exists, and works exactly like documented here except you register with the method
services.AddCodeFirstGrpcClient<IMyService>(o =>
{
o.Address = new Uri("...etc...");
});
I have a class that inherit ActionFilterAttribute.
It decrypt some query string Attributes that comes in the Request encrypted by the caller. This ensures my WebAPI methods receive only "good" parameters. And that is working good.
What I need to do and had no success is to negate/fail the context (request) if a parameter is not encrypted.
How to force the request to terminate?
I´am doing my logic at:
public override void OnActionExecuting(ActionExecutingContext context)
Already tried to set the response to 401, but that doesn´t work:
//Negate the access by default
context.HttpContext.Response.StatusCode = 401;
As Clint B said, one of the approaches is using Middleware.
Here is an ASP Core documentation about Middleware with how-to examples
On the other hand, if you use MVC and want to use ActionFilter to force MVC Action to stop, you can do this by setting Action Context .Result property to a non-null value.
Example:
actionContext.HttpContext.Response.StatusCode = 401;
actionContext.HttpContext.Response.Headers.Clear();
actionContext.Result = new EmptyResult();
//var wrongResult = new { error = "Wrong parameters"};
//actionContext.Result = new JsonResult(wrongResult);
How to chose between Middleware and Action filter? Action filter can be used as a method filter, controller filter, or global filter only for MVC HTTP requests.
Middleware is component that "sits" on the HTTP pipeline and examines all requests and responses.
For Request parameters validation you may use Data Annotations.
The way to do this in ASP.NET Core is to write custom middleware. Just setting the response status code during the request will have no effect. Because when the request reaches the middleware that generates the response, the status code will be overwritten.
If you did this in middleware, you could stop the request right there and send back a 401 response.
EDIT:
I posted a tutorial project on GitHub that demonstrates how to create custom middleware with dependency injection in three easy steps. I haven't converted it to RC2 yet but you don't have to run it to learn how to do it. It's very simple. Read the ReadMe file and it explains how to search the code for the relevant areas. You can download the project here.
I've been following a number of sources to try and implement JWT authentication into my WebApi2 project and have also started using swagger.io and Swashbuckle to both document and test my API. I've been inspired by how exceptionless supports an /auth/login method to generate an api_key which is then used to call other methods requiring authorisation.
I should also say my development has been based on the ASP.NET Web API 2 book which introduces Token-Based security using JwtAuthForWebAPI package and so I have my JWT Delegating Handler implemented for me as follows:
// Add Jwt Authentication Web Handler
var builder = new SecurityTokenBuilder();
var reader = new ConfigurationReader();
GlobalConfiguration.Configuration.MessageHandlers.Add(
new JwtAuthenticationMessageHandler
{
AllowedAudience = reader.AllowedAudience,
Issuer = reader.Issuer,
SigningToken = builder.CreateFromKey(reader.SymmetricKey),
//SigningToken = builder.CreateFromKey(reader.SubjectCertificateName),
CookieNameToCheckForToken = reader.CookieNameToCheckForToken
});
(I've set up web.config credentials and the code steps through fine)
The problem is:
Unlike exceptionless, when I call a method after entering my api_key, swagger is putting the key onto the querystring instead of in a "Bearer" authorisation header
I don't know if the above code checks the querystring for the JWT (and even if it did, it doesn't sound that sensible for lots of reasons (security, long URL)
Because the above code builds the Delegating Handler for me, I can't debug it to see what it is actually doing (can I link log4net into it?)
And as a result, my controller method doesn't get hit because [Authorise] fails.
My suppose my question at this point is to resolve item 1 above which will make 2 a non-issue and (hopefully) make 4 work! Thanks.
I am new to Web Api world and I see a lot of potential for in the new MVC 4 Web Api. I have generated a WCF Web Service but was planning to get to know Web-APIs and their web service capabilities. Now, is MVC 4 Web-Api Service more as front end? I wrote a sample Service by following some examples online in MVC 4 Web Api but how do I consume the Service in just a basic console app? I have figured out the only way to consume it is by using HttpClient are there other ways? I am used to ASP.NET Web Service and WCF Web Service as where you reference it as a service in your references and then you are able to see all of its objects, properties to make appropriate calls.
What happens if web-api is requesting a object "Task" for post method as an example how am I able to fill an object "Task" and post it back to the web-api? As in WCF I am able to see that "Task" object and its properties because of WSDL so I am able to fill them and send it back to the service how is that done in web-api service?
The webservice would be used internally is it worth it to have an web-api service?
Thank you so much for helping clearing some question of what I have about web-api services.
---Edit as per Comment---
This screenshot shows a possible structure which you can approach. Of course, you can take a design that best suit your application.
So ControllerLib is a separate Class Library project which is brought into the main project as a Service Reference (which is not shown in the screenshot but should be inside the References folder of the SecondMVCApplication project). The two controller file (HomeController.cs and LocationController.cs is a controller file that implemented the Controller class, which is the same thing as a Controller file when you create a new MVC4 application using the template)
So for your question regarding if there is a SVC file. NO. In MVC 4, there is no such thing as a SVC file whether the application is one project or a combination of multiple project (unless I am mistaken). Inside the SecondMVCApplication, there is a file called RouteConfig.cs which holds the routing URL and as long as you add the Service Reference and there controller function exists. The code will run. Also the sample screenshot I showed also includes a WebApiConfig.cs file. You can use that file to do API stuff for mobile if you need. So, the regular RouteConfig.cs handles website request and the WebApiConfig.cs handles API request.
Good to Note: If you are using a Model project as a separate project (which I know you will as it is a M-V-C project...DUH!!). Make sure you put your connection string inside the web.config main project (in this case, the SecondMVCApplication). I remember I was stuck in this problem for 3 days (8 hours each) trying to fix this problem. Hope you don't run into it.
---End Edit---
The following answer to your question is mostly based on my knowledge and may or may not be true to all users.
Is MVC 4 Web-Api Service more as front end?
This depends on how you look at it. Typically, a Web-API service is more suited for creating back-end service to provide a data payload to different platforms, like mobile, desktop apps and so on. However, a MVC4 Internet Application will have front-end aspects in them, namely the Views, which end-users sees.
How do I consume the Service in just a basic console app?
AFAIK, there is two way to do this. One if to consume the APIs as a Service Reference. Another is to use HTTP operation (which I will mention in your question regarding the HTTP client and reserve this answer using the Service Reference method).
This depends on how your application is done. Like I said, if it is a website, your MVC pattern will not need to do anything, but the Models, Views and Controllers all are designed to work together without using any service.
Although, as I mentioned in the comments to the questions, if it is a big application then you will need to break them into different projects that will make the app modular and nimble. So you will end up creating different Service Library. If you go down the Service Library road, then you just make use of the Add Reference option to bring in your API/Projects/Whatever-you-call-it into the project. (For this, I normally put all project inside a single solution and let Visual Studio manage the build order as I am lazy to write up a build script).
Similarly, the same logic could be applied when consuming your web service in a console app.
I have figured out the only way to consume it is by using HttpClient are there other ways?
One way to consume web APIs is using HTTP. Are you aware of how to write http request headers and handle http response. If so, this is the second way I mentioned. You call the web service through it's URL and then get the data and do whatever work. If your answer to use http in console app is NO, then look at this post: Create HTTP post request and receive response using C# console application
What happens if web-api is requesting a object "Task" for post method as an example how am I able to fill an object "Task" and post it back to the web-api?
I think I indirectly answered this in your previous answer (assuming you are going to take the HTTP road). If not, then comment and I'll see if I can find some resource for you.
The webservice would be used internally is it worth it to have an web-api service?
I sort of answered this in the comment to the question. Ask if you need clarification.
Hope all this helps.
you can create your own Client Service class that will serve for every request.
public class ClientService
{
#region async helper methods
private static string m_mediaTypeHeaderValue= "application/json";
static HttpClient client = new HttpClient();
static HttpClient createHttpClientInstance()
{
return client ?? new HttpClient();
}
// SELECT
internal static async Task<T> Get<T>(string endpoint)
{
client= createHttpClientInstance();
var response = await client.GetAsync(endpoint);
string content = await response.Content.ReadAsStringAsync();
return await Task.Run(() => JsonConvert.DeserializeObject<T>(content));
}
// INSERT
static async Task<T> Post<T>(string endpoint, object data)
{
client = createHttpClientInstance();
var httpContent = new StringContent(JsonConvert.SerializeObject(data));
httpContent.Headers.ContentType = new MediaTypeHeaderValue(m_mediaTypeHeaderValue);
var response = await client.PostAsync(endpoint, httpContent);
string content = await response.Content.ReadAsStringAsync();
return await Task.Run(() => JsonConvert.DeserializeObject<T>(content));
}
// UPDATE
static async Task<T> Put<T>(string endpoint, object data)
{
client = createHttpClientInstance();
var httpContent = new StringContent(JsonConvert.SerializeObject(data));
httpContent.Headers.ContentType = new MediaTypeHeaderValue(m_mediaTypeHeaderValue);
var response = await client.PutAsync(endpoint, httpContent);
string content = await response.Content.ReadAsStringAsync();
return await Task.Run(() => JsonConvert.DeserializeObject<T>(content));
}
// DELETE
static async Task<T> Delete<T>(string endpoint)
{
client = createHttpClientInstance();
var response = await client.DeleteAsync(endpoint);
string content = await response.Content.ReadAsStringAsync();
return await Task.Run(() => JsonConvert.DeserializeObject<T>(content));
}
#endregion
}