Ajax Conflicts Between .net 2.0 and 3.5

This post is for anyone who is encountering the following errors in their asp.net applications:

  • The server tag ‘asp:ScriptManager’ is ambiguous. Please modify the associated registration that is causing ambiguity and pick a new tag prefix.
  • The base class includes the field ‘ScriptManager1’, but its type (System.Web.UI.ScriptManager) is not compatible with the type of control (System.Web.UI.ScriptManager).

There are many instances where you might exprience this problem. For me, the situation was that we had a virtual directory running asp.net 2.0 with AJAX extensions, while the root site is running asp.net 3.5. The subdirectory was attempting to reference the old 1.0.61025.0 version of the Web.Extensions assembly. Since we upgraded our webserver to asp.net 3.5, this assembly should no longer be used.

So the solution was to upgrade all the Web.Extensions stuff in web.config from the 1.0 to the asp.net 3.5 version. First, upgrade the configSections from this:

<configuration>
<
configSections>
<
sectionGroup name="system.web.extensions" type="System.Web.Configuration.SystemWebExtensionsSectionGroup,
System.Web.Extensions, Version=1.0.61025.0, Culture=neutral,
PublicKeyToken=31bf3856ad364e35
">
<
sectionGroup name="scripting" type="System.Web.Configuration.ScriptingSectionGroup,
System.Web.Extensions, Version=1.0.61025.0, Culture=neutral,
PublicKeyToken=31bf3856ad364e35
">
<
section name="scriptResourceHandler" type="System.Web.Configuration.ScriptingScriptResourceHandlerSection,
System.Web.Extensions, Version=1.0.61025.0,
Culture=neutral, PublicKeyToken=31bf3856ad364e35
" requirePermission="false" allowDefinition="MachineToApplication"/>
<
sectionGroup name="webServices" type="System.Web.Configuration.ScriptingWebServicesSectionGroup,
System.Web.Extensions, Version=1.0.61025.0,
Culture=neutral, PublicKeyToken=31bf3856ad364e35
">
<
section name="jsonSerialization" type="System.Web.Configuration.ScriptingJsonSerializationSection,
System.Web.Extensions, Version=1.0.61025.0,
Culture=neutral, PublicKeyToken=31bf3856ad364e35
" requirePermission="false" allowDefinition="Everywhere" />
<
section name="profileService" type="System.Web.Configuration.ScriptingProfileServiceSection,
System.Web.Extensions, Version=1.0.61025.0,
Culture=neutral, PublicKeyToken=31bf3856ad364e35
" requirePermission="false" allowDefinition="MachineToApplication" />
<
section name="authenticationService" type="System.Web.Configuration.ScriptingAuthenticationServiceSection,
System.Web.Extensions, Version=1.0.61025.0,
Culture=neutral, PublicKeyToken=31bf3856ad364e35
" requirePermission="false" allowDefinition="MachineToApplication" />
</
sectionGroup>
</
sectionGroup>
</
sectionGroup>
</
configSections>
</
configuration>

to this:

<configSections>
<
sectionGroup name="system.web.extensions" type="System.Web.Configuration.SystemWebExtensionsSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
<
sectionGroup name="scripting" type="System.Web.Configuration.ScriptingSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
<
section name="scriptResourceHandler" type="System.Web.Configuration.ScriptingScriptResourceHandlerSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="MachineToApplication"/>
<
sectionGroup name="webServices" type="System.Web.Configuration.ScriptingWebServicesSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
<
section name="jsonSerialization" type="System.Web.Configuration.ScriptingJsonSerializationSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="Everywhere"/>
<
section name="profileService" type="System.Web.Configuration.ScriptingProfileServiceSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="MachineToApplication"/>
<
section name="authenticationService" type="System.Web.Configuration.ScriptingAuthenticationServiceSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="MachineToApplication"/>
<
section name="roleService" type="System.Web.Configuration.ScriptingRoleServiceSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="MachineToApplication"/>
</
sectionGroup>
</
sectionGroup>
</
sectionGroup>
</
configSections>

