Simples features for BoundField

26 11 2008

I was surprised the day I wanted to delimit a textbox in row edition(gridview) and avoid the user to enter unnecessary characters in my textbox. A simple boundfield is not fit to support that and custom code is necessary.

So I have written a long time ago a simple class with C#2 that I used when a define some gridview columns : LimitedBoundField.

public class LimitedBoundField : BoundField

    {

        private int _characterLimit = 0;

        private int _textBoxWidth = 100;

 

        public int MaxLength

        {

            get { return _characterLimit; }

            set { _characterLimit = value; }

        }

 

        public int TextBoxWidth

        {

            get { return _textBoxWidth; }

            set { _textBoxWidth = value; }

        }

 

        public override void InitializeCell(DataControlFieldCell cell, DataControlCellType cellType, DataControlRowState rowState, int rowIndex)

        {

            base.InitializeCell(cell, cellType, rowState, rowIndex);

            if (cellType == DataControlCellType.DataCell)

            {

                if (rowState == DataControlRowState.Edit ||

                    rowState == (DataControlRowState.Normal | DataControlRowState.Edit) ||

                    rowState == (DataControlRowState.Alternate | DataControlRowState.Edit))

                {

                    if (cell.Controls.Count > 0 && cell.Controls[0] is TextBox)

                    {

                        TextBox tb = ((TextBox)cell.Controls[0]);

                        if(MaxLength>0)

                          tb.MaxLength = MaxLength;

                        tb.Width = TextBoxWidth;

                   tb.Text = tb.Text.Substring(0, (tb.Text.Length > _characterLimit) ? _characterLimit : tb.Text.Length);

                    }

                }

            }

        }

    }





AjaxControlToolkit ScriptManager and webfarms

20 10 2008

Using often AjaxControlToolkit controls on all my web applications (Calendar, popup and others), I have discovered the ScriptManager. Immediatly, I was looking to evaluate what this control can bring me compared to the original from ASP.NET. Only the resources script option which allow to download all resource script in one file was sufficient to convince me. So I have tried it.

All was perfect, but after deploying in production environment with many servers(webfarm) , many errors occurs and some pages would not want to work correctly… I searched during 2-3 days and finally find that AjaxControlToolkit is case sensitive on url. And the root url of my application was changing from lower to upper case url according the server which fulfill the request (it’s a specific case of my entreprise but it can occur elsewhere).

I have found a good resource that explain the issue :
http://plainoldstan.blogspot.com/2008/04/ajaxcontroltoolkit-scriptresourceaxd.html





Web.Config : Configuration by Environment

7 10 2008

During a web project lifecycle, we encounter always the same problem when deploiement is required in development environment(Server A + DB A), Receipt/Qualif (Server B + DB B) and production (Server C + DB C without taking care of web farming). Many developpers choose to create one web.config for each environment; not a bad choice but a permanent synchro is required when adding reference or others commons configurations.
I was looking to improve this system to minimize the synchro time between each web.config file and find a simple tips to exclude the specific data from a web.config : the configSource property.

This way, it’s possible to write a web.config like that :

<?xml version=1.0?>

<configuration>

<configSections>

<sectionGroup name=system.web.extensions type=System.Web.Configuration.SystemWebExtensionsSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35>

<sectionGroup name=scripting type=System.Web.Configuration.ScriptingSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35>

<section name=scriptResourceHandler type=System.Web.Configuration.ScriptingScriptResourceHandlerSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35 requirePermission=false allowDefinition=MachineToApplication/>

<sectionGroup name=webServices type=System.Web.Configuration.ScriptingWebServicesSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35>

<section name=jsonSerialization type=System.Web.Configuration.ScriptingJsonSerializationSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35 requirePermission=false allowDefinition=Everywhere/>

<section name=profileService type=System.Web.Configuration.ScriptingProfileServiceSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35 requirePermission=false allowDefinition=MachineToApplication/>

