Archive

Archive for the ‘Share Point’ Category

SharePoint: Brief introduction to Business Data Catalog (BDC)

November 4th, 2011 No comments

    Business Data Catalog (BDC) allows to integrate business data from external business applications into SharePoint application. BDC has built-in support for displaying data from such data sources as databases and web services. In other words, if the business application is a database or comprises a web service to emit data, its data can be easily incorporated into SharePoint application. If some business application isn’t database and doesn’t have any proper web services, you always can develop your own web service to provide access to the business application’s data.

Once the external business application has been registered within Business Data Catalog, its data can be used in Business Data Web Parts, Business Data Column, Search, User profile and other custom solutions.

BDC provides access to the underlying data sources with a declarative Metadata Model. The Metadata Model allows to describe a simplified and consistent client object model over any business application. What is Metadata? Metadata describes the business applications’ APIs. For each business application, Metadata defines the business entities that the business application interacts with and the methods available in the business application. The Business Data Catalog stores the metadata in the metadata repository. So, using the Metadata Model, developer describes the API of the business application in a xml-file, so called Application Definition File. Then SharePoint administrator imports the Application Definition into the Business Data Catalog to register Metadata the file contains. After that the data from business application becomes available. The schematic structure of Application Definition File is shown below:

<LobSystem Type="..." Name="...">
  <LobSystemInstances>
    <LobSystemInstance Name="...">
      <Properties>
        ...
      </Properties>
    </LobSystemInstance>
  </LobSystemInstances>
  <Entities>
    <Entity Name="...">
      <Properties>
        ...
      </Properties>
      <Identifiers>
        ...
      </Identifiers>
      <Methods>
        ...
      </Methods>
    </Entity>
  </Entities>
</LobSystem>

*Note: some attributes are skipped

The LobSystem is a container for all of the other objects in the Metadata object model, and the root node in the Application Definition File. Each LobSystem object has an unique name and is of a certain type: either Database or Web Service. LobSystem object contains LobSystemInstances and Entities. The LobSystemInstance contains properties that define the authentication of the connection and the provider, which is used to connect to the external data source. Entity defines a type of returned business data object, it contains identifiers, methods and actions. Also Entity can have other related entities associated with them.

In terms of SharePoint Central Administration, the LobSystem is a Business Data Catalog Application or BDC Application, while LobSystemInstance can be named Business Data Catalog Application Instance or BDC Application Instance. Entity in SharePoint Central Administration and in Metadata Model means the same.

BDC Metadata Model Schema

All BDC Applications registered in Business Data Catalog can be viewed through the Central Administration: open SharePoint 3.0 Central Administration, click the name of Shared Service Provider (in my case it’s SharedServices1), then click the View applications link in the Business Data Catalog section. To create Application Definition File and import it to the Business Data Catalog, read the very good article written by Tobias Zimmergren.

To work with BDC programmatically you should use types from the following namespaces:

using Microsoft.Office.Server;
using Microsoft.Office.Server.ApplicationRegistry.MetadataModel;
using Microsoft.Office.Server.ApplicationRegistry.Infrastructure;
using Microsoft.Office.Server.ApplicationRegistry.Runtime;

You mainly will use the next interfaces and objects:

namespace Microsoft.Office.Server.ApplicationRegistry.MetadataModel
{
    // Provides access to all of the LOB systems and LOB system instances registered in the Business Data Catalog
    public sealed class ApplicationRegistry { ... }

    // Represents a business application registered in the Business Data Catalog
    public class LobSystem : MetadataObject { ... }

    // Represents an instance of a business application registered in the Business Data Catalog
    public class LobSystemInstance : MetadataObject { ... }

    // Represents a type of returned business data object, contains identifiers, methods and actions
    public class Entity : DataClass { ... }
}

namespace Microsoft.Office.Server.ApplicationRegistry.Runtime
{
    // Represents a filter that limits the instances returned to those that meet the comparison operator condition
    public class ComparisonFilter : UserInputFilter { ... }

    // Represents a filter that limits the instances returned to those where field like value, where value may contain the asterisk (*) wildcard character
    public class WildcardFilter : ComparisonFilter { ... }

    // Represents instances of business objects
    public interface IEntityInstance : IInstance { ... }

    // Provides a single iteration over the entity instance collection
    public interface IEntityInstanceEnumerator : IEnumerator<IEntityInstance> { ... }
}

namespace Microsoft.Office.Server.ApplicationRegistry.Infrastructure
{
    // Provides encoding and decoding of entity instance identifiers
    public static class EntityInstanceIdEncoder { ... }

    // Represents the SQL session provider to connect to the Shared Services Provider database
    public sealed class SqlSessionProvider { ... }
}

The Business Data Catalog is implemented as a Microsoft Office SharePoint Server 2007 Shared Service. If you are going to deal with BDC inside a standalone window/console-based application or inside SharePoint Jobs, you have to prepare Shared Resource Provider to use in the Object Model. For that, you need to invoke the method SqlSessionProvider.Instance().SetSharedResourceProviderToUse:

namespace Microsoft.Office.Server.ApplicationRegistry.Infrastructure
{
    public sealed class SqlSessionProvider
    {
        // some methods are omitted
        public void SetSharedResourceProviderToUse(string sharedResourceProviderName);
        public void SetSharedResourceProviderToUse(ServerContext serverContext);        
    }
}

The first variant of the method accepts a name of Shared Resource Provider. In terms of SharePoint Central Administration, the method requires the name of Shared Service. You can see all deployed Shared Services through the Central Administration: open SharePoint 3.0 Central Administration, find the Shared Services Administration section inside the left-side navigation bar and look through the available Shared Services. One of them is a default Shared Service. Another way is to get programmatically the name of Shared Service, which serves your web application. To learn more, please read the following article – How to get Shared Service name.

The second variant of the method use an instance of ServerContext. Below is two auxiliary methods I usually use as wrappers to SetSharedResourceProviderToUse:

public static void PrepareSharedServices(SPSite spSite)
{
    ServerContext sc = ServerContext.GetContext(spSite);
    SqlSessionProvider.Instance().SetSharedResourceProviderToUse(sc);
}

public static void PrepareSharedServices(string sharedServicesName)
{
    SqlSessionProvider.Instance().SetSharedResourceProviderToUse(sharedServicesName);    
}

Do not use this method inside your SharePoint web application, otherwise an exception will be thrown. Because in web context the Business Data Catalog uses the default Shared Services Provider automatically. As I said above, use SetSharedResourceProviderToUse only inside standalone non-web applications or SP Jobs.

SharePoint: How to find all controls of a certain type

October 12th, 2011 No comments

    A small, but useful method to find recursively all controls of a certain type:

public static List<T> FindControlRecursiveByType<T>(Control root) where T : Control
{
    List<T> res = new List<T>();

    if (root != null)
    {
        Stack<Control> tmpStack = new Stack<Control>();
        tmpStack.Push(root);

        while (tmpStack.Count > 0)
        {
            Control ctrl = tmpStack.Pop();
            if (ctrl is T)
                res.Add(ctrl as T);

            foreach (Control childCtrl in ctrl.Controls)
                tmpStack.Push(childCtrl);
        }
    }

    return res;
}

The sample usage is below:

// return all save buttons on the page
List<SaveButton> saveButtons = FindControlRecursiveByType<SaveButton>(Page);
Related posts:
Categories: ASP.NET, Share Point Tags: ,

SharePoint: How to add Content Type programmatically

September 16th, 2011 No comments

     To add a new Content Type through the SharePoint Object Model I’ve developed a few auxiliary methods and classes. The main class is ContentType class. It contains information used for specifying the required properties of the SPContentType object being added.

public class ContentType
{
    public string         ParentContentTypeName { get; set; }
    public string         Name                  { get; set; }
    public List<FieldRef> FieldRefs             { get; set; }

    public ContentTypeFormUrls   FormUrls       { get; set; }
    public ContentTypeProperties Properties     { get; set; }
    public SPContentTypeId       Id             { get; set; }
}

The FieldRefs property represents a collection of fields that have to be added to the Content Type. The other property names are self-descriptive.

The ContentType class makes reference to classes like FieldRef, ContentTypeFormUrls and ContentTypeProperties.

public class FieldRef
{
    public Guid?  ID                 { get; set; }
    public string Name               { get; set; }
    public string DisplayName        { get; set; }
    public bool?  IsRequired         { get; set; }
    public bool?  IsHidden           { get; set; }
    public bool?  IsReadOnly         { get; set; }

    public string Aggregation        { get; set; }
    public string Customization      { get; set; }
    public string PIAttribute        { get; set; }
    public string PITarget           { get; set; }
    public string PrimaryPIAttribute { get; set; }
    public string PrimaryPITarget    { get; set; }
    public string Node               { get; set; }
}

public class ContentTypeFormUrls
{
    public string DisplayFormUrl { get; set; }
    public string NewFormUrl     { get; set; }
    public string EditFormUrl    { get; set; }
}

public class ContentTypeProperties
{    
    public string Description                 { get; set; }
    public string Group                       { get; set; }
    public bool?  IsHidden                    { get; set; }
    public bool?  IsReadOnly                  { get; set; }
    public string NewDocumentControl          { get; set; }
    public bool?  RequireClientRenderingOnNew { get; set; }
    public bool?  IsSealed                    { get; set; }
}

The main method is AddContentType:

public static void AddContentType(SPWeb spWeb, SPList spList, ContentType contentType)
{
    // get appropriate parent content type
    SPContentType parentContentType = spWeb.AvailableContentTypes[contentType.ParentContentTypeName];
    if (parentContentType != null)
    {
        if (spList.ContentTypes[contentType.Name] == null)
        {
            spList.ContentTypesEnabled = true;

            // create new content type
            SPContentType spContentType = new SPContentType(parentContentType, spList.ContentTypes, contentType.Name);
            // set content type id
            SetContentTypeId(spContentType, contentType.Id);
            // set content type properties
            SetContentTypeProperties(spContentType, contentType.Properties);

            // add fields to conent type
            foreach (FieldRef fieldRef in contentType.FieldRefs)
            {
                if (spList.Fields.ContainsField(fieldRef.Name))
                {
                    SPField targetField = fieldRef.ID.HasValue ? spList.Fields[fieldRef.ID.Value] : spList.Fields.GetFieldByInternalName(fieldRef.Name);
                    if (targetField != null && !spContentType.Fields.ContainsField(fieldRef.Name))
                    {
                        SPFieldLink fLink = new SPFieldLink(targetField);
                        SetFieldRefProperties(fLink, fieldRef);
                        spContentType.FieldLinks.Add(fLink);
                    }
                }
                else
                    Console.Write(string.Format("Couldn't find field {0} in the list {1}", fieldRef.Name, spList.Title));
            }

            // set content type form urls
            SetContentTypeFormUrls(spContentType, contentType.FormUrls);

            // save changes
            spList.ContentTypes.Add(spContentType);
            spContentType.Update();
            spList.Update();
        }
        else
            Console.Write(string.Format("Content type {0} already exists in the list {1}", contentType.Name, spList.Title));
    }
    else
        Console.Write(string.Format("Couldn't find the parent content type {0}", contentType.ParentContentTypeName));
}

It utilizes SetContentTypeFormUrls, SetContentTypeProperties, SetFieldRefProperties and SetContentTypeId (you can read about the last method in detail in my other post – How to set Id to just created SPContentType).

public static void SetContentTypeFormUrls(SPContentType spContentType, ContentTypeFormUrls contentTypeFormUrls)
{
    if (contentTypeFormUrls == null)
        return;

    if (contentTypeFormUrls.NewFormUrl != null)
        spContentType.NewFormUrl = contentTypeFormUrls.NewFormUrl;

    if (contentTypeFormUrls.DisplayFormUrl != null)
        spContentType.DisplayFormUrl = contentTypeFormUrls.DisplayFormUrl;

    if (contentTypeFormUrls.EditFormUrl != null)
        spContentType.EditFormUrl = contentTypeFormUrls.EditFormUrl;
}

public static void SetContentTypeProperties(SPContentType spContentType, ContentTypeProperties cntProps)
{
    if (cntProps == null)
        return;

    if (cntProps.Description != null)
        spContentType.Description = cntProps.Description;
    if (cntProps.Group != null)
        spContentType.Group = cntProps.Group;
    if (cntProps.IsHidden != null)
        spContentType.Hidden = cntProps.IsHidden.Value;
    if (cntProps.IsReadOnly != null)
        spContentType.ReadOnly = cntProps.IsReadOnly.Value;
    if (cntProps.IsSealed != null)
        spContentType.Sealed = cntProps.IsSealed.Value;
    if (cntProps.NewDocumentControl != null)
        spContentType.NewDocumentControl = cntProps.NewDocumentControl;
    if (cntProps.RequireClientRenderingOnNew != null)
        spContentType.RequireClientRenderingOnNew = cntProps.RequireClientRenderingOnNew.Value;
}

public static void SetContentTypeId(SPContentType spContentType, SPContentTypeId contentTypeId)
{
    try
    {
        FieldInfo fi = typeof(SPContentType).GetField("m_id", BindingFlags.NonPublic | BindingFlags.Instance);
        if (fi != null)
            fi.SetValue(spContentType, contentTypeId);
        else
            Console.Write("Couldn't set content type id!");
    }
    catch(Exception ex)
    {
        Console.Write(string.Format("Couldn't set content type id! {0}", ex.Message));
    }
}

public static void SetFieldRefProperties(SPFieldLink fLink, FieldRef fieldRef)
{
    if (fieldRef == null)
        return;

    if (fieldRef.DisplayName != null)
        fLink.DisplayName = fieldRef.DisplayName;
    if (fieldRef.IsHidden != null)
        fLink.Hidden = fieldRef.IsHidden.Value;
    if (fieldRef.IsRequired != null)
        fLink.Required = fieldRef.IsRequired.Value;
    if (fieldRef.IsReadOnly != null)
        fLink.ReadOnly = fieldRef.IsReadOnly.Value;

    if (fieldRef.Aggregation != null)
        fLink.AggregationFunction = fieldRef.Aggregation;
    if (fieldRef.Customization != null)
        fLink.Customization = fieldRef.Customization;
    if (fieldRef.PIAttribute != null)
        fLink.PIAttribute = fieldRef.PIAttribute;
    if (fieldRef.PITarget != null)
        fLink.PITarget = fieldRef.PITarget;
    if (fieldRef.PrimaryPIAttribute != null)
        fLink.PrimaryPIAttribute = fieldRef.PrimaryPIAttribute;
    if (fieldRef.PrimaryPITarget != null)
        fLink.PrimaryPITarget = fieldRef.PrimaryPITarget;
    if (fieldRef.Node != null)
        fLink.XPath = fieldRef.Node;
}

Here is how you can use all of that stuff:

public void AddSomeContentType(SPWeb spWeb, SPList spList)
{
    ContentType newContentType = new ContentType()
    {
        ParentContentTypeName = "Item",

        Id   = new SPContentTypeId("0x0100078C8B39671A4532AB9C5AB6DCB388A6"), // id has to correspond to the parent content type
        Name = "some new content type name",

        Properties = new ContentTypeProperties() { Description = "super modern content type", Group = "List Content Types" },
        FormUrls   = new ContentTypeFormUrls() { DisplayFormUrl = "Lists/your list name/your custom page name.aspx" },
        FieldRefs  = new List<FieldRef>() 
            { 
                new FieldRef() { Name = "Title",  ID = new Guid("{fa564e0f-0b71-4ab7-b863-0177e6ddd247}"), IsRequired = false, IsReadOnly = true, DisplayName = "List Item Title" },
                new FieldRef() { Name = "Status", IsRequired = true, DisplayName = "List Item Status" },
                new FieldRef() { Name = "Involved_User", ID = new Guid("869963ef-9ca3-4ad7-a5f0-8fff724a6877"), DisplayName = "User Involved" } 
            }
    };

    AddContentType(spWeb, spList, newContentType);
}

Please note that you don’t have to fill out all properties of the auxiliary classes (ContentTypeProperties, ContentTypeFormUrls and FieldRef); the unspecified properties will be merely ignored, and the appropriate properties of SPContentType object will be left with their default values. Als,o pay attention to the fact that Content Type identifier has to contain the id of parent Content Type. Find out how to build a Content Type identifier recursively here.

SharePoint: How to set Id to just created SPContentType

September 15th, 2011 No comments

     When adding Content Type programmatically, sometimes you may need to set a certain Id to it, to ensure that other parts of the application can refer to the Content Type using a known identifier. If you use SharePoint 2010 you can get this done instantly, as SharePoint 2010 provides a handy SPContentType constructor, which accepts SPContentTypeId as parameter:

public SPContentType(SPContentTypeId contentTypeId, SPContentTypeCollection contentTypes, string name);

But if you use SharePoint 2007, you don’t have such a constructor or any built-in means to set the required identifier. Besides, the Id property of SPContentType appears to be read-only. After studying the SPContentType class with Reflector I’ve discovered that the Content Type id is stored in the private m_id property:

private SPContentTypeId m_id;

This means that we can use Reflection to set this property. Here is a special SetContentTypeId method I’ve implemented for doing that:

public void SetContentTypeId(SPContentType spContentType, SPContentTypeId contentTypeId)
{
    try
    {
        FieldInfo fi = typeof(SPContentType).GetField("m_id", BindingFlags.NonPublic | BindingFlags.Instance);                
        fi.SetValue(spContentType, contentTypeId);                
    }
    catch (Exception ex)
    {
        Console.Write(string.Format("Couldn't set content type id! {0}", ex.Message));
    }
}

The method accepts the instance of the SPContentType class and the required identifier as the instance of the SPContentTypeId class. Here is an example of use:

public void AddSomeNewContentType(SPWeb spWeb, SPList spList)
{
    SPContentType parentContentType = spWeb.AvailableContentTypes["some parent content type name"];
    if (parentContentType != null)
    {
        SPContentType spContentType = new SPContentType(parentContentType, spList.ContentTypes, "some new content type name");
        SetContentTypeId(spContentType, new SPContentTypeId("0x0100078C8B39671A4532AB9C5AB6DCB388A6")); // content type id you need has to be here, for example 0x0100078C8B39671A4532AB9C5AB6DCB388A6

        // set other properties of content type

        spList.ContentTypes.Add(spContentType);
        spContentType.Update();
        spList.Update();
    }
}

Please note that you must NOT use the SetContentTypeId method with existing built-in or earlier created Content Types. Otherwise, it may corrupt the SharePoint data integrity, especially when there are list items created based on the changed Content Type.

Note again, use the SetContentTypeId method ONLY immediately following the creation of a Content Type and ONLY before adding it to whatever collection of Content Types or before calling SPContentType.Update(). This is very important.

My next post describes how to add Content Type programmatically.

SharePoint: How to enumerate shared services

August 19th, 2011 No comments

     Continuing the previous article – How to get Shared Service name, I’m going to show how to enumerate all available Shared Services (or Shared Resource Providers, to be more precise). Here is the code (with Reflection, of course 🙂 ) :

public static void EnumerateSharedServices(SPSite spSite, Action<object> action)
{
    ServerContext serverContext = ServerContext.GetContext(spSite);
    object serverFarm = serverContext.GetType().GetField("m_ServerFarm", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(serverContext);
    var sspCollectionProp = serverFarm.GetType().GetProperty("SharedResourceProviders");
    var sspCollection = sspCollectionProp.GetValue(serverFarm, null) as IEnumerable;
    foreach (SPPersistedObject sharedServiceProvider in sspCollection)
        action(sharedServiceProvider);    
}

You have to use Reflection to get a property value of a Shared Service Provider; the general use of it is something similar to this:

EnumerateSharedServices(SPContext.Current.Site, delegate(object sharedResourceProvider)
{
    string sspName     = sharedResourceProvider.GetType().GetProperty("Name").GetValue(sharedResourceProvider, null).ToString(); 
    string sspUserName = sharedResourceProvider.GetType().GetProperty("UserName").GetValue(sharedResourceProvider, null).ToString();      
    string sspPassword = sharedResourceProvider.GetType().GetProperty("Password").GetValue(sharedResourceProvider, null).ToString();

    Console.WriteLine(string.Format("{0} [UserName: {1}, Passw: {2}]", sspName, sspUserName, sspPassword));

    // enumerate web applications that are served by the shared service
    var appCollection = sharedResourceProvider.GetType().GetProperty("WebApplications").GetValue(sharedResourceProvider, null) as System.Collections.IEnumerable;
    foreach (Microsoft.SharePoint.Administration.SPWebApplication app in appCollection)
        Console.WriteLine(string.Format("Web app: {0}", app.Name));
});

Let’s take a deeper look at EnumerateSharedServices. Its key object is serverFarm, which is an object of the internal ServerFarm class (the Microsoft.Office.Server.Administration namespace in the Microsoft.Office.Server DLL) containing a collection of Shared Resource Providers (collection of SharedResourceProvider objects). Navigating through this collection an user action is called for each provider.

To make this code more readable, I’d recommend using an object-wrapper for Shared Resource Provider. For example:

public class SharedServiceInfo
{
    protected readonly object _rawSharedResourceProvider;

    public string Name
    {
        get { return GetPropertyValue("Name").ToString(); }
    }
    public string UserName
    {
        get { return GetPropertyValue("UserName").ToString(); }
    }
    public string Password
    {
        get { return GetPropertyValue("Password").ToString(); }
    }
    public List<SPWebApplication> WebApplications
    {
        get 
        {
            IEnumerable iEnumerable = (IEnumerable)GetPropertyValue("WebApplications");
            return new List<SPWebApplication>(iEnumerable.Cast<SPWebApplication>()); 
        }
    }

    public SharedServiceInfo(object rawSharedResourceProvider)
    {
        _rawSharedResourceProvider = rawSharedResourceProvider;
    }

    protected object GetPropertyValue(string propName)
    {
        return _rawSharedResourceProvider.GetType().GetProperty(propName).GetValue(_rawSharedResourceProvider, null);
    }
}
public static void EnumerateSharedServices(SPSite spSite, Action<SharedServiceInfo> action)
{
    ServerContext serverContext = ServerContext.GetContext(spSite);
    object serverFarm = serverContext.GetType().GetField("m_ServerFarm", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(serverContext);

    var sspCollectionProp = serverFarm.GetType().GetProperty("SharedResourceProviders");
    var sspCollection = sspCollectionProp.GetValue(serverFarm, null) as IEnumerable;
    foreach (SPPersistedObject sharedServiceProvider in sspCollection)
        action(new SharedServiceInfo(sharedServiceProvider));
}

You can extend SharedServiceInfo with the properties you need.

Sample of usage:

EnumerateSharedServices(SPContext.Current.Site, delegate(SharedServiceInfo sharedServiceInfo)
{
    Console.WriteLine(string.Format("{0} [UserName: {1}, Passw: {2}]", sharedServiceInfo.Name, sharedServiceInfo.UserName, sharedServiceInfo.Password));

    // enumerate web applications that are served by the shared service
    foreach (Microsoft.SharePoint.Administration.SPWebApplication app in sharedServiceInfo.WebApplications)
        Console.WriteLine(string.Format("Web app: {0}", app.Name));
});