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

Catharsis 教程 03 - 使用 Catharsis 进行快速应用程序开发

starIconstarIconstarIconstarIconstarIcon

5.00/5 (2投票s)

2009年6月29日

CPOL

18分钟阅读

viewsIcon

21737

downloadIcon

189

使用Catharsis自动生成代码基础设施,分步指南,用于构建一个健壮的企业级多层ASP.NET MVC Web应用程序。

引言

Catharsis是一个强大的RAD(快速应用程序开发)工具,它围绕着一个坚实的架构构建,使用ASP.NET MVC和nHibernate。最佳实践设计模式和层之间的关注点分离是Catharsis框架设计的关键因素。

通过Guidance,该框架提供了一套健壮的企业级架构RAD功能,通过自动生成Web应用程序实体所需的大部分代码。在许多情况下,只需填写几个简单的对话框屏幕即可。

本文将解释如何快速构建一个用于创建、读取、更新和删除实体(CRUD)的应用程序。Catharsis Guidance会自动生成多层架构,并添加一个类和接口的骨架基础设施,这些类和接口无需太多额外编码即可工作。本文建立在同一系列的前一篇文章的基础上,该文章研究了Catharsis示例项目。在本文中,我们将向该示例项目添加一个新的实体,该项目可供下载。这些信息将使您能够快速创建自己的CRUD应用程序。

除了创建简单的实体外,本文还将解释如何使用该框架为实体编码引用,例如,一个实体在另一个实体中用作实体类型。最后,我们将看看如何为应用程序添加业务规则。

如果您有一个数据库,并希望快速创建一个企业级Web应用程序来访问该数据库,Catharsis提供了实现此目的的最佳方法。

与许多框架不同,Catharsis使用公共和受保护的方法编写,这使其完全可扩展。程序员可以在需要添加新功能时完全控制自己的应用程序并覆盖方法。在Catharsis上创建许多应用程序时,这并非必需,但对于企业级应用程序而言,知道该选项在需要时可用还是不错的。

在阅读本文之前,请阅读Catharsis安装指南。这是Catharsis教程01,列表在此处:https://codeproject.org.cn/script/Articles/MemberArticles.aspx?amid=4013680

一个基于Catharsis框架的强大示例解决方案可供下载。示例解决方案名为Firm.SmartControls,可在此处下载:http://catharsis.codeplex.com/Release/ProjectReleases.aspx?ReleaseId=28510

本系列的第二篇文章深入研究了示例解决方案,并解释了其设置方式。请在继续阅读之前阅读本文(Catharsis教程02):catharsis_tutorial_02.aspx

要学习Catharsis的一个好方法是安装演示解决方案,并按照本文的分步指南向该解决方案添加一个新实体。

该解决方案包含名为AgentAgentContract的实体,我们将添加一个名为Client的附加实体。

自动创建Web基础设施

Firm.SmartControls解决方案的数据库创建脚本实际上包含两个尚未映射的表,因此我们将使用其中一个作为添加新实体的示例。

catharsis_tutorial_03_001.png

Client”将是我们的新实体。(我们将它添加到Insurance命名空间。)

在通过Guidance添加新实体之前,我们需要启用Guidance程序包。点击“工具”->“Guidance程序包管理器”。

catharsis_tutorial_03_002.png

在出现的对话框中,点击“启用/禁用程序包”,选择ProjectBase.Guidance,然后点击“确定”。

catharsis_tutorial_03_003.png

关闭接下来出现的两个对话框,因为我们现在不需要它们。

请注意,如果您想添加完整的Web基础设施,最好的添加位置是通过实体(或Web项目)。也可以将其添加到数据层,但这将排除我们在此实例中需要的GUI元素。新实体被添加到的文件夹将成为该新实体命名空间的一部分。如果您希望实体位于新的命名空间中,则应在实体项目中创建一个文件夹并在其中添加;或者,您可以将其添加到现有文件夹中,就像我们在此例中所做的那样,因为我们希望新实体位于Insurance命名空间中。

