Freigeben über


Mix08 Session Overview: Building Great AJAX Applications from Scratch Using ASP.NET 3.5 and Visual Studio 2008

I am really looking forward to my Mix08 session this year... It is going to be fun to building an Ajax application from scratch on stage with VS2008 and ASP.NET 3.5.  

 

Update (3/15) : I made a few tweaks based on comments to this post.  thanks!

Update (3/07):   The session video is now posted.  There is even a place to discuss the session.

Here is a play-by-play if you want to follow along and help keep me in line...  This Mix08 folks should have the video up tomorrow... I will link to it when it is up.

Everything I will show can be done with the free-and-always free Visual Web Developer Express edition of VS... Go grab it and play along! 

Download the completed sample or just the starter files to play along at home.  

Part I:  CSS and HTML Designer

I started a brand new project and selected .NET Framework 2.0 as the target runtime.  This allows me use VS2008, but target servers that have not been upgraded to .NET Framework 3.5 yet. 

image

I then showed off the new Split view that is an excellent way to find out what markup goes with what UI element.  Notice as I highlight the text in the markup, it is tracked and highlighted in the designer.  I can also do two-way editing in the designer or the code source view. 

image

Next, I used VS 2008's great new CSS support.  I used the Manage Styles panel to create a new CSS Selector...  Notice I use ".photolist li" this rule will apply to any li inside an element with the class photolist. 

Also, notice we can select where to define the css rules in.  I have selected in a new style sheet. VS will create the new style sheet and (optionally) reference it from the current page. 

image

Next, I go into the markup and set the class for the ul to photolist.  Notice the great automatic completion VS gives me.

image

Now, i can open up the CSS file and see how clean it is?  Also, notice I can do code-focused editing of the CSS rather than use the designers.  Again, I get great help from VS.

image

Next, in design view, I click on an image... notice the breadcrumb at the bottom of VS shows you where you are in the visual tree.  Hit escape to navigate up the tree until the list item (li) is selected. 

image

We want this to layout like a grid, so in the CSS Properties window, under Position, set Display to "inline" and float to "left"

image

Next we use the grab-handles in the designer to give us a little more spacing... Again, notice how all these edits are cleanly going into the attached CSS style sheet rather than gunking up our code.

Finally, let's make this look really nice by throwing it a MasterPage will will give a common look and feel across our site.

And we are starting go have something that looks sort of nice!

image

To close this part of the demo off, let's go ahead and upgrade the site to use .NET Framework 3.5...
Before we do that, notice under Add References... there are a bunch of references grayed out.  This is to keep developers from accidentally using some new stuff that is only in .NET Framework 3.5. 

image

Under Project, Properties, Build Options you can change the target framework to be .NET Framework 3.5

image

Now, you will notice that all the references are available... 

image

Part II:  Data access with Linq

OK, that was great, but in a real world site you don't deal with static images... you need to pull data out of a database.   So let's change the site around a bit to pull data from a database.

First, i will drag in photos.mdb into App_Data, this is a SqlServer database that I created for this demo... I will use the free Sql Express to allow me to use it at development time.

As you can see it is a simple database.

image

Now, as a .NET developer, I'd really like to operate against this database as a set of .NET types rather than having to remember and debug some T-Sql code in my .NET code.  Luckily Linq solves this for me!

To add a Linq data model for this database... Add New Item, then select the Linq To Sql classes (note: Entity Framework works in an identical way)

image

Then drag the Photos and Tags table over the new design surface.

image

This is the view of your .NET types that represent your database.  Notice VS picked up on the relationships between the tables and even gave pretty names to the .NET classes (Photo rather than Photos for example).   You can of course edit these names in this designer view as well. 

Notice, while this will do direct CRUD operations against the database, it is very possible to encapsulate all access through stored procs if need be. 

Now, let's go back to the UI and give us some place to put this data.  I will show off the new ListView control in ASP.NET. 