<section name=authenticationService type=System.Web.Configuration.ScriptingAuthenticationServiceSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35 requirePermission=false allowDefinition=MachineToApplication/>

<section name=roleService type=System.Web.Configuration.ScriptingRoleServiceSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35 requirePermission=false allowDefinition=MachineToApplication/>

</sectionGroup>

</sectionGroup>

</sectionGroup>

</configSections>

<appSettings configSource=Config\ApplicationSettings.Config/>

<connectionStrings configSource=Config\ConnectionStrings.Config/>

and simply create a file named ApplicationSettings.Config(the name can be changed) in my folder Config. This one contains :

<?xml version=1.0?>

<appSettings>

<!– DEV –>

<add key=Key1 value=Value1 for Environment 1/>

Thus we only need to create for each environnement a file named ApplicationSettings.Config with the specific parameters. It reduces the number of parameters to the minimum.
When deploying, you should only copy the web.config and the appropriated ApplicationSettings.Config.

In my example, we can see an other configSource for connectionString based on the same principle. The connectionString section is now describe in another file :

<?xml version=1.0?>

<connectionStrings>

<add name=DB1 connectionString=Application Name=APP1;Data Source=DB1;Initial Catalog=APP1_Dev;Persist Security Info=True; providerName=System.Data.SqlClient/>

</connectionStrings>





Linq To SQL : How to order with a string typed expression ?

23 09 2008

After using Linq To SQL queries, I was quickly confronted to sort my data with a string expression provided, for example, by a gridview when a user require it. At this moment, I understood that the strongly typed system used with Linq cannot help me for this task… Understanding the fact that allowing a string expression representing an object property is contrary to Linq purpose, I began to search with our best friend ”Google” a soluce to resolve quickly my problem. But the best that I can obtain was a “switch case solution” which, according to the string property name, build the linq query sort filter with the associated object property. Immediatly, I guess my switch case code with 8-10 columns… No…

So, I started to write an extended method “OrderBy” allowing to take a string sort expression. Here the result.

/// <summary>

/// Extends method which allow to sort by string field name.

/// Allow to use a relative object definition for sorting (ex:LinkedObject.FieldsName1)

/// </summary>

/// <typeparam name=”TEntity”>Current Object type for query</typeparam>

/// <param name=”source”>list of defined object</param>

/// <param name=”sortExpression”>string name of the field we want to sort by</param>

/// <returns>Query sorted by sortExpression</returns>

public static IQueryable<TEntity> OrderBy<TEntity>(

    this IQueryable<TEntity> source, string sortExpression) where TEntity : class

{

    var type = typeof(TEntity);

    // Remember that for ascending order GridView just returns the column name and

    // for descending it returns column name followed by DESC keyword 

    // Therefore we need to examine the sortExpression and separate out Column Name and

    // order (ASC/DESC) 

    string[] expressionParts = sortExpression.Split(‘ ‘); // Assuming sortExpression is like [ColumnName DESC] or [ColumnName] 

    string orderByProperty = expressionParts[0];

    string sortDirection = “ASC”;

    string methodName = “OrderBy”;

 

    //if sortDirection is descending 

    if (expressionParts.Length > 1 && expressionParts[1] == “DESC”)

    {

        sortDirection = “Descending”;

        methodName += sortDirection; // Add sort direction at the end of Method name 

    }

    MethodCallExpression resultExp = null;

    if (!orderByProperty.Contains(“.”))

    {

        var property = type.GetProperty(orderByProperty);

        var parameter = Expression.Parameter(type, “p”);

        var propertyAccess = Expression.MakeMemberAccess(parameter, property);

        var orderByExp = Expression.Lambda(propertyAccess, parameter);

        resultExp = Expression.Call(typeof(Queryable), methodName,

                        new Type[] { type, property.PropertyType },

                        source.Expression, Expression.Quote(orderByExp));

    }

    else

    {

        Type relationType = type.GetProperty(orderByProperty.Split(‘.’)[0]).PropertyType;

        PropertyInfo relationProperty = type.GetProperty(orderByProperty.Split(‘.’)[0]);

        PropertyInfo relationProperty2 = relationType.GetProperty(orderByProperty.Split(‘.’)[1]);

        var parameter = Expression.Parameter(type, “p”);

        var propertyAccess = Expression.MakeMemberAccess(parameter, relationProperty);

        var propertyAccess2 = Expression.MakeMemberAccess(propertyAccess, relationProperty2);

        var orderByExp = Expression.Lambda(propertyAccess2, parameter);

        resultExp = Expression.Call(typeof(Queryable), methodName,

                        new Type[] { type, relationProperty2.PropertyType },

                        source.Expression, Expression.Quote(orderByExp));

    }

    return source.Provider.CreateQuery<TEntity>(resultExp);

}

