Moves classes/namespaces, reduces statics, reduces usages of "Current", imports some unit tests from orig MB.

This commit is contained in:
Shannon
2019-10-28 18:02:52 +11:00
parent c5bee77537
commit 43cd1268ca
30 changed files with 923 additions and 288 deletions

View File

@@ -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")]

View File

@@ -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("<br />&nbsp;<br />");
sb.Append("ModelsBuilder is enabled, with the following configuration:");
sb.Append("<ul>");
sb.Append("<li>The <strong>models factory</strong> is ");
sb.Append(_config.EnableFactory || _config.ModelsMode == ModelsMode.PureLive
? "enabled"
: "not enabled. Umbraco will <em>not</em> use models");
sb.Append(".</li>");
sb.Append(_config.ModelsMode != ModelsMode.Nothing
? $"<li><strong>{_config.ModelsMode} models</strong> are enabled.</li>"
: "<li>No models mode is specified: models will <em>not</em> be generated.</li>");
sb.Append($"<li>Models namespace is {_config.ModelsNamespace}.</li>");
sb.Append("<li>Tracking of <strong>out-of-date models</strong> is ");
sb.Append(_config.FlagOutOfDateModels ? "enabled" : "not enabled");
sb.Append(".</li>");
sb.Append("</ul>");
return sb.ToString();
}
}
}

View File

@@ -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
{
/// <summary>
/// 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
{

View File

@@ -23,12 +23,11 @@ namespace Umbraco.ModelsBuilder.Building
/// </summary>
internal abstract class Builder
{
private readonly IList<TypeModel> _typeModels;
protected Dictionary<string, string> ModelsMap { get; } = new Dictionary<string, string>();
private static Config Config => Current.Configs.ModelsBuilder();
// the list of assemblies that will be 'using' by default
protected readonly IList<string> TypesUsing = new List<string>
{
@@ -69,27 +68,20 @@ namespace Umbraco.ModelsBuilder.Building
/// <remarks>Includes those that are ignored.</remarks>
internal IList<TypeModel> TypeModels => _typeModels;
/// <summary>
/// Initializes a new instance of the <see cref="Builder"/> class with a list of models to generate
/// and the result of code parsing.
/// </summary>
/// <param name="typeModels">The list of models to generate.</param>
protected Builder(IList<TypeModel> typeModels)
: this(typeModels, null)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="Builder"/> class with a list of models to generate,
/// the result of code parsing, and a models namespace.
/// </summary>
/// <param name="typeModels">The list of models to generate.</param>
/// <param name="modelsNamespace">The models namespace.</param>
protected Builder(IList<TypeModel> typeModels, string modelsNamespace)
protected Builder(IModelsBuilderConfig config, IList<TypeModel> 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; }
/// <summary>
/// Prepares generation by processing the result of code parsing.
/// </summary>
@@ -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)

View File

@@ -20,26 +20,14 @@ namespace Umbraco.ModelsBuilder.Building
/// and the result of code parsing.
/// </summary>
/// <param name="typeModels">The list of models to generate.</param>
public TextBuilder(IList<TypeModel> typeModels)
: base(typeModels)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="TextBuilder"/> class with a list of models to generate,
/// the result of code parsing, and a models namespace.
/// </summary>
/// <param name="typeModels">The list of models to generate.</param>
/// <param name="modelsNamespace">The models namespace.</param>
public TextBuilder(IList<TypeModel> typeModels, string modelsNamespace)
: base(typeModels, modelsNamespace)
public TextBuilder(IModelsBuilderConfig config, IList<TypeModel> typeModels)
: base(config, typeModels)
{ }
// internal for unit tests only
internal TextBuilder()
{ }
private static Config Config => Current.Configs.ModelsBuilder();
/// <summary>
/// Outputs a generated model to a string builder.
/// </summary>
@@ -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");

View File

@@ -77,10 +77,10 @@ namespace Umbraco.ModelsBuilder.Building
/// </summary>
public readonly List<TypeModel> ImplementingInterfaces = new List<TypeModel>();
/// <summary>
/// Gets the list of existing static mixin method candidates.
/// </summary>
public readonly List<string> StaticMixinMethods = new List<string>();
///// <summary>
///// Gets the list of existing static mixin method candidates.
///// </summary>
//public readonly List<string> StaticMixinMethods = new List<string>(); //TODO: Do we need this? it isn't used
/// <summary>
/// Gets a value indicating whether this model has a base class.

View File

@@ -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))
@@ -130,7 +131,6 @@ namespace Umbraco.ModelsBuilder.Umbraco
template.Content = markup;
}
}
}
private void ContentModelBinder_ModelBindingException(object sender, ContentModelBinder.ModelBindingArgs args)
{
@@ -158,7 +158,6 @@ namespace Umbraco.ModelsBuilder.Umbraco
var pureModel = modelAttr.PureLive;
if (sourceAttr.PureLive || modelAttr.PureLive)
{
if (pureSource == false || pureModel == false)
{
// only one is pure - report, but better not restart (loops?)
@@ -182,4 +181,3 @@ namespace Umbraco.ModelsBuilder.Umbraco
}
}
}
}

