Wednesday, February 27, 2008

GridView + DetailsView + LinqDataSource + ModalPopupExtender - Hello World Example

I recently found a limitation when using a GridView, DetailsView, LinqDataSource, and ModalPopupExtender that prevented any records other than the first to be displayed in the DetailsView shown in the ModalPopupExtender. Although I am told that this can be alleviated by using an UpdatePanel, I was working in an extranet environment using Juniper VPN which doesn't like UpdatePanels or at least rewrites the HTML in such a way that prevents an UpdatePanel from working properly. In order to get this to work, all you need to do is hook up the RowCommand and RowDataBound events of the GridView so that a CommandArgument is created and then subsequently used in a Linq query when the item is clicked. You will need SQLExpress with the Northwind sample database and a Linq to SQL Classes (dbml) file that point to the Products table to get this to work.

For amusement, you can read about my struggles in the ASP.NET Forum here: http://forums.asp.net/p/1224034/2195642.aspx Also, a special thanks from the tiredblogger with his post which got me on the right track. http://tiredblogger.wordpress.com/2007/09/16/using-a-modal-popup-to-modify-linq-gridviews/

Without further ado, the code:

ASPX

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Test.aspx.cs" Inherits="LinqTester.Test" EnableEventValidation="false" %>

<%@ Register Assembly="AjaxControlToolkit" Namespace="AjaxControlToolkit" TagPrefix="cc1" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
<title>Untitled Page</title>
<style type="text/css">
.modalBackground
{
background-color: Black;
filter: alpha(opacity=80);
opacity: 0.8;
}
</style>
</head>
<body>
<form id="form1" runat="server">
<asp:ScriptManager ID="ScriptManager1" runat="server" />
<div style="text-align: center;">
<table>
<tr>
<td style="text-align: left;">
<asp:GridView ID="GridView1" runat="server" AllowPaging="True" DataKeyNames="ProductID"
AllowSorting="True" AutoGenerateColumns="False" DataSourceID="LinqDataSource1"
OnRowDataBound="GridView1_RowDataBound"
onrowcommand="GridView1_RowCommand">
<Columns>
<asp:BoundField DataField="ProductName" HeaderText="ProductName" ReadOnly="True"
SortExpression="ProductName" />
<asp:BoundField DataField="QuantityPerUnit" HeaderText="QuantityPerUnit" ReadOnly="True"
SortExpression="QuantityPerUnit" />
<asp:BoundField DataField="ProductID" HeaderText="ProductID" ReadOnly="True" SortExpression="ProductID" />
<asp:TemplateField>
<ItemTemplate>
<asp:LinkButton ID="Details" runat="server" CommandName="ShowDetails" Text="Details"></asp:LinkButton>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
</td>
</tr>
</table>
</div>
<asp:Panel ID="UpdateRecordPanel" runat="server" CssClass="" Style="display:none">
<asp:DetailsView ID="DetailsView1" runat="server" AutoGenerateRows="False" DataSourceID="LinqDataSource2"
Height="50px" Width="125px" BackColor="White">
<Fields>
<asp:BoundField DataField="ProductName" HeaderText="ProductName" ReadOnly="True"
SortExpression="ProductName" />
<asp:BoundField DataField="QuantityPerUnit" HeaderText="QuantityPerUnit" ReadOnly="True"
SortExpression="QuantityPerUnit" />
<asp:BoundField DataField="UnitPrice" HeaderText="UnitPrice" ReadOnly="True" SortExpression="UnitPrice" />
<asp:BoundField DataField="UnitsInStock" HeaderText="UnitsInStock" ReadOnly="True"
SortExpression="UnitsInStock" />
<asp:BoundField DataField="UnitsOnOrder" HeaderText="UnitsOnOrder" ReadOnly="True"
SortExpression="UnitsOnOrder" />
<asp:BoundField DataField="ReorderLevel" HeaderText="ReorderLevel" ReadOnly="True"
SortExpression="ReorderLevel" />
<asp:CheckBoxField DataField="Discontinued" HeaderText="Discontinued" ReadOnly="True"
SortExpression="Discontinued" />
</Fields>
</asp:DetailsView>
<asp:Button ID="CancelPopupButton" runat="server" Text="Cancel" />
</asp:Panel>

<cc1:ModalPopupExtender ID="ModalPopupExtender1" runat="server"
BackgroundCssClass="modalBackground"
CancelControlID="CancelPopupButton"
PopupControlID="UpdateRecordPanel"
TargetControlID="HiddenButton" >
</cc1:ModalPopupExtender>

<asp:Button ID="HiddenButton" runat="server" Style="display:none" />

<asp:LinqDataSource ID="LinqDataSource1" runat="server" ContextTypeName="LinqTester.Data.NorthwindDataContext"
Select="new (ProductName, QuantityPerUnit, ProductID)" TableName="Products">
</asp:LinqDataSource>
<asp:LinqDataSource ID="LinqDataSource2" runat="server" ContextTypeName="LinqTester.Data.NorthwindDataContext"
Select="new (ProductName, QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel, Discontinued)"
TableName="Products" Where="ProductID == @ProductID">
<WhereParameters>
<asp:ControlParameter ControlID="GridView1" Name="ProductID" PropertyName="SelectedValue"
DefaultValue="1" Type="Int32" />
</WhereParameters>
</asp:LinqDataSource>
</form>
</body>
</html>


ASPX.CS



using System;
using System.Collections;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Linq;
using AjaxControlToolkit;

namespace LinqTester
{
public partial class Test : System.Web.UI.Page
{
protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType != DataControlRowType.Header && e.Row.RowType != DataControlRowType.Footer && e.Row.RowType != DataControlRowType.Pager)
{
LinkButton lb = e.Row.FindControl("Details") as LinkButton;
if (lb != null)
{
lb.CommandArgument = ((TableCell)e.Row.Controls[2]).Text;
}
}
}

