Archive

Posts Tagged ‘ASP.NET’

SharePoint: How to hide All Site Content links for unprivileged users

June 8th, 2012 No comments

    If you need to hide All Site Content links for unprivileged users, follow the steps shown below.

All Site Content Links

Your application’s master page is to be modified. If you use a built-in master page (for example, v4.master for 2010 or default.master for 2007), I recommend to create a full copy of the built-in one and deploy it. These steps are described in the article SharePoint: How to create a custom master page.

In the master page, locate all places where _layouts/viewlsts.aspx is pointed out. You’ll likely find several controls (for example, a MenuItemTemplate, a SPLinkButton and so on), which render All Site Content links one way or another. These controls can be divided into three groups described below.

The controls exposing the PermissionsString property

The controls expose such opportune property as PermissionsString defining a permission set the user must have in order to see the content the controls provide. For this group of controls PermissionsString is likely set by default to ViewFormPages. That is, if the user has permission to view forms, views and application pages, and enumerate lists, he will have access to All Site Content links displayed by these controls. So, set PermissionsString to ManageWeb in order that the links in question would be visible only for users able to perform administration tasks for the Web site (e.g. content managing, features activating and deactivating and so on). For example, within your copy of v4.master you’ll definitely run into such controls as MenuItemTemplate and ClusteredSPLinkButton declared as follows:

<SharePoint:SiteActions id="SiteActionsMenuMain" runat="server" ...>
  <CustomTemplate>
    <SharePoint:FeatureMenuTemplate ID="FeatureMenuTemplate1" runat="server" ...>
      ...
        <SharePoint:MenuItemTemplate runat="server" 
          id="MenuItem_ViewAllSiteContents"
          Text="<%$Resources:wss,quiklnch_allcontent%>"
          Description="<%$Resources:wss,siteactions_allcontentdescription%>"
          ImageUrl="/_layouts/images/allcontent32.png"
          MenuGroupId="300"
          Sequence="302"
          UseShortId="true"
          ClientOnClickNavigateUrl="~site/_layouts/viewlsts.aspx"
          PermissionsString="ViewFormPages"
          PermissionMode="Any" />
      ...
    </SharePoint:FeatureMenuTemplate>
  </CustomTemplate>
</SharePoint:SiteActions>

The control represents an item in the drop-down Site Actions menu.

View All Site Content Site Actions Menu Item

Another control is ClusteredSPLinkButton

<asp:ContentPlaceHolder id="PlaceHolderQuickLaunchBottom" runat="server">
  ...
  <SharePoint:UIVersionedContent id="PlaceHolderQuickLaunchBottomV4" UIVersion="4" runat="server">
    <ContentTemplate>
      <ul class="s4-specialNavLinkList">
        ...
        <li>
          <SharePoint:ClusteredSPLinkButton
            id="idNavLinkViewAllV4"
            runat="server"
            PermissionsString="ViewFormPages"
            NavigateUrl="~site/_layouts/viewlsts.aspx"
            ImageClass="s4-specialNavIcon"
            ImageUrl="/_layouts/images/fgimg.png"
            ImageWidth=16
            ImageHeight=16
            OffsetX=0
            OffsetY=0
            Text="<%$Resources:wss,quiklnch_allcontent_short%>"
            accesskey="<%$Resources:wss,quiklnch_allcontent_AK%>"/>
        </li>       
      </ul>
    </ContentTemplate>
  </SharePoint:UIVersionedContent>
</asp:ContentPlaceHolder>

The control represents a link in the Quick Launch Bar.

All Site Content Quick Launch Link

So, change the PermissionsString property of the above controls to ManageWeb.

The controls, which are wrapped in a SPSecurityTrimmedControl

The controls lies in SPSecurityTrimmedControl. SPSecurityTrimmedControl renders the content it contains (Html or other controls) depending on the current user’s permissions. It exposes the same PermissionsString property. Thus, set the property to ManageWeb. Within a master page based on v4.master there is a SPLinkButton surrounded by the SPSecurityTrimmedControl, see the following markup:

<asp:ContentPlaceHolder id="PlaceHolderQuickLaunchTop" runat="server">
  <SharePoint:UIVersionedContent UIVersion="3" runat="server">
    <ContentTemplate>
      <h3 class="ms-standardheader">
        ...
        <Sharepoint:SPSecurityTrimmedControl runat="server" PermissionsString="ViewFormPages">
          <div class="ms-quicklaunchheader">
            <SharePoint:SPLinkButton id="idNavLinkViewAll" 
              runat="server" NavigateUrl="~site/_layouts/viewlsts.aspx" 
              Text="<%$Resources:wss,quiklnch_allcontent%>" 
              accesskey="<%$Resources:wss,quiklnch_allcontent_AK%>"/>
          </div>
        </SharePoint:SPSecurityTrimmedControl>
      </h3>
    </ContentTemplate>
  </SharePoint:UIVersionedContent>
</asp:ContentPlaceHolder>

Here the SPLinkButton represents the All Site Content link in the Quick Launch Bar as well, but it’s displayed for applications migrated from SharePoint 2007 with the previous version of UI. So, set the PermissionsString of the surrounding SPSecurityTrimmedControl to ManageWeb.

All other controls

The controls from the last group are not wrapped in SPSecurityTrimmedControl and don’t provide any ability to limit the access to their contents for unprivileged users. What is needed to do is put such controls into SPSecurityTrimmedControl and set the PermissionsString property of the last to ManageWeb. Within your copy of v4.master there are a few such controls:

<Sharepoint:UIVersionedContent runat="server" UIVersion="3">
  <ContentTemplate>
    <Sharepoint:SPNavigationManager id="TreeViewNavigationManager" runat="server" ...>
      <table class="ms-navSubMenu1" ...>
        <tr>
          <td>
            <table class="ms-navheader" ...>
              <tr>
                <td nowrap="nowrap" id="idSiteHierarchy">
                  <SharePoint:SPLinkButton runat="server" id="idNavLinkSiteHierarchy"
                    NavigateUrl="~site/_layouts/viewlsts.aspx"
                    Text="<%$Resources:wss,treeview_header%>" 
                    accesskey="<%$Resources:wss,quiklnch_allcontent_AK%>"/>
                </td>
              </tr>
            </table>
          </td>
        </tr>
      </table>
      ...
    </Sharepoint:SPNavigationManager>
  </ContentTemplate>
</SharePoint:UIVersionedContent>

Here the All Site Content link is presented by SPLinkButton. It’s displayed under Quick Launch, when the previous version of UI is enabled and when the TreeView navigation option is turned on in Site Settings. Wrap the link into SPSecurityTrimmedControl. It may look like the following:

<Sharepoint:UIVersionedContent runat="server" UIVersion="3">
  <ContentTemplate>
    <Sharepoint:SPNavigationManager id="TreeViewNavigationManager" runat="server" ...>
      <SharePoint:SPSecurityTrimmedControl ID="SPSecurityTrimmedControl2" 
          runat="server" PermissionsString="ManageWeb">
        <table class="ms-navSubMenu1" ...>
          <tr>
            <td>
              <table class="ms-navheader" ...>
                <tr>
                  <td nowrap="nowrap" id="idSiteHierarchy">
                    <SharePoint:SPLinkButton runat="server" id="idNavLinkSiteHierarchy"
                      NavigateUrl="~site/_layouts/viewlsts.aspx"
                      Text="<%$Resources:wss,treeview_header%>" 
                      accesskey="<%$Resources:wss,quiklnch_allcontent_AK%>"/>
                  </td>
                </tr>
              </table>
            </td>
          </tr>
        </table>
      </SharePoint:SPSecurityTrimmedControl>
      ...
    </Sharepoint:SPNavigationManager>
  </ContentTemplate>
</SharePoint:UIVersionedContent>

Another control is declared as SPLinkButton too:

