65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 Castle Windsor 和 NHibernate 进行依赖注入和工作单元

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.94/5 (72投票s)

2013 年 2 月 10 日

CPOL

11分钟阅读

viewsIcon

296770

downloadIcon

5254

使用 Castle Windsor 和 NHibernate 实现依赖注入、存储库和工作单元模式。

目录

引言

在本文中,我将展示如何使用 Castle Windsor 作为 DI (依赖注入) 容器和 NHibernate 作为 ORM (对象关系映射) 工具来实现 依赖注入存储库工作单元 模式。

  • 依赖注入:依赖注入是一种软件设计模式,它允许移除硬编码的依赖项,并可以在运行时或编译时更改它们 [1]
  • 存储库:使用类似集合的接口来访问领域对象,从而在领域和数据映射层之间进行协调。 [2]
  • 工作单元:用于定义和管理应用程序中的事务性作业。 [4]

您可以在网上找到许多关于依赖注入和工作单元的资源、文章和教程。因此,我在这里将不对此进行定义。本文将重点介绍下一节中描述的问题的解决方案。

问题

在开发数据驱动应用程序时,您会考虑一些设计原则。在本节中,我将描述这些原则并简要解释解决方案。

如何打开/关闭连接

第一个问题是:如何以及在哪里打开和关闭连接。当然,最好在数据库层(可能在存储库中)管理连接。因此,我们可以在每个存储库方法调用中打开连接、运行数据库命令并关闭连接。但这可能效率低下且无用,如果我们想在同一存储库的不同方法或不同存储库的不同方法之间共享同一个连接(考虑一个使用不同存储库的某些方法的事务)。

如果我们正在构建一个网站(ASP.NET MVC 或 Web Forms),我们可以打开 Application_BeginRequest 期间的连接,并在 Application_EndRequest 期间关闭它。但是这种方法有一些缺点

  • 即使某些请求不使用数据库,我们也会为每个请求打开/关闭数据库。因此,连接池中的连接会在一段时间内被占用,即使它没有被使用。
  • 我们在请求开始时打开数据库,并在结束时关闭它。但是,有时请求非常长,而数据库操作在该请求中只占很短的时间。同样,连接池的使用效率低下。

上述问题可能对您来说不是大问题(实际上,对我来说,它们是问题)。但是,如果您不是在开发网站,而是开发一个运行许多线程的 Windows 服务,而这些线程在某些时间段使用数据库?那么,在哪里打开/关闭连接。

如何管理事务

如果您的数据库操作中使用事务(大多数应用程序都会这样做),那么在哪里开始、提交或回滚事务?您不能在存储库方法中这样做,因为您的事务可能包含许多不同的存储库方法调用。因此,您的业务层(调用存储库方法)可以开始/提交/回滚事务。这种方法存在一些问题

  • 您的业务层包含数据库特定的代码。这破坏了单一职责和分层。
  • 这种方法会在每个业务方法中重复事务逻辑。

正如我在上一节中所述,您可以使用 Application_BeginRequest 和 Application_EndRequest 来管理事务。同样的问题又出现了:您可能会开始不必要的事务。此外,您必须处理错误并在需要时回滚事务。

另外,如果您不在开发网站,那么很难找到一个好的地方来开始/提交/回滚事务。

因此,最佳方法是在您真正需要开始事务时开始它,当且仅当所有操作都成功时提交事务,并在任何操作失败时回滚事务。在本文中,我实现了正是这种方法。

实现

我使用 ASP.NET MVC(作为 Web 框架)、SQL Server(作为 DBMS)、NHibernate(作为 ORM)和 Castle Windsor(作为依赖注入容器)实现了一个简单的电话簿应用程序。

实体

在我们的实现中,实体映射到数据库表中的一条记录。在领域驱动设计 (DDD) 中,实体是具有唯一标识符的持久化对象 [3]。我们解决方案中的所有实体都派生自下面定义的 Entity

public interface IEntity<TPrimaryKey>
{
    TPrimaryKey Id { get; set; }
}

public class Entity<TPrimaryKey> : IEntity<TPrimaryKey>
{
    public virtual TPrimaryKey Id { get; set; }
}

实体有一个唯一的标识符(主键),其类型可以不同(int、long、guid 等),因此它是一个泛型类。我们的实体(People、Phone、City 等)都派生自此类。例如,People 类定义如下

