Wednesday, November 09, 2011

ASP.Net Custom Menu User Control


ASP.Net provides Menu Control to display top navigation. But the problem with that Menu is its complexity while rendering on the Page. It renders the Menu as html table and each menu item is an individual table.
I have developed a user control to render the top menu using UL and LI tags. This control uses a Repeater to render the menu items. For now I have developed it to render Top level menu items.
CustomMenu.Ascx

<%@ Control Language="C#" AutoEventWireup="true" CodeFile="CustomMenu.ascx.cs" Inherits="UserControls_CustomMenu" %>
  <ul id="menu">
  <asp:Repeater ID="rptMainNav" runat="server" OnItemDataBound="rptMainNav_OnItemDataBound" >
    <ItemTemplate>
    <li id="menuItem" runat="server"  >
        <a href="#" id="menuLink" runat="server" ></a>
    </li>
    </ItemTemplate>
  </asp:Repeater>
  </ul>
  <asp:SiteMapDataSource ID="SiteMapDataSource" runat="server" ShowStartingNode="false" />

CustomMenu.Ascx.cs

using System;
using System.Collections.Generic;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;

public partial class UserControls_CustomMenu : System.Web.UI.UserControl
{
    protected void Page_Load(object sender, EventArgs e)
    {
        rptMainNav.DataSourceID = "SiteMapDataSource";
        rptMainNav.DataBind();
    }

    protected void rptMainNav_OnItemDataBound(object sender, RepeaterItemEventArgs e)
    {
        SiteMapNode siteMapNode = e.Item.DataItem as SiteMapNode;
        if (siteMapNode != null && (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem))
        {
            HtmlAnchor menuLink = e.Item.FindControl("menuLink") as HtmlAnchor;
            HtmlControl menuItem = e.Item.FindControl("menuItem") as HtmlControl;
            if (menuLink != null)
            {
                menuLink.HRef = siteMapNode.Url;
                menuLink.InnerHtml = siteMapNode.Title;

                if (BelongsToThisNode(SiteMap.CurrentNode, siteMapNode))
                {
                    menuItem.Attributes.Add("class", "selected");
                }

            }
        }
    }

    private bool BelongsToThisNode(SiteMapNode siteMapNode,SiteMapNode parentNode)
    {
        if (siteMapNode.Url == parentNode.Url)
            return true;
        if (siteMapNode.ParentNode == null || string.IsNullOrEmpty(siteMapNode.ParentNode.Url))
            return false;
        if (siteMapNode.ParentNode != null && siteMapNode.ParentNode.Url == parentNode.Url)
            return true;

        return BelongsToThisNode(siteMapNode.ParentNode, parentNode);
    }
}

Web.sitemap

<?xml version="1.0" encoding="utf-8" ?>
<siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" >
    <siteMapNode url="" title=""  description="">
      <siteMapNode url="~/Default.aspx" title="Home"  description="" />
      <siteMapNode url="~/AboutUs.aspx" title="About Us"  description="" >
        <siteMapNode url="~/Leadership.aspx" title="Leadership" description="" ></siteMapNode>
      </siteMapNode>
      <siteMapNode url="~/Services.aspx" title="Services"  description="" />
      <siteMapNode url="~/ContactUs.aspx" title="Contact Us"  description="" >
        <siteMapNode url="~/Career.aspx" title="Leadership" description="" ></siteMapNode>
      </siteMapNode>
    </siteMapNode>
</siteMap>



Register this control on Master page and use it instead of the Menu Control. It is easy to  style this control  using css. The below lines of code
if (BelongsToThisNode(SiteMap.CurrentNode, siteMapNode))
                {
                    menuItem.Attributes.Add("class", "selected");
                }
Will add a css class to the selected Menu Item to display it in different style.

Thursday, October 13, 2011

Using Client Object Model, Uploading a new document to a document library which supports different type of Content Types