The way ListView works, is that you give it a layout template that defines exactly how you work the markup to look.  In our case, we will just cut and pate that ul into the layout template and define a place holder of the li's

     <asp:ListView runat="server" ID="photoList" ItemPlaceholderID="ph">
        <LayoutTemplate>
            <ul class="photolist">
                <asp:PlaceHolder runat="server" ID="ph"></asp:PlaceHolder>
            </ul>
        </LayoutTemplate>
    </asp:ListView>

Then, we can set the ItemTemplate to be one of the LIs... later we will need to come back and databind these values rather than using a hard coded example.

     <asp:ListView runat="server" ID="photoList" ItemPlaceholderID="ph">
        <LayoutTemplate>
            <ul class="photolist">
                <asp:PlaceHolder runat="server" ID="ph"></asp:PlaceHolder>
            </ul>
        </LayoutTemplate>
        <ItemTemplate>
            <li>
                <img src="Images/1.jpg" />
                <br />
                Eat more Cake 
           </li>
        </ItemTemplate>
    </asp:ListView>

Flip to design view, it looks pretty nice!  Now we just need some real data.

 image

To get the real data, let's go back to that Linq data model we created and write some code against it. 

    1:          int i = 4;
    2:          PhotosDataContext db = new PhotosDataContext();
    3:          var q = from photo in db.Photos
    4:                  where photo.Rating == i
    5:                  orderby photo.Tags.First().TagName ascending
    6:                  select photo;
    7:          photoList.DataSource = q;
    8:          photoList.DataBind();

In line 2, we create the data context... this is the logic connection string for our Linq data model.

In line 3, we are using C#'s new var keyword which allows us to fudge on the type of the express...the compiler will figure it out...

In line 4, we are doing a where clause, notice that I am capturing a local variable in that, clearly this example is trivial, but it is a powerful feature.

in line 5 notice i am ordering by the first tag name, notice how nice this reads?  It would be much harder to do this  in TSQL!

in line 7 and 8 and I doing the normal databinding.

Now we need to go into the UI and set it up to pull values from this data source.

         <ItemTemplate>
            <li>
                <img src="Images/<%#Eval("ImagePath") %> />
                <br />
                <%#Eval("Title") %>
           </li>

  The results!

image

Now, that is nice, but there are a ton of results here..  we really need a nice way to page through them.   

I use the new DataPager control in ASP.NET 3.5..  Notice it can have filed that are simple Next\Prev or numeric or you can even build your own!

image

Here is the complete code:

     <asp:DataPager runat="server" ID="dp"
                    PagedControlID="photoList">
        <Fields>
            <asp:NumericPagerField />
        </Fields>
    </asp:DataPager>

Now, we need to ensure that the ListView will show the correct items when the data pager changes.  We do this by adding a PreRender() method to the page.  A simplified view of the page lifecycle is Page_Load(), then the control events are raised, then Page_PreRender() is called.   In our case the events from the pager need to be raised to change the page before we bind the list.

 protected void Page_PreRender()
{
    photoslist.DataBind();
}
 

And notice it works great!

image

Part III:  Server Side Ajax

Now it is time to add a little ajax fun to the site.   Notice as I page through the items I am getting a little blink?  That is because the page is doing a full post-back to the server when only the images are changing.  It would be great if I could update only the part of the page that is changing! 

And you can with UpdatePanel. Simply wrap the UpdatePanel's content template around the area of the page that you want to update separately.  the runtime will turn any postbacks into XmlHttp calls, back the the server, get the right HTML and then bring in back and update only the part of the DOM effected.   Simple. Powerful. 

image

Oh, and don't forget to add ScriptManager to the top of the page.  This "turns on" the ajax functionality in ASP.NET...

 <asp:ScriptManager runat=server id=sm></asp:ScriptManager>

Now the page refreshes much more smoothly. 

For the demo, we are running against localhost, but in the real world there is network load, congestion on the Internet, etc that may effect your page's responsiveness.   But you want to give user's an immediate response to their actions... to let them know that something is happening.   

You can use a Thread.Sleep (2000) in your page's load method to simulate this sort of load.. 

