about
framework & approach
knowledge network
news & events
technology council
why join?

Monday, September 01, 2008

MS AJAX, ScriptManager and ASP.NET MVC

Note: this post is NOT about whether or not to use MS Ajax in ASP.NET MVC, nor is it a comparison between various JS framewqorks. I'll discuss this issue in another post. Also, this code will only enable MS Ajax for true Ajax apps that rely on javascript to render the UI and communicate with the server using services. This tutorial does NOT enable UpdatePanels.

Introduction

We've been writing a public API using WCF lately. After ironing out a few config issues and writing a few HTTP modules to make our API truly RESTful we were able to provide SOAP, REST / XML, REST / JSON and MS Ajax endpoints out of one service class. Pretty nice!

I've been pretty happy using MS AJAX and web service javascript proxies in the past, and when Atlas came out we started supporting its "preview" features (UI, Glitz, XML-Script etc...) so I wanted to leverage our existing javascript controls. But I'm also completely sold on the new ASP/NET MVC framework and probably won't ever go back to the postback model and its viewstate nightmare.


Here's the problem. As you probably know, by default MS Ajax requires a ClientScriptManager on your page, which itself requires a form runat="server" around it. While you can definitely do this using MVC, it kind of defeats the purpose.

The Code


After looking around and trying a few things, this is how we got it to work while still benefitting from the ScriptManager's script handling capabilities:
  • Download the AJAX 3.5 javascript library.
  • Place the javascript files somewhere in your MCV application (we'll use a "js" folder as an example).

  • First we need to hack the master page to enable ScriptManager functionality without usinga server form. On your Master Page, do the following:


    • add runat="server" to your tag
    • in the the header ("head" tag), add a placeholder:



<asp:placeholder id="lit_Header" oninit="lit_Header_Init" runat="server" />


And place this in your Master Page's code-behind:


protected void lit_Header_Init(object sender, EventArgs e)
{
((PlaceHolder)sender).SetRenderMethodDelegate(delegate(HtmlTextWriter output, Control container)
{
typeof(ClientScriptManager).GetMethod("RenderClientScriptBlocks", BindingFlags.Instance BindingFlags.NonPublic).Invoke(this.Page.ClientScript, new object[] { output });
});
}


The above code will enable ScriptManager's script handling capabilities in all the pages that implement your master page.

  • On the AJAX-enabled page or your master page's code behind, register your scripts using the ScriptManager's static methods (you can do this by overriding onload or onprerender)



protected override void OnLoad(EventArgs e) {
#if DEBUG
ScriptManager.RegisterClientScriptInclude(this, typeof(Page), "MicrosoftAjax", VirtualPathUtility.ToAbsolute("~/js/MicrosoftAjax.debug.js"));
#else
ScriptManager.RegisterClientScriptInclude(this, typeof(Page), "MicrosoftAjax", VirtualPathUtility.ToAbsolute("~/js/MicrosoftAjax.js"));
#endif
base.OnLoad(e);
}


ComponentArt users: keep reading



We use ComponentArt Web.UI (2008.1 for ASP.Net 3.5). After using the above code our AJAX code worked fine... as long as we didn't have any ComponentArt control in the page. When we included a tabstrip, all hell broke loose. Here's why.



CA components are ASP.NET AJAX aware. If the AJAX javascript library is present, they use it. If not, they create some objects to emulate basic Sys.* capabilities. They do this by checking whether a ScriuptManager instance is located on the page, which makes sense. But in our AJAX implementation, we rely on the ScriptManager's static methods to manually include the Ajax library. Therefore Web.UI can't find a ScriptManager and includes javascsript that conflicts with Microsoft's and breaks it. What we need to do is add one more hack to tell the Web.UI javascript controls the Ajax library is present. This is actually really easy. Just edit your AJAX-enbabled page or Master Page's code behind (we're adding one script block to the above code):




protected override void OnLoad(EventArgs e)
{
#if DEBUG
ScriptManager.RegisterClientScriptInclude(this, typeof(Page), "MicrosoftAjax", VirtualPathUtility.ToAbsolute("~/js/MicrosoftAjax.debug.js"));
#else
ScriptManager.RegisterClientScriptInclude(this, typeof(Page), "MicrosoftAjax", VirtualPathUtility.ToAbsolute("~/js/MicrosoftAjax.js"));
#endif
ScriptManager.RegisterClientScriptBlock(Page, typeof(Site), "ComponentArt_Atlas", "window.ComponentArt_Atlas=1;", true);
base.OnLoad(e);
}


The above code sets a global javascript variable ("ComponentArt_Atlas") to "1" at the top of the page. The is the variable all Web.UI controls to check before rendering themselves.



Service proxies and other .js files



Last step - including the MS Ajax web service client proxies. Just like we registered the framework, we can now use the ScriptManager to register other javascript includes. Just edit your page or user control's code behind to register those scripts:




protected void Page_Load()
{
#if DEBUG
ScriptManager.RegisterClientScriptInclude(this, typeof(Page), "MicrosoftAjaxPreview", VirtualPathUtility.ToAbsolute("~/js/PreviewScript.debug.js"));
ScriptManager.RegisterClientScriptInclude(this, typeof(Page), "MicrosoftAjaxPreviewGlitz", VirtualPathUtility.ToAbsolute("~/js/PreviewGlitz.debug.js"));
ScriptManager.RegisterClientScriptInclude(this, typeof(Page), "PresentationService", VirtualPathUtility.ToAbsolute("~/services/MyService.svc/jsdebug"));
#else
ScriptManager.RegisterClientScriptInclude(this, typeof(Page), "MicrosoftAjaxPreview", VirtualPathUtility.ToAbsolute("~/js/PreviewScript.js"));
ScriptManager.RegisterClientScriptInclude(this, typeof(Page), "MicrosoftAjaxPreviewGlitz", VirtualPathUtility.ToAbsolute("~/js/PreviewGlitz.js"));
ScriptManager.RegisterClientScriptInclude(this, typeof(Page), "MyService", VirtualPathUtility.ToAbsolute("~/services/MyService.svc/js"));
#endif
}


Conclusion

It took me a a little while to figure this out, and I hope this will save you some work. However, my experiences with MS Ajax lead me to a more interesting question. Since its preview code (xml-script, Glitz, Preview.UI etc...) did not get updated in months, I'm going to assume those were put back on the shelf and won't ever be released. So what exactly does MS Ajax give me out of the box? How does it compare with Prototype and jQuery? That's what I'm looking at right now, and it's not looking pretty.

0 comments: