June 2005 - Posts

ASP.NET Today: GridView Gripes
30 June 05 03:15 PM | despos | 9 comment(s)

The following was a rather common task to accomplish with DataGrids. Users click a button in a button column (say, Add to Cart), a server-side event fire (ItemCommand), you handle it and do what's needed. This basic pattern doesn't change with GridViews, but some details are different and require different approaches.

First and foremost, the index of the clicked row is not carried by the event data structure (as with DataGrids), but can be found through the CommandArgument property. Here's how:

protected void GridView1_RowCommand(object sender, GridViewCommandEventArgs e)
{
    if (e.CommandName.Equals("Add"))
    {
      int index = Convert.ToInt32(e.CommandArgument);
      :
    }
}

The Beta 2 documentation mentions you should put the index in the CommandArgument manually by writing a RowCreated event handler. Some sample code in the same Beta 2 documentation uses the code above and it works, at least for GridViews.

At this point, you need to retrieve in the postback some values to identify the corresponding data row--the one whose data you need to process.

  • Option #1: You set one or more key fields to be carried on through the DataKeys collection. Since DataKeys supports multiple fields (it is only one with DataGrids), you can see it as a kind of personal cargo collection for you to retrieve quick-to-use data. You simply use the key field(s) if you need to go down to the database to obtain fresh data.
  • Option #2: With DataGrids you can try a better approach: get the data item index (not the index of the row in the grid, but the index of the corresponding data item in the data source), get the bound enumerable set of rows, and retrieve the data. This works with GridView if you bind through DataSource, because DataSource can return a non-empty collection of data--typically, a DataView object. If you bind the GridView to a data source control through DataSourceID, it doesn't work because the real set of bound rows are not accessible programmatically. 

The code below shows how to retrieve (without running a new query) information about the product in the row where you clicked a Add command button.

     protected void GridView1_RowCommand(object sender, GridViewCommandEventArgs e)
    {
        if (e.CommandName.Equals("Add"))
        {
            // Get the index of the clicked row
            int index = Convert.ToInt32(e.CommandArgument);

            // Add the item to the shopping cart
            AddToShoppingCart(index);
        }
    }

    private void AddToShoppingCart(int rowIndex)
    {
        DataKey data = GridView1.DataKeys[rowIndex];

        ShoppingItem item = new ShoppingItem();
        item.NumberOfItems = 1;
        item.ProductID = (int) data.Values["productid"];
        item.ProductName = data.Values["productname"].ToString();
        item.UnitPrice = (decimal) data.Values["unitprice"];
        MyShoppingCart.Add(item);

        ShoppingCartGrid.DataSource = MyShoppingCart;
        ShoppingCartGrid.DataBind();
    }

In my sample page, ShoppingCartGrid is a grid I use to show the contents of the shopping cart. It is bound to a custom collection object of type ShoppingCart stored in the Session and retrieved through the public MyShoppingCart. Finally, DataKeys. By design, it returns an array of DataKey objects with as many fields as there are elements in the DataKeyNames property of the GridView. For the code above to work, you need the following GridView declaration:

<asp:GridView ID="GridView1" runat="server" DataSourceID="SqlDataSource1" BackColor="white"  
    DataKeyNames
="productid,productname,unitprice"
    AutoGenerateColumns="false" AllowPaging="true"
    OnRowCommand
="GridView1_RowCommand">
    <Columns>
       
<asp:boundfield datafield="productname" headertext="Product" />
        <asp:boundfield datafield="quantityperunit" headertext="Packaging" />
        <asp:boundfield datafield="unitprice" headertext="Price" DataFormatString="{0:c}" />
        <asp:buttonfield buttontype="Button" text="Add" CommandName="Add" />
    </Columns>
</asp:GridView>

 

 

 

 

The DataGrid is Dead? Long-live the DataGrid (Part 1 of N)
29 June 05 10:47 AM | despos | with no comments

