Overview

NCMIS provides a class library for implementing CMIS services to a .NET-based CMS, in order to allow to expose the repository as a CMIS service. It also provides means for consuming an existing repository service.

Limitations

Installation and Usage

...

Starting the Service

The service is started by running the application. This being a WCF application it requires a host, such as a IIS or the Visual Studio Development Server. You may change the server settings in the Web tab of the project properties. By default the Visual Studio Development Server will be used.

If the application is running and showing the root directory it has successfully started. If you browse the a WCF service file (*.svc) the server will display an error, saying that a WSDL file could not be created. This is a known issue due to using the System.Stream class as parameter for service methods. However, this being a REST application there is no need for WSDL files and application will not suffer in functionality from this issue.

Browsing the Service

The service entry point is the service document. The service document is generated by the service method Service.GetServiceDoc (NCMIS.ServiceModel.Ra). The URI template of this method is "/servicedoc", meaning that it is accessed from the root of the service class, adding the string servicedoc, i.e. http://{host}/ServiceModel/Ra/Service.svc/servicedoc.
E.g. http://localhost/ServiceModel/Ra/Service.svc/servicedoc. Notice that this is the only method that does not expect a repository ID as an input parameter.

However you are not required to always start from the service document. The service document returns data that will instruct a client of how to handle the repository. If you are browsing the repository manually, for instance through a web browser, and you know the IDs of certain objects you may reach them by simply entering the correct URL template and ID. When using the sample repository the ID of the Root Folder will be rootfolder. To get the Atom Feed representing the children of the root folder you can call the NavigationService.GetChildren method (NCMIS.ServiceModel.Ra). The URI template of this method is "/{repositoryId}/children/{folderId}", meaning it could be accessed from e.g. http://localhost/ServiceModel/Ra/Service.svc/sr1/children/rootfolder. Note that adding a trailing slash will make this a different URI.

Object Model

The Object Model is the same for both the Restful AtomPub Binding and the Web Services Binding.
The most important classes are:
  • CmisObject – represents any of the base objects cmis:document, cmis:folder, cmis:policy, cmis:relationship, or any of their descendants.
  • CmisProperties
  • ContentStream
  • Rendition

CMIS Object Wrappers

There are a number of CMIS Object wrapper classes. The purpose of these classes is to add data to and group the returned CMIS objects in different constellations.

The wrapper classes are:
  • CmisObjectList
  • CmisObjects
  • CopyFlaggedDocumentId
  • ListOfIds
  • PathedCmisObject
  • PathedCmisObjectContainer
  • PathedCmisObjectList
  • TokedCmisObjectId
  • TokenedCmisObjectList
  • TokenedDocumentId

CMIS Property

The NCMIS.ObjectModel.CmisProperty class is a base class for CMIS Properties. Each CMIS object has a set of typed properties. Each property can be either single valued or multi-valued. The Value member of the CmisProperty descendants are always multi-valued and may be used for representing a single-valued or multi-valued CMIS Property.

The CmisProperty descendats are:
  • CmisPropertyBoolean
  • CmisPropertyDateTime
  • CmisPropertyDecimal
  • CmisPropertyInteger
  • CmisPropertyHtml
  • CmisPropertyId
  • CmisPropertyString
  • CmisPropertyUri

CMIS Property and its Desendants
Figure Sample Provider The Sample Provider classes.

The SingleValue member is a convenience method for returning the value of a single value CMIS property. If used on a multi-value CMIS property, the first value will be returned.

The ToXElement method returns the CMIS property instance as an XElement. Use this method instead of serializing using the DataContractSerializer, who will not serialize the CMIS Property instances correct. For instance the DataContractSerializer will not serialize properties as XML attributes.

Service Model

The service model contains 9 sets of services, defined by the CMIS specification. All services are compiled into a single WCF service, with interface NCMIS.ServiceModel.Ra .IService and service class NCMIS.ServiceModel.Ra .Service.svc . The service interface and service class are divided into partial class files for logical partitioning.

CMIS Service NCMIS WCF Service Class File
ACL Services NCMIS.ServiceModel.Ra.AclService.svc
Discovery Services NCMIS.ServiceModel.Ra.DiscoveryService.svc
Multi-Filing Services NCMIS.ServiceModel.Ra.MultiFilingService.svc
Navigation Services NCMIS.ServiceModel.Ra.NavigationService.svc
Object Services NCMIS.ServiceModel.Ra.ObjectService.svc
Policy Services NCMIS.ServiceModel.Ra.PolicyService.svc
Relationships Services NCMIS.ServiceModel.Ra.RelationshipsService.svc
Repository Services NCMIS.ServiceModel.Ra.RepositoryService.svc
Versioning Services NCMIS.ServiceModel.Ra.VersioningService.svc


Service Model

For information on the CMIS services, see the CMIS specification.

Registering the Service in Web.config

A single service (NCMIS.ServiceModel.Ra.Service), using a custom service behavior and a custom binding, is registered in the <services> section of Web.config.

<service behaviorConfiguration="NCMIS.ServiceModel.Ra.ServiceBehavior" name="NCMIS.ServiceModel.Ra.Service">
  <endpoint address="" behaviorConfiguration="NCMIS.ServiceModel.Ra.EndPointBehavior" binding="customBinding"
    bindingConfiguration="cmisBinding" contract="NCMIS.ServiceModel.Ra.IService" />
  <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
</service>

The service behavior (NCMIS.ServiceModel.Ra.ServiceBehavior) is registered in the <serviceBehaviors> section.

<behavior name="NCMIS.ServiceModel.Ra.ServiceBehavior">
  <serviceMetadata httpGetEnabled="true" />
  <serviceDebug includeExceptionDetailInFaults="true" />
</behavior>

The binding (cmisBinding) is registered in the <customBinding> section.

<binding name="cmisBinding">
  <webMessageEncoding webContentTypeMapperType="NCMIS.ServiceModel.Syndication.CmisContentTypeMapper, NCMIS,
    Version=3.5.0.0, Culture=neutral, PublicKeyToken=null" />
  <httpTransport manualAddressing="true" />
</binding>

Content Type Mapping

The purpose of this custom binding is to add a custom content type mapper, which is implemented in class NCMIS.ServiceModel.Syndication.CmisContentTypeMapper. Incomming messages with the CMIS Atom content type, e.g. application/atom+xml;type=entry are by default mapped to the XML message format (System.ServiceModel.Channels.WebContentFormat.Xml). this create a problem when using System.Stream as an input parameter. The stream must be mapped to the Raw message format (System.ServiceModel.Channels.WebContentFormat.Raw). The job of the CmisContentTypeMapper class is to simple remap the messages with CMIS Atom content types, and the XML content type (text/xml) to use the Raw message format. The reason to why the XML content type is remapped is that some clients use that content type when posting CMIS Atom entries. All other content type mappings are left untouched.

public override WebContentFormat GetMessageFormatForContentType(string contentType)
{
    if (contentType == CmisContentType.Entry || contentType == CmisContentType.Feed || contentType == CmisContentType.Xml)
    {
        return WebContentFormat.Raw;
    }
    else
    {
        return WebContentFormat.Default;
    }
}

Note that if you choose to modify the service methods to having System.Xml.Linq.XElement parameters instead of System.Stream parameters there is no need for this content type mapper.

XML Declaration

By default the XML declaration is not inlcuded in WCF REST output XML. To include the XML in the services reponses the NCMIS.ServiceModel.Ra.IncludeXmlDeclarationAttribute attribute is used. The attribute is applied to an oepration contracts, for instance:
[IncludeXmlDeclaration]
[OperationContract(Name = "getACL")]
Acl GetAcl(string repositoryId, string objectId);

The output XML will include the XML declaration:
<?xml version="1.0" encoding="utf-8" ?>
  <entry xmlns="http://www.w3.org/2005/Atom">

Required and Optional Parameters

Required parameters are received thorugh the UriTemplate of the WebGetAttribute/WebInvokeAttribute attribute. Optional paramters are sent in the query string or though the HttpContext class. The reason for this is that when using the same UriTemplate for both WebGet and WebInvoke, with optional parameters; the optional parameters will not be optional and they will be case sensitive. This due to an issue in WCF REST confirmed in REST/WCF UriTemplate Optional Querystring Parameters.

When a service method is the only method targeting an Atom feed or entry, the optional paramters are retreived from the query string, e.g. the GetObjectParents method:
[WebGet(UriTemplate = "/{repositoryId}/objectparents/?objectId={objectId}&filter={filter}&includeRelationships={includeRelationships}
&renditionFilter={renditionFilter}&includeAllowableActions={includeAllowableActions}&includeRelativePathSegment={includeRelativePathSegment}")]
public Atom10FeedFormatter GetObjectParents(string repositoryId, string objectId, string filter, string includeRelationships, string renditionFilter,
  bool includeAllowableActions, bool includeRelativePathSegment)

When a service methos is one of several methods targeting an Atom feed or entry. the optional parameters are retreived from the NCMIS.ServiceModel.Ra.RequestParameter class, e.g. the GetChildren method.
[WebGet(UriTemplate = "/{repositoryId}/children/{folderId}")]
public Atom10FeedFormatter GetChildren(string repositoryId, string folderId)
{
  int? maxItems = RequestParameter.MaxItems;
  int skipCount = RequestParameter.SkipCount;
  string orderBy = RequestParameter.OrderBy;
  string filter = RequestParameter.Filter;
  IncludeRelationships includeRelationships = RequestParameter.IncludeRelationships;
  string renditionFilter = RequestParameter.RenditionFilter;
  bool includeAllowableActions = RequestParameter.IncludeAllowableActions;
  bool includePathSegment = RequestParameter.IncludePathSegment;

  // Method implementation removed 

}

Class NCMIS.ServiceModel.Ra.RequestParameter is a convenience class that provides access to the NameValueCollection WebOperationContext.Current.IncomingRequest.UriTemplateMatch.QueryParameters. It defines a number of read-only properties, which correspond to the optional parameters defined by CMIS. E.g. the skipCount paramter can be retrieved in two ways:

// Using the RequestParameter class
int skipCount1 = RequestParameter.SkipCount;

// Using the WebOperationContext directly requires to validate that the retreived paramter is a valid integer
int skipCount2 = 0; // default value
string param = WebOperationContext.Current.IncomingRequest.UriTemplateMatch.QueryParameters["skipCount"];
int.TryParse(skipCount, out skipCount2 );

Creating Content

A client application may create content in a NCMIS repository by posting to a valid children feed. The service method listening at this feed is NavigationService.CreateChildren.

The content may be posted in either of two elements cmisra:content or atom:content. The element cmisra:content takes precedence over atom:content. If cmisra:content is used the expected format is as described in 3.4.1.3.6 cmisra:content (CD07). This does not break the specification of 3.4.1.3.6 cmisra:content (CD04) since it does not specify the internal structure of the cmisra:content element. The response content will always be in an atom:content element.

<cmisra:object xmlns:cmisra="http://docs.oasis-open.org/ns/cmis/restatom/200908/">
  <cmis:properties xmlns:cmis="http://docs.oasis-open.org/ns/cmis/core/200908/">
    <cmis:propertyId propertyDefinitionId="cmis:objectTypeId">
      <cmis:value>cmis:document</cmis:value>
    </cmis:propertyId>
  </cmis:properties>
</cmisra:object>
Listing Create Document cmisra:object Example cmisra:object element for creating a new cmis:document.

<cmisra:content xmlns:cmisra="http://docs.oasis-open.org/ns/cmis/restatom/200908/">
  <cmisra:mediatype>text/plain</cmisra:mediatype>
  <cmisra:base64>c2FtcGxlIHRleHQ=</cmisra:base64>
</cmisra:content>
Listing Create Document cmisra:content Example cmisra:content element for creating a new cmis:document.

CreateChildren

The CMIS specification states in 3.8.2 Folder Children Collection that a post request can be handled by either of following services:
  • createDocument
  • createFolder
  • createPolicy
  • moveObject
  • addObjectToFolder

The URI of an Atom feed must be consistent, which means that the mandatory parameters of the different service method targeting the same feed cannot differ. So we cannot have all the above methods targeting the same feed, since this would mean the feed would have multiple URIs and there is no way of telling a potential client that the URI of the feed is different depending on which method the client is invoking.

The solution used is to let the NavigationService.CreateChildren method handle all POST request to the children feed, and then call one of the above methods depending on the value of the optional parameters.

Syndication

The NCMIS.ServiceModel.Syndication namespace handles the serialization of CLR types to Atom Syndication XML and vice versa. Most of the classes derives or make use of the System.ServiceModel.Syndication namespace. These classes require REST Starter Kit.

Class Description
CmisServiceDocument
CmisSyndicationFeed Atom Feed with the extension cmisra:numItems.
CmisSyndicationItem Atom Entry with the extensions cmisra:children, cmisra:object, cmisra:pathSegment, cmisra:relativePathSegment, cmisra:type, and cmisra:content.
CmisWorkspace AtomPub Service Document with the extensions cmisra:collectionType, cmisra:repositoryInfo and cmis:uritemplate.


Sample Provider
Figure Syndication Classes The Syndication classes.

CmisSyndicationItem

Class NCMIS.ServiceModel.Syndication.SyndicationItem is models a CMIS Atom Entry. It is a convenience class for creating CMIS Atom entries and handling the CMIS defined Atom extensions. It also defines a number of static methods that operate on an System.ServiceModel.Syndication.SyndicationItem. These are for instance GetLink, which retrieves a CMIS specific link from a SyndicationItem; GetContentStream, which retrieves the content element from the SyndicationItem (cmisra:content if exists, else atom:content).

Retreiving content

The content of a CMIS Atom entry is either stored in a cmisra:content element or a atom:content element. The method GetContentStream will retreive the content from any of the two elements, with the cmisra:content having precedence over atom:content. The return type is NCMISObjectModel.ContentStream .

Retreiving a CMIS object

The CMIS object of an CMIS Atom entry is stored in the cmisra:object element. The method GetObject retreives it and its properties.

Retreiving Atom Links

The Atom links are a part of the Atom specification, however CMIS specifies a collection of specific link types. Each of these predefined links can be retreived from an CMIS Atom entry using the GetLink method. All valid LinkRels are stored in the class NCMIS.ObjectModel.Rest.CmisLinkRel.

XML Namespaces

The CMIS specification describes 4 namespaces, in CMIS 3.1.1, to be used when referring to XML or XML schema elements. (A fifth element, cmism, is not explicitly stated in the text, however used in the examples and XML files).
The syndication maps these namespaces by the addition of the following namespaces (prefix in bracket):
  • http://docs.oasis-open.org/ns/cmis/core/200908/ (cmis)
  • http://docs.oasis-open.org/ns/cmis/messaging/200908/ (cmism)
  • http://www.w3.org/2005/Atom (atom)
  • http://www.w3.org/2007/app (app)
  • http://docs.oasis-open.org/ns/cmis/restatom/200908/ (cmisra)

All namespaces are added in the constructor of each syndication class to ensure that all namespaces are always present during serialization to XML. E.g. the following is an example of the constrctor of CmisSyndicationItem.

public CmisSyndicationItem()
{
    this.AttributeExtensions.Add(new XmlQualifiedName("cmis", "http://www.w3.org/2000/xmlns/"), CmisNs.Cmis);
    this.AttributeExtensions.Add(new XmlQualifiedName("cmism", "http://www.w3.org/2000/xmlns/"), CmisNs.Cmism);
    this.AttributeExtensions.Add(new XmlQualifiedName("atom", "http://www.w3.org/2000/xmlns/"), CmisNs.Atom);
    this.AttributeExtensions.Add(new XmlQualifiedName("app", "http://www.w3.org/2000/xmlns/"), CmisNs.App);
    this.AttributeExtensions.Add(new XmlQualifiedName("cmisra", "http://www.w3.org/2000/xmlns/"), CmisNs.Cmisra);
}

Provider Model

The NCMIS Provider Model implements the .NET Provider Model Design Pattern.

Each provider consists of a set of 6 classes.

Provider Class Naming Convention Description
Provider Manager [Service]Providermanager, e.g. AclProviderManager Singleton class that initializes the default provider and the provider collection.
Provider Configuration [Service]ProviderConfiguration, e.g. AclProviderConfiguration Provides access to the provider settings collection in the configuration file of the application.
Provider Collection [Service]ProviderCollection, e.g. AclProviderCollection Provides an indexer for getting a provider class by its configured string name.
Provider Base [Service]ProviderBase, e.g. AclProviderBase An abstract class providing method signatures to be implemented in derived classes.
Provider Class [Function][Service]Provider, e.g. SampleAclProvider Concrete provider class that inherits from the abstract provider class and implements its methods.
Configuration File Usually called Web.config or App.config. The configurations file for the current application.


ACL Provider
Figure ACL Provider The Provider Model examplified by the ACL Provider implementation.

Sample Provider

The sample provider exposes a sample repository as CMIS services. It uses an in-memory data store, which will be reset each time the hosting web server is reset. It consists of 10 classes, which each derives from one of the Provider Bases classes, with exception from the NCMIS.Provider.SampleProvider.SampleFactory class. This is a singleton class that creates sample objects and holds the internal data storage.

Sample Provider
Figure Sample Provider The Sample Provider classes.

How to Implement a Custom Provider

When implementing a custom provider the Sample Provider can be used as a reference. Basically you need to implement the same provider classes, but exchange the implementation of the provider methods to your own.
  1. Copy the SampleProvider structure from folder {NCMIS root}\Provider\SampleProvider\ to a new folder {NCMIS Root}\Provider\{Your New Folder}\.
  2. Replace all instances of phrase "Sample" with the name of your provider, e.g. replace "SampleAclProvider" with "EPiServerAclProvider".
  3. Add your provider to the configuration-file (e.g. Web.config) by adding it to the provider element of each CMIS provider (there are 9 providers in total). Make sure to change the default provider to your own provider if you have multiple providers added.
  4. Implement your provider methods.

<AclProvider default="EPiServerAclProvider">
  <providers>
    <add name="EPiServerAclProvider" type="NCMIS.Provider.EPiServerProvider.EPiServerAclProvider, NCMIS" />
    <add name="SampleAclProvider" type="NCMIS.Provider.SampleProvider.SampleAclProvider, NCMIS" />
  </providers>
</AclProvider>
Listing ACL Provider The ACL provider as in the configuration file. Each of the 9 providers has a similar code block.

Last edited Mar 19, 2010 at 10:36 AM by dennisnk, version 12

Comments

No comments yet.