CSLA.NET 企业应用开发艺术:框架开发实战
引言:我的架构启蒙之路
2010 年,我接触了 CSLA.NET 框架,阅读了《C# 企业应用开发艺术:CSLA.NET 框架开发实战》这本书。这本书让我对企业级应用开发有了新的认识,理解了 N-Tier 分布式架构的设计思路。
框架背景
CSLA = Component-based Scalable Logical Architecture(基于组件的可扩展逻辑架构)
| 项目 | 说明 |
|---|---|
| 作者 | Rockford Lhotka |
| 官网 | https://cslanet.com/ |
| GitHub | https://github.com/MarimerLLC/csla |
2010 年的技术环境:Windows Forms 仍是桌面主流,Web Forms 主导 Web 开发,WPF 刚刚兴起,Silverlight 被视为未来。CSLA.NET 在这个时期提供了一套完整的业务层解决方案。
CSLA.NET 框架概述
什么是 CSLA.NET
CSLA = Component-based Scalable Logical Architecture(基于组件的可扩展逻辑架构)
- 作者: Rockford Lhotka
- 官网: https://cslanet.com/
- Wiki: https://en.wikipedia.org/wiki/Component-based_Scalable_Logical_Architecture
- GitHub: https://github.com/MarimerLLC/csla
CSLA.NET 的核心理念是:业务逻辑不应该散落在 UI 层、数据访问层或其他地方,而应该有一个专门的”家”——业务对象层。
核心价值主张
CSLA.NET 的关键特性:
| 特性 | 描述 | 应用场景 |
|---|---|---|
| N-Level Undo | 支持多级撤销操作,对象状态可回溯到任意历史状态 | 表单编辑、复杂向导、取消操作 |
| 业务规则跟踪 | 自动跟踪业务规则执行状态,规则可组合、可依赖 | 复杂业务验证、跨字段验证 |
| 元状态维护 | 自动维护对象的元数据状态(IsNew, IsDirty, IsDeleted, IsValid) | 数据绑定、UI状态控制 |
| 数据访问位置透明 | 通过 DataPortal 实现,业务对象无需关心数据访问位置 | 2-Tier ↔ N-Tier 灵活部署 |
| Web Services 支持 | 原生支持通过 Web Services 进行分布式数据访问 | 跨网络应用服务器部署 |
传统架构的问题:
┌─────────────┐ ┌──────────────┐ ┌──────────────┐
│ UI Layer │ │ Business Layer│ │ Data Layer │
│ (WinForms) │ │ (散乱代码) │ │ (ADO.NET) │
└─────────────┘ └──────────────┘ └──────────────┘
↓ ↓ ↓
业务逻辑散落各处 难以维护和测试 技术债务累积
CSLA.NET 的解决方案:
┌─────────────────────────────────────────────────────────────┐
│ CSLA.NET 业务层 │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ 业务对象 (Business Objects) │ │
│ │ ├── 继承 BusinessBase/ReadOnlyBase │ │
│ │ ├── 业务规则 (Business Rules) │ │
│ │ ├── 验证规则 (Validation Rules) │ │
│ │ ├── 授权规则 (Authorization Rules) │ │
│ │ └── 状态管理 (State Tracking) │ │
│ └────────────────────────────────────────────────────────┘ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ DataPortal (数据门户 - 移动对象的核心) │ │
│ │ ├── Create/Fetch/Update/Delete │ │
│ │ ├── 通道适配器 (Channel Adapter) │ │
│ │ └── 消息路由 (Message Router) │ │
│ └────────────────────────────────────────────────────────┘ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ 配置驱动的部署模型 │ │
│ │ ├── 1-Tier (2层直接访问) │ │
│ │ ├── 2-Tier (客户端-服务器) │ │
│ │ └── N-Tier (多层应用服务器) │ │
│ └────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
↓ 可用于任何 UI 技术
┌─────────────┬─────────────┬─────────────┐
│ WPF/WF │ Blazor/WASM │ WinForms │
└─────────────┴─────────────┴─────────────┘
CSLA.NET 的核心价值在于:
- 业务逻辑的专门容器:为验证规则、业务规则、授权规则提供统一的框架
- 移动对象架构:对象可在客户端和服务器之间移动,同时保持状态和行为
- 配置驱动的部署:无需修改代码即可切换 2-Tier 和 N-Tier 部署
- UI 无关的业务层:同样的业务对象可用于 WinForms、WPF、Blazor 等任何 UI 技术
移动对象:CSLA.NET 的核心创新
什么是移动对象
移动对象(Mobile Objects)是 CSLA.NET 的标志性概念,指的是业务对象可以”物理性地”在不同的计算层之间移动,同时保持其状态和行为的完整性。
传统无状态架构的问题:
客户端 服务器
│ │
│ ┌─────────────┐ │
│ │ 业务对象 │ │
│ │ (无状态) │ │
│ └─────────────┘ │
│ │
└───────── 数据请求 ──────────→
│ │
◄──────── 返回数据 ────────────
CSLA.NET 的解决方案:
客户端 服务器
│ │
│ ┌─────────────┐ │
│ │ 业务对象 │ │
│ │ (有状态) │ │
│ └─────────────┘ │
│ │
└─ DataPortal.Create() ──────→ 对象移动到服务器
│
┌──▼──────────┐
│ 业务逻辑执行 │
│ 数据库操作 │
└─────────────┘
│
◄─ DataPortal.Fetch() ───────── 对象回到客户端
│
│ ┌─────────────┐ │
│ │ 业务对象 │ │
│ │ (完整状态) │ │
│ └─────────────┘ │
移动对象的关键特性
graph TD
A[客户端<br/>业务对象] -->|DataPortal.Create| B[服务器端<br/>执行创建]
B -->|序列化对象| C[数据库]
C -->|DataPortal.Fetch| D[返回客户端]
D -->|反序列化| A
E[部署模型切换] -->|无需代码修改| F[2-Tier ↔ N-Tier]
style A fill:#00fff5,stroke:#00b8b0,color:#008b82
style B fill:#ff006e,stroke:#d6005c,color:#b3004e
style D fill:#faff00,stroke:#e6d900,color:#c9b800
代码示例:
// 业务对象定义
public class Customer : BusinessBase<Customer>
{
public static readonly PropertyInfo<int> IdProperty =
RegisterProperty<int>(c => c.Id);
public static readonly PropertyInfo<string> NameProperty =
RegisterProperty<string>(c => c.Name);
public int Id
{
get => GetProperty(IdProperty);
private set => SetProperty(IdProperty, value);
}
public string Name
{
get => GetProperty(NameProperty);
set => SetProperty(NameProperty, value);
}
// 工厂方法
public static Customer CreateCustomer()
{
return DataPortal.Create<Customer>();
}
public static Customer GetCustomer(int id)
{
return DataPortal.Fetch<Customer>(id);
}
// DataPortal 方法 - 在服务器端执行
private void DataPortal_Fetch(int id)
{
using (var ctx = DbContextManager<DatabaseContext>.GetManager())
{
var data = ctx.DataContext.Customers.Single(c => c.Id == id);
using (BypassPropertyChecks) // 绕过属性检查
{
LoadProperty(IdProperty, data.Id);
LoadProperty(NameProperty, data.Name);
}
}
}
protected override void AddBusinessRules()
{
base.AddBusinessRules();
// 验证规则
BusinessRules.AddRule(new RequiredRule(NameProperty));
BusinessRules.AddRule(new MaxLengthRule(NameProperty, 100));
// 业务规则
BusinessRules.AddRule(new CustomerAgeRule());
// 授权规则
BusinessRules.AddRule(
new AuthorizationRule(
AuthorizationActions.WriteProperty,
NameProperty,
"AdminRole"
)
);
}
}
// 使用示例
var customer = Customer.GetCustomer(1);
customer.Name = "新名称";
customer.Save(); // 自动调用 DataPortal_Update
配置驱动的部署模型
CSLA.NET 最强大的特性之一是通过配置即可切换部署模型,无需修改任何代码。
客户端配置 (appsettings.json):
{
"CSLA": {
"DataPortalProxy": "Local",
"DataPortalUrl": ""
}
}
2-Tier 配置:
{
"CSLA": {
"DataPortalProxy": "HttpProxy",
"DataPortalUrl": "https://api.example.com/api/dataportal"
}
}
N-Tier 配置:
{
"CSLA": {
"DataPortalProxy": "WcfProxy",
"DataPortalUrl": "net.tcp://server:8000/WcfPortal"
}
}
DataPortal:分布式数据门户架构
DataPortal 架构解析
DataPortal 是 CSLA.NET 移动对象架构的核心,它负责在客户端和服务器之间传递业务对象。
┌─────────────────────────────────────────────────────┐
│ 客户端 DataPortal (Static) │
│ ┌───────────────────────────────────────────────┐ │
│ │ Create() / Fetch() / Update() / Delete() │ │
│ └──────────────┬──────────────────────────────┘ │
└─────────────────┼──────────────────────────────────┘
│
▼
┌─────────────────────┐
│ 代理层 │
│ ┌─────────────────┐ │
│ │ LocalProxy │ │ ← 直接访问
│ │ HttpProxy │ │ ← HTTP/HTTPS
│ │ WcfProxy │ │ ← WCF
│ │ RabbitMqProxy │ │ ← 消息队列
│ └─────────────────┘ │
└─────────┬───────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ 服务器端 DataPortal │
│ ┌───────────────────────────────────────────────┐ │
│ │ 消息路由器 (Message Router) │ │
│ │ ├── Create/Fetch (查询条件对象) │ │
│ │ ├── Update/Delete (业务对象本身) │ │
│ │ └── Execute (命令对象) │ │
│ └──────┬─────────────────────────────────────────┘ │
│ │
│ ▼
│ ┌───────────────────────────────────────────────┐ │
│ │ 业务对象 DataPortal_* 方法实现 │ │
│ │ ┌─────────────────────────────────────────┐ │ │
│ │ │ DataPortal_Create() │ │ │
│ │ │ DataPortal_Fetch() │ │ │
│ │ │ DataPortal_Insert() │ │ │
│ │ │ DataPortal_Update() │ │ │
│ │ │ DataPortal_Delete() │ │ │
│ │ └─────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────┐ │
│ │ 数据访问层 (DAL) - 可插拔 │ │
│ │ ├── ADO.NET │ │
│ │ ├── Entity Framework │ │
│ │ ├── NHibernate │ │
│ │ └── LINQ to SQL │ │
│ └─────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────┘
动态层切换能力(运行时代理工厂)
CSLA.NET 4.0 引入了更强大的动态层切换能力,通过自定义代理工厂(IDataPortalProxyFactory)实现运行时根据业务对象类型选择不同的数据访问通道。
配置驱动的代理切换:
// 默认行为:通过配置文件统一切换代理
// app.config/web.config
<configuration>
<appSettings>
<!-- 切换到本地代理 -->
<add key="CslaDataPortalProxy" value="Local" />
<!-- 或切换到 HTTP 代理 -->
<add key="CslaDataPortalProxy" value="HttpProxy" />
<!-- 或切换到 WCF 代理 -->
<add key="CslaDataPortalProxy" value="WcfProxy" />
</appSettings>
</configuration>
自定义代理工厂(高级场景):
// 实现 IDataPortalProxyFactory 接口
public class CustomProxyFactory : IDataPortalProxyFactory
{
public IDataPortalProxy CreateProxy(Type objectType)
{
// 根据业务对象类型动态选择代理
if (objectType.Namespace.StartsWith("MyApp.LocalOnly"))
{
// 本地对象使用直接数据库访问
return new LocalProxy();
}
if (objectType.Namespace.StartsWith("MyApp.Remote"))
{
// 远程对象通过 HTTP 调用应用服务器
return new HttpProxy();
}
if (objectType == typeof(SensitiveData))
{
// 敏感数据使用专用安全通道
return new SecureWcfProxy();
}
// 默认代理
return new HttpProxy();
}
}
// 配置自定义代理工厂
<configuration>
<appSettings>
<add key="CslaDataPortalProxyFactory"
value="MyApp.CustomProxyFactory, MyApp" />
</appSettings>
</configuration>
应用场景:
| 场景 | 实现方案 | 优势 |
|---|---|---|
| 混合部署 | 核心数据用本地代理,报表用远程代理 | 性能与灵活性兼顾 |
| 多租户 | 不同租户使用不同应用服务器 | 租户隔离 |
| 偶尔连接 | 在线时用远程代理,离线时用本地代理 | 支持离线场景 |
| 安全分级 | 敏感操作用加密通道,普通操作用标准通道 | 分级安全 |
通道适配器模式(Channel Adapter)
设计意图:允许应用在不同部署模型之间灵活切换。
ChannelAdapter
│
├─→ LocalAdapter
│ (直接数据库访问,2层)
│
├─→ HttpAdapter
│ (HTTP/HTTPS,3层Web)
│
├─→ WcfAdapter
│ (WCF协议,3层服务)
│
├─→ RabbitMqAdapter
│ (消息队列,异步解耦)
│
└─→ gRPCAdapter
(gRPC,高性能RPC)
消息路由模式(Message Router)
单一入口点设计:
客户端请求 → DataPortal → 代理层 → 网络传输
│
▼
┌────────────────────────┴────────────┐
│ 服务器 DataPortal (单一入口) │
│ ┌───────────────────────────────┐ │
│ │ 消息路由器 │ │
│ │ ┌────────┬────────┐ │ │
│ │ │ Create │ Fetch │ Execute │ │
│ │ └───┬────┴───┬────┘ │ │
│ │ │ │ │ │ │
│ │ ▼ ▼ ▼ │ │
│ │ ┌──▼──┐ ┌──▼──┐ ┌──▼────┐ │ │
│ │ │ DAL │ │ DAL │ │Command│ │ │
│ │ └─────┘ └─────┘ └───────┘ │ │
│ └───────────────────────────────┘ │
└───────────────────────────────────┘
业务规则引擎:声明式规则管理
CSLA.NET 提供了一套完整的业务规则引擎,支持三种类型的规则:验证规则、业务规则和授权规则。
验证规则 (Validation Rules)
验证规则用于确保数据的有效性。
public class Customer : BusinessBase<Customer>
{
protected override void AddBusinessRules()
{
// 必填规则
BusinessRules.AddRule(new RequiredRule(NameProperty));
// 长度规则
BusinessRules.AddRule(new MaxLengthRule(NameProperty, 100));
// 正则表达式规则
BusinessRules.AddRule(new RegExRule(EmailProperty,
@"^[^@\w-\.]+@[\w-\.]+$"));
// 数值范围规则
BusinessRules.AddRule(new MinValueRule(AgeProperty, 18));
// 自定义验证规则
BusinessRules.AddRule(new CustomerEmailUniqueRule());
}
}
// 自定义规则示例
public class CustomerEmailUniqueRule : BusinessRule
{
public CustomerEmailUniqueRule()
: base(Customer.EmailProperty)
{
InputProperties = new List<IPropertyInfo>
{
Customer.EmailProperty
};
AffectedProperties.Add(Customer.EmailProperty);
PrimaryProperty = Customer.EmailProperty;
}
protected override void Execute(IRuleContext context)
{
var email = context.InputPropertyValues[Customer.EmailProperty];
// 检查邮箱是否已存在
var exists = _customerRepository.EmailExists(email.ToString());
if (exists)
{
context.AddErrorResult("邮箱已被使用");
}
}
}
业务规则 (Business Rules)
业务规则用于实现复杂的业务逻辑。
public class Order : BusinessBase<Order>
{
protected override void AddBusinessRules()
{
// 订单金额范围规则
BusinessRules.AddRule(
CommonRules.BetweenValue(OrderAmountProperty, 0.01m, 999999m)
);
// 折扣规则
BusinessRules.AddRule(new OrderDiscountRule());
// 库存检查规则
BusinessRules.AddRule(new InventoryCheckRule());
}
}
// 自定义业务规则
public class OrderDiscountRule : BusinessRule
{
public OrderDiscountRule()
: base(Order.OrderAmountProperty, Order.OrderDateProperty)
{
InputProperties = new List<IPropertyInfo>
{
Order.OrderAmountProperty,
Order.OrderDateProperty
};
}
protected override void Execute(IRuleContext context)
{
var amount = context.InputPropertyValues[Order.OrderAmountProperty] as decimal?;
var date = context.InputPropertyValues[Order.OrderDateProperty] as DateTime?;
// 周末折扣
if (date.Value.DayOfWeek == DayOfWeek.Saturday)
{
context.AddOutValue(Order.DiscountPercentProperty, 0.9m);
}
// 节假日双倍积分
if (IsHoliday(date.Value))
{
context.AddOutValue(Order.PointsProperty, amount * 2);
}
}
}
授权规则 (Authorization Rules)
授权规则用于控制访问权限。
public class SensitiveData : BusinessBase<SensitiveData>
{
protected override void AddBusinessRules()
{
// 属性级授权
BusinessRules.AddRule(
new AuthorizationRule(
AuthorizationActions.ReadProperty,
CreditCardNumberProperty,
"FinanceRole"
)
);
// 方法级授权
BusinessRules.AddRule(
new AuthorizationRule(
AuthorizationActions.ExecuteMethod,
DeleteCustomerMethod,
"AdminRole"
)
);
// 角色继承
BusinessRules.AddRule(
new IsInRoleAuthorizationRule(
"ManagerRole",
"SupervisorRole"
)
);
}
public bool CanReadProperty(string propertyName)
{
return BusinessRules.CheckRules(
AuthorizationActions.ReadProperty,
propertyName
);
}
}
规则执行引擎
graph TD
A[属性赋值] -->|触发规则检查| B[规则引擎]
B --> C{验证规则}
B --> D{业务规则}
B --> E{授权规则}
C -->|通过/失败| F[IsSavable]
D -->|计算输出| G[属性值变换]
E -->|允许/拒绝| H[操作权限]
F & H -->|对象可保存| I["Save()"]
规则特性:
- 优先级控制:规则执行顺序可调整
- 依赖链:规则可依赖其他规则的结果
- 短路执行:第一个失败即停止
- 输出值:业务规则可产生输出值影响其他属性
技术演进:从 2010 到 2024
时间线
graph LR
A[2010<br/>CSLA 3.x<br/>.NET 3.5] --> B[2014<br/>CSLA 4.0<br/>Silverlight支持]
B --> C[2016<br/>CSLA 4.5<br/>Xamarin支持]
C --> D[2018<br/>CSLA 5.0<br/>.NET Core支持]
D --> E[2020<br/>CSLA 6.0<br/>去Silverlight]
E --> F[2022<br/>CSLA 7.0<br/>.NET 6/7]
F --> G[2024<br/>CSLA 8.0<br/>.NET 8]
style A fill:#00fff5,stroke:#00b8b0,color:#008b82
style D fill:#faff00,stroke:#e6d900,color:#c9b800
style F fill:#4c8af5,stroke:#1a5fb4,color:#134a8f
style G fill:#ff006e,stroke:#d6005c,color:#b3004e
2010 年的技术背景
.NET 3.5 时代:
- Windows Forms 是主流
- WPF 刚刚兴起
- Silverlight 是热门技术
- LINQ 刚发布不久
- Entity Framework 尚未成熟
CSLA 3.x 特点:
- 专注 Windows 客户端(WinForms + WPF)
- 2-Tier/3-Tier 部署模型
- 数据绑定支持
- 业务规则引擎
技术演进对比
| 特性 | 2010 (CSLA 3.x) | 2024 (CSLA 8.0) |
|---|---|---|
| .NET 版本 | .NET 3.5 | .NET 8 |
| UI 支持 | WinForms, WPF | Blazor, MAUI, WPF, WinUI 3 |
| 移动端 | 无 | MAUI, Xamarin |
| Web 端 | WebForms | Blazor, ASP.NET Core |
| 数据访问 | ADO.NET | EF Core, Dapper, gRPC |
| 验证 | 自定义规则 | Data Annotations + FluentValidation |
| 测试 | NUnit, MSTest | xUnit + built-in test |
| 部署 | WCF + IIS | Docker + Kubernetes |
设计模式深度解析
CSLA.NET 是各种设计模式的集大成者,理解这些模式对于掌握框架本质至关重要。
工厂模式(Factory Pattern)
CSLA.NET 使用静态工厂方法来创建和获取业务对象,这是使用框架的标准方式。
public class Customer
{
// 创建新对象
public static Customer CreateCustomer()
{
return DataPortal.Create<Customer>();
}
// 获取现有对象
public static Customer GetCustomer(int id)
{
return DataPortal.Fetch<Customer>(id);
}
// 删除对象
public static void DeleteCustomer(int id)
{
DataPortal.Delete<Customer>(id);
}
}
单元工作模式(Unit of Work)
CSLA.NET 的业务对象自动参与事务管理,Save() 方法即为单元工作的边界。
public void ProcessOrder(Order order)
{
using (var scope = new UnitOfWorkScope())
{
order.Save(); // 保存订单
// 批量保存订单项
foreach (var item in order.Items)
{
item.Save();
}
scope.Complete(); // 提交事务
}
}
观察者模式(Observer Pattern)
CSLA.NET 业务对象实现了 INotifyPropertyChanged,自动支持 WPF/WinForms 数据绑定。
public class Customer : BusinessBase<Customer>
{
public static readonly PropertyInfo<string> NameProperty =
RegisterProperty<string>(c => c.Name);
public string Name
{
get => GetProperty(NameProperty);
set => SetProperty(NameProperty, value); // 自动触发 PropertyChanged
}
// 属性变更事件(自动继承自 BusinessBase)
// public event PropertyChangedEventHandler PropertyChanged;
}
策略模式(Strategy Pattern)
DataPortal 的代理层使用策略模式,不同的代理对应不同的部署策略。
public interface IDataPortalProxy
{
TResult Fetch<T>(Criteria criteria);
}
// 具体策略实现
public class HttpProxy : IDataPortalProxy
{
public TResult Fetch<T>(Criteria criteria)
{
// HTTP 实现
}
}
public class WcfProxy : IDataPortalProxy
{
public TResult Fetch<T>(Criteria criteria)
{
// WCF 实现
}
}
// 运行时通过配置选择策略
数据传输对象(DTO)模式
CSLA.NET 的独特之处在于:业务对象既是实体也是 DTO,无需单独的映射层。
public class Customer : BusinessBase<Customer>
{
// 可序列化状态
public int Id { get; private set; }
public string Name { get; set; }
// 验证和授权规则随对象移动
// 无需单独的 DTO 层
}
实战应用:企业级应用架构
典型架构设计
┌─────────────────────────────────────────────────────┐
│ 表现层 │
│ ┌──────────┬──────────┬──────────┬──────────────┐ │
│ │ WinForms │ WPF │ Blazor │ MAUI │ │
│ └──────────┴──────────┴──────────┴──────────────┘ │
└───────────────────────────┬──────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ 业务层 │
│ ┌─────────────────────────────────────────────────┐ │
│ │ 客户对象 (Customer, Order, Product, etc.) │ │
│ │ ├── 继承 BusinessBase<T> │ │
│ │ ├── 业务规则 (Validation, Business, Auth) │ │
│ │ ├── 工厂方法 (Create, Fetch, Save) │ │
│ │ └── DataPortal_* 方法 (数据访问) │ │
│ └─────────────────────────────────────────────────┘ │
└───────────────────────────┬──────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ 数据层 │
│ ┌─────────────────────────────────────────────────┐ │
│ │ DAL 接口 + 具体实现 │ │
│ │ ├── ADO.NET + LINQ to SQL │ │
│ │ ├── Entity Framework + Repository │ │
│ │ ├── NHibernate + HQL │ │
│ │ └── MongoDB + NoRM │ │
│ └─────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────┘
完整业务对象示例
[Serializable]
public class Order : BusinessBase<Order>
{
public static readonly PropertyInfo<int> IdProperty =
RegisterProperty<int>(c => c.Id);
public static readonly PropertyInfo<string> CustomerNameProperty =
RegisterProperty<string>(c => c.CustomerName);
public static readonly PropertyInfo<decimal> AmountProperty =
RegisterProperty<decimal>(c => c.Amount);
public static readonly PropertyInfo<DateTime> OrderDateProperty =
RegisterProperty<DateTime>(c => c.OrderDate);
public static readonly PropertyInfo<List<OrderLine>> LinesProperty =
RegisterProperty<List<OrderLine>>(c => c.Lines);
public int Id
{
get => GetProperty(IdProperty);
private set => SetProperty(IdProperty, value);
}
public string CustomerName
{
get => GetProperty(CustomerNameProperty);
set => SetProperty(CustomerNameProperty, value);
}
public decimal Amount
{
get => GetProperty(AmountProperty);
set => SetProperty(AmountProperty, value);
}
public DateTime OrderDate
{
get => GetProperty(OrderDateProperty);
set => SetProperty(OrderDateProperty, value);
}
public List<OrderLine> Lines
{
get => GetProperty(LinesProperty);
set => SetProperty(LinesProperty, value);
}
// 工厂方法
public static Order CreateOrder()
{
return DataPortal.Create<Order>();
}
public static Order GetOrder(int id)
{
return DataPortal.Fetch<Order>(id);
}
// DataPortal_Insert
private void DataPortal_Insert()
{
using (var ctx = DbContextManager<DatabaseContext>.GetManager())
{
var order = new OrderEntity
{
CustomerName = this.CustomerName,
Amount = this.Amount,
OrderDate = this.OrderDate
};
ctx.DataContext.Orders.Add(order);
ctx.DataContext.SaveChanges();
// 获取数据库生成的 ID
using (BypassPropertyChecks)
{
IdProperty.LoadValue(order.Id);
}
}
// 插入订单项
foreach (var line in Lines)
{
line.Save();
}
}
// 业务规则
protected override void AddBusinessRules()
{
base.AddBusinessRules();
// 验证规则
BusinessRules.AddRule(new RequiredRule(CustomerNameProperty));
BusinessRules.AddRule(new RequiredRule(AmountProperty));
BusinessRules.AddRule(new MinValueRule(AmountProperty, 0.01m));
// 业务规则
BusinessRules.AddRule(new OrderAmountRule());
BusinessRules.AddRule(new OrderDateRule());
// 授权规则
BusinessRules.AddRule(
new AuthorizationRule(
AuthorizationActions.CreateObject,
typeof(Order),
"SalesRole"
)
);
}
}
// 订单金额业务规则
public class OrderAmountRule : BusinessRule
{
public OrderAmountRule()
: base(Order.AmountProperty)
{
InputProperties = new List<IPropertyInfo>
{
Order.AmountProperty,
Order.CustomerNameProperty
};
}
protected override void Execute(IRuleContext context)
{
var amount = context.InputPropertyValues[Order.AmountProperty] as decimal?;
// 白金客户最低订单金额
if (IsVipCustomer(context))
{
context.AddErrorResult("VIP客户最低订单金额为 1000 元");
}
// 普通客户限制
if (amount < 10)
{
context.AddErrorResult("订单金额不能小于 10 元");
}
}
}
CSLA.NET vs 其他架构方案对比
2010 年架构对比
| 特性 | CSLA.NET | DataSet | 自定义三层 | DDD |
|---|---|---|---|---|
| 业务逻辑封装 | ✅ 原生支持 | ❌ 散落 | ❌ 需手动 | ✅ 领域驱动 |
| 验证规则 | ✅ 规则引擎 | ❌ 手动 | ❌ 手动 | ⚠️ 需扩展 |
| 授权控制 | ✅ 声明式 | ❌ 手动 | ❌ 手动 | ⚠️ 需扩展 |
| N-Tier 支持 | ✅ 配置切换 | ❌ 固定 | ⚠️ 需重构 | ⚠️ 需扩展 |
| 数据绑定 | ✅ WPF/WinForms | ✅ 支持 | ⚠️ 需手动 | ⚠️ 需扩展 |
| 分布式事务 | ✅ 内置 | ❌ 手动 | ⚠️ 需手动 | ⚠️ 需扩展 |
适用场景分析
CSLA.NET 最适合:
- ✅ 企业级业务应用(ERP、CRM、MES)
- ✅ 需要复杂业务规则和验证逻辑
- ✅ N-Tier 分布式部署需求
- ✅ 多客户端需求(WPF + Web + 移动)
- ✅ 业务逻辑复用(不同客户端共享)
不适合 CSLA.NET 的场景:
- ❌ 简单 CRUD 应用
- ❌ 性能极致要求的系统
- ❌ 微服务架构(CSLA 更偏向单体架构)
技术深度:为什么说它是”敲门砖”
对分布式系统的深刻理解
DataPortal 的巧妙设计:
- 透明代理:客户端代码无需关心对象在哪里执行
- 位置透明性:通过配置切换 2-Tier ↔ N-Tier
- 序列化透明:对象状态自动在网络间传递
关键代码模式:
// 客户端代码 - 完全不知道服务器端实现
var customer = Customer.GetCustomer(123);
customer.Name = "新名字";
customer.Save(); // 可能运行在客户端,也可能运行在服务器端
// 部署模型通过配置切换,代码零修改
设计模式的学习价值
从 CSLA.NET 学到的设计模式:
- 业务对象封装
- 将业务规则、验证、授权封装在对象内部
- 单一职责:每个对象管理自己的状态和行为
- 规则引擎模式
- 声明式规则定义
- 规则可组合和依赖
- 规则执行顺序可控
- 移动对象模式
- 对象可在层间移动而保持行为
- 序列化/反序列化自动化
- 位置透明性
- 配置驱动架构
- 通过配置而非代码改变行为
- 提高系统灵活性
- 工厂方法模式
- 标准化的对象创建方式
- 统一的数据访问入口
对后续技术的影响
CSLA.NET 带来的思考:
- 业务逻辑应该是领域模型的自然表达
- 架构应该支持灵活的部署模型
- 代码应该关注业务,而非技术细节
与现代架构的映射:
- CSLA → 领域驱动设计 (DDD)
- DataPortal → API Gateway + Microservices
- 移动对象 → 分布式对象模式
- 规则引擎 → 模式验证 + 策略模式
参考资料
- CSLA .NET 官方网站
- CSLA .NET GitHub 仓库
- 《C# 企业应用开发艺术:CSLA.NET 框架开发实战》
- Expert C# 2008 Business Objects - Rockford Lhotka