ASP Web Api - IoC - разрешить HttpRequestMessage
Я пытаюсь настроить Castle Windsor с помощью ASP.NET WebAPI.
Я также использую пакет Hyprlinkr (https://github.com/ploeh/Hyprlinkr), поэтому нужен экземпляр HttpRequestMessage, введенный в одну из зависимостей мой контроллер.
Я следую этой статье Марк Seemann - http://blog.ploeh.dk/2012/04/19/WiringHttpControllerContextWithCastleWindsor.aspx, но я нахожу, что хотя API работает, когда я делаю вызовите его, запрос просто зависает. Нет сообщения об ошибке. Его как бы в бесконечном цикле. Его висит на вызове Resolve в моем Custom ControllerActivator
Я думаю, что некоторые из моих регистраций в замке ошибаются. Если я удалю те, о которых упоминалось в статье выше, то я могу успешно сделать вызов API (хотя без необходимости получить необходимые разрешения)
Любые идеи?
Код ниже
//Global.asax
public class WebApiApplication : HttpApplication
{
private readonly IWindsorContainer container;
public WebApiApplication()
{
container =
new WindsorContainer(
new DefaultKernel(
new InlineDependenciesPropagatingDependencyResolver(),
new DefaultProxyFactory()),
new DefaultComponentInstaller());
container.Install(new DependencyInstaller());
}
protected void Application_Start()
{
GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerActivator), new WindsorCompositionRoot(this.container));
}
// installer
public class DependencyInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.AddFacility<TypedFactoryFacility>();
container.Register(
Component.For<ValuesController>()
.Named("ValuesController")
.LifeStyle.PerWebRequest,
Component.For<IResourceLinker>()
.ImplementedBy<RouteLinker>()
.LifeStyle.PerWebRequest,
Component.For<IResourceModelBuilder>()
.ImplementedBy<ResourceModelBuilder>()
.LifeStyle.PerWebRequest,
Component.For<HttpRequestMessage>()
.Named("HttpRequestMessage")
.LifeStyle.PerWebRequest
);
}
}
//Activator
public class WindsorCompositionRoot : IHttpControllerActivator
{
private readonly IWindsorContainer container;
public WindsorCompositionRoot(IWindsorContainer container)
{
this.container = container;
}
public IHttpController Create(
HttpRequestMessage request,
HttpControllerDescriptor controllerDescriptor,
Type controllerType)
{
var controller = (IHttpController)this.container.Resolve(controllerType, new { request = request });
request.RegisterForDispose(
new Release(
() => this.container.Release(controller)));
return controller;
}
// DependencyResolver
public class InlineDependenciesPropagatingDependencyResolver : DefaultDependencyResolver
{
protected override CreationContext RebuildContextForParameter(CreationContext current, Type parameterType)
{
if (parameterType.ContainsGenericParameters)
{
return current;
}
return new CreationContext(parameterType, current, true);
}
}
ИЗМЕНИТЬ ***********
ДОПОЛНИТЕЛЬНАЯ ИНФОРМАЦИЯ ****************
Итак, я настроил сценарий, когда контроллер просто принимает HttpRequestMessage как аргумент ctor и обнаружил:
Это работает:
//controller
public class ValuesController : ApiController
{
private readonly HttpRequestMessage _httpReq;
public ValuesController(HttpRequestMessage httpReq)
{
_httpReq = httpReq;
}
//IHttpControllerActivator
public IHttpController Create(
HttpRequestMessage httpRequest,
HttpControllerDescriptor controllerDescriptor,
Type controllerType)
{
var controller = (IHttpController)this.container.Resolve(
controllerType, new { httpReq = httpRequest });
return controller;
Однако это не так.
//controller
public class ValuesController : ApiController
{
private readonly HttpRequestMessage _httpReq;
public ValuesController(HttpRequestMessage request)
{
_httpReq = request;
}
//IHttpControllerActivator
public IHttpController Create(
HttpRequestMessage request,
HttpControllerDescriptor controllerDescriptor,
Type controllerType)
{
var controller = (IHttpController)this.container.Resolve(
controllerType, new { request = request });
return controller;
то есть. когда объект anon имеет свойство "запрос", а контроллер ctor arg называется "запрос". Это как-то заставляет контроллер думать, что свойство request имеет значение null. Это и вызывает ошибку, которую я вижу:
Невозможно повторно использовать экземпляр ApiController. "ApiController" должен быть для каждого входящего сообщения. Проверьте свой обычай "IHttpControllerActivator" и убедитесь, что он не будет производить тот же экземпляр.
в System.Web.Http.ApiController.ExecuteAsync(HttpControllerContext controllerContext, CancellationToken cancelationToken) в System.Web.Http.Dispatcher.HttpControllerDispatcher.SendAsyncInternal(HttpRequestMessage просьба, аннулирование отмены бронированияToken) в System.Web.Http.Dispatcher.HttpControllerDispatcher.SendAsync(HttpRequestMessage запрос, Отмена Отмена бронированияТокнуть)
прочитайте это
Как я могу обогатить композицию объекта в StructureMap, не вызывая инъекцию установщика?
Это объясняет аналогичный сценарий.
Конечно, hyprlinkr имеет это ctor arg для HttpRequestMessage, называемого "request", поэтому мне нужно указать объект anon с этим именем свойства.
Любые идеи?
Ответы
Ответ 1
Здесь Корень Композиции, который работает для меня:
public class WindsorCompositionRoot : IHttpControllerActivator
{
private readonly IWindsorContainer container;
public WindsorCompositionRoot(IWindsorContainer container)
{
this.container = container;
}
public IHttpController Create(
HttpRequestMessage request,
HttpControllerDescriptor controllerDescriptor,
Type controllerType)
{
var controller = (IHttpController)this.container.Resolve(
controllerType,
new
{
request = request
});
request.RegisterForDispose(
new Release(
() => this.container.Release(controller)));
return controller;
}
private class Release : IDisposable
{
private readonly Action release;
public Release(Action release)
{
this.release = release;
}
public void Dispose()
{
this.release();
}
}
}
Вот как я создаю контейнер:
this.container =
new WindsorContainer(
new DefaultKernel(
new InlineDependenciesPropagatingDependencyResolver(),
new DefaultProxyFactory()),
new DefaultComponentInstaller())
.Install(new MyWindsorInstaller());
и здесь InlineDependenciesPropagatingDependencyResolver:
public class InlineDependenciesPropagatingDependencyResolver :
DefaultDependencyResolver
{
protected override CreationContext RebuildContextForParameter(
CreationContext current,
Type parameterType)
{
if (parameterType.ContainsGenericParameters)
{
return current;
}
return new CreationContext(parameterType, current, true);
}
}
Наконец, вот как я регистрирую RouteLinker:
container.Register(Component
.For<RouteLinker, IResourceLinker>()
.LifestyleTransient());
Одна вещь, о которой нужно знать, это то, что базовый класс ApiController имеет общедоступное свойство с именем Request
типа HttpRequestMessage. Как объясняется в разделе 10.4.3 моей книги Windsor попытается присвоить значение каждому записываемому свойству, если он имеет соответствующий компонент, - и это совпадение не учитывает регистр.
Когда вы передаете метод HttpRequestMessage с именем Request
методу Resolve, это именно то, что происходит, поэтому вам нужно сообщить Castle Windsor, что он должен отказаться от Injection для ApiControllers. Вот как я нахожу это в регистрации на основе конвенции:
container.Register(Classes
.FromThisAssembly()
.BasedOn<IHttpController>()
.ConfigureFor<ApiController>(c => c.Properties(pi => false))
.LifestyleTransient());
Ответ 2
Почему бы не использовать уже встроенный механизм в ASP.NET Web API - распознаватель зависимостей
http://www.asp.net/web-api/overview/extensibility/using-the-web-api-dependency-resolver
В проекте WebApiContrib реализована реализация резольвера CastleWindsor, но, как я видел, с преобразователем зависимостей
https://github.com/WebApiContrib/WebApiContrib.IoC.CastleWindsor
И как сказал Марк в комментарии - один из способов реализации IHttpControllerActivator
http://blog.ploeh.dk/2012/10/03/DependencyInjectionInASPNETWebAPIWithCastleWindsor.aspx