Nibble

Single page CRUD app with Umbraco 6

A little experiment in using Umbraco as a datasource for a single page CRUD app. Making use of the new Umbraco APIs (ContentService and UmbracoAPIController).

 

Umbraco setup

Using Umbraco 6.1 beta (since that includes the UmbracoApiController).

Default rendering engine is set to MVC (this is setup in the /config/umbracoSettings.config).

Two doctype are in place

    • HomePage (with a property footer of type richtext editor)
    • Status (with a property message of type textbox multiple)

So this is how the content structure looks (and with the app we’ll be able to list/create/update/delete status content docs)

image

Creating the model

The data that we’ll pass between client and server, It’s a pretty simple model with only a couple of properties (the id, need that to be able to edit, and 2 other properties that will be used for the content name and the message field).

   public class Status
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public string Message { get; set; }
 
    }

 

Creating the API

Since we want to create a RESTful application the obvious choice would be to use WEB API and use a API controller, you can do that in Umbraco, there is a base Web Api controller you can inherit from that will expose Umbraco related services and objects, the UmbracoApiController (available since 6.1 beta)

It’s important that the controller class name is suffixed with "ApiController"

public class StatusApiController : UmbracoApiController
{
}

With that in place we can start adding the methods that we’ll need

Fetching documents

For querying published content we’ll be working with strongly typed Content (of type IPublishedContent).

Getting all status docs underneath a certain parent and mapping them to the type of our model

 public IEnumerable<Status> GetAllStatuses(int parentId)
        {
            UmbracoHelper help = new UmbracoHelper(UmbracoContext);
            return help.TypedContent(parentId)
                .Children
                .Where(c => c.DocumentTypeAlias == StatusDocTypeAlias)
                .Select(obj => new Status()
            {
                Title = obj.Name,
                Message = obj.GetPropertyValue<string>(MessagePropertyAlias),
                Id = obj.Id
            });
        }

 

Getting a doc by it’s id and mapping it to our model type

        public Status GetStatus(int id)
        {
            UmbracoHelper help = new UmbracoHelper(UmbracoContext);
            var content  = help.TypedContent(id);
            return new Status
            {
                Id = content.Id,
                Title = content.Name,
                Message = content.GetPropertyValue<string>(MessagePropertyAlias)
            };
        }

Create/Update/Delete documents

For this we’ll make use of the new APIs available in v6, the new API consists of a number of services since we are interacting with content we’ll be using the ContentService. Because our controller inherits from the UmbracoAPIController we can easily access the ContentService with Services.ContentService

Creating a new doc (+ setting prop value + save and publish)

 public Status PostStatus(Status status, int parentId)
        {
            var cs = Services.ContentService;
            var content = cs.CreateContent(status.Title, parentId, StatusDocTypeAlias);
            content.Name = status.Title;
            content.SetValue(MessagePropertyAlias, status.Message);
            cs.SaveAndPublish(content);
            status.Id = content.Id;
            return status;
        
        }

Updating a doc

 public Status PutStatus(Status status)
        {
            var cs = Services.ContentService;
            var content = cs.GetById(status.Id);
            content.Name = status.Title;
            content.SetValue(MessagePropertyAlias, status.Message);
            cs.SaveAndPublish(content);
            return status;
 
        }

Deleting a doc

 public void DeleteStatus(int statusId)
        {
            var cs = Services.ContentService;
            var content = cs.GetById(statusId);
            cs.Delete(content);
        }

 

The App

To create the actual app that will make use of our api   I used AngularJS, that made it pretty simple.

To show a small snippet (http delete against the DeleteStatus method)

            $http({ method: ‘DELETE’, url: ‘/umbraco/api/StatusApi/DeleteStatus/’, params: { statusId: $routeParams.Id } })
                .success(function (data) {
                    
                    $rootScope.alert = true;
                    $rootScope.alertMessage = "Status removed";
                    
                    $location.path(‘/list’);
                })
                .error(function () {
                    $scope.error = "An Error has occured while deleting!";
                });

For an intro on AngularJS check http://www.egghead.io/ 

For the UI Bootstrap is used

Source code

Complete example site and source code is available on github

https://github.com/TimGeyssens/UmbracoSinglePageCrudApp

