I am following some practices documented by steven and using Simple Injector. I have a query that retrieves data from a WCF service and I want to cache the result using an instance of ObjectCache.
I've defined a decorator CachingQueryHandlerDecorator<TQuery, TResult>:
public sealed class CachingQueryHandlerDecorator<TQuery, TResult>
: IQueryHandler<TQuery, TResult>
where TQuery : IQuery<TResult>
{
private readonly IQueryHandler<TQuery, TResult> _handler;
private readonly ObjectCache _cache;
private readonly CacheItemPolicy _policy;
private readonly ILog _log;
public CachingQueryHandlerDecorator(IQueryHandler<TQuery, TResult> handler,
ObjectCache cache,
CacheItemPolicy policy,
ILog log)
{
_handler = handler;
_cache = cache;
_policy = policy;
_log = log;
}
public TResult Handle(TQuery query)
{
var key = query.GetType().ToString();
var result = (TResult) _cache[key];
if (result == null)
{
_log.Debug(m => m("No cache entry for {0}", key));
result = (TResult)_handler.Handle(query);
if (!_cache.Contains(key))
_cache.Add(key, result, _policy);
}
return result;
}
}
Within SimpleInjectorInitializer.cs I define the cache and policy, and add the decorator for a specific query:
container.RegisterSingle<ILog>(LogManager.GetCurrentClassLogger());
container.RegisterSingle<ObjectCache>(() => new MemoryCache("MyCache"));
container.RegisterSingle<CacheItemPolicy>(() => new CacheItemPolicy { AbsoluteExpiration = DateTime.Now.AddMinutes(1) } );
.
.
.
container.RegisterDecorator(typeof(IQueryHandler<,>),
typeof(CachingQueryHandlerDecorator<,>),
ctx => ctx.ServiceType.GetGenericArguments()[0] == typeof(MyQuery));
The problem I'm facing is that I want to be able to specify different CacheItemPolicy's for different queries. I could create a new ICachePolicy<TQuery> interface and then define concrete classes for each different query type but I'm hoping there might be a way to avoid that and define the policy per query directly in the initialization file.
I could create a new ICachePolicy interface and then define
concrete classes for each different query type
I think that's a pretty neat idea actually. You can register a default generic implementation that injected into every decorator that has no specific implementation registered:
container.RegisterOpenGeneric(typeof(ICachePolicy<>), typeof(DefaultCachePolicy<>),
Lifestyle.Singleton);
And for queries that have an alternative cache policy, you can register a specific implementation:
container.RegisterSingle<ICachePolicy<MyQuery>>(new CachePolicy<MyQuery>
{
AbsoluteExpiration = DateTime.Now.AddHour(2)
});
Another option is to mark queries or their query handlers with an attribute that describes the caching policy (this is the route I usually take):
[CachePolicy(AbsoluteExpirationInSeconds = 1 * 60 * 60)]
public class MyQuery : IQuery<string[]> { }
Now you don't have to inject an ICachePolicy<T>, but can read this metadata directly using reflection:
public sealed class CachingQueryHandlerDecorator<TQuery, TResult>
: IQueryHandler<TQuery, TResult>
where TQuery : IQuery<TResult>
{
private static readonly bool shouldCache;
private static readonly CachingPolicySettings policy;
private readonly IQueryHandler<TQuery, TResult> _handler;
private readonly ObjectCache _cache;
private readonly ILog _log;
static CachingQueryHandlerDecorator()
{
var attribute = typeof(TQuery).GetCustomAttribute<CachePolicyAttribute>();
if (attribute != null)
{
shouldCache = true;
policy = attribute.Policy;
}
}
public CachingQueryHandlerDecorator(
IQueryHandler<TQuery, TResult> handler,
ObjectCache cache,
ILog log)
{
_handler = handler;
_cache = cache;
_log = log;
}
public TResult Handle(TQuery query)
{
if (!shouldCache)
{
return this._handler.handle(query);
}
// do your caching stuff here.
}
You can achieve the result you require with an open generic implementation and override specific default values as required. I.e. you define an open generic implementation CachePolicy<TQuery> of ICachePolicy<TQuery> and use the RegisterInitializer method to override parts of the default implementation.
Given these definitions:
public interface ICachePolicy<TQuery>
{
DateTime AbsoluteExpiration { get; }
}
public class CachePolicy<TQuery> : ICachePolicy<TQuery>
{
public CachePolicy()
{
AbsoluteExpiration = Cache.NoAbsoluteExpiration;
}
public DateTime AbsoluteExpiration { get; set; }
}
public interface IQueryHandler<TQuery, TResult> { }
public class QueryHandlerA : IQueryHandler<A, AResult> { }
public class QueryHandlerB : IQueryHandler<B, BResult> { }
public sealed class CachingQueryHandlerDecorator<TQuery, TResult>
: IQueryHandler<TQuery, TResult>
{
private readonly IQueryHandler<TQuery, TResult> decorated;
public readonly ICachePolicy<TQuery> Policy;
public CachingQueryHandlerDecorator(
IQueryHandler<TQuery, TResult> decorated,
ICachePolicy<TQuery> cachePolicy)
{
this.decorated = decorated;
this.Policy = cachePolicy;
}
}
Set up the container using the RegisterOpenGeneric method and configure the non default values using RegisterInitializer:
public Container ConfigureContainer()
{
Container container = new Container();
container.RegisterOpenGeneric(
typeof(ICachePolicy<>),
typeof(CachePolicy<>),
Lifestyle.Singleton);
container.RegisterInitializer<CachePolicy<A>>(a =>
a.AbsoluteExpiration = DateTime.Now.AddMinutes(1));
container.RegisterManyForOpenGeneric(
typeof(IQueryHandler<,>),
typeof(IQueryHandler<,>).Assembly);
container.RegisterDecorator(
typeof(IQueryHandler<,>),
typeof(CachingQueryHandlerDecorator<,>));
container.Verify();
return container;
}
These tests demonstrate the result is as expected:
[Test]
public void GetInstance_A_HasCustomAbsoluteExpiration()
{
Container container = ConfigureContainer();
var a = container.GetInstance<IQueryHandler<A, AResult>>();
Assert.AreNotEqual(
(a as CachingQueryHandlerDecorator<A, AResult>).Policy.AbsoluteExpiration,
Cache.NoAbsoluteExpiration);
}
[Test]
public void GetInstance_B_HasDefaultAbsoluteExpiration()
{
Container container = ConfigureContainer();
var b = container.GetInstance<IQueryHandler<B, BResult>>();
Assert.AreEqual(
(b as CachingQueryHandlerDecorator<B, BResult>).Policy.AbsoluteExpiration,
Cache.NoAbsoluteExpiration);
}
Related
I'm playing with cqs a little bit and I'm trying to implement this in a class library (so there's no IOC, IServiceProvider, etc). Here is some code that I wrote:
public interface IQuery<TResult>
{
}
public interface IQueryHandler<TQuery, TResult> where TQuery : IQuery<TResult>
{
TResult Handle(TQuery query);
}
public class Query : IQuery<bool>
{
public int Value { get; set; }
}
public class QueryHandler : IQueryHandler<Query, bool>
{
public bool Handle(Query query)
{
return query.Value > 0;
}
}
public class Dispatcher
{
private readonly Dictionary<Type, object> handlers = new Dictionary<Type, object>();
public Dispatcher()
{
handlers.Add(typeof(Query), new QueryHandler());
}
public T Dispatch<T>(IQuery<T> query)
{
IQueryHandler<IQuery<T>, T> queryHandler;
if (!this.handlers.TryGetValue(query.GetType(), out object handler) ||
((queryHandler = handler as IQueryHandler<IQuery<T>, T>) == null))
{
throw new Exception();
}
return queryHandler.Handle(query);
}
}
And this si how I am calling my code:
Query query = new Query();
Dispatcher dispatcher = new Dispatcher();
var result = dispatcher.Dispatch(query);
But the problem is that inside the dispatcher, I don't know why the variable handler cannot be casted as IQueryHandler<IQuery<T>,T>. Here is some extra data:
PS: I know how to make this work(with dynamic), but I want to understand why THIS code isn't working.
This is a covariance problem. The real type of handler is QueryHandler, so it is a IQueryHandler<Query, bool>. Of course Query is an IQuery<bool>, but that is the point of covariance.
It is like trying to assign a List<String> to a variable of type List<Object>.
There exists an out keyword that allows you to use the covariance on your IQueryHandler interface as you expect it.
See out for detailed information
EDIT:
As pointed out by Sweeper, you cannot use out on TQuery because it is used as input parameter. The correct solution is to avoid the dependecy of QueryHandler on Query. Isma has shown nicely how it is done.
This code does not work because IQueryHandler is invariant on the TQuery generic parameter. TQuery needs to be covariant in order for handler to be convertible to IQueryHandler<IQuery<T>, T>, but that is impossible, as I will explain later. You could however, make TQuery contravariant, which allows you to convert handler to IQueryHandler<ASubclassOfQuery, T>. TResult can be covariant though. This is the code to do this:
public interface IQueryHandler<in TQuery, out TResult> where TQuery : IQuery<TResult>
See this page for more info about generic variances.
As for why handler is not IQueryHandler<IQuery<T>, T>, let's first suppose that it is, which means this code would compile:
IQueryHandler<IQuery<T>, T> q = handler;
q.Handle(new MyQuery<T>());
where MyQuery is defined like this:
class MyQuery<T> : IQuery<T> {}
However, handler is of runtime type QueryHandler. QueryHandler.Handle only handles Query objects, not MyQuery<T> objects! We have a contradiction, and hence our assumption that handler is a IQueryHandler<IQuery<T>, T> must be false.
Here is a different way to do this to avoid the covariance problem:
public interface IQuery<TResult>
{
TResult Value { get; set; }
}
public interface IQueryHandler<TResult>
{
TResult Handle<TQuery>(TQuery query) where TQuery : IQuery<TResult>;
}
public class Query : IQuery<bool>
{
public bool Value { get; set; }
}
public class QueryHandler : IQueryHandler<bool>
{
public bool Handle<TQuery>(TQuery query) where TQuery : IQuery<bool>
{
return query.Value;
}
}
public class Dispatcher
{
private readonly Dictionary<Type, object> handlers = new Dictionary<Type, object>();
public Dispatcher()
{
handlers.Add(typeof(Query), new QueryHandler());
}
public T Dispatch<T>(IQuery<T> query)
{
if (handlers.ContainsKey(query.GetType()))
{
var queryHandler = (IQueryHandler<T>)handlers[query.GetType()];
return queryHandler.Handle(query);
}
throw new NotSupportedException();
}
}
Example:
var queryHandler = new QueryHandler();
var query = new Query();
query.Value = true;
var dispatcher = new Dispatcher();
dispatcher.Dispatch(query);
>> True
Your following line does not makes sense:
handler as IQueryHandler<T, T>
as the first and second type parameter can never be same as Query and Result will always be of different types.
You need some mechanism to provide the second type parameter in your Dispatcher and one way is this:
public class Dispatcher<TResult>
{
private readonly Dictionary<Type, object> handlers = new Dictionary<Type, object>();
public Dispatcher()
{
handlers.Add(typeof(Query), new QueryHandler());
}
public TResult Dispatch<TQuery>(TQuery query) where TQuery : IQuery<TResult>
{
IQueryHandler<TQuery, TResult> queryHandler;
if (!this.handlers.TryGetValue(query.GetType(), out object handler) ||
((queryHandler = handler as IQueryHandler<TQuery, TResult>) == null))
{
throw new Exception();
}
return queryHandler.Handle(query);
}
}
and it can be called like:
Query query = new Query();
Dispatcher<bool> dispatcher = new Dispatcher<bool>();
var result = dispatcher.Dispatch(query);
Another way around is to take second type parameter in the Dispatch method but in my opinion the first way is more better:
public class Dispatcher<TQuery, TResult> where TQuery : IQuery<TResult>, new()
{
private readonly Dictionary<Type, object> handlers = new Dictionary<Type, object>();
public Dispatcher()
{
handlers.Add(typeof(Query), new QueryHandler());
}
public TResult Dispatch()
{
TQuery query = new TQuery();
IQueryHandler<TQuery, TResult> queryHandler;
if (!this.handlers.TryGetValue(query.GetType(), out object handler) ||
((queryHandler = handler as IQueryHandler<TQuery, TResult>) == null))
{
throw new Exception();
}
return queryHandler.Handle(query);
}
}
and calling side will look like:
Dispatcher<Query, bool> dispatcher = new Dispatcher<Query,bool>();
var result = dispatcher.Dispatch();
Using this PipelineX class below, is there any way to resolve the filters applied to the pipline without injecting the autofac container and calling _container.Resolve();
public class PipelineX<T> : FilterBase<T>, IPipelineX<T>
{
private readonly IContainer _container;
public PipelineX(IContainer container)
{
_container = container;
}
protected override T Process(T input)
{
return input;
}
public PipelineX<T> FilterBy<X>()
{
var filter = _container.Resolve(typeof(X)) as IFilter<T>;
Register(filter);
return this;
}
}
To avoid the usage of Autofac as a service locator you can register your own factory method into it, in this case:
builder.Register<Func<Type, object>>((c, p) =>
{
var context = c.Resolve<IComponentContext>();
return type => context.Resolve(type);
});
and use that in your PipelineX class like this:
private readonly Func<Type, object> filterFactory;
public PipelineX(Func<Type, object> filterFactory)
{
this.filterFactory = filterFactory;
}
protected override T Process(T input)
{
return input;
}
public PipelineX<T> FilterBy<X>()
{
var filter = this.filterFactory(typeof(X)) as IFilter<T>;
Register(filter);
return this;
}
Consider: This only removes the hard reference to the Autofac container, it's still using an abstract object factory which is not self explanatory enough and should be replaced by a custom filter factory or selector implementation.
This is similar to Péter's answer but uses a custom factory:
public class FilterFactory
{
private readonly Func<Type, object> _factoryFunc;
public FilterFactory(Func<Type, object> factoryFunc)
{
_factoryFunc = factoryFunc ?? throw new ArgumentNullException(nameof(factoryFunc));
}
public IFilter<T> Create<X, T>()
{
IFilter<T> filter = Create<T>(typeof(X));
return filter;
}
public IFilter<T> Create<T>(Type type)
{
var filter = _factoryFunc(type) as IFilter<T>;
if (filter == null)
{
throw new ArgumentException($"Could not find filter for type '{type.FullName}'");
}
return filter;
}
}
PipelineX implementation would be:
public class PipelineX<T> : FilterBase<T>, IPipelineX<T>
{
private readonly FilterFactory _factory;
public PipelineX(FilterFactory factory)
{
_factory = factory;
}
protected override T Process(T input)
{
return input;
}
public PipelineX<T> FilterBy<X>()
{
var filter = _factory.Create<X,T>() as IFilter<T>;
Register(filter);
return this;
}
}
Registering the factory using Autofac:
builder.Register<FilterFactory>(c =>
{
var context = c.Resolve<IComponentContext>();
return new FilterFactory(context.Resolve);
});
I design some application with using Repository pattern. I have a generic repository:
public interface IGenericRepository<T> where T : class { // ... }
public class GenericRepository <T> : IGenericRepository<T> where T : class {// ... }
For example, I have two entities: Order and OrderLine.
For entity "Order" I need only generic repository methods, so it's OK.
But for entity "OrderLine" I need some generic repository methods and some addtitional. So, I create a custom repository, which extends generic:
public interface IOrderLinesRepository : IGenericRepository<OrderLine> {...}
public class OrderLinesRepository : GenericRepository<OrderLine>, IOrderLinesRepository
{
//... this is my additional methods here
}
But now I want to create method, which will return a repository by entity type.
If entity has a custom repository - it should be retuned. If not, method must return GenericRepository. Here is my attempt to create this.
public sealed class RepositoryFactory
{
// Here is custom repository types
private IDictionary<Type, Func<DbContext, object>> GetCustomFactories()
{
return new Dictionary<Type, Func<DbContext, object>>
{
{ typeof(OrderLine), dbContext =>
new OrderLinesRepository(dbContext) },
};
}
// Custom repository types
private IDictionary<Type, Type> GetRepositoryTypes()
{
return new Dictionary<Type, Type>
{
{ typeof(OrderLine), typeof(IOrderLinesRepository) }
};
}
private Func<GoodWillDbContext, object> GetDefaultFactory<T>()
where T : class
{
return dbContext => new GenericRepository<T>(dbContext);
}
private Func<DbContext, object> GetFactory<T>() where T : class
{
Func<DbContext, object> factory;
var factories = GetCustomFactories();
factories.TryGetValue(typeof(T), out factory);
return factory ?? (factory = GetDefaultFactory<T>());
}
public Type GetRepoType(Type type)
{
var types = GetRepositoryTypes();
Type repoType;
types.TryGetValue(type, out repoType);
return repoType;
}
public object MakeRepository<U>(DbContext dbContext) where U : class
{
// Get repository type
// If custom type not found, it should be standart type
// IGenericRepository<U>
var type = _repositoryFactory.GetRepoType(typeof(U)) ??
typeof(IGenericRepository<U>);
var f = _repositoryFactory.GetFactory<U>();
var repo = f(dbContext);
return repo;
}
}
But it's not working for some reason. I have some questions:
1. What type of return value should be in MakeRepository?
2. How should I cast var repo = f(dbContext); to repository type?
Or maybe there is another way to do what I need?
Your code seems really complicated for something that should be simple... The below example is just dummy code, feel free to adapt it to your needs.
public static class RepositoryFactory
{
public static IGenericRepository<T> MakeRepository<T>(DbContext context)
where T : class
{
var type = typeof(T);
if (type == typeof(Car))
{
return CarRepositoryFactory.CreateCarRpository(context);
}
else if (type == typeof(Boat))
{
return BoatRepositoryFactory.CreateBoatRepository(context);
}
}
}
How to use it:
var myRepo = (CarRepository)RepositoryFactory.MakeRepository(new MyContext());
These kind of codes are not meant to be modified frequently (you don't add a repository at each commit, do you? ;)), so do yourself a favor and keep it simple. :)
Edit : if you really want to keep your code, please add a comment to my response and I see want I'll can do. :)
So if I understand it correctly, you want to be able to get the repository type by the entity type. I modified your implementation a little and I got it to work as I believe it should.
public sealed class RepositoryFactory
{
public static RepositoryFactory Instance = new RepositoryFactory();
// Here is custom repository types
private IDictionary<Type, Func<DbContext, object>> GetCustomFactories()
{
return new Dictionary<Type, Func<DbContext, object>>
{
// { typeof(IOrderLinesRepository), dbContext => new OrderLinesRepository(dbContext) },
};
}
// Custom repository types
private IDictionary<Type, Type> GetRepositoryTypes()
{
return new Dictionary<Type, Type>
{
{ typeof(OrderLine), typeof(IOrderLinesRepository) }
};
}
private Func<DbContext, object> GetDefaultFactory<T>()
where T : class
{
return dbContext => new GenericRepository<T>(dbContext);
}
private Func<DbContext, object> GetFactory(Type factoryType)
{
if (factoryType == null) return null;
Func<DbContext, object> factory;
var factories = GetCustomFactories();
factories.TryGetValue(factoryType, out factory);
return factory;
}
public Type GetRepoType(Type type)
{
var types = GetRepositoryTypes();
Type repoType;
types.TryGetValue(type, out repoType);
return repoType;
}
public IGenericRepository<TEntity> MakeRepositoryByEntity<TEntity>(DbContext dbContext) where TEntity : class
{
var type = this.GetRepoType(typeof(TEntity));
var f = this.GetFactory(type) ?? GetDefaultFactory<TEntity>();
var repo = f(dbContext);
return (IGenericRepository<TEntity>)repo;
}
public TRepo MakeRepository<TRepo, TEntity>(DbContext dbContext)
where TRepo : class, IGenericRepository<TEntity>
where TEntity : class
{
var repo = this.MakeRepositoryByEntity<TEntity>(dbContext);
try
{
return (TRepo)repo;
}
catch (InvalidCastException)
{
throw new Exception($"Registered repository for entity ${typeof(TEntity).FullName} does not implement {typeof(TRepo).FullName}");
}
}
}
You can invoke it in one of two way:
var d = RepositoryFactory.Instance.MakeRepositoryByEntity<OrderLine>(dbContext);
var d2 = RepositoryFactory.Instance.MakeRepository<IOrderLinesRepository, OrderLine>(dbContext);
I have a factory that creates validator instances. I pass in an object to validate, and it gives me the validator that I can use to validate it.
public class ValidatorFactory : IValidatorFactory
{
public ValidatorFactory(IComponentContext container) { _container = container; }
private readonly IComponentContext _container;
public IValidator create(object objectToValidate)
{
var validatorType = typeof(IValidator<>).MakeGenericType(new Type[] { objectToValidate.GetType() });
object validator;
_container.TryResolve(validatorType, out validator);
return validator as EntityValidatorI;
}
}
It works, but I need to pass in the container IComponentContext.
Is there a better way where I don't need to do that?
Autofac has "Implicit Relationship Types" but I'm unsure how to use them here, as the type would only be known at runtime.
you can do something like below,
Instead of injecting IComponentContext into your main classes, inject a generic
Func method.
The code below might not compile as I quickly just wrote it here but I hope you get the idea.
public class ValidatorFactory : IValidatorFactory
{
public ValidatorFactory(Func<Type, IValidator> factory) { _factory = factory; }
private readonly Func<Type, IValidator> _factory;
public IValidator create(object objectToValidate)
{
var validatorType = typeof(IValidator<>).MakeGenericType(new Type[] { objectToValidate.GetType() });
return _factory(validatorType);
}
}
public static class YourBootstrapperClass{
public static void Register(ContainerBuilder containerBuilder){
containerBuilder.Register(ctx => new ValidatorFactory(type => {
object validator;
containerBuilder.TryResolve(validatorType, out validator);
return validator;
})).As<IValidatorFactory>();
}
}
I have these open-generics:
public interface IQuery<out TResult> {}
public interface ICacheableQuery<out TResult> : IQuery<TResult> {
string CacheKey { get; }
}
public interface IQueryHandler<in TQuery, out TResult>
where TQuery : IQuery<TResult> {
TResult Handle(TQuery query);
}
and this single decorator:
public class CacheableQueryHandlerDecorator<TQuery, TResult>
: IQueryHandler<TQuery, TResult>
where TQuery : ICacheableQuery<TResult> {
public TResult Handle(TQuery query) {
// doing stuffs....
}
}
What I want is to register decorator only for queries which are implementing ICacheableQuery<out TResult>. I'm registering components like this:
builder.RegisterAssemblyTypes(assemblies)
.AsClosedTypesOf(typeof (IQueryHandler<,>))
.AsImplementedInterfaces()
.Named("queryHandler",typeof(IQueryHandler<,>));
builder.RegisterGenericDecorator(
typeof(CacheableQueryHandlerDecorator<,>),
typeof(IQueryHandler<,>),
fromKey: "queryHandler");
But it registers the decorator for all types. Any idea? Thanks in advance.
Unfortunately, Autofac hasn't got this feature out-of-the-box. However, it can be achieved implementing a RegistrationSource:
public interface IGenericDecoratorRegistrationBuilder : IHideObjectMembers
{
IGenericDecoratorRegistrationBuilder Decorator(Type decoratorType, Func<Type, bool> filter = null, Func<Type, IEnumerable<Parameter>> paramsGetter = null);
}
public static class GenericDecorators
{
public class GenericDecoratorRegistration
{
public Type Type;
public Func<Type, bool> Filter;
public Func<Type, IEnumerable<Parameter>> ParamsGetter;
}
class GenericDecoratorRegistrationBuilder : IGenericDecoratorRegistrationBuilder
{
readonly List<GenericDecoratorRegistration> decorators = new List<GenericDecoratorRegistration>();
public IEnumerable<GenericDecoratorRegistration> Decorators
{
get { return decorators; }
}
public IGenericDecoratorRegistrationBuilder Decorator(Type decoratorType, Func<Type, bool> filter, Func<Type, IEnumerable<Parameter>> paramsGetter)
{
if (decoratorType == null)
throw new ArgumentNullException("decoratorType");
if (!decoratorType.IsGenericTypeDefinition)
throw new ArgumentException(null, "decoratorType");
var decorator = new GenericDecoratorRegistration
{
Type = decoratorType,
Filter = filter,
ParamsGetter = paramsGetter
};
decorators.Add(decorator);
return this;
}
}
class GenericDecoratorRegistrationSource : IRegistrationSource
{
readonly Type decoratedType;
readonly IEnumerable<GenericDecoratorRegistration> decorators;
readonly object fromKey;
readonly object toKey;
public GenericDecoratorRegistrationSource(Type decoratedType, IEnumerable<GenericDecoratorRegistration> decorators, object fromKey, object toKey)
{
this.decoratedType = decoratedType;
this.decorators = decorators;
this.fromKey = fromKey;
this.toKey = toKey;
}
public bool IsAdapterForIndividualComponents
{
get { return true; }
}
public IEnumerable<IComponentRegistration> RegistrationsFor(Service service, Func<Service, IEnumerable<IComponentRegistration>> registrationAccessor)
{
var swt = service as IServiceWithType;
KeyedService ks;
if (swt == null ||
(ks = new KeyedService(fromKey, swt.ServiceType)) == service ||
!swt.ServiceType.IsGenericType || swt.ServiceType.GetGenericTypeDefinition() != decoratedType)
return Enumerable.Empty<IComponentRegistration>();
return registrationAccessor(ks).Select(cr => new ComponentRegistration(
Guid.NewGuid(),
BuildActivator(cr, swt),
cr.Lifetime,
cr.Sharing,
cr.Ownership,
new[] { toKey != null ? (Service)new KeyedService(toKey, swt.ServiceType) : new TypedService(swt.ServiceType) },
cr.Metadata,
cr));
}
DelegateActivator BuildActivator(IComponentRegistration cr, IServiceWithType swt)
{
var limitType = cr.Activator.LimitType;
var actualDecorators = decorators
.Where(d => d.Filter != null ? d.Filter(limitType) : true)
.Select(d => new { Type = d.Type, Parameters = d.ParamsGetter != null ? d.ParamsGetter(limitType) : Enumerable.Empty<Parameter>() })
.ToArray();
return new DelegateActivator(cr.Activator.LimitType, (ctx, p) =>
{
var typeArgs = swt.ServiceType.GetGenericArguments();
var service = ctx.ResolveKeyed(fromKey, swt.ServiceType);
foreach (var decorator in actualDecorators)
{
var decoratorType = decorator.Type.MakeGenericType(typeArgs);
var #params = decorator.Parameters.Concat(new[] { new TypedParameter(swt.ServiceType, service) });
var activator = new ReflectionActivator(decoratorType, new DefaultConstructorFinder(), new MostParametersConstructorSelector(),
#params, Enumerable.Empty<Parameter>());
service = activator.ActivateInstance(ctx, #params);
}
return service;
});
}
}
public static IGenericDecoratorRegistrationBuilder RegisterGenericDecorators(this ContainerBuilder builder, Type decoratedServiceType, object fromKey, object toKey = null)
{
if (builder == null)
throw new ArgumentNullException("builder");
if (decoratedServiceType == null)
throw new ArgumentNullException("decoratedServiceType");
if (!decoratedServiceType.IsGenericTypeDefinition)
throw new ArgumentException(null, "decoratedServiceType");
var rb = new GenericDecoratorRegistrationBuilder();
builder.RegisterCallback(cr => cr.AddRegistrationSource(new GenericDecoratorRegistrationSource(decoratedServiceType, rb.Decorators, fromKey, toKey)));
return rb;
}
}
Sample usage:
public interface IGeneric<T>
{
void SomeMethod();
}
class IntImpl : IGeneric<int>
{
public void SomeMethod() { }
}
class StringImpl : IGeneric<string>
{
public void SomeMethod() { }
}
class GenericDecorator<T> : IGeneric<T>
{
IGeneric<T> target;
public GenericDecorator(IGeneric<T> target)
{
this.target = target;
}
public void SomeMethod()
{
target.SomeMethod();
}
}
static void Configure(ContainerBuilder builder)
{
builder.RegisterType<IntImpl>().Named<IGeneric<int>>("generic");
builder.RegisterType<StringImpl>().Named<IGeneric<string>>("generic");
builder.RegisterGenericDecorators(typeof(IGeneric<>), "generic")
// applying decorator to IGeneric<string> only
.Decorator(typeof(GenericDecorator<>), t => typeof(IGeneric<string>).IsAssignableFrom(t));
}
Please note
You must key the registrations of decorated components because (as far as I know) there's no way to override these with dynamic registrations provided by the RegistrationSource.
In this solution the decorated component inherits the configuration of the decorated one (scoping, sharing, ownership, etc)