[WSS]WSS V3 Custom WebPart Features Gothrough

[WSS]WSS V3 Custom WebPart Features Gothrough

Windows Sharepoint Service V3 is built upon ASP.NET 2.0 which makes its development and customization more powerful and flexible. There are also many new cool features for webpart development. I’ll use a single custom webpart to demonstrate some of the custom webpart development features(some are ASP.NET specific and some are WSS specific). Below is the brief list of all the features I’ll covered in the entry:

l Use assembly embedded resources in custom webpart

l Use custom Verbs(client and server)

l Use the Client-Script model(WPSC) of WSS webpart

l Using ASP.NET AJAX in custom Webpart

l Use async page execution in custom webpart

The part I created here(figure below) will present several different UI/views based on its “CurrentMode” property, and user can switch Mode via the corresponding verbs(custom verbs).

 

Let’s now look at how each of them was implemented:

Use assembly embedded resources in custom webpart

Since I’ll use some custom images and javascript in my webpart, it’s really good if all the resource are centralized in the webpart component itself(rather than require additional resource deployment). Well, the “WebResource Accessing” feature of ASP.NET 2.0 well suits this requirement. If you’re not familiar with “accessing embedded assembly resource in ASP.NET 2.0” , here is a good article for you:

#Accessing Embedded Resources through a URL using WebResource.axd

https://aspnet.4guysfromrolla.com/articles/080906-1.aspx

In my webpart project, what I need to do is

1) mark the “Build Action” of all the static resources(image and script file) as “Embedded Resource”.

2) Add attribute to expose those resources to ASP.NET WebResource handler. E.g.

//put this in AssemblyInfo.cs

[assembly: System.Web.UI.WebResource("CustomWebPartLib.scripts.AdvWebPartScripts.js", "text/javascript")]

[assembly: System.Web.UI.WebResource("CustomWebPartLib.images.ServerVerb.bmp","image/bmp")]

Then, I can use these resource in my webpart code as below:

protected override void OnLoad(EventArgs e) {

            base.OnLoad(e);

            string script_url = Page.ClientScript.GetWebResourceUrl(this.GetType(), "CustomWebPartLib.scripts.AdvWebPartScripts.js");

            Page.ClientScript.RegisterClientScriptInclude(this.GetType(), "advwebpart_scripts", script_url);

Use custom Verbs(client and server)

All WSS v3 Web Parts, like their WSS v2 counterparts, have a menu that’s accessible by clicking the small downward facing arrow in the upper right corner of the rendered Web Part. Then, how can we customize it(such as add more new verbs in the menu)? Well, since this menu has been enhanced from it’s underlying model, creating a custom Web Part verb and adding it to the verbs menu is a fairly simple task. What you need to do is simply override the Verbs virtual property.

public override WebPartVerbCollection Verbs

        {

            get

            {

                //client-side verb

                WebPartVerb clientverb = new WebPartVerb("Adv_ClientVerb", "ShowTextElementCount()");

                clientverb.Text = "ClientVerb"; clientverb.Description = "Verb executed at client-side.";

                clientverb.ImageUrl = Page.ClientScript.GetWebResourceUrl(this.GetType(), "CustomWebPartLib.images.ClientVerb.bmp");

                //server-side verb

                WebPartVerb serververb_WPSC = new WebPartVerb("serververb_WPSC", new WebPartEventHandler(ServerVerbHandler_WPSC));

                .............................

                WebPartVerb[] newverbs = new WebPartVerb[4] { clientverb, serververb_WPSC, serververb_ASYNC, serververb_AJAX };

                return new WebPartVerbCollection(base.Verbs, newverbs);

            }

        }

As we can see, there are both client-side verb(execute client-script) and server-side verb(raise postback event). Server-side verb works like a typical ASP.NET postback event.

Use the Client-Script model(WPSC) of WSS webpart

Microsoft has included a client-side component to the Web Part infrastructure in WSS v3 that adds client-side capabilities such as Web Part discovery, notification, and state management. This client-side component is called the Web Part Page Services Component (WPSC) and is included in the WSS v3 installation. Its implemented with client-side JavaScript in one of two .js files found in the 12 Hive under …12\TEMPLATE\LAYOUTS\[LCID]: IE50UP,js and IE55UP.js. Based on the client’s browser version, the appropriate .js file is added to the source of the Web Part Page requested.

Therefore, we can use this WPSC model to manipulate(read and modify) properties of webpart. In my custom webpart, I’ll use WPSC to locate my webpart and access its properties. The client-script is as below(which find the Title property and change it):

//change webpart title

function ChangeCurrentPartTitle(pid, new_title)

{

    var part = pid;

    var i;

   

    for(i=0;i<part.Properties.Count();++i)

    {

        if(part.Properties.Item(i).SchemaElement == "Title") break;

    }

   

    part.Properties.Item(i).Value = new_title.value;

    part.Save(false, CB_ChangeCurrentPartTitle);

}

function CB_ChangeCurrentPartTitle( bSucceeded,strExceptionMessage)

{

    if(bSucceeded)

    {

        alert("Title is successfully changed! Need postback to see the complete change!");

    }

    else{

        alert("Error: " + strExceptionMessage);

    }

}

The script function need a webpart object’s client-side reference, this can be acquired via WSS WebPart(not ASP.NET 2) class’s “ClientName” property. E.g.

            btnChangeTitle = new Button();

            btnChangeTitle.Text = "Change WebPart Title";

            btnChangeTitle.OnClientClick = "ChangeCurrentPartTitle(" + ClientName + "," + txtNewTitle.ClientID + ");return false;";

            Controls.Add(btnChangeTitle);

If you want more information about the WPSC model, the following MSDN reference is available:

#Web Part Page Services Component (WPSC) Object Model

https://msdn.microsoft.com/en-us/library/ms477066.aspx

https://msdn.microsoft.com/en-us/library/ms415880.aspx

The above picture shows the WPSC mode View of my part. The “Explore this webpart’s Properties....” button will help display all the properties of this webpart. While the “Change WebPart Title” button will change the webpart title to the value in the textbox on its left.

Using ASP.NET AJAX in custom Webpart

ASP.NET AJAX is quite popular in ASP.NET 2.0+ applications. However, since WSS 3.0 doesn’t add built-in support of it, use AJAX extensions in WSS v3 webpart require us to do much additional works. Here is what we need to do(on a WSS 3.0 RTM environment):

² Add all the ASP.NET ajax required configuration(such as assemblies, httphandler, httpmodule...) into WSS site’s web.config file.

² Customize the site master page and add a ScriptManager in its source.

² Add AJAX control and some particular scripts(fix some existing issue on script postback)

The following blog entries contains more detailed description on this:

#Integrating ASP.NET AJAX with SharePoint

https://sharepoint.microsoft.com/blogs/mike/Lists/Posts/Post.aspx?ID=3

https://blogs.msdn.com/joelo/archive/2007/11/29/asp-net-ajax-support-in-wss-3-0-and-moss-2007-sp1.aspx

the following picture displays the AJAX mode view of my custom webpart:

By clicking the “Submit as Ajax” button, it will use AJAX postback(via updatepanel) to get the time from server-side.

Use async page execution in custom webpart

Since ASP.NET 2.0 Page support asynchronous execution, synchronous processing is not the only option you have in your bag of tricks when developing Web Parts. Instead of developing your Web Parts in a synchronous manner where database calls or Web service requests could potentially hold up the processing of the page, you can develop them using asynchronous techniques to speed up the processing and rendering of the page. For more detailed background info on ASP.NET async page execution, there are many good web articles:

#Use Threads and Build Asynchronous Handlers in Your Server-Side Web Code

https://msdn.microsoft.com/en-us/magazine/cc164128.aspx

#Asynchronous ASP.NET Page Processing

https://www.eggheadcafe.com/articles/20060918.asp

here I will use Async execution to call a long-run method several times and compare it with the sync execution approach. The async execution code is as below:

  void btnAsyncTasks_Click(object sender, EventArgs e)

        {

            Watcher = new Stopwatch();

            Watcher.Start();

            AsyncWorker worker1 = new AsyncWorker(new TaskMethodDelegate(LongRunJob));

            AsyncWorker worker2 = new AsyncWorker(new TaskMethodDelegate(LongRunJob));

            AsyncWorker worker3 = new AsyncWorker(new TaskMethodDelegate(LongRunJob));

            PageAsyncTask task1 = new PageAsyncTask(worker1.OnBegin, worker1.OnEnd, worker1.OnTimeout, "task1");

            PageAsyncTask task2 = new PageAsyncTask(worker2.OnBegin, worker2.OnEnd, worker2.OnTimeout, "task2");

            PageAsyncTask task3 = new PageAsyncTask(worker3.OnBegin, worker3.OnEnd, worker3.OnTimeout, "task3");

            Page.RegisterAsyncTask(task1);

            Page.RegisterAsyncTask(task2);

            Page.RegisterAsyncTask(task3);

            Page.ExecuteRegisteredAsyncTasks();

            Watcher.Stop();

            lblMessage.Text = "3 Tasks executed in Async Mode, time used(seconds): " + Watcher.Elapsed.TotalSeconds;

        }

And the ASYNC mode webpart view is displayed as the following figure:

Now what is left is deploying the webpart into WSS site. Since I use the VS 2008 WSS v3 extension(version 1.2), it’s quite convenient to deploy a webpart project as a WSS solution(with features).

https://www.microsoft.com/downloads/details.aspx?FamilyID=7BF65B28-06E2-4E87-9BAD-086E32185E68&displaylang=en

I’ll include the full source code and script file here. If you feel necessary, the entire webpart project can be provided.

#webpart source code

using System;

using System.Runtime.InteropServices;

using System.Web.UI;

using System.Web.UI.WebControls;

using System.Web.UI.WebControls.WebParts;

using System.Xml.Serialization;

using Microsoft.SharePoint;

using Microsoft.SharePoint.WebControls;

using Microsoft.SharePoint.WebPartPages;

using System.ComponentModel;

using System.Diagnostics;

using System.Drawing;

namespace CustomWebPartLib

{

    /*use Microsoft.SharePoint.WebPartPages.WebPart since we need the ClientName to reference the current webpart in clientscript(WPSC)

    */

    [Guid("badca4a6-92b1-413e-90de-fd9abc589628")]

    public class AdvWebPart : Microsoft.SharePoint.WebPartPages.WebPart

    {

        //variables

        AdvWebPartMode _mode = AdvWebPartMode.WPSC;

        Stopwatch Watcher = null;

        //common controls

        Label lblMessage = null;

        Button btnSubmit = null;

        //controls for WPSC mode

        Button btnWPSC = null;

        TextBox txtNewTitle = null;

        Button btnChangeTitle = null;

        //controls for ASYNC mode

        Button btnSyncTasks = null;

        Button btnAsyncTasks = null;

        //controls for AJAX mode

        ScriptManager sm = null;

        Label lblAjax = null;

        Button btnAjaxSubmit = null;

        [Category("Custom Properties")]

        [DefaultValue(AdvWebPartMode.WPSC)]

        [WebPartStorage(Storage.Shared)]

        [Browsable(true)]

        [Personalizable(PersonalizationScope.Shared)]

        public AdvWebPartMode CurrentMode

        {

            get

            {

                return _mode;

            }

            set

            {

                _mode = value;

            }

        }

        public AdvWebPart()

        {

            ExportMode = WebPartExportMode.All;

        }

        protected override void OnLoad(EventArgs e)

        {

            base.OnLoad(e);

            //register client script include

            string script_url = Page.ClientScript.GetWebResourceUrl(this.GetType(), "CustomWebPartLib.scripts.AdvWebPartScripts.js");

            Page.ClientScript.RegisterClientScriptInclude(this.GetType(), "advwebpart_scripts", script_url);

        }

        protected override void CreateChildControls()

        {

            Controls.Clear();

            base.CreateChildControls();

            CreateCommonControls();

            switch (CurrentMode)

            {

                case AdvWebPartMode.WPSC:

                    CreateWPSCControls();

                    break;

                case AdvWebPartMode.ASYNC:

                    CreateASYNCControls();

                    break;

                case AdvWebPartMode.AJAX:

                    CreateAJAXControls();

                    break;

            }

        }

        void CreateCommonControls()

        {

            Controls.Add(new LiteralControl("<p style='font-size:25'>CurrentMode: " + CurrentMode.ToString() + "</p>"));

            Controls.Add(new LiteralControl("<div style='border-style:solid;border-color:Red'>"));

            lblMessage = new Label();

            lblMessage.Font.Name = "Verdana"; lblMessage.Font.Size = FontUnit.Large; lblMessage.ForeColor = Color.Red;

            lblMessage.ID = "lblMessage";

            lblMessage.Text = "AdvWebPart at: " + DateTime.Now.ToLongTimeString();

            Controls.Add(lblMessage);

            Controls.Add(new LiteralControl("</div>"));

            Controls.Add(new LiteralControl("<br/>"));

            btnSubmit = new Button();

            btnSubmit.Text = "Normal Submit(postback)";

            btnSubmit.Click += new EventHandler(btnSubmit_Click);

            Controls.Add(btnSubmit);

        }

        void CreateWPSCControls()

        {

            Controls.Add(new LiteralControl("<br/>"));

            Controls.Add(new LiteralControl("<br/><div style='border-style:solid'> "));

            Controls.Add(new LiteralControl("Here is the WPSC section:<hr/>"));

            btnWPSC = new Button();

            btnWPSC.ID = "btnWPSC"; btnWPSC.Text = "Explore this webpart's Properties via WPSC...";

            btnWPSC.OnClientClick = "ShowCurrentWebPartInfo(" + ClientName + "," + lblMessage.ClientID + ");return false;";

            Controls.Add(btnWPSC);

            Controls.Add(new LiteralControl("<br/><br/>New Part Title:"));

            txtNewTitle = new TextBox();

            txtNewTitle.ID = "txtNewTitle";

            Controls.Add(txtNewTitle);

            btnChangeTitle = new Button();

            btnChangeTitle.Text = "Change WebPart Title";

            btnChangeTitle.OnClientClick = "ChangeCurrentPartTitle(" + ClientName + "," + txtNewTitle.ClientID + ");return false;";

            Controls.Add(btnChangeTitle);

            Controls.Add(new LiteralControl("</div>"));

        }

        void CreateASYNCControls()

        {

            Controls.Add(new LiteralControl("<br/>"));

            Controls.Add(new LiteralControl("<br/><div style='border-style:solid'> "));

            Controls.Add(new LiteralControl("Here is the ASYNC section:<hr/>"));

            btnSyncTasks = new Button();

            btnSyncTasks.ID = "btnSyncTasks"; btnSyncTasks.Text = "Execute Sync Tasks";

            btnSyncTasks.Click += new EventHandler(btnSyncTasks_Click);

            Controls.Add(btnSyncTasks);

            btnAsyncTasks = new Button();

            btnAsyncTasks.ID = "btnAsyncTasks"; btnAsyncTasks.Text = "Execute Async Tasks";

            btnAsyncTasks.Click += new EventHandler(btnAsyncTasks_Click);

            Controls.Add(btnAsyncTasks);

            Controls.Add(new LiteralControl("</div>"));

        }

        void CreateAJAXControls()

        {

            if (Page == null) return;

            Controls.Add(new LiteralControl("<br/>"));

            Controls.Add(new LiteralControl("<br/><div style='border-style:solid'> "));

            Controls.Add(new LiteralControl("Here is the AJAX section:<hr/>"));

            EnsurePanelFix();

            //ScriptManager should have been added in the master page(for webpart pages)

          

            UpdatePanel up = new UpdatePanel();

          

            up.ID = "refreshName";

            up.UpdateMode = UpdatePanelUpdateMode.Conditional;

            up.ChildrenAsTriggers = true;

            Controls.Add(up);

            lblAjax = new Label(); lblAjax.BorderStyle = BorderStyle.Solid; lblAjax.BorderWidth = 1;

            lblAjax.Font.Size = FontUnit.XXLarge;

            btnAjaxSubmit = new Button();

            btnAjaxSubmit.ID = "btnAjaxSubmit";

            btnAjaxSubmit.Text = "Submit as Ajax";

            btnAjaxSubmit.Click += new EventHandler(btnAjaxSubmit_Click);

            //Add the user interface (UI) controls to the UpdatePanel.

            up.ContentTemplateContainer.Controls.Add(lblAjax);

            up.ContentTemplateContainer.Controls.Add(new LiteralControl("<br/>"));

   up.ContentTemplateContainer.Controls.Add(btnAjaxSubmit);

            Controls.Add(new LiteralControl("</div>"));

        }

        /// <summary>

        /// ///for custom verbs

        /// </summary>

        public override WebPartVerbCollection Verbs

        {

            get

            {

                //client-side verb

                WebPartVerb clientverb = new WebPartVerb("Adv_ClientVerb", "ShowTextElementCount()");

                clientverb.Text = "ClientVerb"; clientverb.Description = "Verb executed at client-side.";

                clientverb.ImageUrl = Page.ClientScript.GetWebResourceUrl(this.GetType(), "CustomWebPartLib.images.ClientVerb.bmp");

                WebPartVerb serververb_WPSC = new WebPartVerb("serververb_WPSC", new WebPartEventHandler(ServerVerbHandler_WPSC));

                serververb_WPSC.Text = "WPSC Mode"; serververb_WPSC.Description = "Make WebPart enter WPSC Mode";

                serververb_WPSC.ImageUrl = Page.ClientScript.GetWebResourceUrl(this.GetType(), "CustomWebPartLib.images.ServerVerb.bmp");

                WebPartVerb serververb_ASYNC = new WebPartVerb("serververb_ASYNC", new WebPartEventHandler(ServerVerbHandler_ASYNC));

                serververb_ASYNC.Text = "ASYNC Mode"; serververb_ASYNC.Description = "Make WebPart enter ASYNC Mode";

                serververb_ASYNC.ImageUrl = Page.ClientScript.GetWebResourceUrl(this.GetType(), "CustomWebPartLib.images.ServerVerb.bmp");

                WebPartVerb serververb_AJAX = new WebPartVerb("serververb_AJAX", new WebPartEventHandler(ServerVerbHandler_AJAX));

                serververb_AJAX.Text = "AJAX Mode"; serververb_AJAX.Description = "Make WebPart enter AJAX Mode";

                serververb_AJAX.ImageUrl = Page.ClientScript.GetWebResourceUrl(this.GetType(), "CustomWebPartLib.images.ServerVerb.bmp");

                WebPartVerb[] newverbs = new WebPartVerb[4] { clientverb, serververb_WPSC, serververb_ASYNC, serververb_AJAX };

                return new WebPartVerbCollection(base.Verbs, newverbs);

            }

        }

        //three handlers for server-side verbs

        void ServerVerbHandler_WPSC(object sender, WebPartEventArgs e)

        {

            if (CurrentMode != AdvWebPartMode.WPSC) ChangeMode(AdvWebPartMode.WPSC);

        }

        void ServerVerbHandler_ASYNC(object sender, WebPartEventArgs e)

        {

            if (CurrentMode != AdvWebPartMode.ASYNC) ChangeMode(AdvWebPartMode.ASYNC);

        }

        void ServerVerbHandler_AJAX(object sender, WebPartEventArgs e)

        {

            if (CurrentMode != AdvWebPartMode.AJAX) ChangeMode(AdvWebPartMode.AJAX);

        }

        //function for change mode and save properties

        void ChangeMode(AdvWebPartMode newMode)

        {

            CurrentMode = newMode;

            this.SaveProperties = true;

            ChildControlsCreated = false; CreateChildControls();

            lblMessage.Text = "Enter " + newMode.ToString() + " Mode at: " + DateTime.Now.ToLongTimeString();

    }

        /// <summary>

        /// ///for Async mode

        /// </summary>

        void btnSyncTasks_Click(object sender, EventArgs e)

        {

            Watcher = new Stopwatch();

            Watcher.Start();

            LongRunJob();

            LongRunJob();

            LongRunJob();

            Watcher.Stop();

            lblMessage.Text = "3 Tasks executed in Sync Mode, time used(seconds): " + Watcher.Elapsed.TotalSeconds;

        }

        void btnAsyncTasks_Click(object sender, EventArgs e)

        {

            Watcher = new Stopwatch();

            Watcher.Start();

            AsyncWorker worker1 = new AsyncWorker(new TaskMethodDelegate(LongRunJob));

            AsyncWorker worker2 = new AsyncWorker(new TaskMethodDelegate(LongRunJob));

            AsyncWorker worker3 = new AsyncWorker(new TaskMethodDelegate(LongRunJob));

            PageAsyncTask task1 = new PageAsyncTask(worker1.OnBegin, worker1.OnEnd, worker1.OnTimeout, "task1");

            PageAsyncTask task2 = new PageAsyncTask(worker2.OnBegin, worker2.OnEnd, worker2.OnTimeout, "task2");

            PageAsyncTask task3 = new PageAsyncTask(worker3.OnBegin, worker3.OnEnd, worker3.OnTimeout, "task3");

            Page.RegisterAsyncTask(task1);

            Page.RegisterAsyncTask(task2);

            Page.RegisterAsyncTask(task3);

            Page.ExecuteRegisteredAsyncTasks();

            Watcher.Stop();

            lblMessage.Text = "3 Tasks executed in Async Mode, time used(seconds): " + Watcher.Elapsed.TotalSeconds;

        }

        void LongRunJob()

        {

            System.Threading.Thread.Sleep(1000 * 5);

        }

        //////

        //normal postback handler

        void btnSubmit_Click(object sender, EventArgs e)

        {

            lblMessage.Text = "Normal Postback on AdvWebPart at: " + DateTime.Now.ToLongTimeString();

        }

        /// <summary>

        /// methods for AJAX mode

        /// </summary>

        ///

        void btnAjaxSubmit_Click(object sender, EventArgs e)

        {

            lblAjax.Text = "Time at Server-side is : " + DateTime.Now.ToLongTimeString();

         

        }

        private void EnsurePanelFix()

        {

            if (this.Page.Form != null)

            {

                String fixupScript = @"

             _spBodyOnLoadFunctionNames.push(""_initFormActionAjax"");

             function _initFormActionAjax()

             {

               if (_spEscapedFormAction == document.forms[0].action)

               {

                 document.forms[0]._initialAction =

                 document.forms[0].action;

               }

             }

             var RestoreToOriginalFormActionCore =

               RestoreToOriginalFormAction;

             RestoreToOriginalFormAction = function()

             {

               if (_spOriginalFormAction != null)

               {

                 RestoreToOriginalFormActionCore();

                 document.forms[0]._initialAction =

               document.forms[0].action;

               }

             }";

                ScriptManager.RegisterStartupScript(this,

                  typeof(AdvWebPart), "UpdatePanelFixup",

                  fixupScript, true);

            }

        }

    }

    [Serializable]

    public enum AdvWebPartMode

    {

        WPSC = 1,

        ASYNC =2,

        AJAX = 3

    }

}

#script file

function ShowTextElementCount()

{

    var txts = document.getElementsByTagName("input");

     

      var i;

      var count =0;

      for(i=0;i<txts.length;++i)

      {

        if(txts[i].type=="text")

        count++;

       

      }

      alert("This is a pure client-side operation. There are " + count + " input text elements on this page.");

    

   

}

//pid is the current webpart, lbl is the Label used to display message

function ShowCurrentWebPartInfo(pid, lbl)

{

     

var msg = "";

var part = pid;

msg += "<hr/><br/>site url: " + WPSC.WebPartPage.WebURL;

msg += "<br/>WebPartQualifier: "+part.WebPartQualifier;

msg += "<br/>properties:";

var props = part.Properties;

msg += "<br/><table cellspacing='5'><tr><td>NamespaceURN</td><td>SchemaElement</td><td>Value</td></tr>";

  for(var j=0;j<props.Count();++j)

 {

  msg+="<tr><td>"+ props.Item(j).NamespaceURN + "</td><td>" +props.Item(j).SchemaElement + "</td><td>" + props.Item(j).Value +"</td></tr>";

 }

 

msg +="</table><hr/>";

 

 lbl.innerHTML = msg;

}

//change webpart title

function ChangeCurrentPartTitle(pid, new_title)

{

    var part = pid;

    var i;

   

    for(i=0;i<part.Properties.Count();++i)

    {

        if(part.Properties.Item(i).SchemaElement == "Title") break;

    }

   

    part.Properties.Item(i).Value = new_title.value;

    part.Save(false, CB_ChangeCurrentPartTitle);

}

//callback function for ChangeCurrentPartTitle

function CB_ChangeCurrentPartTitle( bSucceeded,strExceptionMessage)

{

    if(bSucceeded)

    {

        alert("Title is successfully changed! Need postback to see the complete change!");

    }

    else{

        alert("Error: " + strExceptionMessage);

    }

}

 

Add the webpart class library in the attachment(contains all the demo webparts).

CustomWebPartLib.zip