Aaron Goldenthal

Sometimes ASP.NET is Rocket Science

Creating Custom Data Source Parameters

I’m a fan of using the data source controls to simplify data access.  While there’s a reasonable set of Parameters that come with the framework, it’s easy to quickly get to a point where you want functionality that doesn’t automatically come from one of these parameters.  You can always use the Parameter and update the value programmatically, but you can end up with a lot of logic repeated in various Inserting/Updating/Deleting events.  Luckily, creating a custom data source Parameter to encapsulate this logic is very easy. There’s a good article on 4Guys on Creating Custom Parameter Controls, which is where I learned this technique.  I’m going to go through a couple of examples quickly, feel free to refer to the 4Guys article for a more thorough discussion. To implement a custom Parameter control you essentially need to do three things: Provide an override for the Evaluate method.  This method returns the value of the Parameter. Provide an override for the Clone method.  This method is used by the Visual Studio designer to make a deep copy of the Parameter. Add any properties required to evaluate your Parameter. To illustrate this, I’m going to walk through two custom Parameters that I use frequently: IdentityParameter and AppSettingParameter. IdentityParameter The IdentityParameter returns the name of the logged in user, specifically from HttpContext.Current.User.Identity.Name.  We’ll start with a basic skeleton for the IdentityParameter, which derives from Parameter. 1: public class IdentityParameter : Parameter 2: { 3: public IdentityParameter() 4: { 5: } 6: } For the IdentityParameter, I wanted the option to either include or exclude the domain name of the user since I have different applications that use both options.  So, we’ll add one property to the Parameter. 1: [DefaultValue(true)] 2: public bool IncludeDomain 3: { 4: get 5: { 6: object o = ViewState["IncludeDomain"]; 7: if (o == null) 8: return true; 9: else 10: return Convert.ToBoolean(o); 11: } 12: set 13: { 14: ViewState["IncludeDomain"] = value; 15: } 16: } All of this is pretty standard for a control property – the value is stored in ViewState so it persists between posts, and a default of true is specified. Now, on to evaluating the value of the control.  The Evaluate method is passed two parameters – a reference to the current HttpContext, and a reference to the control that the Parameter object belongs to (SqlDataSource, ObjectDataSource, etc). 1: protected override object Evaluate(HttpContext context, Control control) 2: { 3: if ((context == null) || (context.User == null)) 4: return null; 5: else 6: { 7: if (IncludeDomain) 8: return context.User.Identity.Name; 9: else 10: { 11: string name = context.User.Identity.Name; 12: return name.Substring(name.IndexOf("\\") + 1); 13: } 14: } 15: } We start by checking that the HttpContext and User are not null since these are required to obtain the Identity.  Then we simply return the name value as is, or with the domain stripped off, based on the value of the IncludeDomain property. Finally, we need to override the Clone method.  With that, we’ll add a second constructor that is passed the current parameter. 1: protected IdentityParameter(IdentityParameter original) : base(original) 2: { 3: IncludeDomain = original.IncludeDomain; 4: } 5:  6: protected override Parameter Clone() 7: { 8: return new IdentityParameter(this); 9: } 10:  The new constructor simply calls the base constructor and copies any new properties (in this case IncludeDomain).  The Clone method returns a new IdentityParameter copied from the current parameter. That’s all there is to it.  The IdentityParameter can now be added to any Parameter collection (with intellisense). 1: <InsertParameters> 2: <adg:IdentityParameter Name="UserID" Type="String" /> 3: </InsertParameters> AppSettingParameter The AppSettingParameter returns the value of the AppSettings collection with the specified key.  Again we’ll start with a basic skeleton for the AppSettingParameter, which derives from Parameter. 1: public class AppSettingParameter : Parameter 2: { 3: public AppSettingParameter() 4: { 5: } 6: } For the AppSettingParameter, a key is required to access the appropriate value from the AppSettings collection, so we’ll add one property to the Parameter. 1: public string Key 2: { 3: get 4: { 5: return ViewState["Key"] as string; 6: } 7: set 8: { 9: ViewState["Key"] = value; 10: } 11: } Again, all of this is pretty standard for a control property, so we’ll move on the Evaluate method. 1: protected override object Evaluate(HttpContext context, Control control) 2: { 3: if (String.IsNullOrEmpty(this.Key)) 4: { 5: throw new InvalidOperationException("A Key must be specified for an AppSettingParameter."); 6: } 7: else 8: { 9: return WebConfigurationManager.AppSettings[this.Key]; 10: } 11: } Since we’re not using the HttpContext, we don’t need to check that it’s null, we simply check that a Key is specified and return the value from AppSettings. Finally, we need to override the Clone method.  Again, we’ll add a second constructor that is passed the current parameter. 1: protected AppSettingParameter(AppSettingParameter original) : base(original) 2: { 3: Key = original.Key; 4: } 5:  6: protected override Parameter Clone() 7: { 8: return new AppSettingParameter(this); 9: } The AppSettingParameter can now be added to any Parameter collection (with intellisense). 1: <InsertParameters> 2: <adg:AppSettingParameter Name="Color" Type="String" Key="Color" /> 3: </InsertParameters> The download below includes the complete source code as well as an example using both of these parameters. Download sample website: CustomParameters.zip (171 kb)

