Salı, Kasım 16, 2010

SharePoint 2010 - Send Notification on Item Approved/Rejected (when Content Approval Status is changed)

In SharePoint 2010 new improvements are made for better event receiving management. Few new event handlers are added for site, web, list, listitems etc. However, One thing that I think badly needed was the content approval events  for list items.

Nowadays content approval has become an integral part of content management system. Requirements has come up to do more work on content approval/reject. But unfortunately, SharePoint list/library doesn’t have events like ContentApproved, ContentRejected, ContentRequestedForReview so that user can tap the events to do their own work on content approval status changes. So it seems we need to do a lot of works manually to send notifications on content approval status changes.

Problem: Approving Status change events Missing

One of my client wanted to get notification on the following scenarios:

1. On Item Add/Update: If a user edit an item and item goes to pending status as the item needs approval, the approving teams need to be notified that an item is waiting for their approval.

2. On Item Approved: If the approving team approve the item,the user who added/updated the item needs to be notified.

3. On Item rejected: If the approving team reject the item, the user who added/updated the item needs to be notified with reasons why the item rejected.

But the SharePoint Object Model doesn’t have the extensibility at this point where approving status changes.

Why Approval Status change event missing?

The best solution would be if SharePoint team would provide us with out-of-box events for content approval. In that case, two events would be suffice. The events might be : ContentApprovingStatusChanging and ContentApprovingStatusChanged and the event argument’s AfterProperties and BeforeProperties values could be filled with the the old value and new value of Content Approving Status field value. However, one may argue that ItemAdded/ItemUpdate events are similar like Content Approval events. So when user add/edit an item and as part of the add/edit if approval status field get updated then which events to fire? ItemAdded/ItemUpdate or content approval events. Hmm.. maybe there’s complexities with the new content approval events and SharePoint team has not added the new content approval events.

Resolution: Use ItemAdded, ItemUpdating and ItemUpdated events to keep track of approval status changing

So consider now the problem we’re going to talk about. We need a notification system where we need to send notification to the approver or user (who is waiting for approval) on approval status change. We’ll develop a list item event receiver for ItemAdded and ItemUpdated events. When a new item will be added it’s easy to identify item status and if the status is pending then we can send notification to all people in the approving team. But when an Item is updated, you need to keep track of if the Approving status field value is changed, if so then u need to send notification. However, you can only get the old approval status field value in ItemUpdating event, but you don’t want to send notification in ItemUpdating. So it’s safe to send notification in ItemUdated event but in ItemUpdated event you’ll not get the old value. You can access the old value in ItemUpdating. So here’s the deal:

    Create a new field say OldStatus in the list. This field will be used to keep track of if the approval status field value has been changed. In ItemUpdating event, set the current approval status (before updating) to OldStutus field. In ItemUpdated, compare the current status to OldStatus field value and if they are not same then it’s for sure that the approval status has been changed. So send notification.

So let’s go with the steps. First we need an List Event Receiver that will listen three events of the list: ItemAdded, ItemUpating and ItemUpdated. You need to send notification on two events: ItemAdded and ItemUpdated. However, we need to hook the event ItemUpdating to know whether the approval status is going to be changed

Create a List Event Receiver to send notification

    Send notification on Item Added

On Item added event, check if the item status is pending. If so then send notification. The following code snippet may give you the gist.

    public override void ItemAdded(SPItemEventProperties properties)
    {
    const string approvalStatusFieldInternalName = "_ModerationStatus";

    var list = properties.List;
    var approvalStatuField =
    list.Fields.GetFieldByInternalName(approvalStatusFieldInternalName);
    var approvalStatusFieldValue = properties.ListItem[approvalStatuField.Id];
    var approvalStatus = (approvalStatusFieldValue == null) ? string.Empty :
    approvalStatusFieldValue.ToString();
    if (approvalStatus == "Pending")
    {
    //SendNotification()
    }
    }




    Keep track of the approval status field value (before updated) on Item Updating event




    I’m assuming that you have a field OldStatus where I’ll keep the approval status field value which is going to be changed. I’ll explain later in this post how to automatically add the field in list. But for now just take for granted that you have a field OldStatus in your list of type string. The following code show how to keep the approval status (before update) value in OldStatus field in ItemUpdating Event.




    public override void ItemUpdating(SPItemEventProperties properties)
    {
    const string approvalStatusFieldInternalName = "_ModerationStatus";

    var list = properties.List;
    var approvalStatuField =
    list.Fields.GetFieldByInternalName(approvalStatusFieldInternalName);
    var approvalStatusFieldValue = properties.ListItem[approvalStatuField.Id];
    var approvalStatusValue = (approvalStatusFieldValue == null) ? string.Empty :
    approvalStatusFieldValue.ToString();

    if (string.IsNullOrEmpty(approvalStatusValue)) return;

    EventFiringEnabled = false;
    properties.ListItem["OldStatus"] = approvalStatusValue;
    properties.ListItem.SystemUpdate(false);
    EventFiringEnabled = true;
    }




    Check the OldStatus field value and current approval status value to know if the approval status changed.</LI>





