使用适配器将GridView与业务对象

分享于 

22分钟阅读

Web开发

  繁體

介绍

本文介绍了如何使用 GridView 控件和带有业务层的ObjectDataSource。 这个解决方案使用领域模型适配器,它是自然轻量级介质,用于消化任意复杂的业务数据,以便在显示器中使用。 本文将介绍 GridView 中数据的基本显示。更新。排序和删除。

背景

GridView 控件是. NET 2.0平台中非常丰富的web控件。 它的目的是以表格格式显示信息,允许可选的更新和删除。 另外,有几十个属性可以让开发人员对最终产品的样式和格式进行精细控制。 这些属性将不会在这里被覆盖,因为有许多好的来源。

为了使用 ObjectDataSource,必须从无状态和 IEnumerable 集合收集 Serializable 对象。 如果这些限制不适合直接在你的域模型上实现,那么就可以使用适配器。 成本是额外的规划和事件处理,以使领域对象与适配器保持最新。

适配器

适配器的目的是包含要在 GridView 中显示的域数据的可以序列化子集。 这个解决方案中的适配器满足三个接口。

IAdaptable 是一个接口,必须由你的域对象实现。 它的目的是帮助将各个适配器映射回它们来自的域对象。 在这个解决方案中,它只包含一个类型为long的单一标识字段。

publicinterface IAdaptable
{
 long ObjectID { get; } 
}

IAdaptor<T> where T : IAdaptable 必须由适配器实现的接口。 它是采用单一类型参数 T的通用接口,它是适应的域类型。 它定义的方法是 void FillFromObject(T obj),它用域数据填充适配器, void FillFromExternalComponent(params object[] updates) 使用外部数据填充适配器,来自更新的GridView 控件的比如,以及 void FillObject(ref T obj) 使用适配器中的数据更新对象。 它还包含它自己的映射适配器到域对象的属性集,这在本例中只是一个单一的身份字段。

publicinterface IAdaptor<T> where T : IAdaptable
{
 long ObjectID { get; } 
 void FillFromObject(T obj);
 void UpdateObject(ref T obj);
 void FillFromExternalComponent(params object[] updates);
}

最后,IAdaptorCollection<U,T> 是由一组适配器实现的接口。 类型参数 T 是适应上述的域类型,类型 U 是它的适配器 U: IAdaptor<T>。 根据你的需要,这里有许多可以能的方法选择,这个示例遵循 List 接口的精神。

publicinterface IAdaptorCollection<T,U> where T : IAdaptor<U> where U : IAdaptable
{
 ICollection GetObjects(List<T> collection);
 void AppendObject(List<T> collection, T obj);
 void AppendObjects(List<T> collection, IEnumerable<T> obj);
 void InsertObject(List<T> collection, int pos, T obj);
 void InsertObjects(List<T> collection, int pos, 
 IEnumerable<T> obj);
 void UpdateObject(List<T> collection, int pos, 
 params object[] updates);
 void DeleteObject(List<T> collection, int pos);
 T GetObjectByPosition(List<T> collection, int pos);
 int GetPositionByID(List<T> collection, long id);
} 

IAdaptorCollection 实现本身必须是无状态的,但是它可以将方法调用参数绑定到会话状态。 它必须是无状态的,因为我们将将它的绑定到与网页上的所有它的他控件相似的ObjectDataSource: 它在每个 postback 上被创建和销毁。 示例中,适配器存储在会话状态的通用 List 中,所有 IAdaptorCollection 方法都有绑定到该列表的参数。

适配器类的实现相对简单,并在示例代码中提供。 对 IAdaptorCollection 实现的其他限制包括

  • 默认值,无arugument构造函数。
  • ObjectDataSource 使用的任何方法都不能是 static。
  • 适配器应该由单个方法调用返回,并且应该通过 public 属性公开它们的数据。

示例业务层

这里的示例业务层是一个简单的采购订单系统,由客户。采购订单和采购项目组成。 本示例中的GridView 控件用于以列表格式显示购买项目,包括编辑/删除按钮。项编号。 只有数量字段才可以更新。 关于示例业务层的更多细节可以在示例代码中的注释中找到。 ASP.NET 使用反射将适配器属性绑定到 ObjectDataSource 数据方法中的参数。 对于该示例,适配器类为

