How to use ModelsBuilder in a multi-project Umbraco solution

ModelsBuilder is the new way to deal with Umbraco content in a strongly-typed fashion, and it's very practical because IntelliSense eliminates the possibility of a typo on a property alias that can sometimes drive you nuts. But using ModelsBuilder can drive you nuts in a number of different scenarios, especially when you use Visual Studio and your Umbraco solution includes multiple projects apart from the main one (your site), some or all of which must also have access to strongly-typed models. (For example, some people like to have their controllers in a different assembly).

Dll mode is the simplest and the easiest to use: Each time you build your models, the Umbraco.Web.PublishedContentModels.dll file containing your compiled models is updated and then you can just use strongly-typed models in your views and controllers. You can also give others the freedom to make changes, even to an already deployed installation, and just rebuild the models, having instant strongly-typed access to newly-created or changed properties.

This kind of freedom has its limits, though. Let's suppose you do the following:

  • Set up a new Visual Studio solution
  • Install Umbraco (via Nuget) as your main project
  • Set up models builder to DLL mode
  • Set up additional projects referenced by your main project that need to reference your strongly-typed models.

Kaboom. Circular references. Not the way to go.

There's a way to overcome that (not the right way, though) by explicitly referencing the DLL from your other projects. It's still a circular reference, but it will work. But even if you do that, you've got another problem at hand: If you're using source control (e.g. Git) you have no other way of submitting changes to models other than adding Umbraco.Web.PublishedContentModels.dll to source control - something that may not seem too much, but as a binary file it will be copied fully every time it changes, blowing your repos out of proportion. Well, maybe not in the first project, but eventually it will get there.

The AppData mode, on the other hand, eliminates the source control problem but limits your options to only having the strongly-typed classes locally on your main project. Other projects cannot reference them easily, and, of course, a change in your document / properties setup means somebody would have to recompile the whole thing instead of just rebuilding models from the Umbraco back-end.

If you ask me what reason would somebody have to rebuild models in a deployed project, I'll say that you should always leave as many options open as possible - your client may need to extend their site at some time in the future in a quick-and-dirty way that doesn't necessarily involve your services. It might sound wrong in your ears, but it happens more often than not. I also used DLL mode on the UMazel Starter Kit to facilitate people wanting to alter document / properties structure without necessarily having to fire up Visual Studio to do so.

That said, I have used the above approach multiple times in the past for client projects, thinking I had no other options at hand. Until I discovered that Models Builder now supports custom paths that can be outside the main project. This essentially means you can use AppData mode without actually having to drop your code inside the App_Data folder, but in a totally different project all together, which can be referenced by both your main project and other projects.

Let's see a hypothetical solution structure for project XYZ:

-- XYZ.Website
-- Project #1
-- Project #2

Now, projects #1 and #2 also need access to strongly-typed models as XYZ.Website (our main project, where Umbraco is installed) does. In order to do that, we must create an additional project and have ModelsBuilder dump its files there. Pay attention at this point - you can name your new project anything you like, but if you give it an assembly name and default namespace of "Umbraco.Web.PublishedContentModels" (actually, whatever the default Models Builder namespace is) you'll have the best of both worlds (AppData and Dll modes). Let's see how this will happen:

Let's first add our new project

-- XYZ.Website
-- Project #1
-- Project #2
-- Umbraco.Web.PublishedContentModels

Now let's have all other project that need access to strongly-typed models reference this project. (You probably don't need any help in doing that :) )

And, finally, let's go to our XYZ.Website's web.config and set up ModelsBuilder:

<add key="Umbraco.ModelsBuilder.Enable" value="true" />
<add key="Umbraco.ModelsBuilder.ModelsMode" value="AppData" />
<add key="Umbraco.ModelsBuilder.ModelsDirectory" value="~/../../XYZ/Umbraco.Web.PublishedContentModels" />
<add key="Umbraco.ModelsBuilder.AcceptUnsafeModelsDirectory" value="true" />

Where XYZ is our solution folder.

This will make ModelsBuilder drop its stuff in our Umbraco.Web.PublishedContentModels project. This project can go into source control like all others.

When deploying, you can (optionally!) at any time substitute this setup with the following:

<add key="Umbraco.ModelsBuilder.Enable" value="true" />
<add key="Umbraco.ModelsBuilder.ModelsMode" value="dll" />

And you'll have Dll models from that point on, with no other changes. The first time you rebuild your models via the Umbraco back-end, your DLL will just be substituted with a new one.

If you're worried about the word "unsafe" in the AcceptUnsafeModelsDirectory setting it's because it is a potential security risk - but, provided that you switch to Dll mode when deploying, I think you won't have any issues with that.

If you don't really want to use the default namespace, you can always use the Umbraco.ModelsBuilder.ModelsNamespace setting in your web.config to change the default namespace to one of your choice.

Here's how the setup looks from Visual Studio on a real project. The "xxx.Website" project is the project where Umbraco is installed, while some other projects need to reference strongly-typed models. The actual project for strongly-typed models is expanded. As you can see, everything is in source control.



Here are the project references of the ".Website" project:

And here is the DLL generated after building in the /bin folder of the main project:


One last thing - if you're using Nuget, you MUST install UmbracoCms.Core (same version as your Umbraco installation) on your new Umbraco.Web.PublishedContentModels (or any other default ModelsBuilder namespace) project or else nothing will compile. Additionally, you may need to install packages that add new data types to your installation - for example, I found out that I had to install LinksPicker via Nuget on my models project in order to have my project compile (I had already installed it in my main project). This may limit your options a bit, since it's harder to manage non-nuget packages in this fashion, but hopefully not much.  

I hope my post made the ModelsBuilder world a bit simpler for you :) If you're following a different approach, I'd be happy to hear it - I'm not claiming that the approach described here is the magical solution to all problems, but it looks like it's working well so far. 

UPDATE: Dave Woestenborghs has written an excellent article that shows you how to achieve the same result using API Models and the Visual Studio custom tool that is available for this purpose. It may not be obvious at first, but this is a setup which also lets you create your model code files in a different project than your main site.