Pregunta

I'm using Windsor with ASP.NET MVC4 and I've written a custom RoleProvider around a legacy security framework. I need to inject a connection string and file path into the provider so I can provide these to the legacy framework, but when I come to use the AuthorizeAttribute, I realise that I have no idea how to intercept the construction of the provider in order to inject these values.

If it helps to include code, my role provider has this kind of constructor :

public class CustomRoleProvider : RoleProvider
{
    public CustomRoleProvider(string connectionString, string logPath)
    {
        LegacySecurity.ConnectionString = connectionString;
        LegacySecurity.LogPath = logPath;
    }

    [Method overrides go here...]
}

My AppSettingsConvention looks like this :

public class AppSettingsConvention : ISubDependencyResolver
{
    public bool CanResolve(CreationContext context,
                           ISubDependencyResolver contextHandlerResolver,
                           ComponentModel model,
                           DependencyModel dependency)
    {
        return ConfigurationManager.AppSettings.AllKeys.Contains(dependency.DependencyKey)
               && TypeDescriptor.GetConverter(dependency.TargetType).CanConvertFrom(typeof (string));
    }

    public object Resolve(CreationContext context,
                          ISubDependencyResolver contextHandlerResolver,
                          ComponentModel model,
                          DependencyModel dependency)
    {
        return TypeDescriptor.GetConverter(dependency.TargetType)
                             .ConvertFrom(ConfigurationManager.AppSettings[dependency.DependencyKey]);
    }
}

(originally from here : http://blog.ploeh.dk/2012/07/02/PrimitiveDependencies/ )

I'm hoping that I can replace one of the services somehow the same way I'm replacing the HttpControllerActivator to use dependency injection with the ApiControllers.

Is this possible? Or do I need to look at another way of providing these dependencies?

¿Fue útil?

Solución

The results of my research :

  • Windsor (unlike StructureMap) does not have a way of injecting properties into existing objects
  • The AuthorizeAttribute does not call into the RoleProvider implementation directly, it calls into Thread.CurrentPrincipal which returns an IPrincipal implementation...
  • Basically it's impossible to provide a resolved RoleProvider to the AuthorizeAttribute, even though you can get hold if it before it calls the provider by writing your own implementation of IFilterProvider (cf. IFilterProvider and separation of concerns)

What I ended up doing was providing a method on the CustomRoleProvider which set the connection string and log path, then just setting it up in Application_Start :

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();
    WebApiConfig.Register(GlobalConfiguration.Configuration);
    FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
    RouteConfig.RegisterRoutes(RouteTable.Routes);
    BundleConfig.RegisterBundles(BundleTable.Bundles);

    var provider = Roles.Provider as CustomRoleProvider;
    if (provider != null) provider.Initialize(ConfigurationManager.AppSettings[ConnectionKey], ConfigurationManager.AppSettings[LogPathKey]);
    GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerActivator), _container.Resolve<IHttpControllerActivator>());
    ControllerBuilder.Current.SetControllerFactory(_container.Resolve<IControllerFactory>());
}

There might be a better way to do this, but for the moment I went with the pragmatic solution.

Update 2014-02-12

I found another way to replace the provider in the Roles class via refelection. All you need in your web config file is <roleManager enabled="true" /> and to override Name in your CustomRoleProvider to return "CustomRoleProvider" and then in the Application_Start method add this :

if (!(Roles.Provider is CustomRoleProvider))
{
    var rolesType = typeof (Roles);
    var flags = BindingFlags.Static | BindingFlags.NonPublic;
    var provider = _container.Resolve<CustomRoleProvider>();
    rolesType.GetField("s_Provider", flags).SetValue(null, provider);
    var providers = new RoleProviderCollection();
    providers.Add(provider);
    rolesType.GetField("s_Providers", flags).SetValue(null, providers);
}

The call to Roles.Provider forces the Roles class to do its initialisation magic (otherwise we'd have to set a ton of other private fields) and we need to replace the collection because consumers of the Roles class call into that looking for the appropriate provider.

However, I'm not sure that I'd necessarily recommend this and I'm not sure if this is entirely sufficient, but it seems to be working for me so far.

Update 2014-02-20

There's a different way to do this which doesn't involve magical reflection - http://bugsquash.blogspot.co.uk/2010/11/windsor-managed-membershipproviders.html

Although this blog post is talking about the MembershipProvider, it's fairly trivial to change the code for a RoleProvider instead. Essentially this is using a kind of adapter pattern leaving the resolution of the actual role provider up to the adapter. It does involve making the container static and using it in a kind of service locator way, but that's a small trade-off I think.

Otros consejos

Old question, but this works well for me in an Asp.Net RoleProvider in 2019.

In your DI Container Configuration

{
    ... Type registrations
    GlobalConfiguration.Configuration.DependencyResolver = 
        new UnityDependencyResolver(container);
}   

in your RoleProvider...

private IYourType _yourType;
public override void Initialize(string name, NameValueCollection config)
{
    ...
     _yourType = GlobalConfiguration
                 .Configuration
                 .DependencyResolver
                 .GetService(typeof(IYourType)) as IYourType;
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top