Resolución de HttpControllerContext con Castle Windsor


En el ASP.NET Web API, las instancias HttpControllerContext proporcionan mucha información sobre el entorno actual, incluido el URI de la solicitud actual.

Si un servicio se basa en dicha información (por ejemplo, el URI de solicitud), debe ser posible inyectar esa información en el servicio.

Esto es bastante fácil de hacer usando el DI de Poor Man: solo implementa un IHTTPCONTROLLER Activator personalizado.

Sin embargo, con el Castillo Windsor de repente se vuelve muy difícil. Anteriormente, he descrito una forma muy complicada para resolver este problema, pero depende del estilo de vida PerWebRequest, y resulta que este estilo de vida no funciona en escenarios de auto-alojamiento, porque HttpContext.La corriente está vacía.

Hasta ahora, he podido hacer que esto funcione pasando la información deseada como un argumento en línea al método Resolve desde un Ihttpcontroller Activator personalizado:

public IHttpController Create(
    HttpControllerContext controllerContext,
    Type controllerType)
{
    var baseUri = new Uri(
        controllerContext
            .Request
            .RequestUri
            .GetLeftPart(UriPartial.Authority));

    return (IHttpController)this.container.Resolve(
        controllerType,
        new { baseUri = baseUri });
}

Sin embargo, por defecto, esto solo funciona si el tipo inmediatamente solicitado se basa en el argumento (es decir, si el propio Controlador solicitado depende del baseUri). Si la dependencia en baseUri está enterrada más profundamente en la jerarquía de dependencias, no funciona de forma predeterminada, porque los argumentos en línea no se propagan a capas más profundas.

Este comportamiento se puede cambiar con un IDependencyResolver personalizado (un IDependencyResolver de Castle Windsor, no un ASP.NET Web API IDependencyResolver):

public class InlineDependenciesPropagatingDependencyResolver :
    DefaultDependencyResolver
{
    protected override CreationContext RebuildContextForParameter(
        CreationContext current, Type parameterType)
    {
        if (parameterType.ContainsGenericParameters)
        {
            return current;
        }

        return new CreationContext(parameterType, current, true);
    }
}

Observe que true se está pasando como el argumento constructor propagateInlineDependencies en lugar de false, que es la implementación predeterminada.

Para conectar una instancia de contenedor con la clase InlineDependenciesPropagatingDependencyResolver, debe construirse de esta manera:

this.container = 
    new WindsorContainer(
        new DefaultKernel(
            new InlineDependenciesPropagatingDependencyResolver(),
            new DefaultProxyFactory()),
        new DefaultComponentInstaller());

Me pregunto si esta es la mejor solución a este problema, o si hay una manera mejor/más simple?

Author: Mark Seemann, 2012-06-01

2 answers

Me parece que su InlineDependenciesPropagatingDependencyResolver en realidad está enmascarando algo bastante crítico para la arquitectura de su aplicación: que uno o más de sus componentes tienen dependencias que no se pueden resolver de forma estática, desde el contenedor o desde el contexto dinámico.

Viola la suposición que la mayoría de los desarrolladores harían al pasar dependencias en línea a Resolve () (que solo pasan un nivel de resolución de dependencias) y en ciertos escenarios podrían causar que una dependencia anule incorrectamente algún otro servicio configurado. (Por ejemplo, si usted tenía otro componente muchos niveles abajo que tenía una dependencia del mismo tipo y nombre). Podría ser una causa potencial de errores que serían muy difíciles de identificar.

El problema en el corazón de esto es difícil para DI y realmente indica que IoC no es realmente factible (es decir, nuestra(s) dependencia (s) necesitan ser 'empujadas' y no pueden ser 'tiradas' para nosotros por el contenedor). Me parece que hay dos opciones:

1) rectificar el problema que está impidiendo la 'inversión'. es decir, envuelva el HttpControllerContext / HttpContext, aumente ese wrapper para que se comporte como sea necesario en un escenario auto-alojado y haga que sus componentes confíen en ese wrapper, en lugar de HttpControllerContext/HttpContext directamente.

2) reflejar las deficiencias del entorno con el que está trabajando (que no admite completamente la 'inversión') y hacer la solución para manejarlas deficiencias muy explícitas. En su escenario esto probablemente implicaría utilizar una fábrica mecanografiada (interfaz) para instanciar el componente que requiere un baseUri en su IHttpControllerActivator.Create(). Esto significaría que si este componente estuviera más abajo en la jerarquía de dependencias, necesitaría construir explícitamente su jerarquía de dependencias hasta que tuviera su controlador.

Probablemente iría por la segunda opción simplemente porque cuando las convenciones no lo cortan prefiero ser tan explícito como posible.

ACTUALIZADO Suponiendo que tuviéramos un tipo de controlador A, que dependía del componente B, que a su vez dependía del baseURI, la segunda opción podría ser algo así como:

// Typed factories for components that have dependencies, which cannot be resolved statically
IBFactory bFactory; 
IAFactory aFactory;

public IHttpController Create(HttpControllerContext controllerContext, Type controllerType)
{
    if (controllerType == typeof(A))
    {
        // Special handling for controller where one or more dependencies
        // are only available via controllerContext.
        var baseUri = new Uri(controllerContext.Request.RequestUri.GetLeftPart(UriPartial.Authority));
        B b = this.bFactory.Create(baseUri);
        return this.aFactory.Create(b);
    }
    // Default for all other controllers 
    return (IHttpController)this.container.Resolve(controllerType);
}

Los puntos clave son que esto trata explícitamente con las deficiencias de nuestro entorno, vincula los tipos afectados específicamente con las anulaciones de dependencias que proporcionamos imperativamente y asegura que no estamos anulando accidentalmente ninguna otra dependencia.

 2
Author: Phil Degenhardt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-07-16 23:15:16

Solo en aras de la integridad, una respuesta que recibí de Krzysztof Koźmic (el actual mantenedor de Castle Windsor) en Twitter indicó que el método descrito en la pregunta es, de hecho, la forma correcta de lograr este objetivo en particular.

(Sin embargo, no puedo vincular a ese tweet, ya que la cuenta de Twitter de Krzysztof está protegida (los tweets no son visibles públicamente.))

 2
Author: Mark Seemann,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-10-15 21:11:57