Enter UpdateProgress...   This little control listens for calls over XmlHttp and shows a progress template as they go out and takes it down when they come back in...  That plus a little animate gif gives users a great sense of "something happening". 

     <asp:UpdateProgress ID="UpdateProgress2" runat="server">
        <ProgressTemplate>
            <div id="UpdateProgressBox">
                <img id="Img2" runat="server" alt="Loading" src="~/Images/ajax-loader.gif" />
            </div>
        </ProgressTemplate>
    </asp:UpdateProgress>

  And now the site looks good!

image

Part IV:  Client Side Ajax

Next we want to allow users to edit those titles, but in a smooth way that doesn't require form postback to the server.  

We start by changing the rendering of the title to an input control 

 <input id="titleTextBox"
       onchange="onTitleChange(this.value, <%#Eval("ID") %>);"
       value="<%#Eval("Title") %>" />

Now we need to go define the onTitleChange client side javascript method. 

We do this in the header content area our MasterPage has left us.  

     <script type="text/javascript">
        function onTitleChange (title, id) {
           
        }
    </script>

Notice that VS 2008 has some great intellisense for JavaScript.   We are actually running a JavaScript interpreter in the background, so we always know what type the objects are. 

For example, here VS knows n is a Dom element. 

image 

But we can easily change the type to a string...  and VS keeps up!

image

We know debugging in JavaScript can be hard... I know I have used alert() based debugging too many times!  Well, VS2008 is here to help with full debugging support for client side javascript. 

image

Lots of cool stuff to show you here.  Notice at the top right, full debugging controls, run, break, step over, step into, etc.
At the mid-left, notice the icon's for break point, and current executing line.
At the mid-right, notice the debugging views I get that show me exactly what the current value of all the locals and parameters are.
At the bottom right, notice my the results of my call to Sys.Debug.Trace() are beging displayed in the output window. 
And finally at the bottom right, notice I have full control to use the immediate window to evaluate results in the current context. 

Ok, cool, but now we want to actually go change the datamodel on the server... How do I do that? 

Well, first define a WCF-Ajax service.  This well expose JSON calls that is very ajax friendly.   Add, New Item, Ajax-enabled WCF service.

image

Now, we need to implement a web services that will update our data model. 

    1:      public string SetPhotoTitle(string title, int id)
    2:      {
    3:          PhotosDataContext db = new PhotosDataContext();
    4:          var q = (from photo in db.Photos
    5:                  where photo.ID == id
    6:                  select photo).First();
    7:          q.Title = title;
    8:          db.SubmitChanges();
    9:          return q.Title;
   10:      }

Then we just need to call it from client side javascript.  To do that, we need to add a reference in ScriptManager. 

     <asp:ScriptManager runat="server" ID="sm">
        <Services>
            <asp:ServiceReference Path="~/PhotoService.svc" />
        </Services>
    </asp:ScriptManager>

Then, we can directly access the SetPhotoTitle method from JavaScript.  Notice we even get auto-completion.

image

Because the is Ajax, we need to be asynchronous... which means we need to define a callback method to get the results. 

         function onTitleChange (title, id) {
          Sys.Debug.trace("Title: " + title);
          PhotoService.SetPhotoTitle(title, id, onComplete);
        }
        function onComplete (results) {
           Sys.Debug.trace (results);
        }

OK... let's run this in FireFox and use FireBug to check out the network traffic. 

First thing to notice is that the Sys.Debug.Trace() calls are making their way to firefox's console window.  Very helpful in debugging. 

image

Next, we will expand that node and notice the request is in JSON format

image

As is the response..

image

Part V:  Ajax Control Toolkit Fun!

Now we need some way to add new items to our database.  Let's add a new page for this purpose. 

Right click on the master and select add content page.  Rename it to "AddPhoto.aspx"

image

Now, I will use a FormView in Insert mode to give us some pre-backed UI to start with. 

     <asp:FormView runat="server" 
                  ID="fv" 
                  DefaultMode="Insert">
    </asp:FormView>

Then, to show databinding in the designer, let's flip to design view and edit databinding.

image

We will use the new LinqDataSource control

image

Notice it already knows about the PhotosDataContext we created earlier

image

We have really customization support

image

The results look good...

image