View File

@@ -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<UmbracoServices>(Lifetime.Singleton);
composition.Configs.Add(() => new Config());
composition.Configs.Add<IModelsBuilderConfig>(() => new ModelsBuilderConfig());
composition.RegisterUnique<ModelsGenerator>();
composition.RegisterUnique<LiveModelsProvider>();
if (composition.Configs.ModelsBuilder().ModelsMode == ModelsMode.PureLive)
ComposeForLiveModels(composition);

View File

@@ -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()
{

View File

@@ -14,7 +14,7 @@ namespace Umbraco.ModelsBuilder
/// <remarks>Getting the models builder configuration freezes its state,
/// and any attempt at modifying the configuration using the Setup method
/// will be ignored.</remarks>
public static Config ModelsBuilder(this Configs configs)
=> configs.GetConfig<Config>();
public static ModelsBuilderConfig ModelsBuilder(this Configs configs)
=> configs.GetConfig<ModelsBuilderConfig>();
}
}

View File

@@ -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; }
}
}

View File

@@ -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
{
/// <summary>
/// Represents the models builder configuration.
/// </summary>
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";
/// <summary>
/// Initializes a new instance of the <see cref="Config"/> class.
/// Initializes a new instance of the <see cref="ModelsBuilderConfig"/> class.
/// </summary>
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
}
/// <summary>
/// Initializes a new instance of the <see cref="Config"/> class.
/// Initializes a new instance of the <see cref="ModelsBuilderConfig"/> class.
/// </summary>
public Config(
public ModelsBuilderConfig(
ModelsMode modelsMode = ModelsMode.Nothing,
string modelsNamespace = null,
bool enableFactory = true,

View File

@@ -8,7 +8,7 @@
/// <summary>
/// Do not generate models.
/// </summary>
Nothing = 0, // default value
Nothing = 0, // default value //TODO: This doesn't make sense since we cannot actualy disable MB since Umbraco would die
/// <summary>
/// Generate models in memory.

View File

@@ -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("<br />&nbsp;<br />");
sb.Append("ModelsBuilder is enabled, with the following configuration:");
sb.Append("<ul>");
sb.Append("<li>The <strong>models factory</strong> is ");
sb.Append(config.EnableFactory || config.ModelsMode == ModelsMode.PureLive
? "enabled"
: "not enabled. Umbraco will <em>not</em> use models");
sb.Append(".</li>");
sb.Append(config.ModelsMode != ModelsMode.Nothing
? $"<li><strong>{config.ModelsMode} models</strong> are enabled.</li>"
: "<li>No models mode is specified: models will <em>not</em> be generated.</li>");
sb.Append($"<li>Models namespace is {config.ModelsNamespace}.</li>");
sb.Append("<li>Tracking of <strong>out-of-date models</strong> is ");
sb.Append(config.FlagOutOfDateModels ? "enabled" : "not enabled");
sb.Append(".</li>");
sb.Append("</ul>");
return sb.ToString();
}
}
}

View File

@@ -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()

View File

@@ -56,10 +56,11 @@
<Compile Include="Building\TextHeaderWriter.cs" />
<Compile Include="Building\TypeModel.cs" />
<Compile Include="ConfigsExtensions.cs" />
<Compile Include="Configuration\Config.cs" />
<Compile Include="Configuration\IModelsBuilderConfig.cs" />
<Compile Include="Configuration\ModelsBuilderConfig.cs" />
<Compile Include="Configuration\ModelsMode.cs" />
<Compile Include="Configuration\ModelsModeExtensions.cs" />
<Compile Include="Dashboard\BuilderDashboardHelper.cs" />
<Compile Include="BackOffice\DashboardReport.cs" />
<Compile Include="ImplementPropertyTypeAttribute.cs" />
<Compile Include="ModelsBuilderAssemblyAttribute.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
@@ -67,13 +68,15 @@
<Compile Include="PureLiveAssemblyAttribute.cs" />
<Compile Include="ReferencedAssemblies.cs" />
<Compile Include="TypeExtensions.cs" />
<Compile Include="Umbraco\HashCombiner.cs" />
<Compile Include="Umbraco\HashHelper.cs" />
<Compile Include="HashCombiner.cs" />
<Compile Include="Umbraco\LiveModelsProvider.cs" />
<Compile Include="Umbraco\ModelsBuilderBackOfficeController.cs" />
<Compile Include="Umbraco\ModelsBuilderComponent.cs" />
<Compile Include="Umbraco\ModelsBuilderComposer.cs" />
<Compile Include="Umbraco\ModelsBuilderInitializer.cs" />
<Compile Include="Umbraco\LiveModelsProviderModule.cs" />
<Compile Include="Umbraco\ModelsGenerator.cs" />
<Compile Include="BackOffice\ModelsBuilderBackOfficeController.cs" />
<Compile Include="Compose\ModelsBuilderComponent.cs" />
<Compile Include="Compose\ModelsBuilderComposer.cs" />
<Compile Include="Umbraco\ModelsBuilderHasher.cs" />
<Compile Include="Compose\ModelsBuilderInitializer.cs" />
<Compile Include="Umbraco\ModelsGenerationError.cs" />
<Compile Include="Umbraco\OutOfDateModelsStatus.cs" />
<Compile Include="Umbraco\PublishedModelUtility.cs" />
@@ -92,10 +95,6 @@
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Umbraco.Abstractions\Umbraco.Abstractions.csproj">
<Project>{29aa69d9-b597-4395-8d42-43b1263c240a}</Project>
<Name>Umbraco.Abstractions</Name>
</ProjectReference>
<ProjectReference Include="..\Umbraco.Core\Umbraco.Core.csproj">
<Project>{31785BC3-256C-4613-B2F5-A1B0BDDED8C1}</Project>
<Name>Umbraco.Core</Name>
@@ -110,5 +109,8 @@
<Version>5.2.7</Version>
</PackageReference>
</ItemGroup>
<ItemGroup>
<Folder Include="Dashboard\" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View File

@@ -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<LiveModelsProvider>("Requested to generate models.");
_logger.Debug<LiveModelsProvider>("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<LiveModelsProvider>("Generate models...");
_logger.Debug<LiveModelsProvider>("Generate models...");
const int timeout = 2*60*1000; // 2 mins
_mutex.WaitOne(timeout); // wait until it is safe, and acquire
Current.Logger.Info<LiveModelsProvider>("Generate models now.");
_logger.Info<LiveModelsProvider>("Generate models now.");
GenerateModels();
ModelsGenerationError.Clear();
Current.Logger.Info<LiveModelsProvider>("Generated.");
_modelGenerator.ClearErrors();
_logger.Info<LiveModelsProvider>("Generated.");
}
catch (TimeoutException)
{
Current.Logger.Warn<LiveModelsProvider>("Timeout, models were NOT generated.");
_logger.Warn<LiveModelsProvider>("Timeout, models were NOT generated.");
}
catch (Exception e)
{
ModelsGenerationError.Report("Failed to build Live models.", e);
Current.Logger.Error<LiveModelsProvider>("Failed to generate models.", e);
_modelGenerator.ReportError("Failed to build Live models.", e);
_logger.Error<LiveModelsProvider>("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);
}
_modelGenerator.GenerateModels();
}
// 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;
}
public void Dispose()
{
// nothing
}
public static void Install()
{
// always - don't read config in PreApplicationStartMethod
HttpApplication.RegisterModule(typeof(LiveModelsProviderModule));
}
}
}

View File

@@ -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<LiveModelsProvider>();
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));
}
}
}

View File

@@ -4,7 +4,7 @@ using Umbraco.ModelsBuilder.Building;
namespace Umbraco.ModelsBuilder.Umbraco
{
class HashHelper
internal class ModelsBuilderHasher
{
public static string Hash(IEnumerable<TypeModel> typeModels)
{

View File

@@ -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;

View File

@@ -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();
}
}

View File

@@ -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()
{

View File

@@ -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> umbracoServices, IProfilingLogger logger, Config config)
public PureLiveModelFactory(Lazy<UmbracoServices> 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<PublishedContentModel>() || x.Inherits<PublishedElementModel>());
_infos = RegisterModels(types);
ModelsGenerationError.Clear();
_modelGenerator.ClearErrors();
}
catch (Exception e)
{
@@ -300,7 +302,7 @@ namespace Umbraco.ModelsBuilder.Umbraco
{
_logger.Error<PureLiveModelFactory>("Failed to build models.", e);
_logger.Warn<PureLiveModelFactory>("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());

View File

@@ -28,8 +28,6 @@ namespace Umbraco.ModelsBuilder.Umbraco
_publishedContentTypeFactory = publishedContentTypeFactory;
}
private static Config Config => Current.Configs.ModelsBuilder();
#region Services
public IList<TypeModel> GetAllTypes()

View File

@@ -35,7 +35,7 @@ namespace Umbraco.ModelsBuilder.Validation
where TModel: ContentTypeSave<TProperty>
where TProperty: PropertyTypeBasic
{
private static Config Config => Current.Configs.ModelsBuilder();
private static ModelsBuilderConfig Config => Current.Configs.ModelsBuilder();
protected override IEnumerable<ValidationResult> Validate(TModel model)
{

View File

@@ -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<string, string>
{
};
var builder = new TextBuilder(Mock.Of<IModelsBuilderConfig>(), 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 = @"//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Umbraco.ModelsBuilder v" + version + @"
//
// Changes to this file will be lost if the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
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<TValue>(Expression<Func<Type1, TValue>> 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<string>(""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<string, string>
{
{ "code", @"
namespace Umbraco.Web.PublishedModels
{
public partial class Foo
{
}
}
" }
};
var builder = new TextBuilder(Mock.Of<IModelsBuilderConfig>(), 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 = @"//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Umbraco.ModelsBuilder v" + version + @"
//
// Changes to this file will be lost if the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
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<TValue>(Expression<Func<Type1, TValue>> 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<global::Foo> Foo => this.Value<global::System.Collections.Generic.IEnumerable<global::Foo>>(""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<string, string>
{
};
var builder = new TextBuilder(Mock.Of<IModelsBuilderConfig>(), 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<int>", typeof(IEnumerable<int>))]
[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<int>", typeof(IEnumerable<int>))]
[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 { }
}

View File

@@ -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<ConfigurationErrorsException>(() =>
{
var modelsDirectory = ModelsBuilderConfig.GetModelsDirectory(root, config, acceptUnsafe);
});
}
}
}

View File

@@ -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", "");
}
}
}

View File

@@ -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<TypeModel>
{
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.");
}
}
}

View File

@@ -134,6 +134,10 @@
<Compile Include="Mapping\MappingTests.cs" />
<Compile Include="Migrations\MigrationPlanTests.cs" />
<Compile Include="Migrations\MigrationTests.cs" />
<Compile Include="ModelsBuilder\BuilderTests.cs" />
<Compile Include="ModelsBuilder\ConfigTests.cs" />
<Compile Include="ModelsBuilder\StringExtensions.cs" />
<Compile Include="ModelsBuilder\UmbracoApplicationTests.cs" />
<Compile Include="Models\ContentScheduleTests.cs" />
<Compile Include="Models\CultureImpactTests.cs" />
<Compile Include="Models\PathValidationTests.cs" />
@@ -549,6 +553,10 @@
<Project>{31785BC3-256C-4613-B2F5-A1B0BDDED8C1}</Project>
<Name>Umbraco.Core</Name>
</ProjectReference>
<ProjectReference Include="..\Umbraco.ModelsBuilder\Umbraco.ModelsBuilder.csproj">
<Project>{52ac0ba8-a60e-4e36-897b-e8b97a54ed1c}</Project>
<Name>Umbraco.ModelsBuilder</Name>
</ProjectReference>
<ProjectReference Include="..\Umbraco.Web\Umbraco.Web.csproj">
<Project>{651E1350-91B6-44B7-BD60-7207006D7003}</Project>
<Name>Umbraco.Web</Name>