<Sharepoint:UIVersionedContent runat="server" UIVersion="4">
  <ContentTemplate>
    <Sharepoint:SPNavigationManager id="TreeViewNavigationManagerV4" runat="server" ...>
      <SharePoint:SPLinkButton runat="server" id="idNavLinkSiteHierarchyV4"
          NavigateUrl="~site/_layouts/viewlsts.aspx" 
          Text="<%$Resources:wss,treeview_header%>" 
          accesskey="<%$Resources:wss,quiklnch_allcontent_AK%>" 
          CssClass="s4-qlheader" />
      ...
    </Sharepoint:SPNavigationManager>
  </ContentTemplate>
</SharePoint:UIVersionedContent>

It’s a link in the treeview under Quick Launch, when the TreeView Navigation option is enabled in Site Settings. It can be modified to the following:

<Sharepoint:UIVersionedContent runat="server" UIVersion="4">
  <ContentTemplate>
    <Sharepoint:SPNavigationManager id="TreeViewNavigationManagerV4" runat="server" ...>
      <SharePoint:SPSecurityTrimmedControl ID="SPSecurityTrimmedControl3" 
          runat="server" PermissionsString="ManageWeb">  
        <SharePoint:SPLinkButton runat="server" id="idNavLinkSiteHierarchyV4"
            NavigateUrl="~site/_layouts/viewlsts.aspx" 
            Text="<%$Resources:wss,treeview_header%>" 
            accesskey="<%$Resources:wss,quiklnch_allcontent_AK%>" 
            CssClass="s4-qlheader" />
      </SharePoint:SPSecurityTrimmedControl>
      ...
    </Sharepoint:SPNavigationManager>
  </ContentTemplate>
</SharePoint:UIVersionedContent>

Summary

Let’s sum up. If you see a control exposing PermissionsString, set the property to ManageWeb. If a control is within a SPSecurityTrimmedControl, set the PermissionsString of the last one to ManageWeb as well. And finally, if it’s just a control, add a new SPSecurityTrimmedControl, place the first one in it and set the ManageWeb permission.

SharePoint: How to create a custom master page

May 26th, 2012 No comments

    Sometimes we need to add a JavaScript to, change layout or make other alterations in a master page that is used by a SharePoint application. Certainly, we can modify built-in master pages, but it’s far away from the best practices. Moreover, all changes we made may be lost after a regular SharePoint update. So, we should make a copy of a particular built-in master page, deploy it through a feature and set as default for our application instead of the built-in one.

Making a full copy

Let’s assume our application is bound to the V4.master (usually located in C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\GLOBAL or C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS), which is default for SharePoint 2010. We make a full copy of the master page and name it MyV4.master.

If you still use SharePoint 2007, your application is likely bound to the default.master (usually located in C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\TEMPLATE\GLOBAL). Create a full copy of it.

Creating a feature

In a new VisualStudio 2010 SharePoint project or in an existent one, we create a feature and call it, for example, MyMasterPageFeature. We add a Module to the project, let’s say MyMasterPage, put the MyV4.master into the Module and modify Elements.xml properly. Below is the possible project’s structure:

Structure of SharePoint Project containing Feature

Where the Elements.xml from the MyMasterPage Module should look like:

<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <Module Name="MyMasterPage" Url="_catalogs/masterpage" Path="" RootWebOnly="FALSE">    
    <File Path="MyMasterPage\MyV4.master" Url="MyV4.master" Type="GhostableInLibrary" />
  </Module>
</Elements>

The manifest of the MyMasterPageFeature in design mode (or the feature.xml in a resultant wsp package) should look like the following:

<?xml version="1.0" encoding="utf-8"?>
<Feature xmlns="http://schemas.microsoft.com/sharepoint/" 
             Title="MySharePointProject Feature" 
             Description="MySharePointProject Feature" 
             Id="a1e26f45-41c5-4581-8b69-8385923ddd11" 
             Scope="Web">
  <ElementManifests>
    <ElementManifest Location="MyMasterPage\Elements.xml" />
    <ElementFile Location="MyMasterPage\MyV4.master" />
  </ElementManifests>
</Feature>

If it’s a new SharePoint project intended for the feature only, we can leave Package.Template.xml and MyMasterPageFeature.Template.xml without any changes.