public class Person : Entity<int>
{
    public virtual int CityId { get; set; }

    public virtual string Name { get; set; }

    public virtual DateTime BirthDay { get; set; }

    public virtual string Notes { get; set; }

    public virtual DateTime RecordDate { get; set; }

    public Person()
    {
        Notes = "";
        RecordDate = DateTime.Now;
    }
}

如您所见,Person 的主键定义为 int

实体映射

ORM 工具(如 Entity Framework 和 NHibernate)需要定义实体到数据库表的映射。实现这一点有很多方法。我使用了 NHibernate Fluent API 来完成这项工作。因此,我们为所有实体定义一个映射类,如下所示为 People 实体

public class PersonMap : ClassMap<Person>
{
    public PersonMap()
    {
        Table("People");
        Id(x => x.Id).Column("PersonId");
        Map(x => x.CityId);
        Map(x => x.Name);
        Map(x => x.BirthDay);
        Map(x => x.Notes);
        Map(x => x.RecordDate);
    }
}

存储库 (数据库层)

存储库 [2] 用于创建数据库层,将数据访问逻辑从上层抽象出来。通常为每个实体(或聚合:一组实体)创建一个存储库类。我为每个实体创建了一个存储库。首先,我定义了一个所有存储库类都必须实现的接口

/// <summary>
/// This interface must be implemented by all repositories to ensure UnitOfWork to work.
/// </summary>
public interface IRepository
{

}

/// <summary>
/// This interface is implemented by all repositories to ensure implementation of fixed methods.
/// </summary>
/// <typeparam name="TEntity">Main Entity type this repository works on</typeparam>
/// <typeparam name="TPrimaryKey">Primary key type of the entity</typeparam>
public interface IRepository<TEntity, TPrimaryKey> : IRepository where TEntity : Entity<TPrimaryKey>
{
    /// <summary>
    /// Used to get a IQueryable that is used to retrive entities from entire table.
    /// </summary>
    /// <returns>IQueryable to be used to select entities from database</returns>
    IQueryable<TEntity> GetAll();

    /// <summary>
    /// Gets an entity.
    /// </summary>
    /// <param name="key">Primary key of the entity to get</param>
    /// <returns>Entity</returns>
    TEntity Get(TPrimaryKey key);

    /// <summary>
    /// Inserts a new entity.
    /// </summary>
    /// <param name="entity">Entity</param>
    void Insert(TEntity entity);

    /// <summary>
    /// Updates an existing entity.
    /// </summary>
    /// <param name="entity">Entity</param>
    void Update(TEntity entity);

    /// <summary>
    /// Deletes an entity.
    /// </summary>
    /// <param name="id">Id of the entity</param>
    void Delete(TPrimaryKey id);
}

因此,所有存储库类都必须实现上述方法。但在 NHibernate 中,这些方法的实现几乎相同。因此,我们可以为所有存储库定义一个基类。这样,我们就不会为所有存储库实现相同的逻辑。请参见下面定义的 NhRepositoryBase

/// <summary>
/// Base class for all repositories those uses NHibernate.
/// </summary>
/// <typeparam name="TEntity">Entity type</typeparam>
/// <typeparam name="TPrimaryKey">Primary key type of the entity</typeparam>
public abstract class NhRepositoryBase<TEntity, TPrimaryKey> : IRepository<TEntity, TPrimaryKey> where TEntity : Entity<TPrimaryKey>
{
    /// <summary>
    /// Gets the NHibernate session object to perform database operations.
    /// </summary>
    protected ISession Session { get { return NhUnitOfWork.Current.Session; } }

    /// <summary>
    /// Used to get a IQueryable that is used to retrive object from entire table.
    /// </summary>
    /// <returns>IQueryable to be used to select entities from database</returns>
    public IQueryable<TEntity> GetAll()
    {
        return Session.Query<TEntity>();
    }

    /// <summary>
    /// Gets an entity.
    /// </summary>
    /// <param name="key">Primary key of the entity to get</param>
    /// <returns>Entity</returns>
    public TEntity Get(TPrimaryKey key)
    {
        return Session.Get<TEntity>(key);
    }

    /// <summary>
    /// Inserts a new entity.
    /// </summary>
    /// <param name="entity">Entity</param>
    public void Insert(TEntity entity)
    {
        Session.Save(entity);
    }

