Data source controls need parameter values to specify which data needs to be selected, or how and what data should be modified. Typically the page contains some UI that defines parameters that must be used as part of the select operation, while data-bound controls provide parameter values for insert, update and delete operations. However, it is equally likely to have a mixture of the two happen in either case. In part 1, the data source control exposed a ZipCode property that could be set declaratively, or in code in response to a user action. Parameters were designed to accomplish this scenario in a declarative (and extensible) manner.
Introduction
The Parameter base class represents a generic parameter. Whidbey provides things like QueryStringParameter to pull data from a query string argument into the data source. Another especially useful parameter is ControlParameter that allows pulling data from any control property. You can define your own parameter type if the built-in types do not satisfy your needs. Doing so will allow you to keep your pages free of glue code that is instead neatly packaged in the parameter implementation.
In addition to pulling values from different sources, parameters can track changes to values, notify the owner data source of changes, which in turn raises data source change notifications, that eventually trigger data-binding in data-bound controls. This in short is the magic behind declarative master detail scenarios, when using ControlParameters.
The Sample
I'll now add parameter functionality to the WeatherDataSource as I build it further.
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();
}
}
}
ASP.NET provides a ParameterCollection that you can use pretty much as-is. It includes both change tracking, and state management functionality. You simply need to call on its API appropriately to incorporate these functionalities in addition to exposing the collection as a property off your control. The key points to note in the code above are:
- The data source control exposes a property of type ParameterCollection to allow the developer to add a parameter representing the zip code value to be used. If a parameter has been set, it is used, otherwise, the ZipCode property value is used.
- The control overrides state management related methods to pull in state management capability built into ParameterCollection.
- The control uses the new LoadComplete event of the page lifecycle to update parameter values, which it registers for by overriding OnInit. The data source control also registers for the ParametersChanged event raised by the ParameterCollection if any parameters have changed values during initialization, postback processing, or page code (which has all happened by the time LoadComplete is raised). Like before, when the ZipCode property was set, a change notification is raised, that indicates to the data-bound control that it will need to perform data-binding again (which then happens during PreRender).
- The need to participate in the lifecycle is one of the reasons data sources are implemented as controls, albeit non-visual controls. Another reason is so that data-bound controls can use FindControl using their DataSourceID property, and reap the benefits of INamingContainer-based hierarchical name scopes (which enables implementing nesting data scenarios by placing a data source control within a template, and having it be repeated per row). The fact that data sources are controls has been a point of debate and disagreement - hopefully this explains some of the reasoning behind this.
The DataSourceView now simply needs to call GetSelectedZipCode instead of directly using the ZipCode property. I've also changed the data source view code to return null if a ZipCode is not selected (rather than throw an exception), which causes the data-bound control to show its "empty" view. This is mostly a convention, though in retrospect, I think this should really be an integral aspect of data source control semantics.
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();
}
}
That's pretty much it. Here is the updated usage sample, which now declarative.
Zip Code: <asp:TextBox runat="server" id="zipCodeTextBox" />
<asp:Button runat="server" Text="Lookup" />
<hr />
<asp:FormView runat="server" DataSourceID="weatherDS">
<ItemTemplate>
<asp:Label runat="server"
Text='<%# Eval("Temperature", "The current temperature is {0}.") %>' />
</ItemTemplate>
</asp:FormView>
<nk:WeatherDataSource runat="server" id="weatherDS">
<Parameters>
<asp:ControlParameter Name="ZipCode" ControlID="zipCodeTextBox" />
</Parameters>
</nk:WeatherDataSource>
Notice that I didn't specify "Text" as the property to lookup on the ControlParameter tag in markup. ControlParameter automatically figures out the default property to work against when one is not specified. It does so by inspecting the ControlValueAttribute on the class. TextBox defines "Text" as the property that contains its "control value." This concept is applicable to a number of controls besides the traditional input controls. For example, GridView exposes its SelectedDataKey as its "control value." This is a new thing that control developers should start thinking about, so as to enable better integration with ControlParameter.
Posted on Wednesday, 7/6/2005 @ 12:16 AM
| #
ASP.NET