The LinqDataSource that was created is far from a black box, you can not only re-run the wizard, but you can edit this in markup as well.

     <asp:LinqDataSource ID="LinqDataSource1" runat="server" 
        ContextTypeName="PhotosDataContext" 
        Select="new (Title, Description, Rating, Tags)" TableName="Photos">
    </asp:LinqDataSource>

Now, let's go in and Ajax enable this form a little more!

First, for the tags, we want to provide a suggest type of feature to give people a hint as to what tags are. This is easy with the Ajax Control Toolkit's AutoCompleteExtender. 

Select Edit Templates, then InsertItem, then Add Extender.

image

Select the AutoCompleteExtender

image

Now, select Add AutoCompletePageMethod

image

And here is the implantation... again, some fun Linq code.

    1:      [System.Web.Services.WebMethodAttribute(), System.Web.Script.Services.ScriptMethodAttribute()]
    2:      public static string[] GetCompletionList(string prefixText, int count, string contextKey)
    3:      {
    4:          PhotosDataContext db = new PhotosDataContext();
    5:          var q = from t in db.Tags
    6:                  where t.TagName.StartsWith(prefixText) 
    7:                  select t.TagName; 
    8:          
    9:          return q.Take(count).ToArray();
   10:   
   11:      }

 

Let's check out the markup and see what is generated.   We will also set the MinimumPrefixLength to 1 to make the demo smoother.

             Tags:
            <asp:TextBox ID="TagsTextBox" runat="server" Text='<%# Bind("Tags") %>' />
            <cc1:AutoCompleteExtender ID="TagsTextBox_AutoCompleteExtender" runat="server" 
                DelimiterCharacters="" 
                Enabled="True" 
                ServiceMethod="GetCompletionList" 
                ServicePath="" 
                MinimumPrefixLength=1 
                TargetControlID="TagsTextBox" 
                UseContextKey="True">
            </cc1:AutoCompleteExtender>

 

When we run it.. it looks great... 

 

image

 

Now, let's add a couple of other toolkit controls..

Replace the rating textbox with a much cooler netflix style ratings control, put a watermark on the description textbox and confirm before submitting. 

             Rating:
            <cc1:Rating ID="Rating1" runat="server" BehaviorID="AutoCompleteEx" CurrentRating='<%# Bind("Rating") %>'
                EmptyStarCssClass="emptyRatingStar" FilledStarCssClass="filledRatingStar" MaxRating="5"
                StarCssClass="ratingStar" WaitingStarCssClass="savedRatingStar">
            </cc1:Rating>
             Description:
            <asp:TextBox ID="DescriptionTextBox" runat="server" 
                Text='<%# Bind("Description") %>' />
            <cc1:TextBoxWatermarkExtender ID="DescriptionTextBox_TextBoxWatermarkExtender" runat="server"
                Enabled="True" TargetControlID="DescriptionTextBox" WatermarkCssClass="watermarked"
                WatermarkText="&lt;type description>">
            </cc1:TextBoxWatermarkExtender>
             <asp:LinkButton ID="InsertButton" runat="server" CausesValidation="True" 
                CommandName="Insert" Text="Insert" />
            <cc1:ConfirmButtonExtender ID="InsertButton_ConfirmButtonExtender" runat="server"
                ConfirmText="Are you sure you want to add this item?" Enabled="True" TargetControlID="InsertButton">
            </cc1:ConfirmButtonExtender>
  

image

  

Ok, that is some really cool functionality, but what about page load time?  Let's go back to FireBug and take a look at the scripts that we are loading.

I am seeing 13 different requests, 122KB and 10.35s... wow that seems like a lot. 

image

We can use the new ScriptCombining feature to combing the scripts into a single request that saves the round trip time and compresses better.

 

Notice this part of the demo will only work once we ship out the update to ASP.NET we are working on right now... So for now, it is just for your reference.  

But first we need to know exactly what scripts are being loaded.  To see this let's look at the ScriptReference debugging tool.

Just drop it on your page

     <cc2:ScriptReferenceProfiler ID="ScriptReferenceProfiler1" runat="server" /> 

and you see exactly what is loaded.

image

We also, have both the debug and retail scripts ready to download in case we want to share this across several pages.

