In this article, I’ll show you how to create a document type with properties and then use it to create a content page in the CMS. Think of it like you’re making a cake…
Ingredients for Our Content Cake
Like a cake, creating content requires ingredients and a recipe. Here’s what you’ll need:
- Several data types created from property editors
- A property type group to organize properties in a tab
- A document type to add these tasty data types to
- A template to serve up this lovely dish
- A startup routine to ensure our content is created in the CMS
Umbraco Services Overview
But first, a bit of background before we dive into the code.
In the beginning, there was Umbraco 3—a beacon of hope in the messy world of CMSs. Then along came Umbraco 4, bringing with it the Umbraco Library, and a way of creating content programmatically. After the chaos of Umbraco 5 (we don’t talk about 5 😐), Umbraco 6 and 7 arrived with improved programmatic content creation organised into services and thus order was rising from the chaos. Now, like a proud beast climbing the food chain, Umbraco has a full set of powerful services that can pretty much let you do whatever you want programmatically. That’s the wizardry we’ll use to magic up our content cake.
This article assumes you have some experience with Umbraco services and dependency injection. If not, you might want to have a look at this documentation from Umbraco which gives a good introduction on what dependency injection is and how you can use it with Umbraco.
Services Galore
There are many services in Umbraco. To save you from digging through the complete list (which you can find here), we’ll use the following to whip up our content cake:
- ContentTypeService
- DataTypeService
- FileService
- ContentService
- PropertyEditorCollection (not a service, but we need it)
You say Potato, I say… err.. Potato
Now here’s an oddity for you if you’re not familiar with the history of Umbraco. For some reason with the advent of Umbraco 8, the decision was made to rename Document Types to Content Types as part of an initiative to simplify the terminology being used. However, nobody liked that, so from Umbraco 9 onwards, content types were switched back to being called Document Types and everyone rejoiced and danced in the streets.
But…
This wasn’t the case for the Content Type service which is still called the ContentTypeService and uses ContentTypes, rather than calling it the DocumentTypeService using DocumentTypes. So during this article you may see the interchanging use of Content Types and Document Types and they are referring to the same thing as they are, actually, the same thing.
Get Your Pinny On: Injecting Services
To get these services working, you need to set up dependency injection. We've not got the full injection code here, but these are the services that we're going to use and the variable names that you'll see in the examples below.
private readonly IContentTypeService _contentTypeService;
private readonly IDataTypeService _dataTypeService;
private readonly IShortStringHelper _shortStringHelper;
private readonly PropertyEditorCollection _propertyEditors;
private readonly IFileService _fileService;
private readonly IContentService _contentService;
Preparation: Sorting Out the Template
A Template is assigned to a document type so that when a content using that document type is viewed on a website, the template will contain the code needed to render that page.
So when we create our template for our document type, it can't be empty as that would just render a blank page, so we need to set the content of the template file with some Razor code so we can see stuff. There's many ways of doing this, but I'm using Razor code embedded in a resource file (TestPageHTML.TestPageHtmlContent
), but you could reference a static file, have all your code as a very long string (no, maybe not do that) or whatever works best for you.
ITemplate testPageTemplate = _fileService.GetTemplate(alias);
if (testPageTemplate == null)
{
testPageTemplate = new Template(_shortStringHelper, name, alias)
{
Content = TestPageHTML.TestPageHtmlContent
};
_fileService.SaveTemplate(testPageTemplate);
}
On Your Marks: Creating the ContentType and PropertyGroup
There is always the possibility that we've been here before, so perhaps we already have a content type (document type) in the CMS or one has been created coincidentally with the same name. Safety first then, let's check if it already exists and if not, we can safely use our alias to create our new content type.
var name = "Test Content Page";
var alias = name.ToSafeAlias(_shortStringHelper);
if (_contentTypeService.Get(alias) == null)
{
var testContentPage = new ContentType(_shortStringHelper, -1)
{
Alias = alias,
Name = "Test Content Page",
Icon = "icon-map",
AllowedTemplates = new [] { testPageTemplate },
DefaultTemplateId = testPageTemplate.Id
};
}
As we can, we might as well add our template that we created earlier and set it to be the default so that's all ready to go when we create our content later on.
We could save our content type now using the ContentTypeService, but let's add our properties first.
Get Set: PropertyTypes and DataTypes
As we know, in Umbraco we have a whole smorgasbord of built in Umbraco property types, such as the Rich Text Editor, dropdowns, textboxes, etc and other wonderful Community created options such as the excellent Contentment package from Lee Kelleher.
We’ll need to use property editors to create data types that we can then add to our property group. Property editors can only be added to a property group by using a PropertyTypeCollection which we’ll need to create before we can start.
var propertyTypes = new PropertyTypeCollection(false);
Next, we’ll create our data types that we want on our new content type. For this example, we’re going to create a textstring to for a title to be put in it and an RTE for the main content on the body of the page.
There’s a few stages to accomplish this. First of all, you need to get the DataType that you want to use by getting it from the PropertyEditorCollection. Whilst not strictly speaking a service, you can inject the PropertyEditorCollection with your other services and as you might of guessed from the name, this brings back a collection of the property editors that are available to you in Umbraco, that all use the IDataEditor interface. By getting the editor from this collection, you can then use it create a new DataType.
if (_propertyEditors.TryGet("Umbraco.TextBox", out IDataEditor? textboxPropertyEditor))
{
var textBoxConfig = new TextboxConfiguration();
var textBoxDataType = new DataType(textboxPropertyEditor, _configurationEditorJsonSerializer)
{
Name = "Test - Title",
Configuration = textBoxConfig
};
_dataTypeService.Save(textBoxDataType);
var propertyTypeTitle = new PropertyType(_shortStringHelper, textBoxDataType, "title")
{
Name = "Title"
};
propertyTypes.Add(propertyTypeTitle);
}
In the above code we’re looking for an Umbraco.Textbox property editor that, it we have, we can use to create a DataType. To create the DataType, you need to have the correct configuration. This contains things like MaxChars, etc. that you can set now if you want to.
Once we have the populated DataType object, we can then save it using the DataTypeService.
Now we have our DataType in Umbaco, we can create our PropertType and add this to the PropertyTypeCollection we created earlier.
Congratulations, you now have your first PropertyType in the collection! You can now repeat as many times as you wish for each property and here’s an example of doing the same thing but with an RTE.
if (_propertyEditors.TryGet("Umbraco.TinyMCE", out IDataEditor? richTextPropertyEditor))
{
var richTextConfig = new RichTextConfiguration();
var richTextDataType = new DataType(richTextPropertyEditor, _configurationEditorJsonSerializer)
{
Name = "Test - Text",
Configuration = richTextConfig
};
_dataTypeService.Save(richTextDataType);
var propertyTypeText = new PropertyType(_shortStringHelper, richTextDataType, "text")
{
Name = "Text"
};
propertyTypes.Add(propertyTypeText);
}
Unless you like your content types chaotic and enjoy giving editors a headache, it’s not a bad idea to organise your properties by tabs or sections. As we’re responsible developers and like to be nice, we’re going to create a property group for our data types that will eventually be added as a tab on the document type.
var propertyGroup = new PropertyGroup(false)
{
Name = "Content",
SortOrder = 1,
Alias = "content"
};
propertyGroup.PropertyTypes = propertyTypes;
Now we have all our properties in our collection, we can add them to the ContentType and save it using the ContentTypeService
.
testContentPage.PropertyGroups.Add(propertyGroup);
_contentTypeService.Save(testContentPage);
Bake! Adding the Content
We now have all we need to create the content in the CMS, a shiny new content type with properties and new data types, all ready to go.
To create the content page, we’ll need to:
- Find where we want to add the content.
- Set permissions so this new ContentType can be created.
- Set up the content.
- Save and publish the content.
So the first thing to do is get the place in the content structure that you want this new bit of content to be created. For simplicities sake, we’re going to assume that we have a site with a hompage at the root and we’re going to create it under there.
First of all, we need to get that content, find it’s ContentType and add permissions for our new one to be created under it.
var homePage = _contentService.GetRootContent().FirstOrDefault();
var homepageContentType = _contentTypeService.Get(homePage.ContentType.Key);
var testContentType = _contentTypeService.Get(testContentPage.Alias);
if (homepageContentType != null && testContentType != null)
{
var allowedContentTypes = homepageContentType.AllowedContentTypes?.ToList();
allowedContentTypes.Add(new ContentTypeSort
{
Alias = testContentType.Alias,
SortOrder = allowedContentTypes.Count() + 1,
Id = new Lazy<int>(() => testContentType.Id)
});
homepageContentType.AllowedContentTypes = allowedContentTypes;
_contentTypeService.Save(homepageContentType);
}
Now that we’ve added our new ContentType as an allowed thing to be created under our Homepage ContentType, we can add the new content, set some values and then save and publish it.
var testPage = new Content("Test", homePage, testContentType);
testPage.SetValue("title", "Test Title");
testPage.SetValue("text", "Test Content");
_contentService.SaveAndPublish(testPage);
Cherry on Top: Create on Startup
If you want to enforce this setup when Umbraco starts, you can add a NotificationHandler
and reference it in Program.cs
(or Startup.cs
).
public class UmbracoApplicationStartingNotificationHandler : INotificationHandler<UmbracoApplicationStartingNotification>
{
private readonly ISetupService _setupService;
public UmbracoApplicationStartingNotificationHandler(ISetupService setupService)
{
_setupService = setupService;
}
public void Handle(UmbracoApplicationStartingNotification notification)
{
_setupService.StartUp();
}
}
For extra fun, you could have it add random stuff on April 1st before deleting it, but you didn’t get that idea from me…
I hope this has been helpful and at the very least gives you a start in writing what you need to create content programmatically in Umbraco. If you’ve got it working, go reward yourself with a nice coffee… and some cake!
Top comments (1)
This looks like a great resource to replace the ageing packageMigrationPlans.. though maybe the setupService could be wrapped as a MigrationPlan.. so that we inject into the UmbracoKeyValues table and use the core checks to see if it's already been run? Also adding on the possibility for managed upgrades later? :-)