Monday, May 4, 2009

ManualWorkflowScheduler causes DelayActivity to not execute

If you are using ASP.NET and Windows Workflow Foundation (WF) you should be using the ManualWorkflowScheduler instead of the default scheduler. Otherwise you will use 2 threads for ever web request instead of 1. This of course assumes you call WF on each request.

If you have a DelayActity in your workflow you may have noticed that it does not automatically execute like it does when you use the default scheduler. I see the behavior when I use a State Machine Workflow. I can't comment on the Sequential Workflow, other than it appears from what I have read that it is affected also. Any comments from anyone?

The reason I believe the behavior is different is because like the name of the ManualWorkflowScheduler implies, it is manual. This translates to the fact that the execution thread that ASP.NET is using is temporarily used for the workflow execute, and then is given back when it is done with it.

In order to restore the behavior to be like the default scheduler where Timers fire as expected, all you need to do is tell the ManualWorkflowScheduler service use active timers. The easiest way is to add the attribute to the line in the web.config where you include the ManualWorkflowScheduler to begin with.

The line in my web.config looks like the following after the change.

<add type="System.Workflow.Runtime.Hosting.ManualWorkflowSchedulerService, System.Workflow.Runtime, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" UseActiveTimers="True"/>

The thing to take note of is the UseActiveTimers=”True”;

One important thing to note about setting UseActiveTimers=”True” is that yes time will run, but it is AFTER ASP.NET response is finished. The stuff after the timer is basically asynchronously executed instead of synchronously like the rest of the workflow. Remember, we are using the ManualWorkflowScheduler to change this behavior.  We have now, change the behavior back to what the default scheduler would have provided, but only for DelayActivities.

This means that anything that happens after the timer will require another server postback to get any changes that happened asynchronously. This may not be what you had in mind.

References

Using FindControl to find your control that is inside a FormView control.

The FindControl method is overloaded. So, be sure you are using the one you want to. The page object has one, but that will only find controls that are at the page level. This means that nothing in a container, or data control such as a FormView will be found by this method, unless you give it the path to the control.

Here is the simple example of a TextBox at the Page level.

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

To find the control from the C# code behind, you would do something like this.

TextBox tb = Page.FindControl("TextBox1") as TextBox;

Now for an example where the same TextBox is in the FormView control.

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

Here is how you would find that same control if it was in a FormView control.

TextBox tb = Page.FindControl("FormView1$TextBox1") as TextBox;

The above line of code only works if the FormView1 control is directly on the page and NOT in some other container. Also, TextBox1 can’t be in a container that is inside of FormView1. If you have additional containers, delimit them with ‘$’.

I recommend saving yourself a bit of headache and use the overloaded FindControl that FormView provides. This way, the path is relative to FormView1 at least. Less dependency on where the controls are placed on the page is good in my book.

TextBox tb = FormView1.FindControl("TextBox1") as TextBox;

The FindControl is nice, but it is NOT recursive. However, this gives you the code to search recursively. There is a bit of a performance penalty (theoretically), so I can’t recommend it. Though, there are likely some cases where you don’t know where the control is on the page or if it is nested in a container, etc, so this kind of strategy might be necessary. Use with care. :)

Reference

Wednesday, April 29, 2009

Keep scroll position on ASP.NET page when using Master Pages

In ASP.NET you can force the browser to scroll back to the position it was prior to a postback. To enable this just add MaintainScrollPositionOnPostBack=”True” to your page declaration. This would look something like this:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="WebForm1.aspx.cs" 
Inherits="MyApp.WebForm4" MaintainScrollPositionOnPostBack=”True”%>
This is great and all, but this doesn’t work with Master Pages. This is because Master Pages are really more like User Controls than a real page. Master Pages don’t have a Page declaration like above. Instead they have something similar, but instead of the word Page, it uses the word Master. It doesn’t support the MaintainScrollPositionOnPostBack property. What to do now? Option 1: Global Change If you want the change to affect all the pages in your application, you can put it in the web.config in the Pages element like the following.
<pages maintainScrollPositionOnPostBack="True">
Option 2: Page Level Change On any page (even ones that use Master Pages), you can add this line to your Page_Load event. Page.MaintainScrollPositionOnPostBack = true; Option 3: Hybrid

