Skip to content

ASP.NET

xhafan edited this page Dec 17, 2018 · 40 revisions

Follow these steps to use CoreDdd in ASP.NET MVC, Web API or Web Forms application:

  1. Create a new ASP.NET MVC, Web API or Web Forms project.
  2. Install following nuget packages into the project:
  1. Pick you favourite IoC container to register CoreDdd services. CoreDdd supports Castle Windsor via following nuget packages:

And Ninject via following nuget packages:

For other IoC containers, manual registration of CoreDdd services into an IoC container is needed.

  1. Add a new empty NHibernate configurator class, example:
public class CoreDddSampleNhibernateConfigurator : BaseNhibernateConfigurator
{
    protected override Assembly[] GetAssembliesToMap()
    {
        return new Assembly[0];
    }
}
  1. Add a new NHibernate config file hibernate.cfg.xml . Here is SQLite example:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
  <session-factory>

    <property name="connection.connection_string">Data Source=CoreDddSampleAspNetWebApp.db</property>
    <property name="dialect">NHibernate.Dialect.SQLiteDialect</property>
    <property name="connection.driver_class">NHibernate.Driver.SQLite20Driver</property>
    <property name="connection.release_mode">on_close</property>

    <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
    <property name="show_sql">false</property>
    <property name="proxyfactory.factory_class">NHibernate.Bytecode.DefaultProxyFactoryFactory, NHibernate</property>
    <property name="transaction.use_connection_on_system_prepare">false</property> <!-- set transaction.use_connection_on_system_prepare to 'false' when using .NET 4.6.1+ or .NET Standard 2.0+, NHibernate 5.1.0+, more info here https://github.com/npgsql/npgsql/issues/1985#issuecomment-397266128 ; remove this line for previous NHibernate versions -->

  </session-factory>
</hibernate-configuration>

Set hibernate.cfg.xml Build Action to Content, and Copy to Output Directory to Copy if newer.

  1. Install System.Data.SQLite.Core nuget package for SQLite database. For different databases, please look at Entity database persistence.

  2. Add the following code into Application_Start() method in Global.asax.cs to register CoreDdd services:

Castle Windsor IoC container:

var iocContainer = new WindsorContainer();

CoreDddNhibernateInstaller.SetUnitOfWorkLifeStyle(x => x.PerWebRequest);

iocContainer.Install(
    FromAssembly.Containing<CoreDddInstaller>(),
    FromAssembly.Containing<CoreDddNhibernateInstaller>()
);
iocContainer.Register(
    Component
        .For<INhibernateConfigurator>()
        .ImplementedBy<CoreDddSampleNhibernateConfigurator>()
        .LifestyleSingleton()
);

For ASP.NET MVC, add MVC controllers registration:

// register MVC controllers
iocContainer.Register(
    Classes
        .FromAssemblyContaining<HomeController>()
        .BasedOn<ControllerBase>()
        .Configure(x => x.LifestyleTransient())
);

For ASP.NET Web API, add Web API controllers registration:

// register Web API controllers
iocContainer.Register(
    Classes
        .FromAssemblyContaining<ValuesController>()
        .BasedOn<ApiController>()
        .Configure(x => x.LifestyleTransient())
);

Ninject IoC container (also needs nuget packages Ninject.MVC5 for MVC and Web Forms, and Ninject.Web.WebApi.WebHost for Web API):

var iocContainer = new StandardKernel();

CoreDddNhibernateBindings.SetUnitOfWorkLifeStyle(x => x.InRequestScope());

iocContainer.Load(
    typeof(CoreDddBindings).Assembly,
    typeof(CoreDddNhibernateBindings).Assembly
);
iocContainer
    .Bind<INhibernateConfigurator>()
    .To<CoreDddSampleNhibernateConfigurator>()
    .InSingletonScope();

For ASP.NET MVC, add MVC controllers registration (also needs Ninject.Extensions.Conventions nuget package installed):

// register MVC controllers
iocContainer.Bind(x => x
    .FromAssemblyContaining<HomeController>()
    .SelectAllClasses()
    .InheritedFrom<ControllerBase>()
    .BindAllInterfaces()
    .Configure(y => y.InTransientScope()));

For ASP.NET Web API, add Web API controllers registration (also needs Ninject.Extensions.Conventions nuget package installed):

// register Web API controllers
iocContainer.Bind(x => x
    .FromAssemblyContaining<ValuesController>()
    .SelectAllClasses()
    .InheritedFrom<ApiController>()
    .BindAllInterfaces()
    .Configure(y => y.InTransientScope()));

For other IoC containers, check out the CoreDdd services and CoreDdd.Nhibernate services registrations for an inspiration how various CoreDdd services should be registered into an IoC container.

  1. To handle each web request within ADO.NET transaction:

Add UnitOfWorkHttpModule into web.config:

<system.webServer>
  <modules>
    <add name="UnitOfWork" type="CoreDdd.AspNet.HttpModules.UnitOfWorkHttpModule, CoreDdd.AspNet" preCondition="managedHandler" />
  </modules>
</system.webServer>

and add the following code to Application_Start() method in Global.asax.cs to initialize UnitOfWorkHttpModule:

Castle Windsor IoC container example:

UnitOfWorkHttpModule.Initialize(
    iocContainer.Resolve<IUnitOfWorkFactory>(),
    isolationLevel: System.Data.IsolationLevel.ReadCommitted
);

DomainEvents.Initialize(
    iocContainer.Resolve<IDomainEventHandlerFactory>(),
    isDelayedDomainEventHandlingEnabled: true
);

Ninject IoC container example:

UnitOfWorkHttpModule.Initialize(
    iocContainer.Get<IUnitOfWorkFactory>(),
    isolationLevel: System.Data.IsolationLevel.ReadCommitted
);

DomainEvents.Initialize(
    iocContainer.Get<IDomainEventHandlerFactory>(),
    isDelayedDomainEventHandlingEnabled: true
);
  1. Only for .NET 4.5.1 and higher: To handle each web request within a transaction scope with ADO.NET connection enlisted in the TransactionScope, add TransactionScopeUnitOfWorkHttpModule into web.config:
<system.webServer>
  <modules>
     <add name="TransactionScopeUnitOfWork" type="CoreDdd.AspNet.HttpModules.TransactionScopeUnitOfWorkHttpModule, CoreDdd.AspNet" preCondition="managedHandler" />
  </modules>
</system.webServer>

and add the following code to Application_Start() method in Global.asax.cs to initialize TransactionScopeUnitOfWorkHttpModule:

Castle Windsor IoC container example:

Action<TransactionScope> transactionScopeEnlistmentAction = transactionScope =>
{
    // enlist custom resource manager into the transaction scope
};
TransactionScopeUnitOfWorkHttpModule.Initialize(
    iocContainer.Resolve<IUnitOfWorkFactory>(),
    transactionScopeEnlistmentAction: transactionScopeEnlistmentAction,
    isolationLevel: System.Transactions.IsolationLevel.ReadCommitted
);

DomainEvents.Initialize(iocContainer.Resolve<IDomainEventHandlerFactory>());

Ninject IoC container example:

Action<TransactionScope> transactionScopeEnlistmentAction = transactionScope =>
{
    // enlist custom resource manager into the transaction scope
};
TransactionScopeUnitOfWorkHttpModule.Initialize(
    iocContainer.Get<IUnitOfWorkFactory>(),
    transactionScopeEnlistmentAction: transactionScopeEnlistmentAction,
    isolationLevel: System.Transactions.IsolationLevel.ReadCommitted
);

DomainEvents.Initialize(iocContainer.Get<IDomainEventHandlerFactory>());
  1. For ASP.NET MVC and Castle Windsor, add a controller factory into the project:
public class IoCContainerCastleWindsorControllerFactory : DefaultControllerFactory
{
    private readonly IWindsorContainer _iocContainer;

    public IoCContainerCastleWindsorControllerFactory(IWindsorContainer iocContainer)
    {
        _iocContainer = iocContainer;
    }

    protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
    {
        if (controllerType == null)
        {
            return base.GetControllerInstance(requestContext, null);
        }

        var controller = (IController)_iocContainer.Resolve(controllerType);
        return controller;
    }

    public override void ReleaseController(IController controller)
    {
        _iocContainer.Release(controller);
        base.ReleaseController(controller);
    }
}

And set the controller factory in Application_Start() method in Global.asax.cs:

ControllerBuilder.Current.SetControllerFactory(new IoCContainerCastleWindsorControllerFactory(iocContainer));
  1. For ASP.NET Web API and Castle Windsor, add a dependency resolver into the project:
public class IoCContainerCastleWindsorDependencyResolver : IDependencyResolver
{
    private readonly IWindsorContainer _container;

    public IoCContainerCastleWindsorDependencyResolver(IWindsorContainer container)
    {
        _container = container;
    }

    public object GetService(Type t)
    {
        return _container.Kernel.HasComponent(t)
            ? _container.Resolve(t) : null;
    }

    public IEnumerable<object> GetServices(Type t)
    {
        return _container.ResolveAll(t).Cast<object>().ToArray();
    }

    public IDependencyScope BeginScope()
    {
        return new WindsorDependencyScope(_container);
    }

    public void Dispose()
    {
    }

    private class WindsorDependencyScope : IDependencyScope
    {
        private readonly IWindsorContainer _container;
        private readonly IDisposable _scope;