Then cut and past that into ScriptManager.

     <asp:ScriptManager runat="server" ID="sm">
        <CompositeScript>
            <Scripts>
                <asp:ScriptReference Name="MicrosoftAjax.js" />
                <asp:ScriptReference Name="MicrosoftAjaxWebForms.js" />
                <asp:ScriptReference Name="AjaxControlToolkit.Common.Common.js" Assembly="AjaxControlToolkit, Version=3.0.11119.25533, Culture=neutral, PublicKeyToken=28f01b0e84b6d53e" />
                <asp:ScriptReference Name="AjaxControlToolkit.ExtenderBase.BaseScripts.js" Assembly="AjaxControlToolkit, Version=3.0.11119.25533, Culture=neutral, PublicKeyToken=28f01b0e84b6d53e" />
                <asp:ScriptReference Name="AjaxControlToolkit.TextboxWatermark.TextboxWatermark.js"
                    Assembly="AjaxControlToolkit, Version=3.0.11119.25533, Culture=neutral, PublicKeyToken=28f01b0e84b6d53e" />
                <asp:ScriptReference Name="AjaxControlToolkit.Rating.RatingBehavior.js" Assembly="AjaxControlToolkit, Version=3.0.11119.25533, Culture=neutral, PublicKeyToken=28f01b0e84b6d53e" />
                <asp:ScriptReference Name="AjaxControlToolkit.Compat.Timer.Timer.js" Assembly="AjaxControlToolkit, Version=3.0.11119.25533, Culture=neutral, PublicKeyToken=28f01b0e84b6d53e" />
                <asp:ScriptReference Name="AjaxControlToolkit.Animation.Animations.js" Assembly="AjaxControlToolkit, Version=3.0.11119.25533, Culture=neutral, PublicKeyToken=28f01b0e84b6d53e" />
                <asp:ScriptReference Name="AjaxControlToolkit.Animation.AnimationBehavior.js" Assembly="AjaxControlToolkit, Version=3.0.11119.25533, Culture=neutral, PublicKeyToken=28f01b0e84b6d53e" />
                <asp:ScriptReference Name="AjaxControlToolkit.PopupExtender.PopupBehavior.js" Assembly="AjaxControlToolkit, Version=3.0.11119.25533, Culture=neutral, PublicKeyToken=28f01b0e84b6d53e" />
                <asp:ScriptReference Name="AjaxControlToolkit.AutoComplete.AutoCompleteBehavior.js"
                    Assembly="AjaxControlToolkit, Version=3.0.11119.25533, Culture=neutral, PublicKeyToken=28f01b0e84b6d53e" />
                <asp:ScriptReference Name="AjaxControlToolkit.ConfirmButton.confirmButtonBehavior.js"
                    Assembly="AjaxControlToolkit, Version=3.0.11119.25533, Culture=neutral, PublicKeyToken=28f01b0e84b6d53e" />
            </Scripts>
        </CompositeScript>
    </asp:ScriptManager>

Now, let's go back to firebug...

image

This time, we have two request 118KB and 4.39s... Much better!

  