    /// <summary>
    /// Updates an existing entity.
    /// </summary>
    /// <param name="entity">Entity</param>
    public void Update(TEntity entity)
    {
        Session.Update(entity);
    }

    /// <summary>
    /// Deletes an entity.
    /// </summary>
    /// <param name="id">Id of the entity</param>
    public void Delete(TPrimaryKey id)
    {
        Session.Delete(Session.Load<TEntity>(id));
    }
}

Session 属性用于从 NhUnitOfWork.Current.Session 获取 Session 对象(NHibernate 中的数据库连接对象)。这会获取当前正在运行的事务的正确 Session 对象,因此存储库无需考虑如何打开/关闭连接和事务。此机制将在后续部分进行解释。

正如您所见,所有CRUD 操作都已默认实现。因此,现在我们可以通过仅声明下面所示的类型来创建 PersonRepository,它可以选择、删除、更新和创建记录

public interface IPersonRepository : IRepository<Person, int>
{
    
}

public class NhPersonRepository : NhRepositoryBase<Person, int>, IPersonRepository
{

}

我们可以对 Phone 和 City 实体做同样的事情。如果我们想添加一个自定义存储库方法,我们可以将其添加到相关实体的存储库中。例如,我们可以向 PhoneRepository 添加一个新方法来删除给定人员的所有电话

public interface IPhoneRepository : IRepository<Phone, int>
{
    /// <summary>
    /// Deletes all phone numbers for given person id.
    /// </summary>
    /// <param name="personId">Id of the person</param>
    void DeletePhonesOfPerson(int personId);
}

public class NhPhoneRepository : NhRepositoryBase<Phone, int>, IPhoneRepository
{
    public void DeletePhonesOfPerson(int personId)
    {
        var phones = GetAll().Where(phone => phone.PersonId == personId).ToList();
        foreach (var phone in phones)
        {
            Session.Delete(phone);
        }
    }
}

工作单元

工作单元用于定义和管理应用程序中的事务性作业。我们定义 IUnitOfWork 接口来定义这些作业

/// <summary>
/// Represents a transactional job.
/// </summary>
public interface IUnitOfWork
{
    /// <summary>
    /// Opens database connection and begins transaction.
    /// </summary>
    void BeginTransaction();

    /// <summary>
    /// Commits transaction and closes database connection.
    /// </summary>
    void Commit();

    /// <summary>
    /// Rollbacks transaction and closes database connection.
    /// </summary>
    void Rollback();
}

我们如下所示实现 NHibernate 的 IUnitOfWork

/// <summary>
/// Implements Unit of work for NHibernate.
/// </summary>
public class NhUnitOfWork : IUnitOfWork
{
    /// <summary>
    /// Gets current instance of the NhUnitOfWork.
    /// It gets the right instance that is related to current thread.
    /// </summary>
    public static NhUnitOfWork Current
    {
        get { return _current; }
        set { _current = value; }
    }
    [ThreadStatic]
    private static NhUnitOfWork _current;

    /// <summary>
    /// Gets Nhibernate session object to perform queries.
    /// </summary>
    public ISession Session { get; private set; }

    /// <summary>
    /// Reference to the session factory.
    /// </summary>
    private readonly ISessionFactory _sessionFactory;

    /// <summary>
    /// Reference to the currently running transcation.
    /// </summary>
    private ITransaction _transaction;

    /// <summary>
    /// Creates a new instance of NhUnitOfWork.
    /// </summary>
    /// <param name="sessionFactory"></param>
    public NhUnitOfWork(ISessionFactory sessionFactory)
    {
        _sessionFactory = sessionFactory;
    }

    /// <summary>
    /// Opens database connection and begins transaction.
    /// </summary>
    public void BeginTransaction()
    {
        Session = _sessionFactory.OpenSession();
        _transaction = Session.BeginTransaction();
    }

    /// <summary>
    /// Commits transaction and closes database connection.
    /// </summary>
    public void Commit()
    {
        try
        {
            _transaction.Commit();
        }
        finally
        {
            Session.Close();                
        }
    }

    /// <summary>
    /// Rollbacks transaction and closes database connection.
    /// </summary>
    public void Rollback()
    {
        try
        {
            _transaction.Rollback();
        }
        finally
        {
            Session.Close();                
        }
    }
}

