Autofac and EasyNetQ are almost all the time easy to work with but today we have kind of challenge: i want to set up unit of work for subscribers
class Program
{
static void Main(string[] args)
{
var container = AutofacContainerFactory.Build();
using (var scope = container.BeginLifetimeScope())
{
var bus = scope.Resolve<IBus>();
bus.Subscribe<SomeMessage>("some.queue", container.Resolve<ISomeMessageHandler>().Handle);
Console.WriteLine("Listening for messages. Hit <return> to quit.");
Console.ReadLine();
}
}
}
This is fairly easy subscriber in console app but how to properly setup UoW: i would be nice if some interceptor was called before handler Handle method was called and UoW was called after execution. Another resolution I can think of is do everything inside Handle method: maybe using some kind of UnitOfWork decorator pattern. I waiting for your thoughts.
I've created a AutofacMessageDispatcher that can do all that:
public class AutofacMessageDispatcher : IAutoSubscriberMessageDispatcher
{
readonly ILifetimeScope _component;
readonly IMessageContextFactory _contextFactory;
public const string PerMessageLifeTimeScopeTag = "AutofacMessageScope";
public const string GlobalPipeTag = "global";
public AutofacMessageDispatcher(ILifetimeScope component, IMessageContextFactory contextFactory)
{
_component = component;
_contextFactory = contextFactory;
}
static IEnumerable<IErrorHandler> GetErrorHandlers<TConsumer>(TConsumer consumer, IComponentContext scope)
{
var errorHandlers = consumer.GetType()
.GetTypeInfo().GetAttributes<ErrorHandlerAttribute>()
.OrderBy(attribute => attribute.Order)
.Select(attribute => attribute.Initialize((IErrorHandler) scope.Resolve(attribute.ErrorHandlerType)))
.Union(scope.ResolveNamed<IEnumerable<IErrorHandler>>(GlobalPipeTag), a => a.GetType()); // perform the distinction in the union on GetType so we only get 1 handler of the same type
if (consumer is IErrorHandler consumerAsErrorHandler)
errorHandlers = errorHandlers.Concat(new[] { consumerAsErrorHandler });
return errorHandlers;
}
static IEnumerable<IPipe> GetPipeLine<TConsumer>(TConsumer consumer, IComponentContext scope)
{
var pipeLine = consumer.GetType()
.GetTypeInfo().GetAttributes<PipeAttribute>()
.OrderBy(attribute => attribute.Order)
.Select(attribute => attribute.Initialize((IPipe) scope.Resolve(attribute.PipeType)))
.Union(scope.ResolveNamed<IEnumerable<IPipe>>(GlobalPipeTag), a => a.GetType()); // perform the distinction in the union on GetType so we only get 1 handler of the same type
return pipeLine;
}
[HandleProcessCorruptedStateExceptions]
public void Dispatch<TMessage, TConsumer>(TMessage message)
where TMessage : class
where TConsumer : IConsume<TMessage>
{
using (var scope = _component.BeginLifetimeScope(PerMessageLifeTimeScopeTag, _contextFactory.RegisterMessageContext(typeof(TConsumer), message)))
{
var consumer = scope.Resolve<TConsumer>();
var pipeLine = GetPipeLine(consumer, scope).ToArray();
pipeLine.Each(p => p.OnBeforeConsume(consumer, message));
Exception exception = null;
try
{
consumer.Consume(message);
}
catch (Exception e) when (GetErrorHandlers(consumer, scope).Any(p => p.OnError(consumer, message, e)))
{
exception = e;
}
pipeLine.Reverse().Each(p => p.OnAfterConsume(consumer, message, exception));
}
}
[HandleProcessCorruptedStateExceptions]
public async Task DispatchAsync<TMessage, TConsumer>(TMessage message)
where TMessage : class
where TConsumer : IConsumeAsync<TMessage>
{
using (var scope = _component.BeginLifetimeScope(PerMessageLifeTimeScopeTag, _contextFactory.RegisterMessageContext(typeof(TConsumer), message)))
{
var consumer = scope.Resolve<TConsumer>();
var pipes = GetPipeLine(consumer, scope).ToArray();
Exception exception = null;
foreach (var hook in pipes)
await hook.OnBeforeConsumeAsync(consumer, message);
try
{
await consumer.Consume(message);
}
catch (Exception e) when (GetErrorHandlers(consumer, scope).Any(p => p.OnErrorAsync(consumer, message, e)))
{
exception = e;
}
foreach (var hook in pipes.Reverse())
await hook.OnAfterConsumeAsync(consumer, message, exception);
}
}
}
public interface IMessageContextFactory
{
Action<ContainerBuilder> RegisterMessageContext<TMessage>(Type consumerType, TMessage message) where TMessage : class;
}
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class ErrorHandlerAttribute : Attribute
{
public ErrorHandlerAttribute(Type errorHandlerType, int order = 0)
{
ErrorHandlerType = errorHandlerType;
Order = order;
}
public Type ErrorHandlerType { get; set; }
public int Order { get; set; }
public virtual IErrorHandler Initialize(IErrorHandler handler)
{
return handler;
}
}
public interface IErrorHandler
{
bool OnError<TMessage, TConsumer>(TConsumer consumer, TMessage message, Exception exception)
where TMessage : class
where TConsumer : IConsume<TMessage>;
bool OnErrorAsync<TMessage, TConsumer>(TConsumer consumer, TMessage message, Exception exception)
where TMessage : class
where TConsumer : IConsumeAsync<TMessage>;
}
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class PipeAttribute : Attribute
{
public PipeAttribute(Type pipeType, int order = 0)
{
PipeType = pipeType;
Order = order;
}
public Type PipeType { get; set; }
public int Order { get; set; }
public IPipe Initialize(IPipe pipe)
{
return pipe;
}
}
public interface IPipe
{
void OnBeforeConsume<TMessage, TConsumer>(TConsumer consumer, TMessage message)
where TMessage : class
where TConsumer : IConsume<TMessage>;
void OnAfterConsume<TMessage, TConsumer>(TConsumer consumer, TMessage message, [CanBeNull] Exception exception)
where TMessage : class
where TConsumer : IConsume<TMessage>;
Task OnBeforeConsumeAsync<TMessage, TConsumer>(TConsumer consumer, TMessage message)
where TMessage : class
where TConsumer : IConsumeAsync<TMessage>;
Task OnAfterConsumeAsync<TMessage, TConsumer>(TConsumer consumer, TMessage message, [CanBeNull] Exception exception)
where TMessage : class
where TConsumer : IConsumeAsync<TMessage>;
}
public interface IMessageContext
{
object Message { get; }
}
public class MessageContext : IMessageContext
{
public MessageContext(object message)
{
Message = message;
}
public object Message { get; set; }
}
public class MessageContextFactory : IMessageContextFactory
{
readonly ILogger _logger;
public MessageContextFactory()
{
_logger = logger;
}
public Action<ContainerBuilder> RegisterMessageContext<TMessage>(Type consumerType, TMessage message) where TMessage : class
{
return builder =>
{
builder.RegisterInstance(new MessageContext(message)).As<IMessageContext>().AsSelf();
var forContext = _logger.ForContext(message.GetType());
builder.RegisterInstance(forContext).As<ILogger>().AsSelf();
};
}
}
public interface IMessageContextFactory
{
Action<ContainerBuilder> RegisterMessageContext<TMessage>(Type consumerType, TMessage message) where TMessage : class;
}
This allows you to:
Register custom 'pipes' and 'errorhandlers' using attributes on your consumers
You can register pipes and error handlers globally on in your container if you register them with the 'global' name
You can resolve 'IMessageContext' anywhere in your dependencies to get access to the message
if you use serilog, it will create a logging context including your message (otherwise just get rid of the ILogger references)
Sorry I just added the code quickly I might have missed some dependencies.
Here's a repo where I keep my EasyNetQ extensions if you miss anything:
https://github.com/zidad/net-tools/tree/master/src/Net.EasyNetQ
Hope this helps!
public class UnitOfWorkDecorator<TRequest, THandler> : IHandler<TRequest>
where TRequest : class
where THandler : IHandler<TRequest>
{
protected readonly Func<ILifetimeScope> ParentScope;
public UnitOfWorkDecorator(Func<ILifetimeScope> parentScope)
{
ParentScope = parentScope;
}
public void Handle(TRequest request)
{
Console.WriteLine("UoW handler start");
using (var scope = ParentScope().BeginLifetimeScope())
{
var scopedHandler = scope.Resolve<THandler>();
scopedHandler.Handle(request);
}
Console.WriteLine("UoW handler end");
}
}
Test
[Test]
public void UnitOfWork()
{
var builder = new ContainerBuilder();
builder.RegisterType<MessageCommandHandler>().As<IHandler<Message>>().InstancePerLifetimeScope();
builder.RegisterType<MessageCommandHandler2>().As<IHandler<Message>>().InstancePerLifetimeScope();
builder.RegisterGeneric(typeof(UnitOfWorkDecorator<,>)).AsSelf().SingleInstance();
var container = builder.Build();
var handler = container.Resolve<IHandler<Message>>();
var uow = container.Resolve<UnitOfWorkDecorator<Message, IHandler<Message>>>();
uow.Handle(new Message());
}
so the main idea is to create nested scope for resolving decorated handler.
Logic for managing unit of work can be resolved inside scope so for example decorated handler and uow can have same instance of EF db context.
Related
I currently wrote an Interceptor which code is below
public class TransactionalInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
using (var transaction = ...)
{
try
{
invocation.Proceed();
transaction.Commit();
}
catch
{
transaction.Rollback();
}
finally
{
transaction.Dispose();
}
}
}
}
but when register this interceptor it will apply to all methods. I have a service class with a repository injected having CRUD methods.
I don't want a transaction to be opened for query methods.
I read this link but I cannot figure out how to apply it to my code
http://docs.autofac.org/en/latest/advanced/adapters-decorators.html#decorators
I don't know who to refactor my TransactionalInterceptor (and register it) to use it in a class like this code
[Intercept(typeof(LoggerInterceptor))] //logger
public class SomeService : ISomeService
{
private readonly ISomeRepository someRepository;
public SomeService(SomeRepository someRepository)
{
this.someRepository = someRepository;
}
public IEnumerable<SomeDto> GetAll()
{
// code
}
public SomeDto GetById()
{
// code
}
[Transactional]
public int Create(SomeDto someDto)
{
// code to insert
}
}
The invocation parameter of the Intercept method contains a Method property which is a MethodInfo of the method currently intercepted.
You can use this property to do what you want.
For example by using the method name :
public void Intercept(IInvocation invocation)
{
if (invocation.MethodInvocationTarget.Name != nameof(ISomeService.Create))
{
invocation.Proceed();
return;
}
using (var transaction = ...)
{
try
{
invocation.Proceed();
transaction.Commit();
}
catch
{
transaction.Rollback();
}
finally
{
transaction.Dispose();
}
}
}
or based on an attribute from the target method :
if (!invocation.MethodInvocationTarget
.CustomAttributes
.Any(a => a.AttributeType == typeof(TransactionalAttribute)))
You can also use the IInterceptorSelector type but it requires more work to register it with Autofac
I solved the problem with ProxyGenerationHook. See the answer
Create your custom attribute for selecting which method to intercept. This attribute's target should be Method.
[System.AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
sealed class UseInterceptorAttribute : Attribute
{
public UseInterceptorAttribute()
{
}
}
Create your service interface and service class:
public interface ISomeService
{
void GetWithoutInterceptor();
[UseInterceptor]
void GetWithInterceptor();
}
public class SomeService
{
void GetWithoutInterceptor()
{
//This method will not be intercepted...
}
[UseInterceptor]
void GetWithInterceptor()
{
//This method will be intercepted...
}
}
Create your ProxyGenerationHook
public class SomeServiceProxyGenerationHook : IProxyGenerationHook
{
public void MethodsInspected()
{
}
public void NonProxyableMemberNotification(Type type, MemberInfo memberInfo)
{
}
public bool ShouldInterceptMethod(Type type, MethodInfo methodInfo)
{
return methodInfo
.CustomAttributes
.Any(a => a.AttributeType == typeof(UseInterceptorAttribute));
}
}
Don't use attributes for enabling interceptors. Enable it when
registering your service like this:
public class AutofacDependencyResolver
{
private readonly IContainer _container;
public AutofacDependencyResolver()
{
_container = BuildContainer();
}
private IContainer BuildContainer()
{
var proxyGenerationOptions = new ProxyGenerationOptions(new ProductServiceProxyGenerationHook());
builder.RegisterType<SomeService>()
.As<ISomeService>()
.EnableInterfaceInterceptors(proxyGenerationOptions)
.InterceptedBy(typeof(TransactionalInterceptor))
builder.Register(c => new TransactionalInterceptor());
return builder.Build();
}
public T GetService<T>()
where T:class
{
var result = _container.TryResolve(out T serviceInstance);
return serviceInstance ?? throw new Exception($"The service could not found: {nameof(T)}");
}
}
This solution is following this article
Also I uploaded the minimal example about this solution.
also can try, it is simple https://fs7744.github.io/Norns.Urd/index.html
public class AddTenInterceptorAttribute : AbstractInterceptorAttribute
{
public override void Invoke(AspectContext context, AspectDelegate next)
{
next(context);
AddTen(context);
}
private static void AddTen(AspectContext context)
{
if (context.ReturnValue is int i)
{
context.ReturnValue = i + 10;
}
else if(context.ReturnValue is double d)
{
context.ReturnValue = d + 10.0;
}
}
public override async Task InvokeAsync(AspectContext context, AsyncAspectDelegate next)
{
await next(context);
AddTen(context);
}
}
[AddTenInterceptor]
public interface IGenericTest<T, R> : IDisposable
{
// or
//[AddTenInterceptor]
T GetT();
}
I am trying to build a microservice that pulls commands and queries off of a queue. My service watches a specific queue and pulls a message off that could be any type command for that domain. That message is pulled off and passed to the dispatcher. My problem is that in trying to make everything as generic as possible, my message is being passed into the dispatcher as an object, but the dispatcher needs a concrete IQuery class. I can cast it to IQuery because I know that it is that type, but when I try to resolve the dependencies in the dispatcher based on the type, it fails because the concrete type is not known. I will try to keep the code samples small and to the point:
Main Service
private static async Task HandleMessageReceived(MessageReceivedEventArgs args) {
await queryDispatcher.Dispatch((IQuery)args.Message.Payload);
}
Query Dispatcher
public class QueryDispatcher : IQueryDispatcher {
#region attributes
private readonly IComponentContext _context;
#endregion
#region ctor
public QueryDispatcher(IComponentContext context) {
_context = context;
}
#endregion
#region methods
public async Task<IEvent> Dispatch<T>(T query) where T : IQuery{
if (query == null) {
return new ArgumentNullEvent(nameof(query));
}
var validator = _context.Resolve<IValidator<T>>();
var handler = _context.Resolve<IQueryHandler<T>>();
List<ValidationResult> errors = await validator.Validate(query);
if(errors.Count == 0) {
return await handler.Handle(query);
} else {
return new ValidationFailedEvent(query.Id, errors);
}
}
#endregion
}
Queue Message
public class QueueMessage {
public Guid OperationId { get; set; }
public object Payload { get; set; }
}
Concrete IQuery Impl
public class GetSite : BaseQuery, IQuery {
public Guid SiteGuid { get; set; }
}
You could build the type using MakeGenericType:
var validator = _context.Resolve(typeof(IValidator<>)
.MakeGenericType(new[] { query.GetType() }));
var handler = _context.Resolve(typeof(IQueryHandler<>)
.MakeGenericType(new[] { query.GetType() }));
Here is my try to add a Stash to the actor:
class Program
{
static void Main(string[] args)
{
var actorSystem = ActorSystem.Create("mySystem");
var container = new WindsorContainer();
container.Install(new CommonWindsorInstaller());
// ReSharper disable once ObjectCreationAsStatement
new WindsorDependencyResolver(container, actorSystem);
var rootActor = actorSystem.ActorOf(actorSystem.DI().Props<RootActor>(), typeof(RootActor).Name);
rootActor.Tell(new CreateChildMessage());
Console.ReadLine();
}
}
internal class CreateChildMessage
{
}
internal class CommonWindsorInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(Component.For<RootActor>().Named("RootActor").LifestyleTransient());
container.Register(Component.For<ChildActor>().Named("ChildActor").LifestyleTransient());
container.Register(Component.For<IChildActor>().ImplementedBy<ChildActor>().Named("IChildActor").LifestyleTransient());
}
}
internal class RootActor : ReceiveActor
{
public RootActor()
{
Receive<CreateChildMessage>(
m =>
{
var child = ActorHelper.CreateActor(Context, typeof(IChildActor), "child");
});
}
}
public interface IChildActor
{
}
public class ChildActor : ReceiveActor, IChildActor, IWithUnboundedStash
{
public IStash Stash { get; set; }
}
public static class ActorHelper
{
public static IActorRef CreateActor(IUntypedActorContext context, Type actorType, string name)
{
var actorName = actorType.Name + "." + name;
var actor = context.Child(actorName);
if (!actor.Equals(ActorRefs.Nobody))
{
return actor;
}
var actorProps = context.DI().Props(actorType);
actor = context.ActorOf(actorProps, actorName);
return actor;
}
}
Unfortunately, I'm getting following exception:
[ERROR][17.02.2017 9:39:19][Thread 0004][ActorSystem(mySystem)] An exception occurred while trying to apply plugin of type Akka.Actor.ActorStashPlugin to the newly created actor (Type = Akka.Stash.ChildActor, Path = akka://mySystem/user/RootActor/IChildActor.child)
Cause: System.NotSupportedException: DequeBasedMailbox required, got: Mailbox
An (unbounded) deque-based mailbox can be configured as follows:
my-custom-mailbox {
mailbox-type = "Akka.Dispatch.UnboundedDequeBasedMailbox"
}
at Akka.Actor.Internal.AbstractStash..ctor(IActorContext context, Int32 capacity)
at Akka.Actor.StashFactory.CreateStash(IActorContext context, Type actorType)
at Akka.Actor.ActorStashPlugin.AfterIncarnated(ActorBase actor, IActorContext context)
at Akka.Actor.ActorProducerPipeline.AfterActorIncarnated(ActorBase actor, IActorContext context)
EDIT
I can get rid of this error by making my IChildActor descendant of IWithUnboundedStash, i.e.
public interface IChildActor: IWithUnboundedStash
{
}
public class ChildActor : ReceiveActor, IChildActor
{
public IStash Stash { get; set; }
}
But it's not the option, I don't want to all my IChildActor implementations has a stash
EDIT 2
Here is the GitHub repository to reproduce https://github.com/bonza/Akka.Net.CastleWindsor.Stash/
I have been building a application with PRISM, this application is going to be groing alot over the coming years so it is set up modular and with IoC in mind.
In this application I am currently implementing Nlog for the logging mechanism.
Because I do not want to add a reference to NLog throughout my whole application I have created an interface for the logger
public interface ILog
{
void Log(LogLevel loglevel, string format);
void Log(LogLevel loglevel, string format, params object[] args);
void Log(LogLevel loglevel, Exception exc, string format, params object[] args);
}
I have also created an interface for the factory
public interface ILogFactory
{
ILog Initialize(Type type);
}
The factory takes a Type parameter that can be used to created a logger specific for that class. Now comes the challenge where I am going to use the ILog I would like to have a instance using the target types class.
public class ClassA
{
private ILog Logger { get; set;}
public ClassA(Ilog logger)
{
Logger = logger;
}
}
How can I wire my ILogFactory into PRISM so on a resolve of a class that uses Ilog that is uses the ILogFactory.Initialize(Type type) with in this case typeof(ClassA).
So after searching more of the web I finally found what I was looking for at
this blog
I had to modify the code a bid to make it not depend on log4net this resulted in the following code
A BuildTrackingExtension which we need to know where the logger we are going the create is needed.
public class BuildTrackingExtension : UnityContainerExtension
{
protected override void Initialize()
{
Context.Strategies.AddNew<BuildTrackingStrategy>(UnityBuildStage.TypeMapping);
}
public static IBuildTrackingPolicy GetPolicy(IBuilderContext context)
{
return context.Policies.Get<IBuildTrackingPolicy>(context.BuildKey, true);
}
public static IBuildTrackingPolicy SetPolicy(IBuilderContext context)
{
IBuildTrackingPolicy policy = new BuildTrackingPolicy();
context.Policies.SetDefault(policy);
return policy;
}
}
public class BuildTrackingStrategy : BuilderStrategy
{
public override void PreBuildUp(IBuilderContext context)
{
var policy = BuildTrackingExtension.GetPolicy(context)
?? BuildTrackingExtension.SetPolicy(context);
policy.BuildKeys.Push(context.BuildKey);
}
public override void PostBuildUp(IBuilderContext context)
{
IBuildTrackingPolicy policy = BuildTrackingExtension.GetPolicy(context);
if ((policy != null) && (policy.BuildKeys.Count > 0))
{
policy.BuildKeys.Pop();
}
}
}
public interface IBuildTrackingPolicy : IBuilderPolicy
{
Stack<object> BuildKeys { get; }
}
public class BuildTrackingPolicy : IBuildTrackingPolicy
{
public BuildTrackingPolicy()
{
BuildKeys = new Stack<object>();
}
public Stack<object> BuildKeys { get; private set; }
}
Then a LogCreationExtension to create the logger from my ILogFactory
public class LogCreationExtension : UnityContainerExtension
{
private ILogFactory LogFactory;
private LogCreationStrategy strategy;
public LogCreationExtension(ILogFactory logFactory)
{
LogFactory = logFactory;
}
protected override void Initialize()
{
strategy = new LogCreationStrategy(LogFactory);
Context.Strategies.Add(strategy, UnityBuildStage.PreCreation);
}
}
public class LogCreationStrategy : BuilderStrategy
{
public bool IsPolicySet { get; private set; }
private ILogFactory LogFactory;
public LogCreationStrategy(ILogFactory logFactory)
{
LogFactory = logFactory;
}
public override void PreBuildUp(IBuilderContext context)
{
Type typeToBuild = context.BuildKey.Type;
if (typeof(ILog).Equals(typeToBuild))
{
if (context.Policies.Get<IBuildPlanPolicy>(context.BuildKey) == null)
{
Type typeForLog = LogCreationStrategy.GetLogType(context);
IBuildPlanPolicy policy = new LogBuildPlanPolicy(typeForLog, LogFactory);
context.Policies.Set<IBuildPlanPolicy>(policy, context.BuildKey);
IsPolicySet = true;
}
}
}
public override void PostBuildUp(IBuilderContext context)
{
if (IsPolicySet)
{
context.Policies.Clear<IBuildPlanPolicy>(context.BuildKey);
IsPolicySet = false;
}
}
private static Type GetLogType(IBuilderContext context)
{
Type logType = null;
IBuildTrackingPolicy buildTrackingPolicy = BuildTrackingExtension.GetPolicy(context);
if ((buildTrackingPolicy != null) && (buildTrackingPolicy.BuildKeys.Count >= 2))
{
logType = ((NamedTypeBuildKey)buildTrackingPolicy.BuildKeys.ElementAt(1)).Type;
}
else
{
StackTrace stackTrace = new StackTrace();
//first two are in the log creation strategy, can skip over them
for (int i = 2; i < stackTrace.FrameCount; i++)
{
StackFrame frame = stackTrace.GetFrame(i);
logType = frame.GetMethod().DeclaringType;
if (!logType.FullName.StartsWith("Microsoft.Practices"))
{
break;
}
}
}
return logType;
}
}
public class LogBuildPlanPolicy : IBuildPlanPolicy
{
private ILogFactory LogFactory;
public LogBuildPlanPolicy(Type logType, ILogFactory logFactory)
{
LogType = logType;
LogFactory = logFactory;
}
public Type LogType { get; private set; }
public void BuildUp(IBuilderContext context)
{
if (context.Existing == null)
{
ILog log = LogFactory.Initialize(LogType);
context.Existing = log;
}
}
}
Wiring it up using unity
//Container.RegisterType<ILogFactory, NLogLogFactory>();
Container.RegisterInstance<ILogFactory>(_LogFactory);
Container.AddNewExtension<BuildTrackingExtension>();
Container.AddNewExtension<LogCreationExtension>();
I am using RegisterInstance because I am using the logfactory before InitializeShell is called
I have the following code:
_container = new Container(x => x.AddRegistry<ManagerRegistry>());
-
public class ManagerRegistry : Registry
{
public ManagerRegistry()
{
var proxyGenerator = new ProxyGenerator();
For<IPersonManager>()
.EnrichAllWith(t => proxyGenerator.CreateInterfaceProxyWithTarget(
t, new AuthenticationInterceptor()))
.Use<PersonManager>();
}
}
-
public class AuthenticationInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
if (!HttpContext.Current.User.IsInRole("Monkey"))
throw new Exception("Only monkeys allowed!");
invocation.Proceed();
}
}
It interceps the creation of a dependency in StructureMap, and decorates it using DynamicProxy.
Now this works fine, because the interceptor has no dependencies itself.
But given the following:
public class LoggingInterceptor : IInterceptor
{
public LoggingInterceptor(ILogger logger)
{
How would I go about wiring that up in StructureMap?
This is what I came up with:
_container.RegisterInterceptor<IPersonManager, LoggingInterceptor>();
-
public static class ContainerExtensions
{
public static void RegisterInterceptor<TDependency, TInterceptor>(this IContainer container)
where TDependency : class
where TInterceptor : IInterceptor
{
IInterceptor interceptor = container.GetInstance<TInterceptor>();
if (interceptor == null)
throw new NullReferenceException("interceptor");
TypeInterceptor typeInterceptor
= new GenericTypeInterceptor<TDependency>(interceptor);
container.Configure(c => c.RegisterInterceptor(typeInterceptor));
}
}
-
public class GenericTypeInterceptor<TDependency> : TypeInterceptor
where TDependency : class
{
private readonly IInterceptor _interceptor;
private readonly ProxyGenerator _proxyGenerator = new ProxyGenerator();
public GenericTypeInterceptor(IInterceptor interceptor)
{
if (interceptor == null)
throw new ArgumentNullException("interceptor");
_interceptor = interceptor;
}
public object Process(object target, IContext context)
{
return _proxyGenerator.CreateInterfaceProxyWithTarget(target as TDependency, _interceptor);
}
public bool MatchesType(Type type)
{
return typeof(TDependency).IsAssignableFrom(type);
}
}
I'm pretty happy with the result.
I can be completely wrong here, but doesn't this do the trick?
For<IPersonManager>()
.EnrichAllWith(t => proxyGenerator.CreateInterfaceProxyWithTarget(
t, _container.GetInstance<AuthenticationInterceptor>()))
.Use<PersonManager>();