In this tutorial, you will learn about the six different types of controllers you can build within Umbraco CMS v9. Understanding how and when to use these controllers will allow you to render pretty much any content or data you can imagine within Umbraco. The uses of these controllers range from rendering CMS pages, APIs, creating secure content as well as controllers that deal with form postbacks. Today, you will learn how to implement all of them. This will include going over all the code required to build each controller as well as any routing rules required to make it work. By the end of this tutorial, you will be a Umbraco controller guru 🧘. So, if you want to attain that higher-level of Umbraco know-how, then read on 🔥🔥🔥

Umbraco Routing

Before talking controllers, you need to understand a key Umbraco fundamental first, routing rules. When talking about the different types of controllers, it is critical that you understand how the routing works. Some of the controllers will magically hook up the routing for you. These controllers tend to be easy to implement, you define one in code and boom it just works. Other controllers will require you to configure the routing yourself. Writing custom routing rules will be very frustrating if you do not understand how CMS routing works. The default Umbraco routing rule is very different compared to the default MVC routing rule. This is true for any .NET CMS and let's consider why 🤔. The default .NET Core MVC routing rule is shown below:

This rule is telling .NET to match any URL, to a controller called home and an action method called index. If a request to was made, this would trigger the rule and the home controller would be loaded. In normal .NET, a URL will tend to map to a controller and an action. As long as the correct code exists, the routing just works 🤔

Umbraco needs to use a different default routing rule in order for it to serve CMS content. When a request is made to Umbraco, there is no direct correlation between the URL and the controller and action that needs to load. Umbraco needs to map the URL to a content page created within the CMS. A page URL is determined If a match is made, Umbraco will then try to call an associated controller based on the pages document type. based on the page name and where it lives within the CMS page tree. Umbraco will need to find and get the information for that page. The request does not simply map to a controller based on the URL. During this look-up process, Umbraco will find the document type for the page, it will add any content an editor may have added for the page into the request context and then re-direct the request to a controller. In a CMS the controller to render a page is based on its document type. This means you need to create a controller per document type defined within your CMS.

The key takeaway is that a simple URL to controller/action mapping will not work in Umbraco out-of-the-box. This is why when you create a certain controller in Umbraco you need to create a rule in the routeing table to trigger the controller to run.

Creating A Vanilla MVC Controller In Umbraco V9

The first controller that we will cover is the normal vanilla MVC controller. The code to create this type of controller in Umbraco is simple enough:

The trickier part of creating a normal MVC controller is the routing. Umbraco overrides the normal MVC routing rules with its own custom rules. When creating a normal MVC controller in Umbraco, you will need to create your own mapping within the routing table before you can get your controller to trigger.

For long-term Umbraco users, it is important to note that how routes are added into the CMS has changed in .NET Core compared to ASP.NET. In .NET Core, the quick and easy way to add a rule is within Start.cs The code to do this, looks like this:

With this rule defined, when you search for the routing rule will trigger and the code in the VanillaController will be executed!

API Controller

Creating a custom API within Umbraco is easy. You can use UmbracoApiController. Using UmbracoApiController will mean you do not need to create any custom rules in the routing table. The code to create a API controller is shown below:

The important thing to understand when using this type of controller is how the API URL is generated. The API URL will be prefixed with Umbraco/Api/. For the controller above, the URL to trigger it would be The last two segments of the URL map to the controller name MyApiController and an action method GetSomeData. It is also possible to not have Umbraco/Api/ in the URL. To accomplish this, create a controller and then manually create a rule in the routing table 💥

Surface Controller

The SurfaceController is not a page-level controller, it is a controller that works on a component/partial/element level. The SurfaceController is useful when you want to build a custom form within your website and you need to post data back to Umbraco. As we have the Umbraco routing to deal with, using the SurfaceController means you do not need to worry about defining tricky routing rules yourself. The code to create a surface controller is shown below:

Notice on Line13, ValidateUmbracoFormRouteString. This attribute means only form data submitted from a Umbraco page will be allowed. This is useful for security and I recommend that you always include it!

For long-term Umbraco users, SurfaceController works a little differently in V9 compared to V8. The main difference is around how the form is rendered. In V8 you used to create two action methods in the surface controller, one to render the HTML and one to deal with the postback. In .NET Core, partial views are no longer a thing. Instead, you will be creating ViewComponents. In V9, you would create a ViewComponent to render the form. You would then create the `SurfaceController to just deal with the postback. In V9, you create two things (view component and surface controller) and in V8 you created one (surface controller). Let us look at the code to make this a little clearer!

First, you would create a normal ViewComponent to render the form. Remember, you can add components onto a Umbraco page using the BlockListEditor property type! You then add all the HTML to build the form into the ViewComponents corresponding .cshtml file. In that view, you need to add code that posts the form data back to the SurfaceController. This is done like this:

To post a form to a SurfaceController you need to use BeginUmbracoForm where T is the surface controller type you want the form to post back to. BeginUmbracoForm() takes a single parameter which is the action within the controller the form should post the data to. In this example HandleSubmit. That's all you need to do in order to start writing custom forms in Umbraco 💥

API Authorized Controller

All the controllers covered so far have allowed public access to the view/data. It's time to talk lockdown (not COIVD!). First, we have UmbracoAuthorizedApiController that will allow you to create a secure API end-point that only logged-in CMS users can access. The nice thing about UmbracoAuthorizedApiController is that it will auto do the routing for you. Meaning you will not need to add any rules to the routing table. The code to create this controller is simple enough and looks like this:

The important thing to know about secure controllers within Umbraco is that they must be prefixed with /umbraco/backoffice in order for the security check to work. The UmbracoAuthorizedApiController also requires an api segment to be included in the URL as well. This means that the URL to trigger a controller of type UmbracoAuthorizedApiController must be prefixed with /umbraco/backoffice/api. To trigger/access the API defined above, you would use this URL /umbraco/backoffice/api/secure/index. Where secure is the controller name and index is the name of the action.

Backend Controller

Finally, we have the UmbracoAuthorizedController controller. This controller is used to create a secure view. As it's a secure controller, the URL needs to be prefixed with /umbraco/backoffice/ in order for the security check to be made. Unfortunately, UmbracoAuthorizedController does not auto map the routing for you. You will need to define your own custom rule in the routeing table to allow a URL to trigger the controller. Adding a rule can be done within Startup.cs and the rule could look like this:

The corresponding controller code looks like this:

As you can see nothing scary here 😱. The important thing to remember is that the controller and action names need to map correctly in your rule!


We will finish with the easiest one, RenderController. You will be using RenderController the most frequently so I thought i'd leave it to last. You will use RenderController to associate a controller to a document type. The mapping between a controller and a document type is dead easy. Make a note of the document-types alias within the CMS and then create a new class that inherits fromRenderController and is named with the alias prefixed first, followed by the word Controller. To be crystal clear, the name of the controller for a document type whose alias is Home would be HomeController. The code for this controller is shown below:

One difference in RenderController between V8 and V9 is that the parameterless constructor is gone. As you can see you need to inject a number of Umbracfo related dependencies into the controller's constructor now. This means you will need to write slightly more code when creating a new controller, however, you get to unit test everything very easily.

Finally, the controller's view (cshtml) would be created here:


That's it, job done!

That covers the main types of controller you will be creating with Umbraco V9. As you can see you have lots of power. All this code can be accessed in my V9 starter kit which you can download here. Happy Coding 🤘