静态 Current 属性是该类的关键点。正如您所见,它获取/设置标记为 ThreadStatic [5] 的 _current 字段。因此,我们可以在同一个线程中使用同一个工作单元对象。这样,我们就可以在对象之间共享同一个连接/事务。最后,我们定义一个属性,用于标记一个必须是事务性的方法

/// <summary>
/// This attribute is used to indicate that declaring method is transactional (atomic).
/// A method that has this attribute is intercepted, a transaction starts before call the method.
/// At the end of method call, transaction is commited if there is no exception, othervise it's rolled back.
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public class UnitOfWorkAttribute : Attribute
{

}

如果一个方法必须是事务性的,我们只需用 UnitOfWork 属性标记它。然后,我们将使用依赖注入来拦截这些方法,如下文所述。

服务层

在领域驱动设计中,领域服务用于实现业务逻辑。它可以利用存储库来执行数据库任务。例如,PersonService 定义如下

public class PersonService : IPersonService
{
    private readonly IPersonRepository _personRepository;
    private readonly IPhoneRepository _phoneRepository;

    public PersonService(IPersonRepository personRepository, IPhoneRepository phoneRepository)
    {
        _personRepository = personRepository;
        _phoneRepository = phoneRepository;
    }
    
    public void CreatePerson(Person person)
    {
        _personRepository.Insert(person);
    }

    [UnitOfWork]
    public void DeletePerson(int personId)
    {
        _personRepository.Delete(personId);
        _phoneRepository.DeletePhonesOfPerson(personId);
    }

    //... some other methods are not shown here since it's not needed. See source codes.
}

关注上一节中定义的 UnitOfWork 属性的用法。DeletePerson 方法标记为 UnitOfWork。因为它调用了两个不同的存储库方法,而这些方法调用必须是事务性的。另一方面,CreatePerson 不是 UnitOfWork,因为它只调用一个存储库方法(Person 存储库的 Insert),并且该存储库方法可以打开/关闭(管理)其自身的事务。我们将在下一节中看到如何实现它。

依赖注入

依赖注入 (DI) 容器(如我们使用的 Castle Windsor)用于管理应用程序中的依赖项和对象的生命周期。因此,它有助于构建松散耦合的组件/模块。它通常在应用程序启动时初始化。在 ASP.NET 应用程序中,global.asax 是初始化它的最常用位置

public class MvcApplication : System.Web.HttpApplication
{
    private WindsorContainer _windsorContainer;

    protected void Application_Start()
    {
        InitializeWindsor();
        // Other startup logic...
    }

    protected void Application_End()
    {
        if (_windsorContainer != null)
        {
            _windsorContainer.Dispose();
        }
    }

    private void InitializeWindsor()
    {
        _windsorContainer = new WindsorContainer();
        _windsorContainer.Install(FromAssembly.This());

        ControllerBuilder.Current.SetControllerFactory(new WindsorControllerFactory(_windsorContainer.Kernel));
    }
}

我们在应用程序启动时创建一个 WindsorContainer 对象(这是使用依赖注入的主要对象),并在结束时将其处置。在 InitializeWindsor 方法中,我们还更改了 MVC 的默认控制器工厂以使用依赖注入。因此,每当 ASP.NET MVC 需要一个 Controller 时(在每个 Web 请求中),它都会使用 DI 来创建它 [6]。请参阅 http://docs.castleproject.org/Windsor.Windsor-tutorial-ASP-NET-MVC-3-application-To-be-Seen.ashx 了解 Castle Windsor。在这里,我们的控制器工厂

public class WindsorControllerFactory : DefaultControllerFactory
{
    private readonly IKernel _kernel;

    public WindsorControllerFactory(IKernel kernel)
    {
        _kernel = kernel;
    }

    public override void ReleaseController(IController controller)
    {
        _kernel.ReleaseComponent(controller);
    }

    protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
    {
        if (controllerType == null)
        {
            throw new HttpException(404, string.Format("The controller for path '{0}' could not be found.", requestContext.HttpContext.Request.Path));
        }

        return (IController)_kernel.Resolve(controllerType);
    }
}

它非常简单,并且不言自明。重要部分是注入我们自己的对象依赖项。这是通过 PhoneBookDependencyInstaller 类完成的。该类会自动被 Castle Windsor 检查,因为它实现了 IWindsorInstaller。请记住 global.asax 文件中的 _windsorContainer.Install(FromAssembly.This()); 行。

