IoC / Dependency Injection - How to handle contextual dependencies (using Structuremap) - c#

After introducing messaging in my application it seems I've found a bit of a smell.
In my multi tenant application, the file system is abstracted and scoped for each tenant. So if a service needs to create files, then we inject an instance of IFileSystem which will be scoped to the tenants directory/container.
This is achieved by configuring structuremap to construct the IFileSystem implementation by getting of a contextual object that has the current users site.
Now we need to use the filesystem when there is no context and no current user (on a background thread). Here's a simple example:
public class SiteContext
{
public string SiteId { get { return "Site123"; } }
}
public class FileSystemSettings
{
public string BaseDirectory { get; set; }
}
public interface IFileSystem { }
public class DefaultFileSystem : IFileSystem
{
public DefaultFileSystem(FileSystemSettings settings)
{
}
}
public interface ISomeService { }
public class SomeService : ISomeService
{
public SomeService(IFileSystem fileSystem)
{
}
}
public class TestMessageHandler : IMessageHandler<TestMessage>
{
public TestMessageHandler(ISomeService someService)
{
// oO we don't have access to site context here :(
}
}
I suppose I could change my FileSystem implementation to expose the FileSystemSettings as a property so it can be set afterwards.
However, even doing this would still require me to construct my ISomeService object manually, which is a pain as some of my services have a number of dependencies = lots of calls to ObjectFactory.GetInstance...
Ideas?

You could use nested containers and configure the nested container to have a dummy implementation of your context.
The code would approximately be:
using (var container = ObjectFactory.Container.GetNestedContainer())
{
container.Configure(config => {
config.For<ISiteContext>().Use<DummyContext>();
});
return container.GetInstance<TestMessageHandler>();
}
This should set a custom (dummy) implementation of ISiteContext without overwriting the global container (ObjectFactory.Container). Of course, I can't give you an appropriate implementation of DummyContext without more information. But this should get you started.

Related

Dependency Injection for singelton class with properties

