Saturday, July 21, 2012

Use provisioning resources in page layouts in Sharepoint

In Sharepoint provisioning resources are stored in 14/Resources folder. In order to use the same resource literals declaratively e.g. in page layouts (remember that uncustomized page layouts are retrieved in 14/Template/Features subfolder, which is not even virtual folder. Nevertheless it is possible to use resources in them declaratively – see below) you may need to copy them to App_GlobalResources folder of your web application in IIS (e.g. C:\inetpub\wwwroot\wss\VirtualDirectories\MyWebApp80\App_GlobalResources). If you worked with Sharepoint 2007 you probably know that there is special stsadm command which makes similar things: copyappbincontent. In this post I will show how to achieve the result using only Visual Studio features.

In order to do that at first double click on your package in VS solution tree:

image

Then in opened package window click “Manifest” tab. Scroll content and below the window you will find Edit options section:

image

Expand it and write the following in text area:

   1: <?xml version="1.0" encoding="utf-8"?>
   2: <Solution xmlns="http://schemas.microsoft.com/sharepoint/">
   3:   <ApplicationResourceFiles>
   4:     <App_GlobalResourceFile Location="Resources\MyResources.resx" />
   5:     <App_GlobalResourceFile Location="Resources\MyResources.ru-RU.resx" />
   6:     <App_GlobalResourceFile Location="Resources\MyResources.fi-FI.resx" />
   7:   </ApplicationResourceFiles>
   8: </Solution>

In this example I assume that your provisioning resources are stored in Resources mapped folder in Visual Studio (this is common scenario). After that package wsp and deploy it. After that check App_GlobalResources folder of your web application. It should contain Resources subfolder with provisioning resources copied in it. The fact that resx files are located in subfolder should not worry you. ASP.Net allows using resources from subfolders of App_GlobalResources, so you will be able to add literals to your page layouts by standard way:

   1: <asp:Literal runat="server" Text="<%$Resources:MyResources,Foo%>" />

And this is useful, because as I said page layouts are located not in virtual folder. In virtual folders like 14/Template/Layouts or 14/Template/ControlTemplates you may create App_LocalResources using default ASP.Net convention (resource file should be the same as page or user control file name with “.resx” extension, e.g. MyControl.ascx.resx) and use resources even simpler without resx file name prefix:

   1: <asp:Literal runat="server" Text="<%$Resources:Foo%>" />

This technique allows to use standard ASP.Net expressions for resources in page layouts.

Thursday, July 19, 2012

Create asynchronous web parts for Sharepoint

One of the way to improve performance of your Sharepoint site is to implement custom web parts via ajax. As you probably know there are standard Sharepoint web parts which support ajax out of the box. E.g. Search core results web part has Ajax options in properties:

image

If you will check “Enable Asynchronous Load” option, web part will be loaded asynchronously and won’t block page loading. But not all standard web parts have this possibility. Custom web parts by default also are not ajax enabled. You need to do additional work in order to implement them asynchronously.

In this post I will show how to create async custom web part using “Framework for loading SharePoint web parts asynchronously” from Codeplex (thanks to author Chaitu Madala for sharing it). There are other ways to do that, but I decided to use this project as it is more or less good extendable and can be reused for many custom web parts in your solution. Basic idea is shown in the following schema:

image