Item updated is fried once the update is done. So we’ll get the updated value of Approval Status. But fortunately, we have kept the old value of Approval Status field in OldStatus field during ItemUpdating event as shown in step 2.




public override void ItemUpdated(SPItemEventProperties properties)
{
const string approvalStatusFieldInternalName = "_ModerationStatus";
var list = properties.List;


var approvalStatusField =
list.Fields.GetFieldByInternalName(approvalStatusFieldInternalName);
var currentStatuFieldValue = properties.ListItem[approvalStatusField.Id];
var currentStatus = (currentStatuFieldValue == null) ? string.Empty :
currentStatuFieldValue.ToString();

var oldStatusFieldValue = properties.ListItem["OldStatus"];
var oldStatus = (oldStatusFieldValue == null) ?
string.Empty : oldStatusFieldValue.ToString();

if (string.IsNullOrEmpty(oldStatus) && oldStatus != currentStatus)
{
//SendNotification();
}
}





 




Create a feature receiver to attached List Event Receiver and to create field OldStatus



Finally We need an feature receiver (not list event receiver) which will do two works: Attached our list event receiver to a list and create a field OldStatus in the list.




    FeatureActivating Event





In FeatureActivating you need to check first if the event is already registered. If not registered then register the event. Also make sure the list has OldStatus field. In the code below, listNeedsToAttachedNotitificationReceivers is array of list names which needs to attach the event receivers.




public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
string[] listNeedsToAttachedNotitificationReceivers = { "Product", "Order" };
var myAssemblyName = "MyProject.SharePoint";

foreach (var listName in listNeedsToAttachedNotitificationReceivers)
{
var web = properties.Feature.Parent as SPWeb;
var list = web.Lists[listName];
SPEventReceiverDefinitionCollection
spEventReceiverDefinitionCollection = list.EventReceivers;
if (!IsEventReceiverAlreadyAttached
(spEventReceiverDefinitionCollection, myAssemblyName))
{
//Attach three ItemAdded, ItemUpdating and itemUpdated event receivers
SPEventReceiverType eventReceiverType = SPEventReceiverType.ItemAdded;
spEventReceiverDefinitionCollection.Add(eventReceiverType,
Assembly.GetExecutingAssembly().FullName,
"MyProject.SharePoint.Receivers.ListItem.ContentApprovalEventHandler");
eventReceiverType = SPEventReceiverType.ItemUpdated;
spEventReceiverDefinitionCollection.Add(eventReceiverType,
Assembly.GetExecutingAssembly().FullName,
"MyProject.SharePoint.Receivers.ListItem.ContentApprovalEventHandler");
eventReceiverType = SPEventReceiverType.ItemUpdating;
spEventReceiverDefinitionCollection.Add(eventReceiverType,
Assembly.GetExecutingAssembly().FullName,
"MyProject.SharePoint.Receivers.ListItem.ContentApprovalEventHandler");
list.Update();
}
EnusureOldStatusFieldExists(list);
}
}

private static bool I
sEventReceiverAlreadyAttached(SPEventReceiverDefinitionCollection
spEventReceiverDefinitionCollection, string myAssemblyName)
{
bool eventReceiverAttached = false;
for (int i = 0; i < spEventReceiverDefinitionCollection.Count; i++)
{
if (spEventReceiverDefinitionCollection[i].Assembly.Contains(myAssemblyName))
{
eventReceiverAttached = true;
break;
}
}
return eventReceiverAttached;
}

private static void EnusureOldStatusFieldExists(SPList list)
{
var field = list.Fields.TryGetFieldByStaticName("OldStatus");
if (field == null)
{
list.Fields.Add("OldStatus", SPFieldType.Text, false);
list.Update();
}
}





    Feature Deactivating Event





In feature deactivating event, unregister the list event receivers. If you want you can delete the OldStatus field. However I have not deleted the field in the code below:




public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
{
string[] listNeedsToAttachedNotitificationReceivers = { "Product", "Order" };
var myAssemblyName = "MyProject.SharePoint";


foreach (var listName in listNeedsToAttachedNotitificationReceivers)
{
var receiversToRemove = new List<Guid>();
var web = properties.Feature.Parent as SPWeb;
var list = web.Lists[listName];
SPEventReceiverDefinitionCollection
spEventReceiverDefinitionCollection = list.EventReceivers;
for (int i = 0; i < spEventReceiverDefinitionCollection.Count; i++)
{
if (spEventReceiverDefinitionCollection[i].Assembly.Contains(myAssemblyName))
{
receiversToRemove.Add(spEventReceiverDefinitionCollection[i].Id);
}
}
if (receiversToRemove.Count > 0)
{
foreach (var guid in receiversToRemove)
{
list.EventReceivers[guid].Delete();

}
list.Update();
}
}
}





 