I am having a custom context class in my ASP.NET 4.8 Framework website:
public sealed class MyCustomContext
{
private static readonly Lazy<MyCustomContext> staticContext =
new Lazy<MyCustomContext>(() => new MyCustomContext());
private MyCustomContext()
{
}
public static MyCustomContext Current => staticContext.Value;
public HttpContext Context => HttpContext.Current;
// Logic to return current user based on logged in user
public User LoggedInUser => ...
// Logic to return SiteWideSettings
public Collection<SiteWideSettings> SiteWideSettings => ...
}
The above class is a Singleton and the usage of the above class in my service class methods is like this:
public class MyService : IMyService
{
public MyService()
{
}
public void DoWork()
{
var current = MyCustomContext.Current;
var loggedInUser = current.LoggedInUser;
var siteWideSettings = current.SiteWideSettings;
var currentContext = current.Context;
// use the above properties further for this method
}
}
My goal is to remove MyCustomContext class dependency hardcoded in my DoWork method of MyService class so that it can look like this:
public class MyService : IMyService
{
private readonly IMyCustomContext _myCustomContext;
public MyService(IMyCustomContext myCustomContext)
{
_myCustomContext = myCustomContext;
}
public void DoWork()
{
var current = _myCustomContext.Current;
var loggedInUser = current.LoggedInUser;
var siteWideSettings = current.SiteWideSettings;
var currentContext = current.Context;
// use the above properties further for this method
}
}
Can you share how to convert my MyCustomContext class so that it can be injected via dependency injection into MyService?
I have one more question, do the properties like LoggedInUser, SiteWideSettings and Context of MyCustomContext class should be written as properties or they should be converted to methods for dependency injection?
For the dependency injection you need an interface which gets initialized, so your MyCustomContext class needs to implement a new interface called IMyCustomContext. The interface can look like following:
public interface IMyCustomContext
{
HttpContext Context { get; }
User LoggedInUser { get; }
Collection<SiteWideSettings> SiteWideSettings { get; }
}
public class MyCustomContext : IMyCustomContext
{
public HttpContext Context
{
get { return HttpContext.Current; }
}
public User LoggedInUser
{
get
{
// Logic to return current user based on logged in user
}
}
public Collection<SiteWideSettings> SiteWideSettings
{
get
{
// Logic to return SiteWideSettings
}
}
}
In the Startup.cs there is a method called ConfigureServices, there you can add the following for the dependency injection:
container.RegisterType<IMyCustomContext, MyCustomContext>(
TypeLifetime.Singleton);
It's worth pointing out that Singleton has dual meaning here:
The Singleton Design Pattern ensures an object is only instantiated once. Its implementation isn't ideal though, as it relies on ambient state.
The Singleton Lifetime is used by IOC frameworks, where it ensures the same reference of an object is used every time.
In short, the Singleton Lifetime effectively removes the need to implement the Design Pattern, because the IOC framework ensures the backing concept for you.
Meaning, if we register our dependency with the Singleton Lifetime.
container.RegisterType<ICustomContext, MyCustomContext>(TypeLifetime.Singleton);
We can remove the code for the Singleton Pattern, as the IOC container will take over the responsibility of guarding the single instance/reference.
public class MyCustomContext : ICustomContext
{
public HttpContext Context => HttpContext.Current;
// Logic to return current user based on logged in user
public User LoggedInUser => ...
// Logic to return SiteWideSettings
public Collection<SiteWideSettings> SiteWideSettings => ...
}
I've also added the ICustomContext interface with the member we're interested in.
public interface ICustomContext
{
HttpContext Context { get; }
User LoggedInUser { get; }
Collection<SiteWideSettings> SiteWideSettings { get; }
}
Can you share how to moq properties of that class?
That's right, we just moved the problem one level, didn't we? If you need to extract an interface, you usually need to do this in a recursive manner.
This also means HttpContext is not a good candidate for an interface member, which makes sense when you think about it. From a unit test's point of view, we're not interested in verifying ASP.NET's inner workings. Instead, we want to check our own code, and only that portion, with no dependencies on foreign libraries. To do so, you should only copy the HttpContext members you need on to your interface and remove the dependency on HttpContext (which is notoriously hard to abstract).
For example:
public interface ICustomContext
{
IPrincipal User { get; }
User LoggedInUser { get; }
Collection<SiteWideSettings> SiteWideSettings { get; }
}
This will require some refactoring / remodeling as the number of properties grows.
For simple DTO's you can even choose not to abstract / interface them, as long as your able to easily create fakes for unit testing. Also remember it only makes sense to introduce an interface if there are going to be multiple implementations.
One more thing about Dependency Inversion, and how IOC frameworks work, you usually let the dependencies bubble up. The recommended approach is through constructor injection, as illustrated in the following ICustomContext implementation for unit tests.
public class TestCustomContext : ICustomContext
{
public MyCustomContext(IPrincipal user, User loggedInUser, Collection<SiteWideSettings> siteWideSettings)
{
User = user;
LoggedInUser = loggedInUser;
SiteWideSettings = siteWideSettings;
}
IPrincipal User { get; }
User LoggedInUser { get; }
Collection<SiteWideSettings> SiteWideSettings { get; }
}
I have one more question, do the properties like LoggedInUser, SiteWideSettings and Context of MyCustomContext class should be written as properties or they should be converted to methods for dependency injection?
You can have both. If the state was injected through constructor injection, you might as well expose it as a property. If the implementing class implements behavior to create / transform the state, you might want to expose the behavior as a method. It all depends on the actual case, there is no golden bullet here. Just remember that in OO design, interfaces are used to model behaviors, with their scope kept as small as possible.
UPDATE
Those properties are not getting filled via constructor. All of these properties "IPrincipal User { get; } User LoggedInUser { get; } Collection SiteWideSettings { get; }" have the body in their getter, they get the data from cache first and if not found then it calls the service to get the data from db for those properties (all that is written in in the get of those properties). Should I keep them as properties only or make them methods?
Let me split up your question.
Should I keep them as properties only or make them methods?
From a technical point of view, it doesn't really matter. Properties, or automated properties (like the ones you're using), are just syntactic sugar over full blown methods. Meaning, they all get compiled into equivalent CIL instructions.
That leaves only the human factor. The readability and maintainability of your code. The agreed upon coding style and practices. That's not something I can answer for you. Personally, I prefer methods for handling these kind of code flows.
they get the data from cache first and if not found then it calls the service to get the data from db for those properties (all that is written in in the get of those properties).
Sounds like this class is more of a service provider than an actual model class in your domain. As there's also I/O involved, I'd definitely recommend switching to asynchronous methods on your interface. The explicit (Task based) signature says a lot to fellow developers reading your code.
The part where I talked about the dependencies bubbling up plays an important role here. The cache and repository are both dependencies of MyCustomContext. IOC and its inherent Dependency Inversion Principle rely on the explicit declaration of dependencies, as shown in the following sample. Note the implementation of GetLoggedInUser() is not what matters here, rather the way the dependencies are set through the constructor. All these dependencies need to be registered with your IOC container first, for it to be able to resolve ICustomContext.
public class MyCustomContext : ICustomContext
{
private readonly IUsersCache _usersCache;
private readonly IUsersRepo _usersRepo;
public MyCustomContext(IUsersCache usersCache, IUsersRepo usersRepo, IPrincipal principal)
{
_usersCache = usersCache;
_usersRepo = usersRepo;
Principal = principal;
}
public IPrincipal Principal { get; }
public async Task<LoggedInUser> GetLoggedInUser()
{
var userId = await GetUserId(Principal);
var user = _usersCache.GetById(userId);
if (user == null)
{
user = _usersRepo.GetById(userId);
_usersCache.Add(user);
}
return user;
}
...
}
Those properties are not getting filled via constructor. All of these properties "IPrincipal User { get; } User LoggedInUser { get; } Collection SiteWideSettings { get; }" have the body in their getter
I don't think that's true for IPrincipal as it, together with HttpContext, is instantiated by ASP.NET behind the scenes. All you need to do is tell the IOC container how to resolve the current IPrincipal and let it work its magic.
Likewise, all classes that depend on ICustomContext should have it injected by the IOC container.
public class MyService : IMyService
{
private readonly ICustomContext _customContext;
public MyService(ICustomContext customContext)
{
_customContext = customContext;
}
public async Task DoWork()
{
var currentPrincipal = _customContext.Principal;
var loggedInUser = await _customContext.GetLoggedInUser();
...
}
}
An important part here is again unit testing. If you design your classes like this, you can easily create fakes for testing. And even if there wasn't any testing involved, which I wouldn't recommend, the ability to decouple classes like this is a good indication of a well designed code base.

