Tuesday, December 9, 2008

Using CSS - Complex Forms Without Tables? Anyone? Anyone?

I have spent the last couple weeks doing research on CSS form development for very complex forms that involve things like multiple sections and multiple columns. I often hear that "tables are evil" or that "tables should only use them for tabular data", and I was determined to find a way to incorporate CSS for my next form.

I decided to do some in depth research to determine if there was a standard or best practice for complex form development. Fortunately for me, there are thousands of examples on the web and others that can be found in various books. I decided to purchase The CSS Anthology - 101 Essential Tips, Tricks & Hacks by Rachel Andrews which has a number of great examples. I also downloaded a free book by the same publisher titled The Art & Science of CSS I also went to Borders bookstore and spent some more time perusing through about eight different CSS books. Another book by Rachel Andrew even had the promising title HTML Utopia: Designing Without Tables.

Unfortunately, I could not find a single example that would meet my requirements. Pretty much every example I encountered had a very simple form, usually a very standard one with name, address, and zip code. I could not find a single form that was more complex that involved things like multiple sections and multiple columns.

Ultimately, I have come to the conclusion that while there may be a solution for a complex form using CSS, it hasn't been documented or standardized. Until then, I plan to continue using tables to develop complex forms. If anyone out there in the blogosphere has any suggestions I am posting a screenshot of the form.

Example:


Friday, December 5, 2008

Quick and Dirty Object Sorting by Implementing IComparer<T>

Let's say you have a simple Person class with some properties of First Name and Last Name. You then populate them into a strongly typed List<T> and would like to sort them by Last Name. Fortunately there is a very easy solution.

1. Create a Person class:

public class Person
{
    public string FirstName {get; set;}
    public string LastName {get; set;}
    public Person
}

2. Create a PersonComparer class that implements IComparer<Person>:

public class PersonComparer : IComparer<Person>
{
    public int Compare(Person p1, Person p2)
    {
        return String.Compare(p1.LastName, p2.LastName);
    }
}

3. It was that easy. Now write some test code:

static void Main(string[] args)
{
    List<Person> personList = new List<Person>();
        personList.Add(new Person { FirstName = "George", LastName = "Washington"});
        personList.Add(new Person { FirstName = "Abraham", LastName = "Lincoln" });
        personList.Add(new Person { FirstName = "John", LastName = "Adams" });
        personList.Sort(new PersonComparer()); //Or use alternative below
        foreach (Person p in personList)
        {
            Console.WriteLine("{0}, {1}", p.LastName, p.FirstName);
        }
        Console.Read();
}

Alternative: If you need a one off, create a delegate that will sort the object on the fly. You will not need to implement the PersonComparer class, however it is not as reusable.

personList.Sort(delegate(Person p1, Person p2)
{
    return p1.LastName.CompareTo(p2.LastName);
}});

Thursday, November 13, 2008

Build StringBuilder Code from C#

With a little quick and dirty code, you can generate StringBuilder code from C# code. To use it, all you need is a WinForms project with two TextBoxes and a Button. This is very useful for creating code generation tools. My reason is that I am in the process of creating a dynamic form generation tool for SharePoint Lists and Document Libraries.

Form Example Screenshot:

C# Code (Not Optimized):

public string GetStringBuilderCodeFromCSharp(string[] lines)
{
    StringBuilder stringBuilder = new StringBuilder();
    string line = string.Empty;
    foreach (string s in lines)
    {
        line = s.Replace("    ", "\\t").TrimEnd();    //Replace four spaces with tabs. VS cs uses spaces
        if (line.StartsWith("\\t\\t"))                      //Eliminate double tabs. VS cs defaults with tab
            line = line.Substring(4, line.Length - 4);
        line = line.Replace("\"", "\\\"");               //Replace double quotes with escaped double quotes
        line = "stringBuilder.Append(\"" + line + "\\r\\n\");\r\n";   //StringBuilder Append Code
        stringBuilder.Append(line);
    }
    stringBuilder.Insert(0, "StringBuilder stringBuilder = new StringBuilder();\r\n");
    return stringBuilder.ToString();
}

Monday, July 28, 2008

Apparently not so Cuil after all...

One of the top stories on Google News this morning was a new search engine called Cuil (pronounced "cool") that is supposed to have a larger search index than Google. Although the search results come out in a pretty Web 2.0 looking layout, I will probably still opt for scrolling down a list of search results. I also found a few other things that raised a few eyebrows.

Broken Contact Us Link - One thing that kind of surprised me was the fact that the Contact Us page resulted in an "Oop! We couldn't find that page." error. I recently saw the same thing on a web page of a company I was being interviewed. Of all the page links that could be broken, you'd think that this one would be pretty important.

No Address Recognition - I also tried typing in my current address, but the search engine simply hung for a few minutes after finally displaying a message that it couldn't find any results.

No Detailed Preferences - The preferences are simply Safe Search (on / off) or Typing Suggestions (on /off). I'd like to see more choices than this.