Let’s begin from custom web part class. It should inherit BaseWebPart class from async framework project (BaseWebPart in turn inherits regular ASP.Net WebPart class). BaseWebPart contains 2 methods: one for creating container for progress indicator, another for registering asynchronous web service proxy. Simplified version looks like this:

   1: public class BaseWebPart : WebPart
   2: {
   3:     protected Panel contentContainer;
   4:  
   5:     protected void PrepareContentContainer()
   6:     {
   7:         try
   8:         {
   9:             if (HttpContext.Current.Request.Browser.EcmaScriptVersion.Major >= 1)
  10:             {
  11:                 //Busy Box View (JS enabled)
  12:                 Image spinnerImage = new Image();
  13:                 spinnerImage.ImageUrl = String.Format("{0}{1}",
  14: SPContext.Current.Web.Url, "/_layouts/AsyncWebpart/Images/loadingspinner.gif");
  15:                 Label spinnerText = new Label()
  16: { Text = "Please wait while the results are being loaded.." };
  17:  
  18:                 contentContainer.ID = "" + this.ID + "Context";
  19:                 contentContainer.Controls.Add(spinnerImage);
  20:                 contentContainer.Controls.Add(new LiteralControl("<br />"));
  21:                 contentContainer.Controls.Add(spinnerText);
  22:                 //AJAX Enabled
  23:             }
  24:             else
  25:             {
  26:                 //AJAX Disabled - Fallback mode
  27:                  //Alternative content in case the user's browser does not support JS
  28:                 contentContainer.Controls.Add(
  29: new LiteralControl("It seems your browser doesn't support Javascript"));
  30:             }
  31:         }
  32:         catch (Exception ex)
  33:         {
  34:             // log
  35:         }
  36:     }
  37:  
  38:     protected void AddScriptManagerReferenceProxy(string referenceProxyUrl,
  39: string referenceProxyJS)
  40:     {
  41:         try
  42:         {
  43:             ScriptManager thisScriptManager = ScriptManager.GetCurrent(this.Page);
  44:             if (thisScriptManager == null)
  45:             {
  46:                 thisScriptManager = new ScriptManager();
  47:                 this.Controls.Add(thisScriptManager);
  48:             }
  49:  
  50:             ServiceReference referenceProxy = new ServiceReference();
  51:             referenceProxy.Path = referenceProxyUrl;
  52:  
  53:             referenceProxy.InlineScript = true;
  54:             //If you are having issues identifying the names of the web service JS methods, uncomment the line above ^
  55:  
  56:             thisScriptManager.Services.Add(referenceProxy);
  57:  
  58:             //JS file contains proxy methods for web service - see file for details
  59:  
  60:             this.Page.ClientScript.RegisterClientScriptInclude(typeof(Page), referenceProxyUrl, referenceProxyJS);
  61:         }
  62:         catch (Exception ex)
  63:         {
  64:             // log
  65:         }
  66:     }
  67: }

Web service will be described below. Here it is important to understand that we registered async proxy which can be called from javascript.

For this article let’s create simple custom web part with single custom property Delay. It contains delay in seconds which web service will wait until return response to the client. Those we will simulate long running operation. The code of web part will look like this:

   1: public class AsyncCustomWebPart : BaseWebPart
   2: {
   3:     [WebBrowsable(true)]
   4:     [Personalizable(PersonalizationScope.Shared)]
   5:     public int Delay { get; set; }
   6:  
   7:     protected override void OnLoad(EventArgs e)
   8:     {
   9:         try
  10:         {
  11:             EnsureChildControls();
  12:  
  13:             // register script to automatically load the web part into the content container
  14:             // pass all the property values through asynchronous call
  15:             this.Page.ClientScript.RegisterStartupScript(typeof (Page),
  16:                 "startupScript" + this.ClientID,
  17:                 string.Format("AsyncWebPartGetData('{0}', '{1}');",
  18:                     this.Delay, contentContainer.ClientID), true);
  19:  
  20:             base.OnLoad(e);
  21:         }
  22:         catch (Exception ex)
  23:         {
  24:             // handle error
  25:         }
  26:     }
  27:  
  28:     protected override void CreateChildControls()
  29:     {
  30:         try
  31:         {
  32:             contentContainer = new Panel();
  33:  
  34:             // prepare container to hold the content
  35:             PrepareContentContainer();
  36:             // add web service reference and js handler
  37:             AddScriptManagerReferenceProxy(
  38:                 "/_layouts/AsyncWebPart/AsyncWebPartService.asmx",
  39:                 "/_layouts/AsyncWebPart/JS/AsyncWebPart.js");
  40:         }
  41:         catch (Exception ex)
  42:         {
  43:             // handle error
  44:         }
  45:     }
  46:  
  47:     public override void RenderControl(HtmlTextWriter writer)
  48:     {
  49:         contentContainer.RenderControl(writer);
  50:         base.RenderControl(writer);
  51:     }
  52: }

Here in OnLoad method we ensure that child controls are created (line 11). In CreateChildControls method (lines 28-45) we create Panel control which will be actual container into which we will load html from web service. As we already saw in PrepareContentContainer method (line 35) we load animated gif which shows progress indicator on the page until web part content won’t be ready:

image

After that we add async web service proxy and reference to the AsyncWebPart.js file which is part of the async web parts framework (lines 37-39). I will show content of this javascript file below.

