I am running some load tests on my Web API application, and am trying to find a way to identify each request as it comes in to the methods OnActionExecuted and OnActionExecuting
My question is, within the objects HttpActionExecutedContext and HttpActionContext is there some kind of unique identifier I can obtain to identify an individual request.
I have tried adding a unix timestamp to my query string, but the requests are often coming in at the same time so this does not help.
I am hoping these objects have some kind of property?
You could add a scoped class with a generated identifier using Dependency Injection.
A scoped class is created once per request.
public class IdentifiedScope
{
public Guid Id { get; } = Guid.NewGuid();
}
// Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IdentifiedScope>();
}
// Controller
public MyController(IdentifiedScope identifiedScope)
{
this.identifiedScope = identifiedScope;
}
// Usage in an ActionFilter
public override async Task OnActionExecutionAsync(ActionExecutingContext context,
ActionExecutionDelegate next)
{
var identifiedScope =
context.HttpContext.RequestServices.GetService<IdentifiedScope>();
}
If you want to check requests execution time you could use serilog
Setup this code on your statup.cs
Log.Logger = new LoggerConfiguration()
.Enrich.With<HttpRequestIdEnricher>()
.Enrich.With<HttpRequestNumberEnricher>()
.Enrich.With<HttpSessionIdEnricher>()
.Enrich.With<HttpRequestTraceIdEnricher>()
.WriteTo.Debug()
.CreateLogger();
HttpRequestIdEnricher
HttpRequestNumberEnricher
HttpSessionIdEnricher
HttpRequestTraceIdEnricher
These might be separate nuget packages just seraching for htem if they
are don't come with Serilog
Install Serilog
and Serilog.Sinks.Debug
The for each request you should see the result in the console
[05:06:14 INF] HTTP GET /api/v1/blablabla/blablablaresponded 401 in
7530ms
Hope it helps
Related
I'm creating a piece of custom middleware that is going to used for telemetry purposes. This middleware is going to have a service that it uses to keep track of the different logs generated by my API, and once the request response is triggered it'll send off the traces to AppInsights.
The Invoke method of the middleware looks like this:
public async Task InvokeAsync(HttpContext context)
{
_telemetryService.InitNewEvent();
_telemetryService.Add("path", context.Request.Path, null); // Add works here
await _next(context);
_telemetryService.Add("statusCode", context.Response.StatusCode, null); // Add throws null error here
_telemetryService.SendEvent(context.Response);
}
Telemetry Service Excerpt
public class TelemetryService
{
private TelemetryClient _telemetryClient;
private List<TelemetryTrace> _currentTelemetryEventData;
private Dictionary<string, string> _finalisedTelemetryEventData;
private string _eventName;
private string _apiName = "pmapi";
public TelemetryService(TelemetryClient telemetryClient)
{
_telemetryClient = telemetryClient;
}
public void InitNewEvent()
{
_currentTelemetryEventData = new List<TelemetryTrace>();
Console.WriteLine("New telemetry event inited");
}
public void Add(string key, object data, SeverityLevel? severityLevel)
{
_currentTelemetryEventData.Add(new TelemetryTrace
{
Key = key,
Data = data,
SeverityLevel = severityLevel
});
}
}
A new event, which is just a dictionary, is inited by the middleware and a log it added to it. The middleware then awaits the response from the pipeline, during this time the API controller will add other logs to the event, and then the status code is added to the event and the event is sent when the pipeline returns to the middleware.
However I've noticed that if I add the telemetryService as a scoped or transient service the dictionary that holds the log data is set back to null after await _next(context) has been called.
This makes sense for transient, but with the definition of scoped being Scoped objects are the same within a request, but different across different requests. I expected that my dictionary state would be kept. But that only occurs with a singleton. Why isn't this happening the way I'm expecting?
I have a project written in ASP.NET Core 3.1.
I need to set data to Session in Singleton service:
_session.SetString("some key", "some value");
I injected the session object from DI:
public OperatorService(ILogger<OperatorService> logger,
ISession session,
IOptions<AppSettings> options)
{
this._session = session;
this._logger = logger;
this._appSettings = options.Value;
}
I calls the my method as below:
public void ChangeOperatorStatus(StatusChangeRequest request)
{
try
{
_session.SetString(request.Key, request.Value);
}
catch (Exception ex)
{
_logger.LogInformation($"Exception while changing status: {ex}");
}
}
but I get the exception below :
IFeatureCollection has been disposed.\r\nObject name: 'Collection'.
and I added some code to Startup.cs's ConfigureServices method:
services.AddHttpContextAccessor();
services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromMinutes(20);
options.Cookie.HttpOnly = true;
})
.AddDistributedMemoryCache();
And I added app.UseSession(); to the Configure method of Startup.cs.
I trid services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); and I get the session from httpContextAccessor.HttpContext.Session but I get the same error.
Please help me, thank you.
An HttpContext is the context of a single request. It provides access to the request, response properties etc of that single request. You can't cache it, it becomes invalid once that request ends.
Session is another transient thing - it lives only as long as a single user session. There's at least one session for every user of a web app. Caching one of those sessions in a singleton guarantees that
The reference will become invalid after a while, when the session expires and
The singleton will use only that user's values, ignoring everyone else's. This is a bug in itself, and a great way to hack into an application.
If an administrator logs in, the Session object may apply the admin's settings alive to everyone for the next 20, 30 or 60 minutes.
That's why using a Session makes sense for per-request middleware, not Singleton services.
Correct usage of HttpContext
The Session can only be reached through the request's context, so getting the correct session means getting the correct HttpContext. The correct way to do this is explained in David Fowler's ASP.NET Core Guidance :
❌ BAD This example stores the HttpContext in a field then attempts to use it later.
private readonly HttpContext _context;
public MyType(IHttpContextAccessor accessor)
{
_context = accessor.HttpContext;
}
public void CheckAdmin()
{
if (!_context.User.IsInRole("admin"))
{
throw new UnauthorizedAccessException("The current user isn't an admin");
}
}
✅ GOOD This example stores the IHttpContextAccesor itself in a field and uses the HttpContext field at the correct time (checking for null).
private readonly IHttpContextAccessor _accessor;
public MyType(IHttpContextAccessor accessor)
{
_accessor = accessor;
}
public void CheckAdmin()
{
var context = _accessor.HttpContext;
if (context != null && !context.User.IsInRole("admin"))
{
throw new UnauthorizedAccessException("The current user isn't an admin");
}
}
Use a Scoped service instead
Since a Singleton can't know what session to use. One option is to simply convert that service to a Scoped service. In ASP.NET Core, a request defines a scope. That's how controller actions and pipeline middleware get access to the correct HttpContext for each request.
Assuming the service is used by an action or middleware, perhaps the only change needed is to replace AddSingleton<ThatService> with AddScoped<ThatService>
Turning the tables, or Inversion of Control
Another option is for callers of that singleton should provide the session to it. Instead of using a cached session eg :
public void SetStatus(string status)
{
_session.SetString(SessionKeys.UserStatus, "some value");
}
Ask for the session or HttpContext as a parameter :
public void SetStatus(string status,ISession session)
{
session.SetString(SessionKeys.UserStatus, "some value");
}
And have callers pass the correct session to it
It took me a while to get this fixed.
In my case, it was a 3.1 aspnetcore and it didn't worked until I turn the container function from
public async void OnPost
to
public async Task<IActionResult> OnPost
Looks like the HttpContext was disposed before it was used...
I am making a DLL to consume a REST API in aspnetcore.
Ideally, I would like it to be accessed this way:
API api = new API(clientInfo);
api.Module.Entity.Action(params);
But I am struggling to make that a reality. I can't make anything static because more than 1 session might be instanced at the same time. I can't pass the session around except by reference otherwise session state(cookies etc.) might change in the copy. Is there a design pattern I should be using?
public class API
{
private Session _session;
public API(ClientInfo clientInfo)
{
_session = new Session(clientInfo);
}
}
The session serves as middleware for the client, stores login data in case the client needs to repeat login, handles some errors/retries and exposes client methods.
public class Session
{
private Client _client;
private string _path;
public Session(ClientInfo clientInfo)
{
_client= new Client(clientInfo);
_path = clientInfo.Path;
}
public HttpResponseMessage Get(string name, string arguments = "")
{
return _client.Get(_path, name, arguments);
}
...
}
The client actually performs the calls.
public class Client
{
public HttpResponseMessage Get(string path, string endpointName, string arguments)
{
return GetClient().GetAsync(path + endpointName + arguments).Result;
}
private HttpClient GetClient(){...}
...
}
Personally, I just build a simple client for my APIs, with methods corresponding to the endpoints the API has:
public class FooClient
{
private readonly HttpClient _httpClient;
public FooClient(HttpClient httpClient)
{
_httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
}
public async Task<GetFooResult> Get(int id)
{
...
}
// etc
}
The HttpClient dependency is provided by registering a typed client in Startup.cs:
services.AddHttpClient<FooClient>(c =>
{
// configure client
});
And I add an IServiceCollection extension to encapsulate this and any other setup logic:
public static class IServiceCollectionExtensions
{
public static IServiceCollection AddFooClient(this IServiceCollection services, string uri)
{
...
}
}
Then, in my Startup, I can simply do something like:
services.AddFooClient(Configuration.GetValue<string>("FooUri"));
This is extremely helpful for setting up automatic error handling, retry policies, etc. via Polly, as you can then set up all that configuration just once in the extension.
Now, getting to your issue of persisting things like auth tokens, you have a few possibilities. I tend to prefer persisting auth tokens as claims, in which case you can simply retrieve the claim and pass it into methods on your client that need it:
var foo = await _fooClient.Get(fooId, User.FindFirstValue("FooAuthToken"));
If you handle things that way, you can bind your client in any scope, including singleton.
An alternative approach would be to actually persist the auth token in your client, but this has to be done with care. You should definitely avoid using singleton scope, unless you're employing something like a ConcurrentDictionary and even then, ensuring that the right token is always used could be a bit gnarly.
Assuming you're using a request scope, you can store the token directly as an ivar or something, but you'd still need to persist it some place else beyond that, or you'd still need to re-auth for each request. If you were to store it in the session, for example, then you could do something like:
services.AddScoped<FooClient>(p =>
{
var httpClientFactory = p.GetRequiredService<IHttpClientFactory>();
var httpContextAccessor = p.GetRequiredService<IHttpContextAccessor>();
var httpClient = httpClientFactory.Create("ClientName");
var session = httpContextAccessor.HttpContext.Session;
var client = new FooClient(httpClient);
client.SetAuthToken(session["FooAuthToken"]);
});
However, even then, I'd still say it's better to pass the auth token into the method than do any of this. It's more explicit about which actions require auth versus those that do not, and you always know exactly what's coming from where.
One of your biggest problems will be the reuse of the HttpClient. This is a known problem for "pre-Core" days. Luckily, its been addressed and as of Net Core 2.1 we now have an HttpClientFactory which allows you to spin up as manage HttpClients as you need and they're handled for you as part of the framework.
https://www.stevejgordon.co.uk/introduction-to-httpclientfactory-aspnetcore
With this in mind, theres nothing stopping you from using DI to inject an IHttpClientFactory which will provide you with the necessary access to the pipeline you need. Other than that, its entirely up to you how you design the code which "manages" your access to the REST resources. Maybe some sort of Repository Pattern? (Purely guess work really without knowing your architecture etc)
This question is essentially the same as the one here, but, for asp.net core while using the asp.net core cookie middleware.
Is accessing query string/request body data possible on validation, and if it is, would you encourage the idea? It seems that according to this that it is very much possible, however, are the same rules in play from big boy asp.net (such as you are only to read the request data once in a given requests lifetime)?
Example: I'm creating an app where people have one account, but, are members of different teams. They can perform many different actions in the app, and, they can perform that action while in the "context" of one team or another that they are a member of. So, I have a teamId integer being passed in requests made to the server. I'd like to pull claims off the ClaimsPrincipal verifying that they really are a member of that team in the authorization portion of the pipeline.
As you said it is possible to access request's data on OnValidatePrincipal event. So, you can write something like this:
OnValidatePrincipal = async (context) =>
{
if (context.Request.Path.Value.StartsWith("/teams/"))
{
var teamId = // get team id from Path;
if (user is not team member)
{
context.Response.StatusCode = 403;
}
}
}
However, i think your requirement is related Authorization rather than Authentication. I would use Policy-Based Authorization to handle the requirement. Example policy should be like this:
Requirement and Handler:
public class TeamMemberHandler: AuthorizationHandler<TeamMemberRequirement>
{
private readonly IActionContextAccessor _accessor; // for getting teamId from RouteData
public TeamMemberHandler(IActionContextAccessor accessor)
{
_accessor = accessor;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, TeamMemberRequirement requirement)
{
var teamId = // get teamId with using _accessor
if (user is not member of team(by teamId))
{
context.Fail();
}
return Task.FromResult(0);
}
}
public class TeamMemberRequirement : IAuthorizationRequirement
{
}
Configure Services:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
services.AddAuthorization(options =>
{
options.AddPolicy("TeamMember",
policy => policy.Requirements.Add(new TeamMemberRequirement()));
});
services.AddSingleton<IAuthorizationHandler, TeamMemberHandler>();
}
Finally use it on top of controller(or if you want, you can add filter globally)
Authorize[(Policy = "TeamMember")]
public class TeamHomeController : Controller
{
// Authorize[(Policy = "AnotherPolicy")]
public IActionResult Index(){}
}
In my Web API project, I have a dependency that requires the current request.
The code is below:
public interface IResourceLinker {
Uri Link(string routeName, object routeValues);
}
public class ResourceLinker : IResourceLinker {
private readonly HttpRequestMessage _request;
public ResourceLinker(HttpRequestMessage request) {
_request = request;
}
public Uri Link(string routeName, object routeValues) {
return new Uri(_request.GetUrlHelper()
.Link(routeName, routeValues));
}
}
public class TestController : ApiController {
private IResourceLinker _resourceLinker;
public TestController(IResourceLinker resourceLinker) {
_resourceLinker = resourceLinker
}
public Test Get() {
var url = _resourceLinker.Link("routeName", routeValues);
// etc.
}
}
Using Simple Injector, is it possible to inject the current request into the container at runtime ?
I tried the following :
public class InjectRequestHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
InjectRequest(request);
return base.SendAsync(request, cancellationToken);
}
public static void InjectCurrentRequestIntoContainer(
HttpRequestMessage request)
{
var resolver = (SimpleInjectorDependencyResolver)
request.GetDependencyScope();
resolver.Container.Register(() => request);
}
}
but recieved the following error
The container can't be changed after the first call to GetInstance, GetAllInstances and Verify.
Is there any way to inject the current request into the container at runtime?
The container blocks any registrations after the registration phase. This splits the usage of the container into two phases: registration and resolve. Simple Injector is not the only container that does this. Autofac for instance, does this even more explicitly by allowing users to make registrations using the ContainerBuilder that builds the container using the Build method as last step in the configuration phase.
Simple Injector disallows this mainly because making registrations later on during the application lifetime can easily lead to all sorts of problematic behavior such as race conditions. It also makes the DI configuration much harder to understand, since registrations are scattered throughout the application, while you should try to centralize registration when applying DI. As a side effect, this design allows the container have a linear performance characteristic in a multi-threading scenario, because the container's happy path is free of locks.
Simple Injector allows just-in-time registration using the ResolveUnregisteredType event, but this is not the way to go in your case.
The problem you are having is that you want to inject an object that is only known at runtime into the object graph. This might not be the best thing to do, since in general you should pass runtime dependencies through method arguments, while compile-time/configuration-time dependencies should be passed through the constructor.
But if you are certain that passing the HttpRequestMessage as a constructor argument is the right thing to do, what you need to do is to cache this message during the lifetime of the request and make a registration that allows to return that cached instance.
This is how this would look like:
// using SimpleInjector.Advanced; // for IsVerifying()
container.Register<HttpRequestMessage>(() =>
{
var context = HttpContext.Current;
if (context == null && container.IsVerifying())
return new HttpRequestMessage();
object message = context.Items["__message"];
return (HttpRequestMessage)message;
});
This registration will retrieve a HttpRequestMessage that is cached in the HttpContext.Items dictionary. There's an extra check to allow this registration to work during verification (when calling container.Verify()), since at that point in time there is no HttpContext.Current. But if you're not interest in verifying the container (which you usually really should btw), you can minimize it to this:
container.Register<HttpRequestMessage>(() =>
(HttpRequestMessage)HttpContext.Current.Items["__message"]);
With this registration, the only thing you have to do is cache an HttpRequestMessage instance before container.GetInstance is called to get the controller:
public static void InjectCurrentRequestIntoContainer(
HttpRequestMessage request)
{
HttpContext.Current.Items["__message"] = request;
}
UPDATE
The Web API Integration package contains a GetCurrentHttpRequestMessage extension method that allows you retrieving the HttpRequestMessage of the current request. The Web API Integration Wiki page describes how to use this extension method.