Comments

  • Anonymous
    March 06, 2008
    Excelent :) Looking forward to the video of the session.

  • Anonymous
    March 06, 2008
    I can't find the  <CompositeScript> under the scriptmanager.  I am targeting .net 3.5.  Is this an add on?

  • Anonymous
    March 06, 2008
    I'm with jp--there are build errors...

  • Anonymous
    March 06, 2008
    @jp: compositescript is in 3.5 SP1.

  • Anonymous
    March 06, 2008
    Aren't the new features like compositeScript still internal?  when will they be released?

  • Anonymous
    March 06, 2008
    Great tutorial - I also can't wait until the video is made available.

  • Anonymous
    March 06, 2008
    ScriptCombining Feature

  • Anonymous
    March 06, 2008
    ScriptCombining Feature

  • Anonymous
    March 07, 2008
    Getting this error: Error 19 Type 'System.Web.UI.ScriptManager' does not have a public property named 'CompositeScript'.

  • Anonymous
    March 07, 2008
    This is cute, but the real issue for me is lazy loading and slicing and dicing data on the client.  There doesn't seem to be an lot of emphasis on client side list data processing by Microsoft insiders.  If we are going to make round trips to the server just to get a string we may as well get dev express or teleric and and call it a day.   I'm waiting for a smooth way to stay on the client as long as is feasible and build a reusable client library from it.  Is this a place we can get to with this version of the framework without some major hacking?  

  • Anonymous
    March 07, 2008
    The great folks here running Mix have done a excellent job getting the videos for the Mix session up

  • Anonymous
    March 07, 2008
    The great folks here running Mix have done a excellent job getting the videos for the Mix session up

  • Anonymous
    March 07, 2008
    I'm sorry Brad, was this a response to the question I posed?  I am new to the blog so pardon if I am missing some of the etiquette.

  • Anonymous
    March 07, 2008
    Just wanted to say that I watched your presentation via video and I thought it was an excellent overview of practical AJAX, LINQ To SQL, and new features in Visual Studio 2008 and the .NET 3.5 Framework presented in a very compact and easy-to-digest format. Great job!

  • Anonymous
    March 07, 2008
    The great folks here running Mix have done a excellent job getting the videos for the Mix session up

  • Anonymous
    March 09, 2008
    I guess MIX-08 was a pretty cool event Unfortunately all I can do was guess because I wasn&#39;t there

  • Anonymous
    March 10, 2008
    The comment has been removed

  • Anonymous
    March 11, 2008
    概述由于最近忙着写Silverlight2系列文章了,所以本期推荐比较少,只有6篇文章:)1.BuildingGreatAJAXApplicationsfromScratchUs...

  • Anonymous
    March 11, 2008
    Excelent :) but  i can't get "completed sample" from you posted Url

  • Anonymous
    March 13, 2008
    In order to go to next page I have to click Datapager 2 times?

  • Anonymous
    March 15, 2008
    >> In order to go to next page I have to click Datapager >>2 times? Good call John!  I made a slight bug.  I was missing the Page_PreRender() method to rebind the listview after the pager control had raised the event to change the page.    So we were always a page off ;-) I updated the text of this post and fixed the sample and snippets file.  Please let me know if that fixes the issue you are seeing. thanks

  • Anonymous
    March 16, 2008
    概述由于最近忙着写Silverlight2系列文章了,所以本期推荐比较少,只有6篇文章:)1.BuildingGreatAJAXApplicationsfromScratchUs...

  • Anonymous
    March 24, 2008
    I have downloaded your completed sample and was attempting to use the AddNewPhoto page but without changing anything I get the error. LinqDataSource 'LinqDataSource1' does not support the Insert operation unless EnableInsert is true. So I added the EnableInsert="true" to the statement and I get a new error. LinqDataSource 'LinqDataSource1' does not support the Select property when the Delete, Insert or Update operations are enabled. What am I missing here? Also would it be possible to add to the add photo page a way to actually upload a new photo?

  • Anonymous
    April 07, 2008
    概述 由于最近忙着写Silverlight2系列文章了,所以本期推荐比较少,只有6篇文章:) 1.BuildingGreatAJAXApplicationsfromScratchU...

  • Anonymous
    May 10, 2008
    I have just finished watching your MIX 08 presentation video. It is a great video. I have tried to download the completed project source code, but failed to do so. Could you please update the download link of the completed project zip? Thanks

  • Anonymous
    May 10, 2008
    Tanvir -- sorry about that, my ISP is having an issue... they hope to have it back up monday.

  • Anonymous
    May 12, 2008
    VS2008 and .NET Framework 3.5 offer a ton of customer value from Linq to Ajax and much more.&#160; The

  • Anonymous
    May 16, 2008
    We recently finished the post product work required to get enhanced videos posted.&#160; The videos not

  • Anonymous
    May 31, 2008
    We recently finished the post product work required to get enhanced videos posted.&#160; The videos not

  • Anonymous
    June 30, 2008
    The comment has been removed

  • Anonymous
    October 20, 2008
    The AJAXWorld Conference and Expo got underway in San Jose today, under the broad theme of "Rich Web