Sometimes we need to upload new document whose metadata is coming from a csv file. Consider that if the document library supports custom content types then before adding the item we need to know which field columns from the csv file are need to be added to the item. The csv file contains the information of Content Type and all possible Fields as columns in the csv file. Then we need to get the corresponding content type object from the content type string present in the csv file and get all the fields that belong to that content type. Then add the data to those fields only.
Below is the sample code.
We are passing  the parameters
  • Metadat List ,
  • Binary content of the file in byte array,
  • DataRow which contains the metadata information of that document.We are getting this datarow from the DataTable which is built from the csv file,
  •  Item Url :Docuemt file name in our case its a download link from another sharepoint server.
  • Contet Type.
I have written another blog post  related to this blog post that contains some of the methods descriptions like GetMappedListItem
/// <summary>
/// Uploads the document to the document library with metadata
/// </summary>
/// <param name="metaDataList"></param>
/// <param name="content"></param>
/// <param name="dataRow"></param>
/// <param name="itemURL"></param>
/// <param name="contentType"></param>
private void UploadDocument(FieldCollection metaDataList,byte[] content,DataRow dataRow,string itemURL,string contentType)
{
  ClientContext ctx = new ClientContext(targetSiteUrl);
  Web web = ctx.Web;
  FileCreationInformation newFile = new FileCreationInformation();
  newFile.Content = content;
  newFile.Url = itemURL.Substring(itemURL.LastIndexOf("/") + 1);
  List docs = web.Lists.GetByTitle(targetListName);
  ctx.Load(docs);
  ctx.ExecuteQuery();
  Microsoft.SharePoint.Client.File uploadFile = docs.RootFolder.Files.Add(newFile);
  ctx.Load(uploadFile);
  ctx.ExecuteQuery();
  ListItem item = uploadFile.ListItemAllFields;
  //Set the metadata     
  item["Title"] = newFile.Url.Substring(0,newFile.Url.IndexOf("."));

  ContentType targetDocumentSetContentType = GetContentType(ctx,docs,contentType);
  item["ContentTypeId"] = targetDocumentSetContentType.Id.ToString();
  item.Update();
  ctx.ExecuteQuery();
           
  foreach (Field field in metaDataList)
  {
   if (!field.Hidden && !field.ReadOnlyField)
      {
        if (field.InternalName != "FileLeafRef" && field.InternalName != "ContentType" && !field.ReadOnlyField && field.Title != "Title")
          {
           if(field.FieldTypeKind==Microsoft.SharePoint.Client.FieldType.Lookup)
             {
               FieldLookup lookupField =field as FieldLookup;
               ListItem mappedListItem = null;

              if (lookupField != null)
              {
                mappedListItem = GetMappedListItem(lookupField, dataRow[field.Title].ToString());
              }
              if (mappedListItem != null)
              {
               FieldLookupValue lookupValue = new FieldLookupValue();
               lookupValue.LookupId = mappedListItem.Id;
               item[field.InternalName] = lookupValue;
              }
           }
           else
             item[field.InternalName] = dataRow[field.Title];
        }
    }
  }
           
 item.Update();
 ctx.ExecuteQuery();
 ctx.Dispose();
 }

Below is the code to Get the content types

/// <summary>
/// Gets the Content Type Object of the given content type string
/// </summary>
/// <param name="ctx"></param>
/// <param name="docs"></param>
/// <param name="contentType"></param>
/// <returns></returns>
private ContentType GetContentType(ClientContext ctx,List docs,string contentType)
{
  ContentTypeCollection listContentTypes = docs.ContentTypes;
  ctx.Load(listContentTypes, types => types.Include
           (type => type.Id, type => type.Name,
             type => type.Parent));
  var result = ctx.LoadQuery(listContentTypes.Where(c => c.Name == contentType));
  ctx.ExecuteQuery();
  ContentType targetDocumentSetContentType = result.FirstOrDefault();
  return targetDocumentSetContentType;
}

