Organize Umbraco projects

This post uses Umbraco 7.12 for the example but probably applies to a wider range of version.

Few weeks ago, I started to use “Umbraco” and I noticed that it was pretty difficult to find a tutorial on how to organize an “Umbraco” project (I didn’t say it does not exist, it’s just difficult to find 🙂 ). Indeed, a lot of different files are required to create a site powered by “Umbraco” or to extend its back-office and it’s sometimes hard to decide where to put all of these files.

In this post, I’ll expose the way I organized the current project I’m working on. The goal of this approach is to put as few files as possible in the main ASP.NET project, this way, you ensure that an upgrade for “Umbraco” will have the least side effects possible on your custom code.

Note that the goal of this post is to give pointers about how to organize the different elements of your solution (dashboards, sections, etc…), so we won’t go into details about these topics. Moreover, this is only the way I do things, it might not be the best one but it works.

Create the project

First thing you have to do is creating your project, so open “Visual Studio 2017” and create an empty “ASP.NET Web Application (.NET Framework)”.

Untitled.png

Untitled.png

Once the project is created, run the following command in the “Package Manager Console” to install “Umbraco”:

Install-Package UmbracoCms

Now you can press “F5” to start the configuration of “Umbraco”. Even though it does not really matter, for this post, I’ll use a “SQL Compact” database without any starter kit.

Core project

As I said, we don’t want to store more files than necessary in the web site project running “Umbraco”, so we need a project to contain the custom code of our application such as:

  • Web APIs.
  • Helper classes.
  • Constant classes.
  • Application handlers.
  • etc…

This project is simply a class library, so add a new one to your solution.

Untitled.png

Moreover, as we are going to use the “Umbraco” API in this project, we also need to install its “NuGet” package. Note that the package to install is not “UmbracoCms” as this one would also add all the files required to run the back office. The package to install is “UmbracoCms.Core”.

Install-Package UmbracoCms.Core -ProjectName AnotherDevBlog.Core

Now, we can add a reference to that project in the main one.

Untitled.png

ModelsBuilder

As you probably know, “ModelsBuilder” has been integrated to “Umbraco” in its version 7.4 to create typed objects from documents. You can add settings in the “web.config” file of your application to leverage the way it works.

The configuration used for this blog post is the following:

  • Enable” enables “ModelsBuilder”.
  • ModelsMode” defines how the models are generated. We use “LiveAppData” to generate models as “.cs” files as soon as a document type changes in the back office.
  • ModelsNamespace” defines the namespace of the generated classes. As we place this classes in the “Core” project, we define the namespace to “AnotherDevBlog.Core.DocumentTypes”.
  • ModelsDirectory” defines the directory where the “.cs” files are placed.
  • By default, “ModelsBuilder” throws a security exception if you try to place models in the different directory than the one containing the web site. Therefore, we need to use “AcceptUnsafeModelsDirectory” to be able to do so.

Now, let’s create a simple document type called “Article”:

Untitled.png

Stop the site, select the “Core” project and display all files of the project.

Untitled.png

The models have been correctly generated in the correct directory. Right click on the “DocumentTypes” directory and select “Include in project” to take them into account in the project compilation. The only drawback with this method is that new document types are not automatically included in your project, you have to stop the site, include them, then launch the site again. However, I consider this as a minor drawback as it does not imply a lot of work.

Note that, right now, the project does not compile correctly as the namespace “Umbraco.ModelsBuilder” can’t be found. To fix this, we need to install its “NuGet” package as well.

Install-Package Umbraco.ModelsBuilder -ProjectName AnotherDevBlog.Core

Unfortunately, a package with only the required “DLL” does not seem to exist for “ModelsBuilder”, so you need to install the regular package, then delete some elements from the “Core” project. These elements are:

  • The directory “App_Plugins” (only required in the “Umbraco” web site project).
  • The file “app.config“.

Before being able to compile the project, you also need to add a reference to “System.Web” DLL.

Untitled.png

We can now compile the project and launch the site. However, prior doing so, we’ll update the document type template. This template can be found in “~/AnotherDevBlog/Views/Article.cshtml” (I recommend including it in your project to be able to edit it directly in “Visual Studio”). Update its content with this:

Now you can create documents based on this document type.

The nice thing is that the code contained in the “Core” project is considered as normal code by “Umbraco”. What I mean is that if you have an application event handler such as:

Even though this code is in a separated project, “Umbraco” is able to “see” it and execute your handler, so you can safely add your code to the “Core” project as you would have done in the default one.

Front end

Now that we decoupled our custom server side code from the web site project, we need to do the same with the front end one. Of course, if you create a very simple site, you might not even need the front end code, but let’s say that we want to create an “AngularJS” component to display some data returned by a custom web service.

