diff --git a/src/Umbraco.Core/Composing/TypeCollectionBuilderBase.cs b/src/Umbraco.Core/Composing/TypeCollectionBuilderBase.cs
new file mode 100644
index 0000000000..35f85c9c2f
--- /dev/null
+++ b/src/Umbraco.Core/Composing/TypeCollectionBuilderBase.cs
@@ -0,0 +1,44 @@
+using System;
+using System.Collections.Generic;
+
+namespace Umbraco.Core.Composing
+{
+ ///
+ /// Provides a base class for collections of types.
+ ///
+ public abstract class TypeCollectionBuilderBase : ICollectionBuilder
+ where TCollection : class, IBuilderCollection
+ {
+ private readonly HashSet _types = new HashSet();
+
+ private Type Validate(Type type, string action)
+ {
+ if (!typeof(TConstraint).IsAssignableFrom(type))
+ throw new InvalidOperationException($"Cannot {action} type {type.FullName} as it does not inherit from/implement {typeof(TConstraint).FullName}.");
+ return type;
+ }
+
+ public void Add(Type type) => _types.Add(Validate(type, "add"));
+
+ public void Add() => Add(typeof(T));
+
+ public void Add(IEnumerable types)
+ {
+ foreach (var type in types) Add(type);
+ }
+
+ public void Remove(Type type) => _types.Remove(Validate(type, "remove"));
+
+ public void Remove() => Remove(typeof(T));
+
+ public TCollection CreateCollection(IFactory factory)
+ {
+ return factory.CreateInstance(_types);
+ }
+
+ public void RegisterWith(IRegister register)
+ {
+ register.Register(CreateCollection, Lifetime.Singleton);
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj
index 672f28bfea..fa046acd63 100755
--- a/src/Umbraco.Core/Umbraco.Core.csproj
+++ b/src/Umbraco.Core/Umbraco.Core.csproj
@@ -214,6 +214,7 @@
+
diff --git a/src/Umbraco.Web/Mvc/SurfaceControllerTypeCollection.cs b/src/Umbraco.Web/Mvc/SurfaceControllerTypeCollection.cs
index d9577417cc..59b8adc950 100644
--- a/src/Umbraco.Web/Mvc/SurfaceControllerTypeCollection.cs
+++ b/src/Umbraco.Web/Mvc/SurfaceControllerTypeCollection.cs
@@ -4,10 +4,6 @@ using Umbraco.Core.Composing;
namespace Umbraco.Web.Mvc
{
- // unless we want to modify the content of the collection
- // which we are not doing at the moment
- // we can inherit from BuilderCollectionBase and just be enumerable
-
public class SurfaceControllerTypeCollection : BuilderCollectionBase
{
public SurfaceControllerTypeCollection(IEnumerable items)
diff --git a/src/Umbraco.Web/Mvc/SurfaceControllerTypeCollectionBuilder.cs b/src/Umbraco.Web/Mvc/SurfaceControllerTypeCollectionBuilder.cs
new file mode 100644
index 0000000000..7ea7472a87
--- /dev/null
+++ b/src/Umbraco.Web/Mvc/SurfaceControllerTypeCollectionBuilder.cs
@@ -0,0 +1,8 @@
+using Umbraco.Core.Composing;
+using Umbraco.Web.WebApi;
+
+namespace Umbraco.Web.Mvc
+{
+ public class SurfaceControllerTypeCollectionBuilder : TypeCollectionBuilderBase
+ { }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Web/Runtime/WebInitialComposer.cs b/src/Umbraco.Web/Runtime/WebInitialComposer.cs
index 3be5ecd21b..5f95ab2b8e 100644
--- a/src/Umbraco.Web/Runtime/WebInitialComposer.cs
+++ b/src/Umbraco.Web/Runtime/WebInitialComposer.cs
@@ -158,12 +158,11 @@ namespace Umbraco.Web.Runtime
.Add(() => composition.TypeLoader.GetTypes());
//we need to eagerly scan controller types since they will need to be routed
- var surfaceControllerTypes = new SurfaceControllerTypeCollection(composition.TypeLoader.GetSurfaceControllers());
- composition.RegisterUnique(surfaceControllerTypes);
-
- //we need to eagerly scan controller types since they will need to be routed
- var umbracoApiControllerTypes = new UmbracoApiControllerTypeCollection(composition.TypeLoader.GetUmbracoApiControllers());
- composition.RegisterUnique(umbracoApiControllerTypes);
+ composition.WithCollectionBuilder()
+ .Add(composition.TypeLoader.GetSurfaceControllers());
+ var umbracoApiControllerTypes = composition.TypeLoader.GetUmbracoApiControllers().ToList();
+ composition.WithCollectionBuilder()
+ .Add(umbracoApiControllerTypes);
// both TinyMceValueConverter (in Core) and RteMacroRenderingValueConverter (in Web) will be
// discovered when CoreBootManager configures the converters. We HAVE to remove one of them
diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj
index f38ac0a58c..985532984f 100755
--- a/src/Umbraco.Web/Umbraco.Web.csproj
+++ b/src/Umbraco.Web/Umbraco.Web.csproj
@@ -204,6 +204,7 @@
+
@@ -603,6 +604,7 @@
+
diff --git a/src/Umbraco.Web/WebApi/UmbracoApiControllerTypeCollection.cs b/src/Umbraco.Web/WebApi/UmbracoApiControllerTypeCollection.cs
index cdb1aa2f51..dc8d1767dc 100644
--- a/src/Umbraco.Web/WebApi/UmbracoApiControllerTypeCollection.cs
+++ b/src/Umbraco.Web/WebApi/UmbracoApiControllerTypeCollection.cs
@@ -4,10 +4,6 @@ using Umbraco.Core.Composing;
namespace Umbraco.Web.WebApi
{
- // unless we want to modify the content of the collection
- // which we are not doing at the moment
- // we can inherit from BuilderCollectionBase and just be enumerable
-
public class UmbracoApiControllerTypeCollection : BuilderCollectionBase
{
public UmbracoApiControllerTypeCollection(IEnumerable items)
diff --git a/src/Umbraco.Web/WebApi/UmbracoApiControllerTypeCollectionBuilder.cs b/src/Umbraco.Web/WebApi/UmbracoApiControllerTypeCollectionBuilder.cs
new file mode 100644
index 0000000000..190fcdb00e
--- /dev/null
+++ b/src/Umbraco.Web/WebApi/UmbracoApiControllerTypeCollectionBuilder.cs
@@ -0,0 +1,7 @@
+using Umbraco.Core.Composing;
+
+namespace Umbraco.Web.WebApi
+{
+ public class UmbracoApiControllerTypeCollectionBuilder : TypeCollectionBuilderBase
+ { }
+}
\ No newline at end of file