For a SharePoint 2007 application or one migrated from it the possible project’s structure is shown below. The content of the xml files in question is the same as for 2010 version.

Structure of SharePoint 2007 Project containing Feature

Subclassing MasterPage

On this stage I also suggest to subclass the master page‘s class. In this case you will have more control under the master page‘s rendering; it can be very useful in the future. For this, just add a new cs-file (for example, MyMasterPageClass.cs) to the project and put the following code into the file:

using System;
using System.Web.UI;

namespace MySharePointProject
{
    public class MyMasterPageClass : MasterPage
    {
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
        }
    }
}

In the MyV4.master replace the line

<%@Master language="C#" %>

with the one like this

<%@Master language="C#" 
   Inherits="MySharePointProject.MyMasterPageClass,MySharePointProject, 
   Version=1.0.0.0, Culture=neutral, PublicKeyToken=acf3114812c89a34" %>

Deploying the wsp package and activating the feature

Ok, now we need to build the project, make a wsp package and deploy it. All these actions can be easily done from Visual Studio 2010, or, in case of SharePoint 2007, by means of WSPBuilder. If you prefer deploying wsp packages through PowerShell (SharePoint 2010 only), it’s mentioned here. Or use stsadm.exe for SharePoint 2007.

Make sure that the MyMasterPageClass is registered as safe control in a web.config corresponding to your SharePoint application:

<SafeControls>
	...
	<SafeControl Assembly="MySharePointProject, Version=1.0.0.0, 
                                  Culture=neutral, PublicKeyToken=acf3114812c89a34" 
                          Namespace="MySharePointProject" TypeName="*" 
                          Safe="True" SafeAgainstScript="False" />
	...
</SafeControls>

Now everything is ready to activate the feature, so we do this through the UI, PowerShell (SharePoint 2010 only) or stsadm.exe (SharePoint 2007 only).

Set the custom Master Page as Default

After the feature is activated we need to set the MyV4.master as Default. For already deployed SharePoint applications it can be done through the SharePoint Designer.

Set Default Master Page

For new applications we should provide something like this within our ONET.xml:

<Configurations>
    ...
    <Configuration ID="0" Name="Default" MasterUrl="_catalogs/masterpage/MyV4.master">
    ...
</Configurations>

Since that moment we have our own customizable master page and have a full control under what and how is being rendered.

SharePoint: Code blocks are not allowed in this file

March 23rd, 2012 No comments

    The SharePoint is based on ASP.Net, so all possible ASP.Net errors may easily become apparent in a SharePoint application. The ‘Code blocks are not allowed in this file‘ issue isn’t an exception. To get rid of it we need to enable server side scripts by modifying the web.config file. Specifically we need to locate <PageParserPaths> within web.config and add a proper <PageParserPath> node to it. The following example demonstrates how to allow server side scripts for all pages, which contain the apps virtual folder in their relative paths:

<PageParserPaths>
    <PageParserPath VirtualPath="/apps/*" CompilationMode="Always" 
              AllowServerSideScript="true" IncludeSubFolders="true" />
</PageParserPaths>

After the modification, server side scripts will work for such pages as e.g.

http://myServer/apps/default.aspx
http://myServer/apps/appsubfolder1/MyPage.aspx (*)
http://myServer/apps/appsubfolder2/MyPage.aspx (*)
http://myServer/apps/appsubfolder2/subfolder3/MyPage.aspx (*)
and so on. 

Note that pages urls marked with asterisks (*) are eligible only if IncludeSubFolders is set to true.

To enable server side scripts for certain page, use something like this:

<PageParserPath VirtualPath="/apps/appsubfolder2/subfolder3/MyPage.aspx" 
              CompilationMode="Always" AllowServerSideScript="true" />

SharePoint: Migration of custom upload page derived from UploadPage to SP 2010

