Archives

Archives / 2006 / May
  • ASP.NET 2.0 Localization (Video, Whitepaper, and Database Provider Support)

    ASP.NET 2.0 and VS 2005 add a bunch of features that make localizing ASP.NET applications much easier.

    To learn more about how the localization features work, I'd recommend first checking out this excellent 13 minute video in the ASP.NET 2.0 "How Do I?" video series.  In it Scott Stansfield walks-through how to build and localize an ASP.NET application from scratch, and how to support both dynamically picking the language used based on the incoming user-agent string of the client, as well as allowing a user to explictly choose their language preference from a dropdownlist.  I think you will walk away being surprised at how easy it is.  

    Wei-Meng Lee has also written a great walkthrough article on ASP.NET 2.0 localization that you can read for free on the O'Reilly network hereFor more detailed information on ASP.NET 2.0 localization, you can also then read Michele Bustamente's excellent ASP.NET 2.0 Localization article on MSDN.  Bilal Haidar also has a great artlce on ASPAlliance here.

    The articles and videos above use XML .resx files to store localized resource strings.  These can either by compiled into binaries or deployed as XML source with an ASP.NET 2.0 application.  Jeff Modzel recently published an article that shows how to build a custom Resource Provider that stores the resource strings in a database instead.  You can read about how he built this as well download his sample code here.

    Hope this helps,

    Scott

     

  • Eliminating CSS Image Flicker with IE6

    One challange that web designers and developers often wrestle with is an annoying "image flicker" issue that sometimes shows up when using CSS image references.  For example, when using a CSS rule like so:

         .someClassName
         {
                background:#AABBCC url(someBackGroundImage.gif) repeat-x;
         }

    This can cause some browsers (including IE 6) to have an annoying flicker when rendering the image (especially when used with hover styles or for background images).  In particular, this often shows up when building hierarchical and show/hide menus, and can degrade the UI experience for the site.  There are two ways to fix the issue for clients:

    1) Adjust your web-server's cache content settings for the static images being referenced from the CSS file.  This unfortunately requires admin access on the machine with IIS 6 (although not with IIS7, which supports a delegated administration model that allows you to configure these rules in a web.config file within the app).

    2) Use ASP.NET to define a handler that dynamically renders images with the appropriate cache content settings set.  This does not require any special configuration on your web-server, and can be done by simply copying a .ashx handler file into your app.

    Russ Helfand (who did the work on the ASP.NET CSS Adapter Toolkit) has a great blog post that details how to-do option #2.  You can read all about his solution here.

    This is a great tip/trick you can use for the Menu and TreeView CSS Adapters, as well as for any static element CSS rules you are using within your page.

    Hope this helps,

    Scott

     

  • Checking all CheckBoxes in a GridView

    Scott Mitchell continues to post great ASP.NET content (if you haven't subscribed to his blog then you should!).

    He has posted a nice new article detailing how to implement checkbox functionality within the ASP.NET 2.0 GridView, as well as add the common "Check All / Uncheck All" functionality to it.  You can read the article here.

    Hope this helps,

    Scott 

     

  • Reading/Writing Excel Spreadsheets with ADO.NET

    David Hayden maintains a really great blog.  He regularly posts about ADO.NET and Data Access, and is someone I'd highly recommend subscribing to.

    He posted a useful blog entry a few days ago that details how to use ADO.NET to read, insert and update data from an Excel spreadsheet.  A useful read if you are looking to quickly export or import data and work with it interatively in Excel.

    Hope this helps,

    Scott

     

  • www.iis.net site launched

    If you haven't visited it yet, I'd highly recommend taking a look at the new dedicated IIS community site: http://www.iis.net 

    This site has a ton of content about IIS 6.  Checkout the "Downloads" tab for log-parsing utilities, MOM managmenet packs, lockdown tools, and more.  The "TechCenter" tab then has an easy interface that you can use to search for whitepapers, KB articles, and web-casts.  

    Best of all is the content the site has about IIS 7.0 -- which as I've blogged in the past is a major, major improvement to the Microsoft web platform.   Read this blog post to learn more about the improvements, and then check out the "IIS 7" tab on the site to read even more.  Here are the high-level sections to visit:

    Make sure to click on the "Featured Articles" to the right of each of the section pages above for detailed articles and "how to" tutorials that you can run for each topic area.

    Try out IIS 7

    You can try out IIS7 either by installing the new Windows Vista Beta2 release (now available for download on MSDN), or by visiting: http://virtuallabs.iis.net/ 

    This cool virtual-labs web-site allows you to spend 90 minutes managing a remote IIS7 server in a virtual sandbox environment -- no need to install any beta software locally.  It also includes three lab tutorials that you can follow along to build a .NET or C++ core module for the server, check out the new unified configuration system, and use the new diagnostic features to inspect what is happening in the web server.  Best of all -- you can use it completely for free.

    IIS Forums

    The new www.iis.net website also now has dedicated forums for IIS questions (for IIS5, IIS6, and IIS7).  This is an ideal place for administrators to ask questions about IIS, and for people using the new IIS7 to connect directly with the product team.

    Hope this helps,

    Scott

     

  • Resharper 2.0 Released

    JetBrains has recently shipped Resharper 2.0 -- a Visual Studio add-in that provides a rich set of developer productivity features for C# and ASP.NET developers (including richer refactoring support, ASP.NET code assistance, unit testing integration, Nant and MSBuild script editing, and more).

    You can learn more about it and download a free 30 day trial here.

    Hope this helps,

    Scott

  • Red-Gate SQL Query Intellisense Tool (Free Download)

    I've seen a lot of people rave about Red-Gate's SQL tools in the past.  They provide a really nice set of tools that are well worth spending sometime to check out.

    They have a promotional offer available now where you can download their new SQL Prompt Intellisense tool for free until September 1st (note: it does not timebomb after that date -- so you can use it forever).  This tools provides intellisense completion for writing SQL queries and looks very useful:

    You can learn more about it and download it for free here.

    Hope this helps,

    Scott 

  • Cool Atlas PasswordStrength Complexity Control

    Paul Glavich blogged about a useful little Atlas externder control he built that enables sites to provide real-time password strength feedback on the client to users as they create new passwords (for example: if you have only letters in the password it will list "weak", and then give you immediate feedback as you type and make it stronger).  Below is a simple example of how to use it to point at a standard ASP.NET TextBox:

    Create New Password: <asp:textbox id="Txt1" runat="server" mode="Password" />

    <PasswordStrength:PasswordStrengthExtenderExtender runat="server">
         <PasswordStrength:PasswordStrengthExtenderProperties DisplayPosition="BelowLeft" PrefixText="Strength: " TargetControlID="Txt1" />
    </PasswordStrength:PasswordStrengthExtenderExtender>

    These types of controls are easy to build using the Atlas Control Toolkit SDK -- which includes 12 Atlas Control Extenders with full source and a project template for creating new ones.  In case you missed it, Scott Cate did a nice blog post earlier in the month where he walksthough step-by-step how to use the Atlas Control Toolkit SDK to create a new "TextBoxCounter" extender control from scratch.  You can read about it in his post here.

    The really cool thing is that anyone (including you reading this blog) will shortly be able to create and contribute new controls like these into the Atlas Control Toolkit itself (and hopefully have millions of ASP.NET developers download and use it).  Our plan is to use a collaborative, community contribution model, and have both Microsoft and non-Microsoft developers work jointly on the code together.

    We are hosting the Altas Control Toolkit now on the new CodePlex source control repository here.  If you have the VSTS source control client installed, you will be able to enlist and (if you are given contributor rights) will be able to checkout/checkin files within the VS IDE from the Atlas Control Toolkit project on CodePlex.  If you don't have VSTS you will still be able to contribute by downloading a separate client source control utility that you can use with either Visual Studio or the free Visual Web Developer.

    Shawn Burke, who runs the Atlas Control Toolkit project, is in the process of finalizing the first draft of the code contribution guidelines and will be publishing it shortly.  Keep an eye on his blog for more details.

    Should be fun -- I'm really looking forward to see all the cool controls people build,

    Scott

  • IIS 7.0 on the MSDN .NET Show

    Bill Staples and I did an interview with the MSDN .NET Show about IIS 7.0 that was just published yesterday.  You can watch it online for free here.  In addition to about 30 minutes of discussions about the new features and architecture are 30 minutes of demos.

  • Commerce Server 2007 RC1 Released

    Today the Microsoft Commerce Server Team announced availability of their Commerce Server 2007 RC1 bits.  The Commerce Server 2007 release is monumental improvement -- you can read about all the details on Ryan's blog post.  Commerce Server 2007 is built using ASP.NET 2.0 and has full integration with VS 2005.

  • Using LINQ with ASP.NET (Part 1)

    One of the new things I’m super excited about right now is the LINQ family of technologies that are starting to come out (LINQ, DLINQ, XLINQ and others soon).

     

    LINQ will be fully integrated with the next release of Visual Studio (code-name: Orcas) and it will include some very cool framework and tool support (including full intellisense and designer support).  Last week the LINQ team released the May CTP drop of LINQ that you can download from here.  What is cool about this CTP is that it works with VS 2005, and allows you to start learning more about it immediately.  It incorporates a bunch of customer feedback (for example: support for stored procedures in DLINQ), and also includes a built-in ASP.NET Web-Site Project to enable you to leverage it with ASP.NET apps (note: you can also use LINQ with the new VS 2005 Web Application Project option as well).

     

    I’m going to put together a few blog postings over the next few weeks that show off ways to use LINQ/DLINQ/XLINQ within ASP.NET projects.  This first walkthrough below will help you get started and introduce some of the important LINQ concepts.  You can follow-along by downloading the May CTP LINQ preview above and typing in the code below (I list all of it below), or you can download and run the complete .zip file of my samples here (note: you still need to install the LINQ May CTP drop for the .zip file of samples to work). 

     

    Note: LINQ, DLINQ and XLINQ will be fully supported in both C# and VB.  I am using C# for the example belows.

     

    Step 0: Creating a C# LINQ ASP.NET Web Site

     

    To create a new ASP.NET Web Site that can use LINQ/DLINQ/XLINQ and the new C# 3.0 language features, choose File->New Web Site in VS and select the “LINQ ASP.NET Web Site Template”:

     

     

    This will create a web-site project with the following files in-it by default:

     

     

    Note that it includes a number of LINQ assemblies in the \bin folder.  It also adds the following setting to the app’s web.config file which tells both VS and ASP.NET to use the C# 3.0 compiler to compile and run the app:

     

        <system.codedom>

          <compilers>

            <compiler language="c#;cs;csharp"       

                      extension=".cs"

                      type="Microsoft.CSharp.CSharp3CodeProvider, CSharp3CodeDomProvider"/>

          </compilers>

        </system.codedom>

     

    Note that the C# 3.0 compiler and CodeDOM provider can run side-by-side with the C# 2.0 versions (so you don’t have to worry about it breaking VS or ASP.NET when you install it). 

     

    Step 1: Creating your first ASP.NET page using LINQ

     

    Create a new page called Step1.aspx.  Within the .aspx page add a GridView control like so:

     

    <%@ Page Language="C#" CodeFile="Step1.aspx.cs" Inherits="Step1" %>

     

    <html>

    <body>

        <form id="form1" runat="server">

        <div>

       

            <h1>City Names</h1>

       

            <asp:GridView ID="GridView1" runat="server">

            </asp:GridView>

       

        </div>

        </form>

    </body>

    </html>

     

    Within the code-behind file we’ll then write the canonical “hello world” LINQ sample – which involves searching and ordering a list of strings:


    using System;

    using System.Web;

    using System.Web.UI;

    using System.Web.UI.WebControls;

    using System.Query;

     

    public partial class Step1 : System.Web.UI.Page

    {

        protected void Page_Load(object sender, EventArgs e)

        {

            string[] cities = { "London", "Amsterdam", "San Francisco", "Las Vegas",

                                "Boston", "Raleigh", "Chicago", "Charlestown",

                                "Helsinki", "Nice", "Dublin" };

     

            GridView1.DataSource = from city in cities

                                   where city.Length > 4

                                   orderby city

                                   select city.ToUpper();

     

            GridView1.DataBind();

        }

    }

     

    In the above sample I’ve created an array of strings listing the cities I’ve visited from Jan->May of this year.   I’m then using a LINQ query expression against the array.  This query expression returns all cities where the city name is greater than 4 characters, and orders the result in alphabetical order and transforms those city names into upper case.

     

    LINQ queries return results of type: IEnumerable<T> -- where <T> is determined by the object type of the “select” clause.  In the above sample “city” is a string, so the type-safe result is a generics based collection like so:

     

            IEnumerable<string> result = from city in cities

                                         where city.Length > 4

                                         orderby city

                                         select city.ToUpper();

     

    Because ASP.NET controls already support databinding to any IEnumerable collection, we can easily assign this LINQ query result to the GridView and call its DataBind() method to generate this page output result:

     

     

    Note that instead of using the GridView control I could have just as easily used the <asp:repeater>, <asp:datalist>, <asp:dropdownlist>, or any other ASP.NET list control (both those built-into the product or ones built by other developers).  For the purposes of these samples I’m just going to use the <asp:gridview> -- but again know that you can use any. 

     

    Step2: Using Richer Collections

     

    Searching an array of strings is not terribly interesting (although sometimes actually useful).  More interesting would be the ability to search and work against richer collections of our own making.  The good news is that LINQ makes this easy.  For example, to better track trips I can create a simple class called “Location” in my project below:

     

    using System;

     

    public class Location

    {

        // Fields

        private string _country;

        private int    _distance;

        private string _city;

     

        // Properties

        public string Country

        {

            get { return _country; }

            set { _country = value; }

        }

     

        public int Distance

        {

            get { return _distance; }

            set { _distance = value; }

        }

     

        public string City

        {

            get { return _city; }

            set { _city = value; }

        }

    }

     

    This exposes 3 public properties to track the County, City name and Distance from Seattle.  I can then create a Step2.aspx file with a GridView control that defines 3 columns like so:

     

    <%@ Page Language="C#" CodeFile="Step2.aspx.cs" Inherits="Step2" %>

     

    <html>

    <body>

        <form id="form1" runat="server">

     

        <h1>Cities and their Distances</h1>

       

        <asp:GridView ID="GridView1" AutoGenerateColumns="false" runat="server">

           <Columns>

              <asp:BoundField HeaderText="Country" DataField="Country" />

              <asp:BoundField HeaderText="City" DataField="City" />

              <asp:BoundField HeaderText="Distance from Seattle" DataField="Distance" />

           </Columns>

        </asp:GridView>

       

        </form>

    </body>

    </html>

     

    I can then populate a collection of Location objects and databind it to the Grid in my code-behind like so:

     

    using System;

    using System.Collections.Generic;

    using System.Web;

    using System.Query;

     

    public partial class Step2 : System.Web.UI.Page

    {

        protected void Page_Load(object sender, EventArgs e)

        {

            List<Location> cities = new List<Location>{

                                        new Location { City="London", Distance=4789, Country="UK" },

                                        new Location { City="Amsterdam", Distance=4869, Country="Netherlands" },

                                        new Location { City="San Francisco", Distance=684, Country="USA" },

                                        new Location { City="Las Vegas", Distance=872, Country="USA" },

                                        new Location { City="Boston", Distance=2488, Country="USA" },

                                        new Location { City="Raleigh", Distance=2363, Country="USA" },

                                        new Location { City="Chicago", Distance=1733, Country="USA" },

                                        new Location { City="Charleston", Distance=2421, Country="USA" },

                                        new Location { City="Helsinki", Distance=4771, Country="Finland" },

                                        new Location { City="Nice", Distance=5428, Country="France" },

                                        new Location { City="Dublin", Distance=4527, Country="Ireland" }

                                    };

     

            GridView1.DataSource = from location in cities

                                   where location.Distance > 1000

                                   orderby location.Country, location.City

                                   select location;

     

            GridView1.DataBind();

        }

    }

     

    The above code-behind shows off a few cool features.  The first is the new C# 3.0 support for creating class instances, and then using a terser syntax for setting properties on them: 

     

    new Location { City="London", Distance=4789, Country="UK" }

     

    This is very useful when instantiating and adding classes within a collection like above (or within an anonymous type like we’ll see later).  Note that rather than use an array this time, I am using a Generics based List collection of type “Location”.  LINQ supports executing queries against any IEnumerable<T> collection, so can be used against any Generics or non-Generics based object collections you already have. 

     

    For my LINQ query I’m then returning a collection of all cities that are more than 1000 miles away from Seattle.  I’ve chosen to order the result in alphabetical order – first by country and then by city name.  The result of this LINQ query is again dictated by the type of the “location” variable – so in this case of type “Location”:

     

            IEumerable<Location> result = from location in cities

                                          where location.Distance > 1000

                                          orderby location.Country, location.City

                                          select location;

     

    When I databind this result against the GridView I get a result like so:

     

     

    Step 3: Refactoring the City Collection Slightly

     

    Since we’ll be re-using this collection of cities in several other samples, I decided to encapsulate my travels in a “TravelOrganizer” class like so:

     

    using System;

    using System.Collections.Generic;

     

    public class TravelOrganizer

    {

        public List<Location> PlacesVisited

        {

            get

            {

                List<Location> cities = new List<Location>{

                                            new Location { City="London", Distance=4789, Country="UK" },

                                            new Location { City="Amsterdam", Distance=4869, Country="Netherlands" },

                                            new Location { City="San Francisco", Distance=684, Country="USA" },

                                            new Location { City="Las Vegas", Distance=872, Country="USA" },

                                            new Location { City="Boston", Distance=2488, Country="USA" },

                                            new Location { City="Raleigh", Distance=2363, Country="USA" },

                                            new Location { City="Chicago", Distance=1733, Country="USA" },

                                            new Location { City="Charleston", Distance=2421, Country="USA" },

                                            new Location { City="Helsinki", Distance=4771, Country="Finland" },

                                            new Location { City="Nice", Distance=5428, Country="France" },

                                            new Location { City="Dublin", Distance=4527, Country="Ireland" }

                                        };

     

                return cities;

            }

        }

    }

     

    This allows me to then just write the below code in our code-behind to get the same result as before:

     

    using System;

    using System.Collections.Generic;

    using System.Web;

    using System.Web.UI;

    using System.Query;

     

    public partial class Step3 : System.Web.UI.Page

    {

        protected void Page_Load(object sender, EventArgs e)

        {

            TravelOrganizer travel = new TravelOrganizer();

     

            GridView1.DataSource = from location in travel.PlacesVisited

                                   where location.Distance > 1000

                                   orderby location.Country, location.City

                                   select location;

     

            GridView1.DataBind();

        }

    }

     

    What is really cool about LINQ is that it is strongly-typed.  What this means is that:

     

    1) You get compile-time checking of all queries.  Unlike SQL statements today (where you typically only find out at runtime if something is wrong), this means you will be able to check during development that your code is correct (for example: if I wrote “distanse” instead of “distance” above the compiler would catch it for me).

     

    2) You will get intellisense within VS (and the free Visual Web Developer) when writing LINQ queries.  This makes both typing faster, but also make it much easier to work against both simple and complex collection and datasource object models.

     

    Step 4: Skipping and Taking using .NET Standard Query Operators

     

    LINQ comes with built-in support for many built-in Standard Query Operators.  These can be used within code by adding a “using System.Query” statement at the top of a class file, and can be applied to any sequence of data.   For example, if I wanted to list cities in order of distance and list the 2nd->6th farthest away cities I could write my code-behind file like so:

     

    using System;

    using System.Web.UI;

    using System.Query;

     

    public partial class Step4 : System.Web.UI.Page

    {

        protected void Page_Load(object sender, EventArgs e)

        {

            TravelOrganizer travel = new TravelOrganizer();

     

            GridView1.DataSource = (from location in travel.PlacesVisited

                                   orderby location.Distance descending

                                   select location).Skip(1).Take(5);

     

            GridView1.DataBind();

        }

    }

     

    Note how I am ordering the result by the distance (farthest to least).  I am then using the “Skip” operator to skip over the first city, and the "Take" operator to only return the remaining 5. 

     

    What is really powerful is that the .NET Standard Query Operators are not a hard-coded list, and can be added to and replaced by any developer.  This enables very powerful domain specific implementations.  For example, when the Skip() and Take() operators are used with DLINQ – it translates the calls into back-end SQL logic that performs server-side paging (so that only a few rows are returned from the SQL database – regardless of whether it is from a table with 100,000+ rows of data).  This means that you will be able to trivially build efficient web data paging over lots of relational data (note: until then you can use the techniques listed here). 

     

    Step 5: More Fun with .NET Standard Query Operators

     

    In addition to returning sequences of data, we can use .NET Standard Query Operators to return single or computed results of data.  The below samples show examples of how to-do this:

     

    <%@ Page Language="C#" CodeFile="Step5.aspx.cs" Inherits="Step5" %>

     

    <html>

    <body>

        <form id="form1" runat="server">

        <div>

            <h1>Aggregate Value Samples</h1>

           

            <div>

                <b>Farthest Distance City:</b>

                <asp:Label ID="MaxCityNameTxt" runat="server" Text="Label"></asp:Label>

                <asp:Label ID="MaxCityDistanceTxt" runat="server" Text="Label"></asp:Label>

            </div>

           

            <div>

                <b>Total Travel Distance (outside of US):</b>

                <asp:Label ID="TotalDistanceTxt" runat="server" Text="Label"></asp:Label>

            </div>       

           

            <div>

                <b>Average Distance:</b>

                <asp:Label ID="AverageDistanceTxt" runat="server" Text="Label"></asp:Label>

            </div>       

           

        </div>

        </form>

    </body>

    </html>

     

    Step5.aspx.cs code-behind file:

     

    using System;

    using System.Collections.Generic;

    using System.Web.UI;

    using System.Query;

     

    public partial class Step5 : System.Web.UI.Page

    {

        protected void Page_Load(object sender, EventArgs e)

        {

            TravelOrganizer travel = new TravelOrganizer();

     

     

            //

            // Calculate farthest city away

     

            Location farthestCity = (from location in travel.PlacesVisited

                                     orderby location.Distance descending

                                     select location).First();

     

            MaxCityNameTxt.Text = farthestCity.City;

            MaxCityDistanceTxt.Text = "(" + farthestCity.Distance + " miles)";

     

     

            //

            // Calculate total city distances of all cities outside US

     

            int totalDistance = (from location in travel.PlacesVisited

                                 where location.Country != "USA"

                                 select location).Sum(loc => loc.Distance);

     

            TotalDistanceTxt.Text = totalDistance + " miles";

     

     

            //

            // Calculate average city distances of each city trip

     

            double averageDistance = travel.PlacesVisited.Average(loc => loc.Distance);

     

            AverageDistanceTxt.Text = averageDistance + " miles";

        }

    }

     

    Note that the last two examples above use the new Lambda Expression support – which enable fragments of code (like delegates) that can operate on top of data to compute a result.  You can build your own .NET Query Operators that use these (for example: you could build domain specific ones to calculate shipping costs or payroll tax).  Everything is strongly-typed, and will support intellisense and compilation checking support.

     

    The output of the above sample looks like so:

     

     

    Step 6: Anonymous Types

     

    One of the new C# and VB language features that LINQ can take advantage of is support for “Anonymous Types”.  This allows you to easily create and use type structures inline without having to formally declare their object model (instead it can be inferred by the initialization of the data).  This is very useful to “custom shape” data with LINQ queries. 

     

    For example, consider a scenario where you are working against a database or strongly-typed collection that has many properties – but you only really care about a few of them.  Rather than create and work against the full type, it might be useful to only return those properties that you need.  To see this in action we’ll create a step6.aspx file like so:

     

    <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Step6.aspx.cs" Inherits="Step6" %>

     

    <html>

    <body>

        <form id="form1" runat="server">

        <div>

           

            <h1>Anonymous Type</h1>

       

            <asp:GridView ID="GridView1" runat="server">

            </asp:GridView>

           

        </div>

        </form>

    </body>

    </html>

     

    And within our code-behind file we’ll write a LINQ query that uses anonymous types like so:

     

    using System;

    using System.Web.UI;

    using System.Query;

     

    public partial class Step6 : System.Web.UI.Page

    {

        protected void Page_Load(object sender, EventArgs e)

        {

            TravelOrganizer travel = new TravelOrganizer();

     

            GridView1.DataSource = from location in travel.PlacesVisited

                                   orderby location.City

                                   select new {

                                        City = location.City,

                                        Distance = location.Distance

                                   };

              

            GridView1.DataBind();

        }

    }

     

    Note that instead of returning a “location” from our select clause like before, I am instead creating a new anonymous type that has two properties – “City” and “Distance”.  The types of these properties are automatically calculated based on the value of their initial assignment (in this case a string and an int), and when databound to the GridView produce an output like so:

     

     

    Step 7: Anonymous Types (again)

     

    The previous sample showed a basic example of using anonymous types to custom-shape the output of a LINQ query.  The below sample provides a richer and more practical scenario.  It transforms our list of cities into a hierarchical result collection – where we group the results around countries using an anonymous type that we define that contains the country name, a sub-collection list of city details, and the sum of the total distance of all cities within the country (computed using a lambda expression like we demonstrated in step5 above):

     

    using System;

    using System.Web.UI;

    using System.Query;

     

    public partial class Step7 : System.Web.UI.Page

    {

        protected void Page_Load(object sender, EventArgs e)

        {

            TravelOrganizer travel = new TravelOrganizer();

     

            GridView1.DataSource = from location in travel.PlacesVisited

                                   group location by location.Country into loc

                                   select new {

                                       Country = loc.Key,

                                       Cities = loc,

                                       TotalDistance = loc.Sum(dist => dist.Distance)

                                   };

              

            GridView1.DataBind();

        }

    }

     

    The GridView on our .aspx page is then defined like so:

     

    <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Step7.aspx.cs" Inherits="Step7" %>

     

    <html>

    <body>

        <form id="form1" runat="server">

        <div>

            <h1>Groupings with Anonymous Classes</h1>

     

            <asp:GridView ID="GridView1" AutoGenerateColumns="false" runat="server">

                <Columns>

               

                    <asp:BoundField HeaderText="Country" DataField="Country" />

               

                    <asp:TemplateField HeaderText="Cities">

                        <ItemTemplate>

                       

                            <asp:BulletedList ID="BulletedList1" runat="server"

                                              DataSource='<%#Eval("Cities")%>' DataValueField="City"/>

                       

                        </ItemTemplate>

                    </asp:TemplateField>

               

                    <asp:BoundField HeaderText="Total Distance" DataField="TotalDistance" />

               

                </Columns>

            </asp:GridView>

        </div>

        </form>

    </body>

    </html>

     

    Notice how I’ve added a GridView templatefield column for the “Cities” column – and within that I’ve then added an <asp:bulletedlist> control (a new control built-in with ASP.NET 2.0) that databinds its values from the cities property of the hierarchical result we created using our LINQ query above.  This generates output like so:

     

     

    Note that all of the databind syntax and hierarchical binding support in the .aspx page above is fully supported in ASP.NET 2.0 today – so you can use this same technique with any existing app you have now.  What is new (and I think very cool) is the data shaping capabilities provided by anonymous types and LINQ – which makes binding hierarchical data against ASP.NET controls very easy.

     

    Next Steps

     

    All of my samples above were against in-memory collections.  They show you how you will be able to use LINQ against any .NET object model (includes all the ones you have already).

     

    In my next few LINQ-related blog postings I’ll show how you can go even further, and take advantage of the new DLINQ support to use the above techniques against relational databases as well as the new XLINQ support to work against XML files and structures.  What is great about the LINQ project is that the syntax and concepts are the same across all of its uses – so once you learn how to use LINQ against an array or collection, you also know all the concepts needed to work against a database or even XML file.

     

    For example, if you use DLINQ to generate a Northwinds database mapping of Suppliers and their Products (no code is required to set this up), the below code is all you need to write to obtain and databind a hierarchical database result against a GridView like we did above (note: we are using the same data-shaping technique as our previous sample to only require fetching two columns from the database, and automatically join the products of each supplier as a hierarchical group result):

     

    using System;

    using System.Query;

     

    public partial class Data_Data2 : System.Web.UI.Page

    {

        protected void Page_Load(object sender, EventArgs e)

        {

            Northwind db = new Northwind();

     

            GridView1.DataSource = from x in db.Suppliers

                                   where x.Country == "USA"

                                   orderby x.Country

                                   select new {

                                        x.CompanyName,

                                        x.Country,

                                        x.Products

                                   };

     

            GridView1.DataBind();

        }

    }

     

    No custom SQL syntax or code is required – this is all that needs to be written to efficiently fetch and populate hierarchical data now (note: only the rows and columns needed will be fetched -- DLINQ can use the remote function support within LINQ so that it does not need to materialize or fetch the full database table or all columns from a row).  And it is all type-safe, with full compiler checking, intellisense, and debugging supported.

     

    Even better, the ability to plug-in new LINQ providers (of which DLINQ and XLINQ are just two examples) is completely open – so developers who either build or use existing data providers today (for example: O/R database mappers) can easily integrate their implementations with LINQ to have a seamless developer experience.  Once you know LINQ you will know all the basics needed to program against any of them.

     

    Summary

     

    Hopefully this provides a glimpse of some of the cool new things coming.  You can try it all out today by downloading the May CTP drop of LINQ today from here.  You can also download and run all of the samples I built above from this .ZIP file here.

     

    Hope this helps,

     

    Scott

  • VS 2005 Web Application Project V1.0 Released

    I am very happy to announce that the final release of the VS 2005 Web Application Project is now available.   You can download it for free here.

     

    The VS 2005 Web Application Project option provides an alternate web project model option to the VS 2005 Web Site Project Model that ships built-into VS 2005.  VS 2005 Web Application Projects support the same project, build and compilation semantics as the VS 2003 web project model.  Specifically:

    • All files contained within the project are defined within a project file (as well as the assembly references and other project meta-data settings).  Files under the web’s file-system root that are not defined in the project file are not considered part of the web project.
    • All code files within the project are compiled into a single assembly that is built and persisted in the \bin directory on each compile.  Incremental publishing of compiled apps is fully supported within the IDE (see this post for details).
    • The compilation system uses a standard MSBuild based compilation process.  This can be extended and customized using MSBuild extensibility rules.  You can control the build through the property pages, name the output assembly or add pre- and post-build action rules.  It can also provide much faster compile times for large web projects.

    Because the VS 2005 Web Application Project model has the same conceptual semantics as the VS 2003 Web Project Model, it also makes migrating VS 2003 web projects very, very easy – with zero/minimal code changes required.  To learn how to automatically upgrade a VS 2003 web project using this option, please review these VB and C# tutorials that walkthrough the VS 2003 to VS 2005 upgrade process step-by-step.

     

    If you want to migrate an existing VS 2005 Web Site Project to be a VS 2005 Web Application Project, please also review these other VB and C# migration tutorials that walkthrough the Web Site to Web Application conversion process step-by-step.  This article here also describes some of the differences between the VS 2005 Web Site Project Model and VS 2005 Web Application Project Model.

     

    Note that the VS 2005 Web Application Project option is available now as a free web download.  It will also be added built-in to VS 2005 SP1, and will be fully supported with Visual Studio releases going forward.  

     

    New Features with this final VS 2005 Web Application Project Release

     

    Last month I provided an end-to-end walkthrough example that detailed using the VS 2005 Web Application Project (Release Candidate) to build, develop and publish a new ASP.NET application.  You can review this walkthrough here.

     

    The main feature additions with the final VS 2005 Web Application Release (above and beyond all the features in the release candidate that you can read about here) include:

    • Team Build Support with VSTS: You can now perform automated and command-line builds with VS 2005 Web Application Projects and VSTS
    • Resource support with the App_GlobalResources directory: Strongly typed resource classes are now automatically generated for resource files defined with the ASP.NET app_globalresources directory (allowing you to program against these directly).  You can also alternatively define .resx files within the code-behind assembly of the project itself.
    • Custom Build Tool Action Support: You can now configure and define custom build tool action support for file-types within a project.  This was missing in the previous release candidate and prevented some third-party utilities from working.
    • Edit and Continue Support: You can now make code updates that apply immediately to applications while the debugger is attached and running (see walkthrough below for details).  This is supported with both VB and C# projects. 

    Available as a separate download is a custom build tool for generating a strongly-typed Profile class for the ASP.NET 2.0 Profile system.  This allows you to right-click on a web.config file containing profile declarations and auto-generate the Profile type into your code-behind project assembly.  You can learn more about this and download it here. 

     

    Walkthrough of Edit and Continue Support with VS 2005 Web Application Projects

     

    One feature we added into this final release of the VS 2005 Web Application Project option is “edit and continue” support for both VB and C# web applications.  “Edit and Continue” allows developers to modify code while running and debugging an application – without having to stop, recompile and re-launch it.  This is true for both code contained within the web project itself as well as for class library projects that the web project references.  This enables a much more dynamic coding experience.

     

    The below walkthrough demonstrates how you can easily enable and use “edit and continue” with web application projects. 

     

    Step 1: Create a New ASP.NET Web Application Project

     

    Select File->New Project in Visual Studio and choose the “ASP.NET Web Application” template to create a new web project.

     

    Step 2: Add a Button to an ASP.NET Page

    Create a new .aspx page within the project.  Add a button to the .aspx page, and then double click to create an event handler like so in the code-behind file:

     

    using System;

    using System.Data;

    using System.Configuration;

    using System.Collections;

    using System.Web;

    using System.Web.Security;

    using System.Web.UI;

    using System.Web.UI.WebControls;

    using System.Web.UI.WebControls.WebParts;

    using System.Web.UI.HtmlControls;

     

    namespace WebApplication3

    {

        public partial class _Default : System.Web.UI.Page

        {

            protected void Button1_Click(object sender, EventArgs e)

            {

                Button1.Text = "I was pushed!";

            }

        }

    }

     

    Step 3: Enable Edit and Continue for the Web Application Project

     

    By default edit and continue is not enabled for newly created web application projects.  To enable it for a web application project, right-click on the web project in the solution explorer and choose the “Properties” menu item.  Select the “web” tab and check the “edit and continue” checkbox:

     

        

     

    Note that this checkbox can only be enabled when running the application using the built-in VS 2005 Web Server (this is because VS needs to launch the process – and IIS is a service app that doesn’t currently support this).

     

    Step 4: Launch the App Using F5

     

    Run the application in VS by pressing F5 (Run with Debugging) or by hitting the “Run” button.  This will launch the VS 2005 built-in web-server and bring up a browser with a page with the button on it.  Push the button to see the message we programmed above.

     

     

    Step 5: Set and hit a Breakpoint

     

    Switch back to VS -- without stopping debugging or shutting down the browser – and set a breakpoint on the line of code we wrote above (press F9 to-do this).  Then within the browser push the button again to break in the debugger at that location:

     

     

    While stopped within the debugger, I can now add or modify any code within this event handler I want.  For example, I could add the following conditional logic to the event handler and modify the Button1.Text statement like so:

     

     

    And then hit “F5” to continue running the app and immediately see the changes:

     

     

    There is no need to stop or recompile the application for these types of changes (you’ll notice I was also using FireFox to run the app just for additional fun <g>).

     

    Note that you can also use Edit and Continue support within Class Library projects consumed by a Web Application Project (as well as obviously standalone classes within the project).  For example: you could modify the method of a class within a class library project that is being called by an ASP.NET page.

     

    The one constraint of using edit and continue within web projects is that you can’t perform any operations that will cause a restart of the application’s app-domain (for example: modifying the web.config file).  Doing so will require you to re-launch the debugger session to see code changes applied.  Note that code changes in a class library project or within the web project don’t cause the app-domain to be restarted while running under the debugger – so these types of code changes are just fine.

     

    Summary

     

    A page containing known issues for this release of the VS 2005 Web Application Projects can be found here (for example: two features that won’t be added until the SP1 release are support for the new Report Designer Controls and Mobile Web Form Pages).  We will also be publishing several additional whitepapers over the next few weeks that go into more details about how to use the VS 2005 Web Application Project option and answer common questions. 

     

    This final release of the VS 2005 Web Application Project does require you to install a publicly available patch for VS 2005 before you install the VS 2005 Web Project template support.  Unfortunately only the VS English version of the patch is currently available – we are actively working on the non-English SKU editions of VS now (note: you can use the English version of VS on a non-English version of Windows -- that is supported by the patch now).  There is a workaround that we discuss in the known-issues/readme link above that you can use if you want to install it immediately (basically you install an English version of VS on your machine to install the patch, and then switch the language back).  We will be publishing a blog post about this in the next week or two that provides more details on how to-do this, and then obviously the non-English patches of VS when they are ready in a few weeks time.

     

    As I mentioned earlier, you can also walkthrough other tutorials on the VS 2005 Web Application Project on my web-site here.  There is also a dedicated VS 2005 Web Application Project forum that you can use here to post questions or get help (the team actively monitors these forums and is eager to help).

     

    Hope this helps – and thanks for your patience over the last few months,

     

    Scott

     

    P.S. If you have a release candidate or beta build of the VS 2005 Web Application Project already installed, you can simple shut-down VS and then uninstall that old version, and then install the final release. 

  • CSS Control Adapter Toolkit for ASP.NET 2.0

    Tired of having <table> elements rendered by the built-in ASP.NET server controls and wishing you could use a pure CSS solution instead?  Read on...

     

    Today we published the CSS Control Adapter Toolkit for ASP.NET.  This toolkit provides information about how the ASP.NET 2.0 Control Adapter Architecture works, as well as a set of 5 sample control adapters (with full source that you can optionally tweak/modify) that provide CSS friendly adapters for 5 of the built-in ASP.NET controls (specifically: Menu, TreeView, DetailsView, FormView and DataList). 

     

    You can download this release for free from here, and immediately begin using it in your ASP.NET 2.0 sites today.

     

    What does a control adapter let me do?

     

    A control adapter allows you to plug-into any ASP.NET server control and override, modify and/or tweak the rendering output logic of that control. 

     

    What is cool about control adapters is that they do not require a page developer to program against a new control, or modify the control programming model semantics (you still use the same control properties, methods, events and templates you did before).  Indeed – a page developer can be entirely oblivious that a control adapter is being used (the control adapter model makes it very clean to register and encapsulate this support).

     

    The CSS Control Adapter Toolkit includes a bunch of pre-built control adapter samples that show how you can use the control adapter architecture to emit 100% CSS based rendering output (no tables or inline styles – instead use external CSS stylesheets for everything). 

     

    [Note: If you want your brain to start exploding in terms of possibilities, you can check out the cool XAML/Avalon control adapters that Plip built that cause ASP.NET controls to output Avalon markup.  You can check it out here]

     

    Show me an example of how I use these control adapters?

     

    To see a simple example of control adapters being used, you can download this self-encapsulated menu sample I built here. 

     

    Note that you do not need to install the CSS Adapter toolkit to run my sample (the CSS Adapter Toolkit mainly includes samples and documentation – you can directly embed adapters into any ASP.NET application and doing so doesn’t require you to install or update any system components). 

     

     

    The above menu sample contains a Web.SiteMap file that defines the site navigation structure of an ASP.NET 2.0 Site (note: to learn more about the new ASP.NET 2.0 Site Navigation features read this past post).  It then contains two pages – menu1.aspx and menu2.aspx – that use the standard ASP.NET 2.0 Menu control to databind against the SiteMap.

     

    Menu1.aspx shows how to build a horizontal sub-menu navigation system (note: below is all of the code and markup – there is no code needed in the code-behind or on other pages or classes):

     

    <%@ Page Language="C#" AutoEventWireup="true"  CodeFile="Menu1.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>Menu Sample1</title>

        <link href="StyleSheets/Menu.css" rel="stylesheet" type="text/css" />

        <link href="StyleSheets/MenuStyle.css" rel="stylesheet" type="text/css" />

    </head>

    <body>

        <form id="form1" runat="server">

        <div>

           

            <asp:Menu ID="Menu1"

                      runat="server"

                      Orientation="Horizontal"

                      CssSelectorClass="PrettyMenu"

                      EnableViewState="false"

                      DataSourceID="SiteMapDataSource1">

            </asp:Menu>

       

            <asp:SiteMapDataSource ID="SiteMapDataSource1"

                                   ShowStartingNode="false"

                                   runat="server" />

        </div>

        </form>

    </body>

    </html>

     

    It renders like this in FireFox:

     

     

    Menu2.aspx shows how to build a vertical sub-menu navigation system:

     

    <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Menu2.aspx.cs" Inherits="Menu2" %>

     

    <!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>Menu Sample2</title>

        <link href="Stylesheets/MenuStyle.css" rel="stylesheet" type="text/css" />

        <link href="Stylesheets/Menu.css" rel="stylesheet" type="text/css" />

    </head>

    <body>

        <form id="form1" runat="server">

        <div>

         

            <asp:Menu ID="Menu3"

                      runat="server"

                      CssSelectorClass="PrettyMenu"

                      EnableViewState="false"

                      DataSourceID="SiteMapDataSource1">

            </asp:Menu>

           

            <asp:SiteMapDataSource ID="SiteMapDataSource1"

                                   ShowStartingNode="false" runat="server" />

       

        </div>

        </form>

    </body>

    </html>

     

    It renders like this (also using FireFox):

     

     

    Because I’ve registered the CSS Menu Adapter for my site, the output from both menu controls are rendered using <ul><li></ul> html elements instead of tables.  I am using two CSS stylesheets within the site to stylize them.  One is a stylesheet called “Menu.css” that comes with the CSS Toolkit and contains the standard “behavioral” rules for menu controls (for example: what elements are blocks vs. inline, etc).  The expectation is that developers don’t change these often.  The second stylesheet is called “MenuStyle.css” and contains the site specific style behavior (colors, fonts, sizes, location, etc).  I can either work on this file directly myself, or pass it off to a designer to use standard CSS styling techniques to customize it. 

     

    Click here to see a diagram that is included with the CSS Toolkit Whitepaper to see the default styling rules used with the CSS Menu Adapter. 

     

    Note that if you want to customize the rendering even further you can easily tweak the CSS Menu Adapter to modify it however you want (the source for each CSS adapter ships with both VB and C# versions).  Laurent has a good example of how he did that to stylize Site Navigation node selection here.

     

    So how does someone create an ASP.NET control adapter from scratch?

     

    Creating a control adapter is fairly straight-forward.  Basically you do two things:

     

    1) Create a class that derives from the System.Web.UI.Adapters.ControlAdapter base class or one of the built-in sub-classes of it (for example: System.Web.UI.WebControls.Adapters.MenuAdapter or System.Web.UI.WebControls.Adapters.DataBoundControlAdapter).  Within the sub-class, you then override the appropriate methods to customize the markup however you want. 

     

    For example, the below code-snippet shows a few snippets from the CSSFriendly.MenuAdapter that ships with the CSS Adapter Toolkit:

     

     

    Note how the “BuildItems” method (which is called from the “RenderContents” virtual method) is used to output <ul> elements instead of tables for the Menu output.  Notice also the built-in helper methods that can be used to support indentation and HTML tag rendering.

     

    The Control Adapter class that you create can be added into your web project and compiled as part of the project.  Alternatively, for greater re-use you could add it to a class library project and reference it from multiple web-projects (allowing nice re-use and factoring). 

     

    2) Once you have created a ControlAdapter class that customizes the output however you want, you register it within your ASP.NET 2.0 Web Application by adding a .browser file within the /App_Browsers sub-directory underneath the application root:

     

     

    This .browser file can contain browser capability rules (declared using XML) that allow you to customize whether a control adapter is used – and optionally even configure a separate one for each browser or input device.  For example, if you wanted to only use the above Menu control adapter for Versions 6-9 of IE (but fall back to using tables with IE5), you would register it like so within your .browser file:

     

    <browsers>

      <browser refID="IE6to9">

        <controlAdapters>

          <adapter controlType="System.Web.UI.WebControls.Menu"

                   adapterType="CSSFriendly.MenuAdapter" />

        </controlAdapters>

      </browser>

     

      <browser refID="MozillaFirefox">

        <controlAdapters>

          <adapter controlType="System.Web.UI.WebControls.Menu"

                   adapterType="CSSFriendly.MenuAdapter" />

        </controlAdapters>

      </browser>

     

    </browsers>

     

    Once you’ve saved the above two rules you are all set.  When an IE 6->9 browser or FireFox browser hits the site now, all Menu controls will render using CSS style-friendly output (specifically <ul><li></ul> elements).  When an IE browser hits the site, the Menu controls will render using tables. 

     

    You do not need to modify any page code or code-behind code to use this.

     

    How to Get Started with the CSS Adapter Toolkit

     

    You can learn more about the CSS Adapter Toolkit and download it from here.  The first thing you should read is the CSS Adapter Toolkit Whitepaper to understand better how it works, and understand the default CSS rules that the CSS Adapters in the toolkit output by default.

     

    Once you’ve download and installed the CSS Adapter Toolkit you can then choose “File->New Web Site” using either VS 2005 or Visual Web Developer (which is free) and select the CSS Adapter Toolkit Starter Kit:

     

     

    You can choose to create either a VB or C# version of it.  Once created it will install the CSS Adapters, associated .browser files, and a bunch of samples that use it:

     

     

    Just select the default.aspx page and hit run to get started.  You can then browse through the samples and step through the adapter code to see how they work.

     

    To re-use the adapters as-is, just copy the files in the /app_code/adapters/ sub-directory into your applications, along with the .browser file in /app_browsers.  You will then also want to copy the appropriate static CSS stylsheets and JavaScript files as well.  And then you are good to go.

     

    To ask questions about the adapters and/or ask questions, please check out this forum here.

     

    Hope this helps,

     

    Scott

     

    P.S. We are calling this first release of these adapter samples a "beta" and are actively looking for feedback (we'll then update the adapter samples and add more).  Please leave suggestions on how to improve it in this forum here.

     

    P.P.S. The CSS Adapter Toolkit is released under a permissive license that allows full re-use of the code for both commercial and non-commercial applications.  Feel free to re-use the adapter code however you want (we’d also love you to post sample adapters that you build!). 

     

    P.P.P.S. If you are looking for another really cool extensibility download we’ve done recently, check out this post about the source release of the ASP.NET 2.0 Providers.

  • SharePoint 2007 -- Built on ASP.NET 2.0

    One of the things we did with ASP.NET 2.0 was to work very closely with the SharePoint and CMS teams within Microsoft to enable much richer architectural and developer integration than we had with previous releases.

     

    Specifically, we tried to drive many of the core architectural requirements and scenarios they and other portal/CMS vendors had into the ASP.NET 2.0 runtime (for example: web parts, virtual path providers and compilation, site navigation, membership and role management, personalization, etc).  The SharePoint/WSS/CMS Teams are then building their new releases on top of these ASP.NET 2.0 APIs -- and will have the Beta2 versions of these apps out shortly.  This will enable .NET developers to learn and master a single core set of APIs and then easily re-use this across any type of web application they are building – whether it is a SharePoint Portal, a CMS application, or a totally custom ASP.NET web application.

     

    A few of the many cool extensibility scenarios this enables:

     

    1) You can now build a web-part control that supports drag/drop user-personalization and customization and use it within any vanilla ASP.NET 2.0 application, or host it within a SharePoint 2007 or Windows SharePoint Services (WSS) site.

     

    2) You can build a class library, control, or page that uses the Membership, Roles, Profile, or Site Navigation APIs and re-use it across both a custom ASP.NET application and a SharePoint/CMS site.  SharePoint will ship with a bunch of SharePoint providers that plug-in using the standard ASP.NET 2.0 Provider API (for example: they ship a SharePoint provider that integrates the SharePoint Page and List models under the ASP.NET 2.0 Site Navigation API).  This means you get *a lot* more mileage with your code, and can re-use your API knowledge for a lot more projects.

     

    3) You can plug-in your own custom providers to extend SharePoint and WSS solutions just like you would vanilla ASP.NET 2.0 sites.  Because SharePoint uses the standard ASP.NET 2.0 APIs for things like Membership, this means you can now easily change the authentication mode and membership storage for SharePoint solutions (previous versions required Windows Credentials).  Sahil Malik posted last week here about how to-do this.  In Sahil’s post he was using the default ASP.NET Membership Provider to enable Forms Authentication for a SharePoint site.  What is cool is that you could actually plug-in *any* ASP.NET membership provider and have this scenario work.  You can also now download the source-code to the built-in ASP.NET providers, customize them or write your own, and then add them to a SharePoint or WSS solution.

     

    We think all of this unification is going to enable a bunch of really cool scenarios for .NET developers going forward.  It also helps validate and drive requirements down to the APIs we ship in ASP.NET and the .NET Framework, and ensures that they provide all the hooks needed to build big feature-rich applications on top of them (having the Office Division starting to build on top of the new ASP.NET 2.0 APIs two years ago really drove a bunch of great enhancements and improvements to their extensibility and capabilities).

     

    Best of all, it means you can also start more projects using SharePoint or WSS (note: Windows SharePoint Services is a free download and can be deployed totally free on Windows Server), and quickly create a solution with rich document management and collaboration support already built-in (including client Office tool support) – and then be able to customize and enhance it further using the ASP.NET 2.0 and VS 2005 skills you already know. 

     

    This should enable you to build and deploy really great solutions even faster, and make your customers (whether internal or external) even happier. 

     

    Hope this helps,

     

    Scott