Introducing ContentService aka the v6 API

Tuesday, January 22, 2013 by Niels Hartvig

The website and the source code used in this tutorial is available on GitHub.

In this little tutorial, I’ll show you how the new APIs in Umbraco 6 works, by importing a CSV file with products into Umbraco nodes.

For a start, I’ve downloaded Umbraco 6 (currently the RC) and installed the Basic starter kit with the “Sweet As” skin. That gives me a nice baseline for a simple site and also confirms that Umbraco 6 is compatible with most of the existing packages.

The products that I want to import, comes from our own ERP in the HQ. We use a SaaS product called E-conomic (which is awesome, btw) and it’s easy to export our products into an Excel/csv file. For simplicity, I’ve kept it to a very simple model where each product has a Product Id, a Name, a Group and a price.

So to prepare for the import, I’ve made three Document Types:

  • A “Store” document type, which is basically just a container for the products
  • A “Product Group” that lives beneath the store. It has a single property which is the e-conomic (our ERP) GroupId. As we only had seven product groups, I’ve just created them manually.
  • A “Product” that is a child of “Product Group”. To match my exported data it has a Product Id, a Name and a Price. The nice thing about Umbraco is that I can always enrich the boring ERP product data with Umbraco properties such as photos and Rich text descriptions (including embedded YouTube videos to show off the product).

Let’s get started

As I won’t go into details about parsing CSV files (you can look at the full source for that), the code shown in this tutorial is the simplified methods that takes a Name, Product Id, Price and a Product Group and creates it as a node. But as I want to be able to upload a CSV file, I’ll add a simple .NET UserControl to the Dashboard inside Umbraco which contains a File Control and a button. It’ll look something like this:

Screen Shot 2013-01-22 at 12.10.30 PM

If you’ve never played with customizing the Umbraco Dashboard, it’s as easy as editing the /Config/Dashboard.config file. In that file you can add tabs to sections, place User Controls inside the tabs and even add permissions to what type of users can see what tabs. The details, however is a different story – this tutorial are supposed to be about the new API!

Meet ContentService

The new API consists of a number of services which is your “gateway” to Umbraco data. In this tutorial we’ll look at the ContentService which – as you may have guessed – is our “gateway” to interact with content in Umbraco.

When you’re in a User Control or a Template, accessing the ContentService is easy and we’ll get to that in a minute. But before we show you the easiest way, it’s good to know that there are various ways to get to the ContentService. Most of all it depends on whether you’re already in a context of an Umbraco page (technically in an UmbracoContext - for instance in a Template or a view) or if you’re in a Console App (yes, that works!). No matter where you are you can always get to the ContentService via ApplicationContext.Current.Services.ContentService. The ApplicationContext as well as the various Services lives in the Umbraco.Core namespace and is part of the Umbraco.Core.dll. The UmbracoContext and UmbracoUserControl is found in the Umbraco.Web namespace which is a part of the “umbraco.dll” assembly (and that is the name – not Umbraco.Web as that would break backwards compatibility with v4).

So to get started, you’ll need to make a reference to “umbraco.dll” in your Visual Studio project. When you use the ContentService through a User Control, it’s also a good idea to make a reference to “Umbraco.Core.dll”. This allows you to make your User Control inherit from “Umbraco.Web.UmbracoUserControl” instead of the normal “System.Web.UI.UserControl” and gives you super easy access to local Umbraco data as well as the ContentService.

In this example I am using an UmbracoUserControl, but be aware that all the examples could also work in a webservice, a console app or even a Windows Service as long as you can get hold of a valid ApplicationContext.

Ok, enough boring theory – let’s import some products

As you may remember, the original purpose of this tutorial was to show you how simple it is to import our ERP product data as Umbraco Content items (nodes). Now that we’ve learned about the ContentService, UmbracoUserControl and our product Document Type, it’s really easy to creating content items:

private void ImportProduct(int productId, string name, int productGroup, int price)
    {
        // Get the Umbraco Content Service
        var contentService = Services.ContentService;
        var product = contentService.CreateContent(
            name, // the name of the product
            1000, // the parent id should be the id of the group node 
            "product", // the alias of the product Document Type
            0);

        // We need to update properties (product id, original name and the price)
        product.SetValue("productId", productId);
        product.SetValue("originalName", name);
        product.SetValue("priceDKK", price);

        // finally we need to save and publish it (which also saves the product!) - that's done via the Content Service
        contentService.SaveAndPublish(product);
}

Because we’re in an UmbracoUserControl, we can access the ContentService via a local variable called “Services”. The ContentService has a handy method to create content called “CreateContent” which takes 4 parameters:

  • Name of the new Content item being created
  • The parent id of the new Content item
  • The alias of the document type
  • The id of the user creating the content

The CreateContent method returns a Content item (an IContent) which also allows us to easily edit custom properties via the SetValue method that simply takes the alias of your custom property and a value (Object).

Once we’ve populated our content item, it’s time to Save and maybe also Publish it. It’s as simple as passing our Content object (product) to the SaveAndPublish method of our ContentService.

If you’re used to the old APIs, then you know that this wasn’t needed as data was saved the moment you assigned values to the properties. This caused a lot of database queries and really not considered good practice. With the new API, it’s not until you pass the Content item to the ContentService that a database query is made within a single transaction. With the old API the code above would generate at least five queries but maybe even more if the cache wasn’t up to date.

In the old API you also had to call two different publish methods – one that prepared the document for publish and one that refreshed the cache. With the ContentService, all you need to do is call the SaveAndPublish() method and you’re in business.

The Visual Studio sample project contains a more thorough example where I also update existing Content items and use the fabulous uQuery to look for data.

More to come

In this little tutorial we’ve shown you the first glimpse of what the ContentService can do for you and I’ve deliberately tried to keep it very simple. But the ContentService is more than just simple save and publish - it also support transactional bulk saves as well as bulk publishes. I’ll show you how we can easily refactor the code to support this in a coming tutorial.

Is there other examples or things you’d like to see covered? Let me know in the comments!

12 comment(s) for “Introducing ContentService aka the v6 API”

  1. Gravatar ImageJon Carlos Says:

    Great job guys this it really tidy.

    Just 1 question:
    When SaveAndPublish is called and the transaction fails for some reason does the whole commit stop? i.e. No node is created at all in your example above?

    I'm just checking if there is any case where a partial commit of a node to the DB can happen?

    This was my biggest problem in the old API and if it's sorted in this one I can't wait to get stuck in! :)

    Jon

  2. Gravatar ImagePinal Bhatt Says:

    Good example to start with but it would be better if we gets some sample code for MVC and how can the content service accessed via console app or web service projects in VS?

  3. Gravatar ImageStefan Bohlin Says:

    Isn't the correct way to call the ContentService this: var contentService = new ContentService(); og is it just me that can't make it work your way?

    Other than that, great API! I love the no more "have to update cache separately". I'm lovin' it!

  4. Gravatar ImageAsbjørn Says:

    I'm missing two things in the new API: Members and Relations? Where are they? Or have they not been implemented yet? From the issue tracker it looks like they have been implemented, but I can't find them...

  5. Gravatar Imageed Says:

    Is the publishing in the cms also faster with alot of childnodes?

  6. Gravatar ImageMagnus Says:

    Good start. Some MVC examples would be great to have a look at later and maybe how to use partial views.

  7. Gravatar ImageMorten Christensen Says:

    @Jon If the transaction should fail then nothing will be committed within the scope of that transaction. In this example a SaveAndPublish is done for each new content item, so there is only one item per transaction, but when you start using the bulk operations you will get more items per transaction.

    @Stefan Yes, its also possible to new up a ContentService as per your example: var contentService = new ContentService();
    But the recommended way is to get the service through the ApplicationContext. If you are in a Controller (which inherits from PluginController or SurfaceController) or UserControl (that inherits from the UmbracoUserControl) you have a Services property available, which gives you access to all the public services.

    @Asbjørn Members and Relations are coming in v6.1. The tasks you can see on the tracker is only the internal stuff, so the missing part is a public service for each of these similar to the ContentService.

    @Ed The underlaying publishing (which is basically the updating of the XML cache) remains the same, so you probably won't see to much of a difference there.

  8. Gravatar ImageAlok Singh Says:

    I have been trying to create testing environment for creating and deleting node/ content. I am using this code :

    private readonly umbracoServices.ContentService contentService = new umbracoServices.ContentService();
    var content = this.contentService.CreateContent(category.Name, category.SectionId, DocumentTypes.Category, 0);

    For some reason the HttpContext field is empty in content service while debugging. How should I go about it ?

    If you need any more info please feel free to ask.

    Thanks

  9. Gravatar ImageKevin Says:

    Does the new umbraco 6 api still need httpcontext to work, or has this limitation been fixed?

  10. Gravatar ImageMatt Quinn Says:

    Looks like a great addition, but I'm encountering an issue with the Auditing.

    I'm following a pattern similar to the above to iterate through some data and create content for each row.

    The code snippet is below:

    var contentService = new ContentService();
    SqlSyntaxContext.SqlSyntaxProvider = new SqlServerSyntaxProvider();

    var courseHolder = contentService.GetById(1200);

    foreach(DataRow row in modules.Tables[0].Rows) {

    var course = contentService.CreateContent(
    row["name"].ToString(),
    courseHolder, /* courses node */
    "CourseDetail",
    0);

    course.SetValue("courseTitle", row["name"]);
    course.SetValue("contactTelephone", row["cicontacttelephone"]);
    course.SetValue("contactEmail", row["ciContactEmail"]);
    course.SetValue("beginDate", row["startdate"]);
    course.SetValue("endDate", row["enddate"]);
    course.SetValue("courseRef", row["reference"]);
    course.SetValue("courseLevel", row["level"]);
    course.SetValue("courseDuration", row["weeks"]);
    course.SetValue("about", row["ciabout"]);
    course.SetValue("assessment", row["ciassessment"]);
    course.SetValue("entryReqs", row["cientry"]);
    course.SetValue("progression", row["ciprogression"]);
    course.SetValue("contact", row["cicontact"]);

    contentService.SaveAndPublish(course);
    }

    It's definitely hooking up to the DB as I can inspect the returned courseHolder and it looks spot on. When I call CreateContent on the contentService however I get the a NullReferenceException buried in the stack where it's attempting to audit the new content and failing...

    [NullReferenceException: Object reference not set to an instance of an object.]
    Umbraco.Core.Auditing.DataAuditWriteProvider.WriteEntry(Int32 objectId, Int32 userId, DateTime date, String header, String comment) +61
    Umbraco.Core.Auditing.AuditTrail.AddEntry(AuditTypes type, String comment, Int32 userId, Int32 objectId) +139
    Umbraco.Core.Auditing.Audit.Add(AuditTypes type, String comment, Int32 userId, Int32 objectId) +62
    Umbraco.Core.Services.ContentService.CreateContent(String name, IContent parent, String contentTypeAlias, Int32 userId) +355

    The exception itself is occurring within the CreateContent line. I've ensured none of the passed parameters are null, userId 0 exists etc, as does a DocumentType of CourseDetail.

    Any thoughts?

  11. Gravatar ImageSagan Internet Marketing Says:

    Thanks for this example. It would be useful to see an example of updating content instead of just creating new content. Using Save and Publish creates duplicate content even when using

    post = contentService.GetById(existingPostingId.Value);

    What am I doing wrong?

  12. Gravatar ImageMark Says:

    Very useful - thanks.

    I have adapted the code to sit in a web service - had to use

    var contentService = new ContentService();

    All nodes are being created correctly in the right place and are published, but are not visible in the site.

    When I find the nodes in the content tree I see the message

    Oops: this document is published but is not in the cache (internal error)

    I've found a few others having the same problem when publishing via a console app - apparently because they do not inherit the web context. Is the same true of web services?

    Thanks in advance

Leave a comment