public class PhoneBookDependencyInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.Kernel.ComponentRegistered += Kernel_ComponentRegistered;

        //Register all controllers
        container.Register(

            //Nhibernate session factory
            Component.For<ISessionFactory>().UsingFactoryMethod(CreateNhSessionFactory).LifeStyle.Singleton,

            //Unitofwork interceptor
            Component.For<NhUnitOfWorkInterceptor>().LifeStyle.Transient,

            //All repoistories
            Classes.FromAssembly(Assembly.GetAssembly(typeof(NhPersonRepository))).InSameNamespaceAs<NhPersonRepository>().WithService.DefaultInterfaces().LifestyleTransient(),

            //All services
            Classes.FromAssembly(Assembly.GetAssembly(typeof(PersonService))).InSameNamespaceAs<PersonService>().WithService.DefaultInterfaces().LifestyleTransient(),

            //All MVC controllers
            Classes.FromThisAssembly().BasedOn<IController>().LifestyleTransient()

            );
    }

    /// <summary>
    /// Creates NHibernate Session Factory.
    /// </summary>
    /// <returns>NHibernate Session Factory</returns>
    private static ISessionFactory CreateNhSessionFactory()
    {
        var connStr = ConfigurationManager.ConnectionStrings["PhoneBook"].ConnectionString;
        return Fluently.Configure()
            .Database(MsSqlConfiguration.MsSql2008.ConnectionString(connStr))
            .Mappings(m => m.FluentMappings.AddFromAssembly(Assembly.GetAssembly(typeof(PersonMap))))
            .BuildSessionFactory();
    }

    void Kernel_ComponentRegistered(string key, Castle.MicroKernel.IHandler handler)
    {
        //Intercept all methods of all repositories.
        if (UnitOfWorkHelper.IsRepositoryClass(handler.ComponentModel.Implementation))
        {
            handler.ComponentModel.Interceptors.Add(new InterceptorReference(typeof(NhUnitOfWorkInterceptor)));
        }

        //Intercept all methods of classes those have at least one method that has UnitOfWork attribute.
        foreach (var method in handler.ComponentModel.Implementation.GetMethods())
        {
            if (UnitOfWorkHelper.HasUnitOfWorkAttribute(method))
            {
                handler.ComponentModel.Interceptors.Add(new InterceptorReference(typeof(NhUnitOfWorkInterceptor)));
                return;
            }
        }
    }
}

正如您所见,我们使用 Windsor 的 Register 方法注册所有组件。请注意,我们用一行代码注册了所有存储库类。我们对服务控制器也做了同样的处理。我们使用工厂方法创建 ISessionFactory,该工厂用于创建 ISession(数据库连接)对象以与 NHibernate 一起使用。最后,在 Install 方法的开头,我们注册到 Kernel 的 ComponentRegistered 事件以注入我们的拦截逻辑。请参阅 Kernel_ComponentRegistered 方法。如果一个方法是存储库方法,我们总是对其使用拦截。此外,如果一个方法被标记为 UnitOfWork 属性,它也会被我们的 NhUnitOfWorkInterceptor 类拦截。

拦截

拦截是一种在方法调用开始时和结束时运行某些代码的技术。它通常用于日志记录、性能分析、缓存等。使用此技术,您可以在不更改方法的情况下,动态地将您的代码注入到所需的方法中。

我们使用拦截来实现工作单元。如果一个方法是存储库方法或标记了 UnitOfWork 属性(在前一节中解释过),我们在方法开始时打开一个数据库连接(NHibernate 中的 Session)并开始一个事务。如果在拦截的方法中没有抛出任何异常,那么事务将在方法结束时提交。如果方法抛出任何异常,则整个事务将回滚。因此,让我们看一下 NhUnitOfWorkInterceptor 类,了解我是如何实现的

/// <summary>
/// This interceptor is used to manage transactions.
/// </summary>
public class NhUnitOfWorkInterceptor : IInterceptor
{
    private readonly ISessionFactory _sessionFactory;

    /// <summary>
    /// Creates a new NhUnitOfWorkInterceptor object.
    /// </summary>
    /// <param name="sessionFactory">Nhibernate session factory.</param>
    public NhUnitOfWorkInterceptor(ISessionFactory sessionFactory)
    {
        _sessionFactory = sessionFactory;
    }

