diff --git a/build/NuSpecs/UmbracoCms.Web.nuspec b/build/NuSpecs/UmbracoCms.Web.nuspec
index c8374bc2f7..658d2f0672 100644
--- a/build/NuSpecs/UmbracoCms.Web.nuspec
+++ b/build/NuSpecs/UmbracoCms.Web.nuspec
@@ -52,14 +52,17 @@
+
+
+
diff --git a/build/NuSpecs/UmbracoCms.nuspec b/build/NuSpecs/UmbracoCms.nuspec
index 97e9ef3df2..a6b06d9964 100644
--- a/build/NuSpecs/UmbracoCms.nuspec
+++ b/build/NuSpecs/UmbracoCms.nuspec
@@ -26,7 +26,6 @@
not want this to happen as the alpha of the next major is, really, the next major already.
-->
-
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs
index 6385482686..e2c3d8c9b5 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs
@@ -16,7 +16,6 @@ using Umbraco.Core.Persistence.Factories;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Scoping;
using Umbraco.Core.Services;
-using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics;
namespace Umbraco.Core.Persistence.Repositories.Implement
{
@@ -506,7 +505,7 @@ AND umbracoNode.id <> @id",
///
/// Corrects the property type variations for the given entity
/// to make sure the property type variation is compatible with the
- /// variation set on the entity itself.
+ /// variation set on the entity itself.
///
/// Entity to correct properties for
private void CorrectPropertyTypeVariations(IContentTypeComposition entity)
@@ -754,7 +753,7 @@ AND umbracoNode.id <> @id",
//we don't need to move the names! this is because we always keep the invariant names with the name of the default language.
//however, if we were to move names, we could do this: BUT this doesn't work with SQLCE, for that we'd have to update row by row :(
- // if we want these SQL statements back, look into GIT history
+ // if we want these SQL statements back, look into GIT history
}
}
@@ -1033,7 +1032,7 @@ AND umbracoNode.id <> @id",
//keep track of this node/lang to mark or unmark a culture as edited
var editedLanguageVersions = new Dictionary<(int nodeId, int? langId), bool>();
- //keep track of which node to mark or unmark as edited
+ //keep track of which node to mark or unmark as edited
var editedDocument = new Dictionary();
var nodeId = -1;
var propertyTypeId = -1;
diff --git a/src/Umbraco.Core/Properties/AssemblyInfo.cs b/src/Umbraco.Core/Properties/AssemblyInfo.cs
index afd602cfc9..87e0732d47 100644
--- a/src/Umbraco.Core/Properties/AssemblyInfo.cs
+++ b/src/Umbraco.Core/Properties/AssemblyInfo.cs
@@ -14,6 +14,7 @@ using System.Runtime.InteropServices;
[assembly: InternalsVisibleTo("Umbraco.Web")]
[assembly: InternalsVisibleTo("Umbraco.Web.UI")]
[assembly: InternalsVisibleTo("Umbraco.Examine")]
+[assembly: InternalsVisibleTo("Umbraco.ModelsBuilder.Embedded")]
[assembly: InternalsVisibleTo("Umbraco.Tests")]
[assembly: InternalsVisibleTo("Umbraco.Tests.Benchmarks")]
diff --git a/src/Umbraco.ModelsBuilder.Embedded/ApiVersion.cs b/src/Umbraco.ModelsBuilder.Embedded/ApiVersion.cs
new file mode 100644
index 0000000000..22347edd60
--- /dev/null
+++ b/src/Umbraco.ModelsBuilder.Embedded/ApiVersion.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Reflection;
+using Semver;
+
+namespace Umbraco.ModelsBuilder.Embedded
+{
+ ///
+ /// Manages API version handshake between client and server.
+ ///
+ public class ApiVersion
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The currently executing version.
+ ///
+ internal ApiVersion(SemVersion executingVersion)
+ {
+ Version = executingVersion ?? throw new ArgumentNullException(nameof(executingVersion));
+ }
+
+ private static SemVersion CurrentAssemblyVersion
+ => SemVersion.Parse(Assembly.GetExecutingAssembly().GetCustomAttribute().InformationalVersion);
+
+ ///
+ /// Gets the currently executing API version.
+ ///
+ public static ApiVersion Current { get; }
+ = new ApiVersion(CurrentAssemblyVersion);
+
+ ///
+ /// Gets the executing version of the API.
+ ///
+ public SemVersion Version { get; }
+ }
+}
diff --git a/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ContentTypeModelValidator.cs b/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ContentTypeModelValidator.cs
new file mode 100644
index 0000000000..1fdb64c62a
--- /dev/null
+++ b/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ContentTypeModelValidator.cs
@@ -0,0 +1,17 @@
+using Umbraco.ModelsBuilder.Embedded.Configuration;
+using Umbraco.Web.Models.ContentEditing;
+
+namespace Umbraco.ModelsBuilder.Embedded.BackOffice
+{
+ ///
+ /// Used to validate the aliases for the content type when MB is enabled to ensure that
+ /// no illegal aliases are used
+ ///
+ // ReSharper disable once UnusedMember.Global - This is typed scanned
+ public class ContentTypeModelValidator : ContentTypeModelValidatorBase
+ {
+ public ContentTypeModelValidator(IModelsBuilderConfig config) : base(config)
+ {
+ }
+ }
+}
diff --git a/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ContentTypeModelValidatorBase.cs b/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ContentTypeModelValidatorBase.cs
new file mode 100644
index 0000000000..15ca2cca24
--- /dev/null
+++ b/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ContentTypeModelValidatorBase.cs
@@ -0,0 +1,71 @@
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using Umbraco.Core;
+using Umbraco.Core.Models.PublishedContent;
+using Umbraco.ModelsBuilder.Embedded.Configuration;
+using Umbraco.Web.Editors;
+using Umbraco.Web.Models.ContentEditing;
+
+namespace Umbraco.ModelsBuilder.Embedded.BackOffice
+{
+ public abstract class ContentTypeModelValidatorBase : EditorValidator
+ where TModel : ContentTypeSave
+ where TProperty : PropertyTypeBasic
+ {
+ private readonly IModelsBuilderConfig _config;
+
+ public ContentTypeModelValidatorBase(IModelsBuilderConfig config)
+ {
+ _config = config;
+ }
+
+ protected override IEnumerable Validate(TModel model)
+ {
+ //don't do anything if we're not enabled
+ if (!_config.Enable) yield break;
+
+ var properties = model.Groups.SelectMany(x => x.Properties)
+ .Where(x => x.Inherited == false)
+ .ToArray();
+
+ foreach (var prop in properties)
+ {
+ var propertyGroup = model.Groups.Single(x => x.Properties.Contains(prop));
+
+ if (model.Alias.ToLowerInvariant() == prop.Alias.ToLowerInvariant())
+ yield return new ValidationResult(string.Format("With Models Builder enabled, you can't have a property with a the alias \"{0}\" when the content type alias is also \"{0}\".", prop.Alias), new[]
+ {
+ $"Groups[{model.Groups.IndexOf(propertyGroup)}].Properties[{propertyGroup.Properties.IndexOf(prop)}].Alias"
+ });
+
+ //we need to return the field name with an index so it's wired up correctly
+ var groupIndex = model.Groups.IndexOf(propertyGroup);
+ var propertyIndex = propertyGroup.Properties.IndexOf(prop);
+
+ var validationResult = ValidateProperty(prop, groupIndex, propertyIndex);
+ if (validationResult != null)
+ yield return validationResult;
+ }
+ }
+
+ private ValidationResult ValidateProperty(PropertyTypeBasic property, int groupIndex, int propertyIndex)
+ {
+ //don't let them match any properties or methods in IPublishedContent
+ //TODO: There are probably more!
+ var reservedProperties = typeof(IPublishedContent).GetProperties().Select(x => x.Name).ToArray();
+ var reservedMethods = typeof(IPublishedContent).GetMethods().Select(x => x.Name).ToArray();
+
+ var alias = property.Alias;
+
+ if (reservedProperties.InvariantContains(alias) || reservedMethods.InvariantContains(alias))
+ return new ValidationResult(
+ $"The alias {alias} is a reserved term and cannot be used", new[]
+ {
+ $"Groups[{groupIndex}].Properties[{propertyIndex}].Alias"
+ });
+
+ return null;
+ }
+ }
+}
diff --git a/src/Umbraco.ModelsBuilder.Embedded/BackOffice/DashboardReport.cs b/src/Umbraco.ModelsBuilder.Embedded/BackOffice/DashboardReport.cs
new file mode 100644
index 0000000000..25ddc838e8
--- /dev/null
+++ b/src/Umbraco.ModelsBuilder.Embedded/BackOffice/DashboardReport.cs
@@ -0,0 +1,61 @@
+using System.Text;
+using Umbraco.ModelsBuilder.Embedded.Configuration;
+
+namespace Umbraco.ModelsBuilder.Embedded.BackOffice
+{
+ internal class DashboardReport
+ {
+ private readonly IModelsBuilderConfig _config;
+ private readonly OutOfDateModelsStatus _outOfDateModels;
+ private readonly ModelsGenerationError _mbErrors;
+
+ public DashboardReport(IModelsBuilderConfig config, OutOfDateModelsStatus outOfDateModels, ModelsGenerationError mbErrors)
+ {
+ _config = config;
+ _outOfDateModels = outOfDateModels;
+ _mbErrors = mbErrors;
+ }
+
+ public bool CanGenerate() => _config.ModelsMode.SupportsExplicitGeneration();
+
+ public bool AreModelsOutOfDate() => _outOfDateModels.IsOutOfDate;
+
+ public string LastError() => _mbErrors.GetLastError();
+
+ public string Text()
+ {
+ if (!_config.Enable)
+ return "Version: " + ApiVersion.Current.Version + "
ModelsBuilder is disabled (the .Enable key is missing, or its value is not 'true').";
+
+ var sb = new StringBuilder();
+
+ sb.Append("Version: ");
+ sb.Append(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(".
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.Embedded/BackOffice/MediaTypeModelValidator.cs b/src/Umbraco.ModelsBuilder.Embedded/BackOffice/MediaTypeModelValidator.cs
new file mode 100644
index 0000000000..9dc1ea6c20
--- /dev/null
+++ b/src/Umbraco.ModelsBuilder.Embedded/BackOffice/MediaTypeModelValidator.cs
@@ -0,0 +1,17 @@
+using Umbraco.ModelsBuilder.Embedded.Configuration;
+using Umbraco.Web.Models.ContentEditing;
+
+namespace Umbraco.ModelsBuilder.Embedded.BackOffice
+{
+ ///
+ /// Used to validate the aliases for the content type when MB is enabled to ensure that
+ /// no illegal aliases are used
+ ///
+ // ReSharper disable once UnusedMember.Global - This is typed scanned
+ public class MediaTypeModelValidator : ContentTypeModelValidatorBase
+ {
+ public MediaTypeModelValidator(IModelsBuilderConfig config) : base(config)
+ {
+ }
+ }
+}
diff --git a/src/Umbraco.ModelsBuilder.Embedded/BackOffice/MemberTypeModelValidator.cs b/src/Umbraco.ModelsBuilder.Embedded/BackOffice/MemberTypeModelValidator.cs
new file mode 100644
index 0000000000..8d0a98eeab
--- /dev/null
+++ b/src/Umbraco.ModelsBuilder.Embedded/BackOffice/MemberTypeModelValidator.cs
@@ -0,0 +1,17 @@
+using Umbraco.ModelsBuilder.Embedded.Configuration;
+using Umbraco.Web.Models.ContentEditing;
+
+namespace Umbraco.ModelsBuilder.Embedded.BackOffice
+{
+ ///
+ /// Used to validate the aliases for the content type when MB is enabled to ensure that
+ /// no illegal aliases are used
+ ///
+ // ReSharper disable once UnusedMember.Global - This is typed scanned
+ public class MemberTypeModelValidator : ContentTypeModelValidatorBase
+ {
+ public MemberTypeModelValidator(IModelsBuilderConfig config) : base(config)
+ {
+ }
+ }
+}
diff --git a/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ModelsBuilderDashboardController.cs b/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ModelsBuilderDashboardController.cs
new file mode 100644
index 0000000000..1d9de265e9
--- /dev/null
+++ b/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ModelsBuilderDashboardController.cs
@@ -0,0 +1,146 @@
+using System;
+using System.Net;
+using System.Net.Http;
+using System.Runtime.Serialization;
+using System.Web.Hosting;
+using Umbraco.Core.Exceptions;
+using Umbraco.ModelsBuilder.Embedded.Building;
+using Umbraco.ModelsBuilder.Embedded.Configuration;
+using Umbraco.Web.Editors;
+using Umbraco.Web.WebApi.Filters;
+
+namespace Umbraco.ModelsBuilder.Embedded.BackOffice
+{
+ ///
+ /// API controller for use in the Umbraco back office with Angular resources
+ ///
+ ///
+ /// We've created a different controller for the backoffice/angular specifically this is to ensure that the
+ /// correct CSRF security is adhered to for angular and it also ensures that this controller is not subseptipal to
+ /// global WebApi formatters being changed since this is always forced to only return Angular JSON Specific formats.
+ ///
+ [UmbracoApplicationAuthorize(Core.Constants.Applications.Settings)]
+ public class ModelsBuilderDashboardController : UmbracoAuthorizedJsonController
+ {
+ private readonly IModelsBuilderConfig _config;
+ private readonly ModelsGenerator _modelGenerator;
+ private readonly OutOfDateModelsStatus _outOfDateModels;
+ private readonly ModelsGenerationError _mbErrors;
+ private readonly DashboardReport _dashboardReport;
+
+ public ModelsBuilderDashboardController(IModelsBuilderConfig config, ModelsGenerator modelsGenerator, OutOfDateModelsStatus outOfDateModels, ModelsGenerationError mbErrors)
+ {
+ //_umbracoServices = umbracoServices;
+ _config = config;
+ _modelGenerator = modelsGenerator;
+ _outOfDateModels = outOfDateModels;
+ _mbErrors = mbErrors;
+ _dashboardReport = new DashboardReport(config, outOfDateModels, mbErrors);
+ }
+
+ // invoked by the dashboard
+ // requires that the user is logged into the backoffice and has access to the settings section
+ // beware! the name of the method appears in modelsbuilder.controller.js
+ [System.Web.Http.HttpPost] // use the http one, not mvc, with api controllers!
+ public HttpResponseMessage BuildModels()
+ {
+ try
+ {
+ var config = _config;
+
+ if (!config.ModelsMode.SupportsExplicitGeneration())
+ {
+ var result2 = new BuildResult { Success = false, Message = "Models generation is not enabled." };
+ return Request.CreateResponse(HttpStatusCode.OK, result2, Configuration.Formatters.JsonFormatter);
+ }
+
+ 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
+ _modelGenerator.GenerateModels();
+ _mbErrors.Clear();
+ }
+ catch (Exception e)
+ {
+ _mbErrors.Report("Failed to build models.", e);
+ }
+
+ return Request.CreateResponse(HttpStatusCode.OK, GetDashboardResult(), Configuration.Formatters.JsonFormatter);
+ }
+
+ // invoked by the back-office
+ // requires that the user is logged into the backoffice and has access to the settings section
+ [System.Web.Http.HttpGet] // use the http one, not mvc, with api controllers!
+ public HttpResponseMessage GetModelsOutOfDateStatus()
+ {
+ var status = _outOfDateModels.IsEnabled
+ ? _outOfDateModels.IsOutOfDate
+ ? new OutOfDateStatus { Status = OutOfDateType.OutOfDate }
+ : new OutOfDateStatus { Status = OutOfDateType.Current }
+ : new OutOfDateStatus { Status = OutOfDateType.Unknown };
+
+ return Request.CreateResponse(HttpStatusCode.OK, status, Configuration.Formatters.JsonFormatter);
+ }
+
+ // invoked by the back-office
+ // requires that the user is logged into the backoffice and has access to the settings section
+ // beware! the name of the method appears in modelsbuilder.controller.js
+ [System.Web.Http.HttpGet] // use the http one, not mvc, with api controllers!
+ public HttpResponseMessage GetDashboard()
+ {
+ return Request.CreateResponse(HttpStatusCode.OK, GetDashboardResult(), Configuration.Formatters.JsonFormatter);
+ }
+
+ private Dashboard GetDashboardResult()
+ {
+ return new Dashboard
+ {
+ Enable = _config.Enable,
+ Text = _dashboardReport.Text(),
+ CanGenerate = _dashboardReport.CanGenerate(),
+ OutOfDateModels = _dashboardReport.AreModelsOutOfDate(),
+ LastError = _dashboardReport.LastError(),
+ };
+ }
+
+ [DataContract]
+ internal class BuildResult
+ {
+ [DataMember(Name = "success")]
+ public bool Success;
+ [DataMember(Name = "message")]
+ public string Message;
+ }
+
+ [DataContract]
+ internal class Dashboard
+ {
+ [DataMember(Name = "enable")]
+ public bool Enable;
+ [DataMember(Name = "text")]
+ public string Text;
+ [DataMember(Name = "canGenerate")]
+ public bool CanGenerate;
+ [DataMember(Name = "outOfDateModels")]
+ public bool OutOfDateModels;
+ [DataMember(Name = "lastError")]
+ public string LastError;
+ }
+
+ internal enum OutOfDateType
+ {
+ OutOfDate,
+ Current,
+ Unknown = 100
+ }
+
+ [DataContract]
+ internal class OutOfDateStatus
+ {
+ [DataMember(Name = "status")]
+ public OutOfDateType Status { get; set; }
+ }
+ }
+}
diff --git a/src/Umbraco.ModelsBuilder/Building/Builder.cs b/src/Umbraco.ModelsBuilder.Embedded/Building/Builder.cs
similarity index 54%
rename from src/Umbraco.ModelsBuilder/Building/Builder.cs
rename to src/Umbraco.ModelsBuilder.Embedded/Building/Builder.cs
index acfa402355..ffd56d4312 100644
--- a/src/Umbraco.ModelsBuilder/Building/Builder.cs
+++ b/src/Umbraco.ModelsBuilder.Embedded/Building/Builder.cs
@@ -1,14 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using System.Text;
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.CSharp;
-using Microsoft.CodeAnalysis.CSharp.Syntax;
-using Umbraco.Core.Configuration;
-using Umbraco.ModelsBuilder.Configuration;
+using Umbraco.ModelsBuilder.Embedded.Configuration;
-namespace Umbraco.ModelsBuilder.Building
+namespace Umbraco.ModelsBuilder.Embedded.Building
{
// NOTE
// The idea was to have different types of builder, because I wanted to experiment with
@@ -22,10 +17,10 @@ namespace Umbraco.ModelsBuilder.Building
///
internal abstract class Builder
{
+
private readonly IList _typeModels;
protected Dictionary ModelsMap { get; } = new Dictionary();
- protected ParseResult ParseResult { get; }
// the list of assemblies that will be 'using' by default
protected readonly IList TypesUsing = new List
@@ -37,8 +32,7 @@ namespace Umbraco.ModelsBuilder.Building
"Umbraco.Core.Models",
"Umbraco.Core.Models.PublishedContent",
"Umbraco.Web",
- "Umbraco.ModelsBuilder",
- "Umbraco.ModelsBuilder.Umbraco",
+ "Umbraco.ModelsBuilder.Embedded"
};
///
@@ -55,10 +49,10 @@ namespace Umbraco.ModelsBuilder.Building
///
/// Gets the list of models to generate.
///
- /// The models to generate, ie those that are not ignored.
+ /// The models to generate
public IEnumerable GetModelsToGenerate()
{
- return _typeModels.Where(x => !x.IsContentIgnored);
+ return _typeModels;
}
///
@@ -67,90 +61,39 @@ 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.
- /// The result of code parsing.
- protected Builder(IList typeModels, ParseResult parseResult)
- {
- _typeModels = typeModels ?? throw new ArgumentNullException(nameof(typeModels));
- ParseResult = parseResult ?? throw new ArgumentNullException(nameof(parseResult));
-
- Prepare();
- }
-
///
/// 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 result of code parsing.
/// The models namespace.
- protected Builder(IList typeModels, ParseResult parseResult, string modelsNamespace)
- : this(typeModels, parseResult)
+ 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();
}
// for unit tests only
protected Builder()
{ }
+ protected IModelsBuilderConfig Config { get; }
+
///
/// Prepares generation by processing the result of code parsing.
///
- ///
- /// Preparation includes figuring out from the existing code which models or properties should
- /// be ignored or renamed, etc. -- anything that comes from the attributes in the existing code.
- ///
private void Prepare()
{
- var pureLive = UmbracoConfig.For.ModelsBuilder().ModelsMode == ModelsMode.PureLive;
+ TypeModel.MapModelTypes(_typeModels, ModelsNamespace);
- // mark IsContentIgnored models that we discovered should be ignored
- // then propagate / ignore children of ignored contents
- // ignore content = don't generate a class for it, don't generate children
- foreach (var typeModel in _typeModels.Where(x => ParseResult.IsIgnored(x.Alias)))
- typeModel.IsContentIgnored = true;
- foreach (var typeModel in _typeModels.Where(x => !x.IsContentIgnored && x.EnumerateBaseTypes().Any(xx => xx.IsContentIgnored)))
- typeModel.IsContentIgnored = true;
-
- // handle model renames
- foreach (var typeModel in _typeModels.Where(x => ParseResult.IsContentRenamed(x.Alias)))
- {
- typeModel.ClrName = ParseResult.ContentClrName(typeModel.Alias);
- typeModel.IsRenamed = true;
- ModelsMap[typeModel.Alias] = typeModel.ClrName;
- }
-
- // handle implement
- foreach (var typeModel in _typeModels.Where(x => ParseResult.HasContentImplement(x.Alias)))
- {
- typeModel.HasImplement = true;
- }
-
- // mark OmitBase models that we discovered already have a base class
- foreach (var typeModel in _typeModels.Where(x => ParseResult.HasContentBase(ParseResult.ContentClrName(x.Alias) ?? x.ClrName)))
- typeModel.HasBase = true;
-
- foreach (var typeModel in _typeModels)
- {
- // mark IsRemoved properties that we discovered should be ignored
- // ie is marked as ignored on type, or on any parent type
- var tm = typeModel;
- foreach (var property in typeModel.Properties
- .Where(property => tm.EnumerateBaseTypes(true).Any(x => ParseResult.IsPropertyIgnored(ParseResult.ContentClrName(x.Alias) ?? x.ClrName, property.Alias))))
- {
- property.IsIgnored = true;
- }
-
- // handle property renames
- foreach (var property in typeModel.Properties)
- property.ClrName = ParseResult.PropertyClrName(ParseResult.ContentClrName(typeModel.Alias) ?? typeModel.ClrName, property.Alias) ?? property.ClrName;
- }
+ var pureLive = Config.ModelsMode == ModelsMode.PureLive;
// for the first two of these two tests,
// always throw, even in purelive: cannot happen unless ppl start fidling with attributes to rename
@@ -158,22 +101,22 @@ namespace Umbraco.ModelsBuilder.Building
// for the last one, don't throw in purelive, see comment
// ensure we have no duplicates type names
- foreach (var xx in _typeModels.Where(x => !x.IsContentIgnored).GroupBy(x => x.ClrName).Where(x => x.Count() > 1))
+ foreach (var xx in _typeModels.GroupBy(x => x.ClrName).Where(x => x.Count() > 1))
throw new InvalidOperationException($"Type name \"{xx.Key}\" is used"
+ $" for types with alias {string.Join(", ", xx.Select(x => x.ItemType + ":\"" + x.Alias + "\""))}. Names have to be unique."
+ " Consider using an attribute to assign different names to conflicting types.");
// ensure we have no duplicates property names
- foreach (var typeModel in _typeModels.Where(x => !x.IsContentIgnored))
- foreach (var xx in typeModel.Properties.Where(x => !x.IsIgnored).GroupBy(x => x.ClrName).Where(x => x.Count() > 1))
+ foreach (var typeModel in _typeModels)
+ foreach (var xx in typeModel.Properties.GroupBy(x => x.ClrName).Where(x => x.Count() > 1))
throw new InvalidOperationException($"Property name \"{xx.Key}\" in type {typeModel.ItemType}:\"{typeModel.Alias}\""
+ $" is used for properties with alias {string.Join(", ", xx.Select(x => "\"" + x.Alias + "\""))}. Names have to be unique."
+ " Consider using an attribute to assign different names to conflicting properties.");
// ensure content & property type don't have identical name (csharp hates it)
- foreach (var typeModel in _typeModels.Where(x => !x.IsContentIgnored))
+ foreach (var typeModel in _typeModels)
{
- foreach (var xx in typeModel.Properties.Where(x => !x.IsIgnored && x.ClrName == typeModel.ClrName))
+ foreach (var xx in typeModel.Properties.Where(x => x.ClrName == typeModel.ClrName))
{
if (!pureLive)
throw new InvalidOperationException($"The model class for content type with alias \"{typeModel.Alias}\" is named \"{xx.ClrName}\"."
@@ -204,7 +147,7 @@ namespace Umbraco.ModelsBuilder.Building
// collect all the (non-removed) types implemented at parent level
// ie the parent content types and the mixins content types, recursively
var parentImplems = new List();
- if (typeModel.BaseType != null && !typeModel.BaseType.IsContentIgnored)
+ if (typeModel.BaseType != null)
TypeModel.CollectImplems(parentImplems, typeModel.BaseType);
// interfaces we must declare we implement (initially empty)
@@ -212,7 +155,6 @@ namespace Umbraco.ModelsBuilder.Building
// and except those that are already declared at the parent level
// in other words, DeclaringInterfaces is "local mixins"
var declaring = typeModel.MixinTypes
- .Where(x => !x.IsContentIgnored)
.Except(parentImplems);
typeModel.DeclaringInterfaces.AddRange(declaring);
@@ -227,43 +169,16 @@ namespace Umbraco.ModelsBuilder.Building
typeModel.ImplementingInterfaces.AddRange(mixinImplems.Except(parentImplems));
}
- // register using types
- foreach (var usingNamespace in ParseResult.UsingNamespaces)
+ // ensure elements don't inherit from non-elements
+ foreach (var typeModel in _typeModels.Where(x => x.IsElement))
{
- if (!TypesUsing.Contains(usingNamespace))
- TypesUsing.Add(usingNamespace);
+ if (typeModel.BaseType != null && !typeModel.BaseType.IsElement)
+ throw new InvalidOperationException($"Cannot generate model for type '{typeModel.Alias}' because it is an element type, but its parent type '{typeModel.BaseType.Alias}' is not.");
+
+ var errs = typeModel.MixinTypes.Where(x => !x.IsElement).ToList();
+ if (errs.Count > 0)
+ throw new InvalidOperationException($"Cannot generate model for type '{typeModel.Alias}' because it is an element type, but it is composed of {string.Join(", ", errs.Select(x => "'" + x.Alias + "'"))} which {(errs.Count == 1 ? "is" : "are")} not.");
}
-
- // discover static mixin methods
- foreach (var typeModel in _typeModels)
- typeModel.StaticMixinMethods.AddRange(ParseResult.StaticMixinMethods(typeModel.ClrName));
-
- // handle ctor
- foreach (var typeModel in _typeModels.Where(x => ParseResult.HasCtor(x.ClrName)))
- typeModel.HasCtor = true;
- }
-
- private SemanticModel _ambiguousSymbolsModel;
- private int _ambiguousSymbolsPos;
-
- // internal for tests
- internal void PrepareAmbiguousSymbols()
- {
- var codeBuilder = new StringBuilder();
- foreach (var t in TypesUsing)
- codeBuilder.AppendFormat("using {0};\n", t);
-
- codeBuilder.AppendFormat("namespace {0}\n{{ }}\n", GetModelsNamespace());
-
- var compiler = new Compiler();
- SyntaxTree[] trees;
- var compilation = compiler.GetCompilation("MyCompilation", new Dictionary { { "code", codeBuilder.ToString() } }, out trees);
- var tree = trees[0];
- _ambiguousSymbolsModel = compilation.GetSemanticModel(tree);
-
- var namespaceSyntax = tree.GetRoot().DescendantNodes().OfType().First();
- //var namespaceSymbol = model.GetDeclaredSymbol(namespaceSyntax);
- _ambiguousSymbolsPos = namespaceSyntax.OpenBraceToken.SpanStart;
}
// looking for a simple symbol eg 'Umbraco' or 'String'
@@ -273,20 +188,12 @@ namespace Umbraco.ModelsBuilder.Building
// - 1 symbol is found BUT not matching (implicitely ambiguous)
protected bool IsAmbiguousSymbol(string symbol, string match)
{
- if (_ambiguousSymbolsModel == null)
- PrepareAmbiguousSymbols();
- if (_ambiguousSymbolsModel == null)
- throw new Exception("Could not prepare ambiguous symbols.");
- var symbols = _ambiguousSymbolsModel.LookupNamespacesAndTypes(_ambiguousSymbolsPos, null, symbol);
+ // 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
- if (symbols.Length > 1) return true;
- if (symbols.Length == 0) return false; // what else?
-
- // only 1 - ensure it matches
- var found = symbols[0].ToDisplayString();
- var pos = found.IndexOf('<'); // generic?
- if (pos > 0) found = found.Substring(0, pos); // strip
- return found != match; // and compare
+ // Essentially this means that a `global::` syntax will be output for the generated models
+ return true;
}
internal string ModelsNamespaceForTests;
@@ -296,25 +203,18 @@ namespace Umbraco.ModelsBuilder.Building
if (ModelsNamespaceForTests != null)
return ModelsNamespaceForTests;
- // code attribute overrides everything
- if (ParseResult.HasModelsNamespace)
- return ParseResult.ModelsNamespace;
-
// if builder was initialized with a namespace, use that one
if (!string.IsNullOrWhiteSpace(ModelsNamespace))
return ModelsNamespace;
- // default
- // fixme - should NOT reference config here, should make ModelsNamespace mandatory
- return UmbracoConfig.For.ModelsBuilder().ModelsNamespace;
+ // use configured else fallback to default
+ return string.IsNullOrWhiteSpace(Config.ModelsNamespace)
+ ? ModelsBuilderConfig.DefaultModelsNamespace
+ : Config.ModelsNamespace;
}
protected string GetModelsBaseClassName(TypeModel type)
{
- // code attribute overrides everything
- if (ParseResult.HasModelsBaseClassName)
- return ParseResult.ModelsBaseClassName;
-
// default
return type.IsElement ? "PublishedElementModel" : "PublishedContentModel";
}
diff --git a/src/Umbraco.ModelsBuilder.Embedded/Building/ModelsGenerator.cs b/src/Umbraco.ModelsBuilder.Embedded/Building/ModelsGenerator.cs
new file mode 100644
index 0000000000..8a3bc5a5b5
--- /dev/null
+++ b/src/Umbraco.ModelsBuilder.Embedded/Building/ModelsGenerator.cs
@@ -0,0 +1,54 @@
+using System.IO;
+using System.Text;
+using Umbraco.ModelsBuilder.Embedded.Configuration;
+
+namespace Umbraco.ModelsBuilder.Embedded.Building
+{
+ public class ModelsGenerator
+ {
+ private readonly UmbracoServices _umbracoService;
+ private readonly IModelsBuilderConfig _config;
+ private readonly OutOfDateModelsStatus _outOfDateModels;
+
+ public ModelsGenerator(UmbracoServices umbracoService, IModelsBuilderConfig config, OutOfDateModelsStatus outOfDateModels)
+ {
+ _umbracoService = umbracoService;
+ _config = config;
+ _outOfDateModels = outOfDateModels;
+ }
+
+ 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}"")]
+";
+ */
+
+ _outOfDateModels.Clear();
+ }
+ }
+}
diff --git a/src/Umbraco.ModelsBuilder/Building/PropertyModel.cs b/src/Umbraco.ModelsBuilder.Embedded/Building/PropertyModel.cs
similarity index 89%
rename from src/Umbraco.ModelsBuilder/Building/PropertyModel.cs
rename to src/Umbraco.ModelsBuilder.Embedded/Building/PropertyModel.cs
index 1595b3f888..af5445b175 100644
--- a/src/Umbraco.ModelsBuilder/Building/PropertyModel.cs
+++ b/src/Umbraco.ModelsBuilder.Embedded/Building/PropertyModel.cs
@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
-namespace Umbraco.ModelsBuilder.Building
+namespace Umbraco.ModelsBuilder.Embedded.Building
{
///
/// Represents a model property.
@@ -41,11 +41,6 @@ namespace Umbraco.ModelsBuilder.Building
///
public string ClrTypeName;
- ///
- /// Gets a value indicating whether this property should be excluded from generation.
- ///
- public bool IsIgnored;
-
///
/// Gets the generation errors for the property.
///
diff --git a/src/Umbraco.ModelsBuilder/Building/TextBuilder.cs b/src/Umbraco.ModelsBuilder.Embedded/Building/TextBuilder.cs
similarity index 83%
rename from src/Umbraco.ModelsBuilder/Building/TextBuilder.cs
rename to src/Umbraco.ModelsBuilder.Embedded/Building/TextBuilder.cs
index 85ccd541b7..d1190a0374 100644
--- a/src/Umbraco.ModelsBuilder/Building/TextBuilder.cs
+++ b/src/Umbraco.ModelsBuilder.Embedded/Building/TextBuilder.cs
@@ -3,11 +3,9 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
-using Umbraco.Core.Configuration;
-using Umbraco.Core.Models.PublishedContent;
-using Umbraco.ModelsBuilder.Configuration;
+using Umbraco.ModelsBuilder.Embedded.Configuration;
-namespace Umbraco.ModelsBuilder.Building
+namespace Umbraco.ModelsBuilder.Embedded.Building
{
///
/// Implements a builder that works by writing text.
@@ -19,20 +17,8 @@ namespace Umbraco.ModelsBuilder.Building
/// and the result of code parsing.
///
/// The list of models to generate.
- /// The result of code parsing.
- public TextBuilder(IList typeModels, ParseResult parseResult)
- : base(typeModels, parseResult)
- { }
-
- ///
- /// 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 result of code parsing.
- /// The models namespace.
- public TextBuilder(IList typeModels, ParseResult parseResult, string modelsNamespace)
- : base(typeModels, parseResult, modelsNamespace)
+ public TextBuilder(IModelsBuilderConfig config, IList typeModels)
+ : base(config, typeModels)
{ }
// internal for unit tests only
@@ -97,6 +83,20 @@ namespace Umbraco.ModelsBuilder.Building
TextHeaderWriter.WriteHeader(sb);
}
+ // writes an attribute that identifies code generated by a tool
+ // (helps reduce warnings, tools such as FxCop use it)
+ // see https://github.com/zpqrtbnk/Zbu.ModelsBuilder/issues/107
+ // see https://docs.microsoft.com/en-us/dotnet/api/system.codedom.compiler.generatedcodeattribute
+ // see https://blogs.msdn.microsoft.com/codeanalysis/2007/04/27/correct-usage-of-the-compilergeneratedattribute-and-the-generatedcodeattribute/
+ //
+ // note that the blog post above clearly states that "Nor should it be applied at the type level if the type being generated is a partial class."
+ // and since our models are partial classes, we have to apply the attribute against the individual members, not the class itself.
+ //
+ private static void WriteGeneratedCodeAttribute(StringBuilder sb, string tabs)
+ {
+ sb.AppendFormat("{0}[global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"Umbraco.ModelsBuilder\", \"{1}\")]\n", tabs, ApiVersion.Current.Version);
+ }
+
private void WriteContentType(StringBuilder sb, TypeModel type)
{
string sep;
@@ -104,11 +104,11 @@ namespace Umbraco.ModelsBuilder.Building
if (type.IsMixin)
{
// write the interface declaration
- sb.AppendFormat("\t// Mixin content Type {0} with alias \"{1}\"\n", type.Id, type.Alias);
+ sb.AppendFormat("\t// Mixin Content Type with alias \"{0}\"\n", type.Alias);
if (!string.IsNullOrWhiteSpace(type.Name))
sb.AppendFormat("\t/// {0}\n", XmlCommentString(type.Name));
sb.AppendFormat("\tpublic partial interface I{0}", type.ClrName);
- var implements = type.BaseType == null || type.BaseType.IsContentIgnored
+ var implements = type.BaseType == null
? (type.HasBase ? null : (type.IsElement ? "PublishedElement" : "PublishedContent"))
: type.BaseType.ClrName;
if (implements != null)
@@ -126,7 +126,7 @@ namespace Umbraco.ModelsBuilder.Building
// write the properties - only the local (non-ignored) ones, we're an interface
var more = false;
- foreach (var prop in type.Properties.Where(x => !x.IsIgnored).OrderBy(x => x.ClrName))
+ foreach (var prop in type.Properties.OrderBy(x => x.ClrName))
{
if (more) sb.Append("\n");
more = true;
@@ -137,8 +137,6 @@ namespace Umbraco.ModelsBuilder.Building
}
// write the class declaration
- if (type.IsRenamed)
- sb.AppendFormat("\t// Content Type {0} with alias \"{1}\"\n", type.Id, type.Alias);
if (!string.IsNullOrWhiteSpace(type.Name))
sb.AppendFormat("\t/// {0}\n", XmlCommentString(type.Name));
// cannot do it now. see note in ImplementContentTypeAttribute
@@ -148,7 +146,7 @@ namespace Umbraco.ModelsBuilder.Building
sb.AppendFormat("\tpublic partial class {0}", type.ClrName);
var inherits = type.HasBase
? null // has its own base already
- : (type.BaseType == null || type.BaseType.IsContentIgnored
+ : (type.BaseType == null
? GetModelsBaseClassName(type)
: type.BaseType.ClrName);
if (inherits != null)
@@ -178,22 +176,25 @@ namespace Umbraco.ModelsBuilder.Building
// as 'new' since parent has its own - or maybe not - disable warning
sb.Append("\t\t// helpers\n");
sb.Append("#pragma warning disable 0109 // new is redundant\n");
+ WriteGeneratedCodeAttribute(sb, "\t\t");
sb.AppendFormat("\t\tpublic new const string ModelTypeAlias = \"{0}\";\n",
type.Alias);
var itemType = type.IsElement ? TypeModel.ItemTypes.Content : type.ItemType; // fixme
+ WriteGeneratedCodeAttribute(sb, "\t\t");
sb.AppendFormat("\t\tpublic new const PublishedItemType ModelItemType = PublishedItemType.{0};\n",
itemType);
- sb.Append("\t\tpublic new static PublishedContentType GetModelContentType()\n");
+ WriteGeneratedCodeAttribute(sb, "\t\t");
+ sb.Append("\t\tpublic new static IPublishedContentType GetModelContentType()\n");
sb.Append("\t\t\t=> PublishedModelUtility.GetModelContentType(ModelItemType, ModelTypeAlias);\n");
- sb.AppendFormat("\t\tpublic static PublishedPropertyType GetModelPropertyType(Expression> selector)\n",
+ WriteGeneratedCodeAttribute(sb, "\t\t");
+ sb.AppendFormat("\t\tpublic static IPublishedPropertyType GetModelPropertyType(Expression> selector)\n",
type.ClrName);
sb.Append("\t\t\t=> PublishedModelUtility.GetModelPropertyType(GetModelContentType(), selector);\n");
sb.Append("#pragma warning restore 0109\n\n");
// write the ctor
- if (!type.HasCtor)
- sb.AppendFormat("\t\t// ctor\n\t\tpublic {0}(IPublished{1} content)\n\t\t\t: base(content)\n\t\t{{ }}\n\n",
- type.ClrName, type.IsElement ? "Element" : "Content");
+ sb.AppendFormat("\t\t// ctor\n\t\tpublic {0}(IPublished{1} content)\n\t\t\t: base(content)\n\t\t{{ }}\n\n",
+ type.ClrName, type.IsElement ? "Element" : "Content");
// write the properties
sb.Append("\t\t// properties\n");
@@ -205,10 +206,10 @@ namespace Umbraco.ModelsBuilder.Building
private void WriteContentTypeProperties(StringBuilder sb, TypeModel type)
{
- var staticMixinGetters = UmbracoConfig.For.ModelsBuilder().StaticMixinGetters;
+ var staticMixinGetters = true;
// write the properties
- foreach (var prop in type.Properties.Where(x => !x.IsIgnored).OrderBy(x => x.ClrName))
+ foreach (var prop in type.Properties.OrderBy(x => x.ClrName))
WriteProperty(sb, type, prop, staticMixinGetters && type.IsMixin ? type.ClrName : null);
// no need to write the parent properties since we inherit from the parent
@@ -217,7 +218,7 @@ namespace Umbraco.ModelsBuilder.Building
// write the mixins properties
foreach (var mixinType in type.ImplementingInterfaces.OrderBy(x => x.ClrName))
- foreach (var prop in mixinType.Properties.Where(x => !x.IsIgnored).OrderBy(x => x.ClrName))
+ foreach (var prop in mixinType.Properties.OrderBy(x => x.ClrName))
if (staticMixinGetters)
WriteMixinProperty(sb, prop, mixinType.ClrName);
else
@@ -242,6 +243,7 @@ namespace Umbraco.ModelsBuilder.Building
sb.Append("\t\t///\n");
}
+ WriteGeneratedCodeAttribute(sb, "\t\t");
sb.AppendFormat("\t\t[ImplementPropertyType(\"{0}\")]\n", property.Alias);
sb.Append("\t\tpublic ");
@@ -256,7 +258,7 @@ namespace Umbraco.ModelsBuilder.Building
private static string MixinStaticGetterName(string clrName)
{
- return string.Format(UmbracoConfig.For.ModelsBuilder().StaticMixinGetterPattern, clrName);
+ return string.Format("Get{0}", clrName);
}
private void WriteProperty(StringBuilder sb, TypeModel type, PropertyModel property, string mixinClrName = null)
@@ -300,6 +302,7 @@ namespace Umbraco.ModelsBuilder.Building
sb.Append("\t\t///\n");
}
+ WriteGeneratedCodeAttribute(sb, "\t\t");
sb.AppendFormat("\t\t[ImplementPropertyType(\"{0}\")]\n", property.Alias);
if (mixinStatic)
@@ -336,13 +339,14 @@ namespace Umbraco.ModelsBuilder.Building
var mixinStaticGetterName = MixinStaticGetterName(property.ClrName);
- if (type.StaticMixinMethods.Contains(mixinStaticGetterName)) return;
+ //if (type.StaticMixinMethods.Contains(mixinStaticGetterName)) return;
sb.Append("\n");
if (!string.IsNullOrWhiteSpace(property.Name))
sb.AppendFormat("\t\t/// Static getter for {0}\n", XmlCommentString(property.Name));
+ WriteGeneratedCodeAttribute(sb, "\t\t");
sb.Append("\t\tpublic static ");
WriteClrType(sb, property.ClrTypeName);
sb.AppendFormat(" {0}(I{1} that) => that.Value",
@@ -397,6 +401,7 @@ namespace Umbraco.ModelsBuilder.Building
if (!string.IsNullOrWhiteSpace(property.Name))
sb.AppendFormat("\t\t/// {0}\n", XmlCommentString(property.Name));
+ WriteGeneratedCodeAttribute(sb, "\t\t");
sb.Append("\t\t");
WriteClrType(sb, property.ClrTypeName);
sb.AppendFormat(" {0} {{ get; }}\n",
@@ -461,7 +466,7 @@ namespace Umbraco.ModelsBuilder.Building
s = Regex.Replace(s, @"\{(.*)\}\[\*\]", m => ModelsMap[m.Groups[1].Value + "[]"]);
// takes care eg of "System.Int32" vs. "int"
- if (TypesMap.TryGetValue(s.ToLowerInvariant(), out string typeName))
+ if (TypesMap.TryGetValue(s, out string typeName))
{
sb.Append(typeName);
return;
@@ -481,6 +486,11 @@ namespace Umbraco.ModelsBuilder.Building
typeName = typeName.Substring(p + 1);
typeUsing = x;
}
+ else if (x == ModelsNamespace) // that one is used by default
+ {
+ typeName = typeName.Substring(p + 1);
+ typeUsing = ModelsNamespace;
+ }
}
// nested types *after* using
@@ -531,24 +541,24 @@ namespace Umbraco.ModelsBuilder.Building
return s.Replace('<', '{').Replace('>', '}').Replace('\r', ' ').Replace('\n', ' ');
}
- private static readonly IDictionary TypesMap = new Dictionary
+ private static readonly IDictionary TypesMap = new Dictionary(StringComparer.OrdinalIgnoreCase)
{
- { "system.int16", "short" },
- { "system.int32", "int" },
- { "system.int64", "long" },
- { "system.string", "string" },
- { "system.object", "object" },
- { "system.boolean", "bool" },
- { "system.void", "void" },
- { "system.char", "char" },
- { "system.byte", "byte" },
- { "system.uint16", "ushort" },
- { "system.uint32", "uint" },
- { "system.uint64", "ulong" },
- { "system.sbyte", "sbyte" },
- { "system.single", "float" },
- { "system.double", "double" },
- { "system.decimal", "decimal" }
+ { "System.Int16", "short" },
+ { "System.Int32", "int" },
+ { "System.Int64", "long" },
+ { "System.String", "string" },
+ { "System.Object", "object" },
+ { "System.Boolean", "bool" },
+ { "System.Void", "void" },
+ { "System.Char", "char" },
+ { "System.Byte", "byte" },
+ { "System.UInt16", "ushort" },
+ { "System.UInt32", "uint" },
+ { "System.UInt64", "ulong" },
+ { "System.SByte", "sbyte" },
+ { "System.Single", "float" },
+ { "System.Double", "double" },
+ { "System.Decimal", "decimal" }
};
}
}
diff --git a/src/Umbraco.ModelsBuilder/Building/TextHeaderWriter.cs b/src/Umbraco.ModelsBuilder.Embedded/Building/TextHeaderWriter.cs
similarity index 89%
rename from src/Umbraco.ModelsBuilder/Building/TextHeaderWriter.cs
rename to src/Umbraco.ModelsBuilder.Embedded/Building/TextHeaderWriter.cs
index d165f03907..a93df97806 100644
--- a/src/Umbraco.ModelsBuilder/Building/TextHeaderWriter.cs
+++ b/src/Umbraco.ModelsBuilder.Embedded/Building/TextHeaderWriter.cs
@@ -1,9 +1,8 @@
using System.Text;
-using Umbraco.ModelsBuilder.Api;
-namespace Umbraco.ModelsBuilder.Building
+namespace Umbraco.ModelsBuilder.Embedded.Building
{
- public static class TextHeaderWriter
+ internal static class TextHeaderWriter
{
///
/// Outputs an "auto-generated" header to a string builder.
diff --git a/src/Umbraco.ModelsBuilder/Building/TypeModel.cs b/src/Umbraco.ModelsBuilder.Embedded/Building/TypeModel.cs
similarity index 85%
rename from src/Umbraco.ModelsBuilder/Building/TypeModel.cs
rename to src/Umbraco.ModelsBuilder.Embedded/Building/TypeModel.cs
index 5ada8e881c..95356cf3ff 100644
--- a/src/Umbraco.ModelsBuilder/Building/TypeModel.cs
+++ b/src/Umbraco.ModelsBuilder.Embedded/Building/TypeModel.cs
@@ -1,8 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using Umbraco.Core.Models.PublishedContent;
-namespace Umbraco.ModelsBuilder.Building
+namespace Umbraco.ModelsBuilder.Embedded.Building
{
///
/// Represents a model.
@@ -76,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.
@@ -88,16 +89,6 @@ namespace Umbraco.ModelsBuilder.Building
/// or because the existing user's code declares a base class for this model.
public bool HasBase;
- ///
- /// Gets a value indicating whether this model has been renamed.
- ///
- public bool IsRenamed;
-
- ///
- /// Gets a value indicating whether this model has [ImplementContentType] already.
- ///
- public bool HasImplement;
-
///
/// Gets a value indicating whether this model is used as a mixin by another model.
///
@@ -108,16 +99,6 @@ namespace Umbraco.ModelsBuilder.Building
///
public bool IsParent;
- ///
- /// Gets a value indicating whether this model should be excluded from generation.
- ///
- public bool IsContentIgnored;
-
- ///
- /// Gets a value indicating whether the ctor is already defined in a partial.
- ///
- public bool HasCtor;
-
///
/// Gets a value indicating whether the type is an element.
///
@@ -181,11 +162,11 @@ namespace Umbraco.ModelsBuilder.Building
/// Includes the specified type.
internal static void CollectImplems(ICollection types, TypeModel type)
{
- if (!type.IsContentIgnored && types.Contains(type) == false)
+ if (types.Contains(type) == false)
types.Add(type);
- if (type.BaseType != null && !type.BaseType.IsContentIgnored)
+ if (type.BaseType != null)
CollectImplems(types, type.BaseType);
- foreach (var mixin in type.MixinTypes.Where(x => !x.IsContentIgnored))
+ foreach (var mixin in type.MixinTypes)
CollectImplems(types, mixin);
}
@@ -204,5 +185,21 @@ namespace Umbraco.ModelsBuilder.Building
typeModel = typeModel.BaseType;
}
}
+
+ ///
+ /// Maps ModelType.
+ ///
+ public static void MapModelTypes(IList typeModels, string ns)
+ {
+ var hasNs = !string.IsNullOrWhiteSpace(ns);
+ var map = typeModels.ToDictionary(x => x.Alias, x => hasNs ? (ns + "." + x.ClrName) : x.ClrName);
+ foreach (var typeModel in typeModels)
+ {
+ foreach (var propertyModel in typeModel.Properties)
+ {
+ propertyModel.ClrTypeName = ModelType.MapToName(propertyModel.ModelClrType, map);
+ }
+ }
+ }
}
}
diff --git a/src/Umbraco.ModelsBuilder/Umbraco/HashHelper.cs b/src/Umbraco.ModelsBuilder.Embedded/Building/TypeModelHasher.cs
similarity index 81%
rename from src/Umbraco.ModelsBuilder/Umbraco/HashHelper.cs
rename to src/Umbraco.ModelsBuilder.Embedded/Building/TypeModelHasher.cs
index c530cbbd6b..2f14bec875 100644
--- a/src/Umbraco.ModelsBuilder/Umbraco/HashHelper.cs
+++ b/src/Umbraco.ModelsBuilder.Embedded/Building/TypeModelHasher.cs
@@ -1,18 +1,14 @@
using System.Collections.Generic;
using System.Linq;
-using Umbraco.ModelsBuilder.Building;
-namespace Umbraco.ModelsBuilder.Umbraco
+namespace Umbraco.ModelsBuilder.Embedded.Building
{
- class HashHelper
+ internal class TypeModelHasher
{
- public static string Hash(IDictionary ourFiles, IEnumerable typeModels)
+ public static string Hash(IEnumerable typeModels)
{
var hash = new HashCombiner();
- foreach (var kvp in ourFiles)
- hash.Add(kvp.Key + "::" + kvp.Value);
-
// see Umbraco.ModelsBuilder.Umbraco.Application for what's important to hash
// ie what comes from Umbraco (not computed by ModelsBuilder) and makes a difference
@@ -39,6 +35,9 @@ namespace Umbraco.ModelsBuilder.Umbraco
}
}
+ // Include the MB version in the hash so that if the MB version changes, models are rebuilt
+ hash.Add(ApiVersion.Current.Version.ToString());
+
return hash.GetCombinedHashCode();
}
}
diff --git a/src/Umbraco.ModelsBuilder.Embedded/Compose/DisabledModelsBuilderComponent.cs b/src/Umbraco.ModelsBuilder.Embedded/Compose/DisabledModelsBuilderComponent.cs
new file mode 100644
index 0000000000..c599785711
--- /dev/null
+++ b/src/Umbraco.ModelsBuilder.Embedded/Compose/DisabledModelsBuilderComponent.cs
@@ -0,0 +1,30 @@
+using Umbraco.Core;
+using Umbraco.Core.Composing;
+using Umbraco.ModelsBuilder.Embedded.BackOffice;
+using Umbraco.Web.Features;
+
+namespace Umbraco.ModelsBuilder.Embedded.Compose
+{
+ ///
+ /// Special component used for when MB is disabled with the legacy MB is detected
+ ///
+ internal class DisabledModelsBuilderComponent : IComponent
+ {
+ private readonly UmbracoFeatures _features;
+
+ public DisabledModelsBuilderComponent(UmbracoFeatures features)
+ {
+ _features = features;
+ }
+
+ public void Initialize()
+ {
+ //disable the embedded dashboard controller
+ _features.Disabled.Controllers.Add();
+ }
+
+ public void Terminate()
+ {
+ }
+ }
+}
diff --git a/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComponent.cs b/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComponent.cs
new file mode 100644
index 0000000000..0e41c9ac62
--- /dev/null
+++ b/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComponent.cs
@@ -0,0 +1,184 @@
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Web;
+using System.Web.Mvc;
+using System.Web.Routing;
+using Umbraco.Core.Composing;
+using Umbraco.Core.IO;
+using Umbraco.Core.Services;
+using Umbraco.Core.Services.Implement;
+using Umbraco.ModelsBuilder.Embedded.BackOffice;
+using Umbraco.ModelsBuilder.Embedded.Configuration;
+using Umbraco.Web;
+using Umbraco.Web.JavaScript;
+using Umbraco.Web.Mvc;
+
+namespace Umbraco.ModelsBuilder.Embedded.Compose
+{
+
+ internal class ModelsBuilderComponent : IComponent
+ {
+
+ private readonly IModelsBuilderConfig _config;
+ private readonly LiveModelsProvider _liveModelsProvider;
+ private readonly OutOfDateModelsStatus _outOfDateModels;
+
+ public ModelsBuilderComponent(IModelsBuilderConfig config, LiveModelsProvider liveModelsProvider, OutOfDateModelsStatus outOfDateModels)
+ {
+ _config = config;
+ _liveModelsProvider = liveModelsProvider;
+ _outOfDateModels = outOfDateModels;
+ }
+
+ public void Initialize()
+ {
+ // always setup the dashboard
+ // note: UmbracoApiController instances are automatically registered
+ InstallServerVars();
+
+ ContentModelBinder.ModelBindingException += ContentModelBinder_ModelBindingException;
+
+ if (_config.Enable)
+ FileService.SavingTemplate += FileService_SavingTemplate;
+
+ if (_config.ModelsMode.IsLiveNotPure())
+ _liveModelsProvider.Install();
+
+ if (_config.FlagOutOfDateModels)
+ _outOfDateModels.Install();
+ }
+
+ public void Terminate()
+ { }
+
+ private void InstallServerVars()
+ {
+ // register our url - for the backoffice api
+ ServerVariablesParser.Parsing += (sender, serverVars) =>
+ {
+ if (!serverVars.ContainsKey("umbracoUrls"))
+ throw new ArgumentException("Missing umbracoUrls.");
+ var umbracoUrlsObject = serverVars["umbracoUrls"];
+ if (umbracoUrlsObject == null)
+ throw new ArgumentException("Null umbracoUrls");
+ if (!(umbracoUrlsObject is Dictionary umbracoUrls))
+ throw new ArgumentException("Invalid umbracoUrls");
+
+ if (!serverVars.ContainsKey("umbracoPlugins"))
+ throw new ArgumentException("Missing umbracoPlugins.");
+ if (!(serverVars["umbracoPlugins"] is Dictionary umbracoPlugins))
+ throw new ArgumentException("Invalid umbracoPlugins");
+
+ if (HttpContext.Current == null) throw new InvalidOperationException("HttpContext is null");
+ var urlHelper = new UrlHelper(new RequestContext(new HttpContextWrapper(HttpContext.Current), new RouteData()));
+
+ umbracoUrls["modelsBuilderBaseUrl"] = urlHelper.GetUmbracoApiServiceBaseUrl(controller => controller.BuildModels());
+ umbracoPlugins["modelsBuilder"] = GetModelsBuilderSettings();
+ };
+ }
+
+ private Dictionary GetModelsBuilderSettings()
+ {
+ var settings = new Dictionary
+ {
+ {"enabled", _config.Enable}
+ };
+
+ return settings;
+ }
+
+ ///
+ /// Used to check if a template is being created based on a document type, in this case we need to
+ /// ensure the template markup is correct based on the model name of the document type
+ ///
+ ///
+ ///
+ private void FileService_SavingTemplate(IFileService sender, Core.Events.SaveEventArgs e)
+ {
+ // don't do anything if the factory is not enabled
+ // because, no factory = no models (even if generation is enabled)
+ if (!_config.EnableFactory) return;
+
+ // don't do anything if this special key is not found
+ if (!e.AdditionalData.ContainsKey("CreateTemplateForContentType")) return;
+
+ // ensure we have the content type alias
+ if (!e.AdditionalData.ContainsKey("ContentTypeAlias"))
+ 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))
+ {
+ // ensure is safe and always pascal cased, per razor standard
+ // + this is how we get the default model name in Umbraco.ModelsBuilder.Umbraco.Application
+ var alias = e.AdditionalData["ContentTypeAlias"].ToString();
+ var name = template.Name; // will be the name of the content type since we are creating
+ var className = UmbracoServices.GetClrName(name, alias);
+
+ var modelNamespace = _config.ModelsNamespace;
+
+ // we do not support configuring this at the moment, so just let Umbraco use its default value
+ //var modelNamespaceAlias = ...;
+
+ var markup = ViewHelper.GetDefaultFileContent(
+ modelClassName: className,
+ modelNamespace: modelNamespace/*,
+ modelNamespaceAlias: modelNamespaceAlias*/);
+
+ //set the template content to the new markup
+ template.Content = markup;
+ }
+ }
+
+ private void ContentModelBinder_ModelBindingException(object sender, ContentModelBinder.ModelBindingArgs args)
+ {
+ var sourceAttr = args.SourceType.Assembly.GetCustomAttribute();
+ var modelAttr = args.ModelType.Assembly.GetCustomAttribute();
+
+ // if source or model is not a ModelsBuider type...
+ if (sourceAttr == null || modelAttr == null)
+ {
+ // if neither are ModelsBuilder types, give up entirely
+ if (sourceAttr == null && modelAttr == null)
+ return;
+
+ // else report, but better not restart (loops?)
+ args.Message.Append(" The ");
+ args.Message.Append(sourceAttr == null ? "view model" : "source");
+ args.Message.Append(" is a ModelsBuilder type, but the ");
+ args.Message.Append(sourceAttr != null ? "view model" : "source");
+ args.Message.Append(" is not. The application is in an unstable state and should be restarted.");
+ return;
+ }
+
+ // both are ModelsBuilder types
+ var pureSource = sourceAttr.PureLive;
+ var pureModel = modelAttr.PureLive;
+
+ 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
+ {
+ // 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 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;
+ }
+ }
+ }
+}
diff --git a/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComposer.cs b/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComposer.cs
new file mode 100644
index 0000000000..c6924e3abe
--- /dev/null
+++ b/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComposer.cs
@@ -0,0 +1,100 @@
+using System.Linq;
+using System.Reflection;
+using Umbraco.Core;
+using Umbraco.Core.Logging;
+using Umbraco.Core.Composing;
+using Umbraco.Core.Models.PublishedContent;
+using Umbraco.ModelsBuilder.Embedded.Building;
+using Umbraco.ModelsBuilder.Embedded.Configuration;
+using Umbraco.Web;
+using Umbraco.Web.PublishedCache.NuCache;
+using Umbraco.Web.Features;
+
+namespace Umbraco.ModelsBuilder.Embedded.Compose
+{
+
+
+ [ComposeBefore(typeof(NuCacheComposer))]
+ [RuntimeLevel(MinLevel = RuntimeLevel.Run)]
+ public sealed class ModelsBuilderComposer : ICoreComposer
+ {
+ public void Compose(Composition composition)
+ {
+ var isLegacyModelsBuilderInstalled = IsLegacyModelsBuilderInstalled();
+
+ if (isLegacyModelsBuilderInstalled)
+ {
+ ComposeForLegacyModelsBuilder(composition);
+ return;
+ }
+
+ composition.Components().Append();
+ composition.Register(Lifetime.Singleton);
+ composition.Configs.Add(() => new ModelsBuilderConfig());
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+
+ if (composition.Configs.ModelsBuilder().ModelsMode == ModelsMode.PureLive)
+ ComposeForLiveModels(composition);
+ else if (composition.Configs.ModelsBuilder().EnableFactory)
+ ComposeForDefaultModelsFactory(composition);
+ }
+
+ private static bool IsLegacyModelsBuilderInstalled()
+ {
+ Assembly legacyMbAssembly = null;
+ try
+ {
+ legacyMbAssembly = Assembly.Load("Umbraco.ModelsBuilder");
+ }
+ catch (System.Exception)
+ {
+ //swallow exception, DLL must not be there
+ }
+
+ return legacyMbAssembly != null;
+ }
+
+ private void ComposeForLegacyModelsBuilder(Composition composition)
+ {
+ composition.Logger.Info("ModelsBuilder.Embedded is disabled, the external ModelsBuilder was detected.");
+ composition.Components().Append();
+ composition.Dashboards().Remove();
+ }
+
+ private void ComposeForDefaultModelsFactory(Composition composition)
+ {
+ composition.RegisterUnique(factory =>
+ {
+ var typeLoader = factory.GetInstance();
+ var types = typeLoader
+ .GetTypes() // element models
+ .Concat(typeLoader.GetTypes()); // content models
+ return new PublishedModelFactory(types);
+ });
+ }
+
+ private void ComposeForLiveModels(Composition composition)
+ {
+ composition.RegisterUnique();
+
+ // the following would add @using statement in every view so user's don't
+ // have to do it - however, then noone understands where the @using statement
+ // comes from, and it cannot be avoided / removed --- DISABLED
+ //
+ /*
+ // no need for @using in views
+ // note:
+ // we are NOT using the in-code attribute here, config is required
+ // because that would require parsing the code... and what if it changes?
+ // we can AddGlobalImport not sure we can remove one anyways
+ var modelsNamespace = Configuration.Config.ModelsNamespace;
+ if (string.IsNullOrWhiteSpace(modelsNamespace))
+ modelsNamespace = Configuration.Config.DefaultModelsNamespace;
+ System.Web.WebPages.Razor.WebPageRazorHost.AddGlobalImport(modelsNamespace);
+ */
+ }
+ }
+}
diff --git a/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderInitializer.cs b/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderInitializer.cs
new file mode 100644
index 0000000000..a86669b135
--- /dev/null
+++ b/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderInitializer.cs
@@ -0,0 +1,28 @@
+using System.Web;
+using System.Web.Compilation;
+using Umbraco.ModelsBuilder.Embedded.Compose;
+
+[assembly: PreApplicationStartMethod(typeof(ModelsBuilderInitializer), "Initialize")]
+
+namespace Umbraco.ModelsBuilder.Embedded.Compose
+{
+ public static class ModelsBuilderInitializer
+ {
+ public static void Initialize()
+ {
+ // for some reason, netstandard is missing from BuildManager.ReferencedAssemblies and yet, is part of
+ // the references that CSharpCompiler receives - in some cases eg when building views - but not when
+ // using BuildManager to build the PureLive models - where is it coming from? cannot figure it out
+
+ // so... cheating here
+
+ // this is equivalent to adding
+ //
+ // to web.config system.web/compilation/assemblies
+
+ var netStandard = ReferencedAssemblies.GetNetStandardAssembly();
+ if (netStandard != null)
+ BuildManager.AddReferencedAssembly(netStandard);
+ }
+ }
+}
diff --git a/src/Umbraco.ModelsBuilder.Embedded/ConfigsExtensions.cs b/src/Umbraco.ModelsBuilder.Embedded/ConfigsExtensions.cs
new file mode 100644
index 0000000000..d634547a49
--- /dev/null
+++ b/src/Umbraco.ModelsBuilder.Embedded/ConfigsExtensions.cs
@@ -0,0 +1,20 @@
+using Umbraco.Core.Configuration;
+using Umbraco.ModelsBuilder.Embedded.Configuration;
+
+namespace Umbraco.ModelsBuilder.Embedded
+{
+ ///
+ /// Provides extension methods for the class.
+ ///
+ public static class ConfigsExtensions
+ {
+ ///
+ /// Gets the models builder configuration.
+ ///
+ /// Getting the models builder configuration freezes its state,
+ /// and any attempt at modifying the configuration using the Setup method
+ /// will be ignored.
+ public static IModelsBuilderConfig ModelsBuilder(this Configs configs)
+ => configs.GetConfig();
+ }
+}
diff --git a/src/Umbraco.ModelsBuilder.Embedded/Configuration/IModelsBuilderConfig.cs b/src/Umbraco.ModelsBuilder.Embedded/Configuration/IModelsBuilderConfig.cs
new file mode 100644
index 0000000000..7e96aec60e
--- /dev/null
+++ b/src/Umbraco.ModelsBuilder.Embedded/Configuration/IModelsBuilderConfig.cs
@@ -0,0 +1,15 @@
+namespace Umbraco.ModelsBuilder.Embedded.Configuration
+{
+ public interface IModelsBuilderConfig
+ {
+ bool Enable { get; }
+ 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.Embedded/Configuration/ModelsBuilderConfig.cs
similarity index 54%
rename from src/Umbraco.ModelsBuilder/Configuration/Config.cs
rename to src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsBuilderConfig.cs
index ebfe4be709..c6bccdcf87 100644
--- a/src/Umbraco.ModelsBuilder/Configuration/Config.cs
+++ b/src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsBuilderConfig.cs
@@ -1,53 +1,24 @@
using System;
using System.Configuration;
using System.IO;
-using System.Reflection;
using System.Web.Configuration;
-using System.Web.Hosting;
-using Microsoft.CodeAnalysis.CSharp;
using Umbraco.Core;
+using Umbraco.Core.IO;
-namespace Umbraco.ModelsBuilder.Configuration
+namespace Umbraco.ModelsBuilder.Embedded.Configuration
{
///
/// Represents the models builder configuration.
///
- public class Config
+ public class ModelsBuilderConfig : IModelsBuilderConfig
{
- private static Config _value;
+ public const string DefaultModelsNamespace = "Umbraco.Web.PublishedModels";
+ public const string DefaultModelsDirectory = "~/App_Data/Models";
///
- /// Gets the configuration - internal so that the UmbracoConfig extension
- /// can get the value to initialize its own value. Either a value has
- /// been provided via the Setup method, or a new instance is created, which
- /// will load settings from the config file.
+ /// Initializes a new instance of the class.
///
- internal static Config Value => _value ?? new Config();
-
- ///
- /// Sets the configuration programmatically.
- ///
- /// The configuration.
- ///
- /// Once the configuration has been accessed via the UmbracoConfig extension,
- /// it cannot be changed anymore, and using this method will achieve nothing.
- /// For tests, see UmbracoConfigExtensions.ResetConfig().
- ///
- public static void Setup(Config config)
- {
- _value = config;
- }
-
- internal const string DefaultStaticMixinGetterPattern = "Get{0}";
- internal const LanguageVersion DefaultLanguageVersion = LanguageVersion.CSharp6;
- internal const string DefaultModelsNamespace = "Umbraco.Web.PublishedModels";
- internal const ClrNameSource DefaultClrNameSource = ClrNameSource.Alias; // for legacy reasons
- internal const string DefaultModelsDirectory = "~/App_Data/Models";
-
- ///
- /// Initializes a new instance of the class.
- ///
- private Config()
+ public ModelsBuilderConfig()
{
const string prefix = "Umbraco.ModelsBuilder.";
@@ -56,13 +27,8 @@ namespace Umbraco.ModelsBuilder.Configuration
Enable = ConfigurationManager.AppSettings[prefix + "Enable"] == "true";
// ensure defaults are initialized for tests
- StaticMixinGetterPattern = DefaultStaticMixinGetterPattern;
- LanguageVersion = DefaultLanguageVersion;
ModelsNamespace = DefaultModelsNamespace;
- ClrNameSource = DefaultClrNameSource;
- ModelsDirectory = HostingEnvironment.IsHosted
- ? HostingEnvironment.MapPath(DefaultModelsDirectory)
- : DefaultModelsDirectory.TrimStart("~/");
+ ModelsDirectory = IOHelper.MapPath(DefaultModelsDirectory);
DebugLevel = 0;
// stop here, everything is false
@@ -80,12 +46,6 @@ namespace Umbraco.ModelsBuilder.Configuration
case nameof(ModelsMode.PureLive):
ModelsMode = ModelsMode.PureLive;
break;
- case nameof(ModelsMode.Dll):
- ModelsMode = ModelsMode.Dll;
- break;
- case nameof(ModelsMode.LiveDll):
- ModelsMode = ModelsMode.LiveDll;
- break;
case nameof(ModelsMode.AppData):
ModelsMode = ModelsMode.AppData;
break;
@@ -94,17 +54,15 @@ namespace Umbraco.ModelsBuilder.Configuration
break;
default:
throw new ConfigurationErrorsException($"ModelsMode \"{modelsMode}\" is not a valid mode."
- + " Note that modes are case-sensitive.");
+ + " Note that modes are case-sensitive. Possible values are: " + string.Join(", ", Enum.GetNames(typeof(ModelsMode))));
}
}
// default: false
- EnableApi = ConfigurationManager.AppSettings[prefix + "EnableApi"].InvariantEquals("true");
AcceptUnsafeModelsDirectory = ConfigurationManager.AppSettings[prefix + "AcceptUnsafeModelsDirectory"].InvariantEquals("true");
// default: true
EnableFactory = !ConfigurationManager.AppSettings[prefix + "EnableFactory"].InvariantEquals("false");
- StaticMixinGetters = !ConfigurationManager.AppSettings[prefix + "StaticMixinGetters"].InvariantEquals("false");
FlagOutOfDateModels = !ConfigurationManager.AppSettings[prefix + "FlagOutOfDateModels"].InvariantEquals("false");
// default: initialized above with DefaultModelsNamespace const
@@ -112,52 +70,11 @@ namespace Umbraco.ModelsBuilder.Configuration
if (!string.IsNullOrWhiteSpace(value))
ModelsNamespace = value;
- // default: initialized above with DefaultStaticMixinGetterPattern const
- value = ConfigurationManager.AppSettings[prefix + "StaticMixinGetterPattern"];
- if (!string.IsNullOrWhiteSpace(value))
- StaticMixinGetterPattern = value;
-
- // default: initialized above with DefaultLanguageVersion const
- value = ConfigurationManager.AppSettings[prefix + "LanguageVersion"];
- if (!string.IsNullOrWhiteSpace(value))
- {
- LanguageVersion lv;
- if (!Enum.TryParse(value, true, out lv))
- throw new ConfigurationErrorsException($"Invalid language version \"{value}\".");
- LanguageVersion = lv;
- }
-
- // default: initialized above with DefaultClrNameSource const
- value = ConfigurationManager.AppSettings[prefix + "ClrNameSource"];
- if (!string.IsNullOrWhiteSpace(value))
- {
- switch (value)
- {
- case nameof(ClrNameSource.Nothing):
- ClrNameSource = ClrNameSource.Nothing;
- break;
- case nameof(ClrNameSource.Alias):
- ClrNameSource = ClrNameSource.Alias;
- break;
- case nameof(ClrNameSource.RawAlias):
- ClrNameSource = ClrNameSource.RawAlias;
- break;
- case nameof(ClrNameSource.Name):
- ClrNameSource = ClrNameSource.Name;
- break;
- default:
- throw new ConfigurationErrorsException($"ClrNameSource \"{value}\" is not a valid source."
- + " Note that sources are case-sensitive.");
- }
- }
-
// default: initialized above with DefaultModelsDirectory const
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.");
@@ -181,19 +98,14 @@ namespace Umbraco.ModelsBuilder.Configuration
}
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
- public Config(
+ public ModelsBuilderConfig(
bool enable = false,
ModelsMode modelsMode = ModelsMode.Nothing,
- bool enableApi = true,
string modelsNamespace = null,
bool enableFactory = true,
- LanguageVersion languageVersion = DefaultLanguageVersion,
- bool staticMixinGetters = true,
- string staticMixinGetterPattern = null,
bool flagOutOfDateModels = true,
- ClrNameSource clrNameSource = DefaultClrNameSource,
string modelsDirectory = null,
bool acceptUnsafeModelsDirectory = false,
int debugLevel = 0)
@@ -201,14 +113,9 @@ namespace Umbraco.ModelsBuilder.Configuration
Enable = enable;
ModelsMode = modelsMode;
- EnableApi = enableApi;
ModelsNamespace = string.IsNullOrWhiteSpace(modelsNamespace) ? DefaultModelsNamespace : modelsNamespace;
EnableFactory = enableFactory;
- LanguageVersion = languageVersion;
- StaticMixinGetters = staticMixinGetters;
- StaticMixinGetterPattern = string.IsNullOrWhiteSpace(staticMixinGetterPattern) ? DefaultStaticMixinGetterPattern : staticMixinGetterPattern;
FlagOutOfDateModels = flagOutOfDateModels;
- ClrNameSource = clrNameSource;
ModelsDirectory = string.IsNullOrWhiteSpace(modelsDirectory) ? DefaultModelsDirectory : modelsDirectory;
AcceptUnsafeModelsDirectory = acceptUnsafeModelsDirectory;
DebugLevel = debugLevel;
@@ -259,27 +166,6 @@ namespace Umbraco.ModelsBuilder.Configuration
///
public ModelsMode ModelsMode { get; }
- ///
- /// Gets a value indicating whether to serve the API.
- ///
- public bool ApiServer => EnableApi && ApiInstalled && IsDebug;
-
- ///
- /// Gets a value indicating whether to enable the API.
- ///
- ///
- /// Default value is true.
- /// The API is used by the Visual Studio extension and the console tool to talk to Umbraco
- /// and retrieve the content types. It needs to be enabled so the extension & tool can work.
- ///
- public bool EnableApi { get; }
-
- ///
- /// Gets a value indicating whether the API is installed.
- ///
- // fixme - this is now always true as the API is part of Core
- public bool ApiInstalled => true;
-
///
/// Gets a value indicating whether system.web/compilation/@debug is true.
///
@@ -287,7 +173,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;
}
}
@@ -304,24 +190,6 @@ namespace Umbraco.ModelsBuilder.Configuration
/// Default value is true because no factory is enabled by default in Umbraco.
public bool EnableFactory { get; }
- ///
- /// Gets the Roslyn parser language version.
- ///
- /// Default value is CSharp6.
- public LanguageVersion LanguageVersion { get; }
-
- ///
- /// Gets a value indicating whether to generate static mixin getters.
- ///
- /// Default value is false for backward compat reaons.
- public bool StaticMixinGetters { get; }
-
- ///
- /// Gets the string pattern for mixin properties static getter name.
- ///
- /// Default value is "GetXxx". Standard string format.
- public string StaticMixinGetterPattern { get; }
-
///
/// Gets a value indicating whether we should flag out-of-date models.
///
@@ -330,11 +198,6 @@ namespace Umbraco.ModelsBuilder.Configuration
/// generated through the dashboard, the files is cleared. Default value is false.
public bool FlagOutOfDateModels { get; }
- ///
- /// Gets the CLR name source.
- ///
- public ClrNameSource ClrNameSource { get; }
-
///
/// Gets the models directory.
///
diff --git a/src/Umbraco.ModelsBuilder/Configuration/ModelsMode.cs b/src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsMode.cs
similarity index 56%
rename from src/Umbraco.ModelsBuilder/Configuration/ModelsMode.cs
rename to src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsMode.cs
index e04c4dee90..e0286fdab1 100644
--- a/src/Umbraco.ModelsBuilder/Configuration/ModelsMode.cs
+++ b/src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsMode.cs
@@ -1,4 +1,4 @@
-namespace Umbraco.ModelsBuilder.Configuration
+namespace Umbraco.ModelsBuilder.Embedded.Configuration
{
///
/// Defines the models generation modes.
@@ -8,7 +8,7 @@
///
/// Do not generate models.
///
- Nothing = 0, // default value
+ Nothing = 0, // default value
///
/// Generate models in memory.
@@ -31,22 +31,6 @@
///
/// Generation can be triggered from the dashboard. The app does not restart.
/// Models are not compiled and thus are not available to the project.
- LiveAppData,
-
- ///
- /// Generates models in AppData and compiles them into a Dll into ~/bin (the app restarts).
- /// When: generation is triggered.
- ///
- /// Generation can be triggered from the dashboard. The app does restart. Models
- /// are available to the entire project.
- Dll,
-
- ///
- /// Generates models in AppData and compiles them into a Dll into ~/bin (the app restarts).
- /// When: a content type change occurs, or generation is triggered.
- ///
- /// Generation can be triggered from the dashboard. The app does restart. Models
- /// are available to the entire project.
- LiveDll
+ LiveAppData
}
}
diff --git a/src/Umbraco.ModelsBuilder/Configuration/ModelsModeExtensions.cs b/src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsModeExtensions.cs
similarity index 61%
rename from src/Umbraco.ModelsBuilder/Configuration/ModelsModeExtensions.cs
rename to src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsModeExtensions.cs
index be609c0548..be638729ea 100644
--- a/src/Umbraco.ModelsBuilder/Configuration/ModelsModeExtensions.cs
+++ b/src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsModeExtensions.cs
@@ -1,4 +1,4 @@
-namespace Umbraco.ModelsBuilder.Configuration
+namespace Umbraco.ModelsBuilder.Embedded.Configuration
{
///
/// Provides extensions for the enumeration.
@@ -12,7 +12,6 @@
{
return
modelsMode == ModelsMode.PureLive
- || modelsMode == ModelsMode.LiveDll
|| modelsMode == ModelsMode.LiveAppData;
}
@@ -22,18 +21,7 @@
public static bool IsLiveNotPure(this ModelsMode modelsMode)
{
return
- modelsMode == ModelsMode.LiveDll
- || modelsMode == ModelsMode.LiveAppData;
- }
-
- ///
- /// Gets a value indicating whether the mode is [Live]Dll.
- ///
- public static bool IsAnyDll(this ModelsMode modelsMode)
- {
- return
- modelsMode == ModelsMode.Dll
- || modelsMode == ModelsMode.LiveDll;
+ modelsMode == ModelsMode.LiveAppData;
}
///
@@ -42,10 +30,8 @@
public static bool SupportsExplicitGeneration(this ModelsMode modelsMode)
{
return
- modelsMode == ModelsMode.Dll
- || modelsMode == ModelsMode.LiveDll
- || modelsMode == ModelsMode.AppData
+ modelsMode == ModelsMode.AppData
|| modelsMode == ModelsMode.LiveAppData;
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Umbraco.ModelsBuilder/Umbraco/HashCombiner.cs b/src/Umbraco.ModelsBuilder.Embedded/HashCombiner.cs
similarity index 76%
rename from src/Umbraco.ModelsBuilder/Umbraco/HashCombiner.cs
rename to src/Umbraco.ModelsBuilder.Embedded/HashCombiner.cs
index e11662eb24..1c1fca6f73 100644
--- a/src/Umbraco.ModelsBuilder/Umbraco/HashCombiner.cs
+++ b/src/Umbraco.ModelsBuilder.Embedded/HashCombiner.cs
@@ -1,17 +1,17 @@
using System;
using System.Globalization;
-namespace Umbraco.ModelsBuilder.Umbraco
+namespace Umbraco.ModelsBuilder.Embedded
{
// 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/ImplementPropertyTypeAttribute.cs b/src/Umbraco.ModelsBuilder.Embedded/ImplementPropertyTypeAttribute.cs
similarity index 79%
rename from src/Umbraco.ModelsBuilder/ImplementPropertyTypeAttribute.cs
rename to src/Umbraco.ModelsBuilder.Embedded/ImplementPropertyTypeAttribute.cs
index c5d8f8cad4..0359c49654 100644
--- a/src/Umbraco.ModelsBuilder/ImplementPropertyTypeAttribute.cs
+++ b/src/Umbraco.ModelsBuilder.Embedded/ImplementPropertyTypeAttribute.cs
@@ -1,10 +1,6 @@
using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-namespace Umbraco.ModelsBuilder
+namespace Umbraco.ModelsBuilder.Embedded
{
///
/// Indicates that a property implements a given property alias.
diff --git a/src/Umbraco.ModelsBuilder.Embedded/LiveModelsProvider.cs b/src/Umbraco.ModelsBuilder.Embedded/LiveModelsProvider.cs
new file mode 100644
index 0000000000..333181f27c
--- /dev/null
+++ b/src/Umbraco.ModelsBuilder.Embedded/LiveModelsProvider.cs
@@ -0,0 +1,110 @@
+using System;
+using System.Threading;
+using System.Web.Hosting;
+using Umbraco.Core.Logging;
+using Umbraco.ModelsBuilder.Embedded.Building;
+using Umbraco.ModelsBuilder.Embedded.Configuration;
+using Umbraco.Web.Cache;
+
+namespace Umbraco.ModelsBuilder.Embedded
+{
+ // supports LiveAppData - but not PureLive
+ public sealed class LiveModelsProvider
+ {
+ private static Mutex _mutex;
+ private static int _req;
+ private readonly ILogger _logger;
+ private readonly IModelsBuilderConfig _config;
+ private readonly ModelsGenerator _modelGenerator;
+ private readonly ModelsGenerationError _mbErrors;
+
+ // we do not manage pure live here
+ internal bool IsEnabled => _config.ModelsMode.IsLiveNotPure();
+
+ public LiveModelsProvider(ILogger logger, IModelsBuilderConfig config, ModelsGenerator modelGenerator, ModelsGenerationError mbErrors)
+ {
+ _logger = logger;
+ _config = config ?? throw new ArgumentNullException(nameof(config));
+ _modelGenerator = modelGenerator;
+ _mbErrors = mbErrors;
+ }
+
+ internal void Install()
+ {
+ // just be sure
+ if (!IsEnabled)
+ return;
+
+ // 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); //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;
+ DataTypeCacheRefresher.CacheUpdated += RequestModelsGeneration;
+
+ // at the end of a request since we're restarting the pool
+ // NOTE - this does NOT trigger - see module below
+ //umbracoApplication.EndRequest += GenerateModelsIfRequested;
+ }
+
+ // NOTE
+ // Using HttpContext Items fails because CacheUpdated triggers within
+ // some asynchronous backend task where we seem to have no HttpContext.
+
+ // So we use a static (non request-bound) var to register that models
+ // need to be generated. Could be by another request. Anyway. We could
+ // have collisions but... you know the risk.
+
+ private void RequestModelsGeneration(object sender, EventArgs args)
+ {
+ //HttpContext.Current.Items[this] = true;
+ _logger.Debug("Requested to generate models.");
+ Interlocked.Exchange(ref _req, 1);
+ }
+
+ public void GenerateModelsIfRequested(object sender, EventArgs args)
+ {
+ //if (HttpContext.Current.Items[this] == null) return;
+ if (Interlocked.Exchange(ref _req, 0) == 0) return;
+
+ // cannot use a simple lock here because we don't want another AppDomain
+ // to generate while we do... and there could be 2 AppDomains if the app restarts.
+
+ try
+ {
+ _logger.Debug("Generate models...");
+ const int timeout = 2 * 60 * 1000; // 2 mins
+ _mutex.WaitOne(timeout); // wait until it is safe, and acquire
+ _logger.Info("Generate models now.");
+ GenerateModels();
+ _mbErrors.Clear();
+ _logger.Info("Generated.");
+ }
+ catch (TimeoutException)
+ {
+ _logger.Warn("Timeout, models were NOT generated.");
+ }
+ catch (Exception e)
+ {
+ _mbErrors.Report("Failed to build Live models.", e);
+ _logger.Error("Failed to generate models.", e);
+ }
+ finally
+ {
+ _mutex.ReleaseMutex(); // release
+ }
+ }
+
+ private void GenerateModels()
+ {
+ // EnableDllModels will recycle the app domain - but this request will end properly
+ _modelGenerator.GenerateModels();
+ }
+
+
+ }
+}
diff --git a/src/Umbraco.ModelsBuilder.Embedded/LiveModelsProviderModule.cs b/src/Umbraco.ModelsBuilder.Embedded/LiveModelsProviderModule.cs
new file mode 100644
index 0000000000..678ff241b0
--- /dev/null
+++ b/src/Umbraco.ModelsBuilder.Embedded/LiveModelsProviderModule.cs
@@ -0,0 +1,47 @@
+using System;
+using System.Web;
+using Umbraco.Core;
+using Umbraco.Core.Composing;
+using Umbraco.ModelsBuilder.Embedded;
+
+// will install only if configuration says it needs to be installed
+[assembly: PreApplicationStartMethod(typeof(LiveModelsProviderModule), "Install")]
+
+namespace Umbraco.ModelsBuilder.Embedded
+{
+ // 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)
+ {
+ if (((HttpApplication)sender).Request.Url.IsClientSideRequest())
+ return;
+
+ // 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.TryGetInstance(); // will be null in upgrade mode or if embedded MB is disabled
+
+ if (_liveModelsProvider?.IsEnabled ?? false)
+ _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/ModelsBuilderAssemblyAttribute.cs b/src/Umbraco.ModelsBuilder.Embedded/ModelsBuilderAssemblyAttribute.cs
similarity index 95%
rename from src/Umbraco.ModelsBuilder/ModelsBuilderAssemblyAttribute.cs
rename to src/Umbraco.ModelsBuilder.Embedded/ModelsBuilderAssemblyAttribute.cs
index ed956852f8..7570c0b5b2 100644
--- a/src/Umbraco.ModelsBuilder/ModelsBuilderAssemblyAttribute.cs
+++ b/src/Umbraco.ModelsBuilder.Embedded/ModelsBuilderAssemblyAttribute.cs
@@ -1,6 +1,6 @@
using System;
-namespace Umbraco.ModelsBuilder
+namespace Umbraco.ModelsBuilder.Embedded
{
///
/// Indicates that an Assembly is a Models Builder assembly.
diff --git a/src/Umbraco.ModelsBuilder.Embedded/ModelsBuilderDashboard.cs b/src/Umbraco.ModelsBuilder.Embedded/ModelsBuilderDashboard.cs
new file mode 100644
index 0000000000..b8b1945f32
--- /dev/null
+++ b/src/Umbraco.ModelsBuilder.Embedded/ModelsBuilderDashboard.cs
@@ -0,0 +1,19 @@
+using System;
+using Umbraco.Core.Composing;
+using Umbraco.Core.Dashboards;
+
+namespace Umbraco.ModelsBuilder.Embedded
+{
+ [Weight(40)]
+ public class ModelsBuilderDashboard : IDashboard
+ {
+ public string Alias => "settingsModelsBuilder";
+
+ public string[] Sections => new [] { "settings" };
+
+ public string View => "views/dashboard/settings/modelsbuildermanagement.html";
+
+ public IAccessRule[] AccessRules => Array.Empty();
+ }
+
+}
diff --git a/src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerationError.cs b/src/Umbraco.ModelsBuilder.Embedded/ModelsGenerationError.cs
similarity index 68%
rename from src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerationError.cs
rename to src/Umbraco.ModelsBuilder.Embedded/ModelsGenerationError.cs
index 7102190b5e..a692f633a5 100644
--- a/src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerationError.cs
+++ b/src/Umbraco.ModelsBuilder.Embedded/ModelsGenerationError.cs
@@ -1,14 +1,20 @@
using System;
using System.IO;
using System.Text;
-using Umbraco.Core.Configuration;
-using Umbraco.ModelsBuilder.Configuration;
+using Umbraco.ModelsBuilder.Embedded.Configuration;
-namespace Umbraco.ModelsBuilder.Umbraco
+namespace Umbraco.ModelsBuilder.Embedded
{
- internal static class ModelsGenerationError
+ public sealed class ModelsGenerationError
{
- public static void Clear()
+ private readonly IModelsBuilderConfig _config;
+
+ public ModelsGenerationError(IModelsBuilderConfig config)
+ {
+ _config = config;
+ }
+
+ public void Clear()
{
var errFile = GetErrFile();
if (errFile == null) return;
@@ -17,7 +23,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;
@@ -33,7 +39,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;
@@ -48,9 +54,9 @@ namespace Umbraco.ModelsBuilder.Umbraco
}
}
- private static string GetErrFile()
+ private string GetErrFile()
{
- var modelsDirectory = UmbracoConfig.For.ModelsBuilder().ModelsDirectory;
+ var modelsDirectory = _config.ModelsDirectory;
if (!Directory.Exists(modelsDirectory))
return null;
diff --git a/src/Umbraco.ModelsBuilder/Umbraco/OutOfDateModelsStatus.cs b/src/Umbraco.ModelsBuilder.Embedded/OutOfDateModelsStatus.cs
similarity index 55%
rename from src/Umbraco.ModelsBuilder/Umbraco/OutOfDateModelsStatus.cs
rename to src/Umbraco.ModelsBuilder.Embedded/OutOfDateModelsStatus.cs
index a047f21edb..5425c31c77 100644
--- a/src/Umbraco.ModelsBuilder/Umbraco/OutOfDateModelsStatus.cs
+++ b/src/Umbraco.ModelsBuilder.Embedded/OutOfDateModelsStatus.cs
@@ -1,55 +1,58 @@
-using System;
-using System.IO;
-using System.Web.Hosting;
-using Umbraco.Core;
-using Umbraco.Core.Configuration;
-using Umbraco.ModelsBuilder.Configuration;
+using System.IO;
+using Umbraco.ModelsBuilder.Embedded.Configuration;
using Umbraco.Web.Cache;
-namespace Umbraco.ModelsBuilder.Umbraco
+namespace Umbraco.ModelsBuilder.Embedded
{
public sealed class OutOfDateModelsStatus
{
- internal static void Install()
+ private readonly IModelsBuilderConfig _config;
+
+ public OutOfDateModelsStatus(IModelsBuilderConfig config)
+ {
+ _config = config;
+ }
+
+ internal void Install()
{
// just be sure
- if (UmbracoConfig.For.ModelsBuilder().FlagOutOfDateModels == false)
+ if (_config.FlagOutOfDateModels == false)
return;
ContentTypeCacheRefresher.CacheUpdated += (sender, args) => Write();
DataTypeCacheRefresher.CacheUpdated += (sender, args) => Write();
}
- private static string GetFlagPath()
+ private string GetFlagPath()
{
- var modelsDirectory = UmbracoConfig.For.ModelsBuilder().ModelsDirectory;
+ var modelsDirectory = _config.ModelsDirectory;
if (!Directory.Exists(modelsDirectory))
Directory.CreateDirectory(modelsDirectory);
return Path.Combine(modelsDirectory, "ood.flag");
}
- private static void Write()
+ private void Write()
{
var path = GetFlagPath();
if (path == null || File.Exists(path)) return;
File.WriteAllText(path, "THIS FILE INDICATES THAT MODELS ARE OUT-OF-DATE\n\n");
}
- public static void Clear()
+ public void Clear()
{
- if (UmbracoConfig.For.ModelsBuilder().FlagOutOfDateModels == false) return;
+ if (_config.FlagOutOfDateModels == false) return;
var path = GetFlagPath();
if (path == null || !File.Exists(path)) return;
File.Delete(path);
}
- public static bool IsEnabled => UmbracoConfig.For.ModelsBuilder().FlagOutOfDateModels;
+ public bool IsEnabled => _config.FlagOutOfDateModels;
- public static bool IsOutOfDate
+ public bool IsOutOfDate
{
get
{
- if (UmbracoConfig.For.ModelsBuilder().FlagOutOfDateModels == false) return false;
+ if (_config.FlagOutOfDateModels == false) return false;
var path = GetFlagPath();
return path != null && File.Exists(path);
}
diff --git a/src/Umbraco.ModelsBuilder.Embedded/Properties/AssemblyInfo.cs b/src/Umbraco.ModelsBuilder.Embedded/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..68c149adde
--- /dev/null
+++ b/src/Umbraco.ModelsBuilder.Embedded/Properties/AssemblyInfo.cs
@@ -0,0 +1,13 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+[assembly: AssemblyTitle("Umbraco.ModelsBuilder")]
+[assembly: AssemblyDescription("Umbraco ModelsBuilder")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyProduct("Umbraco CMS")]
+
+[assembly: ComVisible(false)]
+[assembly: Guid("52ac0ba8-a60e-4e36-897b-e8b97a54ed1c")]
+
+[assembly: InternalsVisibleTo("Umbraco.Tests")]
diff --git a/src/Umbraco.ModelsBuilder/PublishedElementExtensions.cs b/src/Umbraco.ModelsBuilder.Embedded/PublishedElementExtensions.cs
similarity index 80%
rename from src/Umbraco.ModelsBuilder/PublishedElementExtensions.cs
rename to src/Umbraco.ModelsBuilder.Embedded/PublishedElementExtensions.cs
index f3320b5dfb..29429ba74f 100644
--- a/src/Umbraco.ModelsBuilder/PublishedElementExtensions.cs
+++ b/src/Umbraco.ModelsBuilder.Embedded/PublishedElementExtensions.cs
@@ -2,9 +2,12 @@
using System.Linq.Expressions;
using System.Reflection;
using Umbraco.Core.Models.PublishedContent;
-using Umbraco.Web;
+using Umbraco.ModelsBuilder;
+using Umbraco.ModelsBuilder.Embedded;
-namespace Umbraco.ModelsBuilder
+// same namespace as original Umbraco.Web PublishedElementExtensions
+// ReSharper disable once CheckNamespace
+namespace Umbraco.Web
{
///
/// Provides extension methods to models.
@@ -14,13 +17,14 @@ namespace Umbraco.ModelsBuilder
///
/// Gets the value of a property.
///
- public static TValue Value(this TModel model, Expression> property, string culture = ".", string segment = ".")
+ public static TValue Value(this TModel model, Expression> property, string culture = null, string segment = null, Fallback fallback = default, TValue defaultValue = default)
where TModel : IPublishedElement
{
var alias = GetAlias(model, property);
- return model.Value(alias, culture, segment);
+ return model.Value(alias, culture, segment, fallback, defaultValue);
}
+ // fixme that one should be public so ppl can use it
private static string GetAlias(TModel model, Expression> property)
{
if (property.NodeType != ExpressionType.Lambda)
diff --git a/src/Umbraco.ModelsBuilder/Umbraco/PublishedModelUtility.cs b/src/Umbraco.ModelsBuilder.Embedded/PublishedModelUtility.cs
similarity index 77%
rename from src/Umbraco.ModelsBuilder/Umbraco/PublishedModelUtility.cs
rename to src/Umbraco.ModelsBuilder.Embedded/PublishedModelUtility.cs
index c70e8a3b65..8a6ed83ce9 100644
--- a/src/Umbraco.ModelsBuilder/Umbraco/PublishedModelUtility.cs
+++ b/src/Umbraco.ModelsBuilder.Embedded/PublishedModelUtility.cs
@@ -1,11 +1,17 @@
using System;
using System.Linq;
using System.Linq.Expressions;
-using Umbraco.Web.Composing;
using Umbraco.Core.Models.PublishedContent;
+using Umbraco.Web.Composing;
-namespace Umbraco.ModelsBuilder.Umbraco
+namespace Umbraco.ModelsBuilder.Embedded
{
+ ///
+ /// This is called from within the generated model classes
+ ///
+ ///
+ /// DO NOT REMOVE - although there are not code references this is used directly by the generated models.
+ ///
public static class PublishedModelUtility
{
// looks safer but probably useless... ppl should not call these methods directly
@@ -24,7 +30,7 @@ namespace Umbraco.ModelsBuilder.Umbraco
// // etc...
//}
- public static PublishedContentType GetModelContentType(PublishedItemType itemType, string alias)
+ public static IPublishedContentType GetModelContentType(PublishedItemType itemType, string alias)
{
var facade = Current.UmbracoContext.PublishedSnapshot; // fixme inject!
switch (itemType)
@@ -40,8 +46,8 @@ namespace Umbraco.ModelsBuilder.Umbraco
}
}
- public static PublishedPropertyType GetModelPropertyType(PublishedContentType contentType, Expression> selector)
- //where TModel : PublishedContentModel // fixme PublishedContentModel _or_ PublishedElementModel
+ public static IPublishedPropertyType GetModelPropertyType(IPublishedContentType contentType, Expression> selector)
+ //where TModel : PublishedContentModel // fixme PublishedContentModel _or_ PublishedElementModel
{
// fixme therefore, missing a check on TModel here
@@ -54,7 +60,7 @@ namespace Umbraco.ModelsBuilder.Umbraco
// see note above : accepted risk...
var attr = expr.Member
- .GetCustomAttributes(typeof (ImplementPropertyTypeAttribute), false)
+ .GetCustomAttributes(typeof(ImplementPropertyTypeAttribute), false)
.OfType()
.SingleOrDefault();
diff --git a/src/Umbraco.ModelsBuilder/Umbraco/PureLiveModelFactory.cs b/src/Umbraco.ModelsBuilder.Embedded/PureLiveModelFactory.cs
similarity index 72%
rename from src/Umbraco.ModelsBuilder/Umbraco/PureLiveModelFactory.cs
rename to src/Umbraco.ModelsBuilder.Embedded/PureLiveModelFactory.cs
index 9558c0140e..8e8a19c729 100644
--- a/src/Umbraco.ModelsBuilder/Umbraco/PureLiveModelFactory.cs
+++ b/src/Umbraco.ModelsBuilder.Embedded/PureLiveModelFactory.cs
@@ -3,59 +3,60 @@ using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
-using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
+using System.Web;
using System.Web.Compilation;
using System.Web.Hosting;
using System.Web.WebPages.Razor;
using Umbraco.Core;
-using Umbraco.Core.Configuration;
using Umbraco.Core.Logging;
-using Umbraco.Core.Models;
using Umbraco.Core.Models.PublishedContent;
-using Umbraco.Web.Cache;
-using Umbraco.ModelsBuilder.Building;
-using Umbraco.ModelsBuilder.Configuration;
+using Umbraco.ModelsBuilder.Embedded.Building;
+using Umbraco.ModelsBuilder.Embedded.Configuration;
using File = System.IO.File;
-namespace Umbraco.ModelsBuilder.Umbraco
+namespace Umbraco.ModelsBuilder.Embedded
{
- internal class PureLiveModelFactory : IPublishedModelFactory, IRegisteredObject
+ internal class PureLiveModelFactory : ILivePublishedModelFactory, IRegisteredObject
{
private Assembly _modelsAssembly;
private Infos _infos = new Infos { ModelInfos = null, ModelTypeMap = new Dictionary() };
private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim();
private volatile bool _hasModels; // volatile 'cos reading outside lock
private bool _pendingRebuild;
- private readonly ProfilingLogger _logger;
+ private readonly IProfilingLogger _logger;
private readonly FileSystemWatcher _watcher;
private int _ver, _skipver;
private readonly int _debugLevel;
private BuildManager _theBuildManager;
- private readonly Lazy _umbracoServices;
+ private readonly Lazy _umbracoServices; // fixme: this is because of circular refs :(
private UmbracoServices UmbracoServices => _umbracoServices.Value;
private static readonly Regex AssemblyVersionRegex = new Regex("AssemblyVersion\\(\"[0-9]+.[0-9]+.[0-9]+.[0-9]+\"\\)", RegexOptions.Compiled);
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" };
- public PureLiveModelFactory(Lazy umbracoServices, ProfilingLogger logger)
+ private readonly IModelsBuilderConfig _config;
+ private readonly ModelsGenerationError _errors;
+
+ public PureLiveModelFactory(Lazy umbracoServices, IProfilingLogger logger, IModelsBuilderConfig config)
{
_umbracoServices = umbracoServices;
_logger = logger;
+ _config = config;
+ _errors = new ModelsGenerationError(config);
_ver = 1; // zero is for when we had no version
_skipver = -1; // nothing to skip
- ContentTypeCacheRefresher.CacheUpdated += (sender, args) => ResetModels();
- DataTypeCacheRefresher.CacheUpdated += (sender, args) => ResetModels();
+
RazorBuildProvider.CodeGenerationStarted += RazorBuildProvider_CodeGenerationStarted;
if (!HostingEnvironment.IsHosted) return;
- var modelsDirectory = UmbracoConfig.For.ModelsBuilder().ModelsDirectory;
+ var modelsDirectory = _config.ModelsDirectory;
if (!Directory.Exists(modelsDirectory))
Directory.CreateDirectory(modelsDirectory);
@@ -68,9 +69,23 @@ namespace Umbraco.ModelsBuilder.Umbraco
_watcher.EnableRaisingEvents = true;
// get it here, this need to be fast
- _debugLevel = UmbracoConfig.For.ModelsBuilder().DebugLevel;
+ _debugLevel = _config.DebugLevel;
}
+ #region ILivePublishedModelFactory
+
+ ///
+ public object SyncRoot { get; } = new object();
+
+ ///
+ public void Refresh()
+ {
+ ResetModels();
+ EnsureModels();
+ }
+
+ #endregion
+
#region IPublishedModelFactory
public IPublishedElement CreateModel(IPublishedElement element)
@@ -84,7 +99,7 @@ namespace Umbraco.ModelsBuilder.Umbraco
var contentTypeAlias = element.ContentType.Alias;
// lookup model constructor (else null)
- infos.TryGetValue(contentTypeAlias, out ModelInfo info);
+ infos.TryGetValue(contentTypeAlias, out var info);
// create model
return info == null ? element : info.Ctor(element);
@@ -115,7 +130,7 @@ namespace Umbraco.ModelsBuilder.Umbraco
if (ctor != null) return ctor();
var listType = typeof(List<>).MakeGenericType(modelInfo.ModelType);
- ctor = modelInfo.ListCtor = ReflectionUtilities.EmitCtor>(declaring: listType);
+ ctor = modelInfo.ListCtor = ReflectionUtilities.EmitConstructor>(declaring: listType);
return ctor();
}
@@ -163,7 +178,7 @@ namespace Umbraco.ModelsBuilder.Umbraco
if (_modelsAssembly == null) return;
if (_debugLevel > 0)
- _logger.Logger.Debug("RazorBuildProvider.CodeGenerationStarted");
+ _logger.Debug("RazorBuildProvider.CodeGenerationStarted");
if (!(sender is RazorBuildProvider provider)) return;
// add the assembly, and add a dependency to a text file that will change on each
@@ -182,7 +197,7 @@ namespace Umbraco.ModelsBuilder.Umbraco
// tells the factory that it should build a new generation of models
private void ResetModels()
{
- _logger.Logger.Debug("Resetting models.");
+ _logger.Debug("Resetting models.");
try
{
@@ -190,6 +205,19 @@ namespace Umbraco.ModelsBuilder.Umbraco
_hasModels = false;
_pendingRebuild = true;
+
+ var modelsDirectory = _config.ModelsDirectory;
+ if (!Directory.Exists(modelsDirectory))
+ Directory.CreateDirectory(modelsDirectory);
+
+ // clear stuff
+ var modelsHashFile = Path.Combine(modelsDirectory, "models.hash");
+ //var modelsSrcFile = Path.Combine(modelsDirectory, "models.generated.cs");
+ //var projFile = Path.Combine(modelsDirectory, "all.generated.cs");
+ var dllPathFile = Path.Combine(modelsDirectory, "all.dll.path");
+
+ if (File.Exists(dllPathFile)) File.Delete(dllPathFile);
+ if (File.Exists(modelsHashFile)) File.Delete(modelsHashFile);
}
finally
{
@@ -204,10 +232,10 @@ namespace Umbraco.ModelsBuilder.Umbraco
get
{
if (_theBuildManager != null) return _theBuildManager;
- var prop = typeof (BuildManager).GetProperty("TheBuildManager", BindingFlags.NonPublic | BindingFlags.Static);
+ var prop = typeof(BuildManager).GetProperty("TheBuildManager", BindingFlags.NonPublic | BindingFlags.Static);
if (prop == null)
throw new InvalidOperationException("Could not get BuildManager.TheBuildManager property.");
- _theBuildManager = (BuildManager) prop.GetValue(null);
+ _theBuildManager = (BuildManager)prop.GetValue(null);
return _theBuildManager;
}
}
@@ -216,7 +244,7 @@ namespace Umbraco.ModelsBuilder.Umbraco
internal Infos EnsureModels()
{
if (_debugLevel > 0)
- _logger.Logger.Debug("Ensuring models.");
+ _logger.Debug("Ensuring models.");
// don't use an upgradeable lock here because only 1 thread at a time could enter it
try
@@ -264,15 +292,15 @@ namespace Umbraco.ModelsBuilder.Umbraco
var types = assembly.ExportedTypes.Where(x => x.Inherits() || x.Inherits());
_infos = RegisterModels(types);
- ModelsGenerationError.Clear();
+ _errors.Clear();
}
catch (Exception e)
{
try
{
- _logger.Logger.Error("Failed to build models.", e);
- _logger.Logger.Warn("Running without models."); // be explicit
- ModelsGenerationError.Report("Failed to build PureLive models.", e);
+ _logger.Error("Failed to build models.", e);
+ _logger.Warn("Running without models."); // be explicit
+ _errors.Report("Failed to build PureLive models.", e);
}
finally
{
@@ -300,19 +328,12 @@ namespace Umbraco.ModelsBuilder.Umbraco
private Assembly GetModelsAssembly(bool forceRebuild)
{
- var modelsDirectory = UmbracoConfig.For.ModelsBuilder().ModelsDirectory;
+ var modelsDirectory = _config.ModelsDirectory;
if (!Directory.Exists(modelsDirectory))
Directory.CreateDirectory(modelsDirectory);
- // must filter out *.generated.cs because we haven't deleted them yet!
- var ourFiles = Directory.Exists(modelsDirectory)
- ? Directory.GetFiles(modelsDirectory, "*.cs")
- .Where(x => !x.EndsWith(".generated.cs"))
- .ToDictionary(x => x, File.ReadAllText)
- : new Dictionary();
-
var typeModels = UmbracoServices.GetAllTypes();
- var currentHash = HashHelper.Hash(ourFiles, typeModels);
+ var currentHash = TypeModelHasher.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");
@@ -323,31 +344,48 @@ namespace Umbraco.ModelsBuilder.Umbraco
if (!forceRebuild)
{
- _logger.Logger.Debug("Looking for cached models.");
+ _logger.Debug("Looking for cached models.");
if (File.Exists(modelsHashFile) && File.Exists(projFile))
{
var cachedHash = File.ReadAllText(modelsHashFile);
if (currentHash != cachedHash)
{
- _logger.Logger.Debug("Found obsolete cached models.");
+ _logger.Debug("Found obsolete cached models.");
forceRebuild = true;
}
+
+ // else cachedHash matches currentHash, we can try to load an existing dll
}
else
{
- _logger.Logger.Debug("Could not find cached models.");
+ _logger.Debug("Could not find cached models.");
forceRebuild = true;
}
}
Assembly assembly;
- if (forceRebuild == false)
+ if (!forceRebuild)
{
// try to load the dll directly (avoid rebuilding)
+ //
+ // ensure that the .dll file does not have a corresponding .dll.delete file
+ // as that would mean the the .dll file is going to be deleted and should not
+ // be re-used - that should not happen in theory, but better be safe
+ //
+ // ensure that the .dll file is in the current codegen directory - when IIS
+ // or Express does a full restart, it can switch to an entirely new codegen
+ // directory, and then we end up referencing a dll which is *not* in that
+ // directory, and BuildManager fails to instantiate views ("the view found
+ // at ... was not created").
+ //
if (File.Exists(dllPathFile))
{
var dllPath = File.ReadAllText(dllPathFile);
- if (File.Exists(dllPath))
+ var codegen = HttpRuntime.CodegenDir;
+
+ _logger.Debug($"Cached models dll at {dllPath}.");
+
+ if (File.Exists(dllPath) && !File.Exists(dllPath + ".delete") && dllPath.StartsWith(codegen))
{
assembly = Assembly.LoadFile(dllPath);
var attr = assembly.GetCustomAttribute();
@@ -359,13 +397,23 @@ namespace Umbraco.ModelsBuilder.Umbraco
// with the "same but different" version of the assembly in memory
_skipver = assembly.GetName().Version.Revision;
- _logger.Logger.Debug("Loading cached models (dll).");
+ _logger.Debug("Loading cached models (dll).");
return assembly;
}
+
+ _logger.Debug("Cached models dll cannot be loaded (invalid assembly).");
}
+ else if (!File.Exists(dllPath))
+ _logger.Debug("Cached models dll does not exist.");
+ else if (File.Exists(dllPath + ".delete"))
+ _logger.Debug("Cached models dll is marked for deletion.");
+ else if (!dllPath.StartsWith(codegen))
+ _logger.Debug("Cached models dll is in a different codegen directory.");
+ else
+ _logger.Debug("Cached models dll cannot be loaded (why?).");
}
- // mmust reset the version in the file else it would keep growing
+ // must reset the version in the file else it would keep growing
// loading cached modules only happens when the app restarts
var text = File.ReadAllText(projFile);
var match = AssemblyVersionRegex.Match(text);
@@ -381,47 +429,80 @@ namespace Umbraco.ModelsBuilder.Umbraco
//File.WriteAllText(Path.Combine(modelsDirectory, "models.dep"), "VER:" + _ver);
_ver++;
- assembly = BuildManager.GetCompiledAssembly(ProjVirt);
- File.WriteAllText(dllPathFile, assembly.Location);
+ try
+ {
+ assembly = BuildManager.GetCompiledAssembly(ProjVirt);
+ File.WriteAllText(dllPathFile, assembly.Location);
+ }
+ catch
+ {
+ ClearOnFailingToCompile(dllPathFile, modelsHashFile, projFile);
+ throw;
+ }
- _logger.Logger.Debug("Loading cached models (source).");
+ _logger.Debug("Loading cached models (source).");
return assembly;
}
// need to rebuild
- _logger.Logger.Debug("Rebuilding models.");
+ _logger.Debug("Rebuilding models.");
// generate code, save
- var code = GenerateModelsCode(ourFiles, typeModels);
+ var code = GenerateModelsCode(typeModels);
// add extra attributes,
// PureLiveAssembly helps identifying Assemblies that contain PureLive models
// AssemblyVersion is so that we have a different version for each rebuild
var ver = _ver == _skipver ? ++_ver : _ver;
_ver++;
- code = code.Replace("//ASSATTR", $@"[assembly: PureLiveAssembly]
-[assembly:ModelsBuilderAssembly(PureLive = true, SourceHash = ""{currentHash}"")]
+ code = code.Replace("//ASSATTR", $@"[assembly:ModelsBuilderAssembly(PureLive = true, SourceHash = ""{currentHash}"")]
[assembly:System.Reflection.AssemblyVersion(""0.0.0.{ver}"")]");
File.WriteAllText(modelsSrcFile, code);
// generate proj, save
- ourFiles["models.generated.cs"] = code;
- var proj = GenerateModelsProj(ourFiles);
+ var projFiles = new Dictionary
+ {
+ { "models.generated.cs", code }
+ };
+ var proj = GenerateModelsProj(projFiles);
File.WriteAllText(projFile, proj);
// compile and register
- assembly = BuildManager.GetCompiledAssembly(ProjVirt);
- File.WriteAllText(dllPathFile, assembly.Location);
+ try
+ {
+ assembly = BuildManager.GetCompiledAssembly(ProjVirt);
+ File.WriteAllText(dllPathFile, assembly.Location);
+ File.WriteAllText(modelsHashFile, currentHash);
+ }
+ catch
+ {
+ ClearOnFailingToCompile(dllPathFile, modelsHashFile, projFile);
+ throw;
+ }
- // assuming we can write and it's not going to cause exceptions...
- File.WriteAllText(modelsHashFile, currentHash);
-
- _logger.Logger.Debug("Done rebuilding.");
+ _logger.Debug("Done rebuilding.");
return assembly;
}
+ private void ClearOnFailingToCompile(string dllPathFile, string modelsHashFile, string projFile)
+ {
+ _logger.Debug("Failed to compile.");
+
+ // the dll file reference still points to the previous dll, which is obsolete
+ // now and will be deleted by ASP.NET eventually, so better clear that reference.
+ // also touch the proj file to force views to recompile - don't delete as it's
+ // useful to have the source around for debugging.
+ try
+ {
+ if (File.Exists(dllPathFile)) File.Delete(dllPathFile);
+ if (File.Exists(modelsHashFile)) File.Delete(modelsHashFile);
+ if (File.Exists(projFile)) File.SetLastWriteTime(projFile, DateTime.Now);
+ }
+ catch { /* enough */ }
+ }
+
private static Infos RegisterModels(IEnumerable types)
{
- var ctorArgTypes = new[] { typeof (IPublishedElement) };
+ var ctorArgTypes = new[] { typeof(IPublishedElement) };
var modelInfos = new Dictionary(StringComparer.InvariantCultureIgnoreCase);
var map = new Dictionary();
@@ -433,7 +514,7 @@ namespace Umbraco.ModelsBuilder.Umbraco
foreach (var ctor in type.GetConstructors())
{
var parms = ctor.GetParameters();
- if (parms.Length == 1 && typeof (IPublishedElement).IsAssignableFrom(parms[0].ParameterType))
+ if (parms.Length == 1 && typeof(IPublishedElement).IsAssignableFrom(parms[0].ParameterType))
{
if (constructor != null)
throw new InvalidOperationException($"Type {type.FullName} has more than one public constructor with one argument of type, or implementing, IPropertySet.");
@@ -448,16 +529,17 @@ namespace Umbraco.ModelsBuilder.Umbraco
var attribute = type.GetCustomAttribute(false);
var typeName = attribute == null ? type.Name : attribute.ContentTypeAlias;
- if (modelInfos.TryGetValue(typeName, out ModelInfo modelInfo))
+ if (modelInfos.TryGetValue(typeName, out var modelInfo))
throw new InvalidOperationException($"Both types {type.FullName} and {modelInfo.ModelType.FullName} want to be a model type for content type with alias \"{typeName}\".");
// fixme use Core's ReflectionUtilities.EmitCtor !!
- var meth = new DynamicMethod(string.Empty, typeof (IPublishedElement), ctorArgTypes, type.Module, true);
+ // Yes .. DynamicMethod is uber slow
+ var meth = new DynamicMethod(string.Empty, typeof(IPublishedElement), ctorArgTypes, type.Module, true);
var gen = meth.GetILGenerator();
gen.Emit(OpCodes.Ldarg_0);
gen.Emit(OpCodes.Newobj, constructor);
gen.Emit(OpCodes.Ret);
- var func = (Func) meth.CreateDelegate(typeof (Func));
+ var func = (Func)meth.CreateDelegate(typeof(Func));
modelInfos[typeName] = new ModelInfo { ParameterType = parameterType, Ctor = func, ModelType = type };
map[typeName] = type;
@@ -466,26 +548,16 @@ namespace Umbraco.ModelsBuilder.Umbraco
return new Infos { ModelInfos = modelInfos.Count > 0 ? modelInfos : null, ModelTypeMap = map };
}
- private static string GenerateModelsCode(IDictionary ourFiles, IList typeModels)
+ private string GenerateModelsCode(IList typeModels)
{
- var modelsDirectory = UmbracoConfig.For.ModelsBuilder().ModelsDirectory;
+ var modelsDirectory = _config.ModelsDirectory;
if (!Directory.Exists(modelsDirectory))
Directory.CreateDirectory(modelsDirectory);
foreach (var file in Directory.GetFiles(modelsDirectory, "*.generated.cs"))
File.Delete(file);
- var map = typeModels.ToDictionary(x => x.Alias, x => x.ClrName);
- foreach (var typeModel in typeModels)
- {
- foreach (var propertyModel in typeModel.Properties)
- {
- propertyModel.ClrTypeName = ModelType.MapToName(propertyModel.ModelClrType, map);
- }
- }
-
- var parseResult = new CodeParser().ParseWithReferencedAssemblies(ourFiles);
- var builder = new TextBuilder(typeModels, parseResult, UmbracoConfig.For.ModelsBuilder().ModelsNamespace);
+ var builder = new TextBuilder(_config, typeModels);
var codeBuilder = new StringBuilder();
builder.Generate(codeBuilder, builder.GetModelsToGenerate());
@@ -577,7 +649,7 @@ namespace Umbraco.ModelsBuilder.Umbraco
//if (_building && OurFiles.Contains(changed))
//{
- // //_logger.Logger.Info("Ignoring files self-changes.");
+ // //_logger.Info("Ignoring files self-changes.");
// return;
//}
@@ -585,9 +657,10 @@ namespace Umbraco.ModelsBuilder.Umbraco
if (OurFiles.Contains(changed))
return;
- _logger.Logger.Info("Detected files changes.");
+ _logger.Info("Detected files changes.");
- ResetModels();
+ lock (SyncRoot) // don't reset while being locked
+ ResetModels();
}
public void Stop(bool immediate)
diff --git a/src/Umbraco.ModelsBuilder/ReferencedAssemblies.cs b/src/Umbraco.ModelsBuilder.Embedded/ReferencedAssemblies.cs
similarity index 54%
rename from src/Umbraco.ModelsBuilder/ReferencedAssemblies.cs
rename to src/Umbraco.ModelsBuilder.Embedded/ReferencedAssemblies.cs
index 42e8b3b9c9..8886afa1c8 100644
--- a/src/Umbraco.ModelsBuilder/ReferencedAssemblies.cs
+++ b/src/Umbraco.ModelsBuilder.Embedded/ReferencedAssemblies.cs
@@ -4,25 +4,19 @@ using System.Linq;
using System.Reflection;
using System.Web.Compilation;
using System.Web.Hosting;
-using Microsoft.CodeAnalysis;
using Umbraco.Core;
-namespace Umbraco.ModelsBuilder
+namespace Umbraco.ModelsBuilder.Embedded
{
internal static class ReferencedAssemblies
{
private static readonly Lazy> LazyLocations;
- private static readonly Lazy> LazyReferences;
static ReferencedAssemblies()
{
LazyLocations = new Lazy>(() => HostingEnvironment.IsHosted
? GetAllReferencedAssembliesLocationFromBuildManager()
: GetAllReferencedAssembliesFromDomain());
-
- LazyReferences = new Lazy>(() => Locations
- .Select(x => MetadataReference.CreateFromFile(x))
- .ToArray());
}
///
@@ -31,19 +25,68 @@ namespace Umbraco.ModelsBuilder
///
public static IEnumerable Locations => LazyLocations.Value;
- ///
- /// Gets the metadata reference of all the referenced assemblies.
- ///
- public static IEnumerable References => LazyReferences.Value;
+ public static Assembly GetNetStandardAssembly(List assemblies)
+ {
+ if (assemblies == null)
+ assemblies = BuildManager.GetReferencedAssemblies().Cast().ToList();
- // hosted, get referenced assemblies from the BuildManader and filter
+ // for some reason, netstandard is also missing from BuildManager.ReferencedAssemblies and yet, is part of
+ // the references that CSharpCompiler (above) receives - where is it coming from? cannot figure it out
+ try
+ {
+ // so, resorting to an ugly trick
+ // we should have System.Reflection.Metadata around, and it should reference netstandard
+ var someAssembly = assemblies.First(x => x.FullName.StartsWith("System.Reflection.Metadata,"));
+ var netStandardAssemblyName = someAssembly.GetReferencedAssemblies().First(x => x.FullName.StartsWith("netstandard,"));
+ var netStandard = Assembly.Load(netStandardAssemblyName.FullName);
+ return netStandard;
+ }
+ catch { /* never mind */ }
+
+ return null;
+ }
+
+ public static Assembly GetNetStandardAssembly()
+ {
+ // in PreApplicationStartMethod we cannot get BuildManager.Referenced assemblies, do it differently
+ try
+ {
+ var someAssembly = Assembly.Load("System.Reflection.Metadata");
+ var netStandardAssemblyName = someAssembly.GetReferencedAssemblies().First(x => x.FullName.StartsWith("netstandard,"));
+ var netStandard = Assembly.Load(netStandardAssemblyName.FullName);
+ return netStandard;
+ }
+ catch { /* never mind */ }
+
+ return null;
+ }
+
+ // hosted, get referenced assemblies from the BuildManager and filter
private static IEnumerable GetAllReferencedAssembliesLocationFromBuildManager()
{
- return BuildManager.GetReferencedAssemblies()
- .Cast()
+ var assemblies = BuildManager.GetReferencedAssemblies().Cast().ToList();
+
+ assemblies.Add(typeof(ReferencedAssemblies).Assembly); // always include ourselves
+
+ // see https://github.com/aspnet/RoslynCodeDomProvider/blob/master/src/Microsoft.CodeDom.Providers.DotNetCompilerPlatform/CSharpCompiler.cs:
+ // mentions "Bug 913691: Explicitly add System.Runtime as a reference."
+ // and explicitly adds System.Runtime to references before invoking csc.exe
+ // so, doing the same here
+ try
+ {
+ var systemRuntime = Assembly.Load("System.Runtime, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
+ assemblies.Add(systemRuntime);
+ }
+ catch { /* never mind */ }
+
+ // for some reason, netstandard is also missing from BuildManager.ReferencedAssemblies and yet, is part of
+ // the references that CSharpCompiler (above) receives - where is it coming from? cannot figure it out
+ var netStandard = GetNetStandardAssembly(assemblies);
+ if (netStandard != null) assemblies.Add(netStandard);
+
+ return assemblies
.Where(x => !x.IsDynamic && !x.Location.IsNullOrWhiteSpace())
.Select(x => x.Location)
- .And(typeof(ReferencedAssemblies).Assembly.Location) // always include ourselves
.Distinct()
.ToList();
}
@@ -99,26 +142,6 @@ namespace Umbraco.ModelsBuilder
// ----
- private static IEnumerable GetDeepReferencedAssemblies(Assembly assembly)
- {
- var visiting = new Stack();
- var visited = new HashSet();
-
- visiting.Push(assembly);
- visited.Add(assembly);
- while (visiting.Count > 0)
- {
- var visAsm = visiting.Pop();
- foreach (var refAsm in visAsm.GetReferencedAssemblies()
- .Select(TryLoad)
- .Where(x => x != null && visited.Contains(x) == false))
- {
- yield return refAsm;
- visiting.Push(refAsm);
- visited.Add(refAsm);
- }
- }
- }
private static Assembly TryLoad(AssemblyName name)
{
@@ -132,6 +155,5 @@ namespace Umbraco.ModelsBuilder
return null;
}
}
-
}
}
diff --git a/src/Umbraco.ModelsBuilder/TypeExtensions.cs b/src/Umbraco.ModelsBuilder.Embedded/TypeExtensions.cs
similarity index 96%
rename from src/Umbraco.ModelsBuilder/TypeExtensions.cs
rename to src/Umbraco.ModelsBuilder.Embedded/TypeExtensions.cs
index d3b3ff6b4e..1f270a80a6 100644
--- a/src/Umbraco.ModelsBuilder/TypeExtensions.cs
+++ b/src/Umbraco.ModelsBuilder.Embedded/TypeExtensions.cs
@@ -1,6 +1,6 @@
using System;
-namespace Umbraco.ModelsBuilder
+namespace Umbraco.ModelsBuilder.Embedded
{
internal static class TypeExtensions
{
diff --git a/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj b/src/Umbraco.ModelsBuilder.Embedded/Umbraco.ModelsBuilder.Embedded.csproj
similarity index 57%
rename from src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj
rename to src/Umbraco.ModelsBuilder.Embedded/Umbraco.ModelsBuilder.Embedded.csproj
index 60ef944a8c..75121a635d 100644
--- a/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj
+++ b/src/Umbraco.ModelsBuilder.Embedded/Umbraco.ModelsBuilder.Embedded.csproj
@@ -4,13 +4,15 @@
DebugAnyCPU
- {7020A059-C0D1-43A0-8EFD-23591A0C9AF6}
+ {52AC0BA8-A60E-4E36-897B-E8B97A54ED1C}LibraryProperties
- Umbraco.ModelsBuilder
- Umbraco.ModelsBuilder
+ Umbraco.ModelsBuilder.Embedded
+ Umbraco.ModelsBuilder.Embeddedv4.7.2512
+ true
+ 7.3true
@@ -28,13 +30,14 @@
TRACEprompt4
- bin\Release\Umbraco.ModelsBuilder.xml
+ bin\Release\Umbraco.ModelsBuilder.Embedded.xml
+
+
-
@@ -46,62 +49,48 @@
Properties\SolutionInfo.cs
-
-
-
-
-
-
-
-
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
-
-
-
-
-
-
+
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
- 2.8.0
+ 2.10.01.0.0-beta2-19324-01
@@ -111,7 +100,7 @@
- {31785bc3-256c-4613-b2f5-a1b0bdded8c1}
+ {31785BC3-256C-4613-B2F5-A1B0BDDED8C1}Umbraco.Core
@@ -119,5 +108,11 @@
Umbraco.Web
+
+
+ 5.2.7
+
+
+
\ No newline at end of file
diff --git a/src/Umbraco.ModelsBuilder/Umbraco/UmbracoServices.cs b/src/Umbraco.ModelsBuilder.Embedded/UmbracoServices.cs
similarity index 68%
rename from src/Umbraco.ModelsBuilder/Umbraco/UmbracoServices.cs
rename to src/Umbraco.ModelsBuilder.Embedded/UmbracoServices.cs
index f0347d9194..5ede5f45e9 100644
--- a/src/Umbraco.ModelsBuilder/Umbraco/UmbracoServices.cs
+++ b/src/Umbraco.ModelsBuilder.Embedded/UmbracoServices.cs
@@ -2,17 +2,16 @@
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core;
-using Umbraco.Core.Configuration;
+using Umbraco.Core.Exceptions;
using Umbraco.Core.Models;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.Services;
using Umbraco.Core.Strings;
-using Umbraco.ModelsBuilder.Building;
-using Umbraco.ModelsBuilder.Configuration;
+using Umbraco.ModelsBuilder.Embedded.Building;
-namespace Umbraco.ModelsBuilder.Umbraco
+namespace Umbraco.ModelsBuilder.Embedded
{
- public class UmbracoServices
+ public sealed class UmbracoServices
{
private readonly IContentTypeService _contentTypeService;
private readonly IMediaTypeService _mediaTypeService;
@@ -33,6 +32,10 @@ namespace Umbraco.ModelsBuilder.Umbraco
{
var types = new List();
+ // TODO: this will require 3 rather large SQL queries on startup in PureLive. I know that these will be cached after lookup but it will slow
+ // down startup time ... BUT these queries are also used in NuCache on startup so we can't really avoid them. Maybe one day we can
+ // load all of these in in one query and still have them cached per service, and/or somehow improve the perf of these since they are used on startup
+ // in more than one place.
types.AddRange(GetTypes(PublishedItemType.Content, _contentTypeService.GetAll().Cast().ToArray()));
types.AddRange(GetTypes(PublishedItemType.Media, _mediaTypeService.GetAll().Cast().ToArray()));
types.AddRange(GetTypes(PublishedItemType.Member, _memberTypeService.GetAll().Cast().ToArray()));
@@ -60,38 +63,8 @@ namespace Umbraco.ModelsBuilder.Umbraco
public static string GetClrName(string name, string alias)
{
- // ideally we should just be able to re-use Umbraco's alias,
- // just upper-casing the first letter, however in v7 for backward
- // compatibility reasons aliases derive from names via ToSafeAlias which is
- // PreFilter = ApplyUrlReplaceCharacters,
- // IsTerm = (c, leading) => leading
- // ? char.IsLetter(c) // only letters
- // : (char.IsLetterOrDigit(c) || c == '_'), // letter, digit or underscore
- // StringType = CleanStringType.Ascii | CleanStringType.UmbracoCase,
- // BreakTermsOnUpper = false
- //
- // but that is not ideal with acronyms and casing
- // however we CANNOT change Umbraco
- // so, adding a way to "do it right" deriving from name, here
-
- switch (UmbracoConfig.For.ModelsBuilder().ClrNameSource)
- {
- case ClrNameSource.RawAlias:
- // use Umbraco's alias
- return alias;
-
- case ClrNameSource.Alias:
- // ModelsBuilder's legacy - but not ideal
- return alias.ToCleanString(CleanStringType.ConvertCase | CleanStringType.PascalCase);
-
- case ClrNameSource.Name:
- // derive from name
- var source = name.TrimStart('_'); // because CleanStringType.ConvertCase accepts them
- return source.ToCleanString(CleanStringType.ConvertCase | CleanStringType.PascalCase | CleanStringType.Ascii);
-
- default:
- throw new Exception("Invalid ClrNameSource.");
- }
+ // ModelsBuilder's legacy - but not ideal
+ return alias.ToCleanString(CleanStringType.ConvertCase | CleanStringType.PascalCase);
}
private IList GetTypes(PublishedItemType itemType, IContentTypeComposition[] contentTypes)
@@ -116,36 +89,26 @@ namespace Umbraco.ModelsBuilder.Umbraco
// of course this should never happen, but when it happens, better detect it
// else we end up with weird nullrefs everywhere
if (uniqueTypes.Contains(typeModel.ClrName))
- throw new Exception($"Panic: duplicate type ClrName \"{typeModel.ClrName}\".");
+ throw new PanicException($"Panic: duplicate type ClrName \"{typeModel.ClrName}\".");
uniqueTypes.Add(typeModel.ClrName);
- // fixme - we need a better way at figuring out what's an element type!
- // and then we should not do the alias filtering below
- bool IsElement(PublishedContentType x)
- {
- return x.Alias.InvariantEndsWith("Element");
- }
-
var publishedContentType = _publishedContentTypeFactory.CreateContentType(contentType);
switch (itemType)
{
case PublishedItemType.Content:
- if (IsElement(publishedContentType))
- {
- typeModel.ItemType = TypeModel.ItemTypes.Element;
- if (typeModel.ClrName.InvariantEndsWith("Element"))
- typeModel.ClrName = typeModel.ClrName.Substring(0, typeModel.ClrName.Length - "Element".Length);
- }
- else
- {
- typeModel.ItemType = TypeModel.ItemTypes.Content;
- }
+ typeModel.ItemType = publishedContentType.ItemType == PublishedItemType.Element
+ ? TypeModel.ItemTypes.Element
+ : TypeModel.ItemTypes.Content;
break;
case PublishedItemType.Media:
- typeModel.ItemType = TypeModel.ItemTypes.Media;
+ typeModel.ItemType = publishedContentType.ItemType == PublishedItemType.Element
+ ? TypeModel.ItemTypes.Element
+ : TypeModel.ItemTypes.Media;
break;
case PublishedItemType.Member:
- typeModel.ItemType = TypeModel.ItemTypes.Member;
+ typeModel.ItemType = publishedContentType.ItemType == PublishedItemType.Element
+ ? TypeModel.ItemTypes.Element
+ : TypeModel.ItemTypes.Member;
break;
default:
throw new InvalidOperationException(string.Format("Unsupported PublishedItemType \"{0}\".", itemType));
@@ -166,7 +129,7 @@ namespace Umbraco.ModelsBuilder.Umbraco
var publishedPropertyType = publishedContentType.GetPropertyType(propertyType.Alias);
if (publishedPropertyType == null)
- throw new Exception($"Panic: could not get published property type {contentType.Alias}.{propertyType.Alias}.");
+ throw new PanicException($"Panic: could not get published property type {contentType.Alias}.{propertyType.Alias}.");
propertyModel.ModelClrType = publishedPropertyType.ModelClrType;
@@ -188,7 +151,7 @@ namespace Umbraco.ModelsBuilder.Umbraco
foreach (var contentType in contentTypes)
{
var typeModel = typeModels.SingleOrDefault(x => x.Id == contentType.Id);
- if (typeModel == null) throw new Exception("Panic: no type model matching content type.");
+ if (typeModel == null) throw new PanicException("Panic: no type model matching content type.");
IEnumerable compositionTypes;
var contentTypeAsMedia = contentType as IMediaType;
@@ -197,12 +160,12 @@ namespace Umbraco.ModelsBuilder.Umbraco
if (contentTypeAsMedia != null) compositionTypes = contentTypeAsMedia.ContentTypeComposition;
else if (contentTypeAsContent != null) compositionTypes = contentTypeAsContent.ContentTypeComposition;
else if (contentTypeAsMember != null) compositionTypes = contentTypeAsMember.ContentTypeComposition;
- else throw new Exception(string.Format("Panic: unsupported type \"{0}\".", contentType.GetType().FullName));
+ else throw new PanicException(string.Format("Panic: unsupported type \"{0}\".", contentType.GetType().FullName));
foreach (var compositionType in compositionTypes)
{
var compositionModel = typeModels.SingleOrDefault(x => x.Id == compositionType.Id);
- if (compositionModel == null) throw new Exception("Panic: composition type does not exist.");
+ if (compositionModel == null) throw new PanicException("Panic: composition type does not exist.");
if (compositionType.Id == contentType.ParentId) continue;
@@ -223,11 +186,9 @@ namespace Umbraco.ModelsBuilder.Umbraco
{
var groups = typeModels.GroupBy(x => x.Alias.ToLowerInvariant());
foreach (var group in groups.Where(x => x.Count() > 1))
- {
throw new NotSupportedException($"Alias \"{group.Key}\" is used by types"
+ $" {string.Join(", ", group.Select(x => x.ItemType + ":\"" + x.Alias + "\""))}. Aliases have to be unique."
+ " One of the aliases must be modified in order to use the ModelsBuilder.");
- }
return typeModels;
}
diff --git a/src/Umbraco.ModelsBuilder/Api/ApiBasicAuthFilter.cs b/src/Umbraco.ModelsBuilder/Api/ApiBasicAuthFilter.cs
deleted file mode 100644
index cc862ff207..0000000000
--- a/src/Umbraco.ModelsBuilder/Api/ApiBasicAuthFilter.cs
+++ /dev/null
@@ -1,86 +0,0 @@
-using System;
-using System.Linq;
-using System.Net;
-using System.Net.Http;
-using System.Text;
-using System.Web.Http.Controllers;
-using System.Web.Security;
-using Umbraco.Core;
-using Umbraco.Core.Composing;
-using Umbraco.Core.Configuration;
-using Umbraco.Core.Models.Membership;
-
-namespace Umbraco.ModelsBuilder.Api
-{
-
- //TODO: This needs to be changed:
- // * Authentication cannot happen in a filter, only Authorization
- // * The filter must be an AuthorizationFilter, not an ActionFilter
- // * Authorization must be done using the Umbraco logic - it is very specific for claim checking for ASP.Net Identity
- // * Theoretically this shouldn't be required whatsoever because when we authenticate a request that has Basic Auth (i.e. for
- // VS to work, it will add the correct Claims to the Identity and it will automatically be authorized.
- //
- // we *do* have POC supporting ASP.NET identity, however they require some config on the server
- // we'll keep using this quick-and-dirty method for the time being
-
- public class ApiBasicAuthFilter : System.Web.Http.Filters.ActionFilterAttribute // use the http one, not mvc, with api controllers!
- {
- private static readonly char[] Separator = ":".ToCharArray();
- private readonly string _section;
-
- public ApiBasicAuthFilter(string section)
- {
- _section = section;
- }
-
- public override void OnActionExecuting(HttpActionContext actionContext)
- {
- try
- {
- var user = Authenticate(actionContext.Request);
- if (user == null || !user.AllowedSections.Contains(_section))
- {
- actionContext.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
- }
- //else
- //{
- // // note - would that be a proper way to pass data to the controller?
- // // see http://stevescodingblog.co.uk/basic-authentication-with-asp-net-webapi/
- // actionContext.ControllerContext.RouteData.Values["umbraco-user"] = user;
- //}
-
- base.OnActionExecuting(actionContext);
- }
- catch
- {
- actionContext.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
- }
- }
-
- private static IUser Authenticate(HttpRequestMessage request)
- {
- var ah = request.Headers.Authorization;
- if (ah == null || ah.Scheme != "Basic")
- return null;
-
- var token = ah.Parameter;
- var credentials = Encoding.ASCII
- .GetString(Convert.FromBase64String(token))
- .Split(Separator);
- if (credentials.Length != 2)
- return null;
-
- var username = ApiClient.DecodeTokenElement(credentials[0]);
- var password = ApiClient.DecodeTokenElement(credentials[1]);
-
- var providerKey = UmbracoConfig.For.UmbracoSettings().Providers.DefaultBackOfficeUserProvider;
- var provider = Membership.Providers[providerKey];
- if (provider == null || !provider.ValidateUser(username, password))
- return null;
- var user = Current.Services.UserService.GetByUsername(username);
- if (!user.IsApproved || user.IsLockedOut)
- return null;
- return user;
- }
- }
-}
\ No newline at end of file
diff --git a/src/Umbraco.ModelsBuilder/Api/ApiClient.cs b/src/Umbraco.ModelsBuilder/Api/ApiClient.cs
deleted file mode 100644
index dde3641b97..0000000000
--- a/src/Umbraco.ModelsBuilder/Api/ApiClient.cs
+++ /dev/null
@@ -1,159 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Net.Http;
-using System.Net.Http.Formatting;
-using System.Net.Http.Headers;
-using System.Text;
-
-namespace Umbraco.ModelsBuilder.Api
-{
- public class ApiClient
- {
- private readonly string _url;
- private readonly string _user;
- private readonly string _password;
-
- private readonly JsonMediaTypeFormatter _formatter;
- private readonly MediaTypeFormatter[] _formatters;
-
- // fixme hardcoded?
- // could be options - but we cannot "discover" them as the API client runs outside of the web app
- // in addition, anything that references the controller forces API clients to reference Umbraco.Core
- private const string ApiControllerUrl = "/Umbraco/BackOffice/ModelsBuilder/ModelsBuilderApi/";
-
- public ApiClient(string url, string user, string password)
- {
- _url = url.TrimEnd('/');
- _user = user;
- _password = password;
-
- _formatter = new JsonMediaTypeFormatter();
- _formatters = new MediaTypeFormatter[] { _formatter };
- }
-
- private void SetBaseAddress(HttpClient client, string url)
- {
- try
- {
- client.BaseAddress = new Uri(url);
- }
- catch
- {
- throw new UriFormatException($"Invalid URI: the format of the URI \"{url}\" could not be determined.");
- }
- }
-
- public void ValidateClientVersion()
- {
- // FIXME - add proxys support
-
- var hch = new HttpClientHandler();
-
- using (var client = new HttpClient(hch))
- {
- SetBaseAddress(client, _url);
- Authorize(client);
-
- var data = new ValidateClientVersionData
- {
- ClientVersion = ApiVersion.Current.Version,
- MinServerVersionSupportingClient = ApiVersion.Current.MinServerVersionSupportingClient,
- };
-
- var result = client.PostAsync(_url + ApiControllerUrl + nameof(ModelsBuilderApiController.ValidateClientVersion),
- data, _formatter).Result;
-
- // this is not providing enough details in case of an error - do our own reporting
- //result.EnsureSuccessStatusCode();
- EnsureSuccess(result);
- }
- }
-
- public IDictionary GetModels(Dictionary ourFiles, string modelsNamespace)
- {
- // FIXME - add proxys support
-
- var hch = new HttpClientHandler();
-
- //hch.Proxy = new WebProxy("path.to.proxy", 8888);
- //hch.UseProxy = true;
-
- using (var client = new HttpClient(hch))
- {
- SetBaseAddress(client, _url);
- Authorize(client);
-
- var data = new GetModelsData
- {
- Namespace = modelsNamespace,
- ClientVersion = ApiVersion.Current.Version,
- MinServerVersionSupportingClient = ApiVersion.Current.MinServerVersionSupportingClient,
- Files = ourFiles
- };
-
- var result = client.PostAsync(_url + ApiControllerUrl + nameof(ModelsBuilderApiController.GetModels),
- data, _formatter).Result;
-
- // this is not providing enough details in case of an error - do our own reporting
- //result.EnsureSuccessStatusCode();
- EnsureSuccess(result);
-
- var genFiles = result.Content.ReadAsAsync>(_formatters).Result;
- return genFiles;
- }
- }
-
- private static void EnsureSuccess(HttpResponseMessage result)
- {
- if (result.IsSuccessStatusCode) return;
-
- var text = result.Content.ReadAsStringAsync().Result;
- throw new Exception($"Response status code does not indicate success ({result.StatusCode})\n{text}");
- }
-
- private void Authorize(HttpClient client)
- {
- AuthorizeBasic(client);
- }
-
- // fixme - for the time being, we don't cache the token and we auth on each API call
- // not used at the moment
- /*
- private void AuthorizeIdentity(HttpClient client)
- {
- var formData = new FormUrlEncodedContent(new[]
- {
- new KeyValuePair("grant_type", "password"),
- new KeyValuePair("userName", _user),
- new KeyValuePair("password", _password),
- });
-
- var result = client.PostAsync(_url + UmbracoOAuthTokenUrl, formData).Result;
-
- EnsureSuccess(result);
-
- var token = result.Content.ReadAsAsync(_formatters).Result;
- if (token.TokenType != "bearer")
- throw new Exception($"Received invalid token type \"{token.TokenType}\", expected \"bearer\".");
-
- client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.AccessToken);
- }
- */
-
- private void AuthorizeBasic(HttpClient client)
- {
- client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic",
- Convert.ToBase64String(Encoding.UTF8.GetBytes(EncodeTokenElement(_user) + ':' + EncodeTokenElement(_password))));
- }
-
- public static string EncodeTokenElement(string s)
- {
- return s.Replace("%", "%a").Replace(":", "%b");
- }
-
- public static string DecodeTokenElement(string s)
- {
- return s.Replace("%b", ":").Replace("%a", "%");
- }
- }
-}
diff --git a/src/Umbraco.ModelsBuilder/Api/ApiHelper.cs b/src/Umbraco.ModelsBuilder/Api/ApiHelper.cs
deleted file mode 100644
index fa6492fe3f..0000000000
--- a/src/Umbraco.ModelsBuilder/Api/ApiHelper.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-using System.Collections.Generic;
-using System.Text;
-using Umbraco.ModelsBuilder.Building;
-using Umbraco.ModelsBuilder.Umbraco;
-
-namespace Umbraco.ModelsBuilder.Api
-{
- internal static class ApiHelper
- {
- public static Dictionary GetModels(UmbracoServices umbracoServices, string modelsNamespace, IDictionary files)
- {
- var typeModels = umbracoServices.GetAllTypes();
-
- var parseResult = new CodeParser().ParseWithReferencedAssemblies(files);
- var builder = new TextBuilder(typeModels, parseResult, modelsNamespace);
-
- var models = new Dictionary();
- foreach (var typeModel in builder.GetModelsToGenerate())
- {
- var sb = new StringBuilder();
- builder.Generate(sb, typeModel);
- models[typeModel.ClrName] = sb.ToString();
- }
- return models;
- }
- }
-}
diff --git a/src/Umbraco.ModelsBuilder/Api/ApiVersion.cs b/src/Umbraco.ModelsBuilder/Api/ApiVersion.cs
deleted file mode 100644
index 2ee64b8c54..0000000000
--- a/src/Umbraco.ModelsBuilder/Api/ApiVersion.cs
+++ /dev/null
@@ -1,88 +0,0 @@
-using System;
-using System.Reflection;
-
-namespace Umbraco.ModelsBuilder.Api
-{
- ///
- /// Manages API version handshake between client and server.
- ///
- public class ApiVersion
- {
- #region Configure
-
- // indicate the minimum version of the client API that is supported by this server's API.
- // eg our Version = 4.8 but we support connections from VSIX down to version 3.2
- // => as a server, we accept connections from client down to version ...
- private static readonly Version MinClientVersionSupportedByServerConst = new Version(3, 0, 0, 0);
-
- // indicate the minimum version of the server that can support the client API
- // eg our Version = 4.8 and we know we're compatible with website server down to version 3.2
- // => as a client, we tell the server down to version ... that it should accept us
- private static readonly Version MinServerVersionSupportingClientConst = new Version(3, 0, 0, 0);
-
- #endregion
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The currently executing version.
- /// The min client version supported by the server.
- /// An opt min server version supporting the client.
- internal ApiVersion(Version executingVersion, Version minClientVersionSupportedByServer, Version minServerVersionSupportingClient = null)
- {
- if (executingVersion == null) throw new ArgumentNullException(nameof(executingVersion));
- if (minClientVersionSupportedByServer == null) throw new ArgumentNullException(nameof(minClientVersionSupportedByServer));
-
- Version = executingVersion;
- MinClientVersionSupportedByServer = minClientVersionSupportedByServer;
- MinServerVersionSupportingClient = minServerVersionSupportingClient;
- }
-
- ///
- /// Gets the currently executing API version.
- ///
- public static ApiVersion Current { get; }
- = new ApiVersion(Assembly.GetExecutingAssembly().GetName().Version,
- MinClientVersionSupportedByServerConst, MinServerVersionSupportingClientConst);
-
- ///
- /// Gets the executing version of the API.
- ///
- public Version Version { get; }
-
- ///
- /// Gets the min client version supported by the server.
- ///
- public Version MinClientVersionSupportedByServer { get; }
-
- ///
- /// Gets the min server version supporting the client.
- ///
- public Version MinServerVersionSupportingClient { get; }
-
- ///
- /// Gets a value indicating whether the API server is compatible with a client.
- ///
- /// The client version.
- /// An opt min server version supporting the client.
- ///
- /// A client is compatible with a server if the client version is greater-or-equal _minClientVersionSupportedByServer
- /// (ie client can be older than server, up to a point) AND the client version is lower-or-equal the server version
- /// (ie client cannot be more recent than server) UNLESS the server .
- ///
- public bool IsCompatibleWith(Version clientVersion, Version minServerVersionSupportingClient = null)
- {
- // client cannot be older than server's min supported version
- if (clientVersion < MinClientVersionSupportedByServer)
- return false;
-
- // if we know about this client (client is older than server), it is supported
- if (clientVersion <= Version) // if we know about this client (client older than server)
- return true;
-
- // if we don't know about this client (client is newer than server),
- // give server a chance to tell client it is, indeed, ok to support it
- return minServerVersionSupportingClient != null && minServerVersionSupportingClient <= Version;
- }
- }
-}
diff --git a/src/Umbraco.ModelsBuilder/Api/GetModelsData.cs b/src/Umbraco.ModelsBuilder/Api/GetModelsData.cs
deleted file mode 100644
index 9a5c55afc2..0000000000
--- a/src/Umbraco.ModelsBuilder/Api/GetModelsData.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using System.Collections.Generic;
-using System.Runtime.Serialization;
-
-namespace Umbraco.ModelsBuilder.Api
-{
- [DataContract]
- public class GetModelsData : ValidateClientVersionData
- {
- [DataMember]
- public string Namespace { get; set; }
-
- [DataMember]
- public IDictionary Files { get; set; }
-
- public override bool IsValid => base.IsValid && Files != null;
- }
-}
\ No newline at end of file
diff --git a/src/Umbraco.ModelsBuilder/Api/ModelsBuilderApiController.cs b/src/Umbraco.ModelsBuilder/Api/ModelsBuilderApiController.cs
deleted file mode 100644
index 444910b069..0000000000
--- a/src/Umbraco.ModelsBuilder/Api/ModelsBuilderApiController.cs
+++ /dev/null
@@ -1,85 +0,0 @@
-using System;
-using System.Net;
-using System.Net.Http;
-using Umbraco.Core;
-using Umbraco.Core.Configuration;
-using Umbraco.ModelsBuilder.Configuration;
-using Umbraco.ModelsBuilder.Umbraco;
-using Umbraco.Web.Mvc;
-using Umbraco.Web.WebApi;
-
-namespace Umbraco.ModelsBuilder.Api
-{
- // read http://umbraco.com/follow-us/blog-archive/2014/1/17/heads-up,-breaking-change-coming-in-702-and-62.aspx
- // read http://our.umbraco.org/forum/developers/api-questions/43025-Web-API-authentication
- // UmbracoAuthorizedApiController :: /Umbraco/BackOffice/Zbu/ModelsBuilderApi/GetTypeModels
- // UmbracoApiController :: /Umbraco/Zbu/ModelsBuilderApi/GetTypeModels ?? UNLESS marked with isbackoffice
- //
- // BEWARE! the controller url is hard-coded in ModelsBuilderApi and needs to be in sync!
-
- [PluginController(ControllerArea)]
- [IsBackOffice]
- //[UmbracoApplicationAuthorize(Constants.Applications.Developer)] // see ApiBasicAuthFilter - that one would be for ASP.NET identity
- public class ModelsBuilderApiController : UmbracoApiController // UmbracoAuthorizedApiController - for ASP.NET identity
- {
- public const string ControllerArea = "ModelsBuilder";
-
- private readonly UmbracoServices _umbracoServices;
-
- public ModelsBuilderApiController(UmbracoServices umbracoServices)
- {
- _umbracoServices = umbracoServices;
- }
-
- // invoked by the API
- [System.Web.Http.HttpPost] // use the http one, not mvc, with api controllers!
- [ApiBasicAuthFilter("developer")] // have to use our own, non-cookie-based, auth
- public HttpResponseMessage ValidateClientVersion(ValidateClientVersionData data)
- {
- if (!UmbracoConfig.For.ModelsBuilder().ApiServer)
- return Request.CreateResponse(HttpStatusCode.Forbidden, "API server does not want to talk to you.");
-
- if (!ModelState.IsValid || data == null || !data.IsValid)
- return Request.CreateResponse(HttpStatusCode.BadRequest, "Invalid data.");
-
- var checkResult = CheckVersion(data.ClientVersion, data.MinServerVersionSupportingClient);
- return (checkResult.Success
- ? Request.CreateResponse(HttpStatusCode.OK, "OK", Configuration.Formatters.JsonFormatter)
- : checkResult.Result);
- }
-
- // invoked by the API
- [System.Web.Http.HttpPost] // use the http one, not mvc, with api controllers!
- [ApiBasicAuthFilter("developer")] // have to use our own, non-cookie-based, auth
- public HttpResponseMessage GetModels(GetModelsData data)
- {
- if (!UmbracoConfig.For.ModelsBuilder().ApiServer)
- return Request.CreateResponse(HttpStatusCode.Forbidden, "API server does not want to talk to you.");
-
- if (!ModelState.IsValid || data == null || !data.IsValid)
- return Request.CreateResponse(HttpStatusCode.BadRequest, "Invalid data.");
-
- var checkResult = CheckVersion(data.ClientVersion, data.MinServerVersionSupportingClient);
- if (!checkResult.Success)
- return checkResult.Result;
-
- var models = ApiHelper.GetModels(_umbracoServices, data.Namespace, data.Files);
-
- return Request.CreateResponse(HttpStatusCode.OK, models, Configuration.Formatters.JsonFormatter);
- }
-
- private Attempt CheckVersion(Version clientVersion, Version minServerVersionSupportingClient)
- {
- if (clientVersion == null)
- return Attempt.Fail(Request.CreateResponse(HttpStatusCode.Forbidden,
- $"API version conflict: client version () is not compatible with server version({ApiVersion.Current.Version})."));
-
- // minServerVersionSupportingClient can be null
- var isOk = ApiVersion.Current.IsCompatibleWith(clientVersion, minServerVersionSupportingClient);
- var response = isOk ? null : Request.CreateResponse(HttpStatusCode.Forbidden,
- $"API version conflict: client version ({clientVersion}) is not compatible with server version({ApiVersion.Current.Version}).");
-
- return Attempt.If(isOk, response);
- }
- }
-}
diff --git a/src/Umbraco.ModelsBuilder/Api/TokenData.cs b/src/Umbraco.ModelsBuilder/Api/TokenData.cs
deleted file mode 100644
index c34a6c75c5..0000000000
--- a/src/Umbraco.ModelsBuilder/Api/TokenData.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using System.Runtime.Serialization;
-
-namespace Umbraco.ModelsBuilder.Api
-{
- [DataContract]
- class TokenData
- {
- [DataMember(Name = "access_token")]
- public string AccessToken { get; set; }
-
- [DataMember(Name = "token_type")]
- public string TokenType { get; set; }
-
- [DataMember(Name = "expires_in")]
- public int ExpiresIn { get; set; }
- }
-}
diff --git a/src/Umbraco.ModelsBuilder/Api/ValidateClientVersionData.cs b/src/Umbraco.ModelsBuilder/Api/ValidateClientVersionData.cs
deleted file mode 100644
index 39ef08d816..0000000000
--- a/src/Umbraco.ModelsBuilder/Api/ValidateClientVersionData.cs
+++ /dev/null
@@ -1,57 +0,0 @@
-using System;
-using System.Runtime.Serialization;
-
-namespace Umbraco.ModelsBuilder.Api
-{
- [DataContract]
- public class ValidateClientVersionData
- {
- // issues 32, 34... problems when serializing versions
- //
- // make sure System.Version objects are transfered as strings
- // depending on the JSON serializer version, it looks like versions are causing issues
- // see
- // http://stackoverflow.com/questions/13170386/why-system-version-in-json-string-does-not-deserialize-correctly
- //
- // if the class is marked with [DataContract] then only properties marked with [DataMember]
- // are serialized and the rest is ignored, see
- // http://www.asp.net/web-api/overview/formats-and-model-binding/json-and-xml-serialization
-
- [DataMember]
- public string ClientVersionString
- {
- get { return VersionToString(ClientVersion); }
- set { ClientVersion = ParseVersion(value, false, "client"); }
- }
-
- [DataMember]
- public string MinServerVersionSupportingClientString
- {
- get { return VersionToString(MinServerVersionSupportingClient); }
- set { MinServerVersionSupportingClient = ParseVersion(value, true, "minServer"); }
- }
-
- // not serialized
- public Version ClientVersion { get; set; }
- public Version MinServerVersionSupportingClient { get; set; }
-
- private static string VersionToString(Version version)
- {
- return version?.ToString() ?? "0.0.0.0";
- }
-
- private static Version ParseVersion(string value, bool canBeNull, string name)
- {
- if (string.IsNullOrWhiteSpace(value) && canBeNull)
- return null;
-
- Version version;
- if (Version.TryParse(value, out version))
- return version;
-
- throw new ArgumentException($"Failed to parse \"{value}\" as {name} version.");
- }
-
- public virtual bool IsValid => ClientVersion != null;
- }
-}
\ No newline at end of file
diff --git a/src/Umbraco.ModelsBuilder/Building/CodeDomBuilder.cs b/src/Umbraco.ModelsBuilder/Building/CodeDomBuilder.cs
deleted file mode 100644
index 925337bd1e..0000000000
--- a/src/Umbraco.ModelsBuilder/Building/CodeDomBuilder.cs
+++ /dev/null
@@ -1,113 +0,0 @@
-using System.CodeDom;
-using System.Collections.Generic;
-
-namespace Umbraco.ModelsBuilder.Building
-{
- // NOTE
- // See nodes in Builder.cs class - that one does not work, is not complete,
- // and was just some sort of experiment...
-
- ///
- /// Implements a builder that works by using CodeDom
- ///
- internal class CodeDomBuilder : Builder
- {
- ///
- /// Initializes a new instance of the class with a list of models to generate.
- ///
- /// The list of models to generate.
- public CodeDomBuilder(IList typeModels)
- : base(typeModels, null)
- { }
-
- ///
- /// Outputs a generated model to a code namespace.
- ///
- /// The code namespace.
- /// The model to generate.
- public void Generate(CodeNamespace ns, TypeModel typeModel)
- {
- // what about USING?
- // what about references?
-
- if (typeModel.IsMixin)
- {
- var i = new CodeTypeDeclaration("I" + typeModel.ClrName)
- {
- IsInterface = true,
- IsPartial = true,
- Attributes = MemberAttributes.Public
- };
- i.BaseTypes.Add(typeModel.BaseType == null ? "IPublishedContent" : "I" + typeModel.BaseType.ClrName);
-
- foreach (var mixinType in typeModel.DeclaringInterfaces)
- i.BaseTypes.Add(mixinType.ClrName);
-
- i.Comments.Add(new CodeCommentStatement($"Mixin content Type {typeModel.Id} with alias \"{typeModel.Alias}\""));
-
- foreach (var propertyModel in typeModel.Properties)
- {
- var p = new CodeMemberProperty
- {
- Name = propertyModel.ClrName,
- Type = new CodeTypeReference(propertyModel.ModelClrType),
- Attributes = MemberAttributes.Public,
- HasGet = true,
- HasSet = false
- };
- i.Members.Add(p);
- }
- }
-
- var c = new CodeTypeDeclaration(typeModel.ClrName)
- {
- IsClass = true,
- IsPartial = true,
- Attributes = MemberAttributes.Public
- };
-
- c.BaseTypes.Add(typeModel.BaseType == null ? "PublishedContentModel" : typeModel.BaseType.ClrName);
-
- // if it's a missing it implements its own interface
- if (typeModel.IsMixin)
- c.BaseTypes.Add("I" + typeModel.ClrName);
-
- // write the mixins, if any, as interfaces
- // only if not a mixin because otherwise the interface already has them
- if (typeModel.IsMixin == false)
- foreach (var mixinType in typeModel.DeclaringInterfaces)
- c.BaseTypes.Add("I" + mixinType.ClrName);
-
- foreach (var mixin in typeModel.MixinTypes)
- c.BaseTypes.Add("I" + mixin.ClrName);
-
- c.Comments.Add(new CodeCommentStatement($"Content Type {typeModel.Id} with alias \"{typeModel.Alias}\""));
-
- foreach (var propertyModel in typeModel.Properties)
- {
- var p = new CodeMemberProperty
- {
- Name = propertyModel.ClrName,
- Type = new CodeTypeReference(propertyModel.ModelClrType),
- Attributes = MemberAttributes.Public,
- HasGet = true,
- HasSet = false
- };
- p.GetStatements.Add(new CodeMethodReturnStatement( // return
- new CodeMethodInvokeExpression(
- new CodeMethodReferenceExpression(
- new CodeThisReferenceExpression(), // this
- "Value", // .Value
- new[] //
- {
- new CodeTypeReference(propertyModel.ModelClrType)
- }),
- new CodeExpression[] // ("alias")
- {
- new CodePrimitiveExpression(propertyModel.Alias)
- })));
- c.Members.Add(p);
- }
- }
- }
-}
diff --git a/src/Umbraco.ModelsBuilder/Building/CodeParser.cs b/src/Umbraco.ModelsBuilder/Building/CodeParser.cs
deleted file mode 100644
index 30fcbf1f91..0000000000
--- a/src/Umbraco.ModelsBuilder/Building/CodeParser.cs
+++ /dev/null
@@ -1,238 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.CSharp;
-using Microsoft.CodeAnalysis.CSharp.Syntax;
-using Umbraco.Core.Models.PublishedContent;
-
-namespace Umbraco.ModelsBuilder.Building
-{
- ///
- /// Implements code parsing.
- ///
- /// Parses user's code and look for generator's instructions.
- internal class CodeParser
- {
- ///
- /// Parses a set of file.
- ///
- /// A set of (filename,content) representing content to parse.
- /// The result of the code parsing.
- /// The set of files is a dictionary of name, content.
- public ParseResult Parse(IDictionary files)
- {
- return Parse(files, Enumerable.Empty());
- }
-
- ///
- /// Parses a set of file.
- ///
- /// A set of (filename,content) representing content to parse.
- /// Assemblies to reference in compilations.
- /// The result of the code parsing.
- /// The set of files is a dictionary of name, content.
- public ParseResult Parse(IDictionary files, IEnumerable references)
- {
- SyntaxTree[] trees;
- var compiler = new Compiler { References = references };
- var compilation = compiler.GetCompilation("Umbraco.ModelsBuilder.Generated", files, out trees);
-
- var disco = new ParseResult();
- foreach (var tree in trees)
- Parse(disco, compilation, tree);
-
- return disco;
- }
-
- public ParseResult ParseWithReferencedAssemblies(IDictionary files)
- {
- return Parse(files, ReferencedAssemblies.References);
- }
-
- private static void Parse(ParseResult disco, CSharpCompilation compilation, SyntaxTree tree)
- {
- var model = compilation.GetSemanticModel(tree);
-
- //we quite probably have errors but that is normal
- //var diags = model.GetDiagnostics();
-
- var classDecls = tree.GetRoot().DescendantNodes().OfType();
- foreach (var classSymbol in classDecls.Select(x => model.GetDeclaredSymbol(x)))
- {
- ParseClassSymbols(disco, classSymbol);
-
- var baseClassSymbol = classSymbol.BaseType;
- if (baseClassSymbol != null)
- //disco.SetContentBaseClass(SymbolDisplay.ToDisplayString(classSymbol), SymbolDisplay.ToDisplayString(baseClassSymbol));
- disco.SetContentBaseClass(classSymbol.Name, baseClassSymbol.Name);
-
- var interfaceSymbols = classSymbol.Interfaces;
- disco.SetContentInterfaces(classSymbol.Name, //SymbolDisplay.ToDisplayString(classSymbol),
- interfaceSymbols.Select(x => x.Name)); //SymbolDisplay.ToDisplayString(x)));
-
- var hasCtor = classSymbol.Constructors
- .Any(x =>
- {
- if (x.IsStatic) return false;
- if (x.Parameters.Length != 1) return false;
- var type1 = x.Parameters[0].Type;
- var type2 = typeof (IPublishedContent);
- return type1.ToDisplayString() == type2.FullName;
- });
-
- if (hasCtor)
- disco.SetHasCtor(classSymbol.Name);
-
- foreach (var propertySymbol in classSymbol.GetMembers().Where(x => x is IPropertySymbol))
- ParsePropertySymbols(disco, classSymbol, propertySymbol);
-
- foreach (var staticMethodSymbol in classSymbol.GetMembers().Where(x => x is IMethodSymbol))
- ParseMethodSymbol(disco, classSymbol, staticMethodSymbol);
- }
-
- var interfaceDecls = tree.GetRoot().DescendantNodes().OfType();
- foreach (var interfaceSymbol in interfaceDecls.Select(x => model.GetDeclaredSymbol(x)))
- {
- ParseClassSymbols(disco, interfaceSymbol);
-
- var interfaceSymbols = interfaceSymbol.Interfaces;
- disco.SetContentInterfaces(interfaceSymbol.Name, //SymbolDisplay.ToDisplayString(interfaceSymbol),
- interfaceSymbols.Select(x => x.Name)); // SymbolDisplay.ToDisplayString(x)));
- }
-
- ParseAssemblySymbols(disco, compilation.Assembly);
- }
-
- private static void ParseClassSymbols(ParseResult disco, ISymbol symbol)
- {
- foreach (var attrData in symbol.GetAttributes())
- {
- var attrClassSymbol = attrData.AttributeClass;
-
- // handle errors
- if (attrClassSymbol is IErrorTypeSymbol) continue;
- if (attrData.AttributeConstructor == null) continue;
-
- var attrClassName = SymbolDisplay.ToDisplayString(attrClassSymbol);
- switch (attrClassName)
- {
- case "Umbraco.ModelsBuilder.IgnorePropertyTypeAttribute":
- var propertyAliasToIgnore = (string)attrData.ConstructorArguments[0].Value;
- disco.SetIgnoredProperty(symbol.Name /*SymbolDisplay.ToDisplayString(symbol)*/, propertyAliasToIgnore);
- break;
- case "Umbraco.ModelsBuilder.RenamePropertyTypeAttribute":
- var propertyAliasToRename = (string)attrData.ConstructorArguments[0].Value;
- var propertyRenamed = (string)attrData.ConstructorArguments[1].Value;
- disco.SetRenamedProperty(symbol.Name /*SymbolDisplay.ToDisplayString(symbol)*/, propertyAliasToRename, propertyRenamed);
- break;
- // that one causes all sorts of issues with references to Umbraco.Core in Roslyn
- //case "Umbraco.Core.Models.PublishedContent.PublishedContentModelAttribute":
- // var contentAliasToRename = (string)attrData.ConstructorArguments[0].Value;
- // disco.SetRenamedContent(contentAliasToRename, symbol.Name /*SymbolDisplay.ToDisplayString(symbol)*/);
- // break;
- case "Umbraco.ModelsBuilder.ImplementContentTypeAttribute":
- var contentAliasToRename = (string)attrData.ConstructorArguments[0].Value;
- disco.SetRenamedContent(contentAliasToRename, symbol.Name, true /*SymbolDisplay.ToDisplayString(symbol)*/);
- break;
- }
- }
- }
-
- private static void ParsePropertySymbols(ParseResult disco, ISymbol classSymbol, ISymbol symbol)
- {
- foreach (var attrData in symbol.GetAttributes())
- {
- var attrClassSymbol = attrData.AttributeClass;
-
- // handle errors
- if (attrClassSymbol is IErrorTypeSymbol) continue;
- if (attrData.AttributeConstructor == null) continue;
-
- var attrClassName = SymbolDisplay.ToDisplayString(attrClassSymbol);
- // ReSharper disable once SwitchStatementMissingSomeCases
- switch (attrClassName)
- {
- case "Umbraco.ModelsBuilder.ImplementPropertyTypeAttribute":
- var propertyAliasToIgnore = (string)attrData.ConstructorArguments[0].Value;
- disco.SetIgnoredProperty(classSymbol.Name /*SymbolDisplay.ToDisplayString(classSymbol)*/, propertyAliasToIgnore);
- break;
- }
- }
- }
-
- private static void ParseAssemblySymbols(ParseResult disco, ISymbol symbol)
- {
- foreach (var attrData in symbol.GetAttributes())
- {
- var attrClassSymbol = attrData.AttributeClass;
-
- // handle errors
- if (attrClassSymbol is IErrorTypeSymbol) continue;
- if (attrData.AttributeConstructor == null) continue;
-
- var attrClassName = SymbolDisplay.ToDisplayString(attrClassSymbol);
- switch (attrClassName)
- {
- case "Umbraco.ModelsBuilder.IgnoreContentTypeAttribute":
- var contentAliasToIgnore = (string)attrData.ConstructorArguments[0].Value;
- // see notes in IgnoreContentTypeAttribute
- //var ignoreContent = (bool)attrData.ConstructorArguments[1].Value;
- //var ignoreMixin = (bool)attrData.ConstructorArguments[1].Value;
- //var ignoreMixinProperties = (bool)attrData.ConstructorArguments[1].Value;
- disco.SetIgnoredContent(contentAliasToIgnore /*, ignoreContent, ignoreMixin, ignoreMixinProperties*/);
- break;
-
- case "Umbraco.ModelsBuilder.RenameContentTypeAttribute":
- var contentAliasToRename = (string) attrData.ConstructorArguments[0].Value;
- var contentRenamed = (string)attrData.ConstructorArguments[1].Value;
- disco.SetRenamedContent(contentAliasToRename, contentRenamed, false);
- break;
-
- case "Umbraco.ModelsBuilder.ModelsBaseClassAttribute":
- var modelsBaseClass = (INamedTypeSymbol) attrData.ConstructorArguments[0].Value;
- if (modelsBaseClass is IErrorTypeSymbol)
- throw new Exception($"Invalid base class type \"{modelsBaseClass.Name}\".");
- disco.SetModelsBaseClassName(SymbolDisplay.ToDisplayString(modelsBaseClass));
- break;
-
- case "Umbraco.ModelsBuilder.ModelsNamespaceAttribute":
- var modelsNamespace= (string) attrData.ConstructorArguments[0].Value;
- disco.SetModelsNamespace(modelsNamespace);
- break;
-
- case "Umbraco.ModelsBuilder.ModelsUsingAttribute":
- var usingNamespace = (string)attrData.ConstructorArguments[0].Value;
- disco.SetUsingNamespace(usingNamespace);
- break;
- }
- }
- }
-
- private static void ParseMethodSymbol(ParseResult disco, ISymbol classSymbol, ISymbol symbol)
- {
- var methodSymbol = symbol as IMethodSymbol;
-
- if (methodSymbol == null
- || !methodSymbol.IsStatic
- || methodSymbol.IsGenericMethod
- || methodSymbol.ReturnsVoid
- || methodSymbol.IsExtensionMethod
- || methodSymbol.Parameters.Length != 1)
- return;
-
- var returnType = methodSymbol.ReturnType;
- var paramSymbol = methodSymbol.Parameters[0];
- var paramType = paramSymbol.Type;
-
- // cannot do this because maybe the param type is ISomething and we don't have
- // that type yet - will be generated - so cannot put any condition on it really
- //const string iPublishedContent = "Umbraco.Core.Models.IPublishedContent";
- //var implements = paramType.AllInterfaces.Any(x => x.ToDisplayString() == iPublishedContent);
- //if (!implements)
- // return;
-
- disco.SetStaticMixinMethod(classSymbol.Name, methodSymbol.Name, returnType.Name, paramType.Name);
- }
- }
-}
diff --git a/src/Umbraco.ModelsBuilder/Building/Compiler.cs b/src/Umbraco.ModelsBuilder/Building/Compiler.cs
deleted file mode 100644
index 66064bef0b..0000000000
--- a/src/Umbraco.ModelsBuilder/Building/Compiler.cs
+++ /dev/null
@@ -1,171 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Reflection;
-using System.Web;
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.CSharp;
-using Umbraco.Core.Configuration;
-using Umbraco.ModelsBuilder.Configuration;
-
-namespace Umbraco.ModelsBuilder.Building
-{
- // main Roslyn compiler
- internal class Compiler
- {
- private readonly LanguageVersion _languageVersion;
-
- public Compiler()
- : this(UmbracoConfig.For.ModelsBuilder().LanguageVersion)
- { }
-
- public Compiler(LanguageVersion languageVersion)
- {
- _languageVersion = languageVersion;
- References = ReferencedAssemblies.References;
- Debug = HttpContext.Current != null && HttpContext.Current.IsDebuggingEnabled;
- }
-
- // gets or sets the references
- public IEnumerable References { get; set; }
-
- public bool Debug { get; set; }
-
- // gets a compilation
- public CSharpCompilation GetCompilation(string assemblyName, IDictionary files)
- {
- SyntaxTree[] trees;
- return GetCompilation(assemblyName, files, out trees);
- }
-
- // gets a compilation
- // used by CodeParser to get a "compilation" of the existing files
- public CSharpCompilation GetCompilation(string assemblyName, IDictionary files, out SyntaxTree[] trees)
- {
- var options = new CSharpParseOptions(_languageVersion);
- trees = files.Select(x =>
- {
- var text = x.Value;
- var tree = CSharpSyntaxTree.ParseText(text, /*options:*/ options);
- var diagnostic = tree.GetDiagnostics().FirstOrDefault(y => y.Severity == DiagnosticSeverity.Error);
- if (diagnostic != null)
- ThrowExceptionFromDiagnostic(x.Key, x.Value, diagnostic);
- return tree;
- }).ToArray();
-
- var refs = References;
-
- var compilationOptions = new CSharpCompilationOptions(
- OutputKind.DynamicallyLinkedLibrary,
- assemblyIdentityComparer: DesktopAssemblyIdentityComparer.Default,
- optimizationLevel: Debug ? OptimizationLevel.Debug : OptimizationLevel.Release
- );
- var compilation = CSharpCompilation.Create(
- assemblyName,
- /*syntaxTrees:*/ trees,
- /*references:*/ refs,
- compilationOptions);
-
- return compilation;
- }
-
- // compile files into a Dll
- // used by ModelsBuilderBackOfficeController in [Live]Dll mode, to compile the models to disk
- public void Compile(string assemblyName, IDictionary files, string binPath)
- {
- var assemblyPath = Path.Combine(binPath, assemblyName + ".dll");
- using (var stream = new FileStream(assemblyPath, FileMode.Create))
- {
- Compile(assemblyName, files, stream);
- }
-
- // this is how we'd create the pdb:
- /*
- var pdbPath = Path.Combine(binPath, assemblyName + ".pdb");
-
- // create the compilation
- var compilation = GetCompilation(assemblyName, files);
-
- // check diagnostics for errors (not warnings)
- foreach (var diag in compilation.GetDiagnostics().Where(x => x.Severity == DiagnosticSeverity.Error))
- ThrowExceptionFromDiagnostic(files, diag);
-
- // emit
- var result = compilation.Emit(assemblyPath, pdbPath);
- if (result.Success) return;
-
- // deal with errors
- var diagnostic = result.Diagnostics.First(x => x.Severity == DiagnosticSeverity.Error);
- ThrowExceptionFromDiagnostic(files, diagnostic);
- */
- }
-
- // compile files into an assembly
- public Assembly Compile(string assemblyName, IDictionary files)
- {
- using (var stream = new MemoryStream())
- {
- Compile(assemblyName, files, stream);
- return Assembly.Load(stream.GetBuffer());
- }
- }
-
- // compile one file into an assembly
- public Assembly Compile(string assemblyName, string path, string code)
- {
- using (var stream = new MemoryStream())
- {
- Compile(assemblyName, new Dictionary { { path, code } }, stream);
- return Assembly.Load(stream.GetBuffer());
- }
- }
-
- // compiles files into a stream
- public void Compile(string assemblyName, IDictionary files, Stream stream)
- {
- // create the compilation
- var compilation = GetCompilation(assemblyName, files);
-
- // check diagnostics for errors (not warnings)
- foreach (var diag in compilation.GetDiagnostics().Where(x => x.Severity == DiagnosticSeverity.Error))
- ThrowExceptionFromDiagnostic(files, diag);
-
- // emit
- var result = compilation.Emit(stream);
- if (result.Success) return;
-
- // deal with errors
- var diagnostic = result.Diagnostics.First(x => x.Severity == DiagnosticSeverity.Error);
- ThrowExceptionFromDiagnostic(files, diagnostic);
- }
-
- // compiles one file into a stream
- public void Compile(string assemblyName, string path, string code, Stream stream)
- {
- Compile(assemblyName, new Dictionary { { path, code } }, stream);
- }
-
- private static void ThrowExceptionFromDiagnostic(IDictionary files, Diagnostic diagnostic)
- {
- var message = diagnostic.GetMessage();
- if (diagnostic.Location == Location.None)
- throw new CompilerException(message);
-
- var position = diagnostic.Location.GetLineSpan().StartLinePosition.Line + 1;
- var path = diagnostic.Location.SourceTree.FilePath;
- var code = files.ContainsKey(path) ? files[path] : string.Empty;
- throw new CompilerException(message, path, code, position);
- }
-
- private static void ThrowExceptionFromDiagnostic(string path, string code, Diagnostic diagnostic)
- {
- var message = diagnostic.GetMessage();
- if (diagnostic.Location == Location.None)
- throw new CompilerException(message);
-
- var position = diagnostic.Location.GetLineSpan().StartLinePosition.Line + 1;
- throw new CompilerException(message, path, code, position);
- }
- }
-}
diff --git a/src/Umbraco.ModelsBuilder/Building/CompilerException.cs b/src/Umbraco.ModelsBuilder/Building/CompilerException.cs
deleted file mode 100644
index e978f67ae5..0000000000
--- a/src/Umbraco.ModelsBuilder/Building/CompilerException.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-using System;
-
-namespace Umbraco.ModelsBuilder.Building
-{
- public class CompilerException : Exception
- {
- public CompilerException(string message)
- : base(message)
- { }
-
- public CompilerException(string message, string path, string sourceCode, int line)
- : base(message)
- {
- Path = path;
- SourceCode = sourceCode;
- Line = line;
- }
-
- public string Path { get; } = string.Empty;
-
- public string SourceCode { get; } = string.Empty;
-
- public int Line { get; } = -1;
- }
-}
diff --git a/src/Umbraco.ModelsBuilder/Building/ParseResult.cs b/src/Umbraco.ModelsBuilder/Building/ParseResult.cs
deleted file mode 100644
index d1f61363ff..0000000000
--- a/src/Umbraco.ModelsBuilder/Building/ParseResult.cs
+++ /dev/null
@@ -1,275 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-
-namespace Umbraco.ModelsBuilder.Building
-{
- ///
- /// Contains the result of a code parsing.
- ///
- internal class ParseResult
- {
- // "alias" is the umbraco alias
- // content "name" is the complete name eg Foo.Bar.Name
- // property "name" is just the local name
-
- // see notes in IgnoreContentTypeAttribute
-
- private readonly HashSet _ignoredContent
- = new HashSet(StringComparer.InvariantCultureIgnoreCase);
- //private readonly HashSet _ignoredMixin
- // = new HashSet(StringComparer.InvariantCultureIgnoreCase);
- //private readonly HashSet _ignoredMixinProperties
- // = new HashSet(StringComparer.InvariantCultureIgnoreCase);
- private readonly Dictionary _renamedContent
- = new Dictionary(StringComparer.InvariantCultureIgnoreCase);
- private readonly HashSet _withImplementContent
- = new HashSet(StringComparer.InvariantCultureIgnoreCase);
- private readonly Dictionary> _ignoredProperty
- = new Dictionary>();
- private readonly Dictionary> _renamedProperty
- = new Dictionary>();
- private readonly Dictionary _contentBase
- = new Dictionary();
- private readonly Dictionary _contentInterfaces
- = new Dictionary();
- private readonly List _usingNamespaces
- = new List();
- private readonly Dictionary> _staticMixins
- = new Dictionary>();
- private readonly HashSet _withCtor
- = new HashSet(StringComparer.InvariantCultureIgnoreCase);
-
- public static readonly ParseResult Empty = new ParseResult();
-
- private class StaticMixinMethodInfo
- {
- public StaticMixinMethodInfo(string contentName, string methodName, string returnType, string paramType)
- {
- ContentName = contentName;
- MethodName = methodName;
- //ReturnType = returnType;
- //ParamType = paramType;
- }
-
- // short name eg Type1
- public string ContentName { get; private set; }
-
- // short name eg GetProp1
- public string MethodName { get; private set; }
-
- // those types cannot be FQ because when parsing, some of them
- // might not exist since we're generating them... and so prob.
- // that info is worthless - not using it anyway at the moment...
-
- //public string ReturnType { get; private set; }
- //public string ParamType { get; private set; }
- }
-
- #region Declare
-
- // content with that alias should not be generated
- // alias can end with a * (wildcard)
- public void SetIgnoredContent(string contentAlias /*, bool ignoreContent, bool ignoreMixin, bool ignoreMixinProperties*/)
- {
- //if (ignoreContent)
- _ignoredContent.Add(contentAlias);
- //if (ignoreMixin)
- // _ignoredMixin.Add(contentAlias);
- //if (ignoreMixinProperties)
- // _ignoredMixinProperties.Add(contentAlias);
- }
-
- // content with that alias should be generated with a different name
- public void SetRenamedContent(string contentAlias, string contentName, bool withImplement)
- {
- _renamedContent[contentAlias] = contentName;
- if (withImplement)
- _withImplementContent.Add(contentAlias);
- }
-
- // property with that alias should not be generated
- // applies to content name and any content that implements it
- // here, contentName may be an interface
- // alias can end with a * (wildcard)
- public void SetIgnoredProperty(string contentName, string propertyAlias)
- {
- HashSet ignores;
- if (!_ignoredProperty.TryGetValue(contentName, out ignores))
- ignores = _ignoredProperty[contentName] = new HashSet(StringComparer.InvariantCultureIgnoreCase);
- ignores.Add(propertyAlias);
- }
-
- // property with that alias should be generated with a different name
- // applies to content name and any content that implements it
- // here, contentName may be an interface
- public void SetRenamedProperty(string contentName, string propertyAlias, string propertyName)
- {
- Dictionary renames;
- if (!_renamedProperty.TryGetValue(contentName, out renames))
- renames = _renamedProperty[contentName] = new Dictionary(StringComparer.InvariantCultureIgnoreCase);
- renames[propertyAlias] = propertyName;
- }
-
- // content with that name has a base class so no need to generate one
- public void SetContentBaseClass(string contentName, string baseName)
- {
- if (baseName.ToLowerInvariant() != "object")
- _contentBase[contentName] = baseName;
- }
-
- // content with that name implements the interfaces
- public void SetContentInterfaces(string contentName, IEnumerable interfaceNames)
- {
- _contentInterfaces[contentName] = interfaceNames.ToArray();
- }
-
- public void SetModelsBaseClassName(string modelsBaseClassName)
- {
- ModelsBaseClassName = modelsBaseClassName;
- }
-
- public void SetModelsNamespace(string modelsNamespace)
- {
- ModelsNamespace = modelsNamespace;
- }
-
- public void SetUsingNamespace(string usingNamespace)
- {
- _usingNamespaces.Add(usingNamespace);
- }
-
- public void SetStaticMixinMethod(string contentName, string methodName, string returnType, string paramType)
- {
- if (!_staticMixins.ContainsKey(contentName))
- _staticMixins[contentName] = new List();
-
- _staticMixins[contentName].Add(new StaticMixinMethodInfo(contentName, methodName, returnType, paramType));
- }
-
- public void SetHasCtor(string contentName)
- {
- _withCtor.Add(contentName);
- }
-
- #endregion
-
- #region Query
-
- public bool IsIgnored(string contentAlias)
- {
- return IsContentOrMixinIgnored(contentAlias, _ignoredContent);
- }
-
- //public bool IsMixinIgnored(string contentAlias)
- //{
- // return IsContentOrMixinIgnored(contentAlias, _ignoredMixin);
- //}
-
- //public bool IsMixinPropertiesIgnored(string contentAlias)
- //{
- // return IsContentOrMixinIgnored(contentAlias, _ignoredMixinProperties);
- //}
-
- private static bool IsContentOrMixinIgnored(string contentAlias, HashSet ignored)
- {
- if (ignored.Contains(contentAlias)) return true;
- return ignored
- .Where(x => x.EndsWith("*"))
- .Select(x => x.Substring(0, x.Length - 1))
- .Any(x => contentAlias.StartsWith(x, StringComparison.InvariantCultureIgnoreCase));
- }
-
- public bool HasContentBase(string contentName)
- {
- return _contentBase.ContainsKey(contentName);
- }
-
- public bool IsContentRenamed(string contentAlias)
- {
- return _renamedContent.ContainsKey(contentAlias);
- }
-
- public bool HasContentImplement(string contentAlias)
- {
- return _withImplementContent.Contains(contentAlias);
- }
-
- public string ContentClrName(string contentAlias)
- {
- string name;
- return (_renamedContent.TryGetValue(contentAlias, out name)) ? name : null;
- }
-
- public bool IsPropertyIgnored(string contentName, string propertyAlias)
- {
- HashSet ignores;
- if (_ignoredProperty.TryGetValue(contentName, out ignores))
- {
- if (ignores.Contains(propertyAlias)) return true;
- if (ignores
- .Where(x => x.EndsWith("*"))
- .Select(x => x.Substring(0, x.Length - 1))
- .Any(x => propertyAlias.StartsWith(x, StringComparison.InvariantCultureIgnoreCase)))
- return true;
- }
- string baseName;
- if (_contentBase.TryGetValue(contentName, out baseName)
- && IsPropertyIgnored(baseName, propertyAlias)) return true;
- string[] interfaceNames;
- if (_contentInterfaces.TryGetValue(contentName, out interfaceNames)
- && interfaceNames.Any(interfaceName => IsPropertyIgnored(interfaceName, propertyAlias))) return true;
- return false;
- }
-
- public string PropertyClrName(string contentName, string propertyAlias)
- {
- Dictionary renames;
- string name;
- if (_renamedProperty.TryGetValue(contentName, out renames)
- && renames.TryGetValue(propertyAlias, out name)) return name;
- string baseName;
- if (_contentBase.TryGetValue(contentName, out baseName)
- && null != (name = PropertyClrName(baseName, propertyAlias))) return name;
- string[] interfaceNames;
- if (_contentInterfaces.TryGetValue(contentName, out interfaceNames)
- && null != (name = interfaceNames
- .Select(interfaceName => PropertyClrName(interfaceName, propertyAlias))
- .FirstOrDefault(x => x != null))) return name;
- return null;
- }
-
- public bool HasModelsBaseClassName
- {
- get { return !string.IsNullOrWhiteSpace(ModelsBaseClassName); }
- }
-
- public string ModelsBaseClassName { get; private set; }
-
- public bool HasModelsNamespace
- {
- get { return !string.IsNullOrWhiteSpace(ModelsNamespace); }
- }
-
- public string ModelsNamespace { get; private set; }
-
- public IEnumerable UsingNamespaces
- {
- get { return _usingNamespaces; }
- }
-
- public IEnumerable StaticMixinMethods(string contentName)
- {
- return _staticMixins.ContainsKey(contentName)
- ? _staticMixins[contentName].Select(x => x.MethodName)
- : Enumerable.Empty() ;
- }
-
- public bool HasCtor(string contentName)
- {
- return _withCtor.Contains(contentName);
- }
-
- #endregion
- }
-}
\ No newline at end of file
diff --git a/src/Umbraco.ModelsBuilder/Configuration/ClrNameSource.cs b/src/Umbraco.ModelsBuilder/Configuration/ClrNameSource.cs
deleted file mode 100644
index d195846411..0000000000
--- a/src/Umbraco.ModelsBuilder/Configuration/ClrNameSource.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-namespace Umbraco.ModelsBuilder.Configuration
-{
- ///
- /// Defines the CLR name sources.
- ///
- public enum ClrNameSource
- {
- ///
- /// No source.
- ///
- Nothing = 0,
-
- ///
- /// Use the name as source.
- ///
- Name,
-
- ///
- /// Use the alias as source.
- ///
- Alias,
-
- ///
- /// Use the alias directly.
- ///
- RawAlias
- }
-}
diff --git a/src/Umbraco.ModelsBuilder/Configuration/UmbracoConfigExtensions.cs b/src/Umbraco.ModelsBuilder/Configuration/UmbracoConfigExtensions.cs
deleted file mode 100644
index acc587e779..0000000000
--- a/src/Umbraco.ModelsBuilder/Configuration/UmbracoConfigExtensions.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-using System.Threading;
-using Umbraco.Core.Configuration;
-
-namespace Umbraco.ModelsBuilder.Configuration
-{
- ///
- /// Provides extension methods for the class.
- ///
- public static class UmbracoConfigExtensions
- {
- private static Config _config;
-
- ///
- /// Gets the models builder configuration.
- ///
- /// The umbraco configuration.
- /// The models builder configuration.
- /// 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 UmbracoConfig umbracoConfig)
- {
- // capture the current Config2.Default value, cannot change anymore
- LazyInitializer.EnsureInitialized(ref _config, () => Config.Value);
- return _config;
- }
-
- // internal for tests
- internal static void ResetConfig()
- {
- _config = null;
- }
- }
-}
\ No newline at end of file
diff --git a/src/Umbraco.ModelsBuilder/Dashboard/BuilderDashboardHelper.cs b/src/Umbraco.ModelsBuilder/Dashboard/BuilderDashboardHelper.cs
deleted file mode 100644
index 9e5741805e..0000000000
--- a/src/Umbraco.ModelsBuilder/Dashboard/BuilderDashboardHelper.cs
+++ /dev/null
@@ -1,91 +0,0 @@
-using System;
-using System.Text;
-using Umbraco.Core.Configuration;
-using Umbraco.ModelsBuilder.Configuration;
-using Umbraco.ModelsBuilder.Umbraco;
-
-namespace Umbraco.ModelsBuilder.Dashboard
-{
- internal static class BuilderDashboardHelper
- {
- public static bool CanGenerate()
- {
- return UmbracoConfig.For.ModelsBuilder().ModelsMode.SupportsExplicitGeneration();
- }
-
- public static bool GenerateCausesRestart()
- {
- return UmbracoConfig.For.ModelsBuilder().ModelsMode.IsAnyDll();
- }
-
- public static bool AreModelsOutOfDate()
- {
- return OutOfDateModelsStatus.IsOutOfDate;
- }
-
- public static string LastError()
- {
- return ModelsGenerationError.GetLastError();
- }
-
- public static string Text()
- {
- var config = UmbracoConfig.For.ModelsBuilder();
- if (!config.Enable)
- return "ModelsBuilder is disabled (the .Enable key is missing, or its value is not 'true').";
-
- var sb = new StringBuilder();
-
- 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("
The API is ");
- if (config.ApiInstalled && config.EnableApi)
- {
- sb.Append("installed and enabled");
- if (!config.IsDebug) sb.Append(". However, the API runs only with debug compilation mode");
- }
- else if (config.ApiInstalled || config.EnableApi)
- sb.Append(config.ApiInstalled ? "installed but not enabled" : "enabled but not installed");
- else sb.Append("neither installed nor enabled");
- sb.Append(". ");
- if (!config.ApiServer)
- sb.Append("External tools such as Visual Studio cannot use the API");
- else
- sb.Append("The API endpoint is open on this server");
- sb.Append(".
No models mode is specified: models will not be generated.
");
-
- sb.Append($"
Models namespace is {config.ModelsNamespace}.
");
-
- sb.Append("
Static mixin getters are ");
- sb.Append(config.StaticMixinGetters ? "enabled" : "disabled");
- if (config.StaticMixinGetters)
- {
- sb.Append(". The pattern for getters is ");
- sb.Append(string.IsNullOrWhiteSpace(config.StaticMixinGetterPattern)
- ? "not configured (will use default)"
- : $"\"{config.StaticMixinGetterPattern}\"");
- }
- sb.Append(".
");
-
- 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/EnumerableExtensions.cs b/src/Umbraco.ModelsBuilder/EnumerableExtensions.cs
deleted file mode 100644
index da77bfa958..0000000000
--- a/src/Umbraco.ModelsBuilder/EnumerableExtensions.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-using System;
-using System.Collections.Generic;
-
-namespace Umbraco.ModelsBuilder
-{
- public static class EnumerableExtensions
- {
- public static void RemoveAll(this IList list, Func predicate)
- {
- for (var i = 0; i < list.Count; i++)
- {
- if (predicate(list[i]))
- {
- list.RemoveAt(i--); // i-- is important here!
- }
- }
- }
-
- public static IEnumerable And(this IEnumerable enumerable, T item)
- {
- foreach (var x in enumerable) yield return x;
- yield return item;
- }
-
- public static IEnumerable AndIfNotNull(this IEnumerable enumerable, T item)
- where T : class
- {
- foreach (var x in enumerable) yield return x;
- if (item != null)
- yield return item;
- }
- }
-}
diff --git a/src/Umbraco.ModelsBuilder/IgnoreContentTypeAttribute.cs b/src/Umbraco.ModelsBuilder/IgnoreContentTypeAttribute.cs
deleted file mode 100644
index e5ab3a2e35..0000000000
--- a/src/Umbraco.ModelsBuilder/IgnoreContentTypeAttribute.cs
+++ /dev/null
@@ -1,46 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Security.Policy;
-using System.Text;
-using System.Threading.Tasks;
-using Umbraco.ModelsBuilder;
-
-namespace Umbraco.ModelsBuilder
-{
- // for the time being it's all-or-nothing
- // when the content type is ignored then
- // - no class is generated for that content type
- // - no class is generated for any child of that class
- // - no interface is generated for that content type as a mixin
- // - and it is ignored as a mixin ie its properties are not generated
- // in the future we may way to do
- // [assembly:IgnoreContentType("foo", ContentTypeIgnorable.ContentType|ContentTypeIgnorable.Mixin|ContentTypeIgnorable.MixinProperties)]
- // so that we can
- // - generate a class for that content type, or not
- // - if not generated, generate children or not
- // - if generate children, include properties or not
- // - generate an interface for that content type as a mixin
- // - if not generated, still generate properties in content types implementing the mixin or not
- // but... I'm not even sure it makes sense
- // if we don't want it... we don't want it.
-
- // about ignoring
- // - content (don't generate the content, use as mixin)
- // - mixin (don't generate the interface, use the properties)
- // - mixin properties (generate the interface, not the properties)
- // - mixin: local only or children too...
-
- ///
- /// Indicates that no model should be generated for a specified content type alias.
- ///
- /// When a content type is ignored, its descendants are also ignored.
- /// Supports trailing wildcard eg "foo*".
- [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true, Inherited = false)]
- public sealed class IgnoreContentTypeAttribute : Attribute
- {
- public IgnoreContentTypeAttribute(string alias /*, bool ignoreContent = true, bool ignoreMixin = true, bool ignoreMixinProperties = true*/)
- {}
- }
-}
-
diff --git a/src/Umbraco.ModelsBuilder/IgnorePropertyTypeAttribute.cs b/src/Umbraco.ModelsBuilder/IgnorePropertyTypeAttribute.cs
deleted file mode 100644
index 4dce0f9b7f..0000000000
--- a/src/Umbraco.ModelsBuilder/IgnorePropertyTypeAttribute.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace Umbraco.ModelsBuilder
-{
- ///
- /// Indicates that no model should be generated for a specified property type alias.
- ///
- /// Supports trailing wildcard eg "foo*".
- [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
- public sealed class IgnorePropertyTypeAttribute : Attribute
- {
- public IgnorePropertyTypeAttribute(string alias)
- {}
- }
-}
diff --git a/src/Umbraco.ModelsBuilder/ImplementContentTypeAttribute.cs b/src/Umbraco.ModelsBuilder/ImplementContentTypeAttribute.cs
deleted file mode 100644
index 142f115b07..0000000000
--- a/src/Umbraco.ModelsBuilder/ImplementContentTypeAttribute.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using System;
-
-namespace Umbraco.ModelsBuilder
-{
- // NOTE
- // that attribute should inherit from PublishedModelAttribute
- // so we do not have different syntaxes
- // but... is sealed at the moment.
-
- ///
- /// Indicates that a (partial) class defines the model type for a specific alias.
- ///
- /// Though a model will be generated - so that is the way to register a rename.
- [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
- public sealed class ImplementContentTypeAttribute : Attribute
- {
- public ImplementContentTypeAttribute(string alias)
- { }
- }
-}
diff --git a/src/Umbraco.ModelsBuilder/ModelsBaseClassAttribute.cs b/src/Umbraco.ModelsBuilder/ModelsBaseClassAttribute.cs
deleted file mode 100644
index 3c401b7fdb..0000000000
--- a/src/Umbraco.ModelsBuilder/ModelsBaseClassAttribute.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using System;
-
-namespace Umbraco.ModelsBuilder
-{
- ///
- /// Indicates the default base class for models.
- ///
- /// Otherwise it is PublishedContentModel. Would make sense to inherit from PublishedContentModel.
- [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false, Inherited = false)]
- public sealed class ModelsBaseClassAttribute : Attribute
- {
- public ModelsBaseClassAttribute(Type type)
- {}
- }
-}
-
diff --git a/src/Umbraco.ModelsBuilder/ModelsNamespaceAttribute.cs b/src/Umbraco.ModelsBuilder/ModelsNamespaceAttribute.cs
deleted file mode 100644
index 1b1d62d9bc..0000000000
--- a/src/Umbraco.ModelsBuilder/ModelsNamespaceAttribute.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using System;
-
-namespace Umbraco.ModelsBuilder
-{
- ///
- /// Indicates the models namespace.
- ///
- /// Will override anything else that might come from settings.
- [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false, Inherited = false)]
- public sealed class ModelsNamespaceAttribute : Attribute
- {
- public ModelsNamespaceAttribute(string modelsNamespace)
- {}
- }
-}
-
diff --git a/src/Umbraco.ModelsBuilder/ModelsUsingAttribute.cs b/src/Umbraco.ModelsBuilder/ModelsUsingAttribute.cs
deleted file mode 100644
index 8fe1335631..0000000000
--- a/src/Umbraco.ModelsBuilder/ModelsUsingAttribute.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Security.Policy;
-using System.Text;
-using System.Threading.Tasks;
-using Umbraco.ModelsBuilder;
-
-namespace Umbraco.ModelsBuilder
-{
- ///
- /// Indicates namespaces that should be used in generated models (in using clauses).
- ///
- [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true, Inherited = false)]
- public sealed class ModelsUsingAttribute : Attribute
- {
- public ModelsUsingAttribute(string usingNamespace)
- {}
- }
-}
-
diff --git a/src/Umbraco.ModelsBuilder/Properties/AssemblyInfo.cs b/src/Umbraco.ModelsBuilder/Properties/AssemblyInfo.cs
deleted file mode 100644
index a2f8d1ae1e..0000000000
--- a/src/Umbraco.ModelsBuilder/Properties/AssemblyInfo.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using System.Reflection;
-using System.Runtime.InteropServices;
-
-[assembly: AssemblyTitle("Umbraco.ModelsBuilder")]
-[assembly: AssemblyDescription("Umbraco ModelsBuilder")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyProduct("Umbraco CMS")]
-
-[assembly: ComVisible(false)]
-[assembly: Guid("7020a059-c0d1-43a0-8efd-23591a0c9af6")]
-
-// code analysis
-// IDE1006 is broken, wants _value syntax for consts, etc - and it's even confusing ppl at MS, kill it
-[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "~_~")]
diff --git a/src/Umbraco.ModelsBuilder/PublishedPropertyTypeExtensions.cs b/src/Umbraco.ModelsBuilder/PublishedPropertyTypeExtensions.cs
deleted file mode 100644
index b67ba54432..0000000000
--- a/src/Umbraco.ModelsBuilder/PublishedPropertyTypeExtensions.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using System.Collections.Generic;
-using System.Linq;
-using Umbraco.Core;
-using Umbraco.Core.Models.PublishedContent;
-
-namespace Umbraco.ModelsBuilder
-{
- public static class PublishedPropertyTypeExtensions
- {
- // fixme - need to rewrite that one - we don't have prevalues anymore
- //public static KeyValuePair[] PreValues(this PublishedPropertyType propertyType)
- //{
- // return ApplicationContext.Current.Services.DataTypeService
- // .GetPreValuesCollectionByDataTypeId(propertyType.DataType.Id)
- // .PreValuesAsArray
- // .Select(x => new KeyValuePair(x.Id, x.Value))
- // .ToArray();
- //}
- }
-}
diff --git a/src/Umbraco.ModelsBuilder/PureLiveAssemblyAttribute.cs b/src/Umbraco.ModelsBuilder/PureLiveAssemblyAttribute.cs
deleted file mode 100644
index dfe369dc21..0000000000
--- a/src/Umbraco.ModelsBuilder/PureLiveAssemblyAttribute.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-using System;
-
-namespace Umbraco.ModelsBuilder
-{
- ///
- /// Indicates that an Assembly is a PureLive models assembly.
- ///
- /// Though technically not required, ie models will work without it, the attribute
- /// can be used by Umbraco view models binder to figure out whether the model type comes
- /// from a PureLive Assembly.
- [Obsolete("Should use ModelsBuilderAssemblyAttribute but that requires a change in Umbraco Core.")]
- [AttributeUsage(AttributeTargets.Assembly /*, AllowMultiple = false, Inherited = false*/)]
- public sealed class PureLiveAssemblyAttribute : Attribute
- { }
-}
diff --git a/src/Umbraco.ModelsBuilder/RenameContentTypeAttribute.cs b/src/Umbraco.ModelsBuilder/RenameContentTypeAttribute.cs
deleted file mode 100644
index 0f985e70b3..0000000000
--- a/src/Umbraco.ModelsBuilder/RenameContentTypeAttribute.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace Umbraco.ModelsBuilder
-{
- ///
- /// Indicates a model name for a specified content alias.
- ///
- [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true, Inherited = false)]
- public sealed class RenameContentTypeAttribute : Attribute
- {
- public RenameContentTypeAttribute(string alias, string name)
- {}
- }
-}
diff --git a/src/Umbraco.ModelsBuilder/RenamePropertyTypeAttribute.cs b/src/Umbraco.ModelsBuilder/RenamePropertyTypeAttribute.cs
deleted file mode 100644
index 0d8fd31b63..0000000000
--- a/src/Umbraco.ModelsBuilder/RenamePropertyTypeAttribute.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace Umbraco.ModelsBuilder
-{
- ///
- /// Indicates a model name for a specified property alias.
- ///
- [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
- public sealed class RenamePropertyTypeAttribute : Attribute
- {
- public RenamePropertyTypeAttribute(string alias, string name)
- {}
- }
-}
diff --git a/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProvider.cs b/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProvider.cs
deleted file mode 100644
index b6c37a3558..0000000000
--- a/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProvider.cs
+++ /dev/null
@@ -1,142 +0,0 @@
-using System;
-using System.Threading;
-using System.Web;
-using System.Web.Hosting;
-using Umbraco.Core.Composing;
-using Umbraco.Core.Configuration;
-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;
-
- internal static bool IsEnabled
- {
- get
- {
- var config = UmbracoConfig.For.ModelsBuilder();
- return config.ModelsMode.IsLiveNotPure();
- // we do not manage pure live here
- }
- }
-
- internal static void Install(UmbracoServices umbracoServices)
- {
- // 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);
-
- // anything changes, and we want to re-generate models.
- ContentTypeCacheRefresher.CacheUpdated += RequestModelsGeneration;
- DataTypeCacheRefresher.CacheUpdated += RequestModelsGeneration;
-
- // at the end of a request since we're restarting the pool
- // NOTE - this does NOT trigger - see module below
- //umbracoApplication.EndRequest += GenerateModelsIfRequested;
- }
-
- // NOTE
- // Using HttpContext Items fails because CacheUpdated triggers within
- // some asynchronous backend task where we seem to have no HttpContext.
-
- // So we use a static (non request-bound) var to register that models
- // 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)
- {
- //HttpContext.Current.Items[this] = true;
- Current.Logger.Debug("Requested to generate models.");
- Interlocked.Exchange(ref _req, 1);
- }
-
- public static void GenerateModelsIfRequested(object sender, EventArgs args)
- {
- //if (HttpContext.Current.Items[this] == null) return;
- if (Interlocked.Exchange(ref _req, 0) == 0) return;
-
- // cannot use a simple lock here because we don't want another AppDomain
- // to generate while we do... and there could be 2 AppDomains if the app restarts.
-
- try
- {
- Current.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.");
- GenerateModels();
- ModelsGenerationError.Clear();
- Current.Logger.Info("Generated.");
- }
- catch (TimeoutException)
- {
- Current.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);
- }
- finally
- {
- _mutex.ReleaseMutex(); // release
- }
- }
-
- private static void GenerateModels()
- {
- var modelsDirectory = UmbracoConfig.For.ModelsBuilder().ModelsDirectory;
-
- var bin = HostingEnvironment.MapPath("~/bin");
- if (bin == null)
- throw new Exception("Panic: bin is null.");
-
- var config = UmbracoConfig.For.ModelsBuilder();
-
- // EnableDllModels will recycle the app domain - but this request will end properly
- ModelsBuilderBackOfficeController.GenerateModels(_umbracoServices, modelsDirectory, config.ModelsMode.IsAnyDll() ? bin : null);
- }
- }
-
- // 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()
- {
- if (!LiveModelsProvider.IsEnabled)
- return;
-
- HttpApplication.RegisterModule(typeof(LiveModelsProviderModule));
- }
- }
-}
diff --git a/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderBackOfficeController.cs b/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderBackOfficeController.cs
deleted file mode 100644
index 19c9bda5da..0000000000
--- a/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderBackOfficeController.cs
+++ /dev/null
@@ -1,194 +0,0 @@
-using System;
-using System.IO;
-using System.Linq;
-using System.Net;
-using System.Net.Http;
-using System.Runtime.Serialization;
-using System.Text;
-using System.Web.Hosting;
-using Umbraco.Core.Configuration;
-using Umbraco.ModelsBuilder.Building;
-using Umbraco.ModelsBuilder.Configuration;
-using Umbraco.ModelsBuilder.Dashboard;
-using Umbraco.Web.Editors;
-using Umbraco.Web.WebApi.Filters;
-
-namespace Umbraco.ModelsBuilder.Umbraco
-{
- ///
- /// API controller for use in the Umbraco back office with Angular resources
- ///
- ///
- /// We've created a different controller for the backoffice/angular specifically this is to ensure that the
- /// correct CSRF security is adhered to for angular and it also ensures that this controller is not subseptipal to
- /// global WebApi formatters being changed since this is always forced to only return Angular JSON Specific formats.
- ///
- [UmbracoApplicationAuthorize(Core.Constants.Applications.Developer)]
- public class ModelsBuilderBackOfficeController : UmbracoAuthorizedJsonController
- {
- private readonly UmbracoServices _umbracoServices;
-
- public ModelsBuilderBackOfficeController(UmbracoServices umbracoServices)
- {
- _umbracoServices = umbracoServices;
- }
-
- // invoked by the dashboard
- // requires that the user is logged into the backoffice and has access to the developer section
- // beware! the name of the method appears in modelsbuilder.controller.js
- [System.Web.Http.HttpPost] // use the http one, not mvc, with api controllers!
- public HttpResponseMessage BuildModels()
- {
- try
- {
- if (!UmbracoConfig.For.ModelsBuilder().ModelsMode.SupportsExplicitGeneration())
- {
- var result2 = new BuildResult { Success = false, Message = "Models generation is not enabled." };
- return Request.CreateResponse(HttpStatusCode.OK, result2, Configuration.Formatters.JsonFormatter);
- }
-
- var modelsDirectory = UmbracoConfig.For.ModelsBuilder().ModelsDirectory;
-
- var bin = HostingEnvironment.MapPath("~/bin");
- if (bin == null)
- throw new Exception("Panic: bin is null.");
-
- // EnableDllModels will recycle the app domain - but this request will end properly
- GenerateModels(modelsDirectory, UmbracoConfig.For.ModelsBuilder().ModelsMode.IsAnyDll() ? bin : null);
-
- ModelsGenerationError.Clear();
- }
- catch (Exception e)
- {
- ModelsGenerationError.Report("Failed to build models.", e);
- }
-
- return Request.CreateResponse(HttpStatusCode.OK, GetDashboardResult(), Configuration.Formatters.JsonFormatter);
- }
-
- // invoked by the back-office
- // requires that the user is logged into the backoffice and has access to the developer section
- [System.Web.Http.HttpGet] // use the http one, not mvc, with api controllers!
- public HttpResponseMessage GetModelsOutOfDateStatus()
- {
- var status = OutOfDateModelsStatus.IsEnabled
- ? (OutOfDateModelsStatus.IsOutOfDate
- ? new OutOfDateStatus { Status = OutOfDateType.OutOfDate }
- : new OutOfDateStatus { Status = OutOfDateType.Current })
- : new OutOfDateStatus { Status = OutOfDateType.Unknown };
-
- return Request.CreateResponse(HttpStatusCode.OK, status, Configuration.Formatters.JsonFormatter);
- }
-
- // invoked by the back-office
- // requires that the user is logged into the backoffice and has access to the developer section
- // beware! the name of the method appears in modelsbuilder.controller.js
- [System.Web.Http.HttpGet] // use the http one, not mvc, with api controllers!
- public HttpResponseMessage GetDashboard()
- {
- return Request.CreateResponse(HttpStatusCode.OK, GetDashboardResult(), Configuration.Formatters.JsonFormatter);
- }
-
- private Dashboard GetDashboardResult()
- {
- return new Dashboard
- {
- Enable = UmbracoConfig.For.ModelsBuilder().Enable,
- Text = BuilderDashboardHelper.Text(),
- CanGenerate = BuilderDashboardHelper.CanGenerate(),
- GenerateCausesRestart = BuilderDashboardHelper.GenerateCausesRestart(),
- OutOfDateModels = BuilderDashboardHelper.AreModelsOutOfDate(),
- LastError = BuilderDashboardHelper.LastError(),
- };
- }
-
- private void GenerateModels(string modelsDirectory, string bin)
- {
- GenerateModels(_umbracoServices, modelsDirectory, bin);
- }
-
- internal static void GenerateModels(UmbracoServices umbracoServices, string modelsDirectory, string bin)
- {
- if (!Directory.Exists(modelsDirectory))
- Directory.CreateDirectory(modelsDirectory);
-
- foreach (var file in Directory.GetFiles(modelsDirectory, "*.generated.cs"))
- File.Delete(file);
-
- var typeModels = umbracoServices.GetAllTypes();
-
- var ourFiles = Directory.GetFiles(modelsDirectory, "*.cs").ToDictionary(x => x, File.ReadAllText);
- var parseResult = new CodeParser().ParseWithReferencedAssemblies(ourFiles);
- var builder = new TextBuilder(typeModels, parseResult, UmbracoConfig.For.ModelsBuilder().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}"")]
-";
- */
-
- if (bin != null)
- {
- foreach (var file in Directory.GetFiles(modelsDirectory, "*.generated.cs"))
- ourFiles[file] = File.ReadAllText(file);
- var compiler = new Compiler();
- compiler.Compile(builder.GetModelsNamespace(), ourFiles, bin);
- }
-
- OutOfDateModelsStatus.Clear();
- }
-
- [DataContract]
- internal class BuildResult
- {
- [DataMember(Name = "success")]
- public bool Success;
- [DataMember(Name = "message")]
- public string Message;
- }
-
- [DataContract]
- internal class Dashboard
- {
- [DataMember(Name = "enable")]
- public bool Enable;
- [DataMember(Name = "text")]
- public string Text;
- [DataMember(Name = "canGenerate")]
- public bool CanGenerate;
- [DataMember(Name = "generateCausesRestart")]
- public bool GenerateCausesRestart;
- [DataMember(Name = "outOfDateModels")]
- public bool OutOfDateModels;
- [DataMember(Name = "lastError")]
- public string LastError;
- }
-
- internal enum OutOfDateType
- {
- OutOfDate,
- Current,
- Unknown = 100
- }
-
- [DataContract]
- internal class OutOfDateStatus
- {
- [DataMember(Name = "status")]
- public OutOfDateType Status { get; set; }
- }
- }
-}
diff --git a/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderComponent.cs b/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderComponent.cs
deleted file mode 100644
index a581319ba5..0000000000
--- a/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderComponent.cs
+++ /dev/null
@@ -1,178 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Web;
-using System.Web.Mvc;
-using System.Web.Routing;
-using LightInject;
-using Umbraco.Core;
-using Umbraco.Core.Components;
-using Umbraco.Core.Composing;
-using Umbraco.Core.Configuration;
-using Umbraco.Core.IO;
-using Umbraco.Core.Models.PublishedContent;
-using Umbraco.Core.Services;
-using Umbraco.Core.Services.Implement;
-using Umbraco.ModelsBuilder.Api;
-using Umbraco.ModelsBuilder.Configuration;
-using Umbraco.Web;
-using Umbraco.Web.PublishedCache.NuCache;
-using Umbraco.Web.UI.JavaScript;
-
-namespace Umbraco.ModelsBuilder.Umbraco
-{
- [RequiredComponent(typeof(NuCacheComponent))]
- [RuntimeLevel(MinLevel = RuntimeLevel.Run)]
- public class ModelsBuilderComponent : UmbracoComponentBase, IUmbracoCoreComponent
- {
- public override void Compose(Composition composition)
- {
- base.Compose(composition);
-
- composition.Container.Register(new PerContainerLifetime());
-
- var config = UmbracoConfig.For.ModelsBuilder();
-
- if (config.ModelsMode == ModelsMode.PureLive)
- ComposeForLiveModels(composition.Container);
- else if (config.EnableFactory)
- ComposeForDefaultModelsFactory(composition.Container);
-
- // always setup the dashboard
- InstallServerVars(composition.Container.GetInstance().Level);
- composition.Container.Register(typeof(ModelsBuilderBackOfficeController), new PerRequestLifeTime());
-
- // setup the API if enabled (and in debug mode)
- if (config.ApiServer)
- composition.Container.Register(typeof(ModelsBuilderApiController), new PerRequestLifeTime());
- }
-
- public void Initialize(UmbracoServices umbracoServices)
- {
- var config = UmbracoConfig.For.ModelsBuilder();
-
- if (config.Enable)
- FileService.SavingTemplate += FileService_SavingTemplate;
-
- // fixme LiveModelsProvider should not be static
- if (config.ModelsMode.IsLiveNotPure())
- LiveModelsProvider.Install(umbracoServices);
-
- // fixme OutOfDateModelsStatus should not be static
- if (config.FlagOutOfDateModels)
- OutOfDateModelsStatus.Install();
- }
-
- private void ComposeForDefaultModelsFactory(IServiceContainer container)
- {
- container.RegisterSingleton(factory
- => new PublishedModelFactory(factory.GetInstance().GetTypes()));
- }
-
- private void ComposeForLiveModels(IServiceContainer container)
- {
- container.RegisterSingleton();
-
- // the following would add @using statement in every view so user's don't
- // have to do it - however, then noone understands where the @using statement
- // comes from, and it cannot be avoided / removed --- DISABLED
- //
- /*
- // no need for @using in views
- // note:
- // we are NOT using the in-code attribute here, config is required
- // because that would require parsing the code... and what if it changes?
- // we can AddGlobalImport not sure we can remove one anyways
- var modelsNamespace = Configuration.Config.ModelsNamespace;
- if (string.IsNullOrWhiteSpace(modelsNamespace))
- modelsNamespace = Configuration.Config.DefaultModelsNamespace;
- System.Web.WebPages.Razor.WebPageRazorHost.AddGlobalImport(modelsNamespace);
- */
- }
-
- private void InstallServerVars(RuntimeLevel level)
- {
- // register our url - for the backoffice api
- ServerVariablesParser.Parsing += (sender, serverVars) =>
- {
- if (!serverVars.ContainsKey("umbracoUrls"))
- throw new Exception("Missing umbracoUrls.");
- var umbracoUrlsObject = serverVars["umbracoUrls"];
- if (umbracoUrlsObject == null)
- throw new Exception("Null umbracoUrls");
- if (!(umbracoUrlsObject is Dictionary umbracoUrls))
- throw new Exception("Invalid umbracoUrls");
-
- if (!serverVars.ContainsKey("umbracoPlugins"))
- throw new Exception("Missing umbracoPlugins.");
- if (!(serverVars["umbracoPlugins"] is Dictionary umbracoPlugins))
- throw new Exception("Invalid umbracoPlugins");
-
- if (HttpContext.Current == null) throw new InvalidOperationException("HttpContext is null");
- var urlHelper = new UrlHelper(new RequestContext(new HttpContextWrapper(HttpContext.Current), new RouteData()));
-
- umbracoUrls["modelsBuilderBaseUrl"] = urlHelper.GetUmbracoApiServiceBaseUrl(controller => controller.BuildModels());
- umbracoPlugins["modelsBuilder"] = GetModelsBuilderSettings(level);
- };
- }
-
- private Dictionary GetModelsBuilderSettings(RuntimeLevel level)
- {
- if (level != RuntimeLevel.Run)
- return null;
-
- var settings = new Dictionary
- {
- {"enabled", UmbracoConfig.For.ModelsBuilder().Enable}
- };
-
- return settings;
- }
-
- ///
- /// Used to check if a template is being created based on a document type, in this case we need to
- /// ensure the template markup is correct based on the model name of the document type
- ///
- ///
- ///
- private void FileService_SavingTemplate(IFileService sender, Core.Events.SaveEventArgs e)
- {
- // don't do anything if the factory is not enabled
- // because, no factory = no models (even if generation is enabled)
- if (!UmbracoConfig.For.ModelsBuilder().EnableFactory) return;
-
- // don't do anything if this special key is not found
- if (!e.AdditionalData.ContainsKey("CreateTemplateForContentType")) return;
-
- // ensure we have the content type alias
- if (!e.AdditionalData.ContainsKey("ContentTypeAlias"))
- 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))
- {
- // ensure is safe and always pascal cased, per razor standard
- // + this is how we get the default model name in Umbraco.ModelsBuilder.Umbraco.Application
- var alias = e.AdditionalData["ContentTypeAlias"].ToString();
- var name = template.Name; // will be the name of the content type since we are creating
- var className = UmbracoServices.GetClrName(name, alias);
-
- var modelNamespace = UmbracoConfig.For.ModelsBuilder().ModelsNamespace;
-
- // we do not support configuring this at the moment, so just let Umbraco use its default value
- //var modelNamespaceAlias = ...;
-
- var markup = ViewHelper.GetDefaultFileContent(
- modelClassName: className,
- modelNamespace: modelNamespace/*,
- modelNamespaceAlias: modelNamespaceAlias*/);
-
- //set the template content to the new markup
- template.Content = markup;
- }
- }
- }
- }
-}
diff --git a/src/Umbraco.ModelsBuilder/Validation/ContentTypeModelValidator.cs b/src/Umbraco.ModelsBuilder/Validation/ContentTypeModelValidator.cs
deleted file mode 100644
index 20f5e94b64..0000000000
--- a/src/Umbraco.ModelsBuilder/Validation/ContentTypeModelValidator.cs
+++ /dev/null
@@ -1,95 +0,0 @@
-using System.Collections.Generic;
-using System.ComponentModel.DataAnnotations;
-using System.Linq;
-using Umbraco.Core;
-using Umbraco.Core.Configuration;
-using Umbraco.Core.Models;
-using Umbraco.Core.Models.PublishedContent;
-using Umbraco.ModelsBuilder.Configuration;
-using Umbraco.Web.Editors;
-using Umbraco.Web.Models.ContentEditing;
-
-namespace Umbraco.ModelsBuilder.Validation
-{
- ///
- /// Used to validate the aliases for the content type when MB is enabled to ensure that
- /// no illegal aliases are used
- ///
- internal class ContentTypeModelValidator : ContentTypeModelValidatorBase
- {
- }
-
- ///
- /// Used to validate the aliases for the content type when MB is enabled to ensure that
- /// no illegal aliases are used
- ///
- internal class MediaTypeModelValidator : ContentTypeModelValidatorBase
- {
- }
-
- ///
- /// Used to validate the aliases for the content type when MB is enabled to ensure that
- /// no illegal aliases are used
- ///
- internal class MemberTypeModelValidator : ContentTypeModelValidatorBase
- {
- }
-
- internal abstract class ContentTypeModelValidatorBase : EditorValidator
- where TModel: ContentTypeSave
- where TProperty: PropertyTypeBasic
- {
- protected override IEnumerable Validate(TModel model)
- {
- //don't do anything if we're not enabled
- if (UmbracoConfig.For.ModelsBuilder().Enable)
- {
- var properties = model.Groups.SelectMany(x => x.Properties)
- .Where(x => x.Inherited == false)
- .ToArray();
-
- foreach (var prop in properties)
- {
- var propertyGroup = model.Groups.Single(x => x.Properties.Contains(prop));
-
- if (model.Alias.ToLowerInvariant() == prop.Alias.ToLowerInvariant())
- yield return new ValidationResult(string.Format("With Models Builder enabled, you can't have a property with a the alias \"{0}\" when the content type alias is also \"{0}\".", prop.Alias), new[]
- {
- string.Format("Groups[{0}].Properties[{1}].Alias", model.Groups.IndexOf(propertyGroup), propertyGroup.Properties.IndexOf(prop))
- });
-
- //we need to return the field name with an index so it's wired up correctly
- var groupIndex = model.Groups.IndexOf(propertyGroup);
- var propertyIndex = propertyGroup.Properties.IndexOf(prop);
-
- var validationResult = ValidateProperty(prop, groupIndex, propertyIndex);
- if (validationResult != null)
- {
- yield return validationResult;
- }
- }
- }
- }
-
- private ValidationResult ValidateProperty(PropertyTypeBasic property, int groupIndex, int propertyIndex)
- {
- //don't let them match any properties or methods in IPublishedContent
- //TODO: There are probably more!
- var reservedProperties = typeof(IPublishedContent).GetProperties().Select(x => x.Name).ToArray();
- var reservedMethods = typeof(IPublishedContent).GetMethods().Select(x => x.Name).ToArray();
-
- var alias = property.Alias;
-
- if (reservedProperties.InvariantContains(alias) || reservedMethods.InvariantContains(alias))
- {
- return new ValidationResult(
- string.Format("The alias {0} is a reserved term and cannot be used", alias), new[]
- {
- string.Format("Groups[{0}].Properties[{1}].Alias", groupIndex, propertyIndex)
- });
- }
-
- return null;
- }
- }
-}
diff --git a/src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs b/src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs
new file mode 100644
index 0000000000..e1c3ecc891
--- /dev/null
+++ b/src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs
@@ -0,0 +1,429 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Moq;
+using NUnit.Framework;
+using Umbraco.Core.Models.PublishedContent;
+using Umbraco.ModelsBuilder.Embedded;
+using Umbraco.ModelsBuilder.Embedded.Building;
+using Umbraco.ModelsBuilder.Embedded.Configuration;
+
+namespace Umbraco.Tests.ModelsBuilder
+{
+ [TestFixture]
+ public class BuilderTests
+ {
+
+ [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.Embedded;
+
+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.Embedded;
+
+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(global::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.Tests.ModelsBuilder.BuilderTestsClass1", typeof(BuilderTestsClass1))]
+ [TestCase("global::Umbraco.Tests.ModelsBuilder.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.Tests.ModelsBuilder.BuilderTestsClass1", typeof(BuilderTestsClass1))]
+ [TestCase("global::Umbraco.Tests.ModelsBuilder.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.Tests.ModelsBuilder");
+ 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.Tests.ModelsBuilder.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.Tests.ModelsBuilder.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.Tests.ModelsBuilder");
+ builder.ModelsNamespaceForTests = "SomeRandomNamespace";
+ var sb = new StringBuilder();
+ builder.WriteClrType(sb, typeof(global::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.Tests.ModelsBuilder");
+ builder.ModelsNamespaceForTests = "SomeBorkedNamespace";
+ var sb = new StringBuilder();
+ builder.WriteClrType(sb, typeof(global::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.Tests.ModelsBuilder");
+ 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.Tests.ModelsBuilder.ASCIIEncoding", sb.ToString());
+ }
+
+ [Test]
+ public void WriteClrType_AmbiguousNot()
+ {
+ var builder = new TextBuilder();
+ builder.Using.Add("System.Text");
+ builder.Using.Add("Umbraco.Tests.ModelsBuilder");
+ builder.ModelsNamespaceForTests = "Umbraco.Tests.ModelsBuilder.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.Tests.ModelsBuilder.ASCIIEncoding", sb.ToString());
+ }
+
+ [Test]
+ public void WriteClrType_AmbiguousWithNested()
+ {
+ var builder = new TextBuilder();
+ builder.Using.Add("System.Text");
+ builder.Using.Add("Umbraco.Tests.ModelsBuilder");
+ 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.Tests.ModelsBuilder.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 {}
+
+ 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..5e122ad0fa
--- /dev/null
+++ b/src/Umbraco.Tests/ModelsBuilder/ConfigTests.cs
@@ -0,0 +1,49 @@
+using System.Configuration;
+using NUnit.Framework;
+using Umbraco.ModelsBuilder.Embedded.Configuration;
+
+namespace Umbraco.Tests.ModelsBuilder
+{
+ [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..361d104911
--- /dev/null
+++ b/src/Umbraco.Tests/ModelsBuilder/StringExtensions.cs
@@ -0,0 +1,10 @@
+namespace Umbraco.Tests.ModelsBuilder
+{
+ 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..4d2ae0e6c6
--- /dev/null
+++ b/src/Umbraco.Tests/ModelsBuilder/UmbracoApplicationTests.cs
@@ -0,0 +1,60 @@
+using System;
+using System.Collections.Generic;
+using NUnit.Framework;
+using Umbraco.ModelsBuilder.Embedded;
+using Umbraco.ModelsBuilder.Embedded.Building;
+
+namespace Umbraco.Tests.ModelsBuilder
+{
+ [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 2f81623309..06f41b21c6 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.Embedded
+ {651E1350-91B6-44B7-BD60-7207006D7003}Umbraco.Web
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbautoresize.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbautoresize.directive.js
index 56dfb6b180..69ec1be805 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbautoresize.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbautoresize.directive.js
@@ -138,7 +138,11 @@ angular.module("umbraco.directives")
var unbindModelWatcher = scope.$watch(function() {
return ngModelController.$modelValue;
}, function(newValue) {
- update(true);
+ $timeout(
+ function() {
+ update(true);
+ }
+ );
});
scope.$on('$destroy', function() {
diff --git a/src/Umbraco.Web.UI/App_Plugins/ModelsBuilder/modelsbuilder.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/modelsbuildermanagement.resource.js
similarity index 80%
rename from src/Umbraco.Web.UI/App_Plugins/ModelsBuilder/modelsbuilder.resource.js
rename to src/Umbraco.Web.UI.Client/src/common/resources/modelsbuildermanagement.resource.js
index 58ca77cbdb..ee3cd80c71 100644
--- a/src/Umbraco.Web.UI/App_Plugins/ModelsBuilder/modelsbuilder.resource.js
+++ b/src/Umbraco.Web.UI.Client/src/common/resources/modelsbuildermanagement.resource.js
@@ -1,4 +1,4 @@
-function modelsBuilderResource($q, $http, umbRequestHelper) {
+function modelsBuilderManagementResource($q, $http, umbRequestHelper) {
return {
getModelsOutOfDateStatus: function () {
@@ -20,4 +20,4 @@
}
};
}
-angular.module("umbraco.resources").factory("modelsBuilderResource", modelsBuilderResource);
+angular.module("umbraco.resources").factory("modelsBuilderManagementResource", modelsBuilderManagementResource);
diff --git a/src/Umbraco.Web.UI.Client/src/common/services/contenttypehelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/contenttypehelper.service.js
index 5c3e6eb4c8..305e4a694d 100644
--- a/src/Umbraco.Web.UI.Client/src/common/services/contenttypehelper.service.js
+++ b/src/Umbraco.Web.UI.Client/src/common/services/contenttypehelper.service.js
@@ -27,7 +27,7 @@ function contentTypeHelper(contentTypeResource, dataTypeResource, $filter, $inje
generateModels: function () {
var deferred = $q.defer();
- var modelsResource = $injector.has("modelsBuilderResource") ? $injector.get("modelsBuilderResource") : null;
+ var modelsResource = $injector.has("modelsBuilderManagementResource") ? $injector.get("modelsBuilderManagementResource") : null;
var modelsBuilderEnabled = Umbraco.Sys.ServerVariables.umbracoPlugins.modelsBuilder.enabled;
if (modelsBuilderEnabled && modelsResource) {
modelsResource.buildModels().then(function(result) {
@@ -48,7 +48,7 @@ function contentTypeHelper(contentTypeResource, dataTypeResource, $filter, $inje
checkModelsBuilderStatus: function () {
var deferred = $q.defer();
- var modelsResource = $injector.has("modelsBuilderResource") ? $injector.get("modelsBuilderResource") : null;
+ var modelsResource = $injector.has("modelsBuilderManagementResource") ? $injector.get("modelsBuilderManagementResource") : null;
var modelsBuilderEnabled = (Umbraco && Umbraco.Sys && Umbraco.Sys.ServerVariables && Umbraco.Sys.ServerVariables.umbracoPlugins && Umbraco.Sys.ServerVariables.umbracoPlugins.modelsBuilder && Umbraco.Sys.ServerVariables.umbracoPlugins.modelsBuilder.enabled === true);
if (modelsBuilderEnabled && modelsResource) {
diff --git a/src/Umbraco.Web.UI.Client/src/less/main.less b/src/Umbraco.Web.UI.Client/src/less/main.less
index 86a1acbeae..0d646d11c6 100644
--- a/src/Umbraco.Web.UI.Client/src/less/main.less
+++ b/src/Umbraco.Web.UI.Client/src/less/main.less
@@ -183,6 +183,17 @@ h5.-black {
.umb-control-group .umb-el-wrap {
padding: 0;
}
+.umb-control-group .control-header {
+
+ .control-label {
+ float: left;
+ }
+
+ .control-description {
+ display: block;
+ clear: both;
+ }
+}
.form-horizontal .umb-control-group .control-header {
float: left;
width: 160px;
@@ -190,15 +201,12 @@ h5.-black {
text-align: left;
.control-label {
- float: left;
width: auto;
padding-top: 0;
text-align: left;
}
.control-description {
- display: block;
- clear: both;
max-width:480px;// avoiding description becoming too wide when its placed on top of property.
margin-bottom: 10px;
}
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html b/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html
index 46660fc685..c2f9ceebc4 100644
--- a/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html
+++ b/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html
@@ -6,12 +6,12 @@