右键单击文件夹,然后从菜单中选择“(Re) Create COMPLETE WEB infrastructure”。“(Re)”表示如果实体已存在于文件夹中,文件将被覆盖为新的空骨架类。这提供了一种撤销代码或纠正错误的方法。“COMPLETE WEB”意味着骨架类文件将被添加到每个项目中(包括单元测试)。如果您选择“(Re) Create Project - (Entity, Business, Common, Tests)”,则不会在Models、Controllers和Web项目中添加(或更改)任何文件。这是在不需要GUI元素的情况下使用的。

新实体的命名空间应为Firm.SmartControls.Entity.Insurance.Client,因此我们点击Insurance文件夹,如图所示,通过Guidance生成Web基础设施。

catharsis_tutorial_03_004.png

这是我们需要填写的 मुख्य 对话框。在此处提供尽可能多的信息将减少我们以后需要做的工作。

catharsis_tutorial_03_005.png

在对话框中键入新实体的名称。您现在可以添加最多三个其他属性。

在SQL Server 2005中,我们看到了InsuranceClient表的列,因此我们可以添加前三个:CodeNameBankCode。Guidance将自动生成检查以确保Code是唯一的(如果不需要,可以删除)。在此处添加属性可减少我们以后需要做的工作;但是,我们只能添加值类型,例如字符串属性“name”;我们不能添加实体类型,例如引用另一个表(如Country)的外键。

由于命名空间是根据您在解决方案中添加实体的位置确定的,因此它已被提供。

在这种情况下,实体类型应为'Persistent'。这将为没有基类(直接派生自Persistent对象)的业务对象创建骨架。其他类型允许重用先前实现的功能。

第二和第三个选项是CodeLists,您可以选择“simple”或“separate entity”。首先,我们需要清楚什么是CodeList。CodeList实体通常用于填充组合框,允许用户从一组预定义选项中选择一个选项。欧盟的所有国家都可以这样表示,性别男性和女性是另一个很好的例子。CodeList实体的另一个一般属性是数据是静态的。没有必要添加或删除此类型的对象。例如,性别永远不需要超过“男性”和“女性”。基类为CodeList实体提供代码和名称,因此对于性别,名称可以是Male,代码可以是M。这些是简单的实体,因为不需要额外信息。因此,使用简单CodeList的选项适用于性别或国家等内容。如果您需要一个像Gender这样的简单实体,可以使用ICodeList选项。在这种情况下,您不必实现任何东西,您的新ICodeList属性将立即工作,而无需任何额外编码。

该框架还提供了创建CodeList的选项,但允许实体被扩展以包含额外信息。例如,货币可以有一个名为“Dollar”和代码“USD”的对象,但我们也可能想添加一个子单位列并将其值设置为“cents”或“c”。如果我们想扩展CodeList的基本功能,“separate entity”可以被使用。在这种情况下,数据库表中的一个列应该保存子单位的值。

Tracked实体类型与Persistent类型相同,但提供了额外的代码,允许为该实体维护“审计跟踪”。如果您需要跟踪实体何时被更改,谁更改了它,以及它的状态是什么,这是最佳选择。

catharsis_tutorial_03_006.png

点击“完成”,一段时间后,所有文件都将自动生成,并会出现一个弹出窗口告知您下一步该做什么。

catharsis_tutorial_03_007.png

因此,我们按照这些说明,打开Str.Controller.cs文件,并添加高亮显示的行。

catharsis_tutorial_03_008.png

现在我们打开menu.config文件。

catharsis_tutorial_03_009.png

高亮显示的代码应该被添加。

catharsis_tutorial_03_010.png

它被添加到与Agent相同的级别,因此它将作为导航树中该节点的同级节点出现。

catharsis_tutorial_03_011.png

当然,尝试使用导航菜单中的新条目会导致错误,因为数据库表尚未通过nHibernate映射。

catharsis_tutorial_03_012.png

将数据库表映射到实体

我们正在映射的表看起来像这样。

catharsis_tutorial_03_013.png

打开为该实体自动生成的nHibernate文件:Firm.SmartControls.Data.Insurance.Client.hbm.xml

您在创建实体时提供的数据已添加。

