Posted by & filed under .Net.

So I received a question recently via email from someone following the container tutorials, which read like so:

Read through you tutorials and great work!

Have a question for you though, that I can’t seem to find an answer in google. On decorators. How do you provide state dependencies that are unknown at configuration time?

So I ask for an IRepository and I have configuration setup to wrap it in a IValidator and maybe an ISecurity. However, ISecurity has a dependency on a runtime determined role (say from multiple sources, possibly including a state value on IWidget) and the user id (for argument sake isn’t available on the context).

I want to call T Get() on IRepository. How do you get it all setup?

Many Thanks!

As I see it there were two distinct questions asked:

  • How to wire up generic decorator chains (though I suspect they already know how to do that).
  • How to pass in parameters/dependencies at run time.

So Let’s look at doing these two things, I’m going to steer clear of xml configuration because that’s so 2 years ago ;o)

First off let’s create the repository interface:

public interface IRepository<T>
  where T : class, new()
{
    T Get(int id);
}

Then the root implementation (we can’t keep chaining forever, at some point we have to hit an implementation which can actually return the results we want).

public class StubRepository<T> : IRepository<T>
  where T : class, new()
{
    public T Get(int id)
    {
        return new T();
    }
}

And finally a decorator for “security”…

public class SecurityDecorator<T> : IRepository<T>
  where T : class, new()
{
    private readonly IRepository<T> _inner;

    public SecurityDecorator(IRepository<T> inner)
    {
        _inner = inner;
    }

    public T Get(int id)
    {
        return _inner.Get(id);
    }
}

better have a widget too… for good measure:

public class Widget
{

}

At this point we can write a test – I’m an xUnit fanboy these days (typing less == good) so let’s take a look:


public class ContainerTests
{
    private readonly IWindsorContainer container;

    public ContainerTests()
    {
        container = new WindsorContainer();
        container.Register(Component.For(typeof (IRepository<>)).ImplementedBy(typeof (SecurityDecorator<>)));
        container.Register(Component.For(typeof (IRepository<>)).ImplementedBy(typeof (StubRepository<>)));
    }

    [Fact]
    public void GetRepository_ForWidget_ReturnsSecurityDecoratorOfTypeWidget()
    {
        var widgetRepository = container.Resolve<IRepository<Widget>>();
        Assert.True(widgetRepository is SecurityDecorator<Widget>);
    }
}

Notice we register the components in top to bottom order i.e. decorators first, followed by the underlying implementation, for something as simple as this you don’t really need to use the fluent interface for registering components – but I find it’s good to be consistent.

Now, the second question is about injecting “context” – this is really just another way of saying “some of my dependencies can’t be known until just before I attempt to resolve the service” … no problem… so let’s make some modifications:

First off I’m going to make a user…

public interface IUser
{

}

Next thing I’m going to do is add a User property to my security context (ugh, this seems like a better job for some kind of “ICurrentUserHolder” service, but that’s beside the point).

public class SecurityDecorator<T> : IRepository<T>
    where T : class, new()
{
    private readonly IRepository<T> _inner;

    public SecurityDecorator(IRepository<T> inner)
    {
        _inner = inner;
    }

    public T Get(int id)
    {
        return _inner.Get(id);
    }

    public IUser User { get; set; } // <-- the current user
}

Now let’s add another test to ensure everything is being injected properly…

public class ContainerTests
{
    private readonly IWindsorContainer container;
    private readonly IUser user = MockRepository.GenerateStub<IUser>();

    public ContainerTests()
    {
        container = new WindsorContainer();
        container.Register(Component.For(typeof (IRepository<>)).ImplementedBy(typeof (SecurityDecorator<>)));
        container.Register(Component.For(typeof (IRepository<>)).ImplementedBy(typeof (StubRepository<>))); 
    }

    [Fact]
    public void GetRepository_ForWidget_ReturnsSecurityDecoratorOfTypeWidget()
    {
        var widgetRepository = container.Resolve<IRepository<Widget>>();
        Assert.True(widgetRepository is SecurityDecorator<Widget>);
    }

    [Fact]
    public void GetRepository_ForWidget_WhenSupplyingUserInjectsUserIntoSecurityRepository()
    {
        var securityDecoratorForWidget = (SecurityDecorator<Widget>) container.Resolve<IRepository<Widget>>(new {User = user});
        Assert.Same(user, securityDecoratorForWidget.User);
    }
}

Notice the additional argument of an anonymous class being passed to Resolve, this allows us to provide additional parameter dependencies (in this case a stub user is being supplied).

Finally what about passing in some roles… this is much of the same, but I’ll include it for completeness… so we add a role interface:

public interface IRole
{
    bool IsMember(IUser user);
}

Then we’ll add a collection of roles to the decorator and provide a rather primitive check against the roles when attempting to get a widget instance:

public class SecurityDecorator<T> : IRepository<T>
    where T : class, new()
{
    private readonly IRepository<T> _inner;
    
    public SecurityDecorator(IRepository<T> inner)
    {
        _inner = inner;
    }
 
    public T Get(int id)
    {
        CheckPermission();
        return _inner.Get(id);
    }
   
    public IUser User { get; set; }
    
    public IList<IRole> Roles { get; set; } // <-- the roles to check

    private void CheckPermission()
    {
        if (User == null || Roles == null) return;
        if (Roles.Any(role => !role.IsMember(User)))
        {
            throw new Exception("You do not have permission");
        }
    }
}

And finally we update our test with checks for both passing and failing on the permissions check.

public class ContainerTests
{
    private readonly IWindsorContainer container;
    private readonly IUser user = MockRepository.GenerateStub<IUser>();
    private readonly IRole failingRole = MockRepository.GenerateStub<IRole>();
    private readonly IRole passingRole = MockRepository.GenerateStub<IRole>();

    public ContainerTests()
    {
        container = new WindsorContainer();
        container.Register(Component.For(typeof (IRepository<>)).ImplementedBy(typeof (SecurityDecorator<>)));
        container.Register(Component.For(typeof (IRepository<>)).ImplementedBy(typeof (StubRepository<>)));
        passingRole.Stub(stub => stub.IsMember(user)).Return(true);
    }

    [Fact]
    public void GetRepository_ForWidget_ReturnsSecurityDecoratorOfTypeWidget()
    {
        var widgetRepository = container.Resolve<IRepository<Widget>>();
        Assert.True(widgetRepository is SecurityDecorator<Widget>);
    }

    [Fact]
    public void GetRepository_ForWidget_WhenSupplyingUserInjectsUserIntoSecurityRepository()
    {
        var securityDecoratorForWidget = (SecurityDecorator<Widget>) container.Resolve<IRepository<Widget>>(new {User = user});
        Assert.Same(user, securityDecoratorForWidget.User);
    }

    [Fact]
    public void Get_ForWidgetWhenUserMatchesAllRoles_ReturnsWidget()
    {
        var securityDecoratorForWidget = (SecurityDecorator<Widget>) container.Resolve<IRepository<Widget>>(new {User = user, Roles = new List<IRole> {passingRole}});
        Assert.NotNull(securityDecoratorForWidget.Get(1));
    }

    [Fact]
    public void Get_ForWidgetWhenUserDoesNotMatchAllRoles_ThrowsException()
    {
        var securityDecoratorForWidget = (SecurityDecorator<Widget>) container.Resolve<IRepository<Widget>>(new {User = user, Roles = new List<IRole> {passingRole, failingRole}});
       var exception = Assert.Throws<Exception>(() => securityDecoratorForWidget.Get(1));
       Assert.Equal("You do not have permission", exception.Message);
    }
}

And that’s it… could not be simpler.

Comments are closed.