How it works all together?


It’s a bit complex huh? oK, let’s me explain how it works.




    The feature receiver needs to be activated first. The feature receiver attached the event receiver to list and create a string field OldStatus in the list. Next if an item is added to the list, the listItem event gets fired and if the item status is pending (means needs approval) then send notification. If an existing item is edited and saved then ItemUpdating event is fired. This is the event where the item is not yet saved. So I have put the current approval status in the OldStatus field. In ItemUpated event I have compared the OldStatus and current status field value. If the valued doesn’t match then the approval status is changed and we need to send the notification.


Pazartesi, Kasım 15, 2010

Move SharePoint Site/Web From One Development Server to Another

Sometimes, we need to move the site from one server to another, usually in development environment. In that case its required few steps to make sure you have moved your site in new server. I have successfully move my sites and I’m pointing here the exact steps that worked for me.

    Backup the site or export the web

First of all you need to take backup of the site (if you want to move site collection). If you want to move a single site then export the web contents. For site backup use the following command:

Backup-SPSite -Identity http://MySiteColllection.com -Path "C:\Backup\BackupFile.bak" -Confirm:$false

For exporting web use the following command.

Export-SPWeb –Identity http://MySiteCollection.com/myweb –Path “C:\Backup\BackupFile.cmp” –Confirm:$False

For more on these commands follow MSDN link.

    Create Dummy site or web on the destination server to overwrite

Before restoring or importing site/web you need to create a dummy site/web on the destination server. In case of site restore, create a new site collection. In case of web import, create a site with the same name and template used in source server’s web.

    Deploy code on the server

If possible, then deploy the sharepoint solution (generated from your developer code in Visual Studio) on the destination server. In some case the restore/import operation looks for features/resources on the site/web that is referenced in the backup/export file. So deploying the code on the server make sure the code (that will be finally used in the restored site/web) is on the destination server. If your code has web level feature/resources then use the root web as the default one to deploy code.

    Restore or import the site/web

Finally you are ready to restore the site. Run the following command to restore a site collection.

Restore-SPSite -Identity http://MyNewSiteCollection.com -Path "C:\Backup\BackupFile.bak" -Confirm:$false -Force

Run the following import command to import site.

Import-SPWeb –Identity http://MyNewSieCollection.com/MyNewWeb –Path “C:\Backup\BackupFile.cmp” –Overwrite –Confirm:$False

For more on these commands follow MSDN link.

    Check user Permissions

If you restored a site collection then you need to change site collection administrator as the restored site is still pointing to the old site collection administrators (and maybe the administrator is invalid in the new server). From SharePoint central administration site change the site collection administrator to proper one.

Few points to notice

When you move the site/web few points need to remember:

    If you restore just a web then your import operation may fail saying a feature is not installed on the web/site. This may happen in case where the source site /web’s backup file is referring a resource (say feature) that is not available on the destination server. In this case, make sure your code is deployed properly on the destination server. If you get the error for a particular web, then create a dummy web with the same name as on the source server, on the destination server and then deploy the code on the web and finally try to restore the web. In case of web import, make sure you have created the new web on the destination server with the same template. You can’t overwrite a web on destination server with a source one that is using different template.

Pazar, Kasım 14, 2010

SharePoint Error: The exported site is based on the template STS#1 but the destination site is based on the template STS#0

Today I was trying to export one of my site from one Virtual machine to another. First I took a export of the web from my source server and then I tried to import the content in the destination server. And then the import failed with the error “The exported site is based on the template STS#1 but the destination site is based on the template STS#0”. I couldn’t remember the template with which I created the source site but I took for granted from the error message that the source and destination web are not created from the same template. After googling I finally found import will only work if source and destination web are created from the same template. In my case the source site was created from Blank Site template (STS#1) but the destination site was created from Team site template (STS#0).

So I deleted the destination site and recreated with the source template and the import was successful. For full lists of web site templates you can followAravindhan’s  posts.

Cumartesi, Kasım 13, 2010

SharePoint 2010 - Rename Site Url

Sometimes in development environment and even in production you may need to rename a site url. For example you have planned a site with URLwww.mysite.com in Development or in Production. Later you have decided to rename the site url to www.yoursite.com. There’s no shortcut way to do that in SharePoint. You need to delete the existing web application for url www.mysite.com and create a new one with url www.yoursite.com.

1. Backup the site collection first

Run the following powershell command to backup site.

Backup-SPSite –Identity SiteUrl –Path BackupLocation

2. Delete the existing site from central admin.

Navigate to central admin and delete the web application including database and IIS site as shown below:

