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(""); + + 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