How do I handle a configuration class that are loaded at runtime with dependency injection?

I'm currently trying to work with dependency injection and so far I love. But it's one thing I can't really get my head around and where my current solution just seems wrong.
I'm working with WPF, MVVM and many of the classes I inject need an instance of a project configuration class that isn't initialized until the user create or open a new project in the application.
So my current solution is to have a "ConfigurationHandler" with load/save method and a property that hold an instance of the configuration class after it's loaded. I inject ConfigurationHandler to the others classes and then they can access the configuration after it's loaded. But it seems weird to let classes that never should save/load configuration handle the whole "ConfigurationHandler" and 100% they would just use it to access the configuration instance likt this:
var configuration = configurationHandler.Configuration;
Another problem is that if they try to access the configuration before it's loaded they will get exception (should not really happen as you can't do anything before a project is created/loaded, but still).
But the only other solution I can think of is to use "intialize" methods after a project is created/open but that seems just as bad.
So how do you usually handle cases like this?
Edit: Should add that this configuration class handle information like project path, project name, etc so have nothing to do with the dependency injection itself.
If your configuration is static (read: It's only read during startup of your application, such as from project.json or Web.Config), you can also set it during app startup/the composition root.
The new ASP.NET 5 uses it heavily and it works very well. Basically you will have an IConfiguration<T> interface and a POCO class, which you set up during the app startup and can resolve/inject it into your services.
public interface IConfiguration<T> where T : class
{
T Configuration { get; }
}
And it's default implementation
public interface DefaultConfiguration<T> where T : class
{
private readonly T configuration;
public T Configuration {
return configuration;
}
public DefaultConfiguration<T>(T config)
{
this.configuration = this.configuration;
}
}
And your POCO class
public class AppConfiguration
{
public string OneOption { get; set; }
public string OtherOption { get; set; }
}
In your composition root, you would then register it, like
// read Web.Config
Configuration rootWebConfig = System.Web.Configuration.WebConfigurationManager.OpenWebConfiguration(null);
container.AddSingleton<IConfiguration<AppConfiguration>>(new DefaultConfiguration<AppConfiguration>(
new AppConfiguration
{
OneOption = rootWebConfig.AppSettings.Settings["oneSetting"],
OtherOption = rootWebConfig.AppSettings.Settings["otherSetting"],
})
);
And finally, all you have to declare in your services is
public class MyService : IMyService
{
public MyService(IUserRepository, IConfiguration<AppConfiguration> appConfig)
{
...
if(appConfig.OneOption=="someValue") {
// do something
};
}
}
Finally you can make this a bit easier to configure, if you write an extension method like
public static class MyContainerExtension
{
public static void Configure<T>(this IMyContainer container, Action<T> config) where T : class, new()
{
var t = new T();
config(t);
container.AddSingelton<IConfiguration<T>>(t);
}
}
Then all you need to do is
container.Configure<AppConfiguration>(
config =>
{
config.OneOption = rootWebConfig.AppSettings.Settings["oneSetting"],
config.OtherOption = rootWebConfig.AppSettings.Settings["otherSetting"],
})
);
to set it up
Instead of Constructor Injection, consider using an Ambient Context approach.
The last type of DI we’ll discuss is making dependencies available
through a static accessor. It is also called injection through the
ambient context. It is used when implementing cross-cutting concerns.
This is a good option if the classes that need access to your configuration are of different types in different layers or libraries - i.e. is a true cross-cutting concern.
(Quote source)
Example, based on the classic Time Provider one from [Dependency Injection in .NET][2]
abstract class CustomConfiguration
{
//current dependency stored in static field
private static CustomConfiguration current;
//static property which gives access to dependency
public static CustomConfiguration Current
{
get
{
if (current == null)
{
//Ambient Context can't return null, so we assign a Local Default
current = new DefaultCustomConfiguration();
}
return current;
}
set
{
//allows to set different implementation of abstraction than Local Default
current = (value == null) ? new DefaultCustomConfiguration() : value;
}
}
//service which should be override by subclass
public virtual string SomeSetting { get; }
}
//Local Default
class DefaultCustomConfiguration : CustomConfiguration
{
public override string SomeSetting
{
get { return "setting"; }
}
}
Usage
CustomConfiguration.Current.SomeSetting;
There are other DI Patterns that could be used, but require changes to the class that need it. If Configuration is a cross cutting concern Ambient Context could be the best fit.
Constructor Injection Example
public SomeClass(IConfiguration config)
{
}
Property Injection
public SomeClass()
{
IConfiguration configuration { get; set; }
}
Method Injection
public SomeClass()
{
public void DoSomethingNeedingConfiguation(IConfiguration config)
{
}
}
There is also Service Locator, but Service Locator is (IMO) an anti-pattern.

