I'm trying to wrap Web API controllers (IHttpController implementations) with decorators, but when I do this, Web API throws an exception, because somehow it is expecting the actual implementation.
Applying decorators to controllers is a trick I successfully apply to MVC controllers and I obviously like to do the same in Web API.
I created a custom IHttpControllerActivator that allows resolving decorated IHttpController implementations. Here's a stripped implementation:
public class CrossCuttingConcernHttpControllerActivator : IHttpControllerActivator {
private readonly Container container;
public CrossCuttingConcernHttpControllerActivator(Container container) {
this.container = container;
}
public IHttpController Create(HttpRequestMessage request,
HttpControllerDescriptor controllerDescriptor, Type controllerType)
{
var controller = (IHttpController)this.container.GetInstance(controllerType);
// Wrap the instance in one or multiple decorators. Note that in reality, the
// decorator is applied by the container, but that doesn't really matter here.
return new MyHttpControllerDecorator(controller);
}
}
My decorator looks like this:
public class MyHttpControllerDecorator : IHttpController {
private readonly IHttpController decoratee;
public MyHttpControllerDecorator(IHttpController decoratee) {
this.decoratee = decoratee;
}
public Task<HttpResponseMessage> ExecuteAsync(
HttpControllerContext controllerContext,
CancellationToken cancellationToken)
{
// this decorator does not add any logic. Just the minimal amount of code to
// reproduce the issue.
return this.decoratee.ExecuteAsync(controllerContext, cancellationToken);
}
}
However, when I run my application and request the ValuesController, Web API throws me the following InvalidCastException:
Unable to cast object of type 'WebApiTest.MyHttpControllerDecorator'
to type 'WebApiTest.Controllers.ValuesController'.
Stacktrace:
at lambda_method(Closure , Object , Object[] )
at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.<>c__DisplayClass13.<GetExecutor>b__c(Object instance, Object[] methodParameters)
at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.Execute(Object instance, Object[] arguments)
at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.<>c__DisplayClass5.<ExecuteAsync>b__4()
at System.Threading.Tasks.TaskHelpers.RunSynchronously[TResult](Func`1 func, CancellationToken cancellationToken)
It's just as if Web API supplies us with the IHttpController abstraction but skips it and still depends on the implementation itself. This would of course be a severe violation of the Dependency Inversion principle and make the abstraction utterly useless. So I'm probably doing something wrong instead.
What I'm I doing wrong? How can I happily decorate my API Controllers?
I would say, that the natural, designed way how to achieve this behaviour in ASP.NET Web API is with the Custom Message Handlers / Delegation Handlers
For example I do have this DelegationHandler in place
public class AuthenticationDelegationHandler : DelegatingHandler
{
protected override System.Threading.Tasks.Task<HttpResponseMessage>
SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
// I. do some stuff to create Custom Principal
// e.g.
var principal = CreatePrincipal();
...
// II. return execution to the framework
return base.SendAsync(request, cancellationToken).ContinueWith(t =>
{
HttpResponseMessage resp = t.Result;
// III. do some stuff once finished
// e.g.:
// SetHeaders(resp, principal);
return resp;
});
}
And this is how to inject that into the structure:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MessageHandlers.Add(new AuthenticationDelegationHandler());
You can work around this by implementing IHttpActionInvoker and "converting" the decorator into the decorated instance at the point that the IHttpController abstraction is no longer relevant.
This is easily done by inheriting from ApiControllerActionInvoker.
(I've hard coded the example and would expect any real world implementation to be more flexible.)
public class ContainerActionInvoker : ApiControllerActionInvoker
{
private readonly Container container;
public ContainerActionInvoker(Container container)
{
this.container = container;
}
public override Task<HttpResponseMessage> InvokeActionAsync(
HttpActionContext actionContext,
CancellationToken cancellationToken)
{
if (actionContext.ControllerContext.Controller is MyHttpControllerDecorator)
{
MyHttpControllerDecorator decorator =
(MyHttpControllerDecorator)actionContext.ControllerContext.Controller;
// decoratee changed to public for the example
actionContext.ControllerContext.Controller = decorator.decoratee;
}
var result = base.InvokeActionAsync(actionContext, cancellationToken);
return result;
}
}
This was registered in Global.asax.cs
GlobalConfiguration.Configuration.Services.Replace(
typeof(IHttpControllerActivator),
new CrossCuttingConcernHttpControllerActivator(container));
GlobalConfiguration.Configuration.Services.Replace(
typeof(IHttpActionInvoker),
new ContainerActionInvoker(container));
Whether you'd actually want to do this is another matter - who knows the ramifications of altering actionContext?
You can provide a custom implementation of IHttpControllerSelector to alter the type instantiated for a particular controller. (Please note I have not tested this to exhaustion)
Update the decorator to be generic
public class MyHttpControllerDecorator<T> : MyHttpController
where T : MyHttpController
{
public readonly T decoratee;
public MyHttpControllerDecorator(T decoratee)
{
this.decoratee = decoratee;
}
public Task<HttpResponseMessage> ExecuteAsync(
HttpControllerContext controllerContext,
CancellationToken cancellationToken)
{
return this.decoratee.ExecuteAsync(controllerContext, cancellationToken);
}
[ActionName("Default")]
public DtoModel Get(int id)
{
return this.decoratee.Get(id);
}
}
Define the custom implementation of IHttpControllerSelector
public class CustomControllerSelector : DefaultHttpControllerSelector
{
private readonly HttpConfiguration configuration;
public CustomControllerSelector(HttpConfiguration configuration)
: base(configuration)
{
this.configuration = configuration;
}
public override HttpControllerDescriptor SelectController(
HttpRequestMessage request)
{
var controllerTypes = this.configuration.Services
.GetHttpControllerTypeResolver().GetControllerTypes(
this.configuration.Services.GetAssembliesResolver());
var matchedTypes = controllerTypes.Where(i =>
typeof(IHttpController).IsAssignableFrom(i)).ToList();
var controllerName = base.GetControllerName(request);
var matchedController = matchedTypes.FirstOrDefault(i =>
i.Name.ToLower() == controllerName.ToLower() + "controller");
if (matchedController.Namespace == "WebApiTest.Controllers")
{
Type decoratorType = typeof(MyHttpControllerDecorator<>);
Type decoratedType = decoratorType.MakeGenericType(matchedController);
return new HttpControllerDescriptor(this.configuration, controllerName, decoratedType);
}
else
{
return new HttpControllerDescriptor(this.configuration, controllerName, matchedController);
}
}
}
When registering the controllers, add in the registration of a decorated version of the controller type
var container = new SimpleInjector.Container();
var services = GlobalConfiguration.Configuration.Services;
var controllerTypes = services.GetHttpControllerTypeResolver()
.GetControllerTypes(services.GetAssembliesResolver());
Type decoratorType = typeof(MyHttpControllerDecorator<>);
foreach (var controllerType in controllerTypes)
{
if (controllerType.Namespace == "WebApiTest.Controllers")
{
Type decoratedType = decoratorType.MakeGenericType(controllerType);
container.Register(decoratedType, () =>
DecoratorBuilder(container.GetInstance(controllerType) as dynamic));
}
else
{
container.Register(controllerType);
}
}
Register the implementation of IHttpControllerSelector
GlobalConfiguration.Configuration.Services.Replace(
typeof(IHttpControllerSelector),
new CustomControllerSelector(GlobalConfiguration.Configuration));
This is the method for creating the Decorated instance
private MyHttpControllerDecorator<T> DecoratorBuilder<T>(T instance)
where T : IHttpController
{
return new MyHttpControllerDecorator<T>(instance);
}
Related
My .Net 7 application has the following issue:
Autofac.Core.DependencyResolutionException: An exception was thrown while activating MyApp.Modules.MyModule.Application.MyModule.UpdateCommand.UpdateCommandHandler.
---> Autofac.Core.DependencyResolutionException: None of the constructors found with 'MyApp.Modules.MyModule.Infrastructure.Configuration.AllConstructorFinder' on type 'MyApp.Modules.MyModule.Application.MyModule.UpdateCommand.UpdateCommandHandler' can be invoked with the available services and parameters:
Cannot resolve parameter 'MyApp.Modules.MyModule.Application.Contracts.IMyModule myModule' of constructor 'Void .ctor(Serilog.ILogger, MyApp.Modules.MyModule.Application.Contracts.IMyModule)'.
UpdateCommandHandler.cs (where the issue is occurring)
public class UpdateCommandHandler: ICommandHandler<UpdateCommand>
{
private readonly IMyModule _myModule;
private readonly ILogger _logger;
public UpdateCommandHandler(ILogger logger, IMyModule myModule)
{
_myModule = myModule;
_logger = logger;
}
public async Task<Unit> Handle(UpdateCommand request, CancellationToken cancellationToken)
{
var foo = await _myModule.ExecuteQueryAsync(new SampleQuery());
return Unit.Value;
}
}
Program.cs
...
var builder = WebApplication.CreateBuilder(args);
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
builder.Host.ConfigureContainer<ContainerBuilder>(b => b.RegisterModule(new AutofacModules()));
...
I looked at similar issues before posting, such as this, but I do believe I appropriately registered IMyModule in Autofac as MyModule in the following.
AutofacModules.cs
public class AutofacModules: Autofac.Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<MyModule>().As<IMyModule>().InstancePerLifetimeScope();
}
}
IMyModule.cs
public interface IMyModule
{
Task ExecuteCommandAsync(ICommand command);
Task<TResult> ExecuteQueryAsync<TResult>(IQuery<TResult> query);
}
MyModule.cs
public class MyModule: IMyModule
{
public async Task ExecuteCommandAsync(ICommand command)
{
await CommandsExecutor.Execute(command);
}
public Task<TResult> ExecuteQueryAsync<TResult>(IQuery<TResult> query)
{
var scope = MyCompositionRoot.BeginLifetimeScope();
var mediator = scope.Resolve<IMediator>();
return mediator.Send(query);
}
}
AllConstructorFinder.cs
internal class AllConstructorFinder : IConstructorFinder
{
private static readonly ConcurrentDictionary<Type, ConstructorInfo[]> Cache = new();
public ConstructorInfo[] FindConstructors(Type targetType)
{
var result = Cache.GetOrAdd(targetType, t => t.GetTypeInfo().DeclaredConstructors.ToArray());
return result.Length > 0 ? result : throw new NoConstructorsFoundException(targetType);
}
}
In my Program.cs, I had registered MyModule, but, as I have multiple modules with their own containers, I didn't register it in the module's own composition root. By adding the following line, I'm able to include MyModule as a constructor parameter.
MyModuleStartup.cs
...
var containerBuilder = new ContainerBuilder();
...
/* NEW LINE */
containerBuilder.RegisterType<CventModule>().As<ICventModule>().InstancePerLifetimeScope();
...
So lesson here is make sure the component you are using is registered to Autofac root container your module is running directly in. Thanks to #Travis Illig for the troubleshooting link which helped me immensely.
There is an option to add new OutputFormatters globally for all controllers, but how to add OutputFormatters for selected Action?
builder.Services.AddControllers(options =>
{
options.OutputFormatters.Insert(0, new CsvOutputFormatter(new CsvFormatterOptions { CsvDelimiter = "," }));
})
I have found IResultFilter and it has the method OnResultExecuting where formatted can be added. After that, I can decorate the Action with a new ResultFilter attribute. Is this the correct way to assign custom formats for certain actions?
public class CsvResultFilter : IResultFilter
{
public void OnResultExecuting(ResultExecutingContext context)
{
if (context.Result is ObjectResult objectResult)
{
var csvOutputFormatter = new CsvOutputFormatter(new CsvFormatterOptions { CsvDelimiter = "," });
objectResult.Formatters.Add(csvOutputFormatter);
}
}
public void OnResultExecuted(ResultExecutedContext context)
{
}
}
Another problem I am facing is if I decorate Action with attribute I get an exception that the service is not registered.
[ServiceFilter(typeof(CsvResultFilter))]
public async Task<ActionResult> Statistic([FromQuery] ExportStatisticParams model, CancellationToken cancellationToken)
{
}
If I register it in builder.Services.AddControllers(options => options.Filters.Add(typeof(CsvResultFilter))) the CsvResultFilter filter gets executed on all actions.
There's a couple of things you need to do to make this work. If you really need to use ServiceFilterAttribute then you need to ensure:
The output formatter type registered in the DI container (e.g. builder.Services.AddSingleton<CsvOutputFormatter>();
The formatter must also implement IFilterMetadata.
However, there's another way which I think is a little cleaner. You can create your own attribute that inherits from ActionFilterAttribute. Something like this:
public class CsvOutputAttribute : ActionFilterAttribute
{
public override void OnResultExecuting(ResultExecutingContext context)
{
if (context.Result is ObjectResult objectResult)
{
// You could also use DI here with
// context.HttpContext.RequestServices.GetService<CsvOutputFormatter>();
var formatter = new CsvOutputFormatter(...);
//objectResult.Formatters.Clear(); //<- You may want to add this?
objectResult.Formatters.Add(formatter);
}
}
}
And decorate your action or controller with it:
[CsvOutput]
public IActionResult MyAction()
{
return new ObjectResult("hello");
}
I have a use case for HttpMessageHandler that requires new options on instantiation. I believe that IHttpMessageHandlerFactory.CreateHandler is the correct API here, but I'm not sure if this usage is correct:
public class MyDelegatingHandler : DelegatingHandler {
public MyDelegatingHandler(
HttpMessageHandler internalHandler
)
{
_internalHandler = internalHandler;
}
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken
)
{
// custom logic
return await _internalHandler.SendAsync(request, cancellationToken).ConfigureAwait(false);
}
private HttpMessageHandler _internalHandler;
}
public class MyHttpClientFactory {
public MyHttpClientFactory(
IHttpMessageHandlerFactory factory
)
{
_factory = factory;
}
public HttpClient CreateClient(
// arguments
)
{
return new HttpClient(
new MyDelegatingHandler(_factory.CreateHandler()),
disposeHandler: false
);
}
private IHttpMessageHandlerFactory _factory;
}
Assuming MyDelegatingHandler has no state that needs to be disposed on its own, will this correctly use the lifetime management features of HttpClientFactory injected through the IHttpMessageHandlerFactory dependency?
As the heading tells.
Let's say I register a strongly typed client like
var services = new ServiceCollection();
//A named client is another option that could be tried since MSDN documentation shows that being used when IHttpClientFactory is injected.
//However, it appears it gives the same exception.
//services.AddHttpClient("test", httpClient =>
services.AddHttpClient<TestClient>(httpClient =>
{
httpClient.BaseAddress = new Uri("");
});
.AddHttpMessageHandler(_ => new TestMessageHandler());
//Registering IHttpClientFactory isn't needed, hence commented.
//services.AddSingleton(sp => sp.GetRequiredService<IHttpClientFactory>());
var servicesProvider = services.BuildServiceProvider(validateScopes: true);
public class TestClient
{
private IHttpClientFactory ClientFactory { get; }
public TestClient(IHttpClientFactory clientFactory)
{
ClientFactory = clientFactory;
}
public async Task<HttpResponseMessage> CallAsync(CancellationToken cancellation = default)
{
//using(var client = ClientFactory.CreateClient("test"))
using(var client = ClientFactory.CreateClient())
{
return await client.GetAsync("/", cancellation);
}
}
}
// This throws with "Message: System.InvalidOperationException : A suitable constructor
// for type 'Test.TestClient' could not be located. Ensure the type is concrete and services
// are registered for all parameters of a public constructor.
var client = servicesProvider.GetService<TestClient>();
But as noted in the comments, an exception will be thrown. Do I miss something bovious or is this sort of an arrangement not possible?
<edit: If IHttpClientFactory is registered, a NullReferenceException is thrown while trying to resolve client. Strange, strange.
<edit 2: The scenario I'm thinking to avoid is described and discussed also at https://github.com/aspnet/Extensions/issues/924 and maybe the way written there is one, perhaps not as satisfactory, way of avoiding some problems.
This happens in a XUnit project, probably doesn't have anything to do with the problem, but who knows. :)
<edit 3: A console program to show the problem.
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace TypedClientTest
{
public class TestClient
{
private IHttpClientFactory ClientFactory { get; }
public TestClient(IHttpClientFactory clientFactory)
{
ClientFactory = clientFactory;
}
public async Task<HttpResponseMessage> TestAsync(CancellationToken cancellation = default)
{
using (var client = ClientFactory.CreateClient())
{
return await client.GetAsync("/", cancellation);
}
}
}
class Program
{
static void Main(string[] args)
{
var services = new ServiceCollection();
services
.AddHttpClient<TestClient>(httpClient => httpClient.BaseAddress = new Uri("https://www.github.com/"));
var servicesProvider = services.BuildServiceProvider(validateScopes: true);
//This throws that there is not a suitable constructor. Should it?
var client = servicesProvider.GetService<TestClient>();
}
}
}
with install-package Microsoft.Extensions.Http and install-package Microsoft.Extensions.DependencyInjection.
Also in the exception stack there reads
at System.Threading.LazyInitializer.EnsureInitializedCore[T](T& target, Boolean& initialized, Object& syncLock, Func1 valueFactory)
at Microsoft.Extensions.Http.DefaultTypedHttpClientFactory1.Cache.get_Activator()
at ** Microsoft.Extensions.Http.DefaultTypedHttpClientFactory1.CreateClient(HttpClient httpClient) **
at System.Threading.LazyInitializer.EnsureInitializedCore[T](T& target, Boolean& initialized, Object& syncLock, Func1 valueFactory)
at Microsoft.Extensions.Http.DefaultTypedHttpClientFactory1.Cache.get_Activator()
at Microsoft.Extensions.Http.DefaultTypedHttpClientFactory1.CreateClient(HttpClient httpClient)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitTransient(TransientCallSite transientCallSite, ServiceProviderEngineScope scope)
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetService[T](IServiceProvider provider)
at TypedClientTest.Program.Main(String[] args) in C:\projektit\testit\TypedClientTest\TypedClientTest\Program.cs:line 40
which of course points towards the problem. But needs probably further debugging.
<edit 4: So going to the source, the problem is visible at https://github.com/aspnet/Extensions/blob/557995ec322f1175d6d8a72a41713eec2d194871/src/HttpClientFactory/Http/src/DefaultTypedHttpClientFactory.cs#L47 and in https://github.com/aspnet/Extensions/blob/11cf90103841c35cbefe9afb8e5bf9fee696dd17/src/HttpClientFactory/Http/src/DependencyInjection/HttpClientFactoryServiceCollectionExtensions.cs in general.
There are probably some ways to go about this now. :)
<edit 5:
So it appears calling .AddHttpClient for a typed client that has IHttpClientFactory one ends up in a "weird place". And indeed, it's not possible to use IHttpClientFactory to create a typed client of its own type.
One way of making this with named client could be something like
public static class CustomServicesCollectionExtensions
{
public static IHttpClientBuilder AddTypedHttpClient<TClient>(this IServiceCollection serviceCollection, Action<HttpClient> configureClient) where TClient: class
{
//return serviceCollection.Add(new ServiceDescriptor(typeof(TClient).Name, f => new ...,*/ ServiceLifetime.Singleton));
servicesCollection.AddTransient<TClient>();
return serviceCollection.AddHttpClient(typeof(TType).Name, configureClient);
}
}
public static class HttpClientFactoryExtensions
{
public static HttpClient CreateClient<TClient>(this IHttpClientFactory clientFactory)
{
return clientFactory.CreateClient(typeof(TClient).Name);
}
}
public class TestClient
{
private IHttpClientFactory ClientFactory { get; }
public TestClient(IHttpClientFactory clientFactory)
{
ClientFactory = clientFactory;
}
public async Task<HttpResponseMessage> Test(CancellationToken cancellation = default)
{
using(var client = ClientFactory.CreateClient<TestClient>())
{
return await client.GetAsync("/", cancellation);
}
}
}
Which mimicks what the extension methods already do. It's of course now possible to expose the lifetime services better too.
Please read about Typed clients:
A typed client accepts a HttpClient parameter in its constructor
Instead of IHttpClientFactory your class should accept an HttpClient in its constructor, which will be provided by DI (enabled with the AddHttpClient extension).
public class TestClient
{
private HttpClient Client { get; }
public TestClient(HttpClient client)
{
Client = client;
}
public Task<HttpResponseMessage> CallAsync(CancellationToken cancellation = default)
{
return client.GetAsync("/", cancellation);
}
}
Edit
(based on above edits)
If you want to override the default behavior of the AddHttpClient extension method, then you should register your implementation directly:
var services = new ServiceCollection();
services.AddHttpClient("test", httpClient =>
{
httpClient.BaseAddress = new Uri("https://localhost");
});
services.AddScoped<TestClient>();
var servicesProvider = services.BuildServiceProvider(validateScopes: true);
using (var scope = servicesProvider.CreateScope())
{
var client = scope.ServiceProvider.GetRequiredService<TestClient>();
}
public class TestClient
{
private IHttpClientFactory ClientFactory { get; }
public TestClient(IHttpClientFactory clientFactory)
{
ClientFactory = clientFactory;
}
public Task<HttpResponseMessage> CallAsync(CancellationToken cancellation = default)
{
using (var client = ClientFactory.CreateClient("test"))
{
return client.GetAsync("/", cancellation);
}
}
}
I have a Web API 2 project that is implementing a custom IAuthenticationFilter such as the following.
My problem is the UnitOfWork is not injected by Unity in the BasicAuthenticator class. As expected Unity successfully injects UnitOfWork in Controllers.
public class BasicAuthenticator : Attribute, IAuthenticationFilter
{
[Dependency]
public UnitOfWork UoW { get; set; }
public bool AllowMultiple { get { return false; } }
public Task AuthenticateAsync(HttpAuthenticationContext context,
CancellationToken cancellationToken)
{
// ignore missing implementation
context.Principal = new ClaimsPrincipal(new[] { id });
return Task.FromResult(0);
}
public Task ChallengeAsync(HttpAuthenticationChallengeContext context,
CancellationToken cancellationToken) {}
}
You will need to create a custom IFilterProvider that will perform a BuildUp() on the applicable filters. The BuildUp operation will inject all dependencies into the filter.
Here is a UnityFilterProvider that does that:
public class UnityFilterProvider : ActionDescriptorFilterProvider, IFilterProvider
{
private readonly IUnityContainer container;
public UnityFilterProvider(IUnityContainer container)
{
this.container = container;
}
public new IEnumerable<FilterInfo> GetFilters(
HttpConfiguration configuration,
HttpActionDescriptor actionDescriptor)
{
var filters = base.GetFilters(configuration, actionDescriptor);
foreach (var filter in filters)
{
container.BuildUp(filter.Instance.GetType(), filter.Instance);
}
return filters;
}
}
Next at application startup you need to replace the default filter provider with the custom provider above:
GlobalConfiguration.Configuration.Services.Add(
typeof(System.Web.Http.Filters.IFilterProvider),
new UnityFilterProvider(container));
var providers = GlobalConfiguration.Configuration.Services.GetFilterProviders()
.ToList();
var defaultprovider = providers.First(p => p is ActionDescriptorFilterProvider);
GlobalConfiguration.Configuration.Services.Remove(
typeof(System.Web.Http.Filters.IFilterProvider),
defaultprovider);
I usually use the Unity bootstrapper for ASP.NET Web API so I put the above code in the UnityConfig.cs after RegisterTypes().