33 Comments so far

  1. Lennart Stoop on April 30th, 2013

    Nice post, Tim. It really is great to see all the new ASP.NET stuff being integrated in Umbraco so quickly!

    Will the UmbracoApiController somehow support custom routing or is it possible to use attribute routing for example?

    Given your SPA example I wonder how well the UmbracoApiController would integrate with Hot towel SPA - extending the BreezeJS API controller. It would require the content service / typed content repository to support OData queries but I might give it a shot :)

  2. Juan David on August 3rd, 2013

    Im trying to implement this solution, and it works when adding and getting the registers.

    When I trying to delete or edit i get the bellow error:

    HTTP Error 405.0 - Method Not Allowed
    The page you are looking for cannot be displayed because an invalid method (HTTP verb) is being used.

    Let me know if you have any idea related

    Thanks JD

  3. Tim Geyssens on August 5th, 2013

    @Juan, do your methods start with delete / put ? otherwise I think you’ll have to mark them with the correct attribute to allow the correct http methpd

  4. Juan David on August 5th, 2013

    Hi @Tim, thanks for the answer.

    I have the same name of methods in the example.
    http://localhost/umbraco/api/StatusApi/PutStatus/
    http://localhost/umbraco/api/StatusApi/DeleteStatus/?statusId=1230

    Im looking for a solution, i believe is something related IIS or WevDav.

  5. Juan David on August 5th, 2013

    I found the solution adding these lines to the web.config:

    Reference: http://forums.iis.net/t/1166025.aspx

  6. Juan David on August 5th, 2013

    Sorry for the spam: web.config
    system.webServer
    modules
    <remove name=”WebDAVModule”
    modules
    handlers
    remove name=”WebDAV”
    handlers
    system.webServer

  7. Bjarne Fyrstenborg on October 25th, 2014

    Hi @Tim

    Does UmbracoAPIController use Web Api or Web Api 2? It’s possible to use Web Api 2 in Umbraco 6, when using UmbracoAPIController?

  8. Bjarne Fyrstenborg on October 26th, 2014

    Okay I got an answer on this that UmbracoApiController in both Umbraco 6 and 7 use ASP.NET Web Api, but not ASP.NET Web Api v2 yet.

  9. Harsh on October 6th, 2015

    Hi Tim,

    Can it be possible without using bootstrap? I mean using custom dashboard.
    actually i don’t want to use separate UI , i want it to be in the Umbraco dashboard or is there any place we can fit?

    Regards,
    Harsh

  10. Tim Geyssens on October 6th, 2015

    @Harsh of course I’m just using bootstrap to have a quick ui but you could also add this in the umbraco backoffice ui…

  11. Harsh on October 6th, 2015

    Thank you Tim,

    Can you please help on routing part of tree views and actual controller
    (created for custom menus)place in the backoffice.
    i have added tree view in App_plugins/templates/backoffice/create.html
    where we can place the actual controller so that it can called by the API?

    Regards,
    Harsh

  12. Tim Geyssens on October 7th, 2015

    @Harsh maybe this link is usefull: http://www.nibble.be/?p=250

  13. Harshit on October 14th, 2015

    Hi Tim,

    In the below method what would be the parentId?

    public Status PostStatus(Status status, int parentId)
    {
    System.Diagnostics.Debugger.Launch();
    var cs = Services.ContentService;
    var content = cs.CreateContent(status.Title, parentId, StatusDocTypeAlias);
    cs.CreateContent()
    content.Name = status.Title;
    content.SetValue(MessagePropertyAlias, status.Message);
    cs.SaveAndPublishWithStatus(content);
    status.Id = content.Id;
    return status;

    Actually i am trying it,however its not working after

    var content = cs.CreateContent(status.Title, parentId, StatusDocTypeAlias);

    actually i am passing parent id 1046 which is hardcoded and getting internal server error 500.

    Can you please help on this?
    Regards,
    Harshit

  14. Tim Geyssens on October 14th, 2015

    @Harshit, can you post details of the 500 error (full stack trace) ?

  15. Harshit on October 14th, 2015

    @Tim,
    Error message:
    Failed to load resource: the server responded with a status of 500 (Internal Server Error): this is the link:
    http://umbracoCustomSectionTest.com/umbraco/api/StatusApi/PostStatus/?parentId=1046

    this is the error message if i am clicking on the link:

    The requested resource does not support http method ‘GET’.

  16. Harshit on October 14th, 2015

    Actually i want to add the form in the parent tree.
    I am following your approach and using v7 of umbraco.
    Form i creating in Dashboard and passing data from to controller and Post to API where i am creating content.
    I am getting this while creating content and i think, the error is because of parentId.
    So is there a way where we can find the current nodeId and pass it to the controller?

    regards,
    Harshit

  17. Tim Geyssens on October 14th, 2015

    @Harshit not sure I’m following so you have a dashboard control? And you need to fetch a parent id? But it doesn’t work with a hard coded one? Again any details you can share on the 500 would be great

  18. Harshit on October 14th, 2015

    Hi Tim,
    Error message:
    Failed to load resource: the server responded with a status of 500 (Internal Server Error): this is the link:
    _____http://umbracoCustomSectionTest.com/umbraco/api/StatusApi/PostStatus/?parentId=1046

    this is the error message if i am clicking on the link:

    The requested resource does not support http method ‘GET’.

    ===========================================================

    I have a create html which gets invoked when user clicks on right create menu option of a custom tree.if users selects new item it navigate to a form which in a panel section of umbraco and it is having 2 text controls.
    and save button
    =============================

    on save button i want to save it to the parent tree node level as a new node.So i am calling API through controller.

    hope it will help.

    Regards,
    Harshit

  19. Harshit on October 14th, 2015

    Hi Tim,
    i have pasted about the 500 however i am getting
    “Your comment is awaiting moderation”

    Regards,
    Harshit

  20. Harshit on October 14th, 2015

    Hi Tim,
    below is the error message:
    “Failed to load resource: the server responded with a status of 500 (Internal Server Error)”: this is the link:
    _____http://umbracoCustomSectionTest.com/umbraco/api/StatusApi/PostStatus/?parentId=1046

    this is the error message if i am clicking on the link:

    The requested resource does not support http method ‘GET’.

    ===================================================

  21. Harshit on October 14th, 2015

    Hi Tim,

    Failed to load resource: the server responded with a status of 500 (Internal Server Error): this is the link:
    umbracoCustomSectionTest.com/umbraco/api/StatusApi/PostStatus/?parentId=1046

    this is the error message if i am clicking on the link:

    The requested resource does not support http method ‘GET’.

    ===================================================

  22. Harshit on October 14th, 2015

    I have a create html which gets invoked when user clicks on right create menu option of a custom tree.if users selects new item it navigate to a form which in a panel section of umbraco and it is having 2 text controls.
    and save button
    =============================

    on save button i want to save it to the parent tree node level as a new node.So i am calling API through controller.

    hope it will help.

    Regards,
    Harshit

  23. Tim Geyssens on October 15th, 2015

    @Harshit any chance you can share what you have so far? Maybe on Github?

  24. Harshit on October 15th, 2015

    Hi Tim,

    I have added files : gist.github.com/SinghHars/b5bbca636d85eb610a7d

    please check

    thanks,
    Harshit

  25. Tim Geyssens on October 16th, 2015

    @Harshit yeah your code looks ok, can’t really spot what could be wrong.

    If you open dev tools in chrome you should be able to see a detailed error message (on the network tab), since the does not support get is not the error message that will happen when angular is performing a post

  26. Harshit on October 19th, 2015

    Hi Tim,

    thanks,
    just one doubt in the below function :
    Content CreateContent(string name, int parentId, string contentTypeAlias, int userId = 0);

    what is contentTypeAlias?
    you have taken it “Status” as below

    private const string StatusDocTypeAlias = “Status”;

    can you please help me in understading it?

    Thanks,
    Harshit

  27. Harshit on October 19th, 2015

    Hi Tim,
    Actually i am gettig below exception.

    ExceptionMessage: “No ContentType matching the passed in Alias: ‘Status’ was found”
    ExceptionType: “System.Exception”
    Message: “An error has occurred.”
    StackTrace: ” at Umbraco.Core.Services.ContentService.FindContentTypeByAlias(String contentTypeAlias)
    ↵ at Umbraco.Core.Services.ContentService.CreateContent(String name, Int32 parentId, String contentTypeAlias, Int32 userId)
    ↵ at Adweb_OTL.StatusApiController.PostStatus(Status status, Int32 parentId)
    ↵ at lambda_method(Closure , Object , Object[] )
    ↵ at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.c__DisplayClass10.b__9(Object instance, Object[] methodParameters)
    ↵ at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ExecuteAsync(HttpControllerContext controllerContext, IDictionary`2 arguments, CancellationToken cancellationToken)
    ↵— End of stack trace from previous location where exception was thrown —
    ↵ at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
    ↵ at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
    ↵ at System.Web.Http.Controllers.ApiControllerActionInvoker.d__0.MoveNext()
    ↵— End of stack trace from previous location where exception was thrown —
    ↵ at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
    ↵ at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
    ↵ at System.Web.Http.Controllers.ActionFilterResult.d__2.MoveNext()
    ↵— End of stack trace from previous location where exception was thrown —
    ↵ at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
    ↵ at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
    ↵ at System.Web.Http.Dispatcher.HttpControllerDispatcher.d__1.MoveNext()”

  28. Tim Geyssens on October 19th, 2015

    @Harshit ah now we know what is going wrong, you don’t have the doc type that you are trying to create content with , so make sure you have a doctype with alias Status first (or change it to the alias that you wish to use instead)

  29. Harshit on October 19th, 2015

    @Tim,

    Thanks for the clarification.got it now :) Actually,i am trying to add this doc as a child node under custom tree node of a custom section.is this possible?

    Regards,
    Harshit

  30. Tim Geyssens on October 19th, 2015

    @Harshit, sure but that depends on your tree controller then… so it will list the items you want and if that are documents…

  31. Harshit on October 20th, 2015

    Hi Tim,

    Yes i exactly wants is that way.
    any idea how to proceed?

    Many thanks.
    Harshit

  32. Harshit on October 22nd, 2015

    Hi Tim,
    thanks..i got a way to do it.

    Cheers

  33. Tim Geyssens on October 22nd, 2015

    @Harshit great :) yeah you’ll need to look into a TreeController and then fetching the data you wish to have in the tree

Leave a Reply