cuil

cuil2

You can also visit my post labeled "A Few Reason's I will keep using Google Search over Windows Live Search."

Wednesday, July 16, 2008

.NET 3.5 - Extension Methods Magic!

One of my favorite new features about .NET 3.5 is the ability to create Extension Methods. Essentially this provides you with the ability to extend a given type with your own custom methods. Let's think about what you would need to do to retrieve a control value from a ASP.NET FormView. First you need to use the Controls collection or FindControl method to return the control. Then, you need to cast the control to the specific control in question. Finally, you have to cast it to the type you need returned. The following is an example of this:

int woodchucksTotal = Convert.ToInt32(((Label)WoodChuckerForm.FindControl("WoodchucksTotal")).Text);

This syntax is very error prone, tedious, and hard to debug if you are doing it with a large number of controls. Alternatively, and with very little work, you can use extension methods and have syntax like this instead:

int? woodchucksTotal = WoodChuckerForm.ControlToInt32("WoodchucksTotal");

This allows me to simplify my syntax and almost cut it in half and decrease the number of parentheses from 8 to 2. I have also provided means to return a nullable typed integer. This can be helpful if, for example, you'd prefer to insert a null instead of a zero in a database. To be able to implement extension methods, you simply need to use the following two simple steps:

Implement an Extension Method

  1. Create a non-nested static class.
  2. Add static methods that have first parameters use the this modifier with the type being extended.

Sample C# Code

public static class WebControlUtil
{
    public static string ControlToString(this FormView formView, string controlName)
    {
        WebControl ctrl = (WebControl)formView.FindControl(controlName);
        switch (ctrl.GetType().ToString())
        {
            case "System.Web.UI.WebControls.TextBox":
                ctrlText = ((TextBox)ctrl).Text;
                break;
            case "System.Web.UI.WebControls.CheckBox":
                ctrlText = ((CheckBox)ctrl).Checked.ToString();
                break;
            case "System.Web.UI.WebControls.DropDownList":
                ctrlText = ((DropDownList)ctrl).Text;
                break;
        }
        return ctrlText;
    }

    public static bool ControlToBool(this FormView formView, string controlName)
    {
        WebControl ctrl = (WebControl)formView.FindControl(controlName);
        bool ctrlBool = false;
        switch (ctrl.GetType().ToString())
        {
            case "System.Web.UI.WebControls.CheckBox":
                ctrlBool = ((CheckBox)ctrl).Checked;
                break;
        }
        return ctrlBool;
    }

    public static int? ControlToInt32(this FormView formView, string controlName)
    {
        WebControl ctrl = (WebControl)formView.FindControl(controlName);
        int? ctrlInt32 = null;
        switch (ctrl.GetType().ToString())
        {
            case "System.Web.UI.WebControls.TextBox":
                if (((TextBox)ctrl).Text != "")
                {
                    ctrlInt32 = Convert.ToInt32(((TextBox)ctrl).Text);
                }
                break;
            case "System.Web.UI.WebControls.DropDownList":
                ctrlInt32 = Convert.ToInt32(((DropDownList)ctrl).SelectedValue);
                break;
        }
        return ctrlInt32;
    }

    public static DateTime? ControlToDate(this FormView formView, string controlName)
    {
        WebControl ctrl = (WebControl)formView.FindControl(controlName);
        DateTime? ctrlDate = null;
        switch (ctrl.GetType().ToString())
        {
            case "System.Web.UI.WebControls.TextBox":
                if (((TextBox)ctrl).Text != "")
                {
                    ctrlDate = Convert.ToDateTime(((TextBox)ctrl).Text);
                }
                break;
        }
        return ctrlDate;
    }
}

Five Things You Didn't Know About Me

This blog has been strictly technical, but I was feeling gutsy and decided to include a few personal items here just for kicks. With that being said, here are five things about me that you didn't know about me:



funeral_home

In order to pay for college, I lived and worked at a funeral home in downtown Seattle for three years.

We were located right on Pine Street and could see Pike's Place Market from the building. To get to our apartment, you had to climb the back stairs and walk through a rather large coffin display room. Legend has it (as I later confirmed), that services for both Bruce and Brandon Lee were held at this same funeral home. We have many experiences, including tear gas in our apartment during the demonstrations at the WTO Ministerial Round in 1999. Incidentally, I was interning for the Japanese Consulate at the time, specifically for the WTO! Sadly to say, the funeral home got sold to a law firm a few years ago, but leases out the chapel to a bar actually called Chapel. It's on the corner of Melrose and Pine and a great place for a martini and feeling morbid, or in my case nostalgic!


greyhound

I have traveled all the way across the U.S. via train, plane, automobile, and Greyhound bus.

I was seventeen during my four day trip from Groton, CT to San Diego, CA on the Greyhound bus, which was about the distance of 3,000 miles. I somehow subsisted on peanut butter, jam, twenty bucks, and the ticket price of a whopping $69.
Tip: If at all possible, try to avoid the St. Louis bus depot at three in the morning. It can be a very scary place. Also, I highly recommend the train if you want to enjoy seeing the back roads of America. That way you won't have to stop every ten minutes for driver smoke breaks and wonder if every seat has been urinated on. In addition, if you can afford it, I highly recommend a sleeper car as sleeping in the chairs get a little uncomfortable after a few days.

猿も木から落ちる

I can speak and read Japanese and was once a medical interpreter.

I was mostly self taught and actually tested into fourth year Japanese at the University of Washington without ever having taken a class. I ended up excelling and took most of the fourth year classes and some graduate level classes. Due to credit limitations, I ultimately ended up with a Japanese minor along with my major in International Studies, with a focus on East Asia. I have a continued interest in Japan, but it has definitely become more of a hobby. And by the way, I don't do karate or origami, and only watch manga occasionally.
Note: The translation from the Japanese text on the left is from a famous kotowaza or Japanese folk saying: "Even monkeys fall from trees."

bloodplasma

I donated plasma for money during college to make ends meet.

Since I was paying for college on my own, times could be tough and tough times called for desperate measures. However, I drew the line with organ donations and pharmaceutical testing.
Tip (not actually recommended): When it comes to plasma banks, you are required to wait a few days before coming back. I guess there's some silly rule about the fact that they are sucking all this blood out of you and don't want you to die. :) Anyways, competing plasma banks don't always check each others records. This means that if you are swift on your feet, you can hit up a couple a week and buy all the sweet ramen and cabbage you can eat!
oppenheimer

I have a love for reading, especially history and contemporary history.

The last book I read was American Prometheus: The Triumph and Tragedy of J. Robert Oppenheimer, by Kai Bird and Martin Sherwin. I also recently finished Guns, Germs, and Steel, by Jared Diamond and Mao: The Unknown Story, by Jung Chang and Jon Halliday. I highly recommend the first two, but you really need to be prepared and have a strong stomach for the third.
I am currently re-reading the classic Catch-22 by Joseph Heller (along with my normal array of geek books). Some of my favorite author's are Don Delillo, Hermann Hesse, Philip Roth, Ralph Ellison, and David Sedaris (can't wait until his new one comes out in paperback).

Note: I have to give some credit to Scott Hanselman's ComputerZen blog post for encouraging to throw some personal touches to my otherwise mundane technical blog.

Thursday, July 10, 2008

SharePoint 2007 - Using .NET 3.5 AJAX / AjaxControlToolkit

One of the things Microsoft advertised with the release of SharePoint 2007 SP1, was the fact that SharePoint would now jive with AJAX. Although they had previously provided instructions on how to do it before SP1, it was never officially supported.* Unfortunately, once SP1 was released, it wasn't clear on how to get AJAX to work or what you should do if you had followed their previous instructions.

After much troubleshooting, I was finally able to find an easy solution. Essentially, if you had followed the previous instructions that added a couple dozen entries to your web.config, you could now simply use assembly bindings to redirect your old AJAX 1.1 assemblies to the new AJAX 3.5 assemblies.

Instructions

1.) Follow the instructions in the following SharePoint Team Blog post:

Integrating ASP.NET AJAX with SharePoint

2.) Add the following assembly bindings to your SharePoint web.config. (This will redirect your AJAX 1.1 assemblies to use AJAX 3.5.)

<configuration>
   <runtime>
      <assemblyBinding>
         <dependentAssembly>
            <assemblyIdentity name="System.Web.Extensions" publicKeyToken="31bf3856ad364e35"/>
        <bindingRedirect oldVersion="1.0.0.0-1.1.0.0" newVersion="3.5.0.0"/>
         </dependentAssembly>
         <dependentAssembly>
        <assemblyIdentity name="System.Web.Extensions.Design" publicKeyToken="31bf3856ad364e35"/>
        <bindingRedirect oldVersion="1.0.0.0-1.1.0.0" newVersion="3.5.0.0"/>
         </dependentAssembly>
      </assemblyBinding>
   </runtime>
</configuration>

I also highly recommend Daniel Larson' blog if you are trying to troubleshoot SharePoint with AJAX.

Daniel Larson's Developer Blog