I'm writing chapter 10 of "Programming ASP.NET--Fundamentals" entirely dedicated to grids. I split the chapter in two parts--DataGrid and GridView. In the first part, I briefly discuss the main traits of DataGrids and try to compare them to the enhancements brought by the new big brother. In other words, I'm apparently covering the DataGrid control in the book only to show it in a bad light. Apparently? It was a tough decision, actually.

The more I think of it, the more the conclusion appears clear and crystalline. Drop the DataGrid and embrace the GridView--no matter  you're building a new app from scratch or simply adapting an existing one. GridView and DataGrid have nearly the same high-level capabilities, but the GridView has a strong built-in support for data source controls. With DataGrids, you need to be familiar with best practices for paging and sorting, and need to implement them. With GridViews, you still need to be aware of practices but need to write much less code (often, no code at all) and the overall API of the control guides you to write that little code in the best way.

Consider classic paging. In ASP.NET 1.x, you retrieve a DataSet and bind it to DataSource. Next, you enable paging and write a handler for PageIndexChanged. This approach is hardly optimal because it retrieves the whole data set for each and every postback caused by the grid. You need caching. In the end, this means you build an extra layer over your DAL to support ASP.NET caching. In ASP.NET 2.0, if you use the GridView control you can handle of this burden at design-time. If you stick to DataGrids it gets simplified if you employ data source controls.

protected void grid_PageIndexChanged(object sender,
         DataGridPageChangedEventArgs e)
{
    grid.CurrentPageIndex = e.NewPageIndex;

    // Must be repeated to force a refresh
    grid.DataSourceID = "SqlDataSource1";
}

To enable caching, you simply turn on EnableCaching on the SqlDataSource component. Note that you still need to reassign DataSourceID to trigger an internal data source changed event and cause the control to load its new data.

My idea is that DataGrid doesn't really belong to ASP.NET 2.0 but has been reasonably maintained for compatibility, as it is one of the most used controls in ASP.NET 1.x apps. My suggestions go along the following guidelines:

  • Don't touch anything of how it works today if you don't have time/budget for a serious redesign.
  • Don't switch to GridView only to save a few event handlers. Embracing the GridView is a more thoughtful choice; don't use GridView like a DataGrid.
  • If you have time/budget, or if you're building a new app, by all means go with GridViews.
  • To use a GridView at its fullest probably requires you to reconsider the DAL--which is not bad anyway :-)
Atlas to carry the world of scripting
28 June 05 06:37 PM | despos | 4 comment(s)

Now it's official--ASP.NET 2.0 (and even more future versions) will provide a strong support for cross-browser scripting in AJAX-style. Read the news here

ScottGu and NikhilK bring more to the table. Wait (until PDC) and see. Sounds *really* interesting.

Dealing with validation in cross-page postings
17 June 05 06:28 PM | despos | 8 comment(s)

Imagine you're setting up a cross-page call. What if the original page contains validators? Imagine a page with a text box whose value is to be posted to another page. You don’t want the post to occur if the text box is empty. To obtain this, you add a RequiredFieldValidator control and bind it to the text box.

<asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>

<asp:RequiredFieldValidator ID="Validator1" runat="server"

     ControlToValidate="TextBox1" Text="*" />

<asp:Button ID="Button1" runat="server" Text="Apply request..."

     OnClick="Button1_Click" PostBackUrl="targetpage.aspx" />

As expected, when you click the button the page won’t post if the text box is empty; and a red asterisk (plus an optional message) is displayed to remark the error. This is because by default button controls validate the input controls before proceeding with the post. Is that all or is there more to dig out?

In most cases, the RequiredFieldValidator benefits of the client-side capabilities of the browser. This means that in case of empty text boxes the button doesn’t even attempt to make the post. Let’s try with a CustomValidator control which instead requires that some server-side code is run to check the condition. Can you spot the scenario? You’re on, say, post.aspx and want to reach target.aspx; to make sure you post only under valid conditions, though, you first need a trip to post.aspx to perform some validation. Add this control, write the server validation handler and put a breakpoint in its code.

