Contents tagged with ASP.NET

  • Hacking the ASP.NET Parser

    A while ago I blogged about using external templates in an asp.net application. The problem with that was it wasn’t natively supported, so I had to create a derived SpecialRepeater in order to take advantage of the external template system.

    In this latest parser hack I’ve managed to “trick” the asp.net parser into letting me declaratively set any template property to be a virtual path in order to load a user control into that template at runtime.

    How it works

    The ASP.NET compliation system is pretty complex. There are all kinds of extensibility points, including BuildProviders, PageParserFilters, ControlBuilders, ExpressionBuilders and the list goes on. One unknown *feature* of the parser is it’s ability to generate code from something called an InstanceDescriptor.

    The parser has a special way of dealing with ITemplate properties so if we try to do this:

    <asp:Repeater ID="repeater" runat="server" ItemTemplate="~/MyUserControl.ascx">
    </asp:Repeater>

    It fails because the there is no way to convert the string “~/MyUserControl.ascx” into an ITemplate.

    The parser uses the TypeConverter attribute defined on properties it parses to aid in the conversion.  Enter TypeDescriptionProvider. These complex beasts are used at the heart of all designers in Visual Studio. There are used for things like populating the property grid, and adding and removing properties dynamically, basically a general purpose metadata API (think of it as an abstraction on top of reflection).

    VirtualPathTemplate

    The code we are going to generate will instantiate a VirtualPathTemplate with a virtual path pointing to a user control on our site. Normally when you define a template in markup, a special type called CompiledTemplateBuilder (which points to a delegate that builds the template at runtime) is assigned to it. We want to replace a line of code that looks like this:

    repeater.ItemTemplate = new CompiledTemplateBuilder(BuildTemplate);

    to this

    repeater.ItemTemplate = new VirtualPathTemplate("~/MyUserControl.ascx");

    TypeDescriptionProvider

    After overriding about 4 classes (TypeDescriptionProvider, CustomTypeDescriptor, PropertyDescriptor, and finally TypeConverter) we are able to control what happens when the parser asks, “can we convert “~/MyUserControl.ascx” to an ITemplate?”.

    Here is the code for the TemplateTypeConverter:

     

    public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) {
        // Allow InstanceDescriptor so that the code gen engine can use it to generate the correct
        // code for the ITemplate property
        return destinationType == typeof(InstanceDescriptor) || _converter.CanConvertTo(context, destinationType);
    }

    public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) {
        var descriptorProvider = value as IInstanceDescriptorProvider;
        if (descriptorProvider != null) {
            return descriptorProvider.Descriptor;
        }
        return _converter.ConvertTo(context, culture, value, destinationType);
    }

    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) {
        return sourceType == typeof(string) || _converter.CanConvertFrom(context, sourceType);
    }

    public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) {
        string stringValue = value as string;
        
        if (stringValue != null) {
            if (stringValue.StartsWith("~/")) {                    
                // Assume this is a virtual path and return the instance description provider
                // for it
                return new VirtualPathInstanceDescriptorProvider(stringValue);
            }                
        }
        return _converter.ConvertFrom(context, culture, value);
    }

     

    First the parser asks the converter if it can convert from a string so we always say yes. In ConvertFrom we try to convert the value to a string and check if the path starts with “~/” in order to determine if it’s a virtual path. If it is a virtual path we return an object that knows how to get an InstanceDescriptor from the virtual path (VirtualPathInstanceDescriptorProvider). Now we have successfully parsed the control.

     

    Next the code generator tries to generate code for the ITemplate property. The code generator will eventually ask if it can convert the object we returned earlier (VirtualPathInstanceDescriptorProvider) to an InstanceDescriptor. The implementation of VirtualPathInstanceDescriptorProvider returns an instance descriptor that wraps a constructor info for a custom template we are going to use:

    internal class VirtualPathInstanceDescriptorProvider : IInstanceDescriptorProvider {
        private string _virtualPath;
        private static ConstructorInfo s_Constructor = GetConstructor();

        private static ConstructorInfo GetConstructor() {
            return typeof(VirtualPathTemplate).GetConstructor(new[] { typeof(string) });
        }

        public InstanceDescriptor Descriptor {
            get {
                return new InstanceDescriptor(s_Constructor, new[] { _virtualPath });
            }
        }

        public VirtualPathInstanceDescriptorProvider(string virtualPath) {            
            _virtualPath = virtualPath;
        }        
    }

    The code generation engine then generates the resulting code we wanted to specify above using the constructor info and virtual path.

    At runtime we use BuildManager.CreateInstanceFromVirtualPath(“~/MyUserControl.ascx”) to create an instance of the user control and add it to the control’s collection and we’re done.

    public class VirtualPathTemplate : ITemplate {
        private string _virtualPath;
        public VirtualPathTemplate(string virtualPath) {
            _virtualPath = virtualPath;
        }

        public void InstantiateIn(Control container) {
            // Try to create the control from the virtual path
            Control control = (Control)BuildManager.CreateInstanceFromVirtualPath(_virtualPath, typeof(Control));

            if (control != null) {
                // Add it to the controls collection
                container.Controls.Add(control);
            }            
        }
    }

    Caveats

    In order to get the parser to use your TypeDescriptionProvider you have to make one call (which is nicely wrapped up in an API in the sample ParserConfiguration.RegisterTemplateOverride()) which registers a TypeDescriptionProvider for the control type so that it will work with any control that has an ITemplate property. If you are writing custom controls you can also specify the TypeConverter attribute and use this same trick to support this without the global override.

    Also design time support is busted with this, but hey its a hack what would you expect :).

    Check out the sample here.

    Note: Code sample was built using vs2010 beta 2 but nothing should stop it from working on 3.5 sp1.

    Read more...

  • More Data Binding

    Last time I tried to solve one of the deficiencies of data binding by taking advantage of expando attributes. Today I want to throw an idea out there that I’ve been playing with since that blog post.I was looking at WPF’s data binding and wondered what it would take to have data binding in ASP.NET be as first class as data binding in WPF.

    Read more...

  • Databinding 3.0

    There was a post on our internal discussion group recently where a customer pointed out one of the weaknesses of 2 way data binding not working within user controls. Consider the following page:

    Read more...

  • My first channel 9 video!

    Wow, I haven't blogged in a while...but that will end soon. Watch me talk about the QueryExtender control. It's a new control we added in ASP.NET 4.0 to make Linq queries even simpler. The new control works with LinqDataSource and EntityDataSource. We also built all of the new DynamicData filters on top of this new control.

    Check it out here

    Read more...

  • Invalid postback or callback argument

    I'm sure many of you have seen this error message when developing your web application:

    Server Error in '/' Application.
    --------------------------------------------------------------------------------

    Invalid postback or callback argument.  Event validation is enabled using <pages enableEventValidation="true"/> in configuration or <%@ Page EnableEventValidation="true" %> in a page.  For security purposes, this feature verifies that arguments to postback or callback events originate from the server control that originally rendered them.  If the data is valid and expected, use the ClientScriptManager.RegisterForEventValidation method in order to register the postback or callback data for validation.

    I'm going to discuss this in the context of the data controls. This happens when a control that isn't registered for event validation causes a postback, but surely that can't be the case.. right?

    Let's look at a small repro:

    Markup:

    <asp:GridView ID="GridView1" runat="server">
        <Columns>
            <asp:TemplateField>
                <ItemTemplate>
                    <asp:Button runat="server" Text="Button" />
                </ItemTemplate>
            </asp:TemplateField>
        </Columns>
    </asp:GridView>

    Code behind:

    public partial class _Default : System.Web.UI.Page {
        protected void Page_Load(object sender, EventArgs e) {
            GridView1.DataSource = Enumerable.Range(0, 5);
            GridView1.DataBind();
        }
    }

    Now click on the button and see the dreaded error message. Why does this happen? EventValidation was added in ASP.NET to ensure that controls causing the postback came from the same page being rendered. Take a look at __EVENTVALIDATION hidden field on the page. It is a serialized version of all of the controls registered for postbacks(read more here). You might be wondering how they got in there and why is the button inside of a GridView a special case. It's not a special case, in fact, Button registers itself with the current page.

    The reason this happens is because we rebind the data control in Page_Load every time which means that we will lose all of the posted data and viewstate. As a result, the ID of the button is different and when the event is validated there will be no matching unique id and hence event validation will fail. We are acutally raising an event for a button that is no longer in the control tree.

    You can work around this by wrapping that code in if (!IsPostBack). This is a good proof of why you should use DataSource controls.

    Hope this helps

    Read more...

  • A new way to DataBind()

    The Problem 

    I've been thinking alot recently about the problems with data binding (and they're alot). There are some patterns that play well with ASP.NET (and are repeated everywhere) and some that don't quite fit the model. One of those patterns that don't mesh well is, setting the DataSource property of any of the DataControls. Before ASP.NET 2.0 and DataSource controls we'd have to set the DataSource property and manually call DataBind.

    What's wrong with this you may ask? When are you supposed to DataBind? Page_Load ? Page_Init? Page_PreRender? I'm sure anyone that has had to manually data bind has had the problem of figuring out which code goes in and out of the IsPostBack block.

    Whats the solution?

    I'm looking at an alternative approach to this problem. Event handlers are nice because we all feel confident that the author of the event knows when that event should be raised. No need to figure out the postback logic because the control author already thought about that. All you have to worry about is handling the event.

    I propose a new hypothetical event OnDataRetrieveing, that would be on all DataControls in conjunction with a new flag AutoBind to enable it.

    Let's take a look at a sample implementation of a derived GridView control that has this new behavior. First the markup:

    <custom:AutoGridView 
        OnDataRetrieving="OnDataRetrieving"
        OnPageIndexChanging="OnGridViewPageIndexChanging"
        OnRowEditing="OnGridViewRowEditing"
        OnRowCancelingEdit="OnGridViewRowCancelingEdit"
        AllowPaging="true" 
        runat="server" 
        ID="GridView1" 
        AutoGenerateEditButton="true"
        AutoBind="true">
    </custom:AutoGridView>

    Looks pretty similar to the regular GridView besides the AutoBind="true" flag and OnDataRetrieving event.

    And the code behind

    protected void OnGridViewRowCancelingEdit(object sender, GridViewCancelEditEventArgs e) {
        GridView1.EditIndex = -1;
    }
     
    protected void OnGridViewPageIndexChanging(object sender, GridViewPageEventArgs e) {
        GridView1.PageIndex = e.NewPageIndex;
    }
     
    protected void OnGridViewRowEditing(object sender, GridViewEditEventArgs e) {
        GridView1.EditIndex = e.NewEditIndex;
    }
     
    protected void OnDataRetrieving(object sender, DataBindingEventArgs e) {
        NorthwindDataContext db = new NorthwindDataContext();
        e.DataSource = db.Products;
    }

    We write code as we normally would if we'd been binding manually, but instead, we assign our data to the DataSource property in the DataBindingEventArgs (Which gets called at the "right" time).

    So what is the right time to Databind and why does this control do it better than you? I'm not sure :) but let's look at the source of this control.

    public override object DataSource {
        get {
            return base.DataSource;
        }
        set {
            if (!_settingDataSource && AutoBind) {
                throw new InvalidOperationException("When AutoBind is enabled, setting the DataSource property explicitly is not allowed.");
            }
            base.DataSource = value;
        }
    }
     
    protected override void PerformSelect() {
        if (AutoBind) {                
            DataBindingEventArgs args = new DataBindingEventArgs();
            OnDataRetrieving(args); 
            _settingDataSource = true;
            DataSource = args.DataSource;
            _settingDataSource = false;
        }
        base.PerformSelect();
    }
     
    protected override void EnsureDataBound() {
        if (RequiresDataBinding && (AutoBind || IsBoundUsingDataSourceID)) {
            DataBind();
        }
    }

    The most interesting method is EnsureDataBound. This method is called from PreRender and the original condition for making the control DataBind is:

    if (this.RequiresDataBinding && ((this.DataSourceID.Length > 0) || this._requiresBindToNull)) {
        this.DataBind();
        this._requiresBindToNull = false;
    }

    In the case of manually binding this would never be called unless _requiresBindToNull is true. Our AutoGridView control changes this logic by detecting the AutoBind flag and continues to data bind as usual in PreRender.

    If you interested in when the event gets called you can put some break points in the control's code. What do you think about this alternative?

    Here is a link to the source.

    Read more...

  • Client side devevelopment in ASP.NET

    We ASP.NET developers know how much of a pain it is writing javascript in any app we have today because of naming container madness! You've probably done something like this:

    function DoSomeThingCool() {
        var textBox = document.getElementById('ct100_contentplaceholder1_TextBox1');
    }

    or something not so hardcoded

    function DoSomeThingCool() {
        var textBox = document.getElementById('<%= TextBox1.ClientID %>');
    }

    Matthew Osborn, QA on the ASP.NET team has a great post on a new ASP.NET 4.0 feature that gives developers more control over how ClientIDs are generated.

    Read more...

  • Follow me on twitter!

    I've finally got sucked into twitter after avoiding it for many months. Check me out:

    http://twitter.com/davidfowl/

    Since my name is so common it really annoys me when I sign up for any new service because I have to get accept some partially mangled version of my name (i.e. davidfowl).

    Anyways, stay tuned!

    Read more...

  • AutoFieldGenerators

    In 3.5 SP1 we added new properties to GridView and DetailsView which allows the page developer to change the way AutoGenerateColumns creates its columns. This feature is well know in Dynamic Data, but it is not tied to this technology. Dynamic Data takes advantage of this by looking at the meta data that users set on properties to generate columns.

    You too can roll your own IAutoFieldGenerator. Lets look at the interface:

    public interface IAutoFieldGenerator {

        ICollection GenerateFields(Control control);

    }

    The interface itself is pretty weird but it gets the job done. GenerateFields takes the control that we're generating the fields for, and expects to get some ICollection of stuff back. If we had the chance to redo this interface we'd probaby rewrite it to be like this:

    public interface IAutoFieldGenerator {

        IEnumerable<DataControlField> GenerateFields(Control control);

    }

    Now it's clear what we expect to get back, but thats besides the point. Lets implement our own.

    public class ColumnGenerator : IAutoFieldGenerator {

    private IEnumerable<string> _columns;

    public ColumnGenerator(IEnumerable<string> columns) {

        _columns = columns ?? Enumerable.Empty<string>();

    }

     

    public ICollection GenerateFields(Control control) {

        return (from column in _columns

                select new BoundField {

                    SortExpression = column,

                    HeaderText = column,

                    DataField = column

                }).ToArray();

        }

    }


    We're going to pass a set of column names to our generator that just creates bound fields with the column's name. To make use of our new generator we can just set it like this:

    GridView

    gridView1.ColumnsGenerator = new ColumnGenerator(Columns);

    DetailsView

    detailsView1.RowsGenerator = new ColumnGenerator(Columns);

    You can do alot of cool things with these generators. Some things I can think of off the top of my head:

    • Hide/Show columns dynamically based on metadata (like what we do with dynamic data)
    • Hide/Show columns based on permissions
    • Create a configurable UI that allows users to hide or show columns based on their preferences.

    I've written a sample that allows you to hide or show columns based on a selectable UI. You can download it here:

    AutoFieldGenerator.zip

    Here is a screen shot of it running.

    Read more...

  • How <%# Bind %> Works

    In my last post I spoke about 2-way databinding and how it can be used to extract values from control properties. How does this all work? Lets take a look at a page with 2-way databinding:

    <asp:LinqDataSource ID="productsSource"

        runat="server"

        ContextTypeName="FowlerSamples.NorthwindDataContext"

        EnableDelete="True"

        EnableInsert="True"

        EnableUpdate="True" TableName="Products">

    </asp:LinqDataSource>       

    <asp:GridView ID="products"

        DataKeyNames="ProductID,CategoryID"

        AutoGenerateColumns="False"

        runat="server" DataSourceID="productsSource">

        <Columns>

            <asp:CommandField ShowEditButton="True" />               

            <asp:BoundField DataField="ProductName" />

            <asp:TemplateField>

                <EditItemTemplate>

                    <asp:LinqDataSource

                        ID="categoriesSource"

                        runat="server"

                        ContextTypeName="FowlerSamples.NorthwindDataContext"

                        TableName="Categories" AutoGenerateWhereClause="true">

                    </asp:LinqDataSource>

                    <asp:DropDownList

                        runat="server"

                        ID="categories"

                        DataSourceID="categoriesSource"

                        DataTextField="CategoryName"

                        DataValueField="CategoryID"

                        SelectedValue='<%# Bind("CategoryID") %>'>                           

                    </asp:DropDownList>                       

                </EditItemTemplate>                   

            </asp:TemplateField>

        </Columns>

    </asp:GridView>

     In the above example, the GridView has a template field with an EditItemTemplate that has a DropDownList that is 2-way databound. We're going to introduce a small error in the page in order to see what the generated code looks like:

    <EditItemTemplate>

         <asp:LinqDataSource

             ID="categoriesSource"

             runat="server"

             ContextTypeName="FowlerSamples.NorthwindDataContext"

             TableName="Categories" AutoGenerateWhereClause="true">

         </asp:LinqDataSource>

         <asp:DropDownList

            runat="server"

            ID="categories"

            DataSourceID="categoriesSource"

            DataTextField="CategoryName"

            DataValueField="CategoryID"

            SelectedValue='<%# Bind("CategoryID") %>'>                           

        </asp:DropDownList>

        <%# Eval(3) %>

    </EditItemTemplate>


    When we try to run this page we'll get a compile error and the famous ASP.NET YSOD(Yellow Screen of Death):



    Click on Show Complete Compilation Source, if your curious about how ASP.NET converts your the markup to code.

    When examining the source, we see a rather interesting method:

    [System.Diagnostics.DebuggerNonUserCodeAttribute()]

    public System.Collections.Specialized.IOrderedDictionary @__ExtractValues__control8(System.Web.UI.Control @__container) {

        System.Collections.Specialized.OrderedDictionary @__table;

        System.Web.UI.WebControls.DropDownList categories;

     

        categories = ((System.Web.UI.WebControls.DropDownList)(@__container.FindControl("categories")));

     

        @__table = new System.Collections.Specialized.OrderedDictionary();

     

        if ((categories != null)) {

            @__table["CategoryID"] = categories.SelectedValue;

        }

     

        return @__table;

    }


    As you can see in the above method, an OrderedDictionary is created and the SelectedValue property of the DropDownList is pushed into the "CategoryID" field. But how does this get all the way to the data control? Each control has a BuildControl method associated with it, if we examine the BuildControl method for the TemplateField it becomes a bit more clear how things get hooked up.

     

    [System.Diagnostics.DebuggerNonUserCodeAttribute()]

    private global::System.Web.UI.WebControls.TemplateField @__BuildControl__control7() {

        global::System.Web.UI.WebControls.TemplateField @__ctrl;

     

        @__ctrl = new global::System.Web.UI.WebControls.TemplateField();

     

        @__ctrl.EditItemTemplate = new System.Web.UI.CompiledBindableTemplateBuilder(new System.Web.UI.BuildTemplateMethod(this.@__BuildControl__control8), new System.Web.UI.ExtractTemplateValuesMethod(this.@__ExtractValues__control8));

     

        return @__ctrl;

    }

    The EditItemTemplate property is of type ITemplate. CompiledBindableTemplate implements both ITemplate and IBindableTempalte.

    public interface IBindableTemplate : ITemplate {           

        IOrderedDictionary ExtractValues(Control container);

    }

    It's slowly coming together. So lets put together what we're learnt so far:

    1. Code Gen creates ExtractValues method that returns the dictionary of values for each ITemplate that has a Bind expression.
    2. The BuildControl method for the ITemplate's container (TemplateField in this case) assigns a new CompiledBindableTemplate to an ITemplate (EditItemTemplate in this case)
    3. CompiledBindableTemplate implements IBindableTemplate, which has a method, ExtractValues which returns the dictionary given a container.

    It almost all makes sense now. Each data control uses this mechanism to extract values from template fields with 2-way databinding expressions.
    What can you do with your new found knowledge?

    <asp:FormView

        runat="server"

        ID="formView"

        DefaultMode="Edit">

        <EditItemTemplate>

            <asp:TextBox ID="textBox" runat="server" Text='<%# Bind("Name") %>'></asp:TextBox>

            <asp:TextBox ID="textBox1" runat="server" Text='<%# Bind("Age") %>'></asp:TextBox>

            <asp:Button runat="server" ID="updateButton" CommandName="Update" Text="Update" />

        </EditItemTemplate>

    </asp:FormView>


    And the code behind:

     

    class Person {

        public string Name { get; set; }

        public int Age { get; set; }

    }

     

    protected void Page_Load() {

        formView.ItemUpdating += formView_ItemUpdating;

        if (!IsPostBack) {

            formView.DataSource = new Person[] { new Person { Name = "David", Age = 22 } };

            formView.DataBind();

        }

    }

     

    protected void formView_ItemUpdating(object sender, FormViewUpdateEventArgs e) {

        IBindableTemplate template = formView.EditItemTemplate as IBindableTemplate;

        if (template != null) {

            IOrderedDictionary values = template.ExtractValues(formView);

            Response.Write(values["Name"]);

            Response.Write(values["Age"]);

        }

    }


    How cool is that? :)

    Read more...