Managing RavenDB IDocumentSession lifecycles with StructureMap for NServiceBus and MVC - asp.net-mvc-4

I am using NServiceBus v4.3, MVC4, RavenDB 2.5 and StructureMap 2.6.4 in our solution.
I am having a similar issue under StructureMap to that described in this question's responses where I require different lifecycles for the MVC Controller and NServiceBus Handler use of RavenDB's IDocumentSession in my Web project.
Specifically in my case what happens is that if I use the HybridHttpOrThreadLocalScoped (as the above answer suggests for Windsor) lifecycle the sessions are not properly disposed of and I soon hit the 30 transaction limit error. If I use the HttpContext lifecycle my NSB event Handlers in the Web project do not get called.
In my Controllers the session is wrapped in a unit of work applied via an MVC ActionFilter. I also use the UoW within the Handlers as my Registry is wired up to retrieve the session from the UoW. The code is as such:
RavenDbWebRegistry.cs
public sealed class RavenDbWebRegistry : Registry
{
public RavenDbWebRegistry()
{
// register RavenDB document store
ForSingletonOf<IDocumentStore>().Use(() =>
{
var documentStore = new DocumentStore
{
ConnectionStringName = "RavenDB",
Conventions =
{
IdentityPartsSeparator = "-",
JsonContractResolver = new PrivatePropertySetterResolver(),
},
};
documentStore.Initialize();
return documentStore;
});
For<IDocumentSession>().HybridHttpOrThreadLocalScoped().Add(ctx =>
{
var uow = (IRavenDbUnitOfWork)ctx.GetInstance<IUnitOfWork>();
return uow.DocumentSession;
});
For<IUnitOfWork>().HybridHttpOrThreadLocalScoped().Use<WebRavenDbUnitOfWork>();
}
}
Example of Web project Handler:
public class SiteCreatedEventHandler : IHandleMessages<ISiteCreatedEvent>
{
public IBus Bus { get; set; }
public IUnitOfWork Uow { get; set; }
public IDocumentSession DocumentSession { get; set; }
public void Handle(ISiteCreatedEvent message)
{
try
{
Debug.Print(#"{0}{1}", message, Environment.NewLine);
Uow.Begin();
var site = DocumentSession.Load<Site>(message.SiteId);
Uow.Commit();
//invoke Hub and push update to screen
var context = GlobalHost.ConnectionManager.GetHubContext<AlarmAndNotifyHub>();
//TODO make sure this SignalR function is correct
context.Clients.All.displayNewSite(site, message.CommandId);
context.Clients.All.refreshSiteList();
}
catch (Exception ex)
{
Uow.Rollback();
}
}
}
Usage of ActionFilter:
[RavenDbUnitOfWork]
public ViewResult CreateNew(int? id)
{
if (!id.HasValue || id.Value <= 0)
return View(new SiteViewModel { Guid = Guid.NewGuid() });
var targetSiteVm = MapSiteToSiteViewModel(SiteList(false)).FirstOrDefault(s => s.SiteId == id.Value);
return View(targetSiteVm);
}
WebRegistry (that sets up NSB in my MVC project)
public sealed class WebRegistry : Registry
{
public WebRegistry()
{
Scan(x =>
{
x.TheCallingAssembly();
x.Assembly("IS.CommonLibrary.ApplicationServices");
x.LookForRegistries();
});
IncludeRegistry<RavenDbWebRegistry>();
FillAllPropertiesOfType<IUnitOfWork>();
FillAllPropertiesOfType<IDocumentSession>();
FillAllPropertiesOfType<StatusConversionService>();
FillAllPropertiesOfType<IStateRepository<TieState>>();
FillAllPropertiesOfType<IStateRepository<DedState>>();
FillAllPropertiesOfType<ITieService>();
FillAllPropertiesOfType<IDedService>();
FillAllPropertiesOfType<IHbwdService>();
//NServiceBus
ForSingletonOf<IBus>().Use(
NServiceBus.Configure.With()
.StructureMapBuilder()
.DefiningCommandsAs(t => t.Namespace != null && t.Namespace.EndsWith("Command"))
.DefiningEventsAs(t => t.Namespace != null && t.Namespace.EndsWith("Event"))
.DefiningMessagesAs(t => t.Namespace == "Messages")
.RavenPersistence("RavenDB")
.UseTransport<ActiveMQ>()
.DefineEndpointName("IS.Argus.Web")
.PurgeOnStartup(true)
.UnicastBus()
.CreateBus()
.Start(() => NServiceBus.Configure.Instance
.ForInstallationOn<Windows>()
.Install())
);
//Web
For<HttpContextBase>().Use(() => HttpContext.Current == null ? null : new HttpContextWrapper(HttpContext.Current));
For<ModelBinderMappingDictionary>().Use(GetModelBinders());
For<IModelBinderProvider>().Use<StructureMapModelBinderProvider>();
For<IFilterProvider>().Use<StructureMapFilterProvider>();
For<StatusConversionService>().Use<StatusConversionService>();
For<ITieService>().Use<TieService>();
For<IDedService>().Use<DedService>();
For<IHbwdService>().Use<HbwdService>();
For<ISiteService>().Use<SiteService>();
IncludeRegistry<RedisRegistry>();
}
I have tried configuring my Registry using every possible combination I can think of to no avail.
Given that the StructureMap hybrid lifecycle does not work as I would expect, what must I do to achieve the correct behaviour?
Is the UoW necessary/beneficial with RavenDB? I like it (having adapted it from my earlier NHibernate UoW ActionFilter) because of the way it manages the lifecycle of my sessions within Controller Actions, but am open to other approaches.
What I would ideally like is a way to - within the Web project - assign entirely different IDocumentSessions to Controllers and Handlers, but have been unable to work out any way to do so.

Firstly, RavenDB already implements unit of work by the wrapping IDocumentSession, so no need for it. Opening a session, calling SaveChanges() and disposing has completed the unit of work
Secondly, Sessions can be implemented in a few ways for controllers.
The general guidance is to set up the store in the Global.asax.cs. Since there is only 1 framework that implements IDocumentSession - RavenDB, you might as well instantiate it from the Global. If it was NHibernate or Entity Framework behind a repository, I'd understand. But IDocumentSession is RavenDB specific, so go with a direct initialization in the Application_Start.
public class Global : HttpApplication
{
public void Application_Start(object sender, EventArgs e)
{
// Usual MVC stuff
// This is your Registry equivalent, so insert it into your Registry file
ObjectFactory.Initialize(x=>
{
x.For<IDocumentStore>()
.Singleton()
.Use(new DocumentStore { /* params here */ }.Initialize());
}
public void Application_End(object sender, EventArgs e)
{
var store = ObjectFactory.GetInstance<IDocumentStore>();
if(store!=null)
store.Dispose();
}
}
In the Controllers, add a base class and then it can open and close the sessions for you. Again IDocumentSession is specific to RavenDB, so dependency injection doesn't actually help you here.
public abstract class ControllerBase : Controller
{
protected IDocumentSession Session { get; private set; }
protected override void OnActionExecuting(ActionExecutingContext context)
{
Session = ObjectFactory.GetInstance<IDocumentStore>().OpenSession();
}
protected override void OnActionExecuted(ActionExecutedContext context)
{
if(this.IsChildAction)
return;
if(content.Exception != null && Session != null)
using(context)
Session.SaveChanges();
}
}
Then from there, inherit from the base controller and do your work from there:
public class CustomerController : ControllerBase
{
public ActionResult Get(string id)
{
var customer = Session.Load<Customer>(id);
return View(customer);
}
public ActionResult Edit(Customer c)
{
Session.Store(c);
return RedirectToAction("Get", c.Id);
}
}
Finally, I can see you're using StructureMap, so it only takes a few basic calls to get the Session from the DI framework:
public class SiteCreatedEventHandler : IHandleMessages<ISiteCreatedEvent>
{
public IBus Bus { get; set; }
public IUnitOfWork Uow { get; set; }
public IDocumentSession DocumentSession { get; set; }
public SiteCreatedEventHandler()
{
this.DocumentSession = ObjectFactory.GetInstance<IDocumentStore>().OpenSession();
}
public void Handle(ISiteCreatedEvent message)
{
using(DocumentSession)
{
try
{
Debug.Print(#"{0}{1}", message, Environment.NewLine);
///// Uow.Begin(); // Not needed for Load<T>
var site = DocumentSession.Load<Site>(message.SiteId);
//// Uow.Commit(); // Not needed for Load<T>
// invoke Hub and push update to screen
var context = GlobalHost.ConnectionManager.GetHubContext<AlarmAndNotifyHub>();
// TODO make sure this SignalR function is correct
context.Clients.All.displayNewSite(site, message.CommandId);
context.Clients.All.refreshSiteList();
}
catch (Exception ex)
{
//// Uow.Rollback(); // Not needed for Load<T>
}
}
}

Related

.NET Core 3.1 Complex Session Wrapper Not Working

I am trying to create a complex session wrapper in .NET Core 3.1. I ran into an issue where my variables are not being set. This is the way I set up the session wrapper class.
public class SessionWrapper : ISessionWrapper
{
private static IHttpContextAccessor context;
public SessionWrapper(IHttpContextAccessor _context)
{
context = _context;
}
public static Course Course
{
get
{
var key = context.HttpContext.Session.GetString("course");
if (key == null)
{
return default;
}
else
{
return JsonConvert.DeserializeObject<Course>(key);
}
}
set
{
if(value != null)
{
context.HttpContext.Session.SetString("course", JsonConvert.SerializeObject(value));
}
}
}
}
I configured my services to use session and the sessionwrapper.
services.AddDistributedMemoryCache();
services.AddSession();
services.AddHttpContextAccessor();
services.AddScoped<ISessionWrapper, SessionWrapper>();
I configured the pipeline to use session
app.UseSession();
In my controller, I am initializing course and set the session wrapper. Then, I am setting the course id to 4. It's not complaining, but the course id is not being set. It's always null. I've been looking at it for so and is getting frustrated. What am I missing here?
Course myCourse = new Course();
SessionWrapper.Course = myCourse;
SessionWrapper.Course.Id = "4"
I feel like your wrapper in itself isn't really the best approach to do this. A self-aware subclass of Course that has the 'know how' to store itself in Session, seems more logical to me. That way you are freeing your controller(s) from the responsibility for managing the persistence.
public abstract class Course
{
public abstract int Id { get; set; }
}
public class SessionCourse : Course
{
private int _id;
public override int Id
{
get => _id;
set { _id = value; UpdateSession(); }
}
// The GetCourse method is a factory for creating the SessionCourse objects
// and providing it with a ISession object so they can store themselves.
public static Course GetCourse(IServiceProvider services)
{
ISession session = services.GetRequiredService<IHttpContextAccessor>()?.HttpContext.Session;
SessionCourse course = session?.GetJson<SessionCourse>("Course") ?? new SessionCourse();
course.Session = session;
return course;
}
[JsonIgnore]
private ISession Session { get; set; }
private void UpdateSession() {
Session.SetJson("Course", this);
}
}
Now the trick is to satisfy requests for the Course object with the SessionCourse object that will store itself in session. You can do that by adding a scoped service with a lambda expression for the course object. The result is that requests for the Course service will return the SessionCourse object.
services.AddScoped<Course>(sp => SessionCourse.GetCourse(sp));
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
So the benefit of creating this kind of service is that it allows you to simplify the controllers where Course objects are used.
public class CourseController : Controller
{
private Course course;
public CartController(Course courseService)
{
course = courseService;
}
public void SetCourseId()
{
course.Id = "4";
}
SessionExtension.cs defines extension methods for adding objects to the session.
public static class SessionExtensions {
public static void SetJson(this ISession session, string key, object value) {
session.SetString(key, JsonConvert.SerializeObject(value));
}
public static T GetJson<T>(this ISession session, string key) {
var sessionData = session.GetString(key);
return sessionData == null ? default(T) : JsonConvert.DeserializeObject<T>(sessionData);
}
}

NServiceBus Dependency Injection not working for Saga Timeouts

I am having problems getting NServiceBus 4.6.1 dependency injection working with Saga timeouts. I am using self-hosting in an ASP.NET web application and have property injection setup. It works when messages are sent from web controllers however, when a Timeout message is handled in the saga the same DI property is not being set and is null.
Here are the key bits of the setup:
Global.asax.cs
public class MvcApplication : System.Web.HttpApplication
{
public static IWindsorContainer Container { get; private set; }
protected void Application_Start()
{
ConfigureIoC();
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
DeviceManagerDbInitializer.Instance.InitializeDatabase();
ConfigureNServiceBus();
}
protected void Application_End()
{
if (Container != null)
{
Container.Dispose();
}
}
private static void ConfigureIoC()
{
Container = new WindsorContainer()
.Install(FromAssembly.This());
var controllerFactory = new WindsorControllerFactory(Container.Kernel);
ControllerBuilder.Current.SetControllerFactory(controllerFactory);
GlobalConfiguration.Configuration.DependencyResolver
= new WindsorDependencyResolver(Container);
}
private void ConfigureNServiceBus()
{
Configure.ScaleOut(s => s.UseSingleBrokerQueue());
Configure.Instance.PeekInterval(500);
Configure.Instance.MaximumWaitTimeWhenIdle(2000);
Feature.Enable<TimeoutManager>();
Feature.Enable<Sagas>();
IStartableBus startableBus = Configure.With()
.DefineEndpointName("MyQueue")
.CastleWindsorBuilder(Container) //using NServiceBus CastleWindsor 4.6.1
.UseTransport<AzureStorageQueue>()
.UseAzureTimeoutPersister()
.AzureSagaPersister()
.PurgeOnStartup(false)
.UnicastBus()
.LoadMessageHandlers()
.RunHandlersUnderIncomingPrincipal(false)
.Log4Net(new DebugAppender { Threshold = Level.Warn })
.RijndaelEncryptionService()
.CreateBus();
Configure.Instance.ForInstallationOn<Windows>().Install();
startableBus.Start();
}
}
Saga class
public class MySaga: Saga<MySagaData>,
IAmStartedByMessages<StartMySagaCommand>,
IHandleMessages<SomeMessage>,
IHandleTimeouts<SomeTimeout>
{
public DependentService MyInjectedService {get; set;}
public override void ConfigureHowToFindSaga()
{
ConfigureMapping<StartMySagaCommand>( message => message.MyId).ToSaga( saga => saga.MyId );
ConfigureMapping<SomeMessage>( message => message.MyId).ToSaga( saga => saga.MyId );
ConfigureMapping<SomeTimeout>( message => message.MyId).ToSaga( saga => saga.MyId );
}
public void Handle(SomeMessage message)
{
// Here MyInjectedService is fine
MyInjectedService.DoSomething(message);
}
public void Timeout(SomeTimeout state)
{
// Here MyInjectedService is always null
MyInjectedService.DoSomething(state);
}
}
I have tried solutions found here, here and here but none of them fixed the issue.
I figured out the problem here. Dependency injection was not working in the Saga's timeout handler because the Castle.Windsor lifestyle was set to LifestylePerWebRequest, e.g.:
public class WindsorServicesInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register( Component.For<DependentService>()
.LifestylePerWebRequest() );
}
}
After changing the Lifestyle to LifeStyleTransient it started working. Any of the other 'non web request' lifestyles should work as well here.
In our setup the NServiceBus host is running under the web application and the regular message handlers were fine because they are being called in a controller action, e.g.:
[HttpPost]
public ActionResult DoSomething( int myId)
{
_bus.Send( "MyBus", new SomeMessage { MyId = something.MyId } );
return View();
}
When the saga handles the SomeMessage message for this it must still be part of the web request and Windsor resolves the dependency as normal. However, the timeouts are fired some time later (in this case five minutes) are they are not part of a web request. Windsor is not able to resolve the DependentService object and it stays null.

ServiceStack NHibernate and Ninject in Self Hosting App (Request Context)

I have a self hosted ServiceStack application, and I try to build ISession per request. I suppose the following will work:
Bind<ISession>()
.ToMethod(NapraviSesiju)
.InNamedScope(ControllerScope)
.InScope(s => ReuseScope.Request)
.OnActivation(s => s.BeginTransaction())
.OnDeactivation(s =>
{
if (!s.Transaction.IsActive) return;
try
{
s.Transaction.Commit();
}
catch (Exception e)
{
s.Transaction.Rollback();
}
});
private ISession NapraviSesiju(IContext kontekst)
{
var sesija = kontekst.Kernel.Get<ISessionFactory>().OpenSession();
return sesija;
}
This works, but request deactivation is not instant (it happens after 30 seconds, or 1 minute, and some requests don't deactivate at all).
Can someone please tell me the correct way to handle NHibernate Sessions this way?
UPDATE
Can I use this then:
public class AppHost : AppHostHttpListenerBase
{
private IKernel _jezgro;
public override void Configure(Container container)
{
_jezgro = new StandardKernel(new NHibernateModul());
container.Adapter = new NinjectIocAdapter(_jezgro);
}
public override void Release(object instance)
{
_jezgro.Release(((IHasSession)instance).Sesija); //Release Sesija from SomeServis object below
}
}
public class SomeServis : RestServiceBase<Some>, IHasSession //implements NHibernate Session
{
public ISession Sesija { get; set; } //IHasSession implementation. Injected by Ninject.
}
Bind<ISession>()
.ToMethod(NapraviSesiju)
.InScope(s => ReuseScope.Request) //reuse per request scope. Is this really needed, since release is happening at Release in AppHost?
.OnActivation(s => s.BeginTransaction())
.OnDeactivation(s =>
{
if (!s.Transaction.IsActive) return;
try
{
s.Transaction.Commit();
}
catch (Exception)
{
s.Transaction.Rollback();
}
});
The bottom of the IOC Container wiki page explains the Release behavior of IOC resources. The easiest way to handle disposed resources is to implement the IRelease method and delegate the Released instances back into Ninject, e.g:
public class NinjectIocAdapter : IContainerAdapter, IRelease
{
private readonly IKernel kernel;
//...
public void Release(object instance)
{
this.kernel.Release(instance);
}
}

Simplest way to mock properties of PostSharp attribute

I'm using a PostSharp method attribute to do authorisation and auditing on my WCF service. It's working properly but now I'm trying to get my unit tests working with the attribute and am struggling to find a way to mock and inject the properties on the attribute.
My attribute is as below.
[Serializable]
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public class AuthoriseAndAuditAttribute : OnMethodBoundaryAspect
{
private static ILog logger = AppState.logger;
private static Ninject.IKernel _kernel = MyKernel.Kernel;
private UserRoleTypesEnum _requiredRole = UserRoleTypesEnum.None;
[Inject]
public IServiceAuthToken _serviceAuthToken { get; set; }
[Inject]
public UserSessionDataLayer _userSessionDataLayer { get; set; }
public AuthoriseAndAuditAttribute(UserRoleTypesEnum role = UserRoleTypesEnum.None)
{
_requiredRole = role;
_kernel.Inject(this);
}
public override void OnEntry(MethodExecutionArgs args)
{
// Get the user's session from cookie.
UserSession userSession = GetUserSession();
// Check that user is in the required role.
bool isAuthorised = (_requiredRole == UserRoleTypesEnum.None || (userSession != null && userSession.Roles.Contains(_requiredRole)));
if (!isAuthorised)
{
logger.Warn("Not authorised for " + args.Method.Name + ".");
throw new UnauthorizedAccessException();
}
else if (userSession != null)
{
Thread.CurrentPrincipal = new MyPrincipal(userSession);
}
}
private UserSession GetUserSession()
{
if (_serviceAuthToken != null)
{
string sessionID = _serviceAuthToken.GetSessionID();
if (!sessionID.IsNullOrBlank())
{
return _userSessionDataLayer.GetForSessionID(sessionID);
}
}
return null;
}
}
I have a singleton class setting up the Ninject kernel:
public class MyKernel
{
public static StandardKernel Kernel { get; set; }
static MyKernel()
{
Kernel = new StandardKernel();
Kernel.Bind<IServiceAuthToken>().To<ServiceAuthToken>();
Kernel.Bind<UserSessionDataLayer>().To<UserSessionDataLayer>();
}
}
In my WCF service I use the PostSharp attribute as below:
[AuthoriseAndAudit(UserRoleTypesEnum.Operator)]
public JSONResult<bool> IsAliveAuthorised()
{
return new JSONResult<bool>() { Success = true, Result = true };
}
And in my unit test I'm using RhinoMocks to try and mock the two DI properties in the attribute.
[TestMethod]
public void IsAliveAuthorisedIsAuthorisedTest()
{
var mockServiceAuthToken = MockRepository.GenerateStrictMock<ServiceAuthToken>();
mockServiceAuthToken.Stub(x => x.GetSessionID()).Return("x");
var mockUserSessionDataLayer = MockRepository.GenerateStrictMock<UserSessionDataLayer>();
mockUserSessionDataLayer.Stub(x => x.GetForSessionID(Arg<string>.Is.Anything)).Return(new UserSession());
MyKernel.Kernel.Bind<ServiceAuthToken>().ToConstant(mockServiceAuthToken);
MyKernel.Kernel.Bind<UserSessionDataLayer>().ToConstant(mockUserSessionDataLayer);
var service = new MyService();
Assert.IsTrue(service.IsAliveAuthorised().Result);
}
The issue I have is the mock objects in the unit test are never ending up being set as the properties on the attribute. What am I doing wrong or conversely is there a better way to do unit testing on a PostSharp attribute? Also bearing in mind I really want to minimise the use of the Ninject DI to the bare minimum.
Instead of using the [Inject] attribute on your properties, redefine them like this:
public IServiceAuthToken _serviceAuthToken { get { return _kernel.Get<IServiceAuthToken>(); } }
public UserSessionDataLayer _userSessionDataLayer { get { return _kernel.Get<UserSessionDataLayer>(); } }
Also, in your test method you need to re-bind (note also that you were using the concrete type ServiceAuthToken in the first bind instead of the interface IServiceAuthToken):
MyKernel.Kernel.Rebind<IServiceAuthToken>().ToConstant(mockServiceAuthToken);
MyKernel.Kernel.Rebind<UserSessionDataLayer>().ToConstant(mockUserSessionDataLayer);

NHibernate: How to inject dependency on an entity

NHibernate 3.2/Fluent NHibernate 1.3/StructureMap 2.6.3 -
Trying to follow DDD as an architectural strategy, I typically don't have dependencies on domain entities. However, I'm experimenting right now with adding more behavior to my domain entities so that they are not so anemic. Everything was going well until I hooked up NHibernate. I've got two issues:
NH requires a parameterless constructor and I'd rather not have a
ctor that shouldn't be used.
When NH tries to instantiate my entity, it needs to resolve my
dependencies but I haven't given NH anything with which it can do
that.
I've been reading on the web, but most (if not all) of the examples I have found are outdated (or just old). Even though the NH camp probably doesn't approve of what I'm doing, I'm looking for the NH way to do this.
The solution ended up an implementation of NHibernate's IInterceptor. It is actually a very simple implementation when you inherit from EmptyInterceptor and override JUST the Instantiate() and SetSession() methods. Here's my interceptor using StructureMap:
public class DependencyInjectionEntityInterceptor : EmptyInterceptor
{
IContainer _container;
ISession _session;
public DependencyInjectionEntityInterceptor(IContainer container)
{
_container = container;
}
public override void SetSession(ISession session)
{
_session = session;
}
public override object Instantiate(string clazz, EntityMode entityMode, object id)
{
if (entityMode == EntityMode.Poco)
{
var type = Assembly.GetAssembly(typeof (SomeClass)).GetTypes().FirstOrDefault(x => x.FullName == clazz);
var hasParameters = type.GetConstructors().Any(x => x.GetParameters().Any());
if (type != null && hasParameters)
{
var instance = _container.GetInstance(type);
var md = _session.SessionFactory.GetClassMetadata(clazz);
md.SetIdentifier(instance, id, entityMode);
return instance;
}
}
return base.Instantiate(clazz, entityMode, id);
}
}
Then, all you have to do is tell NHibernate to use your interceptor:
public FluentConfiguration GetFluentConfiguration(IContainer container)
{
return Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2008
.ConnectionString(c => c.FromConnectionStringWithKey("Database"))
.ShowSql())
.Mappings(m =>
m.AutoMappings.Add(AutoMap.AssemblyOf<SomeClass>()))
.ExposeConfiguration(x =>
x.SetInterceptor(new DependencyInjectionEntityInterceptor(container)));
}
When I was researching this, some suggested passing in the SessionFactory into the ctor of the interceptor class. Honestly, from a session management perspective, this approach would be better.
If you need additional dependencies in your entities don't use constructor injection. Instead create an additional parameter in the entity method.
Now you will ask yourself how do you get the dependency. For this you can use CommandHandlers and Commands. The command handler takes the dependency within its constructor and calls the method of the entity. In the UI you create a command message and send it to a command processor which is responsible for calling the correct command handler.
I hope my explanation is comprehensible to you.
Domain:
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public void SendNotification(string message, INotifier notifier)
{
notifier.SendMessage(string.Format("Message for customer '{0}' ({1}): {2}", Name, Id, message));
}
}
The INotifier infrastructure component is passed through the method and not the constructor!
Infrastructure:
public interface INotifier
{
void SendMessage(string message);
}
class EmailNotifier : INotifier
{
public void SendMessage(string message)
{
// SmtpClient...
}
}
class SMSNotifier : INotifier
{
public void SendMessage(string message)
{
// SMS ...
}
}
Command and CommandHandler:
public class NotificationCommandHandler : ICommandHandler<NotificationCommand>
{
private readonly INotifier _notifier;
public NotificationCommandHandler(INotifier notifier)
{
_notifier = notifier;
}
public void Execute(NotificationCommand commandMessage)
{
commandMessage.Employee.SendNotification(commandMessage.Message, _notifier);
}
}
public class NotificationCommand
{
public string Message { get; set; }
public Employee Employee { get; set; }
}
The CommandHandler gets the INotifier through constructor injection. So you do not need to use your IoC Container like a ServiceLocator.
Usage i.e. in the UI in a controller:
public class Controller
{
private readonly IMessageProcessor _messageProcessor;
public Controller(IMessageProcessor messageProcessor)
{
_messageProcessor = messageProcessor;
}
public void SendNotification (Employee employee, string message)
{
var sendMailCommand = new NotificationCommand
{
Employee = employee,
Message = message
};
_messageProcessor.Process(sendMailCommand);
}
}
If you have questions about the command processor have a look at the mvccontrib project or ask a separate question.
Sorry my previous answer didn't address the specific question. I did some more research, and it looks like I have much more to learn about when and when not to use an anemic domain model. Regarding your question, I found this article to be very on topic. It is on java, not c#, but the principles are the same. Hope this helps.