You can combine the two options above. The setting in the web.config becomes the default. Then on any particular page you can add the one line to your Page_Load event to change the default behavior.

 

Tuesday, April 28, 2009

AJAX ReorderList breaks when using EntityDataSource entity that has a Navigation Property

After some digging, I figured out the reason why the ReorderList from the AJAX Control Toolkit stopped working for me. When I use the ReorderList with the SqlDataSource it works fine. When I use it with the EntityDataSource it works also. Well sort of. It works fine if the object that you are binding to do not have a Navigation Property in ADO.NET Entity Data Model.

If the object does, you will not receive an error when you reorder items in the ReorderList, but it will not work either. The reason is that the control is not completely robust / completed. If figured this out by changing my reference to the the AjaxControlToolkit.dll that is in the source code version of the Ajax Control Toolkit Sample Application. This allowed me to step through the code. There I saw code that “swallowed” the exception and thus never reported it to the calling method. This is why there is no error, but it is not working either.

Here is the InnerException that I found when I stepped through the ReorderList code:

"Error while setting property 'ICAContract': 'This property descriptor does not support the SetValue method.'." 

   at System.Web.UI.WebControls.EntityDataSourceUtil.SetAllPropertiesWithVerification(EntityDataSourceWrapper entityWrapper, Dictionary`2 changedProperties, Boolean overwrite) 
   at System.Web.UI.WebControls.EntityDataSourceView.InstantiateEntityFromViewState(EntityDataSourceWrapper entityWrapper, IDictionary mergedKeysAndOldValues) 
   at System.Web.UI.WebControls.EntityDataSourceView.ExecuteUpdate(IDictionary keys, IDictionary values, IDictionary oldValues) 
   at System.Web.UI.DataSourceView.Update(IDictionary keys, IDictionary values, IDictionary oldValues, DataSourceViewOperationCallback callback)

Here is the code from ReorderList.cs file.

try{…}
catch (Exception ex)
{
    System.Diagnostics.Debug.Fail(ex.ToString());
    //TODO WHY ARE SWALLOWING THIS EXCEPTION!!!
}

I have to conclude that ReorderList needs to handle error properly, and that potentially the ADO.NET Entity Framework may need some more work. I can’t confirm that, but I do know that I have had to implement several work arounds as noted in these blog entries, and that the experience has been buggy at best.

Monday, April 27, 2009

AJAX ReorderList Example for Adding and Editing Items using the EntityDataSource

This blog entry is very similar to my entry AJAX ReorderList Example for Adding and Editing Items using the SqlDataSource. I highly recommend you read it first to understand the fixes and enhancements I made, since it is the same logic for the EntityDataSource I show here. The functionality is the same, but the difference is that this example show how to use the EntityDataSource (part of the ADO.NET Entity Framework) instead of the SqlDataSource.

The code is very similar to what I did for the SqlDataSource. However, there is some changes that needed to be done as well. The biggest one is that the ReorderList when used with the EntityDataSource requires a DataBind() call after all commands except Edit and Update, and requires special logic for the Update command.

The big change is that there is now a RequiresReorderListDataBind Boolean that I added. This flag is set to true on the initial page load, and then set based on the command that is executed. In turn, when the control is rendered, if RequiresReorderListDataBind is true then DataBind() is called in the PreRender event. You could also call the DataBind() in the appropriate command events such as OnInsertCommand, OnDeleteCommand. However, the Update command needs to call the UpdateItem() and then call DataBind() earlier in the cycle so we put it in the OnItemCommand event. Also, you can’t but the DataBind() call in OnItemCommand for the Insert and Delete. For this reason, I have the logic in the particular events that I do. I wanted to put everything in the OnItemCommand event, but it didn’t work. :(

This example assumes you have a table in your database. Here is the SQL you can use to create one.

CREATE TABLE [dbo].[TestTable1](
    [intID] [int] IDENTITY(1,1) NOT NULL,
    [strName] [varchar](50) NOT NULL,
    [strLink] [varchar](50) NOT NULL,
    [intOrder] [int] NOT NULL,
CONSTRAINT [PK_TestTable1] PRIMARY KEY CLUSTERED
(
    [intID] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

You will need to add a ADO.NET Entity Data Model (just like you add any other file to your web site). Select the database and table you created above. I called my Model MyModel and my entities MyEntities.

Here is the contents of the .aspx file.

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

<%@ Register Assembly="AjaxControlToolkit" Namespace="AjaxControlToolkit" TagPrefix="ajaxToolkit" %>

<%@ Register assembly="System.Web.Entity, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" namespace="System.Web.UI.WebControls" tagprefix="asp" %>

<!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>OrderedList AJAX</title>
    <style type="text/css">
        .ajaxOrderedList li
        {
            list-style:none;
        }
    </style>
</head>
<body>
    <form id="form1" runat="server">
        <asp:ScriptManager ID="ScriptManager1" runat="server" ScriptMode="Release" />       
        <div class="ajaxOrderedList">
          <ajaxToolkit:ReorderList ID="ReorderList1" runat="server"
                AllowReorder="True"
                PostBackOnReorder="True"
                SortOrderField="intOrder"
                DataKeyField="intID"
                DataSourceID="entityDSItems"
                ItemInsertLocation="End"
                onitemreorder="ReorderList1_ItemReorder"
                onitemcommand="ReorderList1_ItemCommand"
                onprerender="ReorderList1_PreRender"
                ShowInsertItem="True">
                <ItemTemplate>
                    &nbsp;
                    <asp:HyperLink ID="HyperLink1" runat="server" Text='<% #Eval("strName") %>' NavigateUrl='<%# Eval("strLink") %>' />
                    <asp:LinkButton ID="LinkButton1" runat="server" CommandName="Edit" Text="Edit" />
                    <asp:LinkButton ID="LinkButton3" runat="server" CommandName="Delete" Text="Delete" />
                </ItemTemplate>
                <DragHandleTemplate>
                    <asp:Panel ID="dragHandle" runat="server"
                        style="height: 20px; width: 20px; border: solid 1px black; background-color: Red; cursor: pointer;"
                        Visible="<%# ShowDragHandle %>">
                        &nbsp;
                    </asp:Panel>   
                </DragHandleTemplate>
                <ReorderTemplate>
                    <div style="width: 300px; height: 20px; border: dotted 2px black;">
                        &nbsp;
                    </div>
                </ReorderTemplate>
                <InsertItemTemplate>
                    <asp:Label ID="Label1" runat="server" Text="Name">
                    </asp:Label><asp:TextBox ID="txtName" runat="server" Text='<%# Bind("strName") %>'></asp:TextBox><br />
                    <asp:Label ID="Label2" runat="server" Text="Link"></asp:Label>
                    <asp:TextBox ID="txtLink" runat="server" Text='<%# Bind("strLink") %>'></asp:TextBox><br />
                    <asp:Button ID="btnInsert" runat="server" Text="Add Link" CommandName="Insert" />
                </InsertItemTemplate>
                <EditItemTemplate>
                    <asp:TextBox ID="txtName" runat="server" Text='<%# Bind("strName") %>'/>
                    <asp:TextBox ID="txtLink" runat="server" Text='<%# Bind("strLink") %>' />
                    <asp:TextBox ID="txtOrder" runat="server" Text='<%# Bind("intOrder") %>' />
                    <asp:LinkButton ID="LinkButton1" runat="server" CommandName="Update" Text="Update" />
                    <asp:LinkButton ID="LinkButton2" runat="server" CommandName="Cancel" Text="Cancel" />                     
                </EditItemTemplate>
            </ajaxToolkit:ReorderList>
            <asp:Label ID="Label3" runat="server" Text="Label"></asp:Label>
        </div>
        <asp:EntityDataSource ID="entityDSItems" runat="server"
            ConnectionString="name=MyEntities"
            ContextTypeName="MyModel.MyEntities"
            DefaultContainerName="MyEntities"
            EnableDelete="True"
            EnableInsert="True"
            EnableUpdate="True"
            EntitySetName="TestTable1"
            EntityTypeFilter="TestTable1"
            OrderBy="it.intOrder ASC"
            StoreOriginalValuesInViewState="False">
        </asp:EntityDataSource>
    </form>
</body>
</html>

Here is the code-behind (.cs) file.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Data.SqlClient;

public partial class EntityDataSourceTest : System.Web.UI.Page 
{
    protected void Page_Load(object sender, EventArgs e)
    {
        Label3.Text = DateTime.Now.ToLongTimeString();
        if (!IsPostBack)
        {
            ShowDragHandle = true;
            RequiresReorderListDataBind = false;
        }
    }
     
    protected void ReorderList1_ItemReorder(object sender, AjaxControlToolkit.ReorderListItemReorderEventArgs e)
    {
        ShowDragHandle = true;
    }

    protected Boolean ShowDragHandle { get; set; }

    protected void ReorderList1_ItemCommand(object sender, AjaxControlToolkit.ReorderListCommandEventArgs e)
    {

        switch (e.CommandName)
        {
            case "Edit":
                ShowDragHandle = false;
                RequiresReorderListDataBind = false;
                break;

            case "Update":
                ShowDragHandle = true;
                ReorderList1.UpdateItem(ReorderList1.EditItemIndex);
                ReorderList1.DataBind();
                RequiresReorderListDataBind = false;
                break;

            // Cancel, Insert, Delete, and any unknown case
            default:
                ShowDragHandle = true;
                RequiresReorderListDataBind = true;
                break;
        }
    }
   
  
    private Boolean RequiresReorderListDataBind { get; set; }
    protected void ReorderList1_PreRender(object sender, EventArgs e)
    {
        if (RequiresReorderListDataBind)
        {
            ReorderList1.DataBind();
        }
    }
}

Tips

  • Be sure to set the ContextType property of the EntityDataSource as described here.
  • There are fewer issues when using a SqlDataSource as described here.
  • Be sure to set the OrderBy property of your EntityDataSource. An example is “it.intOrder ASC”.

AJAX ReorderList Example for Adding and Editing Items using the SqlDataSource

The AJAX Control Toolkit has some very powerful controls in it. The ReorderList is no exception. It basically allows users to drag and drop rows of the list around into any order the user desired. It also, allows the user to edit and add new rows as well.

It does support the SqlDataSource quite well. This blog entry will show how to use the SqlDataSource. However, it seems that does not support the EntityDataSource that well. In a later blog entry I will show how to use the EntityDataSource.

The ReorderList has some undesired behavior such as being able to reorder items when one of the rows is in Edit mode. This “feature” allows the following problem that shows when the items are reordered, after the postback, the same position (EditItemIndex) is the same which means if the item you were editing has a different position, another row (now in that same EditItemIndex) will be edited, not the row you were editing. I show you how to change (fix) this behavior.

 Most examples you find out there, except one that I can find don’t show how to do Edit on the list of items. This blog shows a pretty good implementation of using the SqlDataSource, so I would recommend looking here also. This is where I started and what my examples are based on. The only issue I found with his example is that after an reorder, edit will edit the wrong row. To fix this be sure to do the key extra steps.

There are some key extra steps you need to do to get Editing of the Reorder list to work, that you don’t necessarily have to do for the read only mode.

  1. PostBackOnReorder=”True”
  2. Don’t use the Update panel around the ReorderList (you can, but it won’t do any good).

This example assumes you have a table in your database. Here is the SQL you can use to create one.

CREATE TABLE [dbo].[TestTable1](
    [intID] [int] IDENTITY(1,1) NOT NULL,
    [strName] [varchar](50) NOT NULL,
    [strLink] [varchar](50) NOT NULL,
    [intOrder] [int] NOT NULL,
CONSTRAINT [PK_TestTable1] PRIMARY KEY CLUSTERED
(
    [intID] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

This is an enhanced copy of the blog I noted earlier.

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

<%@ Register Assembly="AjaxControlToolkit" Namespace="AjaxControlToolkit" TagPrefix="ajaxToolkit" %>

<!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>OrderedList AJAX</title>
    
    <style type="text/css">
        .ajaxOrderedList li
        {
            list-style:none;
        }
    </style>
    
</head>
<body>
    <form id="form1" runat="server">
        <asp:ScriptManager ID="ScriptManager1" runat="server" ScriptMode="Release" />        
       
        <div class="ajaxOrderedList">
          <ajaxToolkit:ReorderList ID="ReorderList1" runat="server"
            AllowReorder="True"
            PostBackOnReorder="True"
            SortOrderField="intOrder"
            DataKeyField="intID"
            DataSourceID="sqlDSItems"
            ItemInsertLocation="End" 
            onitemreorder="ReorderList1_ItemReorder" 
            onitemcommand="ReorderList1_ItemCommand">
            
                <ItemTemplate>
                    &nbsp;
                    <asp:HyperLink ID="HyperLink1" runat="server" Text='<% #Eval("strName") %>' NavigateUrl='<%# Eval("strLink") %>' />
                    <asp:LinkButton ID="LinkButton1" runat="server" CommandName="Edit" Text="Edit" />
                    <asp:LinkButton ID="LinkButton3" runat="server" CommandName="Delete" Text="Delete" />
                </ItemTemplate>
                
                <DragHandleTemplate>
                    <asp:Panel ID="dragHandle" runat="server" 
                        style="height: 20px; width: 20px; border: solid 1px black; background-color: Red; cursor: pointer;" 
                        Visible="<%# ShowDragHandle %>">
                        &nbsp;
                    </asp:Panel>
                </DragHandleTemplate>
                
                <ReorderTemplate>
                    <div style="width: 300px; height: 20px; border: dotted 2px black;">
                        &nbsp;
                    </div>
                </ReorderTemplate>
                
                <InsertItemTemplate>
                    <asp:Label ID="Label1" runat="server" Text="Name"></asp:Label>
                    <asp:TextBox ID="txtName" runat="server" Text='<%# Bind("strName") %>'></asp:TextBox><br />
                    
                    <asp:Label ID="Label2" runat="server" Text="Link"></asp:Label>
                    <asp:TextBox ID="txtLink" runat="server" Text='<%# Bind("strLink") %>'></asp:TextBox><br />
                    <asp:Button ID="btnInsert" runat="server" Text="Add Link" CommandName="Insert" />
                </InsertItemTemplate>
                
                <EditItemTemplate>
                    <asp:TextBox ID="txtName" runat="server" Text='<%# Bind("strName") %>'/>
                    <asp:TextBox ID="txtLink" runat="server" Text='<%# Bind("strLink") %>' />
                    <asp:TextBox ID="txtOrder" runat="server" Text='<%# Bind("intOrder") %>' />
                    <asp:LinkButton ID="LinkButton1" runat="server" CommandName="Update" Text="Update" />
                    <asp:LinkButton ID="LinkButton2" runat="server" CommandName="Cancel" Text="Cancel" />                      
                </EditItemTemplate>
                
            </ajaxToolkit:ReorderList>
            
            <asp:Label ID="Label3" runat="server" Text="Label"></asp:Label>
            <asp:Button ID="Button1" runat="server" Text="Button" />
        </div>
                
        <asp:SqlDataSource ID="sqlDSItems" runat="server" ConnectionString="<%$ ConnectionStrings:MyConnectionString %>"
                SelectCommand="SELECT [intID], [strName], [strLink], [intOrder] FROM [TestTable1] ORDER BY [intOrder]"
                DeleteCommand="DELETE FROM [TestTable1] WHERE [intID] = @intID"
                InsertCommand="INSERT INTO [TestTable1] ([strName], [strLink], [intOrder]) VALUES (@strName, @strLink, @intOrder)"
                UpdateCommand="UPDATE [TestTable1] SET [strName] = @strName, [strLink] = @strLink, [intOrder] = @intOrder WHERE [intID] = @intID">
            <DeleteParameters>
                <asp:Parameter Name="intID" Type="Int32" />
            </DeleteParameters>
            <UpdateParameters>
                <asp:Parameter Name="strName" Type="String" />
                <asp:Parameter Name="strLink" Type="String" />
                <asp:Parameter Name="intOrder" Type="Int32" />
                <asp:Parameter Name="intID" Type="Int32" />
            </UpdateParameters>
            <InsertParameters>
                <asp:Parameter Name="strName" Type="String" />
                <asp:Parameter Name="strLink" Type="String" />
                <asp:Parameter Name="intOrder" Type="Int32" />
            </InsertParameters>
        </asp:SqlDataSource>
        
    </form>
</body>
</html>

I do however need some code-behind because I am using events to fix some of the issues I described above. Here is the code-behind

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Data.SqlClient;
using AjaxControlToolkit;

public partial class _Default : System.Web.UI.Page 
{
    protected void Page_Load(object sender, EventArgs e)
    {
        Label3.Text = DateTime.Now.ToLongTimeString();
        if (!IsPostBack)
        {
            ShowDragHandle = true;
        }
    }

    protected void ReorderList1_ItemReorder(object sender, ReorderListItemReorderEventArgs e)
    {
        ShowDragHandle = true;
    }

    protected Boolean ShowDragHandle { get; set; }

    protected void ReorderList1_ItemCommand(object sender, ReorderListCommandEventArgs e)
    {
        switch (e.CommandName)
        {
            case "Cancel":
            case "Insert":
            case "Delete":
            case "Update":
                ShowDragHandle = true;
                break;

            case "Edit":
                ShowDragHandle = false;
                break;
            
            default:
                break;
        }
    }
}

The code is pretty straight forward I think, but here is a bit of explanation to help you understand. The ShowDragHandle boolean is bound to the Panel that makes up the drag handle. It shows and hides based on this boolean. When the page if first loaded (non-postback), it is shown. All events except Edit set the ShowDragHandle to true.

I do have a Label called Label3 that is set to the current data-time on page load. This is there so that you can see when a postback occurs. No real reason to include this in your solution, it is really just to help see when a postback occurs.

Tips

  • Be sure that your select statement has an order by statement and is ordering by (ASCENDING) the same column you set the SortOrderField to.
  • If you need more information on installing the AJAX Control Toolkit in Visual Studio 2008 SP1, check of my blog entry.

Thursday, April 23, 2009

Installing AJAX Control Toolkit in Visual Studio 2008 SP1

I found it a bit frustrating to figure out what I needed and how to setup everything to use the AJAX Control Toolkit. Here is what I learned.

Since ASP.NET AJAX is built into ASP.NET 3.5 and ASP.NET 3.5.1(which is part of Visual Studio 2008 and Visual Studio 2008 SP1 respectively), you don’t need to download or install anything else when usual Visual Studio 2008 SP1, except the Toolkit itself. It is NOT included with any Visual Studio. You MUST download the toolkit itself.

Click here to go to the download page. It is important to get the correct version of the toolkit since it has one for each version of Visual Studio. In particular, there is a version for Visual Studio 2008 (Original Release) and another version for Visual Studio 2008 SP1. To download click one of the four links under the Downloads & Files section on the page. I recommend the AjaxControlToolkit-Framework3.5SP1-NoSource.zip link if you don’t need the source, and just want to use the toolkit. This is the kind of installation you would typically get with any other third party control library.

Figuring that out was the hard part for me. I’m sure there is some document out there that explains all this, but I only found clues in different places.

Now that you have the file, unzip it to a location on your hard drive. You will then need to run the installer. Depending on where you unzipped the file, the installer is located at a path similar to the following:

C:\AjaxControlToolkit-Framework3.5SP1-NoSource\AjaxControlExtender\AjaxControlExtender.vsi

The project template never shows up in Visual Studio, so I don’t know what the installer actually did. I would love to hear what other people experience.

Open Visual Studio 2008 SP1 and add a tab to the Toolbox for the AJAX Toolkit controls by right-clicking on the Toolbox .

Add the toolkit controls to the tab by right-clicking the area below the tab label. Choose the Choose Items…. menu item. Browse to the AjaxControlToolkit.dll assembly.

The DLL for controls that you will use is located in the SampleWebSite\Bin directory. The path should be similar to:

C:\AjaxControlToolkit-Framework3.5SP1-NoSource\SampleWebSite\Bin\AjaxControlToolkit.dll

The AJAX Control Toolkit controls are now available just like any other server control.

NOTE: If you don’t have a project open and the active file (in your main window) is a web page or user control, etc, you won’t see the controls because Visual Studio only shows the controls in the proper context like editing a web page.

References