March 5th, 2012 No comments

    In our SharePoint 2007 application, there was a custom upload page for a document library. The custom upload page was derived from the Microsoft.SharePoint.ApplicationPages.UploadPage defined in the Microsoft.SharePoint.ApplicationPages.dll. Within application web pages, all links to the upload page were direct, while the page itself was located in the _layouts folder. After migration to the SharePoint 2010, I’ve found out that every request to the custom upload page is transferred to the Uploadex.aspx (located in the _layouts folder as well): the URL in browser corresponds to our upload page, but the content corresponds to the Uploadex.aspx. After a short investigation by means of .Net Reflector I found the reason in the UploadPage base class defined in Microsoft.SharePoint.ApplicationPages.dll, Version=14.0.0.0. Let’s take a look at the OnPreInit method of the UploadPage:

protected override void OnPreInit(EventArgs e)
{
    if (!this.customPage)
    {
        string customUploadPage = base.Web.CustomUploadPage;
        if (!string.IsNullOrEmpty(customUploadPage))
        {
            try
            {
                base.Server.Transfer(customUploadPage);
            }
            catch (Exception)
            {
            }
        }
    }
    base.OnPreInit(e);
}

*Note: this code was added in SharePoint 2010 and wasn’t presented in SharePoint 2007

As we can see, the SharePoint 2010 itself allows to define the CustomUploadPage for a website. If it’s defined, the UploadPage automatically transfers every request to it. Apparently, after migration the CustomUploadPage was somehow set to _layouts/Uploadex.aspx. Another interesting moment here is the customPage boolean field, which acts as marker indicating whether the current page is the custom upload page or not.

Probably, the acceptable solution for our issue would be to set the CustomUploadPage property of the SPWeb object to the URL of our own custom upload page. But, firstly, the problem here is that our upload page derived from the UploadPage doesn’t know that it’s a custom one as the customPage field is set to false by default. Thus, without any code modification, we will have a kind of loop here, because our custom page due to the UploadPage base class will be transferring the request to itself infinitely. Secondly, I don’t want to rely on the CustomUploadPage property, which can be unexpectedly changed. I just want when I click on the direct link to our upload page, this page would be opened without any sudden redirections and transfers.

So, the best solution is to set the customPage field to true within the constructor of our custom upload page. No transfer happens in this case, and we don’t need to deal with the CustomUploadPage at all. In our case It looks like:

public class MyUploadPage : UploadPage
{
    // ...
    public MyUploadPage()
    {
        customPage = true;
    }
    // ...
}

That’s all!

SharePoint: Enhanced ItemPicker

February 28th, 2012 No comments

    I was asked to develop a control based on the ItemPicker control, which, in addition to ability of choosing an external data item through BDC, brings it to client side without page postback. There was the following supposed sequence of actions:

  1. An user chooses a data item in the Picker Dialog;
  2. The identifier of the selected item is sent to the server through an Ajax-like technology;
  3. Using the received identifier, the server fetches the proper data out through BDC and sends it back to the client. By “the proper data” I mean the values of either all fields available in the selected data item or only fields defined in control declaration;
  4. On the client side, the received data is parsed and displayed in UI;

Additionally, the control should be free of bindings to SPField as it should be capable to reside within an independent aspx-page locating in the _layout folder.

*Note: for better understanding of BDC infrastructure, please read the following blog posts: SharePoint: Brief introduction to Business Data Catalog and SharePoint: Understanding BusinessData Column.

So, I developed the required control and made it as reusable as possible. Let’s call the control MyItemPicker (it’s so unusual, isn’t? :)). For sake of simplicity I decided to use the ASP.Net client callbacks applied through the ICallbackEventHandler interface. The ASP.Net client callbacks can be considered as a wrapper on XMLHTTP object. Also the MyItemPicker comprises and uses the standard ItemPicker.

Ok, let’s start with declaration of the control within page:

<MYCC:MyItemPicker id="myItemPicker" runat="server" LobSystemInstanceName="Products" 
EntityName="Product" PrimaryColumnName="Name" ClientCallback="MyClientCallback" 
ClientCallbackError="MyClientCallbackError" CallbackBDCFieldFilter="Price,Producer" />