<?xml version='1.0' encoding='utf-8' ?>
<hibernate-mapping xmlns='urn:nhibernate-mapping-2.2'
      namespace='Firm.SmartControls.Entity.Insurance' 
      assembly='Firm.SmartControls.Entity'>
  <class name='Client' table='Client' lazy='true' >
      <id name='ID' column='ClientId' >
        <generator class='native'></generator>
      </id>
    <property not-null='true' name='Code' />
    <property not-null='true' name='Name' />
    <property not-null='true' name='BankCode' />
  </class>
</hibernate-mapping>

需要更改以下部分:表名是InsuranceClient,而不是Client。ID列是InsuranceClientId,而不是ClientIdCountryIdGenderId是CodeLists,需要多对一映射。

这是完成后的版本。

<?xml version='1.0' encoding='utf-8' ?>
<hibernate-mapping xmlns='urn:nhibernate-mapping-2.2'
      namespace='Firm.SmartControls.Entity.Insurance' 
      assembly='Firm.SmartControls.Entity'>
  <class name='Client' table='InsuranceClient' lazy='true' >
      <id name='ID' column='InsuranceClientId' >
        <generator class='native'></generator>
      </id>
    <property not-null='true' name='Code' />
    <property not-null='true' name='Name' />
    <property not-null='true' name='BankCode' />

    <many-to-one name='Country' column='CountryId' lazy='false' ></many-to-one>
    <many-to-one name='Gender' column='GenderId' lazy='false' ></many-to-one>
    
  </class>
</hibernate-mapping>

我们在上面的文件中看到了对Firm.SmartControls.Entity.Insurance的引用,因此需要更改此文件以反映我们在映射文件中所做的更改。

DAO(数据访问对象)也需要更改,但在那之前,我们将为实体文件添加Guidance未自动生成的属性。

打开Client实体文件。

catharsis_tutorial_03_014.png

存在三个属性:CodeNameBankCode。现在我们将添加GenderCountry。这些是CodeList对象,因此我们需要为Firm.SmartControls.Entity.CodeLists添加一个using指令,以便GenderCountry数据类型能够被识别。我们应该添加的代码用**粗体**显示。

using System;                            // ===================================
using System.Collections.Generic;        // Guidance generated code © Catharsis
using System.Linq;                       // ===================================

using ProjectBase.Core;
using ProjectBase.Core.PersistentObjects;
using ProjectBase.Core.Collections;
using Firm.SmartControls.Entity.CodeLists;

namespace Firm.SmartControls.Entity.Insurance
{
    /// <summary>
    /// Entity Client. 
    /// </summary>
    [Serializable]
    public class Client : Persistent
    {
        public virtual string Code { get; set; }
        public virtual string Name { get; set; }
        public virtual string BankCode { get; set; }

        /// <summary>
        /// codelist
        /// </summary>
        public virtual Gender Gender { get; set; }
        /// <summary>
        /// codelist
        /// </summary>
        public virtual Country Country { get; set; }

现在我们将这些附加字段添加到DAO(Firm.SmartControls.Data.Insurance.ClientDao)。当添加新条目时,新添加的GenderCountry应该在智能感知中可用,这显然是因为它们现在是Client实体的属性。

catharsis_tutorial_03_015.png

运行带有新实体的应用程序。

现在我们对nHibernate文件、实体和DAO进行了必要的更改,因此“Client”菜单项将起作用。

catharsis_tutorial_03_016.png

当然,数据库中还没有任何Clients,所以我们需要添加它们。下一步是扩展“New”按钮后面的功能,以便我们能够添加新的Clients。

添加新项。

如果我们现在点击“New”按钮,我们将看到我们在Guidance设置期间指定的属性(CodeNameBankCode)已自动添加。

catharsis_tutorial_03_017.png

现在我们将添加GenderCountry属性。

打开ClientDetailsWC.ascx文件(WC缩写为“Web Control”)。

catharsis_tutorial_03_018.png

此文件显示了用于创建上面所示页面的HTML标记。

我们将缩小两个列(Identification和Description)的大小,并添加一个用于CodeLists的第三列,并将添加CodeLists用于GenderCountry

HTML中的每一行都包含多个字段集。目前有一个用于Identification的字段集和一个用于Description的字段集。我们将这两个的百分比宽度减小到32%,以便我们有足够的空间容纳三个字段集。

<div class='newRow mh50'>
    <fieldset class='newDetail w32p'>

w32p代表宽度CSS类。我们可以在以下文件中检查这些CSS类:

catharsis_tutorial_03_019.png

CSS样式.w32p { width: 32%; }将在我们的案例中使用。

现在我们可以为两个CodeLists添加第三个字段集,代码显示在此处。

<fieldset class='newDetail w32p'>  
      <legend><%= GetLocalized(Str.Business.CodeLists.CodeList)%></legend>
      <div class='fieldset'>
      