Next, in system.web update the controls, compilation, httpHandlers, and httpModules references:

<system.web>
<
pages>
<
controls>
<
add tagPrefix="asp" namespace="System.Web.UI" assembly="System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
</
controls>
</
pages>
<
compilation>
<
assemblies>
<
add assembly="System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
</
assemblies>
</
compilation>
<
httpHandlers>
<
remove verb="*" path="*.asmx"/>
<
add verb="*" path="*.asmx" validate="false" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
<
add verb="*" path="*_AppService.axd" validate="false" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
<
add verb="GET,HEAD" path="ScriptResource.axd" type="System.Web.Handlers.ScriptResourceHandler, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" validate="false"/>
</
httpHandlers>
<
httpModules>
<
add name="ScriptModule" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
</
httpModules>
</
system.web>

to the new 3.5 versions

<pages>
<
controls>
<
add tagPrefix="asp" namespace="System.Web.UI" assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
<
add tagPrefix="asp" namespace="System.Web.UI.WebControls" assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
</
controls>
</
pages>
<
assemblies>
<
add assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
</
assemblies>
<
httpHandlers>
<
remove verb="*" path="*.asmx"/>
<
add verb="*" path="*.asmx" validate="false" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
<
add verb="*" path="*_AppService.axd" validate="false" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
<
add verb="GET,HEAD" path="ScriptResource.axd" validate="false" type="System.Web.Handlers.ScriptResourceHandler, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
</
httpHandlers>
<
httpModules>
<
add name="ScriptModule" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
</
httpModules>

Finally, there may be some scattered tag registrations on your pages that are still directly referencing the old version. Add a bindingRedirect to your web.config so that these references are updated to point to the correct assembly. You can add this section before or after (but not inside!) system.web

<runtime>
<
assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<
dependentAssembly>
<
assemblyIdentity name="System.Web.Extensions" publicKeyToken="31bf3856ad364e35"/>
<
bindingRedirect oldVersion="1.0.0.0-1.1.0.0" newVersion="3.5.0.0"/>
</
dependentAssembly>
<
dependentAssembly>
<
assemblyIdentity name="System.Web.Extensions.Design" publicKeyToken="31bf3856ad364e35"/>
<
bindingRedirect oldVersion="1.0.0.0-1.1.0.0" newVersion="3.5.0.0"/>
</
dependentAssembly>
</
assemblyBinding>
</
runtime>

And that should do it! Our virtual directory now has no conflicts, and completely references the new asp.net 3.5 assemblies. I hope that this post was helpful, and if I left anything out, please let me know in the comments!

AJAX News Rotator in Sitefinity Part 3: Handling Disabled Javascript

In part 1 and part 2 of this series, I discussed how to create a simple News Rotator for Sitefinity. Everything is working great until you try to visit the page with Javascript disabled. Since we’re using link buttons that navigate through the various news items, they require the __dopostback javascript command to initiate the postback to the server and update the news content. Obviously, disabling Javascript will break our functionality.

Incidentally, this is also another reason why I decided to build my own rotator in place of the RadRotator control. Remember that this control loads all of the content as hidden, then uses Javascript to toggle the visibility of each. The control binds on the server-side, loading the content, but since Javascript is disabled, none if it is toggled as visible!

I’ve always hated this aspect of ASP.NET. I know that you can replace LinkButtons with regular buttons, ensuring that a regular postback occurs using the ‘submit’ functionality of the input element. However, I’d rather shoot for a more graceful fall-through mechanism.

Normally, the Javascript __dopostback method is embedded in the href attribute of the link, which is where the “button” in LinkButton is simulated. I’ve never understood this either. The anchor html element supports the onclick attribute, why didn’t they use that instead? This is especially annoying if, like me, you use a browser that supports ‘link dragging’ where you drag and drop a link to open it in a new window. I can’t tell you how many times I have been subject to the Your search – javascript:__doPostBack(‘blah$ctl01$blah$ctl01$blah$ctl01’,”) – did not match any documents message from google. It’s REALLY annoying…

