I'm exploring the use of DryIoc in a .NET WebAPI application and have noticed a strange behavior with the initialization steps. In a simple test webapi application, I have the following DryIoc registration class which gets called immediately after the WebApi config registration.
public class DryIocConfig
{
public static void Register(HttpConfiguration config)
{
var c = new Container().WithWebApi(config);
c.Register<IWidgetService, WidgetService>(Reuse.Singleton);
c.Register<IWidgetRepository, WidgetRepository>(Reuse.Singleton);
}
}
And the following WebApi controller:
public class ValuesController : ApiController
{
private readonly IWidgetService _widgetService;
public ValuesController(IWidgetService widgetService)
{
_widgetService = widgetService;
}
// GET api/values
public IEnumerable<Widget> Get()
{
return _widgetService.GetWidgets();
}
}
This seems to work fine, but in experimenting with was seems to me to be the identical, but written a little more verbose, code I get an error.
public class DryIocConfig
{
public static void Register(HttpConfiguration config)
{
var c = new Container();
c.WithWebApi(config); // Now separate statement rather than chained.
c.Register<IWidgetService, WidgetService>(Reuse.Singleton);
c.Register<IWidgetRepository, WidgetRepository>(Reuse.Singleton);
}
}
The exception I get is the following (as JSON):
{
"Message" : "An error has occurred.",
"ExceptionMessage" : "An error occurred when trying to create a controller of type 'ValuesController'. Make sure that the controller has a parameterless public constructor.",
"ExceptionType" : "System.InvalidOperationException",
"StackTrace" : " at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)\r\n at System.Web.Http.Controllers.HttpControllerDescriptor.CreateController(HttpRequestMessage request)\r\n at System.Web.Http.Dispatcher.HttpControllerDispatcher.<SendAsync>d__1.MoveNext()",
"InnerException" : {
"Message" : "An error has occurred.",
"ExceptionMessage" : "Type 'IOCContainerTest.DryLoc.Controllers.ValuesController' does not have a default constructor",
"ExceptionType" : "System.ArgumentException",
"StackTrace" : " at System.Linq.Expressions.Expression.New(Type type)\r\n at System.Web.Http.Internal.TypeActivator.Create[TBase](Type instanceType)\r\n at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.GetInstanceOrActivator(HttpRequestMessage request, Type controllerType, Func`1& activator)\r\n at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)"
}
}
Is this something odd with DryIoc, or is this a C# nuance that I've just never come across?
This is because .WithWebApi() is an extension method per the source.
public static IContainer WithWebApi(this IContainer container, HttpConfiguration config,
IEnumerable<Assembly> controllerAssemblies = null, IScopeContext scopeContext = null,
Func<Type, bool> throwIfUnresolved = null)
{
container.ThrowIfNull();
if (container.ScopeContext == null)
container = container.With(scopeContext: scopeContext ?? new AsyncExecutionFlowScopeContext());
container.RegisterWebApiControllers(config, controllerAssemblies);
container.SetFilterProvider(config.Services);
InsertRegisterRequestMessageHandler(config);
config.DependencyResolver = new DryIocDependencyResolver(container, throwIfUnresolved);
return container;
}
In the first syntax example, you create a new instance of Container and pass that newly created instance to .WithWebApi(). This in turn updates the container instance and finally returns it back to variable c.
In the second syntax example, you are never returning the value of the extension method BACK to the original variable, updating it. You are calling it as if it were a void method, which does nothing in this case, hence the exception.
If you had instead written:
var c = new Container();
c = c.WithWebApi(config);
it would have essentially been a more verbose example of the first syntax and would have properly updated c with the new functionality.
This seems fine and should work. Obviously the container returned from ccontainer.WithWebApi(config); is not the original container, which is unexpected at this point and therefore at least a code smell, since it gives rise to possible bugs. Better write a couple of unit tests and open an issue at the DryIoC Bitbucket site.
To help you out, here is an template of how to write such tests. Create a new test project and do the following:
Install NuGet Packages
Install-Package Microsoft.AspNet.WebApi.OwinSelfHost
Install-Package DryIoc.WebApi.Owin.dll
Install-Package Xunit
Controller
public sealed class ValuesController : ApiController
{
private IWidgetService WidgetService { get; }
public ValuesController(IWidgetService widgetService)
{
if(widgetService == null)
throw new ArgumentNullException(nameof(widgetService));
WidgetService = widgetService;
}
[HttpGet]
public IEnumerable<Widget> Get()
{
return WidgetService.GetWidgets().ToArray();
}
}
OWIN Startup
public sealed class Startup
{
public void Configuration(IAppBuilder app)
{
var config = new HttpConfiguration()
.ConfigureRouting()
.ConfigureDependencyInjection();
app.UseWebApi(config);
// protip: use OWIN error page instead of ASP.NET yellow pages for better diagnostics
// Install-Package Microsoft.Owin.Diagnostics
// app.UseErrorPage(ErrorPageOptions.ShowAll);
}
private static HttpConfiguration ConfigureRouting(this HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "default",
routeTemplate: "/",
defaults: new { controller = "values" });
return config;
}
private static HttpConfiguration ConfigureDependencyInjection(this HttpConfiguration config)
{
new Container()
.Register<IWidgetService, WidgetService>(Reuse.Singleton)
.Register<IWidgetRepository, WidgetRepository>(Reuse.Singleton)
.WithWebApi(config);
return config;
}
}
Unit Test
[Fact]
public async Task ShouldFoo()
{
const string baseAddress = "http://localhost:8000";
using (WebApp.Start<Startup>(baseAddress))
{
var httpclient = new HttpClient
{
BaseAddress = new Uri(baseAddress)
};
var response = await httpclient.GetAsync("/");
Assert.That(...);
}
}
Related
I have a Web API 2 project with some predefined Controllers and I'm creating the routes using attribute routing.
I have another class library with a web API 2 custom controller and loaded dynamically once its requested;
But the problem is the attribute routing on the above custom controller is not considered at all. Is there any way to inject the custom controller routes to the main Web API project?
Following is the way I'm loading the custom controller to web API project;
Using a custom http controller selector if a requested controller is not found on the Web API project I load the class library and try to load the requested controller from it.
CustomHttpControllerSelector.cs
public class CustomHttpControllerSelector : DefaultHttpControllerSelector
{
private readonly HttpConfiguration _configuration;
public CustomHttpControllerSelector(HttpConfiguration configuration) : base(configuration)
{
_configuration = configuration;
}
public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
HttpControllerDescriptor controller;
try
{
//Select the requested controller
controller = base.SelectController(request);
}
catch (Exception)
{
//Try to load the requested controller from the custom dll
controller = GetCustomContoller(request);
}
return controller;
}
private HttpControllerDescriptor GetCustomContoller(HttpRequestMessage request)
{
HttpControllerDescriptor controller;
try
{
string controllerName = base.GetControllerName(request);
string controllerFormatName = string.Format("{0}Controller", controllerName);
string applicationPath = string.Format(#"{0}\PlugIns\CustomModules.dll", HostingEnvironment.ApplicationPhysicalPath);
Assembly assembly = Assembly.LoadFile(applicationPath);
if (assembly == null)
throw new HttpResponseException(request.CreateErrorResponse(HttpStatusCode.InternalServerError, "CustomModules.dll not found."));
Type controllerType = assembly.GetTypes()
.Where(i => typeof(IHttpController).IsAssignableFrom(i))
.FirstOrDefault(i => i.Name.Equals(controllerFormatName, StringComparison.OrdinalIgnoreCase));
if (controllerType == null)
throw new HttpResponseException(request.CreateErrorResponse(HttpStatusCode.InternalServerError, string.Format("{0} not found.", controllerFormatName)));
controller = new HttpControllerDescriptor(_configuration, controllerName, controllerType);
}
catch (Exception)
{
throw;
}
return controller;
}
}
WebApiConfig.cs
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
config
.Services
.Replace(typeof(IHttpControllerSelector), new CustomHttpControllerSelector(config));
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
Custom Controller - AdministrationController.cs
[RoutePrefix("Administration")]
public class AdministrationController : ApiController
{
[HttpGet, Route("")]
public IEnumerable<string> Get()
{
return new List<string>() { "Item 01", "Item 02", "Item 03" };
}
[HttpGet, Route("Access")]
public HttpResponseMessage GetAccess()
{
return Request.CreateResponse(HttpStatusCode.OK, "Access Granted.");
}
[HttpGet, Route("Info")]
public DataTable GetInfo()
{
return new DataTable();
}
}
The AdministrationController is not available in the original web API project so when I issue a GET request like http://localhost:53076/Administration the AdministrationController should be loaded from CustomModules.all and the method Get should be executed. But instead of the list expected, I'm getting a message as follows;
"Message": "An error has occurred.",
"ExceptionMessage": "Multiple actions were found that match the request:
Get on type CustomModules.AdministrationController
GetAccess on type CustomModules.AdministrationController
GetInfo on type CustomModules.AdministrationController"
This clearly indicates that the attribute routing I provided is not considered. Any fix for this?
I have integrated dependency injection with code first entity framework. But when I call method of api that time I am getting error.
System.InvalidOperationException: An error occurred when trying to create a controller of type 'DrugController'. Make sure that the controller has a parameterless public constructor.
stackTrace:
at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)
at System.Web.Http.Controllers.HttpControllerDescriptor.CreateController(HttpRequestMessage request)
at System.Web.Http.Dispatcher.HttpControllerDispatcher.<SendAsync>d__1.MoveNext()
innerException:
System.ArgumentException: Type 'SA_Admin.Controllers.DrugController' does not have a default constructor
stackTrace:
at System.Linq.Expressions.Expression.New(Type type)
at System.Web.Http.Internal.TypeActivator.Create[TBase](Type instanceType)
at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.GetInstanceOrActivator(HttpRequestMessage request, Type controllerType, Func`1& activator)
at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)
but when I remove constructor with parameter then I can call with out error.
I cross check every file but does not getting error. What I am missing? Also all method of account working perfectly because controller is with out constructor.
Project is only web api project. I did not add mvc.
I used Autofac DI nuget package.
suggest me solution.
Startup.cs
public void Configuration(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
ConfigureOAuth(app);
WebApiConfig.Register(config);
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
app.UseWebApi(config);
IoCConfig.RegisterDependencies();
}
IOCConfig.cs
public class IoCConfig
{
/// <summary>
/// For more info see
/// :http://docs.autofac.org/en/latest/integration/mvc.html
/// </summary>
public static void RegisterDependencies()
{
#region Create the builder
var builder = new ContainerBuilder();
#endregion
#region Register all authentication dependencies
// REGISTER DEPENDENCIES
builder.RegisterType<SA_AdminEntities>().AsSelf().InstancePerRequest();
#endregion
#region Register all web api controllers for the assembly
builder.RegisterApiControllers(typeof(Startup).Assembly);
#endregion
#region Register modules
builder.RegisterModule(new BusinessLogicInstaller());
builder.RegisterModule(new ReposInstaller());
builder.RegisterModule(new ServicesInstaller());
#endregion
#region Set the dependency resolver to be Autofac
var container = builder.Build();
//for WebApi
GlobalConfiguration.Configuration.DependencyResolver = new AutofacWebApiDependencyResolver(container);
#endregion
}
}
ServiceInstaller.cs
public class ServicesInstaller : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterAssemblyTypes(typeof(DrugService).Assembly)
.Where(t => t.Name.EndsWith("Service"))
.AsImplementedInterfaces()
.InstancePerRequest();
}
}
RepoInstaller.cs
public class ReposInstaller : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<UnitOfWork>().As<IUnitOfWork>().InstancePerRequest();
builder.RegisterType<DbFactory>().As<IDbFactory>().InstancePerRequest();
#region Repositories
builder.RegisterAssemblyTypes(typeof(DrugRepo).Assembly)
.Where(t => t.Name.EndsWith("Repo"))
.AsImplementedInterfaces().InstancePerRequest();
#endregion
}
}
BusinessLogicInstaller.cs
public class BusinessLogicInstaller : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<ParamEncryption>().As<IParamEncryption>().InstancePerRequest();
builder.RegisterType<Mapper>().As<IMapper>().InstancePerRequest();
}
}
WebApiConfig.cs
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
config.Filters.Add(new ErrorLogApiHandler());
var constraintResolver = new DefaultInlineConstraintResolver
{
ConstraintMap = { ["apiVersion"] = typeof(ApiVersionRouteConstraint) }
};
//removes xml formatter
config.Formatters.Remove(config.Formatters.XmlFormatter);
//enable json formatters
config.Formatters.JsonFormatter.SerializerSettings.ContractResolver =
new CamelCasePropertyNamesContractResolver();
//TODO Turn off move to dev and prod
//enable cors
//config.EnableCors(new EnableCorsAttribute("*", "*", "*"));
//add api versioning
ApiVersion apiVersion = new ApiVersion(1, 0);
config.AddApiVersioning(options =>
{
//will return the headers "api-supported-versions" and "api-deprecated-versions"
options.ReportApiVersions = true;
//TODO define api controllers here with versions
//Like below Example
options.Conventions.Controller<DrugController>().HasApiVersion(apiVersion);
});
//enable attribute routing
config.MapHttpAttributeRoutes(constraintResolver);
}
}
ApiController
[ApiVersion("1.0")]
[RoutePrefix("api/v{version:apiVersion}/Public")]
public class DrugController : ApiController
{
#region Variables
private readonly IDrugService drugService;
private readonly IPersistService persistService;
private readonly IParamEncryption encryptor;
#endregion
#region Constructor
public DrugController(IDrugService drugService, IPersistService persistService, IParamEncryption encryptor)
{
this.drugService = drugService;
this.persistService = persistService;
this.encryptor = encryptor;
}
[Route("Drugs")]
[HttpGet]
public IHttpActionResult Drugs()
{
HttpResponseMessage resp;
try
{
var lstdrugs = drugService.GetDrugs(xx => true);
resp = Request.CreateResponse(HttpStatusCode.OK, lstdrugs);
}
catch (Exception ex)
{
resp = Request.CustomHandleError(ex);
}
return ResponseMessage(resp);
}
#endregion
}
This is the same error you would get if you didn't register one of the parameters with the autofac container. The actual error may be several layers of inner exceptions down. I only see IParamEncryption registered. Register the other two interfaces.
See this link to solve your problem, mainly it is DI registration and resolver problem.
See the below post for more information.
https://stackoverflow.com/a/24257085/7404931
I'm new to Web API/MVC, Autofac, and DI so I'm sure I've got a mess on my hands.
I have a controller in which I am trying to inject a service interface dependency.
[RoutePrefix("api/gameboard")]
public class GameBoardController : BaseApiController
{
private readonly IGameBoardService _service;
private ApplicationDbContext _con = new ApplicationDbContext();
public GameBoardController(IGameBoardService service)
{
_service = service;
}
/*
Routes
*/
}
The controller implements a base controller:
public class BaseApiController : ApiController
{
private ModelFactory _modelFactory;
private ApplicationUserManager _AppUserManager = null;
private ApplicationRoleManager _AppRoleManager = null;
protected ApplicationUserManager AppUserManager
{
get
{
return _AppUserManager ?? Request.GetOwinContext().GetUserManager<ApplicationUserManager>();
}
}
public BaseApiController()
{
}
protected ModelFactory TheModelFactory
{
get
{
if (_modelFactory == null)
{
_modelFactory = new ModelFactory(this.Request, this.AppUserManager);
}
return _modelFactory;
}
}
protected IHttpActionResult GetErrorResult(IdentityResult result)
{
if (result == null)
{
return InternalServerError();
}
if (!result.Succeeded)
{
if (result.Errors != null)
{
foreach (string error in result.Errors)
{
ModelState.AddModelError("", error);
}
}
if (ModelState.IsValid)
{
// No ModelState errors are available to send, so just return an empty BadRequest.
return BadRequest();
}
return BadRequest(ModelState);
}
return null;
}
protected ApplicationRoleManager AppRoleManager
{
get
{
return _AppRoleManager ?? Request.GetOwinContext().GetUserManager<ApplicationRoleManager>();
}
}
}
When making any call to a route in the GameBoardController that uses the _service , I get the following error:
{
"message": "An error has occurred.",
"exceptionMessage": "An error occurred when trying to create a controller of type 'GameBoardController'. Make sure that the controller has a parameterless public constructor.",
"exceptionType": "System.InvalidOperationException",
"stackTrace": " at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)\r\n at System.Web.Http.Controllers.HttpControllerDescriptor.CreateController(HttpRequestMessage request)\r\n at System.Web.Http.Dispatcher.HttpControllerDispatcher.<SendAsync>d__1.MoveNext()",
"innerException": {
"message": "An error has occurred.",
"exceptionMessage": "Type 'LearningAngular.Api.Controllers.GameBoardController' does not have a default constructor",
"exceptionType": "System.ArgumentException",
"stackTrace": " at System.Linq.Expressions.Expression.New(Type type)\r\n at System.Web.Http.Internal.TypeActivator.Create[TBase](Type instanceType)\r\n at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.GetInstanceOrActivator(HttpRequestMessage request, Type controllerType, Func`1& activator)\r\n at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)"
}
}
If I make a call to a route that does NOT use the service, it works fine.
I'm using Autofac to handle my DI and I have tried countless different attempts to get IGameBoardService registered for use; to the point that I have pretty much exhausted anything I could think to search on SO or Google.
Of course, if I do what the error says and add the parameterless constructor, the error goes away, but _service is always null.
Currently, this is how I have my Autofac configured. I have a config class to handle all of the registrations:
public class AutofacConfig
{
public static IContainer RegisterAutoFac()
{
var builder = new ContainerBuilder();
AddMvcRegistrations(builder);
AddRegisterations(builder);
var container = builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
GlobalConfiguration.Configuration.DependencyResolver = new AutofacWebApiDependencyResolver(container);
return container;
}
private static void AddMvcRegistrations(ContainerBuilder builder)
{
//mvc
builder.RegisterControllers(Assembly.GetExecutingAssembly());
builder.RegisterAssemblyModules(Assembly.GetExecutingAssembly());
builder.RegisterModelBinders(Assembly.GetExecutingAssembly());
builder.RegisterModelBinderProvider();
//web api
builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly()).PropertiesAutowired();
builder.RegisterModule<AutofacWebTypesModule>();
}
private static void AddRegisterations(ContainerBuilder builder)
{
builder.RegisterType<GameBoardService>().As<IGameBoardService>();
builder.RegisterModule(new StandardModule());
}
}
And the StandardModule is as follows:
public class StandardModule : Module
{
protected override void Load(ContainerBuilder builder)
{
base.Load(builder);
// obtain database connection string once and reuse by Connection class
var conn = ConfigurationManager.ConnectionStrings["DBConnection"];
// Register Connection class and expose IConnection
// by passing in the Database connection information
builder.RegisterType<Connection>() // concrete type
.As<IConnection>() // abstraction
.WithParameter("settings", conn)
.InstancePerLifetimeScope();
// Register Repository class and expose IRepository
builder.RegisterType<Repository>()
.As<IRepository>()
.InstancePerLifetimeScope();
builder.RegisterType<GameBoardService>()
.As<IGameBoardService> ()
.InstancePerLifetimeScope();
}
}
Then in my WebApiConfig I make a call to AutofacConfig.RegisterAutoFac();
If I put a breakpoint in the AutofacConfig, it gets hit on startup so I know it's running through it. From all of the information I have gathered, I think I have everything I need, but obviously I can't get it to work. It's probably my unfamiliarity with everything that has me missing something, but I'm at a loss. I've following examples and tutorials and multiple SO threads, but nothing works.
What am I missing here in order to make _service usable in my controller?
Extra Information - I don't know if it is needed or not, but here is my GameBoardService and its interface:
public class GameBoardService : IGameBoardService
{
private readonly IRepository _repo;
private GameBoardHelper gameBoard;
private Cache cache = new Cache();
public GameBoardService(IRepository repo)
{
_repo = repo;
}
public bool createGameBoard()
{
gameBoard = new GameBoardHelper();
cache.insertCacheItem("GameBoard", gameBoard);
return true;
}
public List<Card> playCard(int slot, Card card)
{
gameBoard = (GameBoardHelper)cache.getCacheItemByName("GameBoard");
return gameBoard.playCard(slot, card);
}
public bool setHand(int player, List<Card> cardList)
{
gameBoard = (GameBoardHelper)cache.getCacheItemByName("GameBoard");
gameBoard.setHand(player, cardList);
return true;
}
public int getTurn()
{
gameBoard = (GameBoardHelper)cache.getCacheItemByName("GameBoard");
return gameBoard.turn;
}
public void setTurn(int player)
{
gameBoard = (GameBoardHelper)cache.getCacheItemByName("GameBoard");
gameBoard.turn = player;
}
public Slot opponentTurn()
{
gameBoard = (GameBoardHelper)cache.getCacheItemByName("GameBoard");
return gameBoard.opponentTurn();
}
public async Task<IEnumerable<GameBoard>> GetGameBoardAsync()
{
// execute the stored procedure called GetEmployees
return await _repo.WithConnection(async c =>
{
// map the result from stored procedure to Employee data model
var results = await c.QueryAsync<GameBoard>("GetEmployees", commandType: CommandType.StoredProcedure);
return results;
});
}
}
public interface IGameBoardService
{
Task<IEnumerable<GameBoard>> GetGameBoardAsync();
bool createGameBoard();
List<Card> playCard(int slot, Card card);
bool setHand(int player, List<Card> cardList);
int getTurn();
void setTurn(int player);
Slot opponentTurn();
}
As christophe.chapron mentioned, you need to register you API controllers separately to the MVC controllers with the following line:
builder.RegisterApiControllers(Assembly.GetExecutingAssembly‌​());
as described in documentation.
I have followed the below sample and it works fine until I have replaced the ProductsContext class by my own LDAPConnector class that need to get data from AD and since it's not working and get the Parameterless error.
http://www.asp.net/web-api/overview/advanced/dependency-injection
What I should add in the webAPIConfig.cs in order to register this new class ?
I have try this but it's not working:
container.RegisterType<ILDAPConnector, LDAPConnector>(new HierarchicalLifetimeManager());
I have also tried to add parameterless construction in both Controller and in the LDAPConnector class but still get the same issue.
The controller
public class ADAccountController : ApiController
{
IADAccountRepository _repository;
public ADAccountController() : base()
{
}
public ADAccountController(IADAccountRepository repository)
{
_repository = repository;
}
public IHttpActionResult GetByID(string id)
{
try
{
if (!string.IsNullOrWhiteSpace(id))
{
AccountAD contact = _repository.GetByID(id);
if (contact == null)
{
return NotFound();
}
return Ok(contact);
the Repository where I get the error on the LDAPConnector connector = new LDAPConnector();
public class ADAccountRepository : IADAccountRepository
{
static ConcurrentDictionary<string, AccountAD> _todos = new ConcurrentDictionary<string, AccountAD>();
private LDAPConnector connector = new LDAPConnector();
public ADAccountRepository()
{
Add(new AccountAD { Name = "Item1" });
}
public AccountAD GetByID(string id)
{
return connector.GetAccountDetails(id);
//AccountAD item;
//_todos.TryGetValue(id, out item);
//return item;
}
The ADConnector
public class LDAPConnector : ILDAPConnector
{
public LDAPConnector ()
{
}
public AccountAD GetAccountDetails(string id)
{
AccountAD _AccountDetails = new AccountAD();
List<SearchResult> searchResult = new List<SearchResult>();
finally the webapiconfig
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
var container = new UnityContainer();
container.RegisterType<IADAccountRepository, ADAccountRepository>(new HierarchicalLifetimeManager());
container.RegisterType<ILDAPConnector, LDAPConnector>(new HierarchicalLifetimeManager());
//container.RegisterType<LDAPConnector>(new InjectionFactory(c => new LDAPConnector()));
config.DependencyResolver = new UnityResolver(container);
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
var appXmlType = config.Formatters.XmlFormatter.SupportedMediaTypes.FirstOrDefault(t => t.MediaType == "application/xml");
config.Formatters.XmlFormatter.SupportedMediaTypes.Remove(appXmlType);
}
}
error reported:
{"Message":"An error has occurred.","ExceptionMessage":"An error occurred when trying to create a controller of type 'ADAccountController'. Make sure that the controller has a parameterless public constructor.","ExceptionType":"System.InvalidOperationException","StackTrace":" at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)\r\n at System.Web.Http.Controllers.HttpControllerDescriptor.CreateController(HttpRequestMessage request)\r\n at System.Web.Http.Dispatcher.HttpControllerDispatcher.<SendAsync>d__1.MoveNext()","InnerException":{"Message":"An error has occurred.","ExceptionMessage":"Type 'ADConnector.Controllers.ADAccountController' does not have a default constructor","ExceptionType":"System.ArgumentException","StackTrace":" at System.Linq.Expressions.Expression.New(Type type)\r\n at System.Web.Http.Internal.TypeActivator.Create[TBase](Type instanceType)\r\n at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.GetInstanceOrActivator(HttpRequestMessage request, Type controllerType, Func`1& activator)\r\n at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)"}}
Unity tries to pick the constructor with the most arguments, I think in this case the exception is misleading.
Not got IDE to hand, but does this work?
container.RegisterType<ILDAPConnector>(new HierarchicalLifetimeManager(),
new InjectionFactory(c => new LDAPConnector()));
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);
}