        public WindsorDependencyScope(IWindsorContainer container)
        {
            _container = container;
            _scope = container.BeginScope();
        }

        public object GetService(Type t)
        {
            return _container.Kernel.HasComponent(t) ? _container.Resolve(t) : null;
        }

        public IEnumerable<object> GetServices(Type t)
        {
            return _container.ResolveAll(t).Cast<object>().ToArray();
        }

        public void Dispose()
        {
            _scope.Dispose();
        }
    }
}

And set the dependency resolver in Application_Start() method in Global.asax.cs:

GlobalConfiguration.Configuration.DependencyResolver = new IoCContainerCastleWindsorDependencyResolver(iocContainer);
  1. For ASP.NET Web Forms, constructor dependency injection into pages (classes derived from Page) is not supported, but a workaround in a form of Service Locator pattern is available. Add CoreIoC, CoreIoC.Castle (for Castle Windsor) or CoreIoC.Ninject (for Ninject) nuget packages into the project. Add the following code into Application_Start() method in Global.asax.cs:

Castle Windsor example:

IoC.Initialize(new CastleContainer(iocContainer));

Ninject example:

IoC.Initialize(new NinjectContainer(iocContainer));

Every page has to manually resolve and release its dependencies, example:

public partial class ShipManagement : Page
{
    private ICommandExecutor _commandExecutor;
    private IQueryExecutor _queryExecutor;

    protected void Page_Load(object sender, EventArgs e)
    {
        _commandExecutor = IoC.Resolve<ICommandExecutor>();
        _queryExecutor = IoC.Resolve<IQueryExecutor>();
    }

    protected void Page_Unload(object sender, EventArgs e)
    {
        IoC.Release(_commandExecutor);
        IoC.Release(_queryExecutor);
    }

    protected void UpdateShipButton_OnClick(object sender, EventArgs e)
    {
        _commandExecutor.Execute(new UpdateShipDataCommand
        {
            ShipId = int.Parse(UpdateShipIdTextBox.Text),
            ShipName = UpdateShipNameTextBox.Text,
            Tonnage = int.Parse(UpdateTonnageTextBox.Text)
        });
    }
    ...
}

The code above is available as a sample ASP.NET MVC application, ASP.NET Web API application and ASP.NET Web Forms application.

The sample ASP.NET MVC and Web API application also contains ShipController (MVC, Web API) with methods to create a new Ship aggregate root domain entity, query ships by name and update ship data.

The sample ASP.NET Web Forms application also contains ShipManagement page with methods to create a new Ship aggregate root domain entity, query ships by name and update ship data.

Once command handlers, query handlers and domain event handlers are added to the application, they need to be registered into the IoC container:

Castle Windsor IoC container example:

// register command handlers
iocContainer.Register(
    Classes
        .FromAssemblyContaining<CreateNewShipCommandHandler>()
        .BasedOn(typeof(ICommandHandler<>))
        .WithService.FirstInterface()
        .Configure(x => x.LifestyleTransient())
);
// register query handlers
iocContainer.Register(
    Classes
        .FromAssemblyContaining<GetShipsByNameQueryHandler>()
        .BasedOn(typeof(IQueryHandler<>))
        .WithService.FirstInterface()
        .Configure(x => x.LifestyleTransient())
);
// register domain event handlers
iocContainer.Register(
    Classes 
        .FromAssemblyContaining<ShipUpdatedDomainEventHandler>()
        .BasedOn(typeof(IDomainEventHandler<>))
        .WithService.FirstInterface()
        .Configure(x => x.LifestyleTransient())
);

Ninject IoC container example (needs Ninject.Extensions.Conventions nuget package installed):

// register command handlers
iocContainer.Bind(x => x
    .FromAssemblyContaining<CreateNewShipCommandHandler>()
    .SelectAllClasses()
    .InheritedFrom(typeof(ICommandHandler<>))
    .BindAllInterfaces()
    .Configure(y => y.InTransientScope()));
// register query handlers
iocContainer.Bind(x => x
    .FromAssemblyContaining<GetShipsByNameQueryHandler>()
    .SelectAllClasses()
    .InheritedFrom(typeof(IQueryHandler<>))
    .BindAllInterfaces()
    .Configure(y => y.InTransientScope()));
// register domain event handlers
iocContainer.Bind(x => x
    .FromAssemblyContaining<ShipUpdatedDomainEventHandler>()
    .SelectAllClasses()
    .InheritedFrom(typeof(IDomainEventHandler<>))
    .BindAllInterfaces()
    .Configure(y => y.InTransientScope()));

You can check out other wiki pages how to persist an entity, query data, modify an application state or create more complex DDD implementation.

You can’t perform that action at this time.