Let's say I register a scoped service to my ASP.NET 6 web application in Program.cs
:
services.AddScoped<MyRequestService>();
Now let's say I have some custom middleware that runs after MVC routing:
app.UseRouting();
app.Use(async delegate (HttpContext Context, Func<Task> Next)
{
await Next(); // <-- here a controller gets instantiated based on MVC routing
// and *MIGHT* have 'MyRequestService' injected, but
// it's *NOT GUARANTEED*
// what do I put here to check if 'MyRequestService' was injected into the
// controller that was chosen by MVC?
});
Now, after the call to await Next()
in the middleware, I would like to check if MyRequestService
was used by the controller that was chosen by MVC. How can I do this?
I cannot use Context.RequestServices.GetRequiredService<MyRequestService>()
since that will just instantiate the service if it isn't already instantiated. I need to check if the controller injected it, without inadvertently instantiating it.
CodePudding user response:
Instead of trying to scan the DI container by reflection, I would try to use the HttpContext
class. It has properties like Features
and Items
. Both are able to hold arbitrary data within the scope of the current context. Your service could for example add an entry with a specific key (e.g. a const string) into the Items
dictionary with any data needed within the value and in your middleware you check after the return of Next()
if the dictionary contains the specified key. If yes, your service was used.
In that case the default mechanism of ASP is used and no reflection is needed.
CodePudding user response:
I used reflection per Jonesopolis's suggestion in the comments, but instead of using reflection on controllers, I'm using it to get at the internal dependency injection object cache. I'm caching the ResolvedServices PropertyInfo
using a Lazy<PropertyInfo>
object. This works, but I don't like it. Hopefully ASP.NET will have a public accessor for this soon:
app.UseRouting();
var ResolvedServicesPropLoader = new Lazy<PropertyInfo>(delegate ()
{
var ProviderType = Type.GetType("Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope, Microsoft.Extensions.DependencyInjection");
if (ProviderType == null) { throw new Exception("service caching class type could not be found"); }
var TheProperty = ProviderType.GetProperty("ResolvedServices", BindingFlags.NonPublic | BindingFlags.Instance);
return TheProperty ?? throw new Exception("could not find the cached services property using reflection");
});
app.Use(async delegate (HttpContext Context, Func<Task> Next)
{
await Next();
var InjectedServices = (ICollection<object>?)((IDictionary?)ResolvedServicesPropLoader.Value.GetValue(Context.RequestServices))?.Values;
if (InjectedServices == null) { throw new Exception("cached services collection is null"); }
if (InjectedServices.Where(x => x is MyRequestService).Any())
{
Console.WriteLine("service was injected");
}
});
CodePudding user response:
This is what I would do, it is not tested but I think this concept should work and it is easier to read than reflexion in my opinion:
// Scoped
MyRequestService {
constructor(MyServiceMonitor monitor) {
monitor.AddResolved(this.GetType().Name);
}
}
// Scoped
MyServiceMonitor {
List<string> types;
AddResolved(string type) {
types.Add(type);
}
IsResolved(string name) {
return types.Contains(name);
}
}
// Check in the delegate
context.RequestServices.GetRequiredService<MyServiceMonitor>().IsResolved("MyRequestService");