The significant properties here are

  • LobSystemInstanceName is the name of the Lob System Instance, through which data is provided to pick;
  • EntityName is the type name of data items populating the picker;
  • PrimaryColumnName is the name of the data item field, the value of which is used as a display value;
  • ClientCallback is the name of the JavaScript function, which has to be present within the page. In case of success, the given function accepts and processes the server response containing fetched data;
  • ClientCallbackError is the name of the JavaScript function, which can be within the page and is called, when server fails to fulfill request. This property is optional;
  • CallbackBDCFieldFilter is the comma-separated string containing names of data item fields that should be included in server response. For example, if a BDC Entity has four fields – ID, Name, Price and Producer, you might want to have on client side only two of them – Price and Producer. If the CallbackBDCFieldFilter property is empty or not presented in the declaration, server response contains the values of all available fields of BDC Entity;

The sample of the JavaScript functions, which should be indicated in the ClientCallback and ClientCallbackError properties, is shown below. Note the functions’ signatures.

<script type="text/javascript">

    function MyClientCallback(result, context) {

        alert("Result: " + result);
        
        if (result != null && typeof result != "undefined" && result.length != 0) {
            var res = eval("(" + result + ")");

            alert('Price: '    + res.Price);
            alert('Producer: ' + res.Producer);
            
            // update UI with received data
        }        
    }

    function MyClientCallbackError(result, context) {
        alert('Error: ' + result);            
    }

</script>

The server response looks like

{ 'Price' : '10.00', 
  'Producer' : 'Microsoft Corporation' }

Thus, the response is formatted in the manner to be easily turned into JavaScript object by means of the eval function.

So, it’s time to examine the source code of the MyItemPicker itself.

MyItemPicker Source

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Portal.WebControls;
using System.Web.UI;
using Microsoft.Office.Server.ApplicationRegistry.Infrastructure;
using Microsoft.Office.Server.ApplicationRegistry.MetadataModel;
using Microsoft.Office.Server.ApplicationRegistry.Runtime;
using System.Data;

namespace MyControls
{
    public class MyItemPicker : Control, ICallbackEventHandler
    {
        #region fields & properties
        protected ItemPicker _picker                    = null;
        protected string     _callbackRequestedEntityId = string.Empty;

        public string LobSystemInstanceName  { get; set; }
        public string EntityName             { get; set; }
        public string PrimaryColumnName      { get; set; }

        public string ClientCallback         { get; set; }
        public string ClientCallbackError    { get; set; }

        public string CallbackBDCFieldFilter { get; set; }
        #endregion

        #region public methods
        // Implementation of the ICallbackEventHandler interface
        // Generates response that will be sent to client
        public string GetCallbackResult()
        {
            return GetJSResult();
        }
        // Implementation of the ICallbackEventHandler interface
        // Retrieves and preserves identifier of selected data item sent from client
        public void RaiseCallbackEvent(string eventArgument)
        {
            _callbackRequestedEntityId = eventArgument;
        }
        #endregion