protected void GridView1_RowCommand(object sender, GridViewCommandEventArgs e)
{
if (e.CommandName == "ShowDetails")
{
ProductDetails productDetails = GetProductDetails(Convert.ToInt32(e.CommandArgument));
DetailsView1.Rows[0].Cells[1].Text = productDetails.ProductName;
DetailsView1.Rows[1].Cells[1].Text = productDetails.Quantity;
DetailsView1.Rows[2].Cells[1].Text = productDetails.UnitPrice.ToString();
DetailsView1.Rows[3].Cells[1].Text = productDetails.UnitsInStock.ToString();
DetailsView1.Rows[4].Cells[1].Text = productDetails.UnitsOnOrder.ToString();
DetailsView1.Rows[5].Cells[1].Text = productDetails.ReorderLevel.ToString();
DetailsView1.Rows[6].Cells[1].Text = productDetails.Discontinued.ToString();
ModalPopupExtender1.Show();
}
}

private ProductDetails GetProductDetails(int productId)
{
LinqTester.Data.NorthwindDataContext northwind = new LinqTester.Data.NorthwindDataContext();
List productDetails = (from p in northwind.Products
where p.ProductID == productId
select new ProductDetails
{
ProductName = p.ProductName,
Quantity = p.QuantityPerUnit,
UnitPrice = p.UnitPrice.HasValue == true ? 0 : (int)p.UnitPrice,
UnitsInStock = p.UnitsInStock.HasValue == true ? 0 : (int)p.UnitsInStock,
UnitsOnOrder = p.UnitsOnOrder.HasValue == true ? 0 : (int)p.UnitsOnOrder,
ReorderLevel = p.ReorderLevel.HasValue == true ? 0 : (int)p.ReorderLevel,
Discontinued = p.Discontinued
}).ToList();
return productDetails[0];
}
}

public class ProductDetails
{
public string ProductName { get; set; }
public string Quantity { get; set; }
public decimal UnitPrice { get; set; }
public int UnitsInStock { get; set; }
public int UnitsOnOrder { get; set; }
public int ReorderLevel { get; set; }
public bool Discontinued { get; set; }
}

}

A Few Reason's I will keep using Google Search over Windows Live Search

Every now and then, I check out Live Search to see how they are doing. This time I was prompted by a friend who just happens to work for Live Product Search. I'm afraid to say, that only after a short time I am changing back to Google. First let me say what I think they got right.

Pros

  1. Search Results - I have to admit that the search results are much better than they used to be. I remember a time when I would type in a .NET Namespace and couldn't get the correct result unless I clicked on one of the next pages. That being said, I couldn't say they are necessarily better than Google, but they are definitely more up to par than I found them in the past. (If only they could fix MSDN search results and get rid of that horrible little search popup window on the Microsoft ASP.NET site I will be thrilled.)
  2. Minimalist Design - They have now Googlized the site by taking a minimalist approach and I think this provides a much better user experience. This is the very reason I will never use Yahoo or MSN. There is far too much clutter and it is distracts from the end purpose of searching.
  3. Live Product Search - My friendship notwithstanding, I have to admit that the product search is pretty slick. Not only do they do a great job of recognizing your search results, but they also aggregate pictures, reviews, and features very well. I have not seen Google functionality that offers this type of interface. The only problem I noticed was that when I did a search on "Nikon D70", the "Compare prices" link was disabled. I do like the compare prices functionality and it is a feature I often use with Google Shopping search.

image

And now with the reasons why I will keep using Google Search over Live Search. Some of these may seem fairly mundane, but when combined they add up to a lot.

Cons

  1. Google Toolbar - Neither of the following features were available in the Live Toolbar.
    • Word Find - This functionality shows the search items to right of the toolbar and then you can click on them repeatedly as they will be highlighted in the displayed page. Because many of my searches are technical in nature, I often end up combing through forums and blogs. This makes it much easier. Live Search provides similar functionality, but it involves opening another window which is just one more step.
    • Up Button - It may not seem like much, but another feature I often use is the "Up" button which allows you to move up a subdirectory. This is very handy if you have a URL with a dozen query strings or an FTP directory.
  2. Google News - This is only one click away and I like the way it aggregates the news of the day. You can also rearrange it according to your preferences. I usually hit this once or twice a day to quickly skim the headlines and then drill in if I see something of interest. Live Search does have a News link, but clicking on it only means you can now enter a search term for the news. Of course, the Windows Live Home Page and iGoogle are alternatives since they allow you to customize your own home page, but I have never been happy with how long they take to load.
  3. Street Address Recognition - Although I think the Live Maps are great and have some advantages over Google Maps, Live Search did not recognize my home address. Google Search on the other hand, recognized my address and provided me with a map at the very top.

Windows Live Writer Rocks!

If you haven't checked this out yet, I highly recommend it. As this is a technical blog, the best thing about it is the ability to easily copy and paste code. This is something that Blogger handles very poorly. I have shown some screenshots and sample code that has been copied and pasted below.

Download Windows Live Writer

Screenshot

image

C# Code

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
}
}
}

HTML Code

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title></title>
</head>
<body>
</body>
</html>

Wednesday, February 6, 2008

SharePoint Designer - Disable "Open last Web site automatically"

I have a couple dozen web sites I have to maintain and one annoying thing for me was that SharePoint Designer always opened the last web site automatically. Fortunately, you can disable this by going to the Tools - Application Options menu and then unchecking "Open last Web site automatically when SharePoint Designer starts."