    /// <summary>
    /// Intercepts a method.
    /// </summary>
    /// <param name="invocation">Method invocation arguments</param>
    public void Intercept(IInvocation invocation)
    {
        //If there is a running transaction, just run the method
        if (NhUnitOfWork.Current != null || !RequiresDbConnection(invocation.MethodInvocationTarget))
        {
            invocation.Proceed();
            return;
        }

        try
        {
            NhUnitOfWork.Current = new NhUnitOfWork(_sessionFactory);
            NhUnitOfWork.Current.BeginTransaction();

            try
            {
                invocation.Proceed();
                NhUnitOfWork.Current.Commit();
            }
            catch
            {
                try
                {
                    NhUnitOfWork.Current.Rollback();
                }
                catch
                {

                }

                throw;
            }
        }
        finally
        {
            NhUnitOfWork.Current = null;
        }
    }

    private static bool RequiresDbConnection(MethodInfo methodInfo)
    {
        if (UnitOfWorkHelper.HasUnitOfWorkAttribute(methodInfo))
        {
            return true;
        }

        if (UnitOfWorkHelper.IsRepositoryMethod(methodInfo))
        {
            return true;
        }

        return false;
    }
}

主要方法是 Intercept。它首先检查当前线程之前是否已启动事务。如果是,它不会启动新事务,而是使用当前事务(参见 NhUnitOfWork.Current)。这样,对具有 UnitOfWork 属性的方法的嵌套调用可以使用/共享同一个事务。仅在工作单元方法的第一次入口处创建/提交事务。此外,如果一个方法不是事务性的,它只会调用该方法并返回。 invocation.Proceed() 命令执行对被拦截方法的调用。

如果当前没有事务,我们必须启动一个新事务,并在没有错误时提交事务,在出错时回滚事务。最后,我们将 NhUnitOfWork.Current 设置为 null,以便在需要时允许为当前线程启动其他事务。您还可以查看 UnitOfWorkHelper 类。

这样,整个应用程序中唯一打开/关闭连接和开始/提交/回滚事务的代码只在同一个地方定义。

Web 应用程序

我使用上述模式开发了一个简单的电话簿应用程序。它是一个 ASP.NET MVC 应用程序。因此,服务器端由 ASP.NET MVC Controllers 实现,客户端使用 HTML 和 Javascript 实现。

要运行应用程序,请先使用解决方案中的 PhoneBookDb.sql 文件创建数据库(它需要 SQL Server 2008 R2 或更高版本)。然后,如果您更改数据库名称,请检查 web.config 中的连接字符串。

此外,可能需要启用 Nuget 程序包还原来还原 nuget 程序包。

服务器端

在我们的应用程序中(出于演示目的),我们只定义了一个控制器:HomeController。它在其构造函数中获取所需的服务。Castle Windsor 将这些服务注入到控制器中,因为我们将服务类和 MVC 控制器工厂注册到了 Windsor。

public class HomeController : Controller
{
    private readonly IPersonService _personService;
    private readonly IPhoneService _phoneService;
    private readonly ICityService _cityService;

    public HomeController(IPersonService personService, ICityService cityService, IPhoneService phoneService)
    {
        _personService = personService;
        _cityService = cityService;
        _phoneService = phoneService;
    }

    public ActionResult Index()
    {
        return View();
    }

    public JsonResult PeopleList()
    {
        try
        {
            var personList = _personService.GetPeopleList();
            return Json(new { Result = "OK", Records = personList });
        }
        catch (Exception ex)
        {
            return Json(new {Result = "ERROR", Message = ex.Message});
        }
    }

    public JsonResult DeletePerson(int id)
    {
        try
        {
            _personService.DeletePerson(id);
            return Json(new { Result = "OK" });
        }
        catch (Exception ex)
        {
            return Json(new { Result = "ERROR", Message = ex.Message });
        }
    }
    
    //Other actions...
}

正如您所见,我们使用 JSON 对象与客户端进行通信。网页向此控制器发出 AJAX 请求以执行 CRUD(Create,Retrieve(选择),Update 和 Delete)操作。有关其他操作的源代码,请参阅。

客户端