/// <summary>

/// Allow to add another sorting on a query with a string representation of the field to sort by.

/// </summary>

/// <typeparam name=”TEntity”>Current Object type for query</typeparam>

/// <param name=”source”>list of defined object</param>

/// <param name=”sortExpression”>string name of the field we want to sort by</param>

/// <returns>Query sorted by sortExpression</returns>

public static IOrderedQueryable<TEntity> ThenBy<TEntity>(

    this IOrderedQueryable<TEntity> source, string sortExpression) where TEntity : class

{

    var type = typeof(TEntity);

    // Remember that for ascending order GridView just returns the column name and

    // for descending it returns column name followed by DESC keyword 

    // Therefore we need to examine the sortExpression and separate out Column Name and

    // order (ASC/DESC) 

    string[] expressionParts = sortExpression.Split(‘ ‘); // Assuming sortExpression is like [ColumnName DESC] or [ColumnName] 

    string orderByProperty = expressionParts[0];

    string sortDirection = “ASC”;

    string methodName = “ThenBy”;

 

    //if sortDirection is descending 

    if (expressionParts.Length > 1 && expressionParts[1] == “DESC”)

    {

        sortDirection = “Descending”;

        methodName += sortDirection; // Add sort direction at the end of Method name 

    }

    MethodCallExpression resultExp = null;

    if (!orderByProperty.Contains(“.”))

    {

        var property = type.GetProperty(orderByProperty);

        var parameter = Expression.Parameter(type, “p”);

        var propertyAccess = Expression.MakeMemberAccess(parameter, property);

        var orderByExp = Expression.Lambda(propertyAccess, parameter);

        resultExp = Expression.Call(typeof(Queryable), methodName,

                        new Type[] { type, property.PropertyType },

                        source.Expression, Expression.Quote(orderByExp));

    }

    else

    {

        Type relationType = type.GetProperty(orderByProperty.Split(‘.’)[0]).PropertyType;

        PropertyInfo relationProperty = type.GetProperty(orderByProperty.Split(‘.’)[0]);

        PropertyInfo relationProperty2 = relationType.GetProperty(orderByProperty.Split(‘.’)[1]);

        var parameter = Expression.Parameter(type, “p”);

        var propertyAccess = Expression.MakeMemberAccess(parameter, relationProperty);

        var propertyAccess2 = Expression.MakeMemberAccess(propertyAccess, relationProperty2);

        var orderByExp = Expression.Lambda(propertyAccess2, parameter);

        resultExp = Expression.Call(typeof(Queryable), methodName,

                        new Type[] { type, relationProperty2.PropertyType },

                        source.Expression, Expression.Quote(orderByExp));

    }

    return (IOrderedQueryable<TEntity>)source.Provider.CreateQuery<TEntity>(resultExp);

}





Hierarchical Gridview

23 09 2008

After using many professional/commercial controls, I was often disappointed in using them. When you are following the simple sample path, it will work. But if you really want to customize the control, the percent rate of bugs and unwanted states increase. If a control cannot be reliable, it’s better to do it and keep a full control on the source code.

