SharePoint Layout Pages With CodeBehind And Prototype
Let it be known that I hate out of the box ASP.NET. Hate it, hate it, hate it, hate it. I detest it. The simplicity with which it allows the average developer to create applications leads to applications designed for RAD and not for scalability and it does not encourage good decoupling of business logic from UI logic. Certainly, there are a number of frameworks which aim to alleviate this (the Web Client Software Factory, for example), but I like to take it to another level all together.
Some would argue that I take the separation of UI and application logic to the extreme: my preferred methodology relies almost purely on client side scripts to render UI and using only web services to supply data using ASP.NET AJAX. Certainly, I lose design time support, but I gain in pure speed (all of the UI logic is in Javascript files which are cached by the client), data transfer sizes (since the only traffic is data, no presentation whatsoever), and the ultimate decoupling of UI development and server component development (the UI developer only needs to know the data model exposed by the web services).
The way I look at it, you’ll only write the code a few times, but it could be in use for months (and if you’re lucky in this Web 2.0 age, even a year or two). Sure, you lose some productivity for a single developer with the loss of design time support, but you gain tremendously over time with each request serviced in terms of performance and bytes saved (a particularly important point for high traffic/high data volume applications). As a bonus, I find it generally easier to think about application design in these terms.
Admittedly, this model seems to work better for “business applications” as opposed to “content applications”.
In any case, I was interested to see if this methodology could be applied to SharePoint development as I’ve been working with SharePoint for quite a while now, but not at the UI level. SharePoint allows you to deploy “application pages” which can be seemlessly integrated (kind of) into a SharePoint deployment. This seemed like the perfect starting point to try to integrate ASP.NET AJAX and prototype, one of my favorite Javascript libraries.
The general steps are:
- Create an ASP.NET AJAX web application
- Add a reference to the Microsoft.SharePoint assembly
- Add the prototype.js script file to the project
- Add a strong name key file and sign the project
- Build a simple page
- Create a simple service
- Add a reference to the Microsoft.SharePoint assembly
- Copy the content files (.js, .aspx, .asmx) over to the server to C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\LAYOUTS\ into a new directory.
- Test by visiting the URL: http://myserver/_layouts/mynewdirectory/default.aspx
The base page should be simple. Use the default page created by the ASP.NET AJAX web application project template and change the base class for the _Default.aspx.cs file to LayoutsPageBase instead of of the default of Page. If you’re not using ReSharper ;-), you’ll need to add a using statement to your file:
1 2 3 4 5 6 7 8 9 10 |
<FONT face="Lucida Console"><FONT color=#0000ff>using </FONT><FONT color=#008080>System</FONT><FONT color=#000000>;</FONT> <FONT color=#0000ff>using </FONT><FONT color=#008080>Microsoft</FONT><FONT color=#000000>.SharePoint.</FONT><FONT color=#008080>WebControls</FONT><FONT color=#000000>;</FONT> <FONT color=#0000ff>namespace </FONT><FONT color=#000000>WssAjaxApplicationTest {</FONT> <FONT color=#0000ff>public </FONT><FONT color=#000000>partial </FONT><FONT color=#0000ff>class </FONT><FONT color=#000000>_Default : LayoutsPageBase {</FONT> <FONT color=#0000ff>protected void </FONT><FONT color=#000000>Page_Load(</FONT> <FONT color=#0000ff>object </FONT><FONT color=#000000>sender, </FONT><FONT color=#808000>EventArgs </FONT><FONT color=#000000>e) {}</FONT> <FONT color=#000000>}</FONT> <FONT color=#000000>}</FONT> </FONT> |
Next, you will need to modify the Default.aspx file. The gist of the modifications comes from an MSDN article by Ted Pattison:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
<FONT face="Lucida Console"><FONT color=#800000><%@ Page Language=</FONT><FONT color=#ff00ff>"C#" </FONT> <FONT color=#800000>AutoEventWireup=</FONT><FONT color=#ff00ff>"true" </FONT> <FONT color=#800000>CodeBehind=</FONT><FONT color=#ff00ff>"Default.aspx.cs" </FONT> <FONT color=#800000>Inherits=</FONT><FONT color=#ff00ff>"WssAjaxApplicationTest._Default" </FONT> <STRONG><FONT color=#800000>MasterPageFile=</FONT><FONT color=#ff00ff>"~/_layouts/application.master"</FONT></STRONG><FONT color=#800000>%></FONT> <FONT color=#0000ff><</FONT><FONT color=#808000>asp:Content </FONT><FONT color=#ff0000>ID</FONT><FONT color=#0000ff>=</FONT><FONT color=#ff00ff>"Main" </FONT><FONT color=#ff0000>runat</FONT><FONT color=#0000ff>=</FONT><FONT color=#ff00ff>"server" </FONT><FONT color=#800080>ContentPlaceHolderID</FONT><FONT color=#0000ff>=</FONT><FONT color=#ff00ff>"PlaceHolderMain"</FONT><FONT color=#0000ff>></FONT> <FONT color=#000000> </FONT><FONT color=#0000ff><script </FONT><FONT color=#ff0000>type</FONT><FONT color=#0000ff>=</FONT><FONT color=#ff00ff>"text/javascript" </FONT><FONT color=#ff0000>src</FONT><FONT color=#0000ff>=</FONT><FONT color=#ff00ff>"_scr/prototype.js"</FONT><FONT color=#0000ff>></script></FONT> <FONT color=#000000> </FONT><FONT color=#0000ff><script </FONT><FONT color=#ff0000>type</FONT><FONT color=#0000ff>=</FONT><FONT color=#ff00ff>"text/javascript" </FONT><FONT color=#ff0000>src</FONT><FONT color=#0000ff>=</FONT><FONT color=#ff00ff>"_scr/WssAjaxApplication.js"</FONT><FONT color=#0000ff>></script></FONT> <FONT color=#000000> </FONT><FONT color=#0000ff><script </FONT><FONT color=#ff0000>type</FONT><FONT color=#0000ff>=</FONT><FONT color=#ff00ff>"text/javascript"</FONT><FONT color=#0000ff>> </FONT> <FONT color=#0000ff>var </FONT><FONT color=#800000>application;</FONT> <FONT color=#0000ff>function </FONT><FONT color=#800000>Init() {</FONT> <FONT color=#800000>application = </FONT><FONT color=#0000ff>new </FONT><FONT color=#800000>WssAjaxApplication();</FONT> <FONT color=#800000>}</FONT> <FONT color=#800000>Event.observe(</FONT><FONT color=#ff0000>window</FONT><FONT color=#800000>, </FONT><FONT color=#ff00ff>"load"</FONT><FONT color=#800000>, Init, </FONT><FONT color=#0000ff>false</FONT><FONT color=#800000>);</FONT> <FONT color=#0000ff></script></FONT> <FONT color=#000000> </FONT><FONT color=#0000ff><</FONT><FONT color=#808000>asp:ScriptManager </FONT><FONT color=#ff0000>ID</FONT><FONT color=#0000ff>=</FONT><FONT color=#ff00ff>"ScriptManager1" </FONT><FONT color=#ff0000>runat</FONT><FONT color=#0000ff>=</FONT><FONT color=#ff00ff>"server" </FONT><FONT color=#0000ff>></FONT> <FONT color=#000000> </FONT><FONT color=#0000ff><</FONT><FONT color=#808000>Services</FONT><FONT color=#0000ff>></FONT> <FONT color=#000000> </FONT><STRONG><FONT color=#0000ff><</FONT><FONT color=#808000>asp:ServiceReference </FONT><FONT color=#800080>Path</FONT><FONT color=#0000ff>=</FONT><FONT color=#ff00ff>"~/Services/EchoService.asmx" </FONT><FONT color=#0000ff>/></FONT></STRONG> <FONT color=#000000> </FONT><FONT color=#0000ff></</FONT><FONT color=#808000>Services</FONT><FONT color=#0000ff>></FONT> <FONT color=#000000> </FONT><FONT color=#0000ff></</FONT><FONT color=#808000>asp:ScriptManager</FONT><FONT color=#0000ff>></FONT><FONT color=#000000> </FONT> <FONT color=#000000> </FONT><FONT color=#0000ff><div></FONT> <FONT color=#000000> </FONT><FONT color=#0000ff><input </FONT><FONT color=#ff0000>type</FONT><FONT color=#0000ff>=</FONT><FONT color=#ff00ff>"text" </FONT><FONT color=#ff0000>id</FONT><FONT color=#0000ff>=</FONT><FONT color=#ff00ff>"message-input" </FONT><FONT color=#0000ff>/></FONT> <FONT color=#000000> </FONT><FONT color=#0000ff><input </FONT><FONT color=#ff0000>type</FONT><FONT color=#0000ff>=</FONT><FONT color=#ff00ff>"button" </FONT><FONT color=#ff0000>id</FONT><FONT color=#0000ff>=</FONT><FONT color=#ff00ff>"action-button" </FONT><FONT color=#ff0000>value</FONT><FONT color=#0000ff>=</FONT><FONT color=#ff00ff>"Go!" </FONT><FONT color=#0000ff>/></FONT> <FONT color=#000000> </FONT><FONT color=#0000ff><br /></FONT> <FONT color=#000000> </FONT><FONT color=#0000ff><div </FONT><FONT color=#ff0000>id</FONT><FONT color=#0000ff>=</FONT><FONT color=#ff00ff>"message-output"</FONT><FONT color=#0000ff>></div></FONT><FONT color=#000000> </FONT> <FONT color=#000000> </FONT><FONT color=#0000ff></div></FONT><FONT color=#000000> </FONT> <FONT color=#0000ff></</FONT><FONT color=#808000>asp:Content</FONT><FONT color=#0000ff>></FONT> <FONT color=#0000ff><</FONT><FONT color=#808000>asp:Content </FONT><FONT color=#ff0000>ID</FONT><FONT color=#0000ff>=</FONT><FONT color=#ff00ff>"PageTitle" </FONT> <FONT color=#ff0000>runat</FONT><FONT color=#0000ff>=</FONT><FONT color=#ff00ff>"server" </FONT> <FONT color=#800080>contentplaceholderid</FONT><FONT color=#0000ff>=</FONT><FONT color=#ff00ff>"PlaceHolderPageTitle" </FONT><FONT color=#0000ff>></FONT> <FONT color=#000000> Echo Page</FONT> <FONT color=#0000ff></</FONT><FONT color=#808000>asp:Content</FONT><FONT color=#0000ff>></FONT> <FONT color=#0000ff><</FONT><FONT color=#808000>asp:Content </FONT><FONT color=#ff0000>ID</FONT><FONT color=#0000ff>=</FONT><FONT color=#ff00ff>"PageTitleInTitleArea" </FONT> <FONT color=#ff0000>runat</FONT><FONT color=#0000ff>=</FONT><FONT color=#ff00ff>"server" </FONT> <FONT color=#800080>contentplaceholderid</FONT><FONT color=#0000ff>=</FONT><FONT color=#ff00ff>"PlaceHolderPageTitleInTitleArea" </FONT><FONT color=#0000ff>></FONT> <FONT color=#000000> The Echo Page Test</FONT> <FONT color=#0000ff></</FONT><FONT color=#808000>asp:Content</FONT><FONT color=#0000ff>></FONT> </FONT> |
I’ve bolded the key part above, which is linking to the master page for SharePoint layout application pages. In addition, you can see that I’ve created three placeholder content sections with the key section being the PlaceHolderMain. I’ve placed my Javascript references and my ScriptManager into this section, pointing to our simple service, EchoService.asmx. Notice the use of the root squiggly “~” š and the lack of squiggly on the script references to prototype.js and WssAjaxApplication.js.
The service I’m going to be using for this demo is a simple “echo service” which just echoes the input string with the server timestamp attached. The following is my simple implementation of this web service:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
<FONT face="Lucida Console"><FONT color=#0000ff>using </FONT><FONT color=#008080>System</FONT><FONT color=#000000>;</FONT> <FONT color=#0000ff>using </FONT><FONT color=#008080>System</FONT><FONT color=#000000>.</FONT><FONT color=#008080>ComponentModel</FONT><FONT color=#000000>;</FONT> <FONT color=#0000ff>using </FONT><FONT color=#008080>System</FONT><FONT color=#000000>.</FONT><FONT color=#008080>Web</FONT><FONT color=#000000>.Script.</FONT><FONT color=#008080>Services</FONT><FONT color=#000000>;</FONT> <FONT color=#0000ff>using </FONT><FONT color=#008080>System</FONT><FONT color=#000000>.</FONT><FONT color=#008080>Web</FONT><FONT color=#000000>.</FONT><FONT color=#008080>Services</FONT><FONT color=#000000>;</FONT> <FONT color=#0000ff>namespace </FONT><FONT color=#000000>WssAjaxApplicationTest.</FONT><FONT color=#008080>Services </FONT><FONT color=#000000>{</FONT> <FONT color=#008000>/// <summary></FONT> <FONT color=#008000>/// Summary description for EchoService</FONT> <FONT color=#008000>/// </summary></FONT> <FONT color=#000000>[</FONT><FONT color=#808000>WebService</FONT><FONT color=#000000>(Namespace = </FONT><FONT color=#ff00ff>"http://tempuri.org/"</FONT><FONT color=#000000>)]</FONT> <FONT color=#000000>[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]</FONT> <FONT color=#000000>[</FONT><FONT color=#808000>ToolboxItem</FONT><FONT color=#000000>(</FONT><FONT color=#0000ff>false</FONT><FONT color=#000000>)]</FONT> <FONT color=#000000>[ScriptService]</FONT> <FONT color=#0000ff>public class </FONT><FONT color=#000000>EchoService : </FONT><FONT color=#808000>WebService </FONT><FONT color=#000000>{</FONT> <FONT color=#000000>[WebMethod]</FONT> <FONT color=#000000>[ScriptMethod]</FONT> <FONT color=#0000ff>public string </FONT><FONT color=#000000>Echo(</FONT><FONT color=#0000ff>string </FONT><FONT color=#000000>message) {</FONT> <FONT color=#000000>message = </FONT><FONT color=#0000ff>string</FONT><FONT color=#000000>.Format(</FONT><FONT color=#ff00ff>"You said: \"{0}\" at {1}"</FONT><FONT color=#000000>, </FONT> <FONT color=#000000>message, </FONT><FONT color=#808000>DateTime</FONT><FONT color=#000000>.Now);</FONT> <FONT color=#0000ff>return </FONT><FONT color=#000000>message;</FONT> <FONT color=#000000>}</FONT> <FONT color=#000000>}</FONT> <FONT color=#000000>}</FONT> </FONT> |
As you can see by the .aspx page above, I’ve organized my service in a sub-folder called “Services”. Now just make sure that you’ve added a copy of prototype.js under the _scr directory and my application script:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
<FONT face="Lucida Console"><FONT color=#000000>WssAjaxApplication = Class.create();</FONT> <FONT color=#000000>Object.extend(WssAjaxApplication.prototype, {</FONT> <FONT color=#000000>initialize:</FONT><FONT color=#0000ff>function</FONT><FONT color=#000000>() {</FONT> <FONT color=#0000ff>this</FONT><FONT color=#000000>.MessageInput = $(</FONT><FONT color=#ff00ff>"message-input"</FONT><FONT color=#000000>);</FONT> <FONT color=#0000ff>this</FONT><FONT color=#000000>.ActionButton = $(</FONT><FONT color=#ff00ff>"action-button"</FONT><FONT color=#000000>);</FONT> <FONT color=#0000ff>this</FONT><FONT color=#000000>.MessageOutput = $(</FONT><FONT color=#ff00ff>"message-output"</FONT><FONT color=#000000>);</FONT> <FONT color=#000000>Event.observe($(</FONT><FONT color=#ff00ff>'action-button'</FONT><FONT color=#000000>), </FONT><FONT color=#ff00ff>"click"</FONT><FONT color=#000000>, </FONT> <FONT color=#0000ff>this</FONT><FONT color=#000000>.OnClickActionButton.bindAsEventListener(</FONT><FONT color=#0000ff>this</FONT><FONT color=#000000>), </FONT><FONT color=#0000ff>false</FONT><FONT color=#000000>);</FONT> <FONT color=#000000>},</FONT> <FONT color=#000000>OnClickActionButton:</FONT><FONT color=#0000ff>function</FONT><FONT color=#000000>(e) {</FONT> <FONT color=#0000ff>if</FONT><FONT color=#000000>(e) { Event.stop(e); } </FONT><FONT color=#008000>// Stop the event </FONT> <FONT color=#008000>// Perform the echo.</FONT> <FONT color=#000000>WssAjaxApplicationTest.Services.EchoService.Echo(</FONT> <FONT color=#0000ff>this</FONT><FONT color=#000000>.MessageInput.value,</FONT> <FONT color=#0000ff>this</FONT><FONT color=#000000>.OnClickActionButtonSuccess.bindAsEventListener(</FONT><FONT color=#0000ff>this</FONT><FONT color=#000000>),</FONT> <FONT color=#0000ff>this</FONT><FONT color=#000000>.OnClickActionButtonError.bindAsEventListener(</FONT><FONT color=#0000ff>this</FONT><FONT color=#000000>)</FONT> <FONT color=#000000>); </FONT> <FONT color=#000000>},</FONT> <FONT color=#000000>OnClickActionButtonSuccess:</FONT><FONT color=#0000ff>function</FONT><FONT color=#000000>(result) {</FONT> <FONT color=#0000ff>this</FONT><FONT color=#000000>.MessageOutput.innerHTML = result;</FONT> <FONT color=#000000>},</FONT> <FONT color=#000000>OnClickActionButtonError:</FONT><FONT color=#0000ff>function</FONT><FONT color=#000000>(error, userContext, methodName) {</FONT> <FONT color=#ff0000>window</FONT><FONT color=#000000>.alert(methodName + </FONT> <FONT color=#ff00ff>" failed with the message: " </FONT><FONT color=#000000>+ error.get_message());</FONT> <FONT color=#000000>}</FONT> <FONT color=#000000>});</FONT> </FONT> |
The script simply attachs an event listener to the “Go” button and handles the click event. Notice how clean and simple the HTML portion of the page is and how clean the Javascript is as well (admittedly, this is a very simple example). The client rendering is completely decoupled from the UI logic except for the data and operations contract.
You should be good to go so far as code goes. Now compile your project with a strong named key file.
Hopefully, the project was compiled successfully. The next step is to copy the output dll to the GAC of the SharePoint server. Be sure to note the public key token value.
This is probably the trickiest part: now you need to carefully merge the configuration files (is there a better tool to do this with?) generated by the project template with the web.config file located at the virtual directory root of your SharePoint application. For example, if you have an application deployed at port 8080, the web.config file should be located at C:\Inetpub\wwwroot\wss\VirtualDirectories\8080. Be sure to save a backup copy of the configuration file first before you attempt to merge it!
Once merged, you will need to add one more element to the configuration file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<FONT face="Lucida Console"><FONT color=#0000ff><?xml </FONT><FONT color=#ff0000>version</FONT><FONT color=#0000ff>=</FONT><FONT color=#ff00ff>"1.0" </FONT><FONT color=#ff0000>encoding</FONT><FONT color=#0000ff>=</FONT><FONT color=#ff00ff>"UTF-8" </FONT><FONT color=#800080>standalone</FONT><FONT color=#0000ff>=</FONT><FONT color=#ff00ff>"yes"</FONT><FONT color=#800080>?</FONT><FONT color=#0000ff>></FONT> <FONT color=#0000ff><configuration></FONT><FONT color=#000000> </FONT> <FONT color=#000000> </FONT><FONT color=#0000ff><</FONT><FONT color=#808000>system</FONT><FONT color=#000000>.</FONT><FONT color=#800080>web</FONT><FONT color=#0000ff>></FONT><FONT color=#000000> </FONT> <FONT color=#000000> </FONT><FONT color=#0000ff><</FONT><FONT color=#808000>compilation </FONT><FONT color=#800080>batch</FONT><FONT color=#0000ff>=</FONT><FONT color=#ff00ff>"false" </FONT><FONT color=#800080>debug</FONT><FONT color=#0000ff>=</FONT><FONT color=#ff00ff>"false"</FONT><FONT color=#0000ff>></FONT> <FONT color=#000000> </FONT><FONT color=#0000ff><</FONT><FONT color=#808000>assemblies</FONT><FONT color=#0000ff>></FONT> <FONT color=#000000> </FONT><FONT color=#0000ff><</FONT><FONT color=#808000>add </FONT><FONT color=#800080>assembly</FONT><FONT color=#0000ff>=</FONT><FONT color=#ff00ff>"Microsoft.SharePoint, </FONT> <FONT color=#ff00ff> Version=12.0.0.0, Culture=neutral, </FONT> <FONT color=#ff00ff> PublicKeyToken=71e9bce111e9429c" </FONT><FONT color=#0000ff>/></FONT> <FONT color=#000000> </FONT><FONT color=#0000ff><</FONT><FONT color=#808000>add </FONT><FONT color=#800080>assembly</FONT><FONT color=#0000ff>=</FONT><FONT color=#ff00ff>"System.Web.Extensions, </FONT> <FONT color=#ff00ff> Version=1.0.61025.0, Culture=neutral, </FONT> <FONT color=#ff00ff> PublicKeyToken=31bf3856ad364e35"</FONT><FONT color=#0000ff>/></FONT> <FONT color=#000000> </FONT> <B><FONT color=#0000ff><</FONT><FONT color=#808000>add </FONT><FONT color=#800080>assembly</FONT><FONT color=#0000ff>=</FONT><FONT color=#ff00ff>"WssAjaxApplicationTest,</FONT> <FONT color=#ff00ff> Version=1.0.0.0, Culture=neutral, </FONT> <FONT color=#ff00ff> PublicKeyToken=97d3f1fd9f5212b9"</FONT><FONT color=#0000ff>/></FONT></B><FONT color=#000000> </FONT> <FONT color=#0000ff></</FONT><FONT color=#808000>assemblies</FONT><FONT color=#0000ff>></FONT> <FONT color=#000000> </FONT><FONT color=#0000ff></</FONT><FONT color=#808000>compilation</FONT><FONT color=#0000ff>></FONT> <FONT color=#000000> </FONT><FONT color=#0000ff></</FONT><FONT color=#808000>system</FONT><FONT color=#000000>.</FONT><FONT color=#800080>web</FONT><FONT color=#0000ff>></FONT> <FONT color=#0000ff></configuration></FONT> </FONT> |
I’ve highlighted the key line (the line above it should have been merged into the file previously). The bolded entry above is for the web application binary.
To test whether you’ve succeeded, you can simply point your browser to the URL: http://myserver/_layouts/mywebapp/default.aspx and you will have a fully AJAX enabled application using ASP.NET AJAX to connect to a .NET web service with prototype as a general purpose Javascript utility library (and you can even add scriptaculous on top of that for more awesome).
I’ve included a self extracting 7z file of the solution (see link below) if you’d like a quick start. Note that the Microsoft SharePoint binaries are not included and you will have to add them back manually before the project will build.
Happy coding!
WssAjaxApplicationTest.exe (162.02 KB)