Archive

Posts Tagged ‘ASP.NET’

Custom Paging Using Linq and ObjectDataSource: Caching Results

June 23rd, 2011 No comments

This is a follow-up post to my previous explanation of writing custom Linq extension methods.

The declaration of a custom paging ObjectDataSource looks like this:

<asp:ObjectDataSource ID="SomeDataSource" EnablePaging="true" runat="server" SelectCountMethod="GetCount" 
    SortParameterName="sSortColumn" SelectMethod="GetData" TypeName="SomeNamespace+SomeClass" 
    StartRowIndexParameterName="iSkip" MaximumRowsParameterName="iTake" >
    <SelectParameters>	
        <asp:Parameter Name="sSomeParameter" Type="String" />
     </SelectParameters>	
</asp:ObjectDataSource>

In this case we need to implement a class named SomeClass which has the following public methods: GetData() and GetCount(). We make use of the non-generic IQueryable extension methods from the previous post. Note that the call of the ToArrayExtension() may be necessary to stop the defered execution built into Linq. Otherwise we could run into an exception when the Linq context is disposed (our class implements IDisposable).

public class SomeClass : IDisposable
{
    protected SomeDataContext db = new SomeDataContext();
 
    public IEnumerable GetData( int iSkip, int iTake, string sSortColumn, string sSomeParameter )
    {
        return this.GetDataInternal( sSomeParameter ).OrderBySQLSyntax( sSortColumn ).SkipExtension( iSkip ).TakeExtension( iTake ).ToArrayExtension();
    }
 
    public int GetCount( string sSomeParameter )
    {
        return this.GetDataInternal( sSomeParameter ).CountExtension();
    }
 
    protected IQueryable GetDataInternal( string sSomeParameter )
    {
        return this.db.tblSomeTable.Where( x => x.Value3 = sSomeParameter ).Select( x => new { ID = x.ID, V1 = x.Value1, V2 = x.Value2 } );
    }
 
    public void Dispose()
    {
        this.db.Dispose();
    }
}

The above gets more useful with increased complexity of the returned data.

There is an alternative approach, which will be based on the fact that GridView/ObjectDataSource will usually call the data retrieval method first, and then call the count method. Therefore we could actually cache the value for the count method when the select method is called. This may also have a positive impact on performance. I am not sure though, if this order of calling can be relied on. Caching cannot be done in an instance variable, since ObjectDataSource apparently creates a new instance upon every call. The place where it makes most sense is withing the HttpContext.Current.Items location inside web applications. In case you are using several ObjectDataSources on one page you have to make sure the parameter name for the cached value is unique, so there are no collisions.

Making use of caching, our query can be further simplified as:

public class SomeClass
{
    public IEnumerable GetData( int iSkip, int iTake, string sSortColumn, string sSomeParameter )
    {
        using ( SomeDataContext db = new SomeDataContext() )
        {
            var v = db.tblSomeTable.Where( x => x.Value3 = sSomeParameter ).Select( x => new { ID = x.ID, V1 = x.Value1, V2 = x.Value2 } );
            HttpContext.Current.Items[ "SomeClass.GetData" ] = v.Count();
            return v.OrderBySQLSyntax( sSortColumn ).Skip( iSkip ).Take( iTake ).ToArray();
        }
    }
 
    public int GetCount( string sSomeParameter )
    {
        object o = HttpContext.Current.Items[ "SomeClass.GetData" ];
        if ( o is int )
            return (int)o;
        throw new Exception( "Caching does not work, since GetCount() was called before GetData()!" );
    }
}

The order in which a GridView calls both methods may depend on the pagers used. Therefore a safe and well performing approach could be to combine the first and the second example.

public class SomeClass : IDisposable
{
    protected SomeDataContext db = new SomeDataContext();
 
    public IEnumerable GetData( int iSkip, int iTake, string sSortColumn, string sSomeParameter )
    {
        var v = this.GetDataInternal( sSomeParameter );
        HttpContext.Current.Items[ "SomeClass.GetData" ] = v.CountExtension();
        return v.OrderBySQLSyntax( sSortColumn ).SkipExtension( iSkip ).TakeExtension( iTake ).ToArrayExtension();
    }
 
    public int GetCount( string sSomeParameter )
    {
        object o = HttpContext.Current.Items[ "SomeClass.GetData" ];
        if ( o is int )
            return (int)o;
        return this.GetDataInternal( sSomeParameter ).CountExtension();
    }
 
    protected IQueryable GetDataInternal( string sSomeParameter )
    {
        return this.db.tblSomeTable.Where( x => x.Value3 = sSomeParameter ).Select( x => new { ID = x.ID, V1 = x.Value1, V2 = x.Value2 } );
    }
 
    public void Dispose()
    {
        this.db.Dispose();
    }
}

Limiting ASP.NET GridView Text

March 11th, 2010 No comments

The ASP.NET GridView control offers a large amount of parameters for customization. Yet in some cases we need to go further for realizing specific goals.
One such feature was needed by a customer who had to display text in a grid view that would easily exceed the amount of column space. What are the possible solutions here?

  1. The most basic and least flexible would be to truncate any text exceeding a set limit of characters by applying SUBSTRING in the database query.
  2. The other option would be using a TemplateField, which results in a lot of coding overhead, especially if you have to incorporate markup for the insert and update events as well.
  3. Depending on your needs, using the CSS text-overflow:ellipsis could be a compact solution, yet it is only supported by Internet Explorer (some kind of workaround for FireFox exists).
  4. Perhaps the most elegant approach would be the subclassing of the existing BoundField, which already has all the features ready for inserting and updating. Although we only print the first few characters of the column text, we would give the user an additional feature at hand that allows him to view the whole text by hovering the cursor over the short text.

The class EllipsisTextField extends BoundField by one property that reflects the maximum abount of characters to be displayed. If left empty, the control behaves identical with the BoundField class, so it is backwards compatible.

public class EllipsisTextField : BoundField
{
    public int? MaxChars
    {
        get { return this.ViewState[ "MaxChars" ] as int?; }
        set { this.ViewState[ "MaxChars" ] = value; }
    }
 
    protected override string FormatDataValue( object dataValue, bool encode )
    {
        string sLong = dataValue as string;
        if ( this.MaxChars.HasValue && !String.IsNullOrEmpty( sLong ))
        {
            string sShort = sLong;
            if ( sLong.Length > this.MaxChars.Value )
            {
                sShort = sLong.Substring( 0, this.MaxChars.Value ) + "...";
                sLong = HttpUtility.HtmlEncode( sLong );
                sShort = HttpUtility.HtmlEncode( sShort );
 
                dataValue = "<div title=\"" + sLong + "\" style=\"white-space: nowrap;\">" + sShort + "</div>";
                return base.FormatDataValue( dataValue, false );
            }
        }
        return base.FormatDataValue( dataValue, encode );
    }
}

We override the FormatDataValue method in order to execute our own rendering code. Inside, we truncate the original string if necessary, add ellipses and then add short and long versions to a DIV. When set as the title, any mouse over action will render the full code as a tooltip.

Validating Dates in ASP.NET

February 11th, 2010 No comments

This is a simple custom validator extension class that allows for quick and easy date validation.

public class ValidDateValidator : CustomValidator
{
    public bool RequireInput
    {
        get 
        {
            if ( this.ViewState[ "RequireInput" ] != null )
                return (bool)this.ViewState[ "RequireInput" ];
            return false;
        }
        set { this.ViewState[ "RequireInput" ] = value; }
    }
 
    public ValidDateValidator()
    {
        base.ServerValidate += new ServerValidateEventHandler( ValidDateValidator_ServerValidate );
    }
 
    void ValidDateValidator_ServerValidate( object source, ServerValidateEventArgs args )
    {
        if ( !this.RequireInput && ( String.IsNullOrEmpty( args.Value ) || Convert.IsDBNull( args.Value ) ) )
            args.IsValid = true;
        else
            args.IsValid = IsValidDate( args.Value );
    }
 
    public static bool IsValidDate( string sDate )
    {
        DateTime oDate;
        return DateTime.TryParse( sDate, out oDate );
    }
}

First of all we are adding a new validation event handler in the constructor of the class. Then we add a new property that allows us to specify whether or not empty dates are accepted.
The actual validation function uses the DateTime.TryParse method.
Pay attention though, this sample method parses the given date according to the format rules in your pages locale. If that is not desired, make sure you specify additional parameters in this function to ensure proper results. It is one common pitfall to develop working code on a machine with a different locale than the production system and then see the code break when going live.
If you are accepting the date in a certain format, you might also consider using DateTime.TryParseExact(), which does what it’s name suggests.

This validator works well with Ajax notifications such as the ValidatorCalloutExtender, but it only works on postbacks. If you would like to see it working on the client, you have to resort to JavaScript.