Adding a new document Item whose metadata contains lookup fields using Client Object Model

It is easy to add new document item using client object model. In our case we are having all metadata corresponding to the item in a csv file and the csv file contains the look up filed value.
So we have the metadata including the lookup value, but whenever you try to execute the below code you will end up with an exception "Invalid data has been used to update the list item. The field you are trying to update may be read only".

I have written another blog post  related to this blog post that contains some more information
ClientContext ctx = new ClientContext(targetSiteUrl);
Web web = ctx.Web;
FileCreationInformation newFile = new FileCreationInformation();
newFile.Content = content;
newFile.Url = itemURL.Substring(itemURL.LastIndexOf("/") + 1);
List docs = web.Lists.GetByTitle(targetListName);
ctx.Load(docs);
ctx.ExecuteQuery();
Microsoft.SharePoint.Client.File uploadFile = docs.RootFolder.Files.Add(newFile);
ctx.Load(uploadFile);
ctx.ExecuteQuery();
ListItem item = uploadFile.ListItemAllFields;
//Set the metadata     
item["Title"] = newFile.Url.Substring(0,newFile.Url.IndexOf("."));
item["Lookup Field"]=someLookupValue;  //this is the cause of the error.     

ContentType targetDocumentSetContentType = GetContentType(ctx,docs,contentType);
item["ContentTypeId"] = targetDocumentSetContentType.Id.ToString();
item.Update();
ctx.ExecuteQuery();
           

Then you don’t know what to do and which is causing the error. As shown in the below code, the highlighted line is causing the error. We can only set  FieldLookupValue
Object to the Fields of type Lookup Field. Instead of setting the FieldLookupValue if we set the text filed to it, it will throw the above error. So the solution is to set the  FieldLookupValue object to the lookup field.

The code follows below.
if (lookupField != null)
{
      mappedListItem = GetMappedListItem(lookupField, dataRow[field.Title].ToString());
}
if (mappedListItem != null)
{
     FieldLookupValue lookupValue = new FieldLookupValue();
     lookupValue.LookupId = mappedListItem.Id;
     item[field.InternalName] = lookupValue;
}


If you observe the above code, we are setting the LookupId only, we cannot set the LookupValue field as it is read only filed. But I have Lookup Value with me in the csv file. Then how can I get the ID value of the corresponding lookup value from the base list. The below code solves our problem.
Steps included in the code are  
  1.  Get the Web Object based on the LookupWebId present in the FieldLookup object.
  2.  Get the Look up list from the web based on the List ID present in the FieldLookup object.
  3.  Get the List item with its id based on the Lookup Value we have. 
 /// <summary>
        /// Gets corresponding Lookup field's base list item
        /// </summary>
        /// <param name="lookupField"></param>
        /// <param name="lookupValue"></param>
        /// <returns></returns>
        private ListItem GetMappedListItem(FieldLookup lookupField,string lookupValue)
        {
            string mappingField = lookupField.LookupField;

            ClientContext mappedWebContext = new ClientContext(tbRootWebUrl.Text);

            Web web = mappedWebContext.Web;
            Web mappedWeb = GetLookupWeb(lookupField, web);
            //if(mappedWeb.Id==lookupField.LookupWebId)
            if (mappedWeb == null)
                return null;
            mappedWebContext.Load(mappedWeb, p => p.Id, p => p.Title);
            List lookupList = mappedWeb.Lists.GetById(new Guid(lookupField.LookupList));
            mappedWebContext.Load(lookupList);
            mappedWebContext.ExecuteQuery();

            ListItemCollection libListItems = lookupList.GetItems(CamlQuery.CreateAllItemsQuery());

            mappedWebContext.Load(libListItems, items => items.Include(
                    itemlookup => itemlookup.Id,
                    itemlookup => itemlookup[mappingField]));
            mappedWebContext.ExecuteQuery();
            foreach (ListItem mappedItem in libListItems)
            {
                if (mappedItem[mappingField].ToString() == lookupValue)
                {
                    mappedWebContext.Dispose();
                    return mappedItem;
                }
            }
            mappedWebContext.Dispose();
            return null;
        }

        private Web GetLookupWeb(FieldLookup lookupField,Web web)
        {
            ClientContext mappedWebContext = web.Context as ClientContext;

            Web mappedWeb = null;
            mappedWebContext.Load(web, webitm => webitm.Id, webitm => webitm.Title, webitm => webitm.ServerRelativeUrl);
            mappedWebContext.ExecuteQuery();
            if (web.Id == lookupField.LookupWebId)
            {
                return web;
            }
            WebCollection webCollection = web.Webs;
            mappedWebContext.Load(webCollection, webitems => webitems.Include(itemweb => itemweb.Title, itemweb => itemweb.Id,itemweb=>itemweb.ServerRelativeUrl));
            mappedWebContext.ExecuteQuery();
            foreach (Web webitem in webCollection)
            {
                if (webitem.Id == lookupField.LookupWebId)
                    return webitem;
                mappedWeb=GetLookupWeb(lookupField,webitem);
                if (mappedWeb != null)
                    break;
            }
            return mappedWeb;
        }