Creating a Data Control Field – The CounterField

I’ve created a lot of GridViews to display a lot of data.  In many cases they’re simply displaying tabular data and they include one column with a counter cell, typically created with markup like: 1: <asp:TemplateField HeaderText="#"> 2: <ItemTemplate><%# Container.DataItemIndex + 1 %></ItemTemplate> 3: </asp:TemplateField> While this is not overly complicated, rather than create a field like this yet another time, I created a custom data control field to provide that functionality – the CounterField.  Data control fields can be used by both the GridView and the DetailsView (probably why they’re called fields, not columns).  While the CounterField is a fairly simple field, it’s a good introduction to how to create a custom field. The .Net Framework comes with a handful of data control fields, including the BoundField, ButtonField, CheckBoxField, CommandField, HyperlinkField, ImageField, and TemplateField.  All of these are ultimately derived from one base class – DataControlField (ButtonField and CommandField actually derive from ButtonBaseField, which itself derives from DataControlField and exposes several properties used by fields with buttons).  DataControlField includes many of the properties you’re familiar with that are available in all of the various fields – Header/FooterText, HeaderImageUrl, Control/Footer/Header/ItemStyle, and InsertVisible.  As you’ll soon see, this will make developing the CounterField much easier. To start with, we’ll declare the CounterField class, which derives from DataControlField. 1: public class CounterField : DataControlField 2: { 3: } There’s one additional property to add to CounterField on top of what’s already inherited from DataControlField, which we’ll call CountPerPage. 1: public bool CountPerPage 2: { 3: get 4: { 5: object o = ViewState["CountPerPage"]; 6: if (o != null) 7: return (bool)o; 8: else 9: return false; 10: } 11: set 12: { 13: ViewState["CountPerPage"] = value; 14: } 15: } This property will determine whether the count resets with each page, and since we typically won’t want that, the default is false.  As with most control properties, we’ll persist the value to the ViewState. Technically there’s only one method that must be overridden to create a control that derives from DataControlField, and that’s CreateField.  I say technically because DataControlField is an abstract class, and its only abstract method is CreateField.  Doing that alone, however, will create a very boring field (with a header and footer, but no content). 1: protected override DataControlField CreateField() 2: { 3: return new CounterField(); 4: } DataControlField has a method, CloneField, used to clone an existing Field.  When it’s called, it calls CreateField to create a new instance of the appropriate field type, and then calls CopyProperties to copy all of the field’s properties.  Since CounterField has an additional property, we’ll override CopyProperties as well to retain it’s value if the field is cloned. 1: protected override void CopyProperties(DataControlField newField) 2: { 3: base.CopyProperties(newField); 4: ((CounterField)newField).CountPerPage = this.CountPerPage; 5: } Now, to the real meat of the CounterField.  In either a GridView or a DetailsView, a DataControlField ultimately get converted to a TableCell, and the InitializeCell method is called to add content to the cell. 1: public override void InitializeCell(DataControlFieldCell cell, 2: DataControlCellType cellType, DataControlRowState rowState, int rowIndex) 3: { 4: base.InitializeCell(cell, cellType, rowState, rowIndex); 5:  6: if (cellType == DataControlCellType.DataCell) 7: { 8: cell.DataBinding += new EventHandler(OnDataBindField); 9: } 10: } The DataControlField’s InitializeCell method already has the logic we need to populate the header and footer cells, so we’ll call it first to address those.  If the cell is a DataCell, we’ll attach an event handler to the DataBinding event to populate the row index. 1: protected virtual void OnDataBindField(object sender, EventArgs e) 2: { 3: TableCell cell = (TableCell)sender; 4: IDataItemContainer container = (IDataItemContainer)cell.NamingContainer; 5: int rowCount = 6: (CountPerPage ? container.DisplayIndex : container.DataItemIndex) + 1; 7: cell.Text = rowCount.ToString(); 8: } First we need a reference to the cell so we can add the row index.  Since this field can be used in either a GridView or a DetailsView, we’ll utilize the commonality between the two as much as possible.  Both the GridViewRow and the DetailsView implement the IDataItemContainer interface.  You’ve probably made use of this interface in the past and not known it.  This interface is what’s referenced when you use the “Container” keyword within a data control (e.g. Container.DataItem).  The DisplayIndex property is the displayed index on the current page.  The DataItemIndex is the overall index in the data source (including the offset for the page).  So, based on the CountPerPage setting, we pick the appropriate value and set the cell’s Text property. That’s all there is to it.  What’s really nice is that Visual Studio 2008 recognizes this class as a DataControlField, so it appears in Intellisense when adding Columns to a GridView or Fields to a DetailsView. Obviously this is a very simple field, and in most cases the InitializeCell and OnDataBindingField methods would be more complex, but hopefully this was a good introduction. The download includes the complete source code as well as samples of this field used in a GridView and a DetailsView. Download sample website: CounterField.zip (224 kb)