<asp:CustomValidator ID="CustomValidator1" runat="server" Text="*"

     ControlToValidate="TextBox1" OnServerValidate="ServerValidate" />

Debugging this sample page reveals that posting to another page is two-step operation. First a classic postback is made to run any server-side code registered with the original page (for example, server-side validation code or code associated with the click of the button). Next, the cross-page call is made to reach the desired page.

void ServerValidate(object source, ServerValidateEventArgs args)

{

   args.IsValid = false;

   if (String.Equals(args.Value, "Dino"))

       args.IsValid = true;

}

The preceding code sets the page’s IsValid property to false if the text box contains other than Dino. However, this fact alone doesn’t prevent the transition to the target page. In other words, you could have invalid input data posted to the target page.

Fortunately, this issue finds an easy workaround, as below.

if (!PreviousPage.IsValid)

{

   Response.Write("Sorry, the original page contains invalid input.");

   Response.End();

   return;

}

In the target page, you test the IsValid property on the PreviousPage property and terminates the request in case of a negative answer.

More on ASP.NET Callbacks
17 June 05 03:49 PM | despos | 3 comment(s)

I'm still having troubles with the ASP.NET client callback mechanism with non-IE browsers. However, it seems that all of my troubles originate from imperfect, because likely not standard, DOM calls. I can now have the Web server respond to the calls of a FireFox client page. But I'm still looking for a plausible explanation of the following "miracle". I do have sample pages that, when run in FireFox, update the UI through innerHTML. (It seems that FireFox doesn't accept innerText.) If I do the same in a Javascript callback invoked within a callback, setting innerHTML doesn't work. Nicely enough, one of the official samples you get from MS, just uses innerHTML to refresh the page--and it works for me too through FireFox.

Mysteries of a late spring night 

Callbacks and FireFox
15 June 05 04:47 PM | despos | 9 comment(s)

It's probably just me, but I'm totally unable to have ASP.NET script callback properly work through FireFox or Netscape 7. The example that works just fine with IE 6 doesn't do much if run through FireFox. If I pass a constant as an argument to the callback, it is executed on the server but back on the client the Javascript callback is never invoked. If I pass an expression, some (undisclosed) error happens along the way and nothing happens.

This is as of Beta 2.

Anybody out there with some experience to share and perhaps more luck?

 

 

ASP.NET Today: Data Binding
15 June 05 11:17 AM | despos | 2 comment(s)

Last night I've just finished chapter 9 (out of 17) of the first book. Chapter 9 is likely to be the longest chapter (about 80 printed pages and 10+ examples) in the first book. It covers all aspects of data binding, basically three:

  • Classic binding as in ASP.NET 1.x
  • Data binding expressions (with the improvements brought by ASP.NET 2.0)
  • Data source controls

Classic binding consists of binding an IEnumerable object to the DataSource property of a data-bound control. This approach has the drawback of requiring a bit too much code even for relatively simple and common scenarios. It is fully supported in ASP.NET 2.0. Data binding expressions are those familiar <%# ...%> expressions you can declaratively insert in your ASPX files. It is important to note that those expressions are processed only during a data binding operation--an operation triggered by a call to DataBind. Without a call to DataBind, no expression will ever be evaluated. ASP.NET 2.0 introduces a new model of expressions prefixed with $ symbol instead of #. These expressions require a custom processor (a .NET class from ExpressionBuilder) but work outside the data binding boundaries. ASP.NET 2.0 provides a few built-in expression builders for connection strings, app settings, and resources. As a result, the following is valid markup:

<asp:SqlDataSource ...
    ConnectionString="<%$ ConnectionStrings:LocalNWind %>"
/>

LocalNWind is the name of a connection string stored in the web.config file.

Finally, data source controls are wrappers around IEnumerable-based collections of data. As controls, they have access to caching and viewstate page services and are designed to intelligently work with certain data-bound controls. The motivation for data source controls is saving some boilerplate code by adding intelligence to data source controls and binding them to data-bound controls instead of plain data. The interaction model defined between new and smarter data-bound controls and data source components does the rest. A control executes an operation on the data source to get data, but also to update data or insert and delete data. Want a quick example? Think of a DataGrid control. When you click to see a new page of data, the control fires the PageIndexChanged event. As a page developer, you need to add a event handler with the same code over and over again. Sort of:

void PageChanged(object sender, PageIndexChangedEventArgs e) {
   grid.CurrentPageIndex = e.NewPageIndex;
   BindData();
}

BindData is a page-specific piece of code to retrieve data and rebind the control. The first line is always the same. This code has been standardized and incorporated in a data source control. Now, the old DataGrid control will still fire the PageIndexChanged event in case of paging. However, the newer GridView control--the successor to the DataGrid--will instead eat the paging request, set the new page index, and silently ask the underlying data source control to provide fresh data. This is a bit oversimplified, but it's just what happens. The same model is extended to cover all CRUD operations.

All ASP.NET 1.x data-bound controls support data source controls in ASP.NET 2.0 applications but only for SELECT operations. The binding takes place through the new DataSourceID property. DataSourceID and DataSource are mutually exclusive.

 

Books UPDATE
15 June 05 10:59 AM | despos | 4 comment(s)

Just as any other ASP.NET book available out there, also my Programming Microsoft ASP.NET book from Microsoft Press is going to be updated for ASP.NET 2.0. Today, it counts 1200 pages and this number will likely grow up to 1800 to incorporate coverage of the several new features of ASP.NET 2.0. My goal is to make the book cover all aspects of Web programming with ASP.NET technologies, regardless of the specific version one could use. If the project succeeds, the book will be the state-of-the-art as far as Web development with ASP.NET is concerned. Each and every topic and feature is covered for what it is, represents and provides to developers, rather than as the delta from the previous version. For example, I will have a chapter on data binding that explains old-style binding (via DataSource and IEnumerable) as well as data source controls. Same perspective, same goal, different set of capabilities and programming features. In this way, I hope to update the old book just as Microsoft updates the ASP.NET platform: with new features without breaking backward compatibility. The estimate of 1800 pages is anyway too much for a single book. For this reason, with Microsoft Press we decided to split the book in two--Fundamentals and Advanced of approximately 900 pages each.

The first book should hit the bookshelves in the November timeframe by the time Microsoft is expected to release the new 2.0 platform. It'll cover what you absolutely need to know to plan and realize an ASP.NET powered Web site. Topics include: HTTP runtime, pager authoring, server controls, forms, master pages, themes, profiles, ADO.NET, data binding, data source controls, grids, security, state management, caching, providers. The second book will follow shortly after (ideally by Christmas time or early January). It will discuss modules and handlers, custom controls, user controls, Web Parts, site navigation, Web services, mobile programming, viewstate, build providers, custom providers, Web Forms internals, and more advanced techniques for page authoring and data binding.

If possible, I'll try to post updates regularly. As of today, I completed Chapter 9 (out of 17) of the first book.

The Week of Nov 7
15 June 05 10:19 AM | despos | 2 comment(s)

Don't about the rest of the world, but in Italy November is considered a "sad" month. A month in which nobody would normally get married or celebrate anything. Kind of superstition mostly due to the fact that we celebrate (so to speak, mourn more exactly) for the dead on Nov 2--yep, just Nov 2. We would probably never have important elections in November or release a key product like Visual Studio .NET 2005 and SQL Server 2005. But fortunately for the rest of the world that is not Italy :-)

So we now know the date (precisely, the week not yet the day) of the official release of what was once named Whidbey--the week of November 7th. Nicely enough, it's the same week of the Las Vegas leg of the DevConnections show. One more reason to attend. (My talks and seminars included ...)

More Posts