3. Create the a new site with new Url (say http://www.yoursite.com)

Now create a new web application with the new url in host header as shown below:

4. Restore the backup on the new site

Finally restore the backup taken on step 1 overwriting the new site collection. Run the following command on powershell command

Restore-SPSite –Identity SiteUrl –Path BackupFilePath -Force

Cuma, Kasım 12, 2010

Linq to SharePoint

SharePoint 2010 has added new Linq extension called Linq-to-SharePoint similar like Linq-to-Sql. In Linq to Sql, when you Visual Studio generate classes based on your database schema, under the hood a tool called SqlMetal is used. Though VS generates classes for you, you can use SqlMetal outside of VS for your own purpose. Linq to SharePoint is new extension which allows to generate DataContext (having classes representing lists) based on your sharepoint lists using SPMetal and you can use the DataContext for manipulating SharePoint list in an object-oriented fashion. So you don’t need to worry about CAML or SharePoint Object Model. The steps to work with Linq-to-SharePoint is easy. Let’s dig it deeper.

    Generate Linq-to-SharePoint DataContext class: To use Linq to SharePoint you need to use SPMetal to generate Linq-To-SharePoint class. You can find the SPMetal in the location “C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\BIN”. You can run the command from command prompt. For example the following command will generate Linq-to-SharePoint DataContext class for site ‘http://localhost’ and put the code inC:\MyClass.cs file with namespace MyNameSpace.

    spmetal /web:http://localhost /code:c:\myclass.cs /namespace:mynamespace

    One thing to point here is that there are Visual Studio extension availa to enable developers to generate DataContext from Visual Studio. One such tool isLinq to SharePoint DSL Extension.
    Add DataContext class to your Visual Studio Project: The file you have generated at step 1 need to add in your Visual Studio Project. To do so you need to add reference to Microsoft.SharePoint.Linq.dll from Visual Studio’s Add Reference windows’ ‘.NET’ tab.
    Code Using DataContext Class: Now you can use the DataContext class for manipulating SharePoint lists. For example in the following code I have used the DataContext class generated at step 1 to add a new product in the product list.

    using (var context = new MyclassDataContext(http://mysite))
    {
    var item = new ProductItem();
    item.AvaialableQuantity = 100;
    item.LaunchDate = DateTime.Now;
    item.ProductDescription = "this is computer monitor";
    item.ProductName = "Monitor";
    context.Product.InsertOnSubmit(item);
    context.SubmitChanges();
    }



    Suggestion: If you use SPMetal to generate DataContext class then you’ll find that all classes are placed in a single file (in my case MyClass.cs file). This is very difficult to manage and modify. My suggestion will be to modify the file to move classes in individual pages. You can use some Refactoring tools like Resharper.</LI>




CAML and Linq side-by-side


You may think CAML is dead now as we can do all operations using Linq to SharePoint. Wait, here few point to notice. Firstly, when you use Linq to SharePoint, under the hood, CAML is used. the Linq to SharePoint convert you expression in CAML. However, you can still use CAML for retriving data from database, then you can use Linq to do operations on returned results. For example, you can use CAML to find products. Then you can run linq query against the result set to perform operations like orderby, group, join etc. As shown in the example below, I have used Linq to SharePoint to query Product list to find items whose name contains monitor and also ordered by available quantity.




using (var context = new MyclassDataContext("http://mysite"))
{
var proudcts = from p in context.Product
where p.ProductName.Contains("monitor")
orderby p.AvaialableQuantity
select p;
}




In the following example, I have used CAML query to perform the same operation I have performed above. In the above example I have used Linq to SharePoint extension fully. But in the following example I have used CAML query first to filter data from database. Then I have run Linq query to order data. The following example doesn’t use Linq to SharePoint. Rather it uses the conventional CAML and C# Linq.




SPList productList = SPContext.Current.Web.Lists["Product"];
SPQuery camlQuery=new SPQuery();
camlQuery.Query = "your CAMl query";

var products = productList.GetItems(camlQuery);
var orderProducts = from p in products.Cast<SPListItem>()
where p["ProductName"].ToString().Contains("monitor")
orderby Convert.ToInt32(p["AvaialableQuantity"])
select p;





 



An Exception you may get:


You may find the following exception when you try to run an application in SharePoint server. At first glance you may think the site is not accessible or you have misspelled the site name. But the real fact is that if you try to run your application in 32 bit mode then you may get the exception.



The Web application at http://localhost could not be found. Verify that you have typed the URL correctly. If the URL should be serving existing content, the system administrator may need to add a new request URL mapping to the intended application.


Handy tools for Linq to SharePoint


SharePoint 2010 is not out there for too long. But there are much activities to help developers to easy development/deployment. I have found few codeplex projects aimed at easing development,deployment and more. I have found few Visual Studio extensions for SharePoint in MSDN Visual Studio gallery. The major problem we face in SharePoint development is that we need to deploy our code to test in development environment. VS 2010 has added nice extension already for developer to ease and fast development/deployment. But few VS extensions developed by others may add extra value to SharePoint developer’s life. Here are few useful links SharePoint developers may find handy.




Linq to SharePoint DSL Extension





CKS For SharePoint Server



SharePoint Developer Tool




Linq to SharePoint limitations


With Linq to SharePoint you can’t run all kinds of queries. There some kind of queries which is inefficient. For example if you join two lists in such a way that for each item in first list, you need to go to database for finding matching items in second list then the query is inefficient and not supported in Linq to SharePoint. You can find more details on this on the following link:



http://msdn.microsoft.com/en-us/library/ee536585%28office.14%29.aspx

Perşembe, Kasım 11, 2010

WindowsFormsHost: Host ActiveX and windows form controls in WPF

If you ever need to host ActiveX control in your wpf application then you can do it by using WindowsFormsHost control. Also sometimes you may need to add windows form control in WPF. you may have developed some controls in windows form technology and you don't want to rewrite this for WPF. WindowsFormsHost control can save you for this time. The following steps describes what to do to add ActiveX or windows form control in wpf application:

1. Add reference to WindowsFromsIntegration assembly which is WindowsFormsIntegration.dll. This is the assembly where WindowsFormsHost control is defined. So if you add WIndowsFormsHost control before adding the assembly reference you''ll find that the WindowsFormsHost control is not recognized in XAML (if you just drag and drop the control form toolbox).

2. Add reference to windows forms assembly which is system.windows.forms.dll. You also need to add the namespace in XAML file as shown below:

xmlns:wf="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms"

3. Add reference to the activex control. When you add reference from VS automatically generate an AxHost wrapper for the activex control. Then add the assembly reference in XAML file as shown below:

xmlns:ax="clr-namespace:AxLib;assembly=AxInterop.AxLib"

FYI, if you try to add the activex dll reference without adding the other two reference you may not get the namespace in the XAML. WindowsFormsHost control can also be used to host any control designed for windows form.

Now you can add your ActiveX control in one of two ways:

1. Add Control directly in XAML as shown below:

<WindowsFormsHost Name="wfHostControl">
      <ax:MyControl x:Name="axMyCtrl"/>
</WindowsFormsHost>

2. Add the control from code by instantiating and placing in the child property of the WindowsFormsHost control.

MyControl ctrl=new MyControl();

wfHostControl.Child = ctrl;

If you need to do something on form load its better to work on WindowsFormsHost control's load event instead of wpf application's load event.

Çarşamba, Kasım 10, 2010

SharePoint 2010: Client Object Model – an Introduction

SharePoint 2007 allows using its Object model to run against server running SharePoint. For clients (not running SharePoint in the box) the simplest way to communicate with SharePoint server is web services. SharePoint Client Object Model (OM) can be run on client PC (where SharePoint is not installed) to communicate with SharePoint server. So whereas SharePoint (Server) Object Model runs in a SharePoint server and can manipulate SharePoint objects, Client OM can run in client PC and communicate with SharePoint server remotely.

SharePoint 2010 introduces three new client APIs which can be used to interact with SharePoint sites. The three APIs are targeted for three different types of clients:

1. For .net Managed applications (for example, console applications, window applications, web applications etc, which are not running inside SharePoint Context).

2. For Silverlight applications.

3. For using with JavaScript (called ECMAScript). This API is only available for applications hosted inside SharePoint (for example, web part deployed in SharePoint site can use this JavaScript API for accessing SharePoint from browser using JavaScript).

I’ll explain all of these API sets gradually. In this post I’ll explain some basic problems SharePoint developers faced during development with SharePoint 2007 in absence of Client Object Model.

Why Client Object Model (OM)?

SharePoint 2007 had no Client Object model available. So you may ask why this is introduced in SharePoint 2010? We had no problem without Client OM and millions of sites are running smoothly without having Client OM. The main reason is that Microsoft has found lot of requests from SharePoint users to introduce more and more web services to get data out of SharePoint in the last couple of years. But introducing web services will not fix the issues, as Microsoft found, because then the request for more functionality in the web services will continue. Even if Microsoft provides a good numbers of web services with SharePoint, customization in web services will be required for different clients and this will make the out of the box web services unusable. Also introducing a large number of web services will be a waste as not all companies will use all the web services functionalities.

In response to add more web services from users, Microsoft has taken a different approach called Client Object Model (OM). This SharePoint Client OM will allow getting data out of SharePoint from PCs that are not hosting SharePoint. Also Client OM provides complete API to interact with SharePoint Server which is more intuitive and useful and very much similar with SharePoint Object Model.

Similarity with SharePoint Object Model

Now SharePoint developers will fear that the Client Object Model will introduce new burden for them to get used to it. But SharePoint team provided great efforts to keep the Client OM familiar with SharePoint Object Model. The following table shows the equivalent objects in Client and SharePoint Object Model.

Server (Microsoft.SharePoint)

Client Object Model

SPContext

ClientContext

SPSite

Site

SPWeb

Web

SPList

List

SPListItem

ListItem

SPField

Field

So the class names in Client OM are similar as like SharePoint Object Model. However the way client OM will be used a bit different than usual SharePoint Object Model that we will explore in the upcoming posts.

How Client OM is developed and work under the hood?

It’s interesting how SharePoint team has developed the same set of classes for three different sets of applications (Managed, Silverlight and ECMAScript). There is same class ListItem for three different set of applications. As shown in the following table, three different assemblies/files are used for three different types of applications.

Client Type

Assembly/File

Managed Client

Microsoft.SharePoint.Client

Silverlight

Microsoft.SharePoint.Client.Silverlight

ECMAScript

SP.js

To ensure the same class object (say ListItem) behaves similarly in three different types of applications SharePoint team followed the steps described below:

a) SharePoint team first set attributes to the SharePoint classes and methods and properties that need to be exposed in Client OM.

b) Then a code generator is run against the SharePoint object model to generate client OM automatically.

This automated code generation has ensured maximum compatibility between these three sets of APIs. As the following figure shows client communicate to the server thorough Client OM which under the hood uses Client.svc WCF service to communicate with SharePoint Server. Client.svc service uses Server OM as per client request and return result to the client in JSON format.

 
  image

Figure: How Client Object model works with Server

With this new Client OM, we almost don’t need to use web service to communicate with SharePoint server. However, the client OM just released and we will find its shortcomings as well use it more and more in live projects. In my next posts I’ll go through three differents kinds of Client OM (Managed .net, Silverlight and ECMAScript).

Salı, Kasım 09, 2010

SharePoint 2010: Metadata Service

In SharePoint 2010 two metadata related supports are added which were a big challenges in SharePoint 2007. One Challenges I faced personally was not having the support of taxonomy or hierarchical metadata. Another challenges was the metadata could not be shared across site collection boundaries. So in SharePoint 2010 the following two new features are added to solve the issues:

1. Taxonomy Support:

Taxonomy is a mean of managing data in hierarchy. Though other CMSs have this taxonomy support out of the box, till SharePoint 2007 we had not this support out of the box. But SharePoint 2010 has this support built in. We can now use hierarchical metadata to tag content in SharePoint 2010.

2. Shared Metadata

Prior to SharePoint 2010 there was no way to share metadata across site collection boundaries. So if you ever needed to share metadata then you would go and copy the same metadata in different site collection. Now in SharePoint 2010 there’s a shared service called “Shared Metadata Service” which will facilitate the metadata sharing across the site collection and  web application level.

Few definitions associated with Metadata Service

There are two types of metadata that can be associated with an item in SharePoint 2010:

1. Managed Metadata or Taxonomy: As the name suggested these metadata are managed centrally (from central administration). Users with appropriate permission can only add/edit/deleted these kind of metadata. Other users just use these metadata to tag item.

2. Unmanged Metadata or Folksonomies: These unmanged metadata are not managed centrally. If a user find that managed metadata can’t meet his requirement then he can add his own metadata to the item.

Few others terms are described below:

Term: Term is keyword or phrase that can be associated with a content. Simply we can say term is a single node in taxonomy.

Term Sets: A Collection of terms. So simply we can say term sets as a subset of taxonomy.

Term Store: A store (say database) which store all terms.

Managed Keywords: Managed keyword is a kind of metadata field which supports only non-hierarchical list called keyword-set. Managed Keyword columns allows to select from existing metadata store. It also allow to add the keyword to the store if it doesn’t exist already. This is actually an way to implement the Folksonomies.

How to define Term sets and Terms?

Go to Central administration site –> Application Management –> Manage Service Applications (under service applications). Then click Managed Metadata Service and you’ll be landed in the term store properties page. In that page move your mouse on the “Managed Metadata Service” and you’ll get a popup menu “New Group” as shown  below:


Figure 1: New metadata group.

Once you create a new group you can create term set and term. In the following figure I have defined one group (Sales System) then I have defined two Term sets (IT Product and Region). Then I have defined different terms under the term sets.

Figure 2: Term sets and terms under group.

Use terms in list/library column

To assign metadata you need to create a column of type “Managed Metadata” for your list or libraries. If you go to add a column to your list you’ll find a new column type “Managed Metadata”. In that column you have two options to set your managed metadata.

    Use a Managed term set: With this option user will just be able to select from available terms. User will not be able to add new terms in the term set. As shown in the following figure, this column will only allow the terms defined in the term set “IT Product”</LI>

    Customize your term set: This option is for “Managed Keywords”. With this option selected user will be able to add new term in the custom term set. when you will select this option a new term set will be created for you.</LI>

I had created a column ProductType that is bound to the term set “IT Product”. When I landed to add new item page and started typing in the Product Type field, the suggestions were presented as shown below:

Figure 3: Metadata auto suggestion

Summary

In summary, the shared metadata service will help much to manage metadata from a central place. Also taxonomy will help better content tagging and better content organization.

Pazar, Kasım 07, 2010

SharePoint 2010 Error: System.Web.HttpException: Directory 'C:\inetpub\wwwroot\wss\VirtualDirectories\80\_catalogs\masterpage' does not exist.

I had got the error when I tried to use pagemethods in SharePoint. I had registered the AjaxControlToolkit namespace in masterpage and then I replace ScriptManager with AjaxControlToolkit’s ToolkitScriptManager as shown below:

<ajaxToolkit:ToolkitScriptManager id="ScriptManager" runat="server" 
EnablePageMethods="true" EnablePartialRendering="true"
EnableScriptGlobalization="false" EnableScriptLocalization="true"/>



The problem was I enabled the pagemethods by setting EnablePageMethos to true. Disabling the pagemethods fixed the issue. However I didn’t explore why there’s the error if pagemethods is enabled.



So disable the pagemethods by setting EnablePageMethods=”false” and then then error will disappear…

Cuma, Kasım 05, 2010

SharePoint 2010–Dialog Box Close Not postback

void btnOK_Click(object sender, EventArgs e)
{
    this.Context.Response.Write("<script    type='text/javascript'>window.frameElement.commitPopup();</script>");
this.Context.Response.End();
}

Perşembe, Kasım 04, 2010

SharePoint 2010 Error: Cannot make a cache safe URL for "1031/styles/Themable/corev4.css", file not found. Please verify that the file exists under the layouts directory.

I had to take backup from my client’s production site to make some analysis in my local server. So I took backup and restored the site in local server. Restored was successful. Then I changed the site collection administrator of the restored site as it was still pointing to the production server’s site collection administrator.

Then I tried to browse the site and found the interesting error “Cannot make a cache safe URL for "1031/styles/Themable/corev4.css", file not found. Please verify that the file exists under the layouts directory.” The very first thing I noticed the path contains 1031. Anyone can guess that this is language id and thought came to my mind is that my client SharePoint site not in English. The production site was in German language and when I restored the site, its looking for resources in German language ID folder.

Then I download the language pack from MSND and refreshed the browser. It worked like magic….

Pazartesi, Kasım 01, 2010

SharePoint 2010 - Adding a custom control to the Team Site Wiki Page Template ( wkpstd.aspx ) programmatically

This is maybe my last post about the Team Site Wiki Page customization with the aim of exceeding its limitations. This time we are going to customize the unique template ( wkpstd.aspx) by replacing the control that renders the editable content by a custom one.
In this post example, we will take advantage of the use of this custom control to render not editable additional contents, for instance, a header and a footer that could be the same for a specific Wiki Page Library. Of course, we are not going to really customize the template, but we are going to use a delegate control to replace the control at runtime.

Here are the screen shots of the result:

We can see a non editable Header and Footer on the Team Site Wiki Page

In edit mode these additional contents are not rendered, but we can add new content as usual

And of course, after having saved the page, the new content is rendered wtih the non editable Header and Footer.

Steps required 

If you look at the Team Site Wiki Page template, wkpstd.aspx, you can see the control that is rendering the editable content/

<SharePoint:EmbeddedFormField ID="WikiField" FieldName="WikiField" ControlMode="Display" runat="server" />

We are first going to create a custom EmbeddedFormField control

Then, we are going to use a delegate control to replace the native EmbeddedFormField control by the custom one in order to have the control on the wiki page content rendering.

1 - Creating a custom EmbeddedFormField control

Here is the screen shot of the Visual Studio solution:

and the custom control source code:

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Linq;

using System.Text;

using System.Web;

using System.Web.UI;

using System.Web.UI.WebControls;

using Microsoft.SharePoint;

using Microsoft.SharePoint.WebControls;

using Microsoft.SharePoint.Security;

using System.Security.Permissions;

using System.Diagnostics;

 

namespace CustomControls

{

    [SharePointPermission(SecurityAction.LinkDemand, ObjectModel = true), AspNetHostingPermission(SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal), SharePointPermission(SecurityAction.InheritanceDemand, ObjectModel = true), AspNetHostingPermission(SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]

    public class EmbeddedFormFieldCustom : FormField

    {

        public EmbeddedFormFieldCustom()

        {

            Debug.WriteLine("EmbeddedFormFieldCustom: " + "constructor...");

        }

 

        private string _header = "";

        private string _footer = "";

 

        public string Footer

        {

            get { return _footer; }

            set { _footer = value; }

        }

 

        public string Header

        {

            get { return _header; }

            set { _header = value; }

        }

 

        [PersistenceMode(PersistenceMode.InnerDefaultProperty)]

        public string Content

        {

            get

            {

                SPFieldMultiLineText field = base.Field as SPFieldMultiLineText;

                if (field != null)

                {

                    Debug.WriteLine("EmbeddedFormFieldCustom: " + "SPFieldMultiLineText ok");

                    return field.GetFieldValueAsHtml(this.ItemFieldValue, base.ListItem);

                }

 

                Debug.WriteLine("EmbeddedFormFieldCustom: " + "SPFieldMultiLineText non ok");

                return base.Field.GetFieldValueAsHtml(this.ItemFieldValue);

            }

            set

            {

                Debug.WriteLine("EmbeddedFormFieldCustom: " + "set Content");

                this.ItemFieldValue = value;

            }

        }

 

        [SharePointPermission(SecurityAction.Demand, ObjectModel = true)]

        protected override void Render(HtmlTextWriter output)

        {

            Debug.WriteLine("EmbeddedFormFieldCustom: " + "rendering..");

            output.AddAttribute(HtmlTextWriterAttribute.Id, this.ClientID);

            output.Write(_header);

            output.RenderBeginTag(HtmlTextWriterTag.Div);

            base.Render(output);

            output.Write(_footer);

            output.RenderEndTag();

        }

    }

}

 

 

2 - Replacing the standard control by the custom one by using a delegate control

Here is the delegate control source code:

<%@ Control Language="C#" ClassName="WikiPageCustomLink" %>

<%@ Register TagPrefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls"

    Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>

<%@ Assembly Name="CustomControls, Version=1.0.0.0, Culture=neutral, PublicKeyToken=0f4e5a441a5ac20c" %>

<%@ Import Namespace="Microsoft.SharePoint" %>

<%@ Import Namespace="System.Diagnostics" %>

 

    <!--delegate control begin -->

 

<script runat="server">   

    Control ctrlTofind = null;

    CustomControls.EmbeddedFormFieldCustom myCustomEmbeddedFormField = null;

 

    protected void FindControl(Control aControl, string ID)

    {

        if (aControl.HasControls())

        {

            foreach (Control aControl2 in aControl.Controls)

            {

                if (aControl2.ID == ID)

                {

                    ctrlTofind = aControl2;

                }

                else

                {

                    FindControl(aControl2, ID);

                }

            }

        }

        else

        {

            if (aControl.ID == ID)

            {

                ctrlTofind = aControl;

            }

        }

    }

 

    protected override void OnInit(EventArgs e)

    {

        if (!IsPostBack)

        {

            Debug.WriteLine("onInit");

            if (Page.TemplateControl.ToString().Contains("ASP.WKPSTD_ASPX"))

            {

                try

                {

                    int ctrlNumber = this.Page.Controls.Count;

                    foreach (Control aControl in this.Page.Controls)

                    {

                        FindControl(aControl, "WikiField");

                    }

 

                    myCustomEmbeddedFormField = new CustomControls.EmbeddedFormFieldCustom();

 

                    myCustomEmbeddedFormField.Header = "<span style='color:red;font-size:12pt' >This is my Custom header, not editable</span>";

                    myCustomEmbeddedFormField.Footer = "<span style='color:red;font-size:12pt' >This is my Custom footer, not editable</span>";

 

                    Control updatePannelContentTemplateContainer = ctrlTofind.Parent;

 

                    UpdatePanel myUpdatepannel = (UpdatePanel)updatePannelContentTemplateContainer.Parent;

 

                    int index = 0;

                    for (int i = 0; i < updatePannelContentTemplateContainer.Controls.Count; i++)

                    {

                        if (updatePannelContentTemplateContainer.Controls[i].ID == "WikiField")

                        {

                            index = i;

                        }

                    }

                    updatePannelContentTemplateContainer.Controls.Remove(ctrlTofind);

                    updatePannelContentTemplateContainer.Controls.AddAt(index, myCustomEmbeddedFormField);

                    myCustomEmbeddedFormField.FieldName = "WikiField";

                    myCustomEmbeddedFormField.ID = "WikiField";

                    myCustomEmbeddedFormField.ControlMode = Microsoft.SharePoint.WebControls.SPControlMode.Display;

                }

                catch (Exception ex)

                {

                    Response.Write("<br>error : <br>" + ex.Message);

                    Response.Write("<br>error : <br>" + ex.StackTrace);

                }

            }

        }

        base.OnInit(e);

    }

 

</script>

    <!--delegate control end -->

 

The secret is to load the custom control only if it is not a post back, since the embedded control itself seems to be used only for displaying the content.