Well, my solution was to use the OnDataBind event of my control to override the Javascript and move it from the href attribute into onclick instead. This has the additional benefit of freeing up the href attribute so that we can use that for the actual URL for the news item! This way, when Javascript is disabled, the numbered links at the bottom of the rotator will still correctly point to each individual news story, only instead of causing a postback, they will link directly to the page! Talk about compliance!

The code to do this is pretty straight-forward. All we need to do is modify the ItemDataBound event code to do some additional formatting of the LinkButtons in each bound item.

 

protected void newsNavRepeater_ItemDataBound(object sender, RepeaterItemEventArgs e)  
{  
    // get link  
    LinkButton lnk = e.Item.FindControl(“lnkItem”as LinkButton;  
 
    // get dataitem  
    IContent newsItem = e.Item.DataItem as IContent;  
    int itemIndex = e.Item.ItemIndex + 1;  
    lnk.Text = itemIndex.ToString();  
    lnk.OnClientClick = string.Format(“javascript:__doPostBack(‘{0}’,”); return false;”, lnk.ClientID.Replace(“_”“$”));  
    lnk.Attributes.Add(“href”string.Format(“/news/default{0}.aspx”, newsItem.Url));  
    lnk.CommandArgument = (itemIndex – 1).ToString();  
    lnk.ToolTip = newsItem.GetMetaData(“Title”).ToString();  
    lnk.Attributes.Add(“onMouseOver”string.Format(“window.status='{0}’;return true”, lnk.ToolTip));  
    lnk.Attributes.Add(“onMouseOut”“window.status=””);  

The biggest thing to watch out for here is the ClientID. The parameter for the __dopostback method requires an argument that represents the sender. I don’t know enough about this process to explain why, but I noticed by looking at the html source that this parameter is equivalent to the client id, replacing the underscore _ character with the dollar sign $.

Notice also that I also went ahead and included code to show helpful information in the status bar, so that users can know where the links will be taking them. Also be sure to include the return false; snippet so that when Javascript is enabled, the link is not followed (but the postback still occurs, since it now occurs in the onclick attribute!), and is followed when Javascript is disabled. Not too shabby!

So there you have it: a complete Ajax rotator with fall-through support for Javascript-disabled browsers. The only problem I’ve seen is that in IE, the href is APPENDED and not replaced (meaning the __dopostback is still present in the first href attribute, and the link is in the second), which is obviously not XHTML compliant. Strangely enough, it renders correctly in Firefox… and it does function correctly in both browsers… go figure!

I hope this has been helpful to SOMEONE out there! please feel free to send me your comments; I’d love to know how this approach can be improved!

AJAX News Rotator in Sitefinity Part 2: Adding a Timer

In my last entry (AJAX News Rotator Part I), I described how I used the NewsManager in Sitefinity to create an AJAX news rotator that dynamically fetches news content using AJAX. Using the linkbuttons in the repeater, users can jump to different items in the list. However, this isn’t exactly a “rotator”, because to me that implies some sort of automation, which sounds like a good job for the ASP.NET Timer control.

AJAX News Rotator in Sitefinity Part 1

I’ve recently been developing a control to display recent news in a rotating fashion. Originally, I was using the RadRotator control from Telerik. This is a powerful control that takes a lot of the guesswork out of displaying and rotating news. However, I decided to build my own for two reasons.

First, I didn’t like the way the rotator control loads ALL of the news items before running. The RadRotator works by preloading all of the elements, placing each item into its own div element, then cycling through each, toggling visibility of each one at a time. This solution works for a few elements, but if you’re cyclying through a half dozen or more items, each with an image, it can be an expensive procedure, especially since the control has to finish loading in order for the page to run. This leaves users viewing half a screen while the content loads.

The other reason I decided to build my own was just out of sheer curiousity and a desire to learn, especially since this is a perfect candidate to get my feet wet in the Ajax world that I’ve been dying to dive into.

Anyway, enough back-story, let’s get to the code! The first thing I had to do was retrieve the news items that will be displayed. I’ll be storing the Guids for each news item in the ViewState for quick, on-demand retrieval. The Guids go in a Generic List, which will be accessed by a CurrentItem index, which checks to make sure that this value is always within the valid range of elements.

protected List newsIDs  
{  
    get {  
        if (ViewState["NewsIDs"] == null) ViewState["NewsIDs"] = new List();  
        return (List)ViewState["NewsIDs"];  
    }  
    set { ViewState["NewsIDs"] = value; }  
}  
 
protected int CurrentItem  
{  
    get {  
        if (ViewState["CurrentItem"] == null) ViewState["CurrentItem"] = 0;  
        return (int)ViewState["CurrentItem"];  
    }  
    set {  
        if (value > newsIDs.Count - 1)  
            ViewState["CurrentItem"] = 0;  
        else if (value < 0)  
            ViewState["CurrentItem"] = newsIDs.Count - 1;  
        else ViewState["CurrentItem"] = value;  
    }  
} 

Since I’m using Sitefinity (my content management system of choice!), I’ll need to use the NewsManager to retrieve these items. Because of the nature of the News API, it is necessary for me to retrieve the full list of news items on Page_Load. Ideally, I would only be grabbing the Guid for each item, but since the only way to retrieve them is to retrieve the full items, I’ll have to do that. Fortunately, this is only in the code behind, and isn’t rendering to the page so the overhead should be minimal, especially since it’s only done on the initial Page_Load

protected void Page_Load(object sender, EventArgs e)  
{  
    if (this.manager == null) this.manager = new NewsManager("News");  
      
    if (!IsPostBack)  
    {  
        BindData();  
    }  
}  
 
private void BindData()  
{  
    IMetaSearchInfo[] filters = new IMetaSearchInfo[3];  
    filters[0] = new MetaSearchInfo(MetaValueTypes.DateTime, "Expiration_Date", DateTime.Now, SearchCondition.GreaterOrEqual);  
    filters[1] = new MetaSearchInfo(MetaValueTypes.ShortText, "Category", "Top Story");  
    filters[2] = new MetaSearchInfo(MetaValueTypes.DateTime, "Publication_Date", DateTime.Now, SearchCondition.LessOrEqual);  
 
    IList newsList;  
    newsList = manager.Content.GetContent("Publication_Date DESC", filters);  
 
    newsNavRepeater.DataSource = newsList;  
    newsNavRepeater.DataBind();  
 
    foreach (IContent content in newsList)  
        newsIDs.Add(content.ID);  
 
    // start with the first news Item UpdateNews((IContent)newsList[0]);  
} 

The newsNavRepeater is simply a repeater of LinkButtons that will tie to the individual news item ids.

<div class="sf_newsRotator"> <telerik:radajaxpanel id="NewsAjaxPanel" runat="server" width="550" height="185" loadingpanelid="NewsLoadingPanel"> <div id="newsFrame" class="newsFrame"> <h3><asp:HyperLink ID="lnkTitle" runat="server" /></h3> <asp:HyperLink ID="lnkFullStory" runat="server" CssClass="fullstory" Text="Full Story &raquo;" /> <div class="sf_newsImage"> <asp:HyperLink ID="lnkThumbnail" runat="server" /> </div> <p><strong style="font-size: .9em;">Posted  
                <asp:Label ID="PublicationLabel" runat="server" /></strong><br /> <asp:Literal ID="SummaryLabel" runat="server" /> </p> </div> <div class="newsNav"> <asp:Repeater ID="newsNavRepeater" runat="server" OnItemDataBound="newsNavRepeater_ItemDataBound" OnItemCommand="newsNavRepeater_ItemCommand"> <ItemTemplate> <asp:LinkButton ID="lnkItem" runat="server" /> </ItemTemplate> </asp:Repeater> </div> </telerik:radajaxpanel> <a class="allnews" href="/news/default.aspx" title="View All News from the City of McAllen"> All News</a> <a href="/news.rss" class="rss" target="_blank" title="Subscribe to the City of McAllen Latest News RSS Feed"> <img src="/images/icons/icon-rss.gif" alt="RSS Feed" style="vertical-align: middle;" /></a></div><telerik:radajaxloadingpanel id="NewsLoadingPanel" runat="server" height="190px" width="550px" transparency="50" enableviewstate="false"> <div style="background: #ccc; height: 100%"> <img style="margin-top: 75px; z-index: 1" alt="Loading..." src='<%= RadAjaxLoadingPanel.GetWebResourceUrl(Page, "Telerik.Web.UI.Skins.Default.Ajax.loading.gif") %>' style="border: 0px;" /> </div> </telerik:radajaxloadingpanel>

The buttons are populated using the OnItemBound event to fill each link with the appropriate post information from the bound IContent DataItem.

protected void newsNavRepeater_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
    // get link LinkButton lnk = e.Item.FindControl("lnkItem") as LinkButton;

    // get dataitem IContent newsItem = e.Item.DataItem as IContent;
    int itemIndex = e.Item.ItemIndex + 1;
    lnk.Text = itemIndex.ToString();
    lnk.CommandArgument = (itemIndex - 1).ToString();
    lnk.ToolTip = newsItem.GetMetaData("Title").ToString();
} 

Once the navigation is bound, we need to bind the first news item, which is the first item in the newsList collection above (newsList[0]). Note how this item has to be cast as IContent and passed into the UpdateNews method.

private void UpdateNews(IContent newsItem)  
{  
    // make sure item still exists if (newsItem == null)  
    {  
        // if not, reset data! BindData();  
        return;  
    }  
 
    // bind current item data string newsTitle = newsItem.GetMetaData("Title").ToString();  
    lnkTitle.Text = newsTitle;  
    lnkTitle.NavigateUrl = string.Format("/news/default{0}.aspx", newsItem.Url.ToLower());  
    lnkThumbnail.ImageUrl = newsItem.GetMetaData("Thumbnail").ToString();  
    lnkThumbnail.ToolTip = newsTitle;  
    lnkThumbnail.NavigateUrl = lnkTitle.NavigateUrl;  
    lnkThumbnail.Text = newsTitle;  
    SummaryLabel.Text = newsItem.GetMetaData("Summary").ToString();  
    PublicationLabel.Text = ((DateTime)newsItem.GetMetaData("Publication_Date")).ToString("dddd MMMM dd, yyyy");  
 
    LinkButton selectedLink = newsNavRepeater.Controls[CurrentItem].FindControl("lnkItem") as LinkButton;  
    selectedLink.CssClass = "selected";  
} 

Notice also that this method ensures that a valid item was found, if not (such as a newsitem deleted or new items posted) it will reset, binding all new data and start all over again. Also, at the bottom, we since the Controls array of the newsNavRepeater has as many elements as the newsList, we can grab use that index to select the associated LinkButton and set its CSS class so that we can highlight the selected item!

All that is left to do is handle the OnItemCommand event of the newsNavRepeater (since these buttons are what trigger a different item). Remember that the argument for each LinkButton set in BindData is the index of that news item’s Guid in the newsList Collection. So all we have to do is retrieve that one news item (using the News API) and run the UpdateNews method again! Notice how before we update the CurrentItem with the new value, we use it to “unselect” the previous news item.

protected void newsNavRepeater_ItemCommand(object source, RepeaterCommandEventArgs e)  
{  
    // unselect css before changing LinkButton selectedLink = newsNavRepeater.Controls[CurrentItem].FindControl("lnkItem") as LinkButton;  
    selectedLink.CssClass = "";  
 
    CurrentItem = Int32.Parse(e.CommandArgument.ToString());  
    IContent newsItem = manager.Content.GetContent(newsIDs[CurrentItem]);  
    UpdateNews(newsItem);  
} 

And that’s all there is to it! Wrap all of this inside of a RadAjaxPanel and you’ve got yourself an interactive news roll! There’s still a few things to add, namely a Timer control to automatically cycle the news, but I will cover that my next entry. I hope this has been helpful, and as always your comments are welcome and appreciated!