Then in OnLoad method we register javascript startup script for our custom function AsyncWebPartGetData which is defined in AsyncWebPart.js and pass all custom web part properties into this function. These properties will be then passed to the web service. I.e. we will still have possibility to change web part behavior via its properties even for asynchronous version.

Ok, what happens next? When page is loaded our javascript function is called. Let’s see the code of AsyncWebPart.js:

   1: function AsyncWebPartGetData(delay, containerID) 
   2: {
   3:     //Remember to use entire namespace + class before method name
   4:     AsyncWebPart.AsyncWebPartService.GetUIHtmlForAsyncWebPart(
   5:         delay, AsyncWebPartComplete, AsyncWebPartError, containerID);
   6: }
   7:  
   8: //Client-side onComplete handler
   9: function AsyncWebPartComplete(response, containerID) 
  10: {
  11:     //Populates the container with the response
  12:     var containerDiv = document.getElementById(containerID);
  13:     containerDiv.innerHTML = response;
  14: }
  15:  
  16: //Client-side onError handler
  17: function AsyncWebPartError(response, containerID) 
  18: {
  19:     var containerDiv = document.getElementById(containerID);
  20:     containerDiv.innerHTML = "An error has occurred. Please contact administrator.";
  21: }

AsyncWebPartGetData function is unique for our custom web part. I.e. if you will need to make another web part asynchronously, you need to create another function for it. Two other methods (AsyncWebPartComplete and AsyncWebPartError) can be reused for all web parts. AsyncWebPartGetData receives all custom web part properties as parameters. In our example it is delay and containerID (containerID should be passed to all another functions for other web parts. It is used then in callbacks in order to load html returned from web service). It calls generated web service proxy asynchronously and passes delay, callbacks and containerID (lines 4,5). After that control is returned to the page.

In real life registration of javascript function and asynchronous call to web service is very fast, so web part almost doesn’t affect page loading time. So if you have some slow web part, using of this technique may significantly improve performance and make user experience much better. But let’s continue.

When call reaches web service it makes the following:

   1: [System.Web.Script.Services.ScriptService]
   2: [WebService(Namespace = "http://tempuri.org/", Name = "AsyncWebPartService")]
   3: [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
   4: [ToolboxItem(false)]
   5: public class AsyncWebPartService : System.Web.Services.WebService
   6: {
   7:     [ScriptMethod]
   8:     [WebMethod]
   9:     public string GetUIHtmlForAsyncWebPart(int delay)
  10:     {
  11:         var ctrl = new MyControl();
  12:         ctrl.Delay = delay;
  13:  
  14:         var retOutputBuilder = new StringBuilder();
  15:         using (var stringWriter = new StringWriter(retOutputBuilder))
  16:         {
  17:             using (var textWriter = new HtmlTextWriter(stringWriter))
  18:             {
  19:                 ctrl.RenderControl(textWriter);
  20:             }
  21:         }
  22:         return retOutputBuilder.ToString();
  23:     }
  24: }

At first it creates instance of the custom control (line 11). Then it passes custom properties which we got from web part to created control (line 12). In this example we pass delay and control doesn’t make anything except waiting specified amount of seconds. But in real life you may pass all web part custom properties into it. Then service renders control to the string (lines 14-22) and returns the result. Note that as in web method GetUIHtmlForAsyncWebPart we actually create html for our web part, we should create separate web method for each web part which we would like to make asynchronous. Then as I mentioned above generate proxy for it and create javascript function in AsyncWebPart.js. In this example we used custom control, but it is also possible to use user controls with this technique (see e.g. this forum thread: ASP.NET how to Render a control to HTML). After that rendered string is passed back to the client and javascript handler loads it into container inside web part. It happens asynchronously without page re-loading.

The last thing which is worth mentioning is that if you want to use this approach, you need to separate web part from actual logic which renders UI. I.e. web part itself should be just lightweight container. But this is rather a good practice then limitation (e.g. OTB Sharepoint visual web parts are implemented exactly like this: web part is just a container which logic is located inside user control which is loaded dynamically).

That’s all which I would like to write about this approach. Again thanks to Chaitu Madala who shared it with community. Hope this article will help you if you will decide to use Framework for loading SharePoint web parts asynchronously in your work.

Update 2012-11-25: when use this approach you may encounter with the problem of incorrect context site for rendering your web parts. Solution is described here: Problem with context site in Sharepoint when call web service from javascript.