diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs
index bf3a271d32..93921e07a2 100644
--- a/src/SolutionInfo.cs
+++ b/src/SolutionInfo.cs
@@ -1,5 +1,6 @@
using System.Reflection;
using System.Resources;
+using System.Runtime.CompilerServices;
[assembly: AssemblyCompany("Umbraco")]
[assembly: AssemblyCopyright("Copyright © Umbraco 2019")]
@@ -20,3 +21,5 @@ using System.Resources;
// these are FYI and changed automatically
[assembly: AssemblyFileVersion("8.3.0")]
[assembly: AssemblyInformationalVersion("8.3.0")]
+
+[assembly: InternalsVisibleTo("Umbraco.Tests")]
diff --git a/src/Umbraco.ModelsBuilder/BackOffice/DashboardReport.cs b/src/Umbraco.ModelsBuilder/BackOffice/DashboardReport.cs
new file mode 100644
index 0000000000..57afeaf069
--- /dev/null
+++ b/src/Umbraco.ModelsBuilder/BackOffice/DashboardReport.cs
@@ -0,0 +1,58 @@
+using System.Text;
+using Umbraco.Core.Composing;
+using Umbraco.ModelsBuilder.Configuration;
+using Umbraco.ModelsBuilder.Umbraco;
+
+namespace Umbraco.ModelsBuilder.BackOffice
+{
+ internal class DashboardReport
+ {
+ private readonly IModelsBuilderConfig _config;
+ private readonly ModelsGenerator _modelsGenerator;
+
+ public DashboardReport(IModelsBuilderConfig config, ModelsGenerator modelsGenerator)
+ {
+ _config = config;
+ _modelsGenerator = modelsGenerator;
+ }
+
+ public bool CanGenerate() => _config.ModelsMode.SupportsExplicitGeneration();
+
+ public bool AreModelsOutOfDate() => OutOfDateModelsStatus.IsOutOfDate;
+
+ public string LastError() => _modelsGenerator.GetLastError();
+
+ public string Text()
+ {
+ var sb = new StringBuilder();
+
+ sb.Append("Version: ");
+ sb.Append(Api.ApiVersion.Current.Version);
+ sb.Append("
");
+
+ sb.Append("ModelsBuilder is enabled, with the following configuration:");
+
+ sb.Append("
");
+
+ sb.Append("- The models factory is ");
+ sb.Append(_config.EnableFactory || _config.ModelsMode == ModelsMode.PureLive
+ ? "enabled"
+ : "not enabled. Umbraco will not use models");
+ sb.Append(".
");
+
+ sb.Append(_config.ModelsMode != ModelsMode.Nothing
+ ? $"- {_config.ModelsMode} models are enabled.
"
+ : "- No models mode is specified: models will not be generated.
");
+
+ sb.Append($"- Models namespace is {_config.ModelsNamespace}.
");
+
+ sb.Append("- Tracking of out-of-date models is ");
+ sb.Append(_config.FlagOutOfDateModels ? "enabled" : "not enabled");
+ sb.Append(".
");
+
+ sb.Append("
");
+
+ return sb.ToString();
+ }
+ }
+}
diff --git a/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderBackOfficeController.cs b/src/Umbraco.ModelsBuilder/BackOffice/ModelsBuilderBackOfficeController.cs
similarity index 64%
rename from src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderBackOfficeController.cs
rename to src/Umbraco.ModelsBuilder/BackOffice/ModelsBuilderBackOfficeController.cs
index 88bfb7d7c2..561da0a3d6 100644
--- a/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderBackOfficeController.cs
+++ b/src/Umbraco.ModelsBuilder/BackOffice/ModelsBuilderBackOfficeController.cs
@@ -9,11 +9,11 @@ using System.Web.Hosting;
using Umbraco.Core.Exceptions;
using Umbraco.ModelsBuilder.Building;
using Umbraco.ModelsBuilder.Configuration;
-using Umbraco.ModelsBuilder.Dashboard;
+using Umbraco.ModelsBuilder.Umbraco;
using Umbraco.Web.Editors;
using Umbraco.Web.WebApi.Filters;
-namespace Umbraco.ModelsBuilder.Umbraco
+namespace Umbraco.ModelsBuilder.BackOffice
{
///
/// API controller for use in the Umbraco back office with Angular resources
@@ -26,13 +26,16 @@ namespace Umbraco.ModelsBuilder.Umbraco
[UmbracoApplicationAuthorize(Core.Constants.Applications.Settings)]
public class ModelsBuilderBackOfficeController : UmbracoAuthorizedJsonController
{
- private readonly UmbracoServices _umbracoServices;
- private readonly Config _config;
+ private readonly IModelsBuilderConfig _config;
+ private readonly ModelsGenerator _modelGenerator;
+ private readonly DashboardReport _dashboardReport;
- public ModelsBuilderBackOfficeController(UmbracoServices umbracoServices, Config config)
+ public ModelsBuilderBackOfficeController(IModelsBuilderConfig config, ModelsGenerator modelsGenerator)
{
//_umbracoServices = umbracoServices;
_config = config;
+ _modelGenerator = modelsGenerator;
+ _dashboardReport = new DashboardReport(config, modelsGenerator);
}
// invoked by the dashboard
@@ -51,20 +54,17 @@ namespace Umbraco.ModelsBuilder.Umbraco
return Request.CreateResponse(HttpStatusCode.OK, result2, Configuration.Formatters.JsonFormatter);
}
- var modelsDirectory = config.ModelsDirectory;
-
var bin = HostingEnvironment.MapPath("~/bin");
if (bin == null)
throw new PanicException("bin is null.");
// EnableDllModels will recycle the app domain - but this request will end properly
- GenerateModels(modelsDirectory);
-
- ModelsGenerationError.Clear();
+ _modelGenerator.GenerateModels();
+ _modelGenerator.ClearErrors();
}
catch (Exception e)
{
- ModelsGenerationError.Report("Failed to build models.", e);
+ _modelGenerator.ReportError("Failed to build models.", e);
}
return Request.CreateResponse(HttpStatusCode.OK, GetDashboardResult(), Configuration.Formatters.JsonFormatter);
@@ -76,9 +76,9 @@ namespace Umbraco.ModelsBuilder.Umbraco
public HttpResponseMessage GetModelsOutOfDateStatus()
{
var status = OutOfDateModelsStatus.IsEnabled
- ? (OutOfDateModelsStatus.IsOutOfDate
+ ? OutOfDateModelsStatus.IsOutOfDate
? new OutOfDateStatus { Status = OutOfDateType.OutOfDate }
- : new OutOfDateStatus { Status = OutOfDateType.Current })
+ : new OutOfDateStatus { Status = OutOfDateType.Current }
: new OutOfDateStatus { Status = OutOfDateType.Unknown };
return Request.CreateResponse(HttpStatusCode.OK, status, Configuration.Formatters.JsonFormatter);
@@ -98,52 +98,13 @@ namespace Umbraco.ModelsBuilder.Umbraco
return new Dashboard
{
Enable = true,
- Text = BuilderDashboardHelper.Text(),
- CanGenerate = BuilderDashboardHelper.CanGenerate(),
- OutOfDateModels = BuilderDashboardHelper.AreModelsOutOfDate(),
- LastError = BuilderDashboardHelper.LastError(),
+ Text = _dashboardReport.Text(),
+ CanGenerate = _dashboardReport.CanGenerate(),
+ OutOfDateModels = _dashboardReport.AreModelsOutOfDate(),
+ LastError = _dashboardReport.LastError(),
};
}
- private void GenerateModels(string modelsDirectory)
- {
- GenerateModels(_umbracoServices, modelsDirectory, _config.ModelsNamespace);
- }
-
- internal static void GenerateModels(UmbracoServices umbracoServices, string modelsDirectory, string modelsNamespace)
- {
- if (!Directory.Exists(modelsDirectory))
- Directory.CreateDirectory(modelsDirectory);
-
- foreach (var file in Directory.GetFiles(modelsDirectory, "*.generated.cs"))
- File.Delete(file);
-
- var typeModels = umbracoServices.GetAllTypes();
-
- var builder = new TextBuilder(typeModels, modelsNamespace);
-
- foreach (var typeModel in builder.GetModelsToGenerate())
- {
- var sb = new StringBuilder();
- builder.Generate(sb, typeModel);
- var filename = Path.Combine(modelsDirectory, typeModel.ClrName + ".generated.cs");
- File.WriteAllText(filename, sb.ToString());
- }
-
- // the idea was to calculate the current hash and to add it as an extra file to the compilation,
- // in order to be able to detect whether a DLL is consistent with an environment - however the
- // environment *might not* contain the local partial files, and thus it could be impossible to
- // calculate the hash. So... maybe that's not a good idea after all?
- /*
- var currentHash = HashHelper.Hash(ourFiles, typeModels);
- ourFiles["models.hash.cs"] = $@"using Umbraco.ModelsBuilder;
-[assembly:ModelsBuilderAssembly(SourceHash = ""{currentHash}"")]
-";
- */
-
- OutOfDateModelsStatus.Clear();
- }
-
[DataContract]
internal class BuildResult
{
diff --git a/src/Umbraco.ModelsBuilder/Building/Builder.cs b/src/Umbraco.ModelsBuilder/Building/Builder.cs
index f65d8e9e7c..fa05b9d9a1 100644
--- a/src/Umbraco.ModelsBuilder/Building/Builder.cs
+++ b/src/Umbraco.ModelsBuilder/Building/Builder.cs
@@ -23,12 +23,11 @@ namespace Umbraco.ModelsBuilder.Building
///
internal abstract class Builder
{
+
private readonly IList _typeModels;
protected Dictionary ModelsMap { get; } = new Dictionary();
- private static Config Config => Current.Configs.ModelsBuilder();
-
// the list of assemblies that will be 'using' by default
protected readonly IList TypesUsing = new List
{
@@ -69,27 +68,20 @@ namespace Umbraco.ModelsBuilder.Building
/// Includes those that are ignored.
internal IList TypeModels => _typeModels;
- ///
- /// Initializes a new instance of the class with a list of models to generate
- /// and the result of code parsing.
- ///
- /// The list of models to generate.
- protected Builder(IList typeModels)
- : this(typeModels, null)
- { }
-
///
/// Initializes a new instance of the class with a list of models to generate,
/// the result of code parsing, and a models namespace.
///
/// The list of models to generate.
/// The models namespace.
- protected Builder(IList typeModels, string modelsNamespace)
+ protected Builder(IModelsBuilderConfig config, IList typeModels)
{
_typeModels = typeModels ?? throw new ArgumentNullException(nameof(typeModels));
+ Config = config ?? throw new ArgumentNullException(nameof(config));
+
// can be null or empty, we'll manage
- ModelsNamespace = modelsNamespace;
+ ModelsNamespace = Config.ModelsNamespace;
// but we want it to prepare
Prepare();
@@ -99,6 +91,8 @@ namespace Umbraco.ModelsBuilder.Building
protected Builder()
{ }
+ protected IModelsBuilderConfig Config { get; }
+
///
/// Prepares generation by processing the result of code parsing.
///
@@ -204,6 +198,8 @@ namespace Umbraco.ModelsBuilder.Building
// cannot figure out is a symbol is ambiguous without Roslyn
// so... let's say everything is ambiguous - code won't be
// pretty but it'll work
+
+ // Essentially this means that a `global::` syntax will be output for the generated models
return true;
}
@@ -220,7 +216,7 @@ namespace Umbraco.ModelsBuilder.Building
// default
// fixme - should NOT reference config here, should make ModelsNamespace mandatory
- return Config.ModelsNamespace;
+ return string.IsNullOrWhiteSpace(Config.ModelsNamespace) ? ModelsBuilderConfig.DefaultModelsNamespace : Config.ModelsNamespace;
}
protected string GetModelsBaseClassName(TypeModel type)
diff --git a/src/Umbraco.ModelsBuilder/Building/TextBuilder.cs b/src/Umbraco.ModelsBuilder/Building/TextBuilder.cs
index 7121dad1a9..79a64bd1ed 100644
--- a/src/Umbraco.ModelsBuilder/Building/TextBuilder.cs
+++ b/src/Umbraco.ModelsBuilder/Building/TextBuilder.cs
@@ -20,31 +20,19 @@ namespace Umbraco.ModelsBuilder.Building
/// and the result of code parsing.
///
/// The list of models to generate.
- public TextBuilder(IList typeModels)
- : base(typeModels)
- { }
-
- ///
- /// Initializes a new instance of the class with a list of models to generate,
- /// the result of code parsing, and a models namespace.
- ///
- /// The list of models to generate.
- /// The models namespace.
- public TextBuilder(IList typeModels, string modelsNamespace)
- : base(typeModels, modelsNamespace)
+ public TextBuilder(IModelsBuilderConfig config, IList typeModels)
+ : base(config, typeModels)
{ }
// internal for unit tests only
internal TextBuilder()
{ }
- private static Config Config => Current.Configs.ModelsBuilder();
-
///
- /// Outputs a generated model to a string builder.
- ///
- /// The string builder.
- /// The model to generate.
+ /// Outputs a generated model to a string builder.
+ ///
+ /// The string builder.
+ /// The model to generate.
public void Generate(StringBuilder sb, TypeModel typeModel)
{
WriteHeader(sb);
@@ -354,7 +342,7 @@ namespace Umbraco.ModelsBuilder.Building
var mixinStaticGetterName = MixinStaticGetterName(property.ClrName);
- if (type.StaticMixinMethods.Contains(mixinStaticGetterName)) return;
+ //if (type.StaticMixinMethods.Contains(mixinStaticGetterName)) return;
sb.Append("\n");
diff --git a/src/Umbraco.ModelsBuilder/Building/TypeModel.cs b/src/Umbraco.ModelsBuilder/Building/TypeModel.cs
index 941894774e..06b5e7848a 100644
--- a/src/Umbraco.ModelsBuilder/Building/TypeModel.cs
+++ b/src/Umbraco.ModelsBuilder/Building/TypeModel.cs
@@ -77,10 +77,10 @@ namespace Umbraco.ModelsBuilder.Building
///
public readonly List ImplementingInterfaces = new List();
- ///
- /// Gets the list of existing static mixin method candidates.
- ///
- public readonly List StaticMixinMethods = new List();
+ /////
+ ///// Gets the list of existing static mixin method candidates.
+ /////
+ //public readonly List StaticMixinMethods = new List(); //TODO: Do we need this? it isn't used
///
/// Gets a value indicating whether this model has a base class.
diff --git a/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderComponent.cs b/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderComponent.cs
similarity index 81%
rename from src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderComponent.cs
rename to src/Umbraco.ModelsBuilder/Compose/ModelsBuilderComponent.cs
index 35f953d3f1..6a9b8e1115 100644
--- a/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderComponent.cs
+++ b/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderComponent.cs
@@ -8,23 +8,25 @@ using Umbraco.Core.Composing;
using Umbraco.Core.IO;
using Umbraco.Core.Services;
using Umbraco.Core.Services.Implement;
+using Umbraco.ModelsBuilder.BackOffice;
using Umbraco.ModelsBuilder.Configuration;
+using Umbraco.ModelsBuilder.Umbraco;
using Umbraco.Web;
using Umbraco.Web.JavaScript;
using Umbraco.Web.Mvc;
-namespace Umbraco.ModelsBuilder.Umbraco
+namespace Umbraco.ModelsBuilder.Compose
{
public class ModelsBuilderComponent : IComponent
{
- private readonly UmbracoServices _umbracoServices;
- private readonly Config _config;
+ private readonly IModelsBuilderConfig _config;
+ private readonly LiveModelsProvider _liveModelsProvider;
- public ModelsBuilderComponent(UmbracoServices umbracoServices, Config config)
+ public ModelsBuilderComponent(IModelsBuilderConfig config, LiveModelsProvider liveModelsProvider)
{
- _umbracoServices = umbracoServices;
_config = config;
+ _liveModelsProvider = liveModelsProvider;
}
public void Initialize()
@@ -39,7 +41,7 @@ namespace Umbraco.ModelsBuilder.Umbraco
// fixme LiveModelsProvider should not be static
if (_config.ModelsMode.IsLiveNotPure())
- LiveModelsProvider.Install(_umbracoServices);
+ _liveModelsProvider.Install();
// fixme OutOfDateModelsStatus should not be static
if (_config.FlagOutOfDateModels)
@@ -105,7 +107,6 @@ namespace Umbraco.ModelsBuilder.Umbraco
throw new InvalidOperationException("The additionalData key: ContentTypeAlias was not found");
foreach (var template in e.SavedEntities)
- {
// if it is in fact a new entity (not been saved yet) and the "CreateTemplateForContentType" key
// is found, then it means a new template is being created based on the creation of a document type
if (!template.HasIdentity && string.IsNullOrWhiteSpace(template.Content))
@@ -129,7 +130,6 @@ namespace Umbraco.ModelsBuilder.Umbraco
//set the template content to the new markup
template.Content = markup;
}
- }
}
private void ContentModelBinder_ModelBindingException(object sender, ContentModelBinder.ModelBindingArgs args)
@@ -154,32 +154,30 @@ namespace Umbraco.ModelsBuilder.Umbraco
}
// both are ModelsBuilder types
- var pureSource = sourceAttr.PureLive;
- var pureModel = modelAttr.PureLive;
+ var pureSource = sourceAttr.PureLive;
+ var pureModel = modelAttr.PureLive;
- if (sourceAttr.PureLive || modelAttr.PureLive)
- {
- if (pureSource == false || pureModel == false)
- {
+ if (sourceAttr.PureLive || modelAttr.PureLive)
+ if (pureSource == false || pureModel == false)
+ {
// only one is pure - report, but better not restart (loops?)
- args.Message.Append(pureSource
- ? " The content model is PureLive, but the view model is not."
- : " The view model is PureLive, but the content model is not.");
- args.Message.Append(" The application is in an unstable state and should be restarted.");
- }
- else
- {
+ args.Message.Append(pureSource
+ ? " The content model is PureLive, but the view model is not."
+ : " The view model is PureLive, but the content model is not.");
+ args.Message.Append(" The application is in an unstable state and should be restarted.");
+ }
+ else
+ {
// both are pure - report, and if different versions, restart
// if same version... makes no sense... and better not restart (loops?)
- var sourceVersion = args.SourceType.Assembly.GetName().Version;
+ var sourceVersion = args.SourceType.Assembly.GetName().Version;
var modelVersion = args.ModelType.Assembly.GetName().Version;
- args.Message.Append(" Both view and content models are PureLive, with ");
- args.Message.Append(sourceVersion == modelVersion
- ? "same version. The application is in an unstable state and should be restarted."
- : "different versions. The application is in an unstable state and is going to be restarted.");
- args.Restart = sourceVersion != modelVersion;
- }
- }
+ args.Message.Append(" Both view and content models are PureLive, with ");
+ args.Message.Append(sourceVersion == modelVersion
+ ? "same version. The application is in an unstable state and should be restarted."
+ : "different versions. The application is in an unstable state and is going to be restarted.");
+ args.Restart = sourceVersion != modelVersion;
+ }
}
}
}
diff --git a/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderComposer.cs b/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderComposer.cs
similarity index 89%
rename from src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderComposer.cs
rename to src/Umbraco.ModelsBuilder/Compose/ModelsBuilderComposer.cs
index 3dae2f86ab..71250b2eb6 100644
--- a/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderComposer.cs
+++ b/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderComposer.cs
@@ -3,9 +3,10 @@ using Umbraco.Core;
using Umbraco.Core.Composing;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.ModelsBuilder.Configuration;
+using Umbraco.ModelsBuilder.Umbraco;
using Umbraco.Web.PublishedCache.NuCache;
-namespace Umbraco.ModelsBuilder.Umbraco
+namespace Umbraco.ModelsBuilder.Compose
{
[ComposeBefore(typeof(NuCacheComposer))]
[RuntimeLevel(MinLevel = RuntimeLevel.Run)]
@@ -16,7 +17,9 @@ namespace Umbraco.ModelsBuilder.Umbraco
base.Compose(composition);
composition.Register(Lifetime.Singleton);
- composition.Configs.Add(() => new Config());
+ composition.Configs.Add(() => new ModelsBuilderConfig());
+ composition.RegisterUnique();
+ composition.RegisterUnique();
if (composition.Configs.ModelsBuilder().ModelsMode == ModelsMode.PureLive)
ComposeForLiveModels(composition);
diff --git a/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderInitializer.cs b/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderInitializer.cs
similarity index 88%
rename from src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderInitializer.cs
rename to src/Umbraco.ModelsBuilder/Compose/ModelsBuilderInitializer.cs
index 9bd662da37..63f2336ebf 100644
--- a/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderInitializer.cs
+++ b/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderInitializer.cs
@@ -1,12 +1,12 @@
using System.Web;
using System.Web.Compilation;
-using Umbraco.ModelsBuilder.Umbraco;
+using Umbraco.ModelsBuilder.Compose;
[assembly: PreApplicationStartMethod(typeof(ModelsBuilderInitializer), "Initialize")]
-namespace Umbraco.ModelsBuilder.Umbraco
+namespace Umbraco.ModelsBuilder.Compose
{
- public static class ModelsBuilderInitializer
+ internal static class ModelsBuilderInitializer
{
public static void Initialize()
{
diff --git a/src/Umbraco.ModelsBuilder/ConfigsExtensions.cs b/src/Umbraco.ModelsBuilder/ConfigsExtensions.cs
index dc0b136422..c989be5aca 100644
--- a/src/Umbraco.ModelsBuilder/ConfigsExtensions.cs
+++ b/src/Umbraco.ModelsBuilder/ConfigsExtensions.cs
@@ -14,7 +14,7 @@ namespace Umbraco.ModelsBuilder
/// Getting the models builder configuration freezes its state,
/// and any attempt at modifying the configuration using the Setup method
/// will be ignored.
- public static Config ModelsBuilder(this Configs configs)
- => configs.GetConfig();
+ public static ModelsBuilderConfig ModelsBuilder(this Configs configs)
+ => configs.GetConfig();
}
}
\ No newline at end of file
diff --git a/src/Umbraco.ModelsBuilder/Configuration/IModelsBuilderConfig.cs b/src/Umbraco.ModelsBuilder/Configuration/IModelsBuilderConfig.cs
new file mode 100644
index 0000000000..3bca389f2f
--- /dev/null
+++ b/src/Umbraco.ModelsBuilder/Configuration/IModelsBuilderConfig.cs
@@ -0,0 +1,14 @@
+namespace Umbraco.ModelsBuilder.Configuration
+{
+ public interface IModelsBuilderConfig
+ {
+ bool AcceptUnsafeModelsDirectory { get; }
+ int DebugLevel { get; }
+ bool EnableFactory { get; }
+ bool FlagOutOfDateModels { get; }
+ bool IsDebug { get; }
+ string ModelsDirectory { get; }
+ ModelsMode ModelsMode { get; }
+ string ModelsNamespace { get; }
+ }
+}
diff --git a/src/Umbraco.ModelsBuilder/Configuration/Config.cs b/src/Umbraco.ModelsBuilder/Configuration/ModelsBuilderConfig.cs
similarity index 90%
rename from src/Umbraco.ModelsBuilder/Configuration/Config.cs
rename to src/Umbraco.ModelsBuilder/Configuration/ModelsBuilderConfig.cs
index cf98e383eb..caff5001a4 100644
--- a/src/Umbraco.ModelsBuilder/Configuration/Config.cs
+++ b/src/Umbraco.ModelsBuilder/Configuration/ModelsBuilderConfig.cs
@@ -4,29 +4,28 @@ using System.IO;
using System.Web.Configuration;
using System.Web.Hosting;
using Umbraco.Core;
+using Umbraco.Core.IO;
namespace Umbraco.ModelsBuilder.Configuration
{
///
/// Represents the models builder configuration.
///
- public class Config
+ public class ModelsBuilderConfig : IModelsBuilderConfig
{
- internal const string DefaultModelsNamespace = "Umbraco.Web.PublishedModels";
- internal const string DefaultModelsDirectory = "~/App_Data/Models";
+ public const string DefaultModelsNamespace = "Umbraco.Web.PublishedModels";
+ public const string DefaultModelsDirectory = "~/App_Data/Models";
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
- public Config()
+ public ModelsBuilderConfig()
{
const string prefix = "Umbraco.ModelsBuilder.";
// ensure defaults are initialized for tests
ModelsNamespace = DefaultModelsNamespace;
- ModelsDirectory = HostingEnvironment.IsHosted
- ? HostingEnvironment.MapPath(DefaultModelsDirectory)
- : DefaultModelsDirectory.TrimStart("~/");
+ ModelsDirectory = IOHelper.MapPath("~/");
DebugLevel = 0;
// mode
@@ -69,9 +68,7 @@ namespace Umbraco.ModelsBuilder.Configuration
value = ConfigurationManager.AppSettings[prefix + "ModelsDirectory"];
if (!string.IsNullOrWhiteSpace(value))
{
- var root = HostingEnvironment.IsHosted
- ? HostingEnvironment.MapPath("~/")
- : Directory.GetCurrentDirectory();
+ var root = IOHelper.MapPath("~/");
if (root == null)
throw new ConfigurationErrorsException("Could not determine root directory.");
@@ -95,9 +92,9 @@ namespace Umbraco.ModelsBuilder.Configuration
}
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
- public Config(
+ public ModelsBuilderConfig(
ModelsMode modelsMode = ModelsMode.Nothing,
string modelsNamespace = null,
bool enableFactory = true,
@@ -159,7 +156,7 @@ namespace Umbraco.ModelsBuilder.Configuration
{
get
{
- var section = (CompilationSection) ConfigurationManager.GetSection("system.web/compilation");
+ var section = (CompilationSection)ConfigurationManager.GetSection("system.web/compilation");
return section != null && section.Debug;
}
}
diff --git a/src/Umbraco.ModelsBuilder/Configuration/ModelsMode.cs b/src/Umbraco.ModelsBuilder/Configuration/ModelsMode.cs
index cc36099bc5..1f1d65f4f1 100644
--- a/src/Umbraco.ModelsBuilder/Configuration/ModelsMode.cs
+++ b/src/Umbraco.ModelsBuilder/Configuration/ModelsMode.cs
@@ -8,7 +8,7 @@
///
/// Do not generate models.
///
- Nothing = 0, // default value
+ Nothing = 0, // default value //TODO: This doesn't make sense since we cannot actualy disable MB since Umbraco would die
///
/// Generate models in memory.
diff --git a/src/Umbraco.ModelsBuilder/Dashboard/BuilderDashboardHelper.cs b/src/Umbraco.ModelsBuilder/Dashboard/BuilderDashboardHelper.cs
deleted file mode 100644
index ee9c384f9d..0000000000
--- a/src/Umbraco.ModelsBuilder/Dashboard/BuilderDashboardHelper.cs
+++ /dev/null
@@ -1,62 +0,0 @@
-using System.Text;
-using Umbraco.Core.Composing;
-using Umbraco.ModelsBuilder.Configuration;
-using Umbraco.ModelsBuilder.Umbraco;
-
-namespace Umbraco.ModelsBuilder.Dashboard
-{
- internal static class BuilderDashboardHelper
- {
- private static Config Config => Current.Configs.ModelsBuilder();
-
- public static bool CanGenerate()
- {
- return Config.ModelsMode.SupportsExplicitGeneration();
- }
-
- public static bool AreModelsOutOfDate()
- {
- return OutOfDateModelsStatus.IsOutOfDate;
- }
-
- public static string LastError()
- {
- return ModelsGenerationError.GetLastError();
- }
-
- public static string Text()
- {
- var config = Config;
-
- var sb = new StringBuilder();
-
- sb.Append("Version: ");
- sb.Append(Api.ApiVersion.Current.Version);
- sb.Append("
");
-
- sb.Append("ModelsBuilder is enabled, with the following configuration:");
-
- sb.Append("");
-
- sb.Append("- The models factory is ");
- sb.Append(config.EnableFactory || config.ModelsMode == ModelsMode.PureLive
- ? "enabled"
- : "not enabled. Umbraco will not use models");
- sb.Append(".
");
-
- sb.Append(config.ModelsMode != ModelsMode.Nothing
- ? $"- {config.ModelsMode} models are enabled.
"
- : "- No models mode is specified: models will not be generated.
");
-
- sb.Append($"- Models namespace is {config.ModelsNamespace}.
");
-
- sb.Append("- Tracking of out-of-date models is ");
- sb.Append(config.FlagOutOfDateModels ? "enabled" : "not enabled");
- sb.Append(".
");
-
- sb.Append("
");
-
- return sb.ToString();
- }
- }
-}
diff --git a/src/Umbraco.ModelsBuilder/Umbraco/HashCombiner.cs b/src/Umbraco.ModelsBuilder/HashCombiner.cs
similarity index 76%
rename from src/Umbraco.ModelsBuilder/Umbraco/HashCombiner.cs
rename to src/Umbraco.ModelsBuilder/HashCombiner.cs
index e11662eb24..51e02e93c1 100644
--- a/src/Umbraco.ModelsBuilder/Umbraco/HashCombiner.cs
+++ b/src/Umbraco.ModelsBuilder/HashCombiner.cs
@@ -1,17 +1,17 @@
using System;
using System.Globalization;
-namespace Umbraco.ModelsBuilder.Umbraco
+namespace Umbraco.ModelsBuilder
{
// because, of course, it's internal in Umbraco
// see also System.Web.Util.HashCodeCombiner
- class HashCombiner
+ internal class HashCombiner
{
private long _combinedHash = 5381L;
public void Add(int i)
{
- _combinedHash = ((_combinedHash << 5) + _combinedHash) ^ i;
+ _combinedHash = (_combinedHash << 5) + _combinedHash ^ i;
}
public void Add(object o)
@@ -27,7 +27,7 @@ namespace Umbraco.ModelsBuilder.Umbraco
public void Add(string s)
{
if (s == null) return;
- Add((StringComparer.InvariantCulture).GetHashCode(s));
+ Add(StringComparer.InvariantCulture.GetHashCode(s));
}
public string GetCombinedHashCode()
diff --git a/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj b/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj
index a16579e60d..6945cc4b31 100644
--- a/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj
+++ b/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj
@@ -56,10 +56,11 @@
-
+
+
-
+
@@ -67,13 +68,15 @@
-
-
+
-
-
-
-
+
+
+
+
+
+
+
@@ -92,10 +95,6 @@
-
- {29aa69d9-b597-4395-8d42-43b1263c240a}
- Umbraco.Abstractions
-
{31785BC3-256C-4613-B2F5-A1B0BDDED8C1}
Umbraco.Core
@@ -110,5 +109,8 @@
5.2.7
+
+
+
\ No newline at end of file
diff --git a/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProvider.cs b/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProvider.cs
index d8839a1297..146e837dd9 100644
--- a/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProvider.cs
+++ b/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProvider.cs
@@ -2,43 +2,46 @@
using System.Threading;
using System.Web;
using System.Web.Hosting;
-using Umbraco.Core.Composing;
using Umbraco.Core.Exceptions;
+using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.ModelsBuilder.Configuration;
using Umbraco.ModelsBuilder.Umbraco;
using Umbraco.Web.Cache;
-// will install only if configuration says it needs to be installed
-[assembly: PreApplicationStartMethod(typeof(LiveModelsProviderModule), "Install")]
-
namespace Umbraco.ModelsBuilder.Umbraco
{
// supports LiveDll and LiveAppData - but not PureLive
public sealed class LiveModelsProvider
{
- private static UmbracoServices _umbracoServices;
private static Mutex _mutex;
private static int _req;
-
- private static Config Config => Current.Configs.ModelsBuilder();
+ private readonly ILogger _logger;
+ private readonly IModelsBuilderConfig _config;
+ private readonly ModelsGenerator _modelGenerator;
// we do not manage pure live here
- internal static bool IsEnabled => Config.ModelsMode.IsLiveNotPure();
+ internal bool IsEnabled => _config.ModelsMode.IsLiveNotPure();
- internal static void Install(UmbracoServices umbracoServices)
+ public LiveModelsProvider(ILogger logger, IModelsBuilderConfig config, ModelsGenerator modelGenerator)
+ {
+ _logger = logger;
+ _config = config ?? throw new ArgumentNullException(nameof(config));
+ _modelGenerator = modelGenerator;
+ }
+
+ internal void Install()
{
// just be sure
if (!IsEnabled)
return;
- _umbracoServices = umbracoServices;
-
// initialize mutex
// ApplicationId will look like "/LM/W3SVC/1/Root/AppName"
// name is system-wide and must be less than 260 chars
var name = HostingEnvironment.ApplicationID + "/UmbracoLiveModelsProvider";
- _mutex = new Mutex(false, name);
+
+ _mutex = new Mutex(false, name); //TODO: Replace this with MainDom? Seems we now have 2x implementations of almost the same thing
// anything changes, and we want to re-generate models.
ContentTypeCacheRefresher.CacheUpdated += RequestModelsGeneration;
@@ -57,14 +60,14 @@ namespace Umbraco.ModelsBuilder.Umbraco
// need to be generated. Could be by another request. Anyway. We could
// have collisions but... you know the risk.
- private static void RequestModelsGeneration(object sender, EventArgs args)
+ private void RequestModelsGeneration(object sender, EventArgs args)
{
//HttpContext.Current.Items[this] = true;
- Current.Logger.Debug("Requested to generate models.");
+ _logger.Debug("Requested to generate models.");
Interlocked.Exchange(ref _req, 1);
}
- public static void GenerateModelsIfRequested(object sender, EventArgs args)
+ public void GenerateModelsIfRequested(object sender, EventArgs args)
{
//if (HttpContext.Current.Items[this] == null) return;
if (Interlocked.Exchange(ref _req, 0) == 0) return;
@@ -74,22 +77,22 @@ namespace Umbraco.ModelsBuilder.Umbraco
try
{
- Current.Logger.Debug("Generate models...");
+ _logger.Debug("Generate models...");
const int timeout = 2*60*1000; // 2 mins
_mutex.WaitOne(timeout); // wait until it is safe, and acquire
- Current.Logger.Info("Generate models now.");
+ _logger.Info("Generate models now.");
GenerateModels();
- ModelsGenerationError.Clear();
- Current.Logger.Info("Generated.");
+ _modelGenerator.ClearErrors();
+ _logger.Info("Generated.");
}
catch (TimeoutException)
{
- Current.Logger.Warn("Timeout, models were NOT generated.");
+ _logger.Warn("Timeout, models were NOT generated.");
}
catch (Exception e)
{
- ModelsGenerationError.Report("Failed to build Live models.", e);
- Current.Logger.Error("Failed to generate models.", e);
+ _modelGenerator.ReportError("Failed to build Live models.", e);
+ _logger.Error("Failed to generate models.", e);
}
finally
{
@@ -97,38 +100,16 @@ namespace Umbraco.ModelsBuilder.Umbraco
}
}
- private static void GenerateModels()
+ private void GenerateModels()
{
- var modelsDirectory = Config.ModelsDirectory;
- var modelsNamespace = Config.ModelsNamespace;
-
- var bin = HostingEnvironment.MapPath("~/bin");
+ var bin = IOHelper.MapPath("~/bin");
if (bin == null)
throw new PanicException("Panic: bin is null.");
// EnableDllModels will recycle the app domain - but this request will end properly
- ModelsBuilderBackOfficeController.GenerateModels(_umbracoServices, modelsDirectory, modelsNamespace);
- }
- }
-
- // have to do this because it's the only way to subscribe to EndRequest,
- // module is installed by assembly attribute at the top of this file
- public class LiveModelsProviderModule : IHttpModule
- {
- public void Init(HttpApplication app)
- {
- app.EndRequest += LiveModelsProvider.GenerateModelsIfRequested;
+ _modelGenerator.GenerateModels();
}
- public void Dispose()
- {
- // nothing
- }
-
- public static void Install()
- {
- // always - don't read config in PreApplicationStartMethod
- HttpApplication.RegisterModule(typeof(LiveModelsProviderModule));
- }
+
}
}
diff --git a/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProviderModule.cs b/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProviderModule.cs
new file mode 100644
index 0000000000..1dadbd41b6
--- /dev/null
+++ b/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProviderModule.cs
@@ -0,0 +1,44 @@
+using System;
+using System.Web;
+using Umbraco.Core;
+using Umbraco.Core.Composing;
+using Umbraco.ModelsBuilder.Umbraco;
+
+// will install only if configuration says it needs to be installed
+[assembly: PreApplicationStartMethod(typeof(LiveModelsProviderModule), "Install")]
+
+namespace Umbraco.ModelsBuilder.Umbraco
+{
+ // have to do this because it's the only way to subscribe to EndRequest,
+ // module is installed by assembly attribute at the top of this file
+ public class LiveModelsProviderModule : IHttpModule
+ {
+ private static LiveModelsProvider _liveModelsProvider;
+
+ public void Init(HttpApplication app)
+ {
+ app.EndRequest += App_EndRequest;
+ }
+
+ private void App_EndRequest(object sender, EventArgs e)
+ {
+ // here we're using "Current." since we're in a module, it is possible in a round about way to inject into a module but for now we'll just use Current
+ if (_liveModelsProvider == null)
+ _liveModelsProvider = Current.Factory.GetInstance();
+
+ if (_liveModelsProvider.IsEnabled)
+ _liveModelsProvider.GenerateModelsIfRequested(sender, e);
+ }
+
+ public void Dispose()
+ {
+ // nothing
+ }
+
+ public static void Install()
+ {
+ // always - don't read config in PreApplicationStartMethod
+ HttpApplication.RegisterModule(typeof(LiveModelsProviderModule));
+ }
+ }
+}
diff --git a/src/Umbraco.ModelsBuilder/Umbraco/HashHelper.cs b/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderHasher.cs
similarity index 97%
rename from src/Umbraco.ModelsBuilder/Umbraco/HashHelper.cs
rename to src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderHasher.cs
index e4a0705ec0..3354b4040d 100644
--- a/src/Umbraco.ModelsBuilder/Umbraco/HashHelper.cs
+++ b/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderHasher.cs
@@ -4,7 +4,7 @@ using Umbraco.ModelsBuilder.Building;
namespace Umbraco.ModelsBuilder.Umbraco
{
- class HashHelper
+ internal class ModelsBuilderHasher
{
public static string Hash(IEnumerable typeModels)
{
diff --git a/src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerationError.cs b/src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerationError.cs
index a7b437df57..a7cb1e11c3 100644
--- a/src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerationError.cs
+++ b/src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerationError.cs
@@ -6,11 +6,16 @@ using Umbraco.ModelsBuilder.Configuration;
namespace Umbraco.ModelsBuilder.Umbraco
{
- internal static class ModelsGenerationError
+ internal class ModelsGenerationError
{
- private static Config Config => Current.Configs.ModelsBuilder();
+ private readonly IModelsBuilderConfig _config;
- public static void Clear()
+ public ModelsGenerationError(IModelsBuilderConfig config)
+ {
+ _config = config;
+ }
+
+ public void Clear()
{
var errFile = GetErrFile();
if (errFile == null) return;
@@ -19,7 +24,7 @@ namespace Umbraco.ModelsBuilder.Umbraco
File.Delete(errFile);
}
- public static void Report(string message, Exception e)
+ public void Report(string message, Exception e)
{
var errFile = GetErrFile();
if (errFile == null) return;
@@ -35,7 +40,7 @@ namespace Umbraco.ModelsBuilder.Umbraco
File.WriteAllText(errFile, sb.ToString());
}
- public static string GetLastError()
+ public string GetLastError()
{
var errFile = GetErrFile();
if (errFile == null) return null;
@@ -50,9 +55,9 @@ namespace Umbraco.ModelsBuilder.Umbraco
}
}
- private static string GetErrFile()
+ private string GetErrFile()
{
- var modelsDirectory = Config.ModelsDirectory;
+ var modelsDirectory = _config.ModelsDirectory;
if (!Directory.Exists(modelsDirectory))
return null;
diff --git a/src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerator.cs b/src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerator.cs
new file mode 100644
index 0000000000..4e3607c156
--- /dev/null
+++ b/src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerator.cs
@@ -0,0 +1,63 @@
+using System;
+using System.IO;
+using System.Text;
+using System.Web;
+using Umbraco.ModelsBuilder.Building;
+using Umbraco.ModelsBuilder.Configuration;
+using Umbraco.ModelsBuilder.Umbraco;
+
+namespace Umbraco.ModelsBuilder.Umbraco
+{
+ public class ModelsGenerator
+ {
+ private readonly UmbracoServices _umbracoService;
+ private readonly IModelsBuilderConfig _config;
+ private readonly ModelsGenerationError _errors;
+
+ public ModelsGenerator(UmbracoServices umbracoService, IModelsBuilderConfig config)
+ {
+ _umbracoService = umbracoService;
+ _config = config;
+ _errors = new ModelsGenerationError(config);
+ }
+
+ internal void GenerateModels()
+ {
+ if (!Directory.Exists(_config.ModelsDirectory))
+ Directory.CreateDirectory(_config.ModelsDirectory);
+
+ foreach (var file in Directory.GetFiles(_config.ModelsDirectory, "*.generated.cs"))
+ File.Delete(file);
+
+ var typeModels = _umbracoService.GetAllTypes();
+
+ var builder = new TextBuilder(_config, typeModels);
+
+ foreach (var typeModel in builder.GetModelsToGenerate())
+ {
+ var sb = new StringBuilder();
+ builder.Generate(sb, typeModel);
+ var filename = Path.Combine(_config.ModelsDirectory, typeModel.ClrName + ".generated.cs");
+ File.WriteAllText(filename, sb.ToString());
+ }
+
+ // the idea was to calculate the current hash and to add it as an extra file to the compilation,
+ // in order to be able to detect whether a DLL is consistent with an environment - however the
+ // environment *might not* contain the local partial files, and thus it could be impossible to
+ // calculate the hash. So... maybe that's not a good idea after all?
+ /*
+ var currentHash = HashHelper.Hash(ourFiles, typeModels);
+ ourFiles["models.hash.cs"] = $@"using Umbraco.ModelsBuilder;
+[assembly:ModelsBuilderAssembly(SourceHash = ""{currentHash}"")]
+";
+ */
+
+ OutOfDateModelsStatus.Clear();
+ }
+
+ internal void ClearErrors() => _errors.Clear();
+ internal void ReportError(string message, Exception e) => _errors.Report(message, e);
+ internal string GetLastError() => _errors.GetLastError();
+
+ }
+}
diff --git a/src/Umbraco.ModelsBuilder/Umbraco/OutOfDateModelsStatus.cs b/src/Umbraco.ModelsBuilder/Umbraco/OutOfDateModelsStatus.cs
index 142cc7578a..3326379871 100644
--- a/src/Umbraco.ModelsBuilder/Umbraco/OutOfDateModelsStatus.cs
+++ b/src/Umbraco.ModelsBuilder/Umbraco/OutOfDateModelsStatus.cs
@@ -7,7 +7,12 @@ namespace Umbraco.ModelsBuilder.Umbraco
{
public sealed class OutOfDateModelsStatus
{
- private static Config Config => Current.Configs.ModelsBuilder();
+ public OutOfDateModelsStatus()
+ {
+
+ }
+
+ private static ModelsBuilderConfig Config => Current.Configs.ModelsBuilder();
internal static void Install()
{
diff --git a/src/Umbraco.ModelsBuilder/Umbraco/PureLiveModelFactory.cs b/src/Umbraco.ModelsBuilder/Umbraco/PureLiveModelFactory.cs
index 05f748a588..0f888cc428 100644
--- a/src/Umbraco.ModelsBuilder/Umbraco/PureLiveModelFactory.cs
+++ b/src/Umbraco.ModelsBuilder/Umbraco/PureLiveModelFactory.cs
@@ -42,13 +42,15 @@ namespace Umbraco.ModelsBuilder.Umbraco
private const string ProjVirt = "~/App_Data/Models/all.generated.cs";
private static readonly string[] OurFiles = { "models.hash", "models.generated.cs", "all.generated.cs", "all.dll.path", "models.err" };
- private readonly Config _config;
+ private readonly IModelsBuilderConfig _config;
+ private readonly ModelsGenerator _modelGenerator;
- public PureLiveModelFactory(Lazy umbracoServices, IProfilingLogger logger, Config config)
+ public PureLiveModelFactory(Lazy umbracoServices, IProfilingLogger logger, IModelsBuilderConfig config, ModelsGenerator modelGenerator)
{
_umbracoServices = umbracoServices;
_logger = logger;
_config = config;
+ _modelGenerator = modelGenerator;
_ver = 1; // zero is for when we had no version
_skipver = -1; // nothing to skip
@@ -292,7 +294,7 @@ namespace Umbraco.ModelsBuilder.Umbraco
var types = assembly.ExportedTypes.Where(x => x.Inherits() || x.Inherits());
_infos = RegisterModels(types);
- ModelsGenerationError.Clear();
+ _modelGenerator.ClearErrors();
}
catch (Exception e)
{
@@ -300,7 +302,7 @@ namespace Umbraco.ModelsBuilder.Umbraco
{
_logger.Error("Failed to build models.", e);
_logger.Warn("Running without models."); // be explicit
- ModelsGenerationError.Report("Failed to build PureLive models.", e);
+ _modelGenerator.ReportError("Failed to build PureLive models.", e);
}
finally
{
@@ -333,7 +335,7 @@ namespace Umbraco.ModelsBuilder.Umbraco
Directory.CreateDirectory(modelsDirectory);
var typeModels = UmbracoServices.GetAllTypes();
- var currentHash = HashHelper.Hash(typeModels);
+ var currentHash = ModelsBuilderHasher.Hash(typeModels);
var modelsHashFile = Path.Combine(modelsDirectory, "models.hash");
var modelsSrcFile = Path.Combine(modelsDirectory, "models.generated.cs");
var projFile = Path.Combine(modelsDirectory, "all.generated.cs");
@@ -557,7 +559,7 @@ namespace Umbraco.ModelsBuilder.Umbraco
foreach (var file in Directory.GetFiles(modelsDirectory, "*.generated.cs"))
File.Delete(file);
- var builder = new TextBuilder(typeModels, _config.ModelsNamespace);
+ var builder = new TextBuilder(_config, typeModels);
var codeBuilder = new StringBuilder();
builder.Generate(codeBuilder, builder.GetModelsToGenerate());
diff --git a/src/Umbraco.ModelsBuilder/Umbraco/UmbracoServices.cs b/src/Umbraco.ModelsBuilder/Umbraco/UmbracoServices.cs
index 32f0703bac..410349096a 100644
--- a/src/Umbraco.ModelsBuilder/Umbraco/UmbracoServices.cs
+++ b/src/Umbraco.ModelsBuilder/Umbraco/UmbracoServices.cs
@@ -28,8 +28,6 @@ namespace Umbraco.ModelsBuilder.Umbraco
_publishedContentTypeFactory = publishedContentTypeFactory;
}
- private static Config Config => Current.Configs.ModelsBuilder();
-
#region Services
public IList GetAllTypes()
diff --git a/src/Umbraco.ModelsBuilder/Validation/ContentTypeModelValidator.cs b/src/Umbraco.ModelsBuilder/Validation/ContentTypeModelValidator.cs
index 4b38de0168..fdae56be0d 100644
--- a/src/Umbraco.ModelsBuilder/Validation/ContentTypeModelValidator.cs
+++ b/src/Umbraco.ModelsBuilder/Validation/ContentTypeModelValidator.cs
@@ -35,7 +35,7 @@ namespace Umbraco.ModelsBuilder.Validation
where TModel: ContentTypeSave
where TProperty: PropertyTypeBasic
{
- private static Config Config => Current.Configs.ModelsBuilder();
+ private static ModelsBuilderConfig Config => Current.Configs.ModelsBuilder();
protected override IEnumerable Validate(TModel model)
{
diff --git a/src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs b/src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs
new file mode 100644
index 0000000000..18be0a37a3
--- /dev/null
+++ b/src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs
@@ -0,0 +1,443 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Microsoft.CodeAnalysis;
+using Moq;
+using NUnit.Framework;
+using Umbraco.Core.Composing;
+using Umbraco.Core.Models.PublishedContent;
+using Umbraco.ModelsBuilder.Api;
+using Umbraco.ModelsBuilder.Building;
+using Umbraco.ModelsBuilder.Configuration;
+
+namespace Umbraco.ModelsBuilder.Tests
+{
+ [TestFixture]
+ public class BuilderTests
+ {
+ [SetUp]
+ public void Setup()
+ {
+ Current.Reset();
+ Current.UnlockConfigs();
+ Current.Configs.Add(() => new ModelsBuilderConfig());
+ }
+
+ [Test]
+ public void GenerateSimpleType()
+ {
+ // Umbraco returns nice, pascal-cased names
+
+ var type1 = new TypeModel
+ {
+ Id = 1,
+ Alias = "type1",
+ ClrName = "Type1",
+ ParentId = 0,
+ BaseType = null,
+ ItemType = TypeModel.ItemTypes.Content,
+ };
+ type1.Properties.Add(new PropertyModel
+ {
+ Alias = "prop1",
+ ClrName = "Prop1",
+ ModelClrType = typeof(string),
+ });
+
+ var types = new[] { type1 };
+
+ var code = new Dictionary
+ {
+ };
+
+ var builder = new TextBuilder(Mock.Of(), types);
+ var btypes = builder.TypeModels;
+
+ var sb = new StringBuilder();
+ builder.Generate(sb, builder.GetModelsToGenerate().First());
+ var gen = sb.ToString();
+
+ var version = ApiVersion.Current.Version;
+ var expected = @"//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+//
+// Umbraco.ModelsBuilder v" + version + @"
+//
+// Changes to this file will be lost if the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Linq.Expressions;
+using System.Web;
+using Umbraco.Core.Models;
+using Umbraco.Core.Models.PublishedContent;
+using Umbraco.Web;
+using Umbraco.ModelsBuilder;
+using Umbraco.ModelsBuilder.Umbraco;
+
+namespace Umbraco.Web.PublishedModels
+{
+ [PublishedModel(""type1"")]
+ public partial class Type1 : PublishedContentModel
+ {
+ // helpers
+#pragma warning disable 0109 // new is redundant
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder"", """ + version + @""")]
+ public new const string ModelTypeAlias = ""type1"";
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder"", """ + version + @""")]
+ public new const PublishedItemType ModelItemType = PublishedItemType.Content;
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder"", """ + version + @""")]
+ public new static IPublishedContentType GetModelContentType()
+ => PublishedModelUtility.GetModelContentType(ModelItemType, ModelTypeAlias);
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder"", """ + version + @""")]
+ public static IPublishedPropertyType GetModelPropertyType(Expression> selector)
+ => PublishedModelUtility.GetModelPropertyType(GetModelContentType(), selector);
+#pragma warning restore 0109
+
+ // ctor
+ public Type1(IPublishedContent content)
+ : base(content)
+ { }
+
+ // properties
+
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder"", """ + version + @""")]
+ [ImplementPropertyType(""prop1"")]
+ public string Prop1 => this.Value(""prop1"");
+ }
+}
+";
+ Console.WriteLine(gen);
+ Assert.AreEqual(expected.ClearLf(), gen);
+ }
+
+ [Test]
+ public void GenerateSimpleType_Ambiguous_Issue()
+ {
+ // Umbraco returns nice, pascal-cased names
+
+ var type1 = new TypeModel
+ {
+ Id = 1,
+ Alias = "type1",
+ ClrName = "Type1",
+ ParentId = 0,
+ BaseType = null,
+ ItemType = TypeModel.ItemTypes.Content,
+ };
+ type1.Properties.Add(new PropertyModel
+ {
+ Alias = "foo",
+ ClrName = "Foo",
+ ModelClrType = typeof(IEnumerable<>).MakeGenericType(ModelType.For("foo")),
+ });
+
+ var type2 = new TypeModel
+ {
+ Id = 2,
+ Alias = "foo",
+ ClrName = "Foo",
+ ParentId = 0,
+ BaseType = null,
+ ItemType = TypeModel.ItemTypes.Element,
+ };
+
+ var types = new[] { type1, type2 };
+
+ var code = new Dictionary
+ {
+ { "code", @"
+namespace Umbraco.Web.PublishedModels
+{
+ public partial class Foo
+ {
+ }
+}
+" }
+ };
+
+ var builder = new TextBuilder(Mock.Of(), types);
+ var btypes = builder.TypeModels;
+
+ builder.ModelsNamespace = "Umbraco.Web.PublishedModels";
+
+ var sb1 = new StringBuilder();
+ builder.Generate(sb1, builder.GetModelsToGenerate().Skip(1).First());
+ var gen1 = sb1.ToString();
+ Console.WriteLine(gen1);
+
+ var sb = new StringBuilder();
+ builder.Generate(sb, builder.GetModelsToGenerate().First());
+ var gen = sb.ToString();
+
+ var version = ApiVersion.Current.Version;
+ var expected = @"//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+//
+// Umbraco.ModelsBuilder v" + version + @"
+//
+// Changes to this file will be lost if the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Linq.Expressions;
+using System.Web;
+using Umbraco.Core.Models;
+using Umbraco.Core.Models.PublishedContent;
+using Umbraco.Web;
+using Umbraco.ModelsBuilder;
+using Umbraco.ModelsBuilder.Umbraco;
+
+namespace Umbraco.Web.PublishedModels
+{
+ [PublishedModel(""type1"")]
+ public partial class Type1 : PublishedContentModel
+ {
+ // helpers
+#pragma warning disable 0109 // new is redundant
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder"", """ + version + @""")]
+ public new const string ModelTypeAlias = ""type1"";
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder"", """ + version + @""")]
+ public new const PublishedItemType ModelItemType = PublishedItemType.Content;
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder"", """ + version + @""")]
+ public new static IPublishedContentType GetModelContentType()
+ => PublishedModelUtility.GetModelContentType(ModelItemType, ModelTypeAlias);
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder"", """ + version + @""")]
+ public static IPublishedPropertyType GetModelPropertyType(Expression> selector)
+ => PublishedModelUtility.GetModelPropertyType(GetModelContentType(), selector);
+#pragma warning restore 0109
+
+ // ctor
+ public Type1(IPublishedContent content)
+ : base(content)
+ { }
+
+ // properties
+
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder"", """ + version + @""")]
+ [ImplementPropertyType(""foo"")]
+ public global::System.Collections.Generic.IEnumerable Foo => this.Value>(""foo"");
+ }
+}
+";
+ Console.WriteLine(gen);
+ Assert.AreEqual(expected.ClearLf(), gen);
+ }
+
+ [Test]
+ public void GenerateAmbiguous()
+ {
+ // NOTE: since
+
+ var type1 = new TypeModel
+ {
+ Id = 1,
+ Alias = "type1",
+ ClrName = "Type1",
+ ParentId = 0,
+ BaseType = null,
+ ItemType = TypeModel.ItemTypes.Content,
+ IsMixin = true,
+ };
+ type1.Properties.Add(new PropertyModel
+ {
+ Alias = "prop1",
+ ClrName = "Prop1",
+ ModelClrType = typeof(IPublishedContent),
+ });
+ type1.Properties.Add(new PropertyModel
+ {
+ Alias = "prop2",
+ ClrName = "Prop2",
+ ModelClrType = typeof(System.Text.StringBuilder),
+ });
+ type1.Properties.Add(new PropertyModel
+ {
+ Alias = "prop3",
+ ClrName = "Prop3",
+ ModelClrType = typeof(global::Umbraco.Core.IO.FileSecurityException),
+ });
+ var types = new[] { type1 };
+
+ var code = new Dictionary
+ {
+ };
+
+ var builder = new TextBuilder(Mock.Of(), types);
+ builder.ModelsNamespace = "Umbraco.ModelsBuilder.Models"; // forces conflict with Umbraco.ModelsBuilder.Umbraco
+ var btypes = builder.TypeModels;
+
+ var sb = new StringBuilder();
+ foreach (var model in builder.GetModelsToGenerate())
+ builder.Generate(sb, model);
+ var gen = sb.ToString();
+
+ Console.WriteLine(gen);
+
+ Assert.IsTrue(gen.Contains(" global::Umbraco.Core.Models.PublishedContent.IPublishedContent Prop1"));
+ Assert.IsTrue(gen.Contains(" global::System.Text.StringBuilder Prop2"));
+ Assert.IsTrue(gen.Contains(" global::Umbraco.Core.IO.FileSecurityException Prop3"));
+ }
+
+ [TestCase("int", typeof(int))]
+ [TestCase("global::System.Collections.Generic.IEnumerable", typeof(IEnumerable))]
+ [TestCase("global::Umbraco.ModelsBuilder.Tests.BuilderTestsClass1", typeof(BuilderTestsClass1))]
+ [TestCase("global::Umbraco.ModelsBuilder.Tests.BuilderTests.Class1", typeof(Class1))]
+ public void WriteClrType(string expected, Type input)
+ {
+ // note - these assertions differ from the original tests in MB because in the embedded version, the result of Builder.IsAmbiguousSymbol is always true
+ // which means global:: syntax will be applied to most things
+
+ var builder = new TextBuilder();
+ builder.ModelsNamespaceForTests = "ModelsNamespace";
+ var sb = new StringBuilder();
+ builder.WriteClrType(sb, input);
+ Assert.AreEqual(expected, sb.ToString());
+ }
+
+ [TestCase("int", typeof(int))]
+ [TestCase("global::System.Collections.Generic.IEnumerable", typeof(IEnumerable))]
+ [TestCase("global::Umbraco.ModelsBuilder.Tests.BuilderTestsClass1", typeof(BuilderTestsClass1))]
+ [TestCase("global::Umbraco.ModelsBuilder.Tests.BuilderTests.Class1", typeof(Class1))]
+ public void WriteClrTypeUsing(string expected, Type input)
+ {
+ // note - these assertions differ from the original tests in MB because in the embedded version, the result of Builder.IsAmbiguousSymbol is always true
+ // which means global:: syntax will be applied to most things
+
+ var builder = new TextBuilder();
+ builder.Using.Add("Umbraco.ModelsBuilder.Tests");
+ builder.ModelsNamespaceForTests = "ModelsNamespace";
+ var sb = new StringBuilder();
+ builder.WriteClrType(sb, input);
+ Assert.AreEqual(expected, sb.ToString());
+ }
+
+ [Test]
+ public void WriteClrType_WithUsing()
+ {
+ var builder = new TextBuilder();
+ builder.Using.Add("System.Text");
+ builder.ModelsNamespaceForTests = "Umbraco.ModelsBuilder.Tests.Models";
+ var sb = new StringBuilder();
+ builder.WriteClrType(sb, typeof(StringBuilder));
+
+ // note - these assertions differ from the original tests in MB because in the embedded version, the result of Builder.IsAmbiguousSymbol is always true
+ // which means global:: syntax will be applied to most things
+
+ Assert.AreEqual("global::System.Text.StringBuilder", sb.ToString());
+ }
+
+ [Test]
+ public void WriteClrTypeAnother_WithoutUsing()
+ {
+ var builder = new TextBuilder();
+ builder.ModelsNamespaceForTests = "Umbraco.ModelsBuilder.Tests.Models";
+ var sb = new StringBuilder();
+ builder.WriteClrType(sb, typeof(StringBuilder));
+ Assert.AreEqual("global::System.Text.StringBuilder", sb.ToString());
+ }
+
+ [Test]
+ public void WriteClrType_Ambiguous1()
+ {
+ var builder = new TextBuilder();
+ builder.Using.Add("System.Text");
+ builder.Using.Add("Umbraco.ModelsBuilder.Tests");
+ builder.ModelsNamespaceForTests = "SomeRandomNamespace";
+ var sb = new StringBuilder();
+ builder.WriteClrType(sb, typeof(System.Text.ASCIIEncoding));
+
+ // note - these assertions differ from the original tests in MB because in the embedded version, the result of Builder.IsAmbiguousSymbol is always true
+ // which means global:: syntax will be applied to most things
+
+ Assert.AreEqual("global::System.Text.ASCIIEncoding", sb.ToString());
+ }
+
+ [Test]
+ public void WriteClrType_Ambiguous()
+ {
+ var builder = new TextBuilder();
+ builder.Using.Add("System.Text");
+ builder.Using.Add("Umbraco.ModelsBuilder.Tests");
+ builder.ModelsNamespaceForTests = "SomeBorkedNamespace";
+ var sb = new StringBuilder();
+ builder.WriteClrType(sb, typeof(System.Text.ASCIIEncoding));
+
+ // note - these assertions differ from the original tests in MB because in the embedded version, the result of Builder.IsAmbiguousSymbol is always true
+ // which means global:: syntax will be applied to most things
+
+ Assert.AreEqual("global::System.Text.ASCIIEncoding", sb.ToString());
+ }
+
+ [Test]
+ public void WriteClrType_Ambiguous2()
+ {
+ var builder = new TextBuilder();
+ builder.Using.Add("System.Text");
+ builder.Using.Add("Umbraco.ModelsBuilder.Tests");
+ builder.ModelsNamespaceForTests = "SomeRandomNamespace";
+ var sb = new StringBuilder();
+ builder.WriteClrType(sb, typeof(ASCIIEncoding));
+
+ // note - these assertions differ from the original tests in MB because in the embedded version, the result of Builder.IsAmbiguousSymbol is always true
+ // which means global:: syntax will be applied to most things
+
+ Assert.AreEqual("global::Umbraco.ModelsBuilder.Tests.ASCIIEncoding", sb.ToString());
+ }
+
+ [Test]
+ public void WriteClrType_AmbiguousNot()
+ {
+ var builder = new TextBuilder();
+ builder.Using.Add("System.Text");
+ builder.Using.Add("Umbraco.ModelsBuilder.Tests");
+ builder.ModelsNamespaceForTests = "Umbraco.ModelsBuilder.Tests.Models";
+ var sb = new StringBuilder();
+ builder.WriteClrType(sb, typeof(ASCIIEncoding));
+
+ // note - these assertions differ from the original tests in MB because in the embedded version, the result of Builder.IsAmbiguousSymbol is always true
+ // which means global:: syntax will be applied to most things
+
+ Assert.AreEqual("global::Umbraco.ModelsBuilder.Tests.ASCIIEncoding", sb.ToString());
+ }
+
+ [Test]
+ public void WriteClrType_AmbiguousWithNested()
+ {
+ var builder = new TextBuilder();
+ builder.Using.Add("System.Text");
+ builder.Using.Add("Umbraco.ModelsBuilder.Tests");
+ builder.ModelsNamespaceForTests = "SomeRandomNamespace";
+ var sb = new StringBuilder();
+ builder.WriteClrType(sb, typeof(ASCIIEncoding.Nested));
+
+ // note - these assertions differ from the original tests in MB because in the embedded version, the result of Builder.IsAmbiguousSymbol is always true
+ // which means global:: syntax will be applied to most things
+
+ Assert.AreEqual("global::Umbraco.ModelsBuilder.Tests.ASCIIEncoding.Nested", sb.ToString());
+ }
+
+ public class Class1 { }
+ }
+
+ // make it public to be ambiguous (see above)
+ public class ASCIIEncoding
+ {
+ // can we handle nested types?
+ public class Nested { }
+ }
+
+ class BuilderTestsClass1 {}
+}
+
+namespace SomeBorkedNamespace
+{
+ public class System { }
+}
diff --git a/src/Umbraco.Tests/ModelsBuilder/ConfigTests.cs b/src/Umbraco.Tests/ModelsBuilder/ConfigTests.cs
new file mode 100644
index 0000000000..58215707f7
--- /dev/null
+++ b/src/Umbraco.Tests/ModelsBuilder/ConfigTests.cs
@@ -0,0 +1,49 @@
+using System.Configuration;
+using NUnit.Framework;
+using Umbraco.ModelsBuilder.Configuration;
+
+namespace Umbraco.ModelsBuilder.Tests
+{
+ [TestFixture]
+ public class ModelsBuilderConfigTests
+ {
+ [Test]
+ public void Test1()
+ {
+ var config = new ModelsBuilderConfig(modelsNamespace: "test1");
+ Assert.AreEqual("test1", config.ModelsNamespace);
+ }
+
+ [Test]
+ public void Test2()
+ {
+ var config = new ModelsBuilderConfig(modelsNamespace: "test2");
+ Assert.AreEqual("test2", config.ModelsNamespace);
+ }
+
+ [Test]
+ public void DefaultModelsNamespace()
+ {
+ var config = new ModelsBuilderConfig();
+ Assert.AreEqual(ModelsBuilderConfig.DefaultModelsNamespace, config.ModelsNamespace);
+ }
+
+ [TestCase("c:/path/to/root", "~/dir/models", false, "c:\\path\\to\\root\\dir\\models")]
+ [TestCase("c:/path/to/root", "~/../../dir/models", true, "c:\\path\\dir\\models")]
+ [TestCase("c:/path/to/root", "c:/another/path/to/elsewhere", true, "c:\\another\\path\\to\\elsewhere")]
+ public void GetModelsDirectoryTests(string root, string config, bool acceptUnsafe, string expected)
+ {
+ Assert.AreEqual(expected, ModelsBuilderConfig.GetModelsDirectory(root, config, acceptUnsafe));
+ }
+
+ [TestCase("c:/path/to/root", "~/../../dir/models", false)]
+ [TestCase("c:/path/to/root", "c:/another/path/to/elsewhere", false)]
+ public void GetModelsDirectoryThrowsTests(string root, string config, bool acceptUnsafe)
+ {
+ Assert.Throws(() =>
+ {
+ var modelsDirectory = ModelsBuilderConfig.GetModelsDirectory(root, config, acceptUnsafe);
+ });
+ }
+ }
+}
diff --git a/src/Umbraco.Tests/ModelsBuilder/StringExtensions.cs b/src/Umbraco.Tests/ModelsBuilder/StringExtensions.cs
new file mode 100644
index 0000000000..13a256aa14
--- /dev/null
+++ b/src/Umbraco.Tests/ModelsBuilder/StringExtensions.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Umbraco.ModelsBuilder.Tests
+{
+ public static class StringExtensions
+ {
+ public static string ClearLf(this string s)
+ {
+ return s.Replace("\r", "");
+ }
+ }
+}
diff --git a/src/Umbraco.Tests/ModelsBuilder/UmbracoApplicationTests.cs b/src/Umbraco.Tests/ModelsBuilder/UmbracoApplicationTests.cs
new file mode 100644
index 0000000000..12fa777e69
--- /dev/null
+++ b/src/Umbraco.Tests/ModelsBuilder/UmbracoApplicationTests.cs
@@ -0,0 +1,63 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using NUnit.Framework;
+using Umbraco.ModelsBuilder.Building;
+using Umbraco.ModelsBuilder.Umbraco;
+
+namespace Umbraco.ModelsBuilder.Tests
+{
+ [TestFixture]
+ public class UmbracoApplicationTests
+ {
+ //[Test]
+ //public void Test()
+ //{
+ // // start and terminate
+ // using (var app = Application.GetApplication(TestOptions.ConnectionString, TestOptions.DatabaseProvider))
+ // { }
+
+ // // start and terminate
+ // using (var app = Application.GetApplication(TestOptions.ConnectionString, TestOptions.DatabaseProvider))
+ // { }
+
+ // // start, use and terminate
+ // using (var app = Application.GetApplication(TestOptions.ConnectionString, TestOptions.DatabaseProvider))
+ // {
+ // var types = app.GetContentTypes();
+ // }
+ //}
+
+ [Test]
+ public void ThrowsOnDuplicateAliases()
+ {
+ var typeModels = new List
+ {
+ new TypeModel { ItemType = TypeModel.ItemTypes.Content, Alias = "content1" },
+ new TypeModel { ItemType = TypeModel.ItemTypes.Content, Alias = "content2" },
+ new TypeModel { ItemType = TypeModel.ItemTypes.Media, Alias = "media1" },
+ new TypeModel { ItemType = TypeModel.ItemTypes.Media, Alias = "media2" },
+ new TypeModel { ItemType = TypeModel.ItemTypes.Member, Alias = "member1" },
+ new TypeModel { ItemType = TypeModel.ItemTypes.Member, Alias = "member2" },
+ };
+
+ Assert.AreEqual(6, UmbracoServices.EnsureDistinctAliases(typeModels).Count);
+
+ typeModels.Add(new TypeModel { ItemType = TypeModel.ItemTypes.Media, Alias = "content1" });
+
+ try
+ {
+ UmbracoServices.EnsureDistinctAliases(typeModels);
+ }
+ catch (NotSupportedException e)
+ {
+ Console.WriteLine(e.Message);
+ return;
+ }
+
+ Assert.Fail("Expected NotSupportedException.");
+ }
+ }
+}
diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj
index 39826fcc38..ebf081160b 100644
--- a/src/Umbraco.Tests/Umbraco.Tests.csproj
+++ b/src/Umbraco.Tests/Umbraco.Tests.csproj
@@ -134,6 +134,10 @@
+
+
+
+
@@ -549,6 +553,10 @@
{31785BC3-256C-4613-B2F5-A1B0BDDED8C1}
Umbraco.Core
+
+ {52ac0ba8-a60e-4e36-897b-e8b97a54ed1c}
+ Umbraco.ModelsBuilder
+
{651E1350-91B6-44B7-BD60-7207006D7003}
Umbraco.Web