Update: I am including all web.config settings below. There are a whopping 8 updates!

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<
configuration>
<
configSections>
<!--
AJAX.NET SUPPORT 1 of 8 START-->
<
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" />
</
sectionGroup>
</
sectionGroup>
</
sectionGroup>
<!--
AJAX.NET SUPPORT 1 of 8 END-->
</
configSections>
<
SharePoint>
<
SafeControls>
<!--
AJAX.NET SUPPORT 2 of 8 START-->
<
SafeControl Assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" Namespace="System.Web.UI" TypeName="*" Safe="True" />
<!--
AJAX.NET SUPPORT 2 of 8 END-->
</
SafeControls>
</
SharePoint>
<
system.web>
<
httpHandlers>
<!--
AJAX.NET SUPPORT 3 of 8 START-->
<
remove verb="*" path="*.asmx" />
<
add verb="*" path="*.asmx" validate="false" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<
add verb="*" path="*_AppService.axd" validate="false" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<
add verb="GET,HEAD" path="ScriptResource.axd" type="System.Web.Handlers.ScriptResourceHandler, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" validate="false" />
<!--
AJAX.NET SUPPORT 3 of 8 END-->
</
httpHandlers>
<
httpModules>
<!--
AJAX.NET SUPPORT 4 of 8 START-->
<
add name="ScriptModule" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<!--
AJAX.NET SUPPORT 4 of 8 END-->
</
httpModules>
<
compilation batch="false" debug="true">
<
assemblies>
<!--
AJAX.NET SUPPORT 5 of 8 END-->
<
add assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<
add assembly="System.Web.Extensions.Design, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
<!--
AJAX.NET SUPPORT 5 of 8 START-->
</
assemblies>
</
compilation>
<
pages enableSessionState="false" enableViewState="true" enableViewStateMac="true" validateRequest="false" pageParserFilterType="Microsoft.SharePoint.ApplicationRuntime.SPPageParserFilter, Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" asyncTimeout="7">
<!--
AJAX.NET SUPPORT 6 of 8 START-->
<
controls>
<
add tagPrefix="asp" namespace="System.Web.UI" assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<
add tagPrefix="asp" namespace="System.Web.UI.WebControls" assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
<
add namespace="AjaxControlToolkit" assembly="AjaxControlToolkit, Version=3.0.20229.28221, Culture=neutral, PublicKeyToken=28f01b0e84b6d53e" tagPrefix="ajax" />
</
controls>
<!--
AJAX.NET SUPPORT 6 of 8 END-->
</
pages>
<
trust level="Full" originUrl="" />
</
system.web>
<!--
AJAX.NET SUPPORT 7 of 8 START-->
<
runtime>
<
assemblyBinding>
<
dependentAssembly>
<
assemblyIdentity name="System.Web.Extensions" publicKeyToken="31bf3856ad364e35"/>
<
bindingRedirect oldVersion="1.0.0.0-1.1.0.0" newVersion="3.5.0.0"/>
</
dependentAssembly>
<
dependentAssembly>
<
assemblyIdentity name="System.Web.Extensions.Design" publicKeyToken="31bf3856ad364e35"/>
<
bindingRedirect oldVersion="1.0.0.0-1.1.0.0" newVersion="3.5.0.0"/>
</
dependentAssembly>
</
assemblyBinding>
</
runtime>
<!--
AJAX.NET SUPPORT 7 of 8 END-->
<!--
AJAX.NET SUPPORT 8 of 8 START-->
<
system.web.extensions>
<
scripting>
<
webServices>
<
jsonSerialization maxJsonLength="500000">
<
converters>
</
converters>
</
jsonSerialization>
</
webServices>
<
scriptResourceHandler enableCompression="false" enableCaching="true" />
</
scripting>
</
system.web.extensions>
<
system.webServer>
<
validation validateIntegratedModeConfiguration="false" />
<
modules>
<
add name="ScriptModule" preCondition="integratedMode" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
</
modules>
<
handlers>
<
remove name="WebServiceHandlerFactory-ISAPI-2.0" />
<
add name="ScriptHandlerFactory" verb="*" path="*.asmx" preCondition="integratedMode" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<
add name="ScriptHandlerFactoryAppServices" verb="*" path="*_AppService.axd" preCondition="integratedMode" type="System.Web.Script.Services.ScriptHandlerFactory, &#xD;&#xA; System.Web.Extensions, Version=3.5.0.0, Culture=neutral, &#xD;&#xA; PublicKeyToken=31bf3856ad364e35" />
<
add name="ScriptResource" preCondition="integratedMode" verb="GET,HEAD" path="ScriptResource.axd" type="System.Web.Handlers.ScriptResourceHandler, &#xD;&#xA; System.Web.Extensions, Version=3.5.0.0, Culture=neutral, &#xD;&#xA; PublicKeyToken=31bf3856ad364e35" />
</
handlers>
</
system.webServer>
<!--
AJAX.NET SUPPORT 8 of 8 END-->
</
configuration>



* Mike Fitzmaurice was Microsoft's SharePoint Developer Evangelist at the time and is now at Nintex.

Wednesday, July 9, 2008

SharePoint - Web Part Custom Properties

I recently got asked by a colleague about this and thought I'd write up a quick blog post on this. Creating custom properties for a web part can be done very easily with only a few steps. This simple example creates custom properties, uses the values to populate controls, and then adds those controls to the web part.

  1. Create a class that inherits from Microsoft.SharePoint.WebPartPages.WebPart.
  2. Add public properties that have the following attributes: Personalizable, WebBrowsable, WebDisplayName. (Option: You can also add Category and WebDescription attributes.) You will notice that depending on what data type you use, you can get different controls to appear. For example, a string will render a TextBox, a bool will render a CheckBox, and an enum will render a DropDownBox.
  3. Override the CreateChildControls method and then use the properties to populate control values which are then added to the web part control. (Option: You can also override Render or RenderContents, but I prefer using CreateChildControls.)