That’s why, rather than spending some money to own your multi-gridview and waiting each day after a patch, here is a cool tip to adapt a standard Gridview in hierarchical Gridview:

Create a standard GridView on a webform and create a template field column at the end of the columns collection. Add in the template item : “<tr><td> Second Row </td></tr>”.

That all folk ! When addind this code, the DOM structure of the gridview is broken(Yes, it’s not W3C). But the result is there. Working with IE,Firefox and Chrome.

If we analyse in deep the generating HTML Code of the gridview, we can see the following structure:

<table>

<tr><th>col1</th><th>col second row</th></tr>

<tr><td>col1 value</td><td>

<tr><td>Second Row</td></tr>

</td></tr>

</table>

When adding TR in td(cell of gridview), a new row is created. However, we can manage the visibility of this new row through cell properties. Thus, with an appropriate extender(link,image,other control), we can hide or show the row.

I have created for my personal purpose 2 definition of template Field Column that I can add to a gridview and render it like a Hierarchical Gridview.

Here the code:

 /// <summary>

    /// Build a sub row for each row.

    /// </summary>

    [ToolboxData(@"<{0}:SubRowBoundField></{0}:SubRowBoundField>")]

    public class SubRowBoundField : TemplateField

    {

        public TextBox ExpandedRowsState

        {

            get;

            set;

        }

 

        public SubRowBoundField()

            : base()

        {

            LeftPadding = new Unit(“20px”);

            TextBox txt = new TextBox();

            txt.ID = “_ExpandedRowsState”;

            ExpandedRowsState = txt;

        }

 

        /// <summary>

        /// Space at the left of the cell (default:20px)

        /// </summary>

        public Unit LeftPadding { get; set; }

 

        public override void InitializeCell(DataControlFieldCell cell, DataControlCellType cellType, DataControlRowState rowState, int rowIndex)

        {

            base.InitializeCell(cell, cellType, rowState, rowIndex);

            string expandedRowStateValue = (HttpContext.Current.Request.Form[ExpandedRowsState.UniqueID] != null) ? HttpContext.Current.Request.Form[ExpandedRowsState.UniqueID] : ExpandedRowsState.Text;

            if (cellType == DataControlCellType.Header)

            {

                //cell.Controls.Clear();

                //we store the expanded row collection in an hidden field

                ExpandedRowsState.Style.Add(“display”, “none”);

                cell.Controls.Add(ExpandedRowsState);

                //cell.Text = string.Empty;

                cell.Style.Add(“width”, “100px”);

                cell.Style.Add(“display”, “none”);

 

            }

            else if (cellType == DataControlCellType.DataCell)

            {

                GridView g = this.Control as GridView;

                cell.Style.Add(“display”, “none”);

 

                bool displayPlus = (expandedRowStateValue.IndexOf(“[" + g.PageIndex + "," + rowIndex + "]“) > -1) ? false : true;

 

                cell.Controls.AddAt(0, new LiteralControl(

                    @”<tr>

                        <td colspan=”"100%”" style=”"padding-left:” + LeftPadding.ToString() + @”"”>

                            <div id=” + g.ClientID + @”_div” + rowIndex + @” 

                                style=”"display:” + ((displayPlus)?“none”:“block”) + @”;OVERFLOW: auto;WIDTH:100%”" >”));

                cell.Controls.Add(new LiteralControl(cell.Text));

                cell.Controls.Add(new LiteralControl(@”</div></td></tr>”));

 

            }

        }

    }

 

    /// <summary>

    /// Build a sub row expand-collapse action for each row.

    /// </summary>

    [ToolboxData(@"<{0}:SubRowExtendBoundField></{0}:SubRowExtendBoundField>")]

    public class SubRowExtendBoundField : TemplateField

    {

        public string CssClassPlus { get; set; }

        public string CssClassMinus { get; set; }

        public TextBox ExpandedRowsState { get; set; }

 

        public SubRowExtendBoundField()

            : base()

        {

            TextBox txt = new TextBox();

            txt.ID = “_ExpandedRowsStateExpander”;

            ExpandedRowsState = txt;

        }

 

        public override void InitializeCell(DataControlFieldCell cell, DataControlCellType cellType, DataControlRowState rowState, int rowIndex)

        {

            base.InitializeCell(cell, cellType, rowState, rowIndex);

            string expandedRowStateValue = (HttpContext.Current.Request.Form[ExpandedRowsState.UniqueID] != null) ? HttpContext.Current.Request.Form[ExpandedRowsState.UniqueID] : ExpandedRowsState.Text;

            if (cellType == DataControlCellType.Header)

            {

                //we store the expanded row collection in an hidden field

                ExpandedRowsState.Style.Add(“display”, “none”);

                cell.Controls.Add(ExpandedRowsState);

            }

            else if (cellType == DataControlCellType.DataCell)

            {

                GridView g = this.Control as GridView;

                TextBox txtExpanded = g.HeaderRow.Cells[g.Columns.Count - 1].Controls[0] as TextBox;

                string sExpand = @”

                    document.getElementById(‘” + g.ClientID + @”_btnPlus” + rowIndex + @”‘).style.display=’none’;

                    document.getElementById(‘” + g.ClientID + @”_btnMinus” + rowIndex + @”‘).style.display=’block’;

                    document.getElementById(‘” + g.ClientID + @”_div” + rowIndex + @”‘).style.display=’block’;

                    document.getElementById(‘” + ExpandedRowsState.ClientID + @”‘).value=document.getElementById(‘” + ExpandedRowsState.ClientID + @”‘).value+’["+g.PageIndex+"," + rowIndex + @"]‘;

                    document.getElementById(‘” + txtExpanded.ClientID + @”‘).value=document.getElementById(‘” + txtExpanded.ClientID + @”‘).value+’[" + g.PageIndex + "," + rowIndex + @"]‘;”;

                string sCollapse = @”

                    document.getElementById(‘” + g.ClientID + @”_btnPlus” + rowIndex + @”‘).style.display=’block’;

                    document.getElementById(‘” + g.ClientID + @”_btnMinus” + rowIndex + @”‘).style.display=’none’;

                    document.getElementById(‘” + g.ClientID + @”_div” + rowIndex + @”‘).style.display=’none’;

                    document.getElementById(‘” + ExpandedRowsState.ClientID + @”‘).value=document.getElementById(‘” + ExpandedRowsState.ClientID + @”‘).value.replace(‘[" + g.PageIndex + "," + rowIndex + @"]‘,”);

                    document.getElementById(‘” + txtExpanded.ClientID + @”‘).value=document.getElementById(‘” + txtExpanded.ClientID + @”‘).value.replace(‘[" + g.PageIndex + "," + rowIndex + @"]‘,”);”;

 

                string cancelBubble = @”window.event.cancelBubble=true;window.event.returnValue=false;”;

                sExpand += cancelBubble;

                sCollapse += cancelBubble;

 

 

                bool displayPlus = (expandedRowStateValue.IndexOf(“[" + g.PageIndex + "," + rowIndex + "]“) > -1) ? false : true;

 

                LiteralControl lcPlus = new LiteralControl(

                    @”<div id=’” + g.ClientID + @”_btnPlus” + rowIndex + @”‘ style=”"display:” + ((displayPlus) ? “block” : “none”) + @”"” class=” + CssClassPlus + @”

                    onclick=”"javascript:” + sExpand + @”"”></div>”);

                cell.Controls.Add(lcPlus);

 

                LiteralControl lcMinus = new LiteralControl(

                    @”<div id=’” + g.ClientID + @”_btnMinus” + rowIndex + @”‘ style=”"display:” + ((!displayPlus) ? “block” : “none”) + @”"” class=” + CssClassMinus + @”

                    onclick=”"javascript:” + sCollapse + @”"”></div>”);

                cell.Controls.Add(lcMinus);

            }

 

        }

 

    }