Wednesday, August 24, 2011

SharePoint 2010 Managed Client Object Model. How to Get Site Properties?


Silverlight applications use this Managed Client Object Model .In the Client Object Model, each line of statement that requires some site information is a request to the server, the server responds to the request and the Response is packed into a Client Object which is easy to use. In Client Object Model we cannot get all the required data on a single request as the data is too big SharePoint uses On Demand serve request methodology for the Client Object Model. The developer should take care when getting data from the server.He should request minimal information he needs from the Server using LINQ filter queries.

Below is the code to Get Site Properties.

PropertyValues pValues = null;
string siteCollectionUrl = string.Empty;  // stores sitecollection url;
string siteUrl = http://yoursitename ; // site url, may be site under a sitecollection;
string PropertyIDValue = string.Empty;



private void LoadSite()
        {
            ClientContext ctx = new ClientContext(siteUrl);
            siteCollection = ctx.Site;                     
            ctx.Load(siteCollection, s => s.Url);
            ctx.ExecuteQueryAsync(GetSiteRequestSucceeded, RequestFailed);
        }

        private void GetSiteRequestSucceeded(object sender, SP.ClientRequestSucceededEventArgs e)
        {
            Dispatcher.BeginInvoke(() =>
            {
                siteCollectionUrl = siteCollection.Url;
                ClientContext ctx = new ClientContext(siteUrl)
                currentWeb = ctx.Web;
                ctx.Load(currentWeb, w => w.Title, w => w.ServerRelativeUrl, w => w.AllProperties);
                ctx.ExecuteQueryAsync(GetWebRequestSucceeded, RequestFailed);
            });
        }

        private void GetWebRequestSucceeded(object sender, SP.ClientRequestSucceededEventArgs e)
        {
            Dispatcher.BeginInvoke(() =>
            {
                pValues = currentWeb.AllProperties;
         ClientContext ctx = new ClientContext(siteUrl) ;
         ctx.Load(pValues);
                context.ExecuteQueryAsync(GetPropsSuccess, RequestFailed);
                }
            });
        }

        private void GetPropsSuccess(object sender, SP.ClientRequestSucceededEventArgs e)
        {
            Dispatcher.BeginInvoke(() =>
            {
              this.PropertyIDValue = (string)pValues.FieldValues["propertyid"];
             
            });
        }

       

private void RequestFailed(object sender, SP.ClientRequestFailedEventArgs e)
        {
            Dispatcher.BeginInvoke(() =>
            {
                MessageBox.Show("Error occured:  " + e.Exception);
            });
        }