Web Part in Action

WebPartCustomProperties

Source Code

using Microsoft.SharePoint;
using System;
using System.ComponentModel;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;

namespace Acme.Web
{
public class MyWebPart : Microsoft.SharePoint.WebPartPages.WebPart
{
public enum FavoriteColor { Red, Yellow, Blue, Green, Orange, Purple }

[Personalizable(PersonalizationScope.Shared), WebBrowsable(true),
Category("Custom Properties"), WebDisplayName("Motto"),
WebDescription("Personal Motto")]
public string Motto { get; set; }

[Personalizable(PersonalizationScope.Shared), WebBrowsable(true),
Category("Custom Properties"), WebDisplayName("Favorite Color"),
WebDescription("Favorite Color")]
public FavoriteColor MyFavoriteColor { get; set; }

[Personalizable(PersonalizationScope.Shared), WebBrowsable(true),
Category("Custom Properties"), WebDisplayName("Vegetarian"),
WebDescription("Are you vegetarian?")]
public bool Vegetarian { get; set; }

public MyWebPart()
{
this.ExportMode = WebPartExportMode.All;
}

protected override void CreateChildControls()
{
try
{
base.CreateChildControls();
this.Controls.Clear();
Label lblMotto = new Label();
lblMotto.Text = Motto;
Label lblFavoriteColor = new Label();
lblFavoriteColor.Text = "My favorite color is " +
Enum.GetName(typeof(FavoriteColor), MyFavoriteColor).ToLower() + ".";
Label lblVegetarian = new Label();
if (Vegetarian)
lblVegetarian.Text = "I am a vegetarian.";
else
lblVegetarian.Text = "I am not a vegetarian.";
this.Controls.Add(lblMotto);
this.Controls.Add(new LiteralControl("<br />"));
this.Controls.Add(lblFavoriteColor);
this.Controls.Add(new LiteralControl("<br />"));
this.Controls.Add(lblVegetarian);
}
catch (Exception ex)
{
Label label = new Label();
label.Text = "Error - " + ex.Message;
this.Controls.Add(label);
}
}
}
}

For more information, see the System.Web.UI.WebControls.WebParts namespace on MSDN.

Tuesday, June 17, 2008

Simple LINQ Projection Example

Here is a simple example using LINQ projection. It takes a List<string> that include space-delimited strings, queries them and then project into an IEnumerable<Person>. I then loop through the output sequence and write the results to the console.

class Program
    {
        static void Main(string[] args)
        {
            List<string> people = new List<string>
                { "Joe Smith 20", "Jim Jackson 30", "Jason Suzuki 40" };
            IEnumerable<Person> query =
                from p in people
                select new Person
                {
                    FirstName = p.Split(' ')[0],
                    LastName = p.Split(' ')[1],
                    Age = Convert.ToInt32(p.Split(' ')[2])
                };
            foreach (Person p in query)
            {
                Console.WriteLine("First Name:\t{0}\r\nLastName:\t{1}\r\nAge:\t\t{2}",
                    p.FirstName, p.LastName, p.Age);
            }
        }
    }

    public class Person
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public int Age { get; set; }
    }

The Albahari Brothers Rock!

I recently ordered two books and wanted to give a quick plug to the Albahari brothers. They have provided a great service to C# programmers by writing two excellent books that cover an incredible amount of material with examples in a very few pages. Unlike many technical books these are thorough, concise, and believe it or not, portable! I have many other books that have far more pages and simply don't measure up in explaining or providing good examples. I should also mention that they have also written the new O'Reilly C# In A Nutshell book. I own the older version, but find it more of a reference book and am less likely to throw it in my backpack.

In addition, they have written a lightweight tool that allows you to learn and write ad-hoc LINQ expressions. You can even connect to a SQL Server and write SQL queries as well. What's more, they have included all of the code samples from their books. (See screenshot below)

CSharp3PocketReference

C# 3.0 Pocket Reference: Instant Help for C# 3.0 Programmers

Authors: Joseph Albahari and Ben Albahari

Publisher: O'Reilly

Publish Date: 02/26/2008

Pages: 172

Current Amazon Price: $10.19 (Used - $7.79)

LINQPocketReference

LINQ Pocket Reference: The Concise Reference to LINQ via C# 3.0


Authors: Joseph Albahari and Ben Albahari

Publisher: O'Reilly

Publish Date: 02/26/2008

Pages: 242

Current Amazon Price: $10.19 (Used - $8.00)

linqpadlogo

LINQPad

Authors: Joseph Albahari and Ben Albahari

Free Download!

LINQPad

Hi Mom, I'm on MSDN - System.Collections.IEnumerable