客户端是 HTML 和 Javascript。我使用 jTable [7] 实现的客户端。它是由我开发的 jQuery 插件。它自动化了数据库记录列表和更新/创建/删除表单。有关更多信息,请参阅 jtable.org。有关实现,请参阅源代码。结果如下

Client side jTable implementation

控制台应用程序

在本节中,我将展示如何为控制台应用程序实现所有(上述)技术。我创建了一个简单的控制台应用程序,该应用程序定期运行、检查并删除所有年龄超过 120 岁的人(想想我不想在电话簿中存储老人)。这只是一个关于在后台运行并执行某些任务的服务示例。它可以是控制台应用程序,也可以是后台Windows 服务。没关系。在这里,我们的主类准备并运行依赖注入,并启动我们定期运行的服务

public class PhoneBookRunner : IDisposable
{
    private WindsorContainer _windsorContainer;

    public void Start()
    {
        _windsorContainer = new WindsorContainer();

        _windsorContainer.Install(FromAssembly.Containing<PhoneBookDependencyInstaller>());
        _windsorContainer.Install(FromAssembly.This());

        _windsorContainer.Resolve<IPeriodicServiceTrigger>().Start();
    }
    
    public void Dispose()
    {
        if (_windsorContainer != null)
        {
            _windsorContainer.Resolve<IPeriodicServiceTrigger>().Stop();
            _windsorContainer.Dispose();
        }
    }
}

在 Start 方法中,我们初始化 Castle Windsor 容器。然后我们使用 Installers 注册依赖项。请参阅使用 PhoneBookRunner 类的 Program.cs 的源代码。PhoneBookDependencyInstaller 注册了我们应用程序的主要组件。我还为这个控制台应用程序添加了一个新的安装程序

public class BackgroundServiceInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.Register(
            Component.For<IDeleteOldRecordsService>().ImplementedBy<DeleteOldRecordsService>().LifestyleTransient(),
            Component.For<IPeriodicServiceTrigger>().ImplementedBy<PeriodicServiceTrigger>().LifestyleSingleton()
            );
    }
}

PeriodicServiceTrigger 使用 Timer 执行定期检查

public class PeriodicServiceTrigger : IPeriodicServiceTrigger
{
    private readonly IDeleteOldRecordsService _deleteOldRecordsService;
    private readonly Timer _timer;

    public PeriodicServiceTrigger(IDeleteOldRecordsService deleteOldRecordsService)
    {
        _deleteOldRecordsService = deleteOldRecordsService;
        _timer = new Timer(10000);
        _timer.Elapsed += Timer_Elapsed;
    }

    public void Start()
    {
        _timer.Start();
    }

    public void Stop()
    {
        _timer.Stop();
    }

    private void Timer_Elapsed(object sender, ElapsedEventArgs e)
    {
        _deleteOldRecordsService.DeletePeopleOlderThan120Age();
    }
}

最后,我们有一个 IDeleteOldRecordsService,用于实现业务规则

public class DeleteOldRecordsService : IDeleteOldRecordsService
{
    private readonly IPersonRepository _personRepository;
    private readonly IPersonService _personService;

    public DeleteOldRecordsService(IPersonRepository personRepository, IPersonService personService)
    {
        _personRepository = personRepository;
        _personService = personService;
    }
    
    [UnitOfWork]
    public void DeletePeopleOlderThan120Age()
    {
        var yearAge120 = DateTime.Now.AddYears(-120);

        var oldRecords = _personRepository.GetAll().Where(person => person.BirthDay < yearAge120).ToList();
        foreach (var oldRecord in oldRecords)
        {
            _personService.DeletePerson(oldRecord.Id);
        }
    }
}

正如您所见,DeletePeopleOlderThan120Age 是一个 UnitOfWork 方法。因此,它以事务方式运行。

更多

如果您喜欢本文中的模式和技术,您也会喜欢 ASP.NET Boilerplate 应用程序框架。它实现了本文中描述的所有技术。它是开源的,并在 nuget 上分发。

请参阅 ASP.NET Boilerplate 入门文章:https://codeproject.org.cn/Articles/768664/Introduction-to-ASP-NET-Boilerplate

文章历史

  • 2013 年 2 月 13 日 - 添加了控制台应用程序
  • 2013 年 2 月 10 日 - 首个版本

参考文献

© . All rights reserved.