A Custom LinkButton for the ASP.NET Calendar

A couple of weeks ago I posted an article on creating a dynamic button in an ASP.NET Calendar.  The one aspect of that solution that wasn’t quite as clean as I would have liked is how the data key for the button that was clicked was determined.  To remedy that, I’ve created a custom server control that inherits from LinkButton, but provides some customization for this particular application. Specifically, this control: Retains all functionality of the original LinkButton Adds a CalendarClick event that is raised when a button in a Calendar is clicked Within the CalendarClick event, passes the data key of the clicked button in the event args Nothing really monumental, but I thought it was a good discussion on creating a relatively simple custom server control with a new event and EventArgs. Step 1 - CalendarClickEventArgs Building from the bottom up, we’ll first create a class that will provide data for our new event.  In this case, that’s CalendarClickEventArgs. 1: public class CalendarClickEventArgs : EventArgs 2: { 3: public CalendarClickEventArgs() 4: { 5: } 6:  7: public CalendarClickEventArgs(object dataKey) 8: { 9: this.DataKey = dataKey; 10: } 11:  12: public object DataKey { get; set; } 13: } This class inherits from EventsArgs, which is the base class for all event argument classes.  There’s one public property, DataKey, that provides the key identifying the button within the Calendar that was clicked, and a couple of constructors. Step 2 - CalendarClickEventHandler Next, we’ll create an event handler for our new event. 1: public delegate void CalendarClickEventHandler (object sender, 2: CalendarClickEventArgs e); Here we’re simply defining a new delegate with the appropriate signature.  Specifically, a method handling this event should accept an object (the sender), and an instance of CalendarClickEventArgs. Step 3 - CalendarLinkButton Finally, we’ll implement the CalendarLinkButtonClass itself.  To start with, we’ll create a class that inherits from LinkButton. 1: [ 2: DefaultEvent("CalendarClick"), 3: ToolboxData("<{0}:CalendarLinkButton runat=\"server\">CalendarLinkButton</{0}:CalendarLinkButton>") 4: ] 5: public class CalendarLinkButton : LinkButton 6: { 7: } The DefaultEvent attribute specifies the default event for the control (i.e. the event that’s added if you double click on the control in design view).  The ToolBoxData attribute specifies the markup that’s generated when you add the control from the toolbox. Next we’ll add an event to raise when the calendar is clicked.  We’re using the Events collection (inherited from control) to hold delegates for the event, which is a pretty standard convention. 1: public event CalendarClickEventHandler CalendarClick 2: { 3: add 4: { 5: base.Events.AddHandler(EventCalendarClick, value); 6: } 7: remove 8: { 9: base.Events.RemoveHandler(EventCalendarClick, value); 10: } 11: } 12:  13: private static readonly object EventCalendarClick; We’ll also define a method to raise the events.  This method will simply call any applicable delegates for this event. 1: protected virtual void OnCalendarClick(CalendarClickEventArgs e) 2: { 3: CalendarClickEventHandler handler = 4: (CalendarClickEventHandler)base.Events[EventCalendarClick]; 5: if (handler != null) 6: { 7: handler(this, e); 8: } 9: } Finally, we need to implement a mechanism to raise the event.  To accomplish this, we’ll override the RaisePostBackEvent method, which is called by the Page when processing a postback.  Since we want to retain all of the functionality of the original LinkButton, we’ll first call the LinkButton’s RaisePostBackEvent.  This call will: Perform event validation (which includes the event argument, so we get the validation for our Calendar button events) Call the Page.Validate() method if the button causes validation Raise the LinkButton’s Click event Raise the LinkButton’s Command event After that, we’ll raise our new event – CalendarClick.  Recall from my original post on dynamic buttons in a calendar that the __EVENTARGUMENT form parameter is being used to identify the Calendar button that was clicked.  The CalendarClick event is only raised if an event argument is specified, which should always be true when triggered from the Calendar, but wouldn’t be true if the button itself were clicked.  The Page takes care of pulling __EVENTARGUMENT  and passing it to the RaisePostBackEvent method, so that value is passed to the CalendarClickEventArgs. 1: protected override void RaisePostBackEvent(string eventArgument) 2: { 3: base.RaisePostBackEvent(eventArgument); 4:  5: if (!String.IsNullOrEmpty(eventArgument)) 6: { 7: this.OnCalendarClick(new CalendarClickEventArgs(eventArgument)); 8: } 9: } Below is the complete code for CalendarLinkButton. 1: [ 2: DefaultEvent("CalendarClick"), 3: ToolboxData("<{0}:CalendarLinkButton runat=\"server\">CalendarLinkButton</{0}:CalendarLinkButton>") 4: ] 5: public class CalendarLinkButton : LinkButton 6: { 7: protected override void RaisePostBackEvent(string eventArgument) 8: { 9: base.RaisePostBackEvent(eventArgument); 10:  11: if (!String.IsNullOrEmpty(eventArgument)) 12: { 13: this.OnCalendarClick(new CalendarClickEventArgs(eventArgument)); 14: } 15: } 16:  17: protected virtual void OnCalendarClick(CalendarClickEventArgs e) 18: { 19: CalendarClickEventHandler handler = 20: (CalendarClickEventHandler)base.Events[EventCalendarClick]; 21: if (handler != null) 22: { 23: handler(this, e); 24: } 25: } 26:  27: public event CalendarClickEventHandler CalendarClick 28: { 29: add 30: { 31: base.Events.AddHandler(EventCalendarClick, value); 32: } 33: remove 34: { 35: base.Events.RemoveHandler(EventCalendarClick, value); 36: } 37: } 38:  39: private static readonly object EventCalendarClick; 40: } That’s all there is to the code, which can be downloaded with the example from my original post.  It also includes a sample page with the required changes (which overall are pretty minor).