I was recently implementing the IEnumerable and IEnumerator interface and needed to check out the documentation on MSDN. I thought the example could use some sprucing up since it used an array that has a fixed size and had two separate classes for the interfaces. Here are the updates:

  1. Implemented both IEnumerable and IEnumerator interfaces into one class.
  2. Useed a generic list instead of an array which has a fixed size.
  3. Added an indexer.

IEnumerable Interface

http://msdn.microsoft.com/en-us/library/system.collections.ienumerable.aspx

Source

using System;
using System.Collections;
using System.Collections.Generic;

namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
List<Person> peopleArray = new List<Person>()
{
new Person("John", "Smith"),
new Person("Jim", "Johnson"),
new Person("Sue", "Rabon"),
};

People peopleList = new People(peopleArray);
foreach (Person p in peopleList)
Console.WriteLine(p.firstName + " " + p.lastName);

Person p2 = peopleList[1]; //Jim Johnson
Console.WriteLine(p2.firstName + " " + p2.lastName);
}
}

public class Person
{
public Person(string firstName, string lastName)
{
this.firstName = firstName;
this.lastName = lastName;
}

public string firstName;
public string lastName;
}

public class People : IEnumerable, IEnumerator
{
private List<Person> people;
int position = -1;
public People(List<Person> list)
{
people = list;
}

public Person this[int indexer]
{
get { return people[indexer]; }
set { people[indexer] = value; }
}

public bool MoveNext()
{
position++;
return (position < people.Count);
}

public void Reset()
{
position = -1;
}

public object Current
{
get
{
try
{
return people[position];
}
catch (IndexOutOfRangeException)
{
throw new InvalidOperationException();
}
}
}

public IEnumerator GetEnumerator()
{
return new People(people);
}
}
}

Friday, June 6, 2008

Upload File to SharePoint Document Library Using the ASP.NET FileUpload Control

Recently I needed to create a Web Part that included the following:

  • A Master / DetailsView form that integrates with an external database.
  • A way to show existing documents and add new documents (and column metadata) to an existing SharePoint Document Library.

After some research I figured out how to do this by converting the FileUpload control and a ASP.NET Button control which does the actual uploading. In a nutshell you convert the posted file to a byte array and then use the SharePoint OM to upload the file. I have included the following functionality:

  • Overwrite File - You have the option of overwriting existing files or creating a new version if you have versioning turned on for the Document Library. 
  • Include Subfolders - This is done by passing a slash delimited string relative to the Document Library.
  • Update Column Metadata - This is done by passing a Dictionary<string, string> object that includes a key which is the column name and value as the value.

There is a probably a little more work I need to do to make this more bullet-proof, but it should give you a start. You can also combine it to do multiple files using my previous post: Upload Multiple Files With ASP.NET. Note: I have abbreviated the code below for brevity.

ASP.NET

<asp:FileUpload ID="fileBrowse" runat="server" />
<asp:Button ID="fileUpload" runat="server" Text="Upload Files" OnClick="fileUpload_Click" />

Code Behind:

protected void fileUpload_Click(object sender, EventArgs e)
{
    if (fileBrowse.PostedFile != null)
    {
        string siteUrl = @"http://myportal/teamsite";
        string docLibraryName = "Shared Documents";
        string folderPath = "2008/01";
        string fileName = Path.GetFileName(fileBrowse.PostedFile.FileName);
        byte[] fileBytes = GetFileBytes();
        Dictionary<string, string> columnValues = new Dictionary<string, string>();
        columnValues.Add("Document Type", "Financial");
        columnValues.Add("Author", "Mr. Burns");
        UploadFile(siteUrl, docLibraryName, folderPath, fileName, fileBytes, columnValues, true);
    }
}

public byte[] GetFileBytes()
{
    Stream stream = fileBrowse.PostedFile.InputStream;
    byte[] bytes = new byte[stream.Length];
    stream.Read(bytes, 0, (int)stream.Length);
    stream.Close();
    return bytes;
}

public void UploadFile(string siteUrl, string docLibraryName, string folderPath,
    string fileName, byte[] fileByteArray, Dictionary<string, string> columnValues, bool overWrite)
{
    try
    {
        string fileUrl = siteUrl.EnsureEndSlash() + docLibraryName.EnsureEndSlash() +
            folderPath.EnsureEndSlash() + fileName;
        string currentFolder = siteUrl.EnsureEndSlash() + docLibraryName.EnsureEndSlash();
        using (SPWeb web = new SPSite(siteUrl).OpenWeb())
        {
            web.AllowUnsafeUpdates = true;
            SPFolder folder = web.Folders[docLibraryName];
            if (folderPath != string.Empty)
            {
                foreach (string subFolder in folderPath.Split('/'))
                {
                    currentFolder = currentFolder.EnsureEndSlash() + subFolder;
                    if (!web.GetFolder(currentFolder).Exists)
                    {
                        folder.SubFolders.Add(subFolder);
                    }
                    folder = folder.SubFolders[subFolder];
                }
            }
            if (overWrite || !web.GetFile(fileUrl).Exists)
            {
                folder.Files.Add(fileName, fileByteArray, true);
            }
            else
            {
                folder.Files[fileName].CheckOut();
                folder.Files.Add(fileName, fileByteArray, true);
                folder.Files[fileName].CheckIn(String.Empty);
            }
            SPFile file = folder.Files[fileUrl];
            foreach (KeyValuePair<string, string> columnValue in columnValues)
            {
                file.Item.Properties[columnValue.Key] = columnValue.Value;
            }
            file.Item.Update();
        }
    }
    catch (Exception ex)
    {
        //TODO: Add Exception Handling
    }
}

Upload Multiple Files With ASP.NET

Out of the box, you can easily upload files using the ASP.NET FileUpload control. However there are times when the design calls for the ability to upload multiple files. In my example, I use a single FileUpload control along with a GridView for layout and some Session state to store the posted files. You can copy and paste the code below to accomplish this task.

Design

MultipleUpload

ASP.NET

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<!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 runat="server">
    <title>Untitled Page</title>
    <script language="javascript" type="text/javascript">
        function SetFileGridIndex(index)
        {
            var myControl = document.getElementById('<%= fileGridIndex.ClientID %>');
            myControl.value = index;
        }
    </script>
</head>
<body>
    <form id="form1" runat="server">
    <div style="font-family: Verdana; font-size: small; text-align:center; width:100%;">
        <asp:GridView ID="fileGrid" AutoGenerateColumns="False" ShowFooter="true" BorderColor="Black"
            BorderWidth="1px" runat="server" OnRowCommand="fileGrid_RowCommand" GridLines="None"
            OnRowDeleting="fileGrid_RowDeleting">
            <Columns>
                <asp:BoundField DataField="Key" HeaderText="Files To Upload" />
                <asp:TemplateField ShowHeader="False">
                    <ItemTemplate>
                        <asp:LinkButton ID="LinkButton1" runat="server" CausesValidation="False" CommandName="Delete"
                            Text="Delete" OnClientClick='<%# Eval("Key", "SetFileGridIndex(\"{0}\");") %>'></asp:LinkButton>
                    </ItemTemplate>
                    <FooterTemplate>
                        <asp:Button ID="Upload" runat="server" Text="Upload" CommandName="Upload" />
                    </FooterTemplate>
                </asp:TemplateField>
            </Columns>
        </asp:GridView>
        <asp:HiddenField ID="fileGridIndex" runat="server" />
        <div id="fileList" runat="server">
        </div>
        <asp:FileUpload ID="fileUpload" runat="server" />
        <asp:Button ID="AddFile" runat="server" Text="Add" OnClick="AddFile_Click" Height="20px" />
        <br />
    </div>
    </form>
</body>
</html>

Code Behind:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.IO;
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;

public partial class _Default : System.Web.UI.Page
{
    private Dictionary<string, HttpPostedFile> postedFiles;
    public Dictionary<string, HttpPostedFile> PostedFiles
    {
        get
        {
            return ((Dictionary<string, HttpPostedFile>)Session["PostedFiles"]);
        }
        set
        {
            Session["PostedFiles"] = value;
            postedFiles = value;
        }
    }

    protected void Page_Load(object sender, EventArgs e)
    {
        if (!Page.IsPostBack)
        {
            Session["PostedFiles"] = new Dictionary<string, HttpPostedFile>();
        }
    }

    public void LoadData()
    {
        fileGrid.DataSource = PostedFiles;
        fileGrid.DataBind();
    }

    protected void AddFile_Click(object sender, EventArgs e)
    {
        if (!PostedFiles.ContainsKey(Path.GetFileName(fileUpload.PostedFile.FileName)))
        {
            PostedFiles.Add(Path.GetFileName(fileUpload.PostedFile.FileName), fileUpload.PostedFile);
            LoadData();
        }
    }

    protected void fileGrid_RowCommand(object sender, GridViewCommandEventArgs e)
    {
        switch (e.CommandName)
        {
            case "Delete":
                string fileName = fileGridIndex.Value;
                PostedFiles.Remove(fileName);
                LoadData();
                break;
            case "Upload":
                foreach (HttpPostedFile postedFile in PostedFiles.Values)
                {
                    postedFile.SaveAs(@"c:\\temp\" + Path.GetFileName(postedFile.FileName));    //CHANGE ME
                }
                PostedFiles = new Dictionary<string, HttpPostedFile>();
                LoadData();
                break;
        }
    }
    protected void fileGrid_RowDeleting(object sender, GridViewDeleteEventArgs e)
    {

    }
}

Friday, May 30, 2008

Troubleshoot Web Pages with the Web Development Helper tool

One of my favorite tools for development is Nikhil Kothari's Web Development Helper. This is an IE Add-On that allows you to do things like navigate through the page DOM, capture screenshots, and examine ASP.NET view state. Of all the features, my favorite is probably the Script Console feature which allows you to execute client side JavaScript on any page.

Hello World

For example, the following is a Hello World example that executes the alert function.

Source:

SayHi();
function SayHi()
{
alert('hi'); //Optionally, you can just use this one liner.
}

WebDevelopmentHelper

Don't Save Your Page Script and Refresh the Page!

Where this get's more interesting is when you need to troubleshoot a client side script problem, perhaps an AJAX related issue. What's really nice about this is that you don't need to save your page script and refresh the page every time you want to troubleshoot your JavaScript.

Run Existing Page JavaScript

Another great way to use this tool is to run existing JavaScript from a page. For example, SharePoint 2007 uses JavaScript extensively. If you open the source (View --> Source in IE) from your browser, you can look at the page source and find snippets like the following:

SharePointJavaScript

You can then copy and paste this snippet into Web Development Helper Script Console and click the Execute link. As you can see this opens the page in Edit mode.

Source:

OpenSharePointEditMode();
function OpenSharePointEditMode()
{
window.location = 'javascript:MSOLayout_ChangeLayoutMode(false);';
}

SharePointWebDevelopmentHelper

Wednesday, May 7, 2008

Visual Studio - Object is in a zombie state.

I just got this message during some ASP.NET debugging on Visual Studio 2008. Since Microsoft is doing it, perhaps I'll start adding these types of messages. ;)

VSZombieState 

Microsoft Visual Studio

Cannot detach from one or more processes:

[4742] WebDev.WebServer.EXE: Object is in a zombie state.

Do you want to terminate them instead?

Monday, April 28, 2008

.NET 3.0 - Use Extension Methods to Simplify Your Code!

How would you like to have syntax like this that is fully IntelliSense capable?

List<string> list = new List<string>();
list.Add("john@email.com");
list.Add("joe@email.com");
list.Add("james@email.com");
Console.WriteLine(list.ToDelimitedString(","));
////or this...
string emailAddress = "test@test.com";
Console.WriteLine(emailAddress.IsEmailValid().ToString());

Now with the new .NET 3.0 Extension methods it is extremely trivial:

  1. Create a non-generic static class. This will contain the extension methods.
  2. Create a static method where the first parameter uses the type you would like to extend. It must also be prefixed by the "this" keyword. Note: this can be any type, including those that you create yourself.
  3. Simply call the extension method from an instantiated type. You will see that the extension method is added to the list of IntelliSense members.

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

namespace TestConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            string sampleEmailAddresses = @"john@email.com,jane@email.com,josh@email.com,jill@email.com";
            List<string> emailAddresses = sampleEmailAddresses.Split(new char[] { ',' }).ToList<string>();
            Console.WriteLine(emailAddresses.ToDelimitedString(",")); //Calling the extension method
            Console.Read();
        }
    }

    public static class Utils //Make sure this is static
    {
        public static string ToDelimitedString(this List<string> strings, string delimiter) //Defining the first parameter with the this keyword
        {
            return string.Join(delimiter, strings.ToArray());
        }
    }
}

For a more complete discussion, check out Scott Guthrie's blog:
http://weblogs.asp.net/scottgu/archive/2007/03/13/new-orcas-language-feature-extension-methods.aspx

Sunday, March 30, 2008

The Raconteurs Home Page - Sweet, Sweet DOS 1.1 User Interface

I usually limit myself to things purely technical on this blog, but this was a little cool to pass up and is somewhat technical in nature. The Raconteurs have created their web site using a user interface reminiscent of something circa the early 80's. Looking at the page source, it is done mostly using Macromedia Flash. Disregarding the evil splash page, JavaScript errors, and possible copyright infringements (oh my!), there is truly something wonderful about this minimalist approach. What's the point of being so pretentious when you are only providing basic information? Far too many web sites are bloated with ubiquitous links and ads, and provide for a truly miserable user experience. Needless to say, it would have been way cooler (and no doubt, achievable) if they could have done this using only HTML, JavaScript, and CSS. By the way, if you haven't already, check out the their new album. This is true and unadulterated rock n' roll.

Splash Page

image

Home Page

image

DOS 1.10 - Notice the dates 1981, 1982.

image

Thursday, March 6, 2008

Get any color on the screen quickly with PkColorPicker

This is a nice light utility that allows you to find colors by hovering over them using cross hairs. You can then copy the RGB hexadecimal values and paste them. This is much better than having to open something like Photoshop which takes forever an eats up all your processing power.

image

Monday, March 3, 2008

Enable Linq for SharePoint 2007

1.) Update the web.config with the Linq assemblies by inserting the following tags under <system.web><compilation>:

<add assembly="System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />

<add assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />

<add assembly="System.Xml.Linq, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />

<add assembly="System.Data.DataSetExtensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />

2.) Update the web.config with the Linq tag prefixes by inserting  following tags under <system.web><pages><controls>:

<add tagPrefix="asp" namespace="System.Web.UI" assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />

<add tagPrefix="asp" namespace="System.Web.UI.WebControls" assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />

3.) Reset IIS on the SharePoint server.

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; }
}

}