Apply SOA Design Patterns with WCF (5) WCF Based ASP.NET DataSouce Control (基于WCF的数据源控件)
Original (原创) by Teddy’s Knowledge Base
Content (目录)
(1) WCF Configuration Centralization (WCF配置集中管理)
(2) WCF Automatic Deployment (WCF自动化部署)
(3) WCF Automatic Service Locating (WCF自动化服务定位)
(4) WCF Database Paging & Sorting (WCF数据库分页和排序)
(5) WCF Based ASP.NET DataSouce Control (基于WCF的数据源控件)
摘要
本文介绍如何实现一个基于WCF的ASP.NET数据源控件,从而使得跨WCF通信的数据库CRUD,尤其是复杂的分页排序更简单。
正文
我们需要基于WCF的ASP.NET数据源控件吗?
ASP.NET的数据源设计是的 ASP.NET页面上的数据绑定十分简单,但是.NET Framework到目前为止内置提供的DataSource控件,对WCF的支持都不是很方便。SqlDataSource和LinqDataSource暴露了太多SQL的细节,仅支持SQL Server数据库,并且完全不支持WCF;ObjectDataSOource则太通用了,以至于仅仅为了实现一个很简单的数据库分页排序功能也需要写很多代码。因此,如果我们基于WCF和ASP.NET来实现SOA,那么,一个基于WCF的ASP.NET数据源控件绝对值得去设计。
我们手头都有哪些武器了?
实现
首先,我们可以定义一个为所有的查询共享的WCF服务契约。下面的代码是IQueryService服务契约:
1 [ServiceContract]
2 public interface IQueryService
3 {
4 [OperationContract]
5 DataTable Select(Criteria criteria);
6 [OperationContract]
7 int SelectCount(Criteria criteria);
8 [OperationContract]
9 int Update(Criteria criteria, DataTable modifiedTable, ConflictOption conflictDetection);
10 }
然后,我们可以为上面这个服务契约,定义一个基于查询对象的默认的实现:
1 public sealed class QueryService : IQueryService
2 {
3 public DataTable Select(Criteria criteria)
4 {
5 if (criteria == null)
6 throw new ArgumentNullException("criteria");
7
8 using (var adapter = new QueryCommandFactory(criteria).GetQueryDataAdapter())
9 {
10 var connection = adapter.SelectCommand.Connection;
11 try
12 {
13 if (connection.State != ConnectionState.Open)
14 connection.Open();
15 var table = new DataTable(criteria._tableName);
16 adapter.Fill(table);
17 return table;
18 }
19 finally
20 {
21 if (connection.State != ConnectionState.Closed)
22 connection.Close();
23 connection.Dispose();
24 }
25 }
26 }
27
28 public int SelectCount(Criteria criteria)
29 {
30 if (criteria == null)
31 throw new ArgumentNullException("criteria");
32
33 using (var cmd = new QueryCommandFactory(criteria).GetCountCommand())
34 {
35 var connection = cmd.Connection;
36 try
37 {
38 if (connection.State != ConnectionState.Open)
39 connection.Open();
40 return Convert.ToInt32(cmd.ExecuteScalar());
41 }
42 finally
43 {
44 if (connection.State != ConnectionState.Closed)
45 connection.Close();
46 connection.Dispose();
47 }
48 }
49 }
50
51 public int Update(Criteria criteria, DataTable modifiedTable, ConflictOption conflictDetection)
52 {
53 if (criteria == null)
54 throw new ArgumentNullException("criteria");
55
56 using (var adapter = new QueryCommandFactory(criteria).GetUpdatableQueryDataAdapter(conflictDetection))
57 {
58 var connection = adapter.SelectCommand.Connection;
59 try
60 {
61 if (connection.State != ConnectionState.Open)
62 connection.Open();
63 return adapter.Update(modifiedTable);
64 }
65 finally
66 {
67 if (connection.State != ConnectionState.Closed)
68 connection.Close();
69 connection.Dispose();
70 }
71 }
72 }
73 }
最后,只要通过能和第三方服务定位器整合的ServiceManager类,参见文章(3),如果我们能实现一个带一个Criteria属性作为查询的输入的数据源控件,我们就能很容易的基于能自动化的定位的IQueryService服务的实现CRUD。要实现一个自定义的自定义的数据源控件,可以参考.NET Framework中的SqlDataSource控件的实现,下面的代码是这个基于WCF的QueryDataSource控件的实现摘要:
1 public sealed class QueryDataSource : DataSourceControl
2 {
3 #region Private Fields
4
5 //…
6
7 #endregion
8
9 #region Protected Methods
10
11 protected override DataSourceView GetView(string viewName)
12 {
13 if (_view == null)
14 _view = new QueryDataSourceView(this);
15 return _view;
16 }
17
18 protected override void OnInit(System.EventArgs e)
19 {
20 base.OnInit(e);
21
22 if (HttpContext.Current == null)
23 return;
24 if (!UseLocalQueryService)
25 {
26 _locator = ServiceManager.GetServiceLocator(typeof(IQueryService));
27 _service = _locator.GetService<IQueryService>();
28 }
29 else
30 {
31 var serviceType = Type.GetType(_defaultQueryServiceImplType);
32 _service = (IQueryService)Activator.CreateInstance(serviceType);
33 if (_service == null)
34 throw new FileLoadException("Could not load assembly - NIntegrate.Query.Command.dll.");
35 }
36 }
37
38 #endregion
39
40 #region Public Properties
41
42 [Category("Data"), DefaultValue(false), Description("When the value of this property equals true, it always using NIntegrate.Query.Command.QueryService class as QueryService insteads of trying to get the IQueryService implementation instance from ServiceManager class.")]
43 public bool UseLocalQueryService { get; set; }
44
45 [Category("Data"), Description("Specify the criteria.")]
46 public Criteria Criteria
47 {
48 internal get
49 {
50 if (_criteria == null && EnableViewState
51 && ViewState["Criteria"] != null)
52 {
53 _criteria = QueryHelper.CriteriaDeserialize(
54 (string)ViewState["Criteria"]);
55 }
56
57 return _criteria;
58 }
59 set
60 {
61 if (value == null)
62 return;
63
64 _criteria = value;
65 if (EnableViewState)
66 ViewState["Criteria"] = QueryHelper.CriteriaSerialize(value.ToBaseCriteria());
67 }
68 }
69
70 //…
71
72 #endregion
73
74 #region Events
75
76 //…
77
78 #endregion
79
80 #region Dispose()
81
82 public override void Dispose()
83 {
84 Dispose(true);
85 GC.SuppressFinalize(this);
86 }
87
88 private bool disposed;
89
90 private void Dispose(bool disposing)
91 {
92 if (disposed) return;
93 if (disposing)
94 {
95 var dispose = _service as IDisposable;
96 if (dispose != null)
97 dispose.Dispose();
98 if (_locator != null)
99 _locator.Dispose();
100 }
101
102 disposed = true;
103 }
104
105 ~QueryDataSource()
106 {
107 Dispose(false);
108 }
109
110 #endregion
111 }
112
113 internal sealed class QueryDataSourceView : DataSourceView
114 {
115 #region Private Membes
116
117 //…
118
119 #endregion
120
121 #region Constructors
122
123 internal QueryDataSourceView(QueryDataSource owner)
124 : base(owner, "Default")
125 {
126 if (owner == null)
127 throw new ArgumentNullException("owner");
128
129 _owner = owner;
130 }
131
132 #endregion
133
134 #region Public Properties
135
136 public override bool CanInsert
137 {
138 get
139 {
140 return true;
141 }
142 }
143
144 public override bool CanUpdate
145 {
146 get
147 {
148 return true;
149 }
150 }
151
152 public override bool CanDelete
153 {
154 get
155 {
156 return true;
157 }
158 }
159
160 public override bool CanRetrieveTotalRowCount
161 {
162 get
163 {
164 return true;
165 }
166 }
167
168 public override bool CanPage
169 {
170 get
171 {
172 return true;
173 }
174 }
175
176 public override bool CanSort
177 {
178 get
179 {
180 return true;
181 }
182 }
183
184 #endregion
185
186 #region Protected Methods
187
188 protected override int ExecuteInsert(IDictionary values)
189 {
190 //…
191
192 var table = _owner._service.Select(criteria);
193 var row = table.NewRow();
194 var en = values.GetEnumerator();
195 while (en.MoveNext())
196 {
197 if (table.Columns.Contains(en.Key.ToString()))
198 row[en.Key.ToString()] = TransformType(table.Columns[en.Key.ToString()].DataType, en.Value);
199 }
200 table.Rows.Add(row);
201
202 var conflictDetection = ConflictOption.OverwriteChanges;
203 if (_owner.ConflictDetection == ConflictOptions.CompareAllValues)
204 {
205 conflictDetection = ConflictOption.CompareAllSearchableValues;
206 }
207
208 var affectedRows = _owner._service.Update(_owner.Criteria, table, conflictDetection);
209
210 //…
211 }
212
213 protected override int ExecuteUpdate(IDictionary keys, IDictionary values, IDictionary oldValues)
214 {
215 //…
216
217 var table = _owner._service.Select(criteria);
218 if (table == null || table.Rows.Count == 0)
219 throw new DataException("No row is matching specified key values.");
220 if (table.Rows.Count > 1)
221 throw new DataException("More than one rows are matching specified key values, please check the key columns setting.");
222 var row = table.Rows[0];
223 var conflictDetection = ConflictOption.OverwriteChanges;
224 if (_owner.ConflictDetection == ConflictOptions.CompareAllValues)
225 {
226 DetectDataRowConflicts(oldValues, row);
227 conflictDetection = ConflictOption.CompareAllSearchableValues;
228 }
229
230 var en = values.GetEnumerator();
231 while (en.MoveNext())
232 {
233 if (table.Columns.Contains(en.Key.ToString()))
234 row[en.Key.ToString()] = TransformType(table.Columns[en.Key.ToString()].DataType, en.Value);
235 }
236
237 var affectedRows = _owner._service.Update(_owner.Criteria, table, conflictDetection);
238
239 //…
240 }
241
242 protected override int ExecuteDelete(IDictionary keys, IDictionary oldValues)
243 {
244 //…
245
246 var table = _owner._service.Select(criteria);
247 if (table == null || table.Rows.Count == 0)
248 throw new DataException("No row is matching specified key values.");
249 if (table.Rows.Count > 1)
250 throw new DataException("More than one rows are matching specified key values, please check the key columns setting.");
251
252 var row = table.Rows[0];
253 var conflictDetection = ConflictOption.OverwriteChanges;
254 if (_owner.ConflictDetection == ConflictOptions.CompareAllValues)
255 {
256 DetectDataRowConflicts(oldValues, row);
257 conflictDetection = ConflictOption.CompareAllSearchableValues;
258 }
259
260 row.Delete();
261
262 var affectedRows = _owner._service.Update(_owner.Criteria, table, conflictDetection);
263 //…
264 }
265
266 protected override IEnumerable ExecuteSelect(DataSourceSelectArguments arguments)
267 {
268 //…
269
270 if (arguments != null && arguments != DataSourceSelectArguments.Empty)
271 {
272 //adjust criteria according to arguments
273 if (arguments.RetrieveTotalRowCount)
274 {
275 arguments.TotalRowCount = _owner._service.SelectCount(criteria);
276 _owner.LastTotalCount = arguments.TotalRowCount;
277 }
278 if (arguments.MaximumRows > 0)
279 criteria.MaxResults(arguments.MaximumRows);
280 if (arguments.StartRowIndex > 0)
281 criteria.SkipResults(arguments.StartRowIndex);
282 if (!string.IsNullOrEmpty(arguments.SortExpression))
283 {
284 if (_owner.AlwaysAppendDefaultSortBysWhenSorting)
285 InsertSortExpressinAtTopOfSortBys(arguments.SortExpression, criteria);
286 else
287 {
288 criteria._sortBys.Clear();
289 AppendSortExpression(criteria, arguments.SortExpression);
290 }
291 }
292 }
293
294 return new DataView(_owner._service.Select(criteria));
295 }
296
297 #endregion
298 }
参考
(1) SOA Design Pattern Catalog: http://www.soapatterns.org///我是结尾符,待续…