[Serializable]publicclass PurchaseItemAdaptor : Adaptor<IPurchaseItem>
 {
 privatelong itemNumber;
 publiclong ItemNumber { get { return itemNumber; } set { itemNumber = 
 value; } }
 privatestring description;
 publicstring Description { get { return description; } set { 
 description = value; } }
 privateint quantity;
 publicint Quantity { get { return quantity; } set { 
 quantity = value; } }
 privatedecimal cost;
 publicdecimal Cost { get { return cost; } set { cost = value; } }
 publicoverridevoid FillFromExternalComponent(params object[] updates)
 {
 quantity = (int)updates[0];
 }
 publicoverridevoid FillFromObject(IPurchaseItem obj)
 {
 base.FillFromObject(obj); 
 itemNumber = obj.InventoryNumber;
 description = obj.ItemDescription;
 quantity = obj.Quantity;
 cost = obj.TotalCost();
 }
 publicoverridevoid UpdateObject(ref IPurchaseItem obj)
 {
 obj.Quantity = quantity;
 }
 }

publicclass PurchaseItemAdaptorCollection : 
 AdaptorCollection<PurchaseItemAdaptor, IPurchaseItem>
{
 publicvoid UpdatePurchaseItem(List<PurchaseItemAdaptor> collection, 
 int pos, int quantity)
 {
 UpdateObject(collection, pos, quantity);
 }
} 

方法中第一个参数是从会话状态到适配器的通用接口,第二个参数是更新适配器的行号,第三个参数是来自 GridView 控件的update值。

这里代码应该与域业务对象或者其他库位于相同的库中。 我更喜欢将适配器放在 App_Code 中,因为适配器的编译错误,因这里适配器类型将不可以用。

ObjectDataSource

在使用 Visual Studio 创建 GridView 控件时,将指定一个 DataSource。 选择 ObjectDataSource,并将它的配置为指向 PurchaseItemAdaptorCollection,以 GetObjects 方法作为 Select 方法。 下一个屏幕应该让你选择在方法调用中绑定参数。 选择 GetObjects() 方法的集合参数,选择一个会话字段并选取一个 NAME。 在本例中,我使用" GridViewOrderItems"。 后面的代码将负责将 PurchaseItemAdaptors的List 放入这个会话域中。

Screenshot - GridViewBusinessLayer1.gif

在使用GUI完成语法之后,我更愿意直接在XML中执行它的他工作。 下面是 ObjectDataSource 其余部分的XML。 受影响行的行数也将保存在会话字段中。

<asp:ObjectDataSource ID="OrderItemDataSource" runat="server" 
 DeleteMethod="DeleteObject" SelectMethod="GetObjects" 
 TypeName="ExampleBusinessLayer.PurchaseItemAdaptorCollection" UpdateMethod="UpdatePurchaseItem"> <UpdateParameters>
 <asp:SessionParameter Name="collection" 
 SessionField="GridViewOrderItems" Type="Object"/>
 <asp:SessionParameter Name="pos" 
 SessionField="GVOrderItems_RowUpdating" Type="Int32"/>
 <asp:Parameter Name="quantity" Type="Int32"/>
 </UpdateParameters> <SelectParameters>
 <asp:SessionParameter Name="collection" 
 SessionField="GridViewOrderItems" Type="Object"/>
 </SelectParameters> <DeleteParameters>
 <asp:SessionParameter Name="collection" 
 SessionField="GridViewOrderItems" Type="Object"/>
 <asp:SessionParameter Name="pos" 
 SessionField="GVOrderItems_RowDeleting" Type="Int32"/>
 </DeleteParameters></asp:ObjectDataSource>

解决方案使用由 GridView 控件发出的事件来同步 GridView 中的数据和业务层中的数据,以便更新和删除数据。

<asp:GridView ID="OrderItems" runat="server" AllowPaging="True" 
 OnRowDeleting="HandleRowDeleting" OnRowDeleted="HandleRowDeleted" 
 OnRowUpdating="HandleRowUpdating" OnRowUpdated="HandleRowUpdated" 
 AutoGenerateColumns="False" DataSourceID="OrderItemDataSource" 
 PageSize="2"> <Columns>
 <asp:CommandField ShowDeleteButton="True" ShowEditButton="True"/>
 <asp:BoundField HeaderText="Item No." ReadOnly="True" 
 DataField="ItemNumber"/>
 <asp:BoundField HeaderText="Item Description" ReadOnly="True" 
 DataField="Description"/>
 <asp:BoundField HeaderText="Quantity" DataField="Quantity"/>
 <asp:BoundField HeaderText="Cost" ReadOnly="True" 
 DataField="Cost"/>
 </Columns></asp:GridView>