        #region internal methods
        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);
            EnsureChildControls();            
        }

        protected override void CreateChildControls()
        {            
            base.CreateChildControls();

            if (_picker == null)
            {
                _picker = new ItemPicker();                
                _picker.MultiSelect = false;
                _picker.ID = ID + "_ItemPicker";
                try
                {
                    this.SetExtendedDataOnPicker(_picker);
                }
                catch (Exception exception)
                {
                    _picker.ErrorMessage = exception.Message;
                    _picker.Enabled = false;
                }

                this.Controls.Add(_picker);
            }            
        }

	/// <summary>
        /// Initilizes main item picker's properties
        /// </summary>        
        protected virtual void SetExtendedDataOnPicker(ItemPicker picker)
        {            
            ItemPickerExtendedData data = new ItemPickerExtendedData();

            BDCMetaRequest request = new BDCMetaRequest(LobSystemInstanceName, EntityName);
            data.SystemInstanceId  = request.FoundLobSystemInstance.Id;
            data.EntityId          = request.FoundEntity.Id;

            List<uint> list = new List<uint>();
            FieldCollection fields = request.FoundEntity.GetSpecificFinderView().Fields;
            foreach (Field field in fields)
                if (string.Equals(field.Name, PrimaryColumnName, StringComparison.OrdinalIgnoreCase))                
                    data.PrimaryColumnId = field.TypeDescriptor.Id;
                else
                    list.Add(field.TypeDescriptor.Id);

            data.SecondaryColumnsIds = list.ToArray();
            picker.ExtendedData = data;
        }

        protected override void OnPreRender(EventArgs e)
        {
            base.OnPreRender(e);
            AddJSCallbackFunctions();
            AddAdditionalJSFunctions();
        }

        /// <summary>
        /// Generates and adds auxiliary JavaScript functions to the page
        /// </summary> 
        protected void AddAdditionalJSFunctions()
        {
            if (_picker != null)
            {
                _picker.LoadPostData(null, null); // this line is required to force CreateChildControls() and to have HiddenEntityKey created
                Control upLevelDiv = FindControlRecursive(_picker, "upLevelDiv");
                if (upLevelDiv != null)
                {
                    string clearFuncName = "ClearItemPicker_" + ID;
                    string clearFunc =
                        "function " + clearFuncName + "() {" +
                            "var upLevelDiv = document.getElementById('" + upLevelDiv.ClientID + "');" +
                            "if (upLevelDiv != null) {" +
                                "upLevelDiv.innerHTML = '';" +
                                "updateControlValue('" + _picker.ClientID + "');" +
                                "}" +
                            "}";
                    Page.ClientScript.RegisterClientScriptBlock(GetType(), clearFuncName, clearFunc, true);
                }
                
                Control hiddenEntityDisplayTextControl = FindControlRecursive(_picker, "HiddenEntityDisplayText");
                if (hiddenEntityDisplayTextControl != null)
                {
                    string getDisplayTextFuncName = "GetDisplayText_" + ID;
                    string getDisplayTextFunc =
                        "function " + getDisplayTextFuncName + "() {" +
                            "var hiddenEntityDisplayTextControl = document.getElementById('" + hiddenEntityDisplayTextControl.ClientID + "');" +
                            "return hiddenEntityDisplayTextControl != null ? hiddenEntityDisplayTextControl.value : '';" +
                        "}";
                    Page.ClientScript.RegisterClientScriptBlock(GetType(), getDisplayTextFuncName, getDisplayTextFunc, true);
                }
            }            
        }

        /// <summary>
        /// Generates and adds the picker's AfterCallbackClientScript to the page
        /// </summary> 
        protected void AddJSCallbackFunctions()
        {
            if (_picker != null)
            {
                string callbackFunc = null;
                if (!string.IsNullOrEmpty(ClientCallback) && !string.IsNullOrEmpty(ClientCallbackError))
                    callbackFunc = Page.ClientScript.GetCallbackEventReference(this, "arg", ClientCallback, "context", ClientCallbackError, true);
                else
                {
                    if (!string.IsNullOrEmpty(ClientCallback))
                        callbackFunc = Page.ClientScript.GetCallbackEventReference(this, "arg", ClientCallback, "context", true);
                }
                if (!string.IsNullOrEmpty(callbackFunc))
                {
                    _picker.LoadPostData(null, null); // this line is required to force CreateChildControls() and to have HiddenEntityKey created

                    Control pickerEntityKeyHidden = FindControlRecursive(_picker, "HiddenEntityKey");
                    if (pickerEntityKeyHidden != null)
                    {
                        string clientFuncName = "GetBdcFieldValuesAsync_" + ID;
                        string clientFunc =
                            "function " + clientFuncName + "(context)" +
                            "{" +
                                "var pickerEntityKeyHidden = document.getElementById('" + pickerEntityKeyHidden.ClientID + "');" +
                                "if (pickerEntityKeyHidden != null)" +
                                "{" +
                                    "var arg = pickerEntityKeyHidden.value;" +
                                    callbackFunc + ";" +
                                "}" +
                            "}";
                        Page.ClientScript.RegisterClientScriptBlock(GetType(), clientFuncName, clientFunc, true);
                        _picker.AfterCallbackClientScript = clientFuncName + "('" + ID + "');";
                    }
                }
            }
        }

        /// <summary>
        /// Makes request to external data source and returns json-result
        /// </summary>         
        protected string GetJSResult()
        {
            string res = string.Empty;

            try
            {
                if (!string.IsNullOrEmpty(_callbackRequestedEntityId))
                {
                    Dictionary<string, byte> bdcFieldFilter = GetBDCFieldFilter(CallbackBDCFieldFilter);
                    
                    BDCRequestById request = new BDCRequestById(LobSystemInstanceName, EntityName, _callbackRequestedEntityId);

                    StringBuilder sb = new StringBuilder();
                    sb.Append("{");
                    foreach (Field field in request.FoundEntityInstance.ViewDefinition.Fields)
                    {
                        if (bdcFieldFilter.Count == 0 || bdcFieldFilter.ContainsKey(field.Name))
                        {
                            if (sb.Length > 1)
                                sb.Append(", ").AppendLine();
                            sb.Append("'").Append(field.Name).Append("' : ");
                            sb.Append("'").Append(Convert.ToString(request.FoundEntityInstance.GetFormatted(field))).Append("'");
                        }
                    }
                    sb.Append("}");                    

                    res = sb.ToString();
                }
            }
            catch (Exception ex)
            {
                // write error to log
            }

            return res;
        }

        /// <summary>
        /// Parses the user defined list of bdc fields, the values of which should be retrieved
        /// </summary>        
        protected static Dictionary<string, byte> GetBDCFieldFilter(string commaSeparatedBdcFields)
        {
            Dictionary<string, byte> res = new Dictionary<string, byte>(StringComparer.OrdinalIgnoreCase);

            if (!string.IsNullOrEmpty(commaSeparatedBdcFields))
            {
                string[] bdcFields = commaSeparatedBdcFields.Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries);
                foreach (string field in bdcFields)
                    res.Add(field, 0);
            }

            return res;
        }
        #endregion
    }
}

