Archives

Archives / 2012 / October
  • Wrapping ASP.NET Client Callbacks

    Client Callbacks are probably the less known (and I dare say, less loved) of all the AJAX options in ASP.NET, which also include the UpdatePanel, Page Methods and Web Services. The reason for that, I believe, is it’s relative complexity:

    • Get a reference to a JavaScript function;
    • Dynamically register function that calls the above reference;
    • Have a JavaScript handler call the registered function.

    However, it has some the nice advantage of being self-contained, that is, doesn’t need additional files, such as web services, JavaScript libraries, etc, or static methods declared on a page, or any kind of attributes.

    So, here’s what I want to do:

    • Have a DOM element which exposes a method that is executed server side, passing it a string and returning a string;
    • Have a server-side event that handles the client-side call;
    • Have two client-side user-supplied callback functions for handling the success and error results.

    I’m going to develop a custom control without user interface that does the registration of the client JavaScript method as well as a server-side event that can be hooked by some handler on a page. My markup will look like this:

       1: <script type="text/javascript">
       1:  
       2:  
       3:     function onCallbackSuccess(result, context)
       4:     {
       5:     }
       6:  
       7:     function onCallbackError(error, context)
       8:     {
       9:     }
      10:  
    </script>
       2: <my:CallbackControl runat="server" ID="callback" SendAllData="true" OnCallback="OnCallback"/>

    The control itself looks like this:

       1: public class CallbackControl : Control, ICallbackEventHandler
       2: {
       3:     #region Public constructor
       4:     public CallbackControl()
       5:     {
       6:         this.SendAllData = false;
       7:         this.Async = true;
       8:     }
       9:     #endregion
      10:  
      11:     #region Public properties and events
      12:     public event EventHandler<CallbackEventArgs> Callback;
      13:  
      14:     [DefaultValue(true)]
      15:     public Boolean Async
      16:     {
      17:         get;
      18:         set;
      19:     }
      20:  
      21:     [DefaultValue(false)]
      22:     public Boolean SendAllData
      23:     {
      24:         get;
      25:         set;
      26:     }
      27:  
      28:     #endregion
      29:  
      30:     #region Protected override methods
      31:  
      32:     protected override void Render(HtmlTextWriter writer)
      33:     {
      34:         writer.AddAttribute(HtmlTextWriterAttribute.Id, this.ClientID);
      35:         writer.RenderBeginTag(HtmlTextWriterTag.Span);
      36:  
      37:         base.Render(writer);
      38:  
      39:         writer.RenderEndTag();
      40:     }
      41:  
      42:     protected override void OnInit(EventArgs e)
      43:     {
      44:         String reference = this.Page.ClientScript.GetCallbackEventReference(this, "arg", "onCallbackSuccess", "context", "onCallbackError", this.Async);
      45:         String script = String.Concat("\ndocument.getElementById('", this.ClientID, "').callback = function(arg, context, onCallbackSuccess, onCallbackError){", ((this.SendAllData == true) ? "__theFormPostCollection.length = 0; __theFormPostData = '';  WebForm_InitCallback(); " : String.Empty), reference, ";};\n");
      46:  
      47:         this.Page.ClientScript.RegisterStartupScript(this.GetType(), String.Concat("callback", this.ClientID), script, true);
      48:  
      49:         base.OnInit(e);
      50:     }
      51:  
      52:     #endregion
      53:  
      54:     #region Protected virtual methods
      55:     protected virtual void OnCallback(CallbackEventArgs args)
      56:     {
      57:         EventHandler<CallbackEventArgs> handler = this.Callback;
      58:  
      59:         if (handler != null)
      60:         {
      61:             handler(this, args);
      62:         }
      63:     }
      64:  
      65:     #endregion
      66:  
      67:     #region ICallbackEventHandler Members
      68:  
      69:     String ICallbackEventHandler.GetCallbackResult()
      70:     {
      71:         CallbackEventArgs args = new CallbackEventArgs(this.Context.Items["Data"] as String);
      72:  
      73:         this.OnCallback(args);
      74:  
      75:         return (args.Result);
      76:     }
      77:  
      78:     void ICallbackEventHandler.RaiseCallbackEvent(String eventArgument)
      79:     {
      80:         this.Context.Items["Data"] = eventArgument;
      81:     }
      82:  
      83:     #endregion
      84: }

    And the event argument class:

       1: [Serializable]
       2: public class CallbackEventArgs : EventArgs
       3: {
       4:     public CallbackEventArgs(String argument)
       5:     {
       6:         this.Argument = argument;
       7:         this.Result = String.Empty;
       8:     }
       9:  
      10:     public String Argument
      11:     {
      12:         get;
      13:         private set;
      14:     }
      15:  
      16:     public String Result
      17:     {
      18:         get;
      19:         set;
      20:     }
      21: }

    You will notice two properties on the CallbackControl:

    • Async: indicates if the call should be made asynchronously or synchronously (the default);
    • SendAllData: indicates if the callback call will include the view and control state of all of the controls on the page, so that, on the server side, they will have their properties set when the Callback event is fired.

    The CallbackEventArgs class exposes two properties:

    • Argument: the read-only argument passed to the client-side function;
    • Result: the result to return to the client-side callback function, set from the Callback event handler.

    An example of an handler for the Callback event would be:

       1: protected void OnCallback(Object sender, CallbackEventArgs e)
       2: {
       3:     e.Result = String.Join(String.Empty, e.Argument.Reverse());
       4: }

    Finally, in order to fire the Callback event from the client, you only need this:

       1: <input type="text" id="input"/>
       2: <input type="button" value="Get Result" onclick="document.getElementById('callback').callback(callback(document.getElementById('input').value, 'context', onCallbackSuccess, onCallbackError))"/>

    The syntax of the callback function is:

    • arg: some string argument;
    • context: some context that will be passed to the callback functions (success or failure);
    • callbackSuccessFunction: some function that will be called when the callback succeeds;
    • callbackFailureFunction: some function that will be called if the callback fails for some reason.

    Give it a try and see if it helps! Winking smile

    Read more...

  • More SharePoint 2010 Expression Builders

    Introduction

    Following my last post, I decided to publish the whole set of expression builders that I use with SharePoint. For all who don’t know about expression builders, they allow us to employ a declarative approach, so that we don’t have to write code for “gluing” things together, like getting a value from the query string, the page’s underlying SPListItem or the current SPContext and assigning it to a control’s property.

    These expression builders are for some quite common scenarios, I use them quite often, and I hope you find them useful as well.

    SPContextExpression

    This expression builder allows us to specify an expression to be processed on the SPContext.Current property object. For example:

       1: <asp:Literal runat="server" Text=“<%$ SPContextExpression:Site.RootWeb.Lists[0].Author.LoginName %>”/>

    It is identical to having the following code:

       1: String authorName = SPContext.Current.Site.RootWeb.Lists[0].Author.LoginName;

    SPFarmProperty

    Returns a property stored on the farm level:

       1: <asp:Literal runat="server" Text="<%$ SPFarmProperty:SomeProperty %>"/>

    Identical to:

       1: Object someProperty = SPFarm.Local.Properties["SomeProperty"];

    SPField

    Returns the value of a selected page’s list item field:

       1: <asp:Literal runat="server" Text="<%$ SPField:Title %>"/>

    Does the same as:

       1: String title = SPContext.Current.ListItem["Title"] as String;

    SPIsInAudience

    Checks if the current user belongs to an audience:

       1: <asp:CheckBox runat="server" Checked="<%$ SPIsInAudience:SomeAudience %>"/>

    Equivalent to:

       1: AudienceManager audienceManager = new AudienceManager(SPServiceContext.Current);
       2: Audience audience = audienceManager.Audiences["SomeAudience"];
       3: Boolean isMember = audience.IsMember(SPContext.Current.Web.User.LoginName);

    SPIsInGroup

    Checks if the current user belongs to a group: 
       1: <asp:CheckBox runat="server" Checked="<%$ SPIsInGroup:SomeGroup %>"/>

    The equivalent C# code is:

       1: SPContext.Current.Web.CurrentUser.Groups.OfType<SPGroup>().Any(x => String.Equals(x.Name, “SomeGroup”, StringComparison.OrdinalIgnoreCase));

    SPProperty

    Returns the value of a user profile property for the current user: 
       1: <asp:Literal runat="server" Text="<%$ SPProperty:LastName %>"/>

    Where the same code in C# would be:

       1: UserProfileManager upm = new UserProfileManager(SPServiceContext.Current);
       2: UserProfile u = upm.GetUserProfile(false);
       3: Object property = u["LastName"].Value;

    SPQueryString

    Returns a value passed on the query string:
       1: <asp:GridView runat="server" PageIndex="<%$ SPQueryString:PageIndex %>" />

    Is equivalent to (no SharePoint code this time):

       1: Int32 pageIndex = Convert.ChangeType(typeof(Int32), HttpContext.Current.Request.QueryString["PageIndex"]);

    SPWebProperty

    Returns the value of a property stored at the site level:
       1: <asp:Literal runat="server" Text="<%$ SPWebProperty:__ImagesListId %>"/>

    You can get the same result as:

       1: String imagesListId = SPContext.Current.Web.AllProperties["__ImagesListId"] as String;

    Code

    OK, let’s move to the code. First, a common abstract base class, mainly for inheriting the conversion method:

       1: public abstract class SPBaseExpressionBuilder : ExpressionBuilder
       2: {
       3:     #region Protected static methods
       4:     protected static Object Convert(Object value, PropertyInfo propertyInfo)
       5:     {
       6:         if (value != null)
       7:         {
       8:             if (propertyInfo.PropertyType.IsAssignableFrom(value.GetType()) == false)
       9:             {
      10:                 if (propertyInfo.PropertyType.IsEnum == true)
      11:                 {
      12:                     value = Enum.Parse(propertyInfo.PropertyType, value.ToString(), true);
      13:                 }
      14:                 else if (propertyInfo.PropertyType == typeof(String))
      15:                 {
      16:                     value = value.ToString();
      17:                 }
      18:                 else if ((typeof(IConvertible).IsAssignableFrom(propertyInfo.PropertyType) == true) && (typeof(IConvertible).IsAssignableFrom(value.GetType()) == true))
      19:                 {
      20:                     value = System.Convert.ChangeType(value, propertyInfo.PropertyType);
      21:                 }
      22:             }
      23:         }
      24:  
      25:         return (value);
      26:     }
      27:     #endregion
      28:  
      29:     #region Public override methods
      30:     public override CodeExpression GetCodeExpression(BoundPropertyEntry entry, Object parsedData, ExpressionBuilderContext context)
      31:     {            
      32:         if (String.IsNullOrEmpty(entry.Expression) == true)
      33:         {
      34:             return (new CodePrimitiveExpression(String.Empty));
      35:         }
      36:         else
      37:         {
      38:             return (new CodeMethodInvokeExpression(new CodeMethodReferenceExpression(new CodeTypeReferenceExpression(this.GetType()), "GetValue"), new CodePrimitiveExpression(entry.Expression.Trim()), new CodePropertyReferenceExpression(new CodeArgumentReferenceExpression("entry"), "PropertyInfo")));
      39:         }
      40:     }
      41:     #endregion
      42:  
      43:     #region Public override properties
      44:     public override Boolean SupportsEvaluate
      45:     {
      46:         get
      47:         {
      48:             return (true);
      49:         }
      50:     }
      51:     #endregion
      52: }

    Next, the code for each expression builder:

       1: [ExpressionPrefix("SPContext")]
       2: public class SPContextExpressionBuilder : SPBaseExpressionBuilder
       3: {
       4:     #region Public static methods
       5:     public static Object GetValue(String expression, PropertyInfo propertyInfo)
       6:     {
       7:         SPContext context = SPContext.Current;
       8:         Object expressionValue = DataBinder.Eval(context, expression.Trim().Replace('\'', '"'));
       9:  
      10:         expressionValue = Convert(expressionValue, propertyInfo);
      11:  
      12:         return (expressionValue);
      13:     }
      14:  
      15:     #endregion
      16:  
      17:     #region Public override methods
      18:     public override Object EvaluateExpression(Object target, BoundPropertyEntry entry, Object parsedData, ExpressionBuilderContext context)
      19:     {
      20:         return (GetValue(entry.Expression, entry.PropertyInfo));
      21:     }
      22:     #endregion
      23: }

     

       1: [ExpressionPrefix("SPFarmProperty")]
       2: public class SPFarmPropertyExpressionBuilder : SPBaseExpressionBuilder
       3: {
       4:     #region Public static methods
       5:     public static Object GetValue(String propertyName, PropertyInfo propertyInfo)
       6:     {
       7:         Object propertyValue = SPFarm.Local.Properties[propertyName];
       8:  
       9:         propertyValue = Convert(propertyValue, propertyInfo);
      10:  
      11:         return (propertyValue);
      12:     }
      13:  
      14:     #endregion
      15:  
      16:     #region Public override methods
      17:     public override Object EvaluateExpression(Object target, BoundPropertyEntry entry, Object parsedData, ExpressionBuilderContext context)
      18:     {
      19:         return (GetValue(entry.Expression, entry.PropertyInfo));
      20:     }
      21:     #endregion
      22: }

     

       1: [ExpressionPrefix("SPField")]
       2: public class SPFieldExpressionBuilder : SPBaseExpressionBuilder
       3: {
       4:     #region Public static methods
       5:     public static Object GetValue(String fieldName, PropertyInfo propertyInfo)
       6:     {
       7:         Object fieldValue = SPContext.Current.ListItem[fieldName];
       8:  
       9:         fieldValue = Convert(fieldValue, propertyInfo);
      10:  
      11:         return (fieldValue);
      12:     }
      13:  
      14:     #endregion
      15:  
      16:     #region Public override methods
      17:     public override Object EvaluateExpression(Object target, BoundPropertyEntry entry, Object parsedData, ExpressionBuilderContext context)
      18:     {
      19:         return (GetValue(entry.Expression, entry.PropertyInfo));
      20:     }
      21:     #endregion
      22: }

     

       1: [ExpressionPrefix("SPIsInAudience")]
       2: public class SPIsInAudienceExpressionBuilder : SPBaseExpressionBuilder
       3: {
       4:     #region Public static methods
       5:     public static Object GetValue(String audienceName, PropertyInfo info)
       6:     {
       7:         Debugger.Break();
       8:         audienceName = audienceName.Trim();
       9:  
      10:         if ((audienceName.StartsWith("'") == true) && (audienceName.EndsWith("'") == true))
      11:         {
      12:             audienceName = audienceName.Substring(1, audienceName.Length - 2);
      13:         }
      14:  
      15:         AudienceManager manager = new AudienceManager();
      16:         Object value = manager.IsMemberOfAudience(SPControl.GetContextWeb(HttpContext.Current).CurrentUser.LoginName, audienceName);
      17:  
      18:         if (info.PropertyType == typeof(String))
      19:         {
      20:             value = value.ToString();
      21:         }
      22:  
      23:         return(value);
      24:     }
      25:  
      26:     #endregion
      27:  
      28:     #region Public override methods
      29:     public override Object EvaluateExpression(Object target, BoundPropertyEntry entry, Object parsedData, ExpressionBuilderContext context)
      30:     {
      31:         return (GetValue(entry.Expression, entry.PropertyInfo));
      32:     }
      33:     #endregion
      34: }

     

       1: [ExpressionPrefix("SPIsInGroup")]
       2: public class SPIsInGroupExpressionBuilder : SPBaseExpressionBuilder
       3: {
       4:     #region Public static methods
       5:     public static Object GetValue(String groupName, PropertyInfo info)
       6:     {
       7:         groupName = groupName.Trim();
       8:  
       9:         if ((groupName.StartsWith("'") == true) && (groupName.EndsWith("'") == true))
      10:         {
      11:             groupName = groupName.Substring(1, groupName.Length - 2);
      12:         }
      13:  
      14:         Object value = SPControl.GetContextWeb(HttpContext.Current).CurrentUser.Groups.OfType<SPGroup>().Any(x => String.Equals(x.Name, groupName, StringComparison.OrdinalIgnoreCase));
      15:  
      16:         if (info.PropertyType == typeof(String))
      17:         {
      18:             value = value.ToString();
      19:         }
      20:  
      21:         return(value);
      22:     }
      23:  
      24:     #endregion
      25:  
      26:     #region Public override methods
      27:     public override Object EvaluateExpression(Object target, BoundPropertyEntry entry, Object parsedData, ExpressionBuilderContext context)
      28:     {
      29:         return (GetValue(entry.Expression, entry.PropertyInfo));
      30:     }
      31:     #endregion
      32: }

     

       1: [ExpressionPrefix("SPProperty")]
       2: public class SPPropertyExpressionBuilder : SPBaseExpressionBuilder
       3: {
       4:     #region Public static methods
       5:     public static Object GetValue(String propertyName, System.Reflection.PropertyInfo propertyInfo)
       6:     {
       7:         SPServiceContext serviceContext = SPServiceContext.GetContext(HttpContext.Current);
       8:         UserProfileManager upm = new UserProfileManager(serviceContext);
       9:         UserProfile up = upm.GetUserProfile(false);
      10:         Object propertyValue = (up[propertyName] != null) ? up[propertyName].Value : null;
      11:  
      12:         propertyValue = Convert(propertyValue, propertyInfo);
      13:  
      14:         return (propertyValue);
      15:     }
      16:  
      17:     #endregion
      18:  
      19:     #region Public override methods
      20:     public override Object EvaluateExpression(Object target, BoundPropertyEntry entry, Object parsedData, ExpressionBuilderContext context)
      21:     {
      22:         return (GetValue(entry.Expression, entry.PropertyInfo));
      23:     }
      24:     #endregion
      25: }

     

       1: [ExpressionPrefix("SPQueryString")]
       2: public class SPQueryStringExpressionBuilder : SPBaseExpressionBuilder
       3: {
       4:     #region Public static methods
       5:     public static Object GetValue(String parameterName, PropertyInfo propertyInfo)
       6:     {
       7:         Object parameterValue = HttpContext.Current.Request.QueryString[parameterName];
       8:  
       9:         parameterValue = Convert(parameterValue, propertyInfo);
      10:  
      11:         return (parameterValue);
      12:     }
      13:  
      14:     #endregion
      15:  
      16:     #region Public override methods
      17:     public override Object EvaluateExpression(Object target, BoundPropertyEntry entry, Object parsedData, ExpressionBuilderContext context)
      18:     {
      19:         return (GetValue(entry.Expression, entry.PropertyInfo));
      20:     }
      21:     #endregion
      22: }

     

       1: [ExpressionPrefix("SPWebProperty")]
       2: public class SPWebPropertyExpressionBuilder : SPBaseExpressionBuilder
       3: {
       4:     #region Public static methods
       5:     public static Object GetValue(String propertyName, PropertyInfo propertyInfo)
       6:     {
       7:         Object propertyValue = SPContext.Current.Web.AllProperties[propertyName];
       8:  
       9:         propertyValue = Convert(propertyValue, propertyInfo);
      10:  
      11:         return (propertyValue);
      12:     }
      13:  
      14:     #endregion
      15:  
      16:     #region Public override methods
      17:     public override Object EvaluateExpression(Object target, BoundPropertyEntry entry, Object parsedData, ExpressionBuilderContext context)
      18:     {
      19:         return (GetValue(entry.Expression, entry.PropertyInfo));
      20:     }
      21:     #endregion
      22: }

    Registration

    You probably know how to register them, but here it goes again: add this following snippet to your Web.config file, inside the configuration/system.web/compilation/expressionBuilders section:

       1: <add expressionPrefix="SPContext" type="MyNamespace.SPContextExpressionBuilder, MyAssembly, Culture=neutral, Version=1.0.0.0, PublicKeyToken=xxx" />
       2: <add expressionPrefix="SPFarmProperty" type="MyNamespace.SPFarmPropertyExpressionBuilder, MyAssembly, Culture=neutral, Version=1.0.0.0, PublicKeyToken=xxx" />
       3: <add expressionPrefix="SPField" type="MyNamespace.SPFieldExpressionBuilder, MyAssembly, Culture=neutral, Version=1.0.0.0, PublicKeyToken=xxx" />
       4: <add expressionPrefix="SPIsInAudience" type="MyNamespace.SPIsInAudienceExpressionBuilder, MyAssembly, Culture=neutral, Version=1.0.0.0, PublicKeyToken=xxx" />
       5: <add expressionPrefix="SPIsInGroup" type="MyNamespace.SPIsInGroupExpressionBuilder, MyAssembly, Culture=neutral, Version=1.0.0.0, PublicKeyToken=xxx" />
       6: <add expressionPrefix="SPProperty" type="MyNamespace.SPPropertyExpressionBuilder, MyAssembly, Culture=neutral, Version=1.0.0.0, PublicKeyToken=xxx" />
       7: <add expressionPrefix="SPQueryString" type="MyNamespace.SPQueryStringExpressionBuilder, MyAssembly, Culture=neutral, Version=1.0.0.0, PublicKeyToken=xxx" />
       8: <add expressionPrefix="SPWebProperty" type="MyNamespace.SPWebPropertyExpressionBuilder, MyAssembly, Culture=neutral, Version=1.0.0.0, PublicKeyToken=xxx" />

    I’ll leave it up to you to figure out the best way to deploy this to your server! Winking smile

    Read more...

  • General Purpose ASP.NET Data Source Control

    OK, you already know about the ObjectDataSource control, so what’s wrong with it? Well, for once, it doesn’t pass any context to the SelectMethod, you only get the parameters supplied on the SelectParameters plus the desired ordering, starting page and maximum number of rows to display. Also, you must have two separate methods, one for actually retrieving the data, and the other for getting the total number of records (SelectCountMethod). Finally, you don’t get a chance to alter the supplied data before you bind it to the target control.

    I wanted something simple to use, and more similar to ASP.NET 4.5, where you can have the select method on the page itself, so I came up with CustomDataSource. Here’s how to use it (I chose a GridView, but it works equally well with any regular data-bound control):

       1: <web:CustomDataSourceControl runat="server" ID="datasource" PageSize="10" OnData="OnData" />
       2: <asp:GridView runat="server" ID="grid" DataSourceID="datasource" DataKeyNames="Id" PageSize="10" AllowPaging="true" AllowSorting="true" />

    The OnData event handler receives a DataEventArgs instance, which contains some properties that describe the desired paging location and size, and it’s where you return the data plus the total record count. Here’s a quick example:

       1: protected void OnData(object sender, DataEventArgs e)
       2: {
       3:     //just return some data
       4:     var data = Enumerable.Range(e.StartRowIndex, e.PageSize).Select(x => new { Id = x, Value = x.ToString(), IsPair = ((x % 2) == 0) });
       5:     e.Data = data;
       6:     //the total number of records
       7:     e.TotalRowCount = 100;
       8: }

    Here’s the code for the DataEventArgs:

       1: [Serializable]
       2: public class DataEventArgs : EventArgs
       3: {
       4:     public DataEventArgs(Int32 pageSize, Int32 startRowIndex, String sortExpression, IOrderedDictionary parameters)
       5:     {
       6:         this.PageSize = pageSize;
       7:         this.StartRowIndex = startRowIndex;
       8:         this.SortExpression = sortExpression;
       9:         this.Parameters = parameters;
      10:     }
      11:  
      12:     public IEnumerable Data
      13:     {
      14:         get;
      15:         set;
      16:     }
      17:  
      18:     public IOrderedDictionary Parameters
      19:     {
      20:         get;
      21:         private set;
      22:     }
      23:  
      24:     public String SortExpression
      25:     {
      26:         get;
      27:         private set;
      28:     }
      29:  
      30:     public Int32 StartRowIndex
      31:     {
      32:         get;
      33:         private set;
      34:     }
      35:  
      36:     public Int32 PageSize
      37:     {
      38:         get;
      39:         private set;
      40:     }
      41:  
      42:     public Int32 TotalRowCount
      43:     {
      44:         get;
      45:         set;
      46:     }
      47: }

    As you can guess, the StartRowIndex and PageSize receive the starting row and the desired page size, where the page size comes from the PageSize property on the markup. There’s also a SortExpression, which gets passed the sorted-by column and direction (if descending) and a dictionary containing all the values coming from the SelectParameters collection, if any. All of these are read only, and it is your responsibility to fill in the Data and TotalRowCount.

    The code for the CustomDataSource is very simple:

       1: [NonVisualControl]
       2: public class CustomDataSourceControl : DataSourceControl
       3: {
       4:     public CustomDataSourceControl()
       5:     {
       6:         this.SelectParameters = new ParameterCollection();
       7:     }
       8:  
       9:     protected override DataSourceView GetView(String viewName)
      10:     {
      11:         return (new CustomDataSourceView(this, viewName));
      12:     }
      13:  
      14:     internal void GetData(DataEventArgs args)
      15:     {
      16:         this.OnData(args);
      17:     }
      18:  
      19:     protected virtual void OnData(DataEventArgs args)
      20:     {
      21:         EventHandler<DataEventArgs> data = this.Data;
      22:  
      23:         if (data != null)
      24:         {
      25:             data(this, args);
      26:         }
      27:     }
      28:  
      29:     [Browsable(false)]
      30:     [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
      31:     [PersistenceMode(PersistenceMode.InnerProperty)]
      32:     public ParameterCollection SelectParameters
      33:     {
      34:         get;
      35:         private set;
      36:     }
      37:  
      38:     public event EventHandler<DataEventArgs> Data;
      39:  
      40:     public Int32 PageSize
      41:     {
      42:         get;
      43:         set;
      44:     }
      45: }

    Also, the code for the accompanying internal – as there is no need to use it from outside of its declaring assembly - data source view:

       1: sealed class CustomDataSourceView : DataSourceView
       2: {
       3:     private readonly CustomDataSourceControl dataSourceControl = null;
       4:  
       5:     public CustomDataSourceView(CustomDataSourceControl dataSourceControl, String viewName) : base(dataSourceControl, viewName)
       6:     {
       7:         this.dataSourceControl = dataSourceControl;
       8:     }
       9:  
      10:     public override Boolean CanPage
      11:     {
      12:         get
      13:         {
      14:             return (true);
      15:         }
      16:     }
      17:  
      18:     public override Boolean CanRetrieveTotalRowCount
      19:     {
      20:         get
      21:         {
      22:             return (true);
      23:         }
      24:     }
      25:  
      26:     public override Boolean CanSort
      27:     {
      28:         get
      29:         {
      30:             return (true);
      31:         }
      32:     }
      33:  
      34:     protected override IEnumerable ExecuteSelect(DataSourceSelectArguments arguments)
      35:     {
      36:         IOrderedDictionary parameters = this.dataSourceControl.SelectParameters.GetValues(HttpContext.Current, this.dataSourceControl);
      37:         DataEventArgs args = new DataEventArgs(this.dataSourceControl.PageSize, arguments.StartRowIndex, arguments.SortExpression, parameters);
      38:  
      39:         this.dataSourceControl.GetData(args);
      40:  
      41:         arguments.TotalRowCount = args.TotalRowCount;
      42:         arguments.MaximumRows = this.dataSourceControl.PageSize;
      43:         arguments.AddSupportedCapabilities(DataSourceCapabilities.Page | DataSourceCapabilities.Sort | DataSourceCapabilities.RetrieveTotalRowCount);
      44:         arguments.RetrieveTotalRowCount = true;
      45:  
      46:         if (!(args.Data is ICollection))
      47:         {
      48:             return (args.Data.OfType<Object>().ToList());
      49:         }
      50:         else
      51:         {
      52:             return (args.Data);
      53:         }
      54:     }
      55: }

    As always, looking forward to hearing from you!

    Read more...