StructureMap override dependencies at runtime based on resolved type

In my MVC application I defined two interfaces: IQueryMappingsConfigurator and ICommandMappingsConfigurator. Those two interfaces are used for supplying EntityFramework mappings for query and command contexts.
In the same solution I have two services: IMembershipService and IMessagingService; for each of those services there is a Registry specifying implementations for ICommandMappingsConfigurator and IQueryMappingsConfigurator:
// In Services.Membership project
public class MembershipRegistry : Registry
{
public MembershipRegistry()
{
For<ICommandMappingsConfigurator>()
.Use<MembershipCommandMappingsConfigurator>();
For<IQueryMappingsConfigurator>()
.Use<MembershipQueryMappingsConfigurator>();
For<IMembershipService>()
.Use<MembershipService>();
}
}
// In Services.Messaging project
public class MessagingRegistry : Registry
{
public MessagingRegistry()
{
For<ICommandMappingsConfigurator>()
.Use<MessagingCommandMappingsConfigurator>();
For<IQueryMappingsConfigurator>()
.Use<MessagingQueryMappingsConfigurator>();
For<IMessagingService>()
.Use<MessagingService>();
}
}
Each service has a dependency on both IQueryMappingsConfigurator and ICommandMappingsConfigurator.
IMembershipService and IMessagingService are used by controllers in the MVC project:
public class MessageController : Controller
{
public MessageController(IMessagingService service){ }
}
public class MembershipController : Controller
{
public MembershipController(IMembershipService service){}
}
How can I configure the StructureMap container so that when a dependency for IMessagingService is required it will load the proper implementation for ICommandMappingsConfigurator and IQueryMappingsConfigurator?
I've tried using a custom registration convention like this:
public class ServiceRegistrationConvention : IRegistrationConvention
{
public void Process(Type type, Registry registry)
{
if (IsApplicationService(type))
{
registry.Scan(_ =>
{
_.AssemblyContainingType(type);
_.LookForRegistries();
});
}
}
}
However, when I try accessing an action method from MessageController I get the error
There is no configuration specified for IMessagingService
When I debug the application I can see the Process method being hit with the type of IMessagingService.
The correct way to compose your application is to make a composition root. If you indeed have one (which isn't clear from your question), this step is done only 1 time per application start so there is no way to change the DI configuration's state during runtime (or at least you should assume there is not). It doesn't matter if the dependencies are in different application layers, they will overlap if they use the same interface.
Before you change your DI configuration, you should check whether you are violating the Liskov Substitution Principle. Unless you really need the ability to swap the MembershipCommandMappingsConfigurator and MessagingCommandMappingsConfigurator in your application, a simple solution is just to give each a different interface (in this case IMembershipCommandMappingsConfigurator and IMessagingCommandMappingsConfigurator).
If you are not violating the LSP, one option is to use generics to disambiguate the dependency chain.
public class MyRegistry : Registry
{
public MyRegistry()
{
For(typeof(ICommandMappingsConfigurator<>))
.Use(typeof(CommandMappingsConfigurator<>));
For(typeof(IQueryMappingsConfigurator<>)
.Use(typeof(QueryMappingsConfigurator<>));
For<IMessagingService>()
.Use<MessagingService>();
For<IMembershipService>()
.Use<MembershipService>();
}
}
public class CommandMappingsConfigurator<MessagingService> : ICommandMappingsConfigurator<MessagingService>
{
// ...
}
public class QueryMappingsConfigurator<MessagingService> : IQueryMappingsConfigurator<MessagingService>
{
// ...
}
public class MessagingService
{
public MessagingService(
ICommandMappingsConfigurator<MessagingService> commandMappingsConfigurator,
IQueryMappingsConfigurator<MessagingService> queryMappingsConfigurator)
{
// ...
}
}
public class CommandMappingsConfigurator<MembershipService> : ICommandMappingsConfigurator<MembershipService>
{
// ...
}
public class QueryMappingsConfigurator<MembershipService> : IQueryMappingsConfigurator<MembershipService>
{
// ...
}
public class MembershipService
{
public MembershipService(
ICommandMappingsConfigurator<MembershipService> commandMappingsConfigurator,
IQueryMappingsConfigurator<MembershipService> queryMappingsConfigurator)
{
// ...
}
}
Another option - in StructureMap you can used smart instances in the configuration to specify exactly what instance goes where, so at runtime you can have different implementations of the same interface.
public class MembershipRegistry : Registry
{
public MembershipRegistry()
{
var commandMappingsConfigurator = For<ICommandMappingsConfigurator>()
.Use<MembershipCommandMappingsConfigurator>();
var queryMappingsConfigurator = For<IQueryMappingsConfigurator>()
.Use<MembershipQueryMappingsConfigurator>();
For<IMembershipService>()
.Use<MembershipService>()
.Ctor<ICommandMappingsConfigurator>().Is(commandMappingsConfigurator)
.Ctor<IQueryMappingsConfigurator>().Is(queryMappingsConfigurator);
}
}
public class MessagingRegistry : Registry
{
public MessagingRegistry()
{
var commandMappingsConfigurator = For<ICommandMappingsConfigurator>()
.Use<MessagingCommandMappingsConfigurator>();
var queryMappingsConfigurator = For<IQueryMappingsConfigurator>()
.Use<MessagingQueryMappingsConfigurator>();
For<IMessagingService>()
.Use<MessagingService>();
.Ctor<ICommandMappingsConfigurator>().Is(commandMappingsConfigurator)
.Ctor<IQueryMappingsConfigurator>().Is(queryMappingsConfigurator);
}
}
You can also use named instances, but smart instances have compile-time type checking support which makes them easier to configure.
There is no reason to use .Scan (which uses Reflection) to configure the registries, unless your application has some kind of plugin architecture. For a normal application with multiple layers, you can configure them explicitly.
var container = new Container();
container.Configure(r => r.AddRegistry<MembershipRegistry>());
container.Configure(r => r.AddRegistry<MessagingRegistry>());

How to inject a property into a class that I don't control the creation of

How do I inject the IServiceManager property into this class using autofac? This is a custom resource provider factory class that gets called when you make a call to HttpContext.GetGlobalResourceObject("", "MyResource") to get a resource string.
public class SqlResourceProviderFactory : ResourceProviderFactory
{
// needs autofac property injection
public IServiceManager ServiceManager { get; set; }
public override IResourceProvider CreateGlobalResourceProvider(string classKey)
{
...
}
public override IResourceProvider CreateLocalResourceProvider(string virtualPath)
{
...
}
public static string GetAppRelativePath(string logicalPath)
{
...
}
}
I've faced this exact same problem - with an ASP.NET ResourceProviderFactory - and the answer is, unfortunately, that you have to use service location.
There's no "hook" into the pipeline anywhere that you can inject anything or change the built-in ASP.NET behavior. Thus, if you need something put into a property, it has to be set in the constructor.
public class SqlResourceProviderFactory : ResourceProviderFactory
{
public IServiceManager ServiceManager { get; set; }
public SqlResourceProviderFactory()
{
this.ServiceManager =
DependencyResolver.Current.GetService<IServiceManager>();
}
}
Yeah, it's really ugly.
Something very important to consider here, especially with respect to Autofac, is the lifetime scope for which your IServiceManager is registered.
The ResourceProviderFactory is created once and cached. Same with the global/local resource providers that come out of the Create* methods. we had a heck of a time with this because it means there's not necessarily an HttpContext at the time the factories get created, and even if there is, if any of the downstream dependencies are registered InstancePerHttpRequest then they'll be disposed of and you're hosed.
Anything used by your ResourceProviderFactory or generated resource providers - all the way down the stack - should be registered either SingleInstance or InstancePerDependency.
If possible, instead of using DependencyResolver.Current (which, for Autofac, requires an active HttpContext), reference the application container directly. That means you need to store a reference to it somewhere else (a global static variable?) and use that.
This is what a more complete solution might involve:
// Create some "holder" for the app container.
public static class ApplicationContainerProvider
{
public static ILifetimeScope Container { get; set; }
}
// In Global.asax, build your container and set it in both
// the DependencyResolver AND in the holder class.
var builder = new ContainerBuilder();
builder.RegisterType<Something>().As<ISomething>();
var container = builder.Build();
var resolver = new AutofacDependencyResolver(container);
DependencyResolver.SetResolver(resolver);
ApplicationContainerProvider.Container = container;
// In your service location, reference the container instead of
// DependencyResolver.
public class SqlResourceProviderFactory : ResourceProviderFactory
{
public IServiceManager ServiceManager { get; set; }
public SqlResourceProviderFactory()
{
this.ServiceManager =
ApplicationContainerProvider.Container.Resolve<IServiceManager>();
}
}
Note that since you're resolving that out of the root container, it'll stick around for the lifetime of the application. Even if you register it as InstancePerDependency, because of the internal caching .NET does, it'll only get created once.
If you don't like creating your own static holder class like that, you can abstract it away by using the CommonServiceLocator and the Autofac.Extras.CommonServiceLocator packages.

ASP.NET: dependency injection and roles

I have a page using an injected BLL service: a simple service returning a set of objects with a function like this:
public IMyService { List<Foo> All(); }
There is a default implementation for normal users.
Now, i need that users in administrative role can view more objects, with another implementation of the service.
Where can i configure my page to use the second implementation?
My first solution is to put the dependency to the IUnityContainer in the page, and use it to resolve the dependency:
[Dependency]
public IUnityContainer Container { get; set;}
Page_Init(..)
{
_myService = User.IsInRole(MyRoles.Administrators)
? Container.Resolve<IMyService>("forAdmins")
: Container.Resolve<IMyService>();
}
But it's very ugly: it's a ServiceLocator and it's neither scalable neither testable.
How can i handle this situation? Maybe creating a child container for every role?
You could implement it as a combination of Decorator and Composite:
public SelectiveService : IMyService
{
private readonly IMyService normalService;
private readonly IMyService adminService;
public SelectiveService(IMyService normalService, IMyService adminService)
{
if (normalService == null)
{
throw new ArgumentNullException("normalService");
}
if (adminService == null)
{
throw new ArgumentNullException("adminService");
}
this.normalService = normalService;
this.adminService = adminService;
}
public List<Foo> All()
{
if(Thread.CurrentPrincipal.IsInRole(MyRoles.Administrators))
{
return this.adminService.All();
}
return this.normalService.All();
}
}
This follows the Single Responsibility Principle since each implementation does only one thing.
I agree with you that your current design is ugly. What I personally dislike about this approach is that you are setting up the security configuration inside a page. You will have a security bug when anyone forgets this and how are you testing that this page configuration is correct?
Here are two ideas:
First:
Use a factory that is able to resolve the correct implementation of that service based on the user roles:
public static class MyServiceFactory
{
public static IMyService GetServiceForCurrentUser()
{
var highestRoleForUser = GetHighestRoleForUser();
Container.Resolve<IMyService>(highestRoleForUser);
}
private static string GetHighestRoleForUser()
{
var roles = Roles.GetRolesForUser().ToList();
roles.Sort();
return roles.Last();
}
}
Second:
Have multiple methods on that interface, one for normal users, one for administrators. The implementation of that interface can have the PrincipalPermissionAttribute defined on the restricted methods:
class MyServiceImpl : IMyService
{
public List<Foo> All()
{
// TODO
}
[PrincipalPermission(SecurityAction.Demand, Role ="Administrator")]
public List<Foo> AllAdmin()
{
// TODO
}
}
I hope this helps.

Categories

Resources