Thursday, May 26, 2011

Programmatically Setting Header on ASP.NET GridView

It is actually very frustrating to change the header text on an ASP.NET GridView if you don’t do it in the right place. At first, I thought I would do it in the PreRender event as I figured that was plenty late in the cycle and should stick. Then I noticed it would not stick when I would sort for example.

In the end, it was very easy once I figured out what the proper event in the page lifecycle to use. You answer to this riddle is the RowCreated event. My GridView is called GridView1 in this example. I added onrowcreated=”GridView1_RowCreated” to my GridView in my .aspx file. In my code behind this is the code I used.

protected void GridView1_RowCreated(object sender, GridViewRowEventArgs e)
{
    if (e.Row.RowType == DataControlRowType.Header && e.Row.Cells.Count > SOME_COLUMN_INDEX)
    {
        e.Row.Cells[SOME_COLUMN_INDEX].Text = "New Header Text here";
    }
}

Some things to know. You can’t reference the HeaderRow because it is still null at this point. It is there, but not accessible via the GridView1.HeaderRow property. You need to use e.Row instead. You can be guaranteed that this is not null since we are its event handler. You may also want to make sure the cell you are trying to set is not out of bounds. As long as you check to make sure you are in the header row you should be ok though. An example of when you would not have cells is when you get the EmptyData row which is displayed only when there are no rows to display. In that case, there are no headers. So, in general, I found it safe to always check to make sure the item I assume is in the array is actually in the array.

Wednesday, May 18, 2011

Programmatically adding Query Parameter to ASP.NET DomainDataSource

I love the new ASP.NET DomainDataSource because it allows me to talk to my Domain Service.

You could do this declaratively like the following:

Declaratively

<asp:DomainDataSource ID="dsDetails" runat="server">
<QueryParameters>
        <asp:ControlParameter ControlID="GridView1" PropertyName="SelectedValue" Name="id" Type="Int32" />
</QueryParameters>
</asp:DomainDataSource>

In this example, this a DomainDataSource that I have connect to a FormView. I want the FormView to update when a GridView called GridView1.SelectedValue changes. I am setting the QueryName and the DomainServiceTypeName in my Page_Init so you don’t see it in the declaration above, but you could put it there as well.

In this scenario, the Query in my Domain Service is expecting a parameter called id. This must the Name property of the ControlParameter control.

Programmatically (Good Option)

We can do the same thing with the parameters, but this time do it programmatically. The declaration would now look like this:

<asp:DomainDataSource ID="dsDetails" runat="server">
</asp:DomainDataSource>

Notice there is not QueryParameters declared, so we need to do this in code. The best place is in the Page_Init() method since it is very early in the page lifecycle.

Add the following line to the Page_Init() method:

dsDetails.QueryParameters.Add(new ControlParameter("id", TypeCode.Int32, "GridView1", "SelectedValue"));

This will do exactly the same as doing it declaratively, but now you nave more control of all the values passed since you are in code. One thing to point out is that when the GridView1.SelectedValue changes, the dsDetails datasource executes the query again automatically. That is why I like this method.

Programmatically (Okay Option)

The above is identical and great and my first choice. This approach here works best if you are getting your value from something that doesn’t change like the QueryString. If you need to set the current user, or some other value (like a querystring value) and don’t care that the dsDetails DomainDataSource will NOT be updated automatically and you will instead need to tell it to databind using something like SelectedItemChanged in the GridView1, you can use the Querying event on the DomainDataSource. In this case you would do the following.

<asp:DomainDataSource ID="dsEditor" runat="server"  
    onquerying="dsEditor_Querying" >
</asp:DomainDataSource>

protected void dsDetails_Querying(object sender, Microsoft.Web.UI.WebControls.DomainDataSourceQueryingEventArgs e)
{
    e.QueryParameters.Clear();
    e.QueryParameters.Add("id", GridView1.SelectedValue);
}

Wednesday, May 4, 2011

Why aren’t my ForeignKeys being loaded on my Custom Dynamic Data page?

This is one entry in a series of blog entries:

If you look at Part I and Part II, you may have noticed that the Product Category is a textfield when it should be a Drop Down List of the Product Categories. If this were a page that was in the DynamicData\CustomPages directory then you would not be having this problem. The reason we are having this problem is because we are calling EnableDynamicData() on our FormView which is great to get basic dynamic data functionality, but it doesn’t know how to load all the metadata for the Product entity. If you debug you will see that the ProductCategory DynamicControl column is not of type MetaForeignKeyColumn. After futher debugging you will see that much of the other metadata from the model is actually missing as well. Thankfully, the fix is very easy.

Change from:

protected void Page_Init(object sender, EventArgs e)
{
     FormView1.EnableDynamicData(typeof(MyDynamicDataExample.Models.Product));
}

to this:

protected void Page_Init(object sender, EventArgs e)
{
    // to get all the goodness like foreign-keys, etc we need to tell it to use our model
    FormView1.SetMetaTable(Global.DefaultModel.GetTable(typeof(MyDynamicDataExample.Models.Product)));
}

Also, from what I can tell the AutoLoadForeignKeys=”true” attribute of the DynamicDataManager has no bearing on whether the Drop Down List is populated or not.

You may have also noticed that the ProductCategory field when in ReadOnly mode shows a link with an number showing instead of the nice text description of the product category.  Luckily, this is an easy fix as well. In the AWDomainService.cs find we need to change the GetProducts() method to include the ProductCategory navigation property. This will cause the SQL that is generated to bring in the ProductCategory table.

Change from:

public IQueryable<Product> GetProducts()
{
    return this.ObjectContext.Products.OrderBy(o => o.Name);
}

to this:

public IQueryable<Product> GetProducts()
{
    return this.ObjectContext.Products.Include("ProductCategory").OrderBy(o => o.Name);
}

The change above will do the trick. However, if you are using this Domain Service with a Silverlight Client, you should also update the AWDomainService.metadata.cs such that the ProductCategory property on the ProductMetadata class has the Include() attribute. If you don’t have both of these Silverlight will not populate the ComboBox (assuming that is what you are using in Silverlight).

Change from:

[Display(Name = "Product Category")]
public ProductCategory ProductCategory { get; set; }

to this:

[Include()]
[Display(Name = "Product Category")]
public ProductCategory ProductCategory { get; set; }

You can download the complete source here.