        <div class='inputWC inputWC60 w100p'>
          <div class='label'><%= GetLocalized(Str.Controllers.Country)%></div>
          <div class='input'><% Country.SetEntityAsDataSource(Model.Item.Country); %>
            <smart:AsyncComboBoxWC ID='Country' runat='server' 
                   TargetController='Country' /> </div>
        </div>

        <div class='inputWC inputWC60 w100p'>
          <div class='label'><%= GetLocalized(Str.Controllers.Gender)%></div>
          <div class='input'><% Gender.SetEntityAsDataSource(Model.Item.Gender); %>
            <smart:AsyncComboBoxWC ID='Gender' runat='server' 
                   TargetController='Gender' /> </div>
        </div>
    </div>
</fieldset>

此代码使用Model访问项(实体)及其属性。现在我们可以看到添加了两个新的下拉列表,并用我们所需的数据填充。

尝试实际添加新Client将失败。

这是因为当我们点击“Add”按钮时,Controller将尝试添加实体,但由于它尚不能处理实体类型属性,因此无法添加。

我们需要查看为新实体自动生成的Controller,它位于以下位置:

catharsis_tutorial_03_022.png

这里有许多区域可供我们添加代码,其中大多数在新的Controller文件中是空的。

catharsis_tutorial_03_023.png

知道按住CTRL键并键入mo将展开所有区域,同样CTRLml将折叠所有区域,这可能会很有用。

  • Members:用于存放类中使用的局部变量。
  • OnList:此区域包含重写的方法,我们可以使用这些方法来扩展创建实体列表的功能,以便在应用程序的“List”视图中显示,以及将该列表导出到Excel电子表格。
  • OnAfter:此区域包含一些用于在特定事件发生后执行任务的重写方法。默认Controller实现了OnAfterBindModelOnAfterBindSearch。这两个用于处理实体类型属性,因此我们将使用它们来添加Country和Gender。
  • OnBefore:与上面的区域类似,我们可以在此部分重写基类的几个方法。可以通过键入“override OnBefore”来查看可用方法的列表,然后智能感知将显示可用方法。
  • catharsis_tutorial_03_024.png

  • Actions:此区域可用于重写与Actions相关的方法,但如果不需要,可以将其删除。重要的是要认识到,自动生成的许多代码可能不适合您的特定需求,因此可以删除。删除不需要的代码比编写缺失的代码更容易。
  • ClearSearch:用于在使用搜索对象后清除其中的参数。
  • Properties:此区域有一个返回当前Controller名称的方法,在本例中当然是ClientController。它还用于加载我们可能需要的实体,如果它们是实体类型的话。在我们的示例中,这将是这种情况,因为我们将有两个实体类型。这些对象使用“惰性加载”方法,这意味着它们仅在需要时才创建,从而提高了应用程序的效率。

现在我们将进行必要的更改,以便能够保存新的Client。

我们需要向OnAfter区域添加两个方法来处理实体类型。

/// <summary>
/// Binds non value type properties for an Item
/// </summary>
/// <returns></returns>
protected override bool OnAfterBindModel()
{
    var success = base.OnAfterBindModel();
    int id = 0;
    // Country
    if (Request.Form.AllKeys.Contains(Str.Controllers.Country)
     && int.TryParse(Request.Form[Str.Controllers.Country], out id))
    {
        Model.Item.Country = CountryFacade.GetById(id);
    }
    // Gender
    if (Request.Form.AllKeys.Contains(Str.Controllers.Gender)
     && int.TryParse(Request.Form[Str.Controllers.Gender], out id))
    {
        Model.Item.Gender = GenderFacade.GetById(id);
    }
    return success;
}

/// <summary>
/// Binds non value type properties for searching
/// </summary>
/// <returns></returns>
protected override bool OnAfterBindSearch()
{
    var success = base.OnAfterBindSearch();
    int id;
    // Country
    if (Request.Form.AllKeys.Contains(Str.Controllers.Country))
    // there was some selection
    {
        // clear previous to null (it could be final stage also)
        Model.SearchParam.Example.Country = null; 
        if (int.TryParse(Request.Form[Str.Controllers.Country], out id))
        {
            Model.SearchParam.Example.Country = CountryFacade.GetById(id);

        }
    }
    // Gender
    if (Request.Form.AllKeys.Contains(Str.Controllers.Gender))
    // there was some selection
    {
        // clear previous to null (it could be final stage also)
        Model.SearchParam.Example.Gender = null;
        if (int.TryParse(Request.Form[Str.Controllers.Gender], out id))
        {
            Model.SearchParam.Example.Gender = GenderFacade.GetById(id);

        }
    }
    return success;
}

从代码可以看出,会进行一些检查以确保在表单(在ASCX控件中)中提供了Country的值,并确保提供的值是一个整数。然后,我们调用CountryFacade来查找具有表单发送过来的ID的Country,并将Country对象返回并添加到Client对象中。

我们还需要在Properties区域添加一些属性。

public override string ControllerName { get { return Str.Controllers.Client; } }
/// <summary>
/// Allowes LAZILY (or via IoC) to work with Country
/// </summary>
public virtual ICountryFacade CountryFacade
{
    protected get
    {
        if (_countryFacade.IsNull())
        {
            _countryFacade = FacadeFactory.CreateFacade<ICountryFacade>(Model.Messages);
        }
        return _countryFacade;
    }
    set
    {
        Check.Require(value.Is(), " ICountryFacade cannot be null");
        _countryFacade = value;
    }
}
/// <summary>
/// Allowes LAZILY (or via IoC) to work with Gender
/// </summary>
public virtual IGenderFacade GenderFacade
{
    protected get
    {
        if (_genderFacade.IsNull())
        {
            _genderFacade = FacadeFactory.CreateFacade<IGenderFacade>(Model.Messages);
        }
        return _genderFacade;
    }
    set
    {
        Check.Require(value.Is(), " IGenderFacade cannot be null");
        _genderFacade = value;
    }
}

这为上面OnAfter方法将使用的两个实体类型提供了一个外观。

上面的方法需要两个局部成员,这些成员在成员区域中添加,如下所示。

#region members
IGenderFacade _genderFacade;
ICountryFacade _countryFacade;
#endregion members

现在我们已经添加了所有必需的代码,以便能够添加新的Client。

当我们在列表中点击Client菜单项时,可以看到上面新添加的Client。

catharsis_tutorial_03_026.png

请注意,GenderCountry未显示在列表中。显示的Client实体属性是我们在创建Web基础设施时在Guidance对话框中提供给的那些。如上所述,应扩展控件中的OnList区域来处理此问题。

列表

在本节中,我们将向ClientController中的OnList方法添加内容,以显示列出的Client实体的GenderCountry

这是控制列表中显示内容的代码。

protected override void OnListToDisplay()
{
    Model.ListModel.ItemsToDisplay = Facade.GetBySearch(Model.SearchParam)
        .Select(i => new ItemToDisplay()
        {
            ID = i.ID,
            Description = i.ToDisplay(),
            Items = new List<IHeaderDescription>
            {   
              new HeaderDescription { HeaderName = "Code", Value = i.Code},
              new HeaderDescription { HeaderName = "Name" , Value = i.Name },
              new HeaderDescription { HeaderName = "BankCode" , Value = i.BankCode },
              new HeaderDescription { HeaderName = Str.Common.ID , 
                                      Value = i.ID.ToDisplay(), Align = Align.right },
            }
        } as IItemToDisplay);
}

我们将添加另一行来显示Country代码。

new HeaderDescription { HeaderName = Str.Controllers.Country, 
  Value = i.Country.Code, SortByObject=Str.Controllers.Country, 
  SortByProperty=Str.Common.Code},

此行还提供了列排序属性。您可以选择显示Country.Code,例如“IR”,或Country.Display,例如“IR (Ireland)”。

第二个实体类型属性Gender以类似方式添加。

catharsis_tutorial_03_027.png

在处理Catharsis框架时,请务必注意,在调试模式下运行应用程序时,通常需要重新生成整个应用程序才能在Web浏览器中看到更改。这是因为Catharsis框架的层之间存在关注点分离。当您在代码(如本例中的ClientController)中进行某些更改并按F5或点击Debug按钮时,只有Visual Studio认为需要更新的文件(DLL)才会被更新,因为Controller和Web项目之间不存在引用。稍后将对此进行更详细的解释,但请记住,如果您期望看到更改,请在测试更改之前重新生成整个解决方案。

catharsis_tutorial_03_028.png

未使用。

无需额外编码即可使实体可编辑。

当在Detail视图中查看实体时,点击Edit按钮,文本框将变得可编辑,更改需要更新的属性,然后点击Update以保存实体。

catharsis_tutorial_03_029.png

搜索

通过点击Search按钮可以访问搜索功能。

catharsis_tutorial_03_030.png

Guidance创建的默认搜索会处理我们在设置新实体Guidance时提供的属性。

catharsis_tutorial_03_031.png

HTML和CSS可以根据您的需求进行调整。

在搜索中显而易见地使用ID、Code、Name和Bank Code。搜索页面上显示的行数可以在搜索页面上定义。也可以在新窗口中显示搜索结果。

现在我们将添加必要的代码以搜索Country和Gender等实体类型属性。首先,我们将向ASCX控件添加元素。将添加一个包含两个属性组合框的字段集。

<fieldset class='newDetail w30p'>  
      <legend><%= GetLocalized(Str.Business.CodeLists.CodeList)%></legend>
      <div class='fieldset'>
      
        <div class='inputWC inputWC60 w100p'>
          <div class='label'><%= GetLocalized(Str.Controllers.Country)%></div>
          <div class='input'><% Country.SetEntityAsDataSource(
                                   Model.SearchParam.Example.Country); %>
            <smart:AsyncComboBoxWC ID='Country' runat='server' 
              TargetController='Country' ComboBoxShowEmpty='true' /> </div>
        </div>
        <div class='inputWC inputWC60 w100p'>
          <div class='label'><%= GetLocalized(Str.Controllers.Gender)%></div>
          <div class='input'><% Gender.SetEntityAsDataSource(
                  Model.SearchParam.Example.Gender).SetComboBoxName(
                  Str.Controllers.Gender) ; %>
            <smart:AsyncComboBoxWC ID='Gender' runat='server' 
              TargetController='Gender' ComboBoxShowEmpty='true' /> </div>
        </div>
    </div>
</fieldset>

这将创建我们所需的GUI元素,并用预期的列表填充它们。

catharsis_tutorial_03_032.png

这足以让系统搜索CountryGender

还可以将搜索功能扩展到按Country名称搜索,例如,这将在后续部分中进行描述。

业务规则

大多数应用程序在操作实体时都需要应用一些业务规则。例如,如果我们有一个Country为“Germany”的Client,那么不允许系统从可用Country列表中删除Country Germany是一个好主意。这将导致实体使用系统中已不再存在的实体的情况。这类似于数据库级别的外键数据完整性。我们不依赖数据库来处理这种情况,而在代码中处理这种情况更有效,因此我们将看到如何做到这一点。

业务规则应用于业务外观,可以在此处找到该位置。

catharsis_tutorial_03_033.png

为了强制执行一项业务规则,即如果一个Country被Client使用,则不允许删除该Country,我们需要让CountryFacade询问ClientFacade是否有任何Client在使用我们想要删除的Country。

这包括四个步骤。

  1. 必须重写CountryFacade中的“CheckDelete”方法,并在允许处理Country删除之前执行检查。
  2. 重写的CheckDelete方法中的检查应调用ClientFacade中的另一个方法来检查Country是否被Client使用。
  3. ClientFacade接口(IClientfacade)应扩展一个名为“IsCountryInUse”的方法。
  4. 应在ClientFacade中实现IsCountryInUse方法。

我们首先打开CountryFacade并添加以下代码。

/// <summary>
/// Must check if current entity is not used!
/// if any other entity use this instance we stop deleting and add an error message
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
protected override bool CheckDelete(Country entity)
{
    var result = base.CheckDelete(entity);
    if (ClientFacade.IsCountryInUse(entity))
    {
        Messages.AddError(this, Str.Messages.Keys.CannotDeleteItem, 
                          Str.Messages.Templates.CannotDelete1, 
                          entity.ToDisplay());
        result = false;
    }
    return result;
}

此方法使用ClientFacade,因此我们需要添加一个局部成员_clientFacade...

#region members
IClientFacade _clientFacade;
#endregion members

我们还需要一个ClientFacade的属性。

#region properties
/// <summary>
/// Allowes to set Agent using login
/// </summary>
public virtual IAgentFacade AgentFacade
{
    protected get
    {
        if (_agentFacade.IsNull())
        {
            _agentFacade = FacadeFactory.CreateFacade<IAgentFacade>(Messages);
        }
        return _agentFacade;
    }
    set
    {
        Check.Require(value.Is(), " IAgentFacade cannot be null");
        _agentFacade = value;
    }
}
#endregion properties

上面的CheckDelete方法调用IsCountryInUse方法,并根据该调用的结果确定Country是否可以删除。

必须将IsCountryInUse添加到IClientFacade

/// <summary>
/// Allows to provide check before delete.
/// Is there any Agent using 'entity' instance as Country
/// </summary>
/// <param name="entity"></param>
/// <returns>true if is in use</returns>
bool IsCountryInUse(Country entity);

请注意,还需要添加一个using指令,以便接口能够访问CodeList命名空间,因为它需要访问Country对象。

using Firm.SmartControls.Entity.CodeLists;

上面的using指令也需要添加到ClientFacade

现在我们在ClientFacade中实现IsCountryInUse方法。

#region IClientFacade
/// <summary>
/// Provides checking before a deletion takes place.
/// Are there any Clients using 'entity' instance as Country
/// </summary>
/// <param name="entity"></param>
/// <returns>true if is in use</returns>
public virtual bool IsCountryInUse(Country entity)
{
    var item = Dao.GetBySearch(new ClientSearch()
    {
        MaxRowsPerPage = 1,
        Example = new Client()
        {
            Country = entity
        }
    }).FirstOrDefault();

    if (item.Is())
    {
        Messages.AddWarning(this, Str.Messages.Keys.ItemInUse, 
                 Str.Messages.Templates.ItemIsUsedForEntity3, 
                 entity.ToDisplay(), item.ToDisplay(), Str.Controllers.Country);
        return true;
    }
    return false;
}
#endregion IClientFacade

现在我们可以测试我们的代码。运行应用程序,然后点击Clients以查看当前Clients的列表。

catharsis_tutorial_03_034.png

我们可以看到爱尔兰作为一个国家正在使用中,所以现在打开导航树的CodeLists分支,然后点击Country。我们可以使用Country爱尔兰旁边的红色X来尝试删除它。由于我们添加的业务规则,删除将失败。请注意,错误消息可以根据需要格式化。

业务规则还可以用于控制在添加或升级实体时允许的操作。

摘要

您现在应该已经理解了如何向示例解决方案添加新实体,将它们与其他实体(CodeLists)链接,以及添加一些基本业务规则。利用这些信息以及本系列第一篇文章中的指南,您现在应该能够使用Catharsis框架在SQL Server中创建一个新数据库并快速开发一个CRUD Web应用程序。

在未来的本系列教程中,我们将解决Catharsis框架用户遇到的一些问题。我们将更深入地研究如何使用Catharsis,并制作更多的示例应用程序。

Catharsis教程03 - 使用Catharsis进行快速应用程序开发 - CodeProject - 代码之家
© . All rights reserved.