First, let’s create this very simple web service. To do so, create a directory “API” in the “Core” project and add a file called “CustomData.cs” with the following code:

You can access the new web service via the URL “http://site/umbraco/api/CustomData/GetData”.

Untitled.png

Now, we need to create the project to contain our front end application. To do so, create a “Shared Project” in “Visual Studio” and give it a meaningful name.

Untitled.png

This application is powered by “AngularJS”, written in “TypeScript” and compiled with “WebPack”. If you don’t know what “WebPack” is, I recommend you to read this blog post to know more about it as we won’t go into much details in this blog post.

Let’s start by installing all the required packages:

yarn add @types/angular @types/node css-loader file-loader html-loader mini-css-extract-plugin ngtemplate-loader style-loader ts-loader typescript url-loader webpack webpack-cli -D

yarn add angular tslib -S

Show all the files of your project to include the file “package.json” and “yarn.lock” (or the “npm” equivalent if you use “npm”). Now, create a file called “webpack.config.js” to define the behavior of “WebPack”.

  • output.path” is configured to emit the bundle in the “Scripts” folder of the “Umbraco” project. This way, the file is included in the “Umbraco” site after compilation.
  • mini-css-extract-plugin” has been configured the same way to output the bundled style sheets in the “css” directory of the “Umbraco” web site.
  • ngtemplate-loader” is used to bundle the “AngularJS” templates inside the bundle.

Now that “WebPack” is configured, create a file called “tsconfig.json” to configure the “TypeScript” compilation:

This file contains the bare minimum for the sake of the example. In real life scenario, you’ll probably want/need to define other settings for the “TypeScript” compilation.

The next step is to create the “AngularJs” application. Start by creating the file “./app/services/custom-data.service.ts

This service only exposes one method (“getData”) that calls the web service and returns the data. Now, let’s create the component that uses this service to display the returned data. This component is declared in the file “./app/components/custom-data.component.ts“:

And its template is defined in the file “./app/components/custom-data.component.html“:

Nothing very complicated here. The component simply calls the “getData” function of the service during its initialization then assigns the result to the “data” property which is displayed in an unordered list.

In the “webpack.config.js” file, we defined that the entry point was the file “./app/index.ts“, so let’s create it.

On the top of registering the service and the component, we also override the “Promise” property of the “window” object to ensure that promises are handled by the $q service.

Finally, add a “npm” script in the “package.json” file to launch webpack.

You can now run the following command to build your application as soon as a file changes.

npm run build

To use this application, we are going to create a master page that references and bootstraps it in the page. To do so, open “Umbraco” back office and create a new template called “Master” with the following content:

And update the content of the “Article” template by adding a reference to our component:

And don’t forget to select the “Master” template as the parent.

Untitled

And you’re all set. If everything went as expected, “WebPack” has generated two “JavaScript” files and put them into the “Scripts” folder of the “Umbraco” project. Then, the “Master” template loads them and is able to use the components, services, directives, etc… defined in the “AngularJs” application.

Let’s go a bit further by handling style sheets and images. Add the following picture in “./assets/img/umbraco.png“.

umbraco

And create the file “./assets/css/main.css” with this content:

You have to reference this style sheet in the “index.ts” file to tell “WebPack” to bundle it, so add this line at the beginning of the file:

Edit also the template of your component to insert a “div” with the “logo” class:

Based on its configuration, “WebPack” generates a file called “adb.main.css” in the “css” folder of the “Umbraco” project and will copy all the referenced images in the “img” folder of that project. So the only thing left to do to make all of this work is to add a reference to the bundled style sheet in the “Master” template (and if you didn’t do it, don’t forget to include the “Master.cshtml” file in your project to be able to edit it directly in “Visual Studio”).

You can’t test the final result by creating a page based on the “Article” template and display it.

Untitled.png

So now, you have everything you need to create an awesome “AngularJs” front end application for your site.

It’s stupid but… when I was writing this article, I kept having an issue with my application, it didn’t work. The “adb.min.js” file couldn’t be loaded by Chrome except in a private session. After investigation, it turns out that “AdBlock” was blocking this file based on its name, so if you followed these instructions to the letter and configured “WebPack” to generate a file called” adb.min.js“, deactivate “AdBlock” if you want your application to work.

Back office

“Umbraco” provides a lot of neat functionalities but sometimes it’s not enough. Indeed, your web site could have some specific needs that are not covered by “Umbraco”. Fortunately, it is very easy to extend its back office by creating “AngularJs” components used by “Umbraco” to do whatever you want. Here, we won’t see how to create every type of extension element, we are just going to focus on creating a simple dashboard (along with its section).

Basically, the idea behind this is the same than the one for the front end code. First we create a “Shared Project” called “AnotherDevBlog.BackOffice”.

Untitled.png

Then we create a “webpack.config.js” file to configure the compilation of our application.

