Thursday, February 25, 2010

Creating SP2010 Social Comments programmatically for another user.

Technorati Tags: ,,

Recently I have seen people wanting to migrate user’s social comments or tags from custom SP 2007 solutions to SP2010 sites. Creating social comments programmatically requires the use of the new “Service Application Framework” and using the “User Profile Service Application”. The problem is how to create these comments with the appropriate user when doing this in bulk. You must use the Microsoft.Office.Server.SocialData.SocialCommentManager. This class takes a SPServiceContext object in its constructor. Now most SharePoint developers would expect that by creating a SPSite with the user’s SPUserToken and passing in this SPSite to the SPServiceContext.GetContext method that it would create an impersonated SPServiceContext that would generate a comment with that user’s name and id. Unfortunately, this is not the case. It seems that the “User Profile Service Application” relies on the HttpContext object to get user profile information to determine who is generating tags and comments.

So the requirement seems that you must create an HttpContext and add the impersonated user to the HttpContext. There are many problems with this. First it requires that you know the user’s password in order to impersonate and secondly it requires that you add that user to the list of users who can call the “User Profile Service Application”. Having to know the user’s password and some how pass  it to the code is worth the following small “hack”.  This “hack” uses reflection to change the WindowsIdentity’s name property. It is this property that is used to grab the user’s profile in order to label the comment.  So all you have to do is substitute the SPUser.LoginName for the WindowsIdentity.Name and all is taken care of. What is also nice is that this change only changes that instance of the WindowsIdentity, so you are not actually changing the current user’s name. Below is the code.

 

public static void CallServiceApplicationImpersonated()
      {          

          using (SPSite site = new SPSite("http://basesmc2008"))
          {
              using (SPWeb web = site.OpenWeb())
              {
                  SPUser user = web.EnsureUser("servername\\steve.curran");
                  web.AllowUnsafeUpdates = true;

                  HttpRequest request = new HttpRequest("", http://servername, "");

                  HttpContext.Current =
                      new HttpContext(request,
                          new HttpResponse(new StringWriter(CultureInfo.CurrentCulture)));

                  HttpContext.Current.Items["HttpHandlerSPWeb"] = web;

                  WindowsIdentity wi = WindowsIdentity.GetCurrent();

                  typeof(WindowsIdentity).
                      GetField("m_name", BindingFlags.NonPublic | BindingFlags.Instance)
                      .SetValue(wi, user.LoginName);

                  HttpContext.Current.User = new GenericPrincipal(wi, new string[0]);

                  WindowsIdentity wi2 = WindowsIdentity.GetCurrent();

                  SocialCommentManager socialCommentManager =
                      new SocialCommentManager(
                          SPServiceContext.GetContext(HttpContext.Current));

                  socialCommentManager.AddComment
                      (new Uri("http://basesmc2008/page.aspx"), "mycomment");

                  web.AllowUnsafeUpdates = false;
              }

          }

      }

Monday, February 8, 2010

How To: Create a document set remotely SP2010

Technorati Tags: ,,

One of the big questions these days is working with SP2010 remotely using web services, client object model or even WCF. Everyone is familiar with SP2007 web services, but the question is whether the same web services work in SP2010. This question came up when I wanted to create a “document set” which is a new feature in SP2010. This a new contenttype that is similar to the folder contenttype in SP2007. It allows much more metadata to be contained and used within a folder. So I thought by just using the Webs.asmx web service and the  “CreateContentType” method I could achieve this.  Unfortunately not. Below is the code that does not work:

 

public static void CreateDocumentSetWithWebService()
{

            websservice.Webs ws = new websservice.Webs();
            ws.Url = "http://basesmc2008";
            ws.UseDefaultCredentials = true;

            XmlDocument doc = new XmlDocument();
            XmlNode fields = doc.CreateNode(XmlNodeType.Element, "Fields", string.Empty);
            XmlNode contentType =
                doc.CreateNode(XmlNodeType.Element, "ContentType", string.Empty);

            XmlAttribute desc = doc.CreateAttribute("Description");
            XmlAttribute title = doc.CreateAttribute("Title");    
            title.Value = "nds";
            desc.Value = "My new document set";
            contentType.Attributes.Append(desc);
            contentType.Attributes.Append(title);

            try
            {
                string returnVal =
                    ws.CreateContentType("nds", "0x0120D520", fields, contentType);                    
            }
            catch (Exception e)
            {
                string mystack = e.StackTrace;          
            }
}

 

So I decided to look at the client object model. This is also a new feature in SP2010 and allows remote applications to work with a subset of the SharePoint object model from applications not running on the SharePoint server. The dlls that you need to reference in your project are Microsoft.SharePoint.Client and Microsoft.SharePoint.Client.Runtime. The object model is very easy to use and wraps all the complexity of calling WCF services. Basically you create the objects you want to work with then make the calls using ClientContext.ExecuteQuery method. Very simple.

public static void CreateDocumentSet_ClientOM()
{
            ClientContext clientContext = new ClientContext("http://basesmc2008");
            Web site = clientContext.Web;
            clientContext.Load(site);

            ContentTypeCollection contentTypes = site.ContentTypes;
            IEnumerable<ContentType> filteredTypes =
                clientContext.LoadQuery(contentTypes.Where(c => c.Name == "Document Set"));

            FieldCollection fields = site.AvailableFields;
            IEnumerable<Field> filteredFields =
                clientContext.LoadQuery(fields.Where(f=> f.InternalName == "Company"));
            clientContext.ExecuteQuery();        

            ContentTypeCreationInformation ctypeInfo = new ContentTypeCreationInformation();
            ctypeInfo.Description = "docset";
            ctypeInfo.Group = "Custom Content Types";
            ctypeInfo.Name = "docset";
            ctypeInfo.ParentContentType = filteredTypes.First();

            ContentType newContentType = contentTypes.Add(ctypeInfo);

            FieldLinkCreationInformation fieldLinkInfo = new FieldLinkCreationInformation();
            filteredFields.First().Required = true;
            fieldLinkInfo.Field = filteredFields.First();
            newContentType.FieldLinks.Add(fieldLinkInfo);
            newContentType.Update(true);

            clientContext.ExecuteQuery();
}

It is very easy to remotely integrate your applications with SP2010. SP2010 offers the client object model in three different flavors, .Net, SilverLight and ECMAScript.

http://msdn.microsoft.com/en-us/library/ee537247(office.14).aspx