How to create a ContentHandler

From Sense/Net Wiki
Jump to: navigation, search
  •  
  •  
  •  
  •  
  • 100%
  • 6.0
  • Enterprise
  • Community
  • Planned

Overview

Content Types Definition
The content handler defines custom programmed logic of a content type implemented in .Net code (ie. C#). Attached business logic can be added to a content type by implementing a custom content handler. In this article we describe how to develop a content handler.

Steps

In this section I will show you how to create a new content handler step-by-step and explain the parts of the implementation.

1. Create a new class

2.Writing the minimal content handler

Rename your created class to MyContentHandler.cs and paste the code below. We put the ContentHandler attribute onto the class, inherit from GenericContent (or any class that is Node) and write the constructors:

using SenseNet.ContentRepository;
using SenseNet.ContentRepository.Storage;
using SenseNet.ContentRepository.Schema;
 
namespace MyContentHandler
{
    [ContentHandler]
    public class MyContentHandler : GenericContent
    {
        // =================================================================================================== Constructors
        // for initialize a new MyContentHandler instance
        public MyContentHandler(Node parent) : this(parent, null) { }
        // for initialize a new MyContentHandler inherited instance
        public MyContentHandler(Node parent, string nodeTypeName) : base(parent, nodeTypeName) { }
        // for initialize instance in the loading operation
        protected MyContentHandler(NodeToken nt) : base(nt) { }
    }
}

This is the minimal but usable content handler.

The first constructor calls the base with null parameter as nodeTypeName. This means that the name of the associated content type definition will be the same as the name of this class (in our case MyContentHandler). If you want to use a different name for the associated CTD, use a string in this parameter:

        // for initialize a new MyContentHandler instance
        public MyContentHandler(Node parent) : this(parent, "DifferentTypeName") { }

3. Implement properties

Let's implement two properties. Define a property that can store the birth date and another property that calculates the age from the birth date. The birth date will be stored in the database so mark this property with a RepositoryProperty attribute. The Age property stores nothing because it is calculated so it is a simple read only property:

        // =================================================================================================== Properties
        private const string BIRTHDATE = "BirthDate";
        [RepositoryProperty(BIRTHDATE, RepositoryDataType.DateTime)]
        public virtual DateTime BirthDate
        {
            get { return base.GetProperty<DateTime>(BIRTHDATE); }
            set { base.SetProperty(BIRTHDATE, value); }
        }
 
        private const string AGE = "Age";
        public virtual string Age
        {
            get { return (DateTime.Now - BirthDate).ToString(); }
        }

4. Implement property routing

Both new properties are readable so override the GetProperty method and place the appropriate routing logic inside the required switch block. Override the SetProperty method and place routing logic inside the switch block for the writeable BirthDate property. You should also not forget to call the base implementation in the default branches of the switch blocks.

        // =================================================================================================== Property routing
        public override object GetProperty(string name)
        {
            switch (name)
            {
                case BIRTHDATE:
                    return this.BirthDate;
                case AGE:
                    return this.Age;
                default:
                    return base.GetProperty(name);
            }
        }
 
        public override void SetProperty(string name, object value)
        {
            switch (name)
            {
                case BIRTHDATE:
                    this.BirthDate = (DateTime)value;
                    break;
                default:
                    base.SetProperty(name, value);
                    break;
            }
        }

5. Implement custom business logic

In the most common scenario you must validate the whole object considering all property values and other things when the object is being saved. This kind of validation can be very complicated but the skeleton is simple. Override the Save(NodeSaveSettings) method, check conditions and call the base if the whole content is valid:

        public override void Save(NodeSaveSettings settings)
        {
            if (CheckAllRequirements())
                base.Save(settings);
            else
                throw new InvalidOperationException("Content cannot be saved because it is invalid.");
        }
        private bool CheckAllRequirements()
        {
            //TODO: return false if a condition isn't satisfied.
            return true;
        }

6. Create the associated content type definition

Right click on the project node in the Solution Explorer containing your content handler and select Add / New item..., select Data / XML file, type into the Name textbox: MyContentHandlerCTD.xml, and click Add button.

Adding Content Type Definition

The minimal requirements are the following:

  • Unique name: MyContentHandler.
  • Existing parent type: GenericContent.
  • Existing content handler type: MyContentHandler.MyContentHandler (after installation this will exist).
  • Display name and description of the CTD
  • All fields in the Fields element
    • Field name that equals to the desired property or right property binding
    • Appropriate field type
    • Display name of the field
<?xml version="1.0" encoding="utf-8" ?>
<ContentType name="MyContentHandler"
             parentType="GenericContent" 
             handler="MyContentHandler.MyContentHandler" 
             xmlns="http://schemas.sensenet.com/SenseNet/ContentRepository/ContentTypeDefinition">
  <DisplayName>My Content Type</DisplayName>
  <Description>For demonstration only.</Description>
  <Fields>
    <Field name="BirthDate" type="DateTime">
      <DisplayName>Birth date</DisplayName>
    </Field>
    <Field name="Age" type="ShortText">
      <DisplayName>Age</DisplayName>
    </Field>
  </Fields>
</ContentType>

The Content Type Definition schema can help you to write easier the CTD if you copy it into the your project. Right click on the project node in the Solution Explorer, select Add / Existing Item, browse the schema that is in your Sense/Net code: "...\Source\SenseNet\ContentRepository\Schema\ContentTypeDefinition.xsd". If the schema is in your project and you edit an XML element that's namespace is the CTD's namespace, the Visual Studio code completion will work:

Editing CTD with its schema

7. Deploy your content handler

Compile the project (F6, or CTRL+SHIFT+B for newer versions of Visual Studio). If you implemented your content handler in a separate project other than your web application project copy your dll into the bin directory of the Sense/Net's web folder and start the portal. Follow the How to create a Content Type article to install your content type definition. After that you have finished the development and your Content Handler is usable in the Sense/Net content repository.

8. Test your content handler

You can test the following:

  • when you save a content of MyContentHandler type, the Age field will not be stored in the database even if it is presented with an editable control,
  • when you save a content of MyContentHandler type, your custom validation code may cancel saving and the provided error message will be displayed,
  • you can create a content of MyContentHandler type from code using strong type reference and you also get intellisense:
var node = new MyContentHandler(Node.LoadNode("/Root/Sites/Default_Site"));
node.Name = "mycontent";
node.Save();