95% of this file is the same as the one used for the front end application. However, here are the differences:

  • output.path” has been changed to set the destination directory to “./AnotherDevBlog/App_Plugins/AnotherDevBlog”.
  • filename” has been changed to output the “JavaScript” bundle in the “js” directory. We could have used “Scripts” but I prefer “js”.
  • HTML files are not loaded via “ngtemplate-loader” anymore (we’ll see later why).

The “package.json” file is also very similar.

The two differences are that we don’t install “ngtemplate-loader” anymore nor “AngularJs”. Indeed, unlike for the front end application, we are not free about the version of “AngularJs” to use, we need to use the one used by the back office (which in “Umbraco v7” is 1.1.5). This also means that we are not really going to create an “AngularJs” app but rather, we are going to extend the one of the back office and this has some consequences. Besides, after some tests, I noticed that “ngtemplate-loader” does not work well with this version of “AngularJs”, which is why we don’t use it.

“Umbraco v8” plans on using “AngularJs” 1.7.x.

Another annoying point to keep in mind is that we want to use “TypeScript”, so we need typings. However, typings for “AngularJs” 1.1.5 can’t be found (at least in the “@types” scope), so we use the ones of the version 1.6.x which is of course not correct. Fortunately, the main principle of “AngularJs” didn’t dramatically changed between these two versions but it means that sometimes, you’ll probably have to tweak a bit your code to make “TypeScript” accepts it (of course, you can also simply use plain “JavaScript” along with “Babel” instead of “TypeScript” if you prefer).

Install the packages by running the command below.

yarn

And include the file “yarn.lock” in your project. Add also a “tsconfig.json” file with the same content as the one for the front end application to configure the “TypeScript” compilation.

Now, let’s create a very simple component to display a static text that will be displayed in a custom dashboard. Remember that, as we are using “AngularJs” 1.1.5, we can’t create a component, so we need to create a directive. Create the file “./app/component/hello-world/hello-world.component.ts” with the following content.

This simply creates a directive bound to the “HelloWorldController” controller that defines the “data” scope property to “Hello world!” in the “$onInit” function. Note that this function needs to be called from the constructor as life-cycle events are not implemented in “AngularJs” 1.1.5.

Another difference with the front end application is that we use the property “template” instead of “templateUrl” to define the template of the component. In the front end application, “ngtemplate-loader” returned the template URL that was pre-loaded in the “$templateCache”. In the back office application, “html-loader” simply returns the HTML that we use to define the “template” property.

By the way, create the file “./app/components/hello-world/hello-world.component.html” to define this template.

Now that the component is created, create the file “./app/index.ts” to tie everything together.

As explained above, we don’t want to create a new “AngularJs” application, we just want to extend the existing one. Moreover, as we didn’t install “AngularJs” in that project, we cannot import it, so we fake it by declaring the global variable “angular” of type “ng.IAngularStatic”.

You can now run “WebPack” via the following command to build the application.

npm run build

This creates a folder called “AnotherDevBlog” in the “App_Plugins” one of the “Umbraco” project. As by default it is not included in the project, display all its files and include the generated folder.

The last step consists in creating the dashboard. To do so, open the file “~/config/applications.config” and add the following section.

Then open the file “~/config/Dashboard.config” and add the following “XML” before the “dashBoard” closing tag.

This simply registers a new “Umbraco” dashboard that loads the specified view. As this one does not exist yet, create the file “~/App_Plugins/AnotherDevBlog/dashboards/hello-world.html” with the following content.

We just insert a reference to the component we created in our back office application. The last step required in the creation of the dashboard is to add the “package.manifest” file to reference our files, so create “~/App_Plugins/AnotherDevBlog/package.manifest” with the content below.

And voilà! You’re all set. You can now launch “Umbraco” and access your dashboard.

Don’t forget that you won’t see the new section if you don’t allow the security group you are in to see it.

Untitled

The name of the section is “[sections_anotherDevBlog]” because we didn’t define the translations as this is out of the scope of this blog post.

There is one thing that you could wonder. How would this work with components such as “property editors” that need to access “$scope.model” to update its value. This is actually pretty simple. You can simply add a binding to your directive called “data” or something (using the “=” symbol to pass it by reference) then reference your component like this.

If you have trouble doing it, just leave a comment and I’ll update the blog post.

Conclusion

Phew! That was a long one but here we are, we have a nicely structured solution where we decoupled the custom server side code, the front end application and the back office extension. Now, when an upgrade of “Umbraco” is released, there is a lot of chance that it won’t collide with your code as most of it is contained in another project that the “Umbraco” one.

As I said, this is not the only way nor the best (at least I suppose) way to organize an “Umbraco” project. If you have tips and tricks, a different approach or even questions about this blog post, don’t hesitate to contribute in comments.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.