数据源控件需要使用参数值来指定需要选择哪些数据,或者指定如何修改数据以及修改什么数据。通常情况下,页面包含一些 UI,以定义那些必须在选择操作中使用的参数,而数据绑定控件提供了参数值来进行插入、更新和删除操作。但是,在任意一种情况下,都可能同时出现两种现象。在第 1 部分中,数据源控件揭示了 ZipCode 属性,该属性可进行声明性设置,或者以编码来设置以响应用户操作。参数被设计为以声明性(且可扩展)的方式来完成此方案。 
   
  引言 
   
  Parameter 基类代表一个通用参数。Microsoft Visual Studio 2005 提供了诸如 QueryStringParameter 之类的参数,以便将数据从查询字符串参数请求到数据源中。另一个非常有用的参数是 ControlParameter,该参数允许从任一控件属性中请求数据。如果内置参数类型不能满足您的要求,则您可以定义自己的参数类型。这样您就可以使页面与粘接代码不相关,而是将该代码整齐地封装在参数实现中。 
   
  除了从不同的源中请求值,这些参数还可以跟踪值的更改情况,并通知这些更改的所属数据源,进而引发数据源更改通知,最终在数据绑定控件中触发数据绑定操作。简而言之,这就是使用 ControlParameters 时,主要的声明性详细方案所依据的原理。 
   
  示例 
   
  在此将向 WeatherDataSource 添加参数功能,然后进一步阐述。
  
  public class WeatherDataSource : DataSourceControl {
  
   public static readonly string ZipCodeParameterName = "ZipCode";
   ...
  
   private ParameterCollection _parameters;
  
   private ParameterCollection Parameters {
    get {
     if (_parameters == null) {
      _parameters = new ParameterCollection();
      _parameters.ParametersChanged += new EventHandler(this.OnParametersChanged);
      if (IsTrackingViewState) {
       ((IStateManager)_parameters).TrackViewState();
      }
     }
     return _parameters;
    }
   }
   ...
  
   public string GetSelectedZipCode() {
    if (_parameters != null) {
     Parameter zipCodeParameter = _parameters[ZipCodeParameterName];
     if (zipCodeParameter != null) {
      IOrderedDictionary parameterValues = _parameters.GetValues(Context, this);
      return (string)parameterValues[zipCodeParameter.Name];
     }
    }
  
    return ZipCode;
   }
  
   protected override void LoadViewState(object state) {
    object baseState = null;
  
    if (state != null) {
     Pair p = (Pair)state;
     baseState = p.First;
  
     if (p.Second != null) {
      ((IStateManager)Parameters).LoadViewState(p.Second);
     }
    }
    base.LoadViewState(baseState);
   }
  
   protected override void OnInit(EventArgs e) {
    Page.LoadComplete += new EventHandler(this.OnPageLoadComplete);
   }
  
   private void OnPageLoadComplete(object sender, EventArgs e) {
    if (_parameters != null) {
     _parameters.UpdateValues(Context, this);
    }
   }
  
   private void OnParametersChanged(object sender, EventArgs e) {
    CurrentConditionsView.RaiseChangedEvent();
   }
  
   protected override object SaveViewState() {
    object baseState = base.SaveViewState();
    object parameterState = null;
  
    if (_parameters != null) {
     parameterState = ((IStateManager)_parameters).SaveViewState();
    }
  
    if ((baseState != null) || (parameterState != null)) {
     return new Pair(baseState, parameterState);
    }
    return null;
   }
  
   protected override void TrackViewState() {
    base.TrackViewState();
    if (_parameters != null) {
     ((IStateManager)_parameters).TrackViewState();
    }
   }
  } 
   
  Microsoft ASP.net 提供了 ParameterCollection,您可以完全按原样使用该集合。它同时包含更改跟踪和状态管理功能。您只需相应地调用该集合的 API 来合并这些功能,另外还可以在控件外将该集合揭示为属性。在上述代码中,需要注意的关键点为: 
   
  ·该数据源控件揭示了一个 ParameterCollection 类型的属性,以使开发人员能够添加表示要使用的邮政编码值的参数。如果已经设置了参数,则使用该参数;否则,将使用 ZipCode 属性值。 
   
  ·该控件替代了与状态管理相关的方法,以请求 ParameterCollection 中内置的状态管理功能。 
   
  ·该控件使用页面生命周期的新 LoadComplete 事件来更新参数值,它通过替代 OnInit 来注册这些值。如果在初始化、回发处理或页面编码(当引发 LoadComplete 时,全部都会发生)期间更改了任何参数的值,则该数据源控件还会注册 ParameterCollection 所引发的 ParametersChanged 事件。与上述情况一样,如果设置了 ZipCode 属性,将会引发更改通知,向数据绑定控件指明它需要再次执行数据绑定操作(随后在 PreRender 期间将会发生此情况)。 
   
  ·需要参与生命周期是数据源作为控件(即使是非可视控件)来实现的一个原因。另一个原因是为了使数据绑定控件能够通过使用其 DataSourceID 属性来使用 FindControl,并能够获得基于 INamingContainer 的分层名称领域的益处(这样就能够实现嵌套数据方案,方法是在模板内放置一个数据源控件,并使其在每行中重复一次)。数据源是控件这一事实早已是争论的焦点 - 但愿这能够说明此问题的一些论据。 
   
  在此 DataSourceView 只需调用 GetSelectedZipCode,而不是直接使用 ZipCode 属性。此外,还更改了数据源视图代码,以便在未选中 ZipCode 的情况下返回 null(而不是抛出异常),这会导致数据绑定控件显示“空”视图。这在通常情况下是一个惯例,但是回顾来看,这应该成为数据源控件语义的一个不可获缺的方面。
  
  private sealed class WeatherDataSourceView : DataSourceView {
   ...
  
   internal Weather GetWeather() {
    string zipCode = _owner.GetSelectedZipCode();
    if (zipCode.Length == 0) {
     return null;
    }
  
    WeatherService weatherService = new WeatherService(zipCode);
    return weatherService.GetWeather();
   }
  } 
   
  完整的代码就是这个样子。以下是经过更新的用法示例,该示例现在是声明性的。
  
  Zip Code: <asp:TextBox runat="server" id="zipCodeTextBox" />
  <asp:Button runat="server" Text="查找" />
  <hr />
  
  <asp:FormView runat="server" DataSourceID="weatherDS">
  <ItemTemplate>
  <asp:Label runat="server"
  Text='<%# Eval("Temperature",
  "当前温度是 {0}。") %>' />
  </ItemTemplate>
  </asp:FormView>
  <nk:WeatherDataSource runat="server" id="weatherDS">
  <Parameters>
  <asp:ControlParameter Name="ZipCode" ControlID="zipCodeTextBox" />
  </Parameters>
  </nk:WeatherDataSource> 
   
  请注意,在标记中并未指定 Text 作为在 ControlParameter 标记上查找的属性。ControlParameter 自动计算出了在未指定属性的情况下要使用的默认属性。它通过检查该类中的 ControlValueAttribute 来实现此目的。TextBox 将 Text 定义为包含其“控件值”的属性。除了传统输入控件之外,此概念还适用于多个控件。例如,GridView 将其 SelectedDataKey 揭示为“控件值”。这是一个新事物,控件开发人员从此以后应该予以考虑,以便与 ControlParameter 更好地进行集成。