You probably noticed that the functions AddJSCallbackFunctions and AddAdditionalJSFunctions generate and add some JavaScript functions to the page. The exact names of these JavaScript functions depend on the id attribute defined in control declaration. For example, if control id is “myItemPicker“, the functions’ name will be GetBdcFieldValuesAsync_myItemPicker, ClearItemPicker_myItemPicker and GetDisplayText_myItemPicker.

Let’s take a look at the functions. The main function is GetBdcFieldValuesAsync_myItemPicker, which extracts the encoded id of selected item from ItemPicker and then makes the Ajax-like client callback to the server. The rest two functions are auxiliary, they are not used by MyItemPicker directly, but they are very useful for developing an interaction between user and MyItemPicker. As their names imply, the ClearItemPicker_myItemPicker clears the ItemPicker, and the GetDisplayText_myItemPicker returns the text displayed to user in ItemPicker. The listing below demonstrates the functions within page:

<script type="text/javascript">

    function GetBdcFieldValuesAsync_myItemPicker(context) {
        var pickerEntityKeyHidden = document.getElementById('myItemPicker_ItemPicker_HiddenEntityKey');
        if (pickerEntityKeyHidden != null) {
            var arg = pickerEntityKeyHidden.value;
            WebForm_DoCallback('myItemPicker', arg, ClientCallback, context, ClientCallbackError, true);
        }
    }

    function ClearItemPicker_myItemPicker() {
        var upLevelDiv = document.getElementById('myItemPicker_ItemPicker_upLevelDiv');
        if (upLevelDiv != null) {
            upLevelDiv.innerHTML = '';
            updateControlValue('myItemPicker_ItemPicker');
        }
    }

    function GetDisplayText_myItemPicker() {
        var hiddenEntityDisplayTextControl = document.getElementById('myItemPicker_ItemPicker_HiddenEntityDisplayText');
        return hiddenEntityDisplayTextControl != null ? hiddenEntityDisplayTextControl.value : '';
    }
</script>

The SetExtendedDataOnPicker and GetJSResult methods of MyItemPicker employ the classes BDCMetaRequest and BDCRequestById that are described in my post SharePoint: How to get value from BDC.

The FindControlRecursive method is mentioned in another my post.