更新的基本 Pattern 是在更新之前设置行位置参数,并在更新后同步域对象。 这个 Pattern的原因是在更新前,行号只在事件参数中可以用,但是适配器在操作之前没有受到影响。 下面的代码显示如下。 ( 请注意," GridViewUpdateEventArgs"和" GridViewUpdatedEventArgs"是不同类型。

protectedvoid HandleRowUpdating(object sender, GridViewUpdateEventArgs args)
{
 // Fires before the update: set the affected rowint globalRowIndex = (OrderItems.PageIndex * OrderItems.PageSize) + 
 args.RowIndex;
 Session["GVOrderItems_RowUpdating"] = globalRowIndex;
}protectedvoid HandleRowUpdated(object sender, GridViewUpdatedEventArgs args)
{
 // Fires after the update: get the affected rowint globalRowIndex = (int)Session["GVOrderItems_RowUpdating"];
 // Find the corresponding domain object PurchaseItemAdaptorCollection helper = new PurchaseItemAdaptorCollection();
 List<PurchaseItemAdaptor> orderItems =
 (List<PurchaseItemAdaptor>)Session["GridViewOrderItems"];
 PurchaseItemAdaptor adaptor = helper.GetObjectByPosition(orderItems, 
 globalRowIndex);
 long domainID = adaptor.ObjectID;
 // Update the domain object IPurchaseOrder po = (IPurchaseOrder)Session["PurchaseOrder"];
 IPurchaseItem item = po.FindByID(domainID);
 adaptor.UpdateObject(ref item);
 // (Optional) Refresh the adaptor to catch any quantities // that changed because of business rules adaptor.FillFromObject(item);
 // (Optional) Resort // HandleSort(sender, args);// Clean up the Session state Session.Remove("GVOrderItems_RowUpdating");
}

请注意,适配器刷新域对象以防业务规则强制更改,这将在业务对象中正确封装。 在我们的示例中,总成本是数量的函数,这是在域对象中计算的。 如果没有这里代码,则总成本不会随数量的变化而更新,直到随后的postback。 如果业务规则永远不会导致这种更改,则可以省略。 类似地,搜索结果可能需要在更新之后进行。

除了工作完全在第一个 GridView 事件上完成之外,行删除的代码是相似的。 在 delete 事件之后,受影响的适配器已经消失,因此对域对象的映射丢失。

排序

应该注意,ObjectDataSource 不直接支持排序,SQLDataSource 也不直接支持排序。 相反,你可以将排序方法绑定到 GridView 或者其他控件以处理排序。 为了指示排序,我通常更喜欢使用列选择的下拉 List 加上一个复选按钮来表示正向/反向排序。 在列标题中使用链接来完成同样的操作也很常见,但是我总是找到一个下拉列表,用户马上就可以看到一个列,该列表被排序。

在这里解决方案中使用的排序方法使用绑定到列选择器下拉列表的OnSelectedIndexChanged 事件和反向排序复选框的OnCheckChanged 事件的HandleSort 方法。 排序后,需要调用重新绑定 GridView。 基于代理模板的分类算法,给出了一种基于代理模板的分类算法。 ( 这是在Sestoft和汉森的类似实现中产生的;参见下面的参考资料

publicdelegateint SortCompare&lT>(T p1, T p2);

列 List 中的每个选项都获得它自己的比较函数,并且反过来处理构建代理的反向排序。 有了"项目说明"的例子

protectedstaticint Compare1(PurchaseItemAdaptor p1, PurchaseItemAdaptor p2)
{
 returnstring.Compare(p1.Description, p2.Description);
}
SortCompare<PurchaseItemAdaptor>[] comparers = new 
 SortCompare<PurchaseItemAdaptor>[] 
{ Compare0, Compare1, Compare2, Compare3 };protectedvoid HandleSort(object sender, EventArgs e)
{
 int sign = cbReverse.Checked?-1:1;
 SortCompare<PurchaseItemAdaptor> sc = delegate(
 PurchaseItemAdaptor p1, PurchaseItemAdaptor p2)
 {
 return (sign * comparers[ddlSortingChooser.SelectedIndex](p1, p2));
 };
 PurchaseItemAdaptor[] items = ((
 List<PurchaseItemAdaptor>)Session["GridViewOrderItems"]).ToArray();
 QuickSort<PurchaseItemAdaptor>.Sort(items, sc);
 Session["GridViewOrderItems"] = new List<PurchaseItemAdaptor>(items);
 OrderItems.DataBind();
}

在任何情况下,确保适配器本身是按会话状态排序的,否则使用行号的后续 GridView 操作可以能不会。

使用代码

要在 Visual Studio 中运行代码,解压附带的文件并打开 GridViewExample.sln 文件。 GridView 位于名为 OrderItems.ascx的用户控件中。 打开 GridViewExample.aspx 页面并打开 !

Screenshot - GridViewBusinessLayer2.gif

根据你对更新处理程序的编码方式,可能会自动在更新时自动执行行。 在所提供的示例中,这是关闭的,因为用户可能会导致行跳转到另一页。

Points of Interest

在这一点上花一点时间,回顾一下本例中所做的一些设计决策是值得的。

  • 在会话状态中存储适配器是示例中最有趣的设计决策。 通过在会话状态存储业务数据,处理适配器的方法必须限定为可以访问会话状态或者使会话状态通过方法参数传递给会话状态。 对于我来说,这是最大的概念,因为在会话状态下使用 ObjectDataSource 而不是无状态集合类更自然。 因为 ObjectDataSource 在每个 postback 上都被破坏和重建,而且所有其他控件都不会在大多数情况下发生。
  • 适配器封装了关于如何将 GridView 更新映射到特定适配器属性的信息,并将它的封装在两个紧密相关的类中。 GridViewUpdatedEventArgs 还包含有关更改数据的信息。 原则上,你可以使用该信息来完成更新。 但是逻辑已经封装在适配器类中,因这里适配器应该使用它实际进行更新。
  • 更新期间,ObjectDataSource 不使用行号,而是使用指定的UpdateParameters 来选择更新适配器的方法。 如果没有指定,ObjectDataSource 将使用参数名绑定到适配器的命名属性,但是,除非 GridView 中的相应参数可以更新并可见,否则它不会绑定到 GridView。 一般来说,可以能有一个方法,但在这个解决方案中,可以能意味着可以更新的标识字段。 为了找到正确的适配器,需要在 GridView 中使用行号,只需在 GridView 中使用。
  • 另一种方法是重新创建适配器并在每个 postback 上重新绑定 GridView。 这是一种可以保证 GridView 与业务层中的数据同步的方法,但它可能会导致大量不必要的重新绑定。 对于不属于 GridView 本身的事件,我认为这是正确的路由。 对于起源于 GridView的更新和 delete,可以直接处理由用户操作引发的事件并直接修复域数据。
  • 通过在 ObjectDataSource 上设置 SortParameterName 属性,可以使用 GridViewObjectDataSource 进行排序。 当调用指定的选择方法 GetObjects() 时,ObjectDataSource 会将给定 NAME的参数附加到参数 List 中。 例如,如果选择对成本列进行排序以保持它的他所有内容相同,则将 SortParameterName="Cost" 设置为 ObjectDataSourceGetObject(List<PurchaseItemAdaptor> collection, object Cost) 在这个额外的参数( 在 Visual Studio 调试器中验证) 中没有被传递,但它看起来只是一种在运行时选择正确方法的方法。

结束语

当我首先考虑使用 GridView 时,我已经使用域对象和数据库方法完成了一个业务层。 虽然我被 GridView的所有特性所吸引,但是我首先尝试将它集成到代码中。 在几个小时的时间和 GridView 一起玩,我最后花了几个小时的时间来编写用户控件。 上述实现是许多其他后续试验和错误的结果。

这个解决方案使用轻量级适配器来代替域对象来实现 ObjectDataSource。 可以能适合某些业务层直接采用连接到 ObjectDataSource 所需的序列化和无状态需求。 使用领域模型适配器的好处是,它们可以自然地在你的体系结构中的许多地方。 例如使用适配器提供 GridView,在域模型和外部数据库和远程服务之间交换信息。

最后,我认为 GridView 是业务层的一个很好的选择。 上面的解决方案需要大量的规划,但它实际上不是很多代码,而且代码大多是样板。 另外,在使用 GridView 时,代码整体有良好的组织;比没有它更好。

引用

  • C# 精确,2nd 米,由 Peter Sestoft和Henrik我。 汉森,麻省理工学院出版社( 2004 )
  • Pro,Pro,APress,APress,APress,APress,APress,APress
  • 更多信息在我的博客上,。

对象  GRID  Using  Gridview  ADA  总线  
相关文章