From 355a5e0eb490173a10c72681bbbbe2cf565bdaa2 Mon Sep 17 00:00:00 2001 From: Stephan Date: Mon, 24 Jun 2019 10:32:52 +0200 Subject: [PATCH 01/49] Remove ModelsBuilder NuGet dependency, add project --- .../Api/ApiBasicAuthFilter.cs | 86 --- src/Umbraco.ModelsBuilder/Api/ApiClient.cs | 159 ----- src/Umbraco.ModelsBuilder/Api/ApiHelper.cs | 27 - src/Umbraco.ModelsBuilder/Api/ApiVersion.cs | 88 --- .../Api/GetModelsData.cs | 17 - .../Api/ModelsBuilderApiController.cs | 85 --- src/Umbraco.ModelsBuilder/Api/TokenData.cs | 17 - .../Api/ValidateClientVersionData.cs | 57 -- src/Umbraco.ModelsBuilder/Building/Builder.cs | 322 ---------- .../Building/CodeDomBuilder.cs | 113 ---- .../Building/CodeParser.cs | 238 ------- .../Building/Compiler.cs | 171 ----- .../Building/CompilerException.cs | 25 - .../Building/ParseResult.cs | 275 -------- .../Building/PropertyModel.cs | 66 -- .../Building/TextBuilder.cs | 554 ---------------- .../Building/TextHeaderWriter.cs | 26 - .../Building/TypeModel.cs | 208 ------ .../Configuration/ClrNameSource.cs | 28 - .../Configuration/Config.cs | 357 ----------- .../Configuration/ModelsMode.cs | 52 -- .../Configuration/ModelsModeExtensions.cs | 51 -- .../Configuration/UmbracoConfigExtensions.cs | 34 - .../Dashboard/BuilderDashboardHelper.cs | 91 --- .../EnumerableExtensions.cs | 33 - .../IgnoreContentTypeAttribute.cs | 46 -- .../IgnorePropertyTypeAttribute.cs | 19 - .../ImplementContentTypeAttribute.cs | 20 - .../ImplementPropertyTypeAttribute.cs | 23 - .../ModelsBaseClassAttribute.cs | 16 - .../ModelsBuilderAssemblyAttribute.cs | 23 - .../ModelsNamespaceAttribute.cs | 16 - .../ModelsUsingAttribute.cs | 21 - .../Properties/AssemblyInfo.cs | 34 +- .../PublishedElementExtensions.cs | 48 -- .../PublishedPropertyTypeExtensions.cs | 20 - .../PureLiveAssemblyAttribute.cs | 15 - .../ReferencedAssemblies.cs | 137 ---- .../RenameContentTypeAttribute.cs | 18 - .../RenamePropertyTypeAttribute.cs | 18 - src/Umbraco.ModelsBuilder/TypeExtensions.cs | 22 - .../Umbraco.ModelsBuilder.csproj | 75 +-- .../Umbraco/HashCombiner.cs | 38 -- .../Umbraco/HashHelper.cs | 45 -- .../Umbraco/LiveModelsProvider.cs | 142 ----- .../ModelsBuilderBackOfficeController.cs | 194 ------ .../Umbraco/ModelsBuilderComponent.cs | 178 ------ .../Umbraco/ModelsGenerationError.cs | 60 -- .../Umbraco/OutOfDateModelsStatus.cs | 58 -- .../Umbraco/PublishedModelUtility.cs | 67 -- .../Umbraco/PureLiveModelFactory.cs | 602 ------------------ .../Umbraco/UmbracoServices.cs | 236 ------- .../Validation/ContentTypeModelValidator.cs | 95 --- src/umbraco.sln | 10 +- 54 files changed, 38 insertions(+), 5438 deletions(-) delete mode 100644 src/Umbraco.ModelsBuilder/Api/ApiBasicAuthFilter.cs delete mode 100644 src/Umbraco.ModelsBuilder/Api/ApiClient.cs delete mode 100644 src/Umbraco.ModelsBuilder/Api/ApiHelper.cs delete mode 100644 src/Umbraco.ModelsBuilder/Api/ApiVersion.cs delete mode 100644 src/Umbraco.ModelsBuilder/Api/GetModelsData.cs delete mode 100644 src/Umbraco.ModelsBuilder/Api/ModelsBuilderApiController.cs delete mode 100644 src/Umbraco.ModelsBuilder/Api/TokenData.cs delete mode 100644 src/Umbraco.ModelsBuilder/Api/ValidateClientVersionData.cs delete mode 100644 src/Umbraco.ModelsBuilder/Building/Builder.cs delete mode 100644 src/Umbraco.ModelsBuilder/Building/CodeDomBuilder.cs delete mode 100644 src/Umbraco.ModelsBuilder/Building/CodeParser.cs delete mode 100644 src/Umbraco.ModelsBuilder/Building/Compiler.cs delete mode 100644 src/Umbraco.ModelsBuilder/Building/CompilerException.cs delete mode 100644 src/Umbraco.ModelsBuilder/Building/ParseResult.cs delete mode 100644 src/Umbraco.ModelsBuilder/Building/PropertyModel.cs delete mode 100644 src/Umbraco.ModelsBuilder/Building/TextBuilder.cs delete mode 100644 src/Umbraco.ModelsBuilder/Building/TextHeaderWriter.cs delete mode 100644 src/Umbraco.ModelsBuilder/Building/TypeModel.cs delete mode 100644 src/Umbraco.ModelsBuilder/Configuration/ClrNameSource.cs delete mode 100644 src/Umbraco.ModelsBuilder/Configuration/Config.cs delete mode 100644 src/Umbraco.ModelsBuilder/Configuration/ModelsMode.cs delete mode 100644 src/Umbraco.ModelsBuilder/Configuration/ModelsModeExtensions.cs delete mode 100644 src/Umbraco.ModelsBuilder/Configuration/UmbracoConfigExtensions.cs delete mode 100644 src/Umbraco.ModelsBuilder/Dashboard/BuilderDashboardHelper.cs delete mode 100644 src/Umbraco.ModelsBuilder/EnumerableExtensions.cs delete mode 100644 src/Umbraco.ModelsBuilder/IgnoreContentTypeAttribute.cs delete mode 100644 src/Umbraco.ModelsBuilder/IgnorePropertyTypeAttribute.cs delete mode 100644 src/Umbraco.ModelsBuilder/ImplementContentTypeAttribute.cs delete mode 100644 src/Umbraco.ModelsBuilder/ImplementPropertyTypeAttribute.cs delete mode 100644 src/Umbraco.ModelsBuilder/ModelsBaseClassAttribute.cs delete mode 100644 src/Umbraco.ModelsBuilder/ModelsBuilderAssemblyAttribute.cs delete mode 100644 src/Umbraco.ModelsBuilder/ModelsNamespaceAttribute.cs delete mode 100644 src/Umbraco.ModelsBuilder/ModelsUsingAttribute.cs delete mode 100644 src/Umbraco.ModelsBuilder/PublishedElementExtensions.cs delete mode 100644 src/Umbraco.ModelsBuilder/PublishedPropertyTypeExtensions.cs delete mode 100644 src/Umbraco.ModelsBuilder/PureLiveAssemblyAttribute.cs delete mode 100644 src/Umbraco.ModelsBuilder/ReferencedAssemblies.cs delete mode 100644 src/Umbraco.ModelsBuilder/RenameContentTypeAttribute.cs delete mode 100644 src/Umbraco.ModelsBuilder/RenamePropertyTypeAttribute.cs delete mode 100644 src/Umbraco.ModelsBuilder/TypeExtensions.cs delete mode 100644 src/Umbraco.ModelsBuilder/Umbraco/HashCombiner.cs delete mode 100644 src/Umbraco.ModelsBuilder/Umbraco/HashHelper.cs delete mode 100644 src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProvider.cs delete mode 100644 src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderBackOfficeController.cs delete mode 100644 src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderComponent.cs delete mode 100644 src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerationError.cs delete mode 100644 src/Umbraco.ModelsBuilder/Umbraco/OutOfDateModelsStatus.cs delete mode 100644 src/Umbraco.ModelsBuilder/Umbraco/PublishedModelUtility.cs delete mode 100644 src/Umbraco.ModelsBuilder/Umbraco/PureLiveModelFactory.cs delete mode 100644 src/Umbraco.ModelsBuilder/Umbraco/UmbracoServices.cs delete mode 100644 src/Umbraco.ModelsBuilder/Validation/ContentTypeModelValidator.cs 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/Builder.cs b/src/Umbraco.ModelsBuilder/Building/Builder.cs deleted file mode 100644 index acfa402355..0000000000 --- a/src/Umbraco.ModelsBuilder/Building/Builder.cs +++ /dev/null @@ -1,322 +0,0 @@ -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; - -namespace Umbraco.ModelsBuilder.Building -{ - // NOTE - // The idea was to have different types of builder, because I wanted to experiment with - // building code with CodeDom. Turns out more complicated than I thought and maybe not - // worth it at the moment, to we're using TextBuilder and its Generate method is specific. - // - // Keeping the code as-is for the time being... - - /// - /// Provides a base class for all builders. - /// - 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 - { - "System", - "System.Collections.Generic", - "System.Linq.Expressions", - "System.Web", - "Umbraco.Core.Models", - "Umbraco.Core.Models.PublishedContent", - "Umbraco.Web", - "Umbraco.ModelsBuilder", - "Umbraco.ModelsBuilder.Umbraco", - }; - - /// - /// Gets or sets a value indicating the namespace to use for the models. - /// - /// May be overriden by code attributes. - public string ModelsNamespace { get; set; } - - /// - /// Gets the list of assemblies to add to the set of 'using' assemblies in each model file. - /// - public IList Using => TypesUsing; - - /// - /// Gets the list of models to generate. - /// - /// The models to generate, ie those that are not ignored. - public IEnumerable GetModelsToGenerate() - { - return _typeModels.Where(x => !x.IsContentIgnored); - } - - /// - /// Gets the list of all models. - /// - /// 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) - { - // can be null or empty, we'll manage - ModelsNamespace = modelsNamespace; - } - - // for unit tests only - protected Builder() - { } - - /// - /// 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; - - // 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; - } - - // for the first two of these two tests, - // always throw, even in purelive: cannot happen unless ppl start fidling with attributes to rename - // things, and then they should pay attention to the generation error log - there's no magic here - // 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)) - 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)) - 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 xx in typeModel.Properties.Where(x => !x.IsIgnored && x.ClrName == typeModel.ClrName)) - { - if (!pureLive) - throw new InvalidOperationException($"The model class for content type with alias \"{typeModel.Alias}\" is named \"{xx.ClrName}\"." - + $" CSharp does not support using the same name for the property with alias \"{xx.Alias}\"." - + " Consider using an attribute to assign a different name to the property."); - - // for purelive, will we generate a commented out properties with an error message, - // instead of throwing, because then it kills the sites and ppl don't understand why - xx.AddError($"The class {typeModel.ClrName} cannot implement this property, because" - + $" CSharp does not support naming the property with alias \"{xx.Alias}\" with the same name as content type with alias \"{typeModel.Alias}\"." - + " Consider using an attribute to assign a different name to the property."); - - // will not be implemented on interface nor class - // note: we will still create the static getter, and implement the property on other classes... - } - } - - // ensure we have no collision between base types - // NO: we may want to define a base class in a partial, on a model that has a parent - // we are NOT checking that the defined base type does maintain the inheritance chain - //foreach (var xx in _typeModels.Where(x => !x.IsContentIgnored).Where(x => x.BaseType != null && x.HasBase)) - // throw new InvalidOperationException(string.Format("Type alias \"{0}\" has more than one parent class.", - // xx.Alias)); - - // discover interfaces that need to be declared / implemented - foreach (var typeModel in _typeModels) - { - // 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) - TypeModel.CollectImplems(parentImplems, typeModel.BaseType); - - // interfaces we must declare we implement (initially empty) - // ie this type's mixins, except those that have been removed, - // 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); - - // interfaces we must actually implement (initially empty) - // if we declare we implement a mixin interface, we must actually implement - // its properties, all recursively (ie if the mixin interface implements...) - // so, starting with local mixins, we collect all the (non-removed) types above them - var mixinImplems = new List(); - foreach (var i in typeModel.DeclaringInterfaces) - TypeModel.CollectImplems(mixinImplems, i); - // and then we remove from that list anything that is already declared at the parent level - typeModel.ImplementingInterfaces.AddRange(mixinImplems.Except(parentImplems)); - } - - // register using types - foreach (var usingNamespace in ParseResult.UsingNamespaces) - { - if (!TypesUsing.Contains(usingNamespace)) - TypesUsing.Add(usingNamespace); - } - - // 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' - // expecting to match eg 'Umbraco' or 'System.String' - // returns true if either - // - more than 1 symbol is found (explicitely ambiguous) - // - 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); - - 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 - } - - internal string ModelsNamespaceForTests; - - public string GetModelsNamespace() - { - 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; - } - - 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/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/Building/PropertyModel.cs b/src/Umbraco.ModelsBuilder/Building/PropertyModel.cs deleted file mode 100644 index 1595b3f888..0000000000 --- a/src/Umbraco.ModelsBuilder/Building/PropertyModel.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Umbraco.ModelsBuilder.Building -{ - /// - /// Represents a model property. - /// - public class PropertyModel - { - /// - /// Gets the alias of the property. - /// - public string Alias; - - /// - /// Gets the name of the property. - /// - public string Name; - - /// - /// Gets the description of the property. - /// - public string Description; - - /// - /// Gets the clr name of the property. - /// - /// This is just the local name eg "Price". - public string ClrName; - - /// - /// Gets the Model Clr type of the property values. - /// - /// As indicated by the PublishedPropertyType, ie by the IPropertyValueConverter - /// if any, else object. May include some ModelType that will need to be mapped. - public Type ModelClrType; - - /// - /// Gets the CLR type name of the property values. - /// - 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. - /// - /// This should be null, unless something prevents the property from being - /// generated, and then the value should explain what. This can be used to generate - /// commented out code eg in PureLive. - public List Errors; - - /// - /// Adds an error. - /// - public void AddError(string error) - { - if (Errors == null) Errors = new List(); - Errors.Add(error); - } - } -} diff --git a/src/Umbraco.ModelsBuilder/Building/TextBuilder.cs b/src/Umbraco.ModelsBuilder/Building/TextBuilder.cs deleted file mode 100644 index 85ccd541b7..0000000000 --- a/src/Umbraco.ModelsBuilder/Building/TextBuilder.cs +++ /dev/null @@ -1,554 +0,0 @@ -using System; -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; - -namespace Umbraco.ModelsBuilder.Building -{ - /// - /// Implements a builder that works by writing text. - /// - internal class TextBuilder : Builder - { - /// - /// 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. - 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) - { } - - // internal for unit tests only - internal TextBuilder() - { } - - /// - /// Outputs a generated model to a string builder. - /// - /// The string builder. - /// The model to generate. - public void Generate(StringBuilder sb, TypeModel typeModel) - { - WriteHeader(sb); - - foreach (var t in TypesUsing) - sb.AppendFormat("using {0};\n", t); - - sb.Append("\n"); - sb.AppendFormat("namespace {0}\n", GetModelsNamespace()); - sb.Append("{\n"); - - WriteContentType(sb, typeModel); - - sb.Append("}\n"); - } - - /// - /// Outputs generated models to a string builder. - /// - /// The string builder. - /// The models to generate. - public void Generate(StringBuilder sb, IEnumerable typeModels) - { - WriteHeader(sb); - - foreach (var t in TypesUsing) - sb.AppendFormat("using {0};\n", t); - - // assembly attributes marker - sb.Append("\n//ASSATTR\n"); - - sb.Append("\n"); - sb.AppendFormat("namespace {0}\n", GetModelsNamespace()); - sb.Append("{\n"); - - foreach (var typeModel in typeModels) - { - WriteContentType(sb, typeModel); - sb.Append("\n"); - } - - sb.Append("}\n"); - } - - /// - /// Outputs an "auto-generated" header to a string builder. - /// - /// The string builder. - public static void WriteHeader(StringBuilder sb) - { - TextHeaderWriter.WriteHeader(sb); - } - - private void WriteContentType(StringBuilder sb, TypeModel type) - { - string sep; - - if (type.IsMixin) - { - // write the interface declaration - sb.AppendFormat("\t// Mixin content Type {0} with alias \"{1}\"\n", type.Id, 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 - ? (type.HasBase ? null : (type.IsElement ? "PublishedElement" : "PublishedContent")) - : type.BaseType.ClrName; - if (implements != null) - sb.AppendFormat(" : I{0}", implements); - - // write the mixins - sep = implements == null ? ":" : ","; - foreach (var mixinType in type.DeclaringInterfaces.OrderBy(x => x.ClrName)) - { - sb.AppendFormat("{0} I{1}", sep, mixinType.ClrName); - sep = ","; - } - - sb.Append("\n\t{\n"); - - // 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)) - { - if (more) sb.Append("\n"); - more = true; - WriteInterfaceProperty(sb, prop); - } - - sb.Append("\t}\n\n"); - } - - // 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 - //if (!type.HasImplement) - // sb.AppendFormat("\t[ImplementContentType(\"{0}\")]\n", type.Alias); - sb.AppendFormat("\t[PublishedModel(\"{0}\")]\n", type.Alias); - sb.AppendFormat("\tpublic partial class {0}", type.ClrName); - var inherits = type.HasBase - ? null // has its own base already - : (type.BaseType == null || type.BaseType.IsContentIgnored - ? GetModelsBaseClassName(type) - : type.BaseType.ClrName); - if (inherits != null) - sb.AppendFormat(" : {0}", inherits); - - sep = inherits == null ? ":" : ","; - if (type.IsMixin) - { - // if it's a mixin it implements its own interface - sb.AppendFormat("{0} I{1}", sep, type.ClrName); - } - else - { - // write the mixins, if any, as interfaces - // only if not a mixin because otherwise the interface already has them already - foreach (var mixinType in type.DeclaringInterfaces.OrderBy(x => x.ClrName)) - { - sb.AppendFormat("{0} I{1}", sep, mixinType.ClrName); - sep = ","; - } - } - - // begin class body - sb.Append("\n\t{\n"); - - // write the constants & static methods - // 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"); - sb.AppendFormat("\t\tpublic new const string ModelTypeAlias = \"{0}\";\n", - type.Alias); - var itemType = type.IsElement ? TypeModel.ItemTypes.Content : type.ItemType; // fixme - sb.AppendFormat("\t\tpublic new const PublishedItemType ModelItemType = PublishedItemType.{0};\n", - itemType); - sb.Append("\t\tpublic new static PublishedContentType GetModelContentType()\n"); - sb.Append("\t\t\t=> PublishedModelUtility.GetModelContentType(ModelItemType, ModelTypeAlias);\n"); - sb.AppendFormat("\t\tpublic static PublishedPropertyType 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"); - - // write the properties - sb.Append("\t\t// properties\n"); - WriteContentTypeProperties(sb, type); - - // close the class declaration - sb.Append("\t}\n"); - } - - private void WriteContentTypeProperties(StringBuilder sb, TypeModel type) - { - var staticMixinGetters = UmbracoConfig.For.ModelsBuilder().StaticMixinGetters; - - // write the properties - foreach (var prop in type.Properties.Where(x => !x.IsIgnored).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 - // and the parent defines its own properties. need to write the mixins properties - // since the mixins are only interfaces and we have to provide an implementation. - - // 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)) - if (staticMixinGetters) - WriteMixinProperty(sb, prop, mixinType.ClrName); - else - WriteProperty(sb, mixinType, prop); - } - - private void WriteMixinProperty(StringBuilder sb, PropertyModel property, string mixinClrName) - { - sb.Append("\n"); - - // Adds xml summary to each property containing - // property name and property description - if (!string.IsNullOrWhiteSpace(property.Name) || !string.IsNullOrWhiteSpace(property.Description)) - { - sb.Append("\t\t///\n"); - - if (!string.IsNullOrWhiteSpace(property.Description)) - sb.AppendFormat("\t\t/// {0}: {1}\n", XmlCommentString(property.Name), XmlCommentString(property.Description)); - else - sb.AppendFormat("\t\t/// {0}\n", XmlCommentString(property.Name)); - - sb.Append("\t\t///\n"); - } - - sb.AppendFormat("\t\t[ImplementPropertyType(\"{0}\")]\n", property.Alias); - - sb.Append("\t\tpublic "); - WriteClrType(sb, property.ClrTypeName); - - sb.AppendFormat(" {0} => ", - property.ClrName); - WriteNonGenericClrType(sb, GetModelsNamespace() + "." + mixinClrName); - sb.AppendFormat(".{0}(this);\n", - MixinStaticGetterName(property.ClrName)); - } - - private static string MixinStaticGetterName(string clrName) - { - return string.Format(UmbracoConfig.For.ModelsBuilder().StaticMixinGetterPattern, clrName); - } - - private void WriteProperty(StringBuilder sb, TypeModel type, PropertyModel property, string mixinClrName = null) - { - var mixinStatic = mixinClrName != null; - - sb.Append("\n"); - - if (property.Errors != null) - { - sb.Append("\t\t/*\n"); - sb.Append("\t\t * THIS PROPERTY CANNOT BE IMPLEMENTED, BECAUSE:\n"); - sb.Append("\t\t *\n"); - var first = true; - foreach (var error in property.Errors) - { - if (first) first = false; - else sb.Append("\t\t *\n"); - foreach (var s in SplitError(error)) - { - sb.Append("\t\t * "); - sb.Append(s); - sb.Append("\n"); - } - } - sb.Append("\t\t *\n"); - sb.Append("\n"); - } - - // Adds xml summary to each property containing - // property name and property description - if (!string.IsNullOrWhiteSpace(property.Name) || !string.IsNullOrWhiteSpace(property.Description)) - { - sb.Append("\t\t///\n"); - - if (!string.IsNullOrWhiteSpace(property.Description)) - sb.AppendFormat("\t\t/// {0}: {1}\n", XmlCommentString(property.Name), XmlCommentString(property.Description)); - else - sb.AppendFormat("\t\t/// {0}\n", XmlCommentString(property.Name)); - - sb.Append("\t\t///\n"); - } - - sb.AppendFormat("\t\t[ImplementPropertyType(\"{0}\")]\n", property.Alias); - - if (mixinStatic) - { - sb.Append("\t\tpublic "); - WriteClrType(sb, property.ClrTypeName); - sb.AppendFormat(" {0} => {1}(this);\n", - property.ClrName, MixinStaticGetterName(property.ClrName)); - } - else - { - sb.Append("\t\tpublic "); - WriteClrType(sb, property.ClrTypeName); - sb.AppendFormat(" {0} => this.Value", - property.ClrName); - if (property.ModelClrType != typeof(object)) - { - sb.Append("<"); - WriteClrType(sb, property.ClrTypeName); - sb.Append(">"); - } - sb.AppendFormat("(\"{0}\");\n", - property.Alias); - } - - if (property.Errors != null) - { - sb.Append("\n"); - sb.Append("\t\t *\n"); - sb.Append("\t\t */\n"); - } - - if (!mixinStatic) return; - - var mixinStaticGetterName = MixinStaticGetterName(property.ClrName); - - 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)); - - sb.Append("\t\tpublic static "); - WriteClrType(sb, property.ClrTypeName); - sb.AppendFormat(" {0}(I{1} that) => that.Value", - mixinStaticGetterName, mixinClrName); - if (property.ModelClrType != typeof(object)) - { - sb.Append("<"); - WriteClrType(sb, property.ClrTypeName); - sb.Append(">"); - } - sb.AppendFormat("(\"{0}\");\n", - property.Alias); - } - - private static IEnumerable SplitError(string error) - { - var p = 0; - while (p < error.Length) - { - var n = p + 50; - while (n < error.Length && error[n] != ' ') n++; - if (n >= error.Length) break; - yield return error.Substring(p, n - p); - p = n + 1; - } - if (p < error.Length) - yield return error.Substring(p); - } - - private void WriteInterfaceProperty(StringBuilder sb, PropertyModel property) - { - if (property.Errors != null) - { - sb.Append("\t\t/*\n"); - sb.Append("\t\t * THIS PROPERTY CANNOT BE IMPLEMENTED, BECAUSE:\n"); - sb.Append("\t\t *\n"); - var first = true; - foreach (var error in property.Errors) - { - if (first) first = false; - else sb.Append("\t\t *\n"); - foreach (var s in SplitError(error)) - { - sb.Append("\t\t * "); - sb.Append(s); - sb.Append("\n"); - } - } - sb.Append("\t\t *\n"); - sb.Append("\n"); - } - - if (!string.IsNullOrWhiteSpace(property.Name)) - sb.AppendFormat("\t\t/// {0}\n", XmlCommentString(property.Name)); - sb.Append("\t\t"); - WriteClrType(sb, property.ClrTypeName); - sb.AppendFormat(" {0} {{ get; }}\n", - property.ClrName); - - if (property.Errors != null) - { - sb.Append("\n"); - sb.Append("\t\t *\n"); - sb.Append("\t\t */\n"); - } - } - - // internal for unit tests - internal void WriteClrType(StringBuilder sb, Type type) - { - var s = type.ToString(); - - if (type.IsGenericType) - { - var p = s.IndexOf('`'); - WriteNonGenericClrType(sb, s.Substring(0, p)); - sb.Append("<"); - var args = type.GetGenericArguments(); - for (var i = 0; i < args.Length; i++) - { - if (i > 0) sb.Append(", "); - WriteClrType(sb, args[i]); - } - sb.Append(">"); - } - else - { - WriteNonGenericClrType(sb, s); - } - } - - internal void WriteClrType(StringBuilder sb, string type) - { - var p = type.IndexOf('<'); - if (type.Contains('<')) - { - WriteNonGenericClrType(sb, type.Substring(0, p)); - sb.Append("<"); - var args = type.Substring(p + 1).TrimEnd('>').Split(','); // fixme will NOT work with nested generic types - for (var i = 0; i < args.Length; i++) - { - if (i > 0) sb.Append(", "); - WriteClrType(sb, args[i]); - } - sb.Append(">"); - } - else - { - WriteNonGenericClrType(sb, type); - } - } - - private void WriteNonGenericClrType(StringBuilder sb, string s) - { - // map model types - 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)) - { - sb.Append(typeName); - return; - } - - // if full type name matches a using clause, strip - // so if we want Umbraco.Core.Models.IPublishedContent - // and using Umbraco.Core.Models, then we just need IPublishedContent - typeName = s; - string typeUsing = null; - var p = typeName.LastIndexOf('.'); - if (p > 0) - { - var x = typeName.Substring(0, p); - if (Using.Contains(x)) - { - typeName = typeName.Substring(p + 1); - typeUsing = x; - } - } - - // nested types *after* using - typeName = typeName.Replace("+", "."); - - // symbol to test is the first part of the name - // so if type name is Foo.Bar.Nil we want to ensure that Foo is not ambiguous - p = typeName.IndexOf('.'); - var symbol = p > 0 ? typeName.Substring(0, p) : typeName; - - // what we should find - WITHOUT any generic thing - just the type - // no 'using' = the exact symbol - // a 'using' = using.symbol - var match = typeUsing == null ? symbol : (typeUsing + "." + symbol); - - // if not ambiguous, be happy - if (!IsAmbiguousSymbol(symbol, match)) - { - sb.Append(typeName); - return; - } - - // symbol is ambiguous - // if no 'using', must prepend global:: - if (typeUsing == null) - { - sb.Append("global::"); - sb.Append(s.Replace("+", ".")); - return; - } - - // could fullname be non-ambiguous? - // note: all-or-nothing, not trying to segment the using clause - typeName = s.Replace("+", "."); - p = typeName.IndexOf('.'); - symbol = typeName.Substring(0, p); - match = symbol; - - // still ambiguous, must prepend global:: - if (IsAmbiguousSymbol(symbol, match)) - sb.Append("global::"); - - sb.Append(typeName); - } - - private static string XmlCommentString(string s) - { - return s.Replace('<', '{').Replace('>', '}').Replace('\r', ' ').Replace('\n', ' '); - } - - private static readonly IDictionary TypesMap = new Dictionary - { - { "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/Building/TextHeaderWriter.cs deleted file mode 100644 index d165f03907..0000000000 --- a/src/Umbraco.ModelsBuilder/Building/TextHeaderWriter.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Text; -using Umbraco.ModelsBuilder.Api; - -namespace Umbraco.ModelsBuilder.Building -{ - public static class TextHeaderWriter - { - /// - /// Outputs an "auto-generated" header to a string builder. - /// - /// The string builder. - public static void WriteHeader(StringBuilder sb) - { - sb.Append("//------------------------------------------------------------------------------\n"); - sb.Append("// \n"); - sb.Append("// This code was generated by a tool.\n"); - sb.Append("//\n"); - sb.AppendFormat("// Umbraco.ModelsBuilder v{0}\n", ApiVersion.Current.Version); - sb.Append("//\n"); - sb.Append("// Changes to this file will be lost if the code is regenerated.\n"); - sb.Append("// \n"); - sb.Append("//------------------------------------------------------------------------------\n"); - sb.Append("\n"); - } - } -} diff --git a/src/Umbraco.ModelsBuilder/Building/TypeModel.cs b/src/Umbraco.ModelsBuilder/Building/TypeModel.cs deleted file mode 100644 index 5ada8e881c..0000000000 --- a/src/Umbraco.ModelsBuilder/Building/TypeModel.cs +++ /dev/null @@ -1,208 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Umbraco.ModelsBuilder.Building -{ - /// - /// Represents a model. - /// - public class TypeModel - { - /// - /// Gets the unique identifier of the corresponding content type. - /// - public int Id; - - /// - /// Gets the alias of the model. - /// - public string Alias; - - /// - /// Gets the name of the content type. - /// - public string Name; - - /// - /// Gets the description of the content type. - /// - public string Description; - - /// - /// Gets the clr name of the model. - /// - /// This is the complete name eg "Foo.Bar.MyContent". - public string ClrName; - - /// - /// Gets the unique identifier of the parent. - /// - /// The parent can either be a base content type, or a content types container. If the content - /// type does not have a base content type, then returns -1. - public int ParentId; - - /// - /// Gets the base model. - /// - /// - /// If the content type does not have a base content type, then returns null. - /// The current model inherits from its base model. - /// - public TypeModel BaseType; // the parent type in Umbraco (type inherits its properties) - - /// - /// Gets the list of properties that are defined by this model. - /// - /// These are only those property that are defined locally by this model, - /// and the list does not contain properties inherited from base models or from mixins. - public readonly List Properties = new List(); - - /// - /// Gets the mixin models. - /// - /// The current model implements mixins. - public readonly List MixinTypes = new List(); - - /// - /// Gets the list of interfaces that this model needs to declare it implements. - /// - /// Some of these interfaces may actually be implemented by a base model - /// that this model inherits from. - public readonly List DeclaringInterfaces = new List(); - - /// - /// Gets the list of interfaces that this model needs to actually implement. - /// - public readonly List ImplementingInterfaces = new List(); - - /// - /// Gets the list of existing static mixin method candidates. - /// - public readonly List StaticMixinMethods = new List(); - - /// - /// Gets a value indicating whether this model has a base class. - /// - /// Can be either because the content type has a base content type declared in Umbraco, - /// 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. - /// - public bool IsMixin; - - /// - /// Gets a value indicating whether this model is the base model of another model. - /// - 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. - /// - public bool IsElement => ItemType == ItemTypes.Element; - - /// - /// Represents the different model item types. - /// - public enum ItemTypes - { - /// - /// Element. - /// - Element, - - /// - /// Content. - /// - Content, - - /// - /// Media. - /// - Media, - - /// - /// Member. - /// - Member - } - - private ItemTypes _itemType; - - /// - /// Gets or sets the model item type. - /// - public ItemTypes ItemType - { - get { return _itemType; } - set - { - switch (value) - { - case ItemTypes.Element: - case ItemTypes.Content: - case ItemTypes.Media: - case ItemTypes.Member: - _itemType = value; - break; - default: - throw new ArgumentException("value"); - } - } - } - - /// - /// Recursively collects all types inherited, or implemented as interfaces, by a specified type. - /// - /// The collection. - /// The type. - /// Includes the specified type. - internal static void CollectImplems(ICollection types, TypeModel type) - { - if (!type.IsContentIgnored && types.Contains(type) == false) - types.Add(type); - if (type.BaseType != null && !type.BaseType.IsContentIgnored) - CollectImplems(types, type.BaseType); - foreach (var mixin in type.MixinTypes.Where(x => !x.IsContentIgnored)) - CollectImplems(types, mixin); - } - - /// - /// Enumerates the base models starting from the current model up. - /// - /// Indicates whether the enumeration should start with the current model - /// or from its base model. - /// The base models. - public IEnumerable EnumerateBaseTypes(bool andSelf = false) - { - var typeModel = andSelf ? this : BaseType; - while (typeModel != null) - { - yield return typeModel; - typeModel = typeModel.BaseType; - } - } - } -} 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/Config.cs b/src/Umbraco.ModelsBuilder/Configuration/Config.cs deleted file mode 100644 index ebfe4be709..0000000000 --- a/src/Umbraco.ModelsBuilder/Configuration/Config.cs +++ /dev/null @@ -1,357 +0,0 @@ -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; - -namespace Umbraco.ModelsBuilder.Configuration -{ - /// - /// Represents the models builder configuration. - /// - public class Config - { - private static Config _value; - - /// - /// 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. - /// - 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() - { - const string prefix = "Umbraco.ModelsBuilder."; - - // giant kill switch, default: false - // must be explicitely set to true for anything else to happen - 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("~/"); - DebugLevel = 0; - - // stop here, everything is false - if (!Enable) return; - - // mode - var modelsMode = ConfigurationManager.AppSettings[prefix + "ModelsMode"]; - if (!string.IsNullOrWhiteSpace(modelsMode)) - { - switch (modelsMode) - { - case nameof(ModelsMode.Nothing): - ModelsMode = ModelsMode.Nothing; - break; - 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; - case nameof(ModelsMode.LiveAppData): - ModelsMode = ModelsMode.LiveAppData; - break; - default: - throw new ConfigurationErrorsException($"ModelsMode \"{modelsMode}\" is not a valid mode." - + " Note that modes are case-sensitive."); - } - } - - // 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 - var value = ConfigurationManager.AppSettings[prefix + "ModelsNamespace"]; - 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(); - if (root == null) - throw new ConfigurationErrorsException("Could not determine root directory."); - - // GetModelsDirectory will ensure that the path is safe - ModelsDirectory = GetModelsDirectory(root, value, AcceptUnsafeModelsDirectory); - } - - // default: 0 - value = ConfigurationManager.AppSettings[prefix + "DebugLevel"]; - if (!string.IsNullOrWhiteSpace(value)) - { - int debugLevel; - if (!int.TryParse(value, out debugLevel)) - throw new ConfigurationErrorsException($"Invalid debug level \"{value}\"."); - DebugLevel = debugLevel; - } - - // not flagging if not generating, or live (incl. pure) - if (ModelsMode == ModelsMode.Nothing || ModelsMode.IsLive()) - FlagOutOfDateModels = false; - } - - /// - /// Initializes a new instance of the class. - /// - public Config( - 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) - { - 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; - } - - // internal for tests - internal static string GetModelsDirectory(string root, string config, bool acceptUnsafe) - { - // making sure it is safe, ie under the website root, - // unless AcceptUnsafeModelsDirectory and then everything is OK. - - if (!Path.IsPathRooted(root)) - throw new ConfigurationErrorsException($"Root is not rooted \"{root}\"."); - - if (config.StartsWith("~/")) - { - var dir = Path.Combine(root, config.TrimStart("~/")); - - // sanitize - GetFullPath will take care of any relative - // segments in path, eg '../../foo.tmp' - it may throw a SecurityException - // if the combined path reaches illegal parts of the filesystem - dir = Path.GetFullPath(dir); - root = Path.GetFullPath(root); - - if (!dir.StartsWith(root) && !acceptUnsafe) - throw new ConfigurationErrorsException($"Invalid models directory \"{config}\"."); - - return dir; - } - - if (acceptUnsafe) - return Path.GetFullPath(config); - - throw new ConfigurationErrorsException($"Invalid models directory \"{config}\"."); - } - - /// - /// Gets a value indicating whether the whole models experience is enabled. - /// - /// - /// If this is false then absolutely nothing happens. - /// Default value is false which means that unless we have this setting, nothing happens. - /// - public bool Enable { get; } - - /// - /// Gets the models mode. - /// - 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. - /// - public bool IsDebug - { - get - { - var section = (CompilationSection) ConfigurationManager.GetSection("system.web/compilation"); - return section != null && section.Debug; - } - } - - /// - /// Gets the models namespace. - /// - /// That value could be overriden by other (attribute in user's code...). Return default if no value was supplied. - public string ModelsNamespace { get; } - - /// - /// Gets a value indicating whether we should enable the models factory. - /// - /// 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. - /// - /// Models become out-of-date when data types or content types are updated. When this - /// setting is activated the ~/App_Data/Models/ood.txt file is then created. When models are - /// 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. - /// - /// Default is ~/App_Data/Models but that can be changed. - public string ModelsDirectory { get; } - - /// - /// Gets a value indicating whether to accept an unsafe value for ModelsDirectory. - /// - /// An unsafe value is an absolute path, or a relative path pointing outside - /// of the website root. - public bool AcceptUnsafeModelsDirectory { get; } - - /// - /// Gets a value indicating the debug log level. - /// - /// 0 means minimal (safe on live site), anything else means more and more details (maybe not safe). - public int DebugLevel { get; } - } -} diff --git a/src/Umbraco.ModelsBuilder/Configuration/ModelsMode.cs b/src/Umbraco.ModelsBuilder/Configuration/ModelsMode.cs deleted file mode 100644 index e04c4dee90..0000000000 --- a/src/Umbraco.ModelsBuilder/Configuration/ModelsMode.cs +++ /dev/null @@ -1,52 +0,0 @@ -namespace Umbraco.ModelsBuilder.Configuration -{ - /// - /// Defines the models generation modes. - /// - public enum ModelsMode - { - /// - /// Do not generate models. - /// - Nothing = 0, // default value - - /// - /// Generate models in memory. - /// When: a content type change occurs. - /// - /// The app does not restart. Models are available in views exclusively. - PureLive, - - /// - /// Generate models in AppData. - /// When: generation is triggered. - /// - /// Generation can be triggered from the dashboard. The app does not restart. - /// Models are not compiled and thus are not available to the project. - AppData, - - /// - /// Generate models in AppData. - /// When: a content type change occurs, or generation is triggered. - /// - /// 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 - } -} diff --git a/src/Umbraco.ModelsBuilder/Configuration/ModelsModeExtensions.cs b/src/Umbraco.ModelsBuilder/Configuration/ModelsModeExtensions.cs deleted file mode 100644 index be609c0548..0000000000 --- a/src/Umbraco.ModelsBuilder/Configuration/ModelsModeExtensions.cs +++ /dev/null @@ -1,51 +0,0 @@ -namespace Umbraco.ModelsBuilder.Configuration -{ - /// - /// Provides extensions for the enumeration. - /// - public static class ModelsModeExtensions - { - /// - /// Gets a value indicating whether the mode is LiveAnything or PureLive. - /// - public static bool IsLive(this ModelsMode modelsMode) - { - return - modelsMode == ModelsMode.PureLive - || modelsMode == ModelsMode.LiveDll - || modelsMode == ModelsMode.LiveAppData; - } - - /// - /// Gets a value indicating whether the mode is LiveAnything but not PureLive. - /// - 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; - } - - /// - /// Gets a value indicating whether the mode supports explicit generation (as opposed to pure live). - /// - public static bool SupportsExplicitGeneration(this ModelsMode modelsMode) - { - return - modelsMode == ModelsMode.Dll - || modelsMode == ModelsMode.LiveDll - || modelsMode == ModelsMode.AppData - || modelsMode == ModelsMode.LiveAppData; - } - } -} \ No newline at end of file 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(".
  • "); - - sb.Append(config.ModelsMode != ModelsMode.Nothing - ? $"
  • {config.ModelsMode} models are enabled.
  • " - : "
  • No models mode is specified: models will not be generated.
  • "); - - sb.Append($"
  • Models namespace is {config.ModelsNamespace}.
  • "); - - sb.Append("
  • 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/ImplementPropertyTypeAttribute.cs b/src/Umbraco.ModelsBuilder/ImplementPropertyTypeAttribute.cs deleted file mode 100644 index c5d8f8cad4..0000000000 --- a/src/Umbraco.ModelsBuilder/ImplementPropertyTypeAttribute.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Umbraco.ModelsBuilder -{ - /// - /// Indicates that a property implements a given property alias. - /// - /// And therefore it should not be generated. - [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)] - public sealed class ImplementPropertyTypeAttribute : Attribute - { - public ImplementPropertyTypeAttribute(string alias) - { - Alias = alias; - } - - public string Alias { get; private set; } - } -} 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/ModelsBuilderAssemblyAttribute.cs b/src/Umbraco.ModelsBuilder/ModelsBuilderAssemblyAttribute.cs deleted file mode 100644 index ed956852f8..0000000000 --- a/src/Umbraco.ModelsBuilder/ModelsBuilderAssemblyAttribute.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; - -namespace Umbraco.ModelsBuilder -{ - /// - /// Indicates that an Assembly is a Models Builder assembly. - /// - [AttributeUsage(AttributeTargets.Assembly /*, AllowMultiple = false, Inherited = false*/)] - public sealed class ModelsBuilderAssemblyAttribute : Attribute - { - /// - /// Gets or sets a value indicating whether the assembly is a PureLive assembly. - /// - /// A Models Builder assembly can be either PureLive or normal Dll. - public bool PureLive { get; set; } - - /// - /// Gets or sets a hash value representing the state of the custom source code files - /// and the Umbraco content types that were used to generate and compile the assembly. - /// - public string SourceHash { get; set; } - } -} 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 index a2f8d1ae1e..8ec221bffb 100644 --- a/src/Umbraco.ModelsBuilder/Properties/AssemblyInfo.cs +++ b/src/Umbraco.ModelsBuilder/Properties/AssemblyInfo.cs @@ -1,14 +1,36 @@ using System.Reflection; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. [assembly: AssemblyTitle("Umbraco.ModelsBuilder")] -[assembly: AssemblyDescription("Umbraco ModelsBuilder")] +[assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] -[assembly: AssemblyProduct("Umbraco CMS")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Umbraco.ModelsBuilder")] +[assembly: AssemblyCopyright("Copyright © 2019")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. [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 = "~_~")] +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("52ac0ba8-a60e-4e36-897b-e8b97a54ed1c")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/Umbraco.ModelsBuilder/PublishedElementExtensions.cs b/src/Umbraco.ModelsBuilder/PublishedElementExtensions.cs deleted file mode 100644 index f3320b5dfb..0000000000 --- a/src/Umbraco.ModelsBuilder/PublishedElementExtensions.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; -using System.Linq.Expressions; -using System.Reflection; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Web; - -namespace Umbraco.ModelsBuilder -{ - /// - /// Provides extension methods to models. - /// - public static class PublishedElementExtensions - { - /// - /// Gets the value of a property. - /// - public static TValue Value(this TModel model, Expression> property, string culture = ".", string segment = ".") - where TModel : IPublishedElement - { - var alias = GetAlias(model, property); - return model.Value(alias, culture, segment); - } - - private static string GetAlias(TModel model, Expression> property) - { - if (property.NodeType != ExpressionType.Lambda) - throw new ArgumentException("Not a proper lambda expression (lambda).", nameof(property)); - - var lambda = (LambdaExpression) property; - var lambdaBody = lambda.Body; - - if (lambdaBody.NodeType != ExpressionType.MemberAccess) - throw new ArgumentException("Not a proper lambda expression (body).", nameof(property)); - - var memberExpression = (MemberExpression) lambdaBody; - if (memberExpression.Expression.NodeType != ExpressionType.Parameter) - throw new ArgumentException("Not a proper lambda expression (member).", nameof(property)); - - var member = memberExpression.Member; - - var attribute = member.GetCustomAttribute(); - if (attribute == null) - throw new InvalidOperationException("Property is not marked with ImplementPropertyType attribute."); - - return attribute.Alias; - } - } -} 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/ReferencedAssemblies.cs b/src/Umbraco.ModelsBuilder/ReferencedAssemblies.cs deleted file mode 100644 index 42e8b3b9c9..0000000000 --- a/src/Umbraco.ModelsBuilder/ReferencedAssemblies.cs +++ /dev/null @@ -1,137 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Web.Compilation; -using System.Web.Hosting; -using Microsoft.CodeAnalysis; -using Umbraco.Core; - -namespace Umbraco.ModelsBuilder -{ - 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()); - } - - /// - /// Gets the assembly locations of all the referenced assemblies, that - /// are not dynamic, and have a non-null nor empty location. - /// - public static IEnumerable Locations => LazyLocations.Value; - - /// - /// Gets the metadata reference of all the referenced assemblies. - /// - public static IEnumerable References => LazyReferences.Value; - - // hosted, get referenced assemblies from the BuildManader and filter - private static IEnumerable GetAllReferencedAssembliesLocationFromBuildManager() - { - return BuildManager.GetReferencedAssemblies() - .Cast() - .Where(x => !x.IsDynamic && !x.Location.IsNullOrWhiteSpace()) - .Select(x => x.Location) - .And(typeof(ReferencedAssemblies).Assembly.Location) // always include ourselves - .Distinct() - .ToList(); - } - - // non-hosted, do our best - private static IEnumerable GetAllReferencedAssembliesFromDomain() - { - //TODO: This method has bugs since I've been stuck in an infinite loop with it, though this shouldn't - // execute while in the web application anyways. - - var assemblies = new List(); - var tmp1 = new List(); - var failed = new List(); - foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies() - .Where(x => x.IsDynamic == false) - .Where(x => !string.IsNullOrWhiteSpace(x.Location))) // though... IsDynamic should be enough? - { - assemblies.Add(assembly); - tmp1.Add(assembly); - } - - // fixme - AssemblyUtility questions - // - should we also load everything that's in the same directory? - // - do we want to load in the current app domain? - // - if this runs within Umbraco then we have already loaded them all? - - while (tmp1.Count > 0) - { - var tmp2 = tmp1 - .SelectMany(x => x.GetReferencedAssemblies()) - .Distinct() - .Where(x => assemblies.All(xx => x.FullName != xx.FullName)) // we don't have it already - .Where(x => failed.All(xx => x.FullName != xx.FullName)) // it hasn't failed already - .ToArray(); - tmp1.Clear(); - foreach (var assemblyName in tmp2) - { - try - { - var assembly = AppDomain.CurrentDomain.Load(assemblyName); - assemblies.Add(assembly); - tmp1.Add(assembly); - } - catch - { - failed.Add(assemblyName); - } - } - } - return assemblies.Select(x => x.Location).Distinct(); - } - - - // ---- - - 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) - { - try - { - return AppDomain.CurrentDomain.Load(name); - } - catch (Exception) - { - //Console.WriteLine(name); - return null; - } - } - - } -} 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/TypeExtensions.cs b/src/Umbraco.ModelsBuilder/TypeExtensions.cs deleted file mode 100644 index d3b3ff6b4e..0000000000 --- a/src/Umbraco.ModelsBuilder/TypeExtensions.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; - -namespace Umbraco.ModelsBuilder -{ - internal static class TypeExtensions - { - /// - /// Creates a generic instance of a generic type with the proper actual type of an object. - /// - /// A generic type such as Something{} - /// An object whose type is used as generic type param. - /// Arguments for the constructor. - /// A generic instance of the generic type with the proper type. - /// Usage... typeof (Something{}).CreateGenericInstance(object1, object2, object3) will return - /// a Something{Type1} if object1.GetType() is Type1. - public static object CreateGenericInstance(this Type genericType, object typeParmObj, params object[] ctorArgs) - { - var type = genericType.MakeGenericType(typeParmObj.GetType()); - return Activator.CreateInstance(type, ctorArgs); - } - } -} diff --git a/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj b/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj index b9a5890d57..d1d3ca205d 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj +++ b/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj @@ -4,13 +4,14 @@ Debug AnyCPU - {7020A059-C0D1-43A0-8EFD-23591A0C9AF6} + {52AC0BA8-A60E-4E36-897B-E8B97A54ED1C} Library Properties Umbraco.ModelsBuilder Umbraco.ModelsBuilder v4.7.2 512 + true true @@ -28,13 +29,10 @@ TRACE prompt 4 - bin\Release\Umbraco.ModelsBuilder.xml - - @@ -43,76 +41,7 @@ - - Properties\SolutionInfo.cs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 2.8.0 - - - - - {31785bc3-256c-4613-b2f5-a1b0bdded8c1} - Umbraco.Core - - - {651e1350-91b6-44b7-bd60-7207006d7003} - Umbraco.Web - \ No newline at end of file diff --git a/src/Umbraco.ModelsBuilder/Umbraco/HashCombiner.cs b/src/Umbraco.ModelsBuilder/Umbraco/HashCombiner.cs deleted file mode 100644 index e11662eb24..0000000000 --- a/src/Umbraco.ModelsBuilder/Umbraco/HashCombiner.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using System.Globalization; - -namespace Umbraco.ModelsBuilder.Umbraco -{ - // because, of course, it's internal in Umbraco - // see also System.Web.Util.HashCodeCombiner - class HashCombiner - { - private long _combinedHash = 5381L; - - public void Add(int i) - { - _combinedHash = ((_combinedHash << 5) + _combinedHash) ^ i; - } - - public void Add(object o) - { - Add(o.GetHashCode()); - } - - public void Add(DateTime d) - { - Add(d.GetHashCode()); - } - - public void Add(string s) - { - if (s == null) return; - Add((StringComparer.InvariantCulture).GetHashCode(s)); - } - - public string GetCombinedHashCode() - { - return _combinedHash.ToString("x", CultureInfo.InvariantCulture); - } - } -} diff --git a/src/Umbraco.ModelsBuilder/Umbraco/HashHelper.cs b/src/Umbraco.ModelsBuilder/Umbraco/HashHelper.cs deleted file mode 100644 index c530cbbd6b..0000000000 --- a/src/Umbraco.ModelsBuilder/Umbraco/HashHelper.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Umbraco.ModelsBuilder.Building; - -namespace Umbraco.ModelsBuilder.Umbraco -{ - class HashHelper - { - public static string Hash(IDictionary ourFiles, 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 - - foreach (var typeModel in typeModels.OrderBy(x => x.Alias)) - { - hash.Add("--- CONTENT TYPE MODEL ---"); - hash.Add(typeModel.Id); - hash.Add(typeModel.Alias); - hash.Add(typeModel.ClrName); - hash.Add(typeModel.ParentId); - hash.Add(typeModel.Name); - hash.Add(typeModel.Description); - hash.Add(typeModel.ItemType.ToString()); - hash.Add("MIXINS:" + string.Join(",", typeModel.MixinTypes.OrderBy(x => x.Id).Select(x => x.Id))); - - foreach (var prop in typeModel.Properties.OrderBy(x => x.Alias)) - { - hash.Add("--- PROPERTY ---"); - hash.Add(prop.Alias); - hash.Add(prop.ClrName); - hash.Add(prop.Name); - hash.Add(prop.Description); - hash.Add(prop.ModelClrType.ToString()); // see ModelType tests, want ToString() not FullName - } - } - - return hash.GetCombinedHashCode(); - } - } -} 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/Umbraco/ModelsGenerationError.cs b/src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerationError.cs deleted file mode 100644 index 7102190b5e..0000000000 --- a/src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerationError.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; -using System.IO; -using System.Text; -using Umbraco.Core.Configuration; -using Umbraco.ModelsBuilder.Configuration; - -namespace Umbraco.ModelsBuilder.Umbraco -{ - internal static class ModelsGenerationError - { - public static void Clear() - { - var errFile = GetErrFile(); - if (errFile == null) return; - - // "If the file to be deleted does not exist, no exception is thrown." - File.Delete(errFile); - } - - public static void Report(string message, Exception e) - { - var errFile = GetErrFile(); - if (errFile == null) return; - - var sb = new StringBuilder(); - sb.Append(message); - sb.Append("\r\n"); - sb.Append(e.Message); - sb.Append("\r\n\r\n"); - sb.Append(e.StackTrace); - sb.Append("\r\n"); - - File.WriteAllText(errFile, sb.ToString()); - } - - public static string GetLastError() - { - var errFile = GetErrFile(); - if (errFile == null) return null; - - try - { - return File.ReadAllText(errFile); - } - catch // accepted - { - return null; - } - } - - private static string GetErrFile() - { - var modelsDirectory = UmbracoConfig.For.ModelsBuilder().ModelsDirectory; - if (!Directory.Exists(modelsDirectory)) - return null; - - return Path.Combine(modelsDirectory, "models.err"); - } - } -} diff --git a/src/Umbraco.ModelsBuilder/Umbraco/OutOfDateModelsStatus.cs b/src/Umbraco.ModelsBuilder/Umbraco/OutOfDateModelsStatus.cs deleted file mode 100644 index a047f21edb..0000000000 --- a/src/Umbraco.ModelsBuilder/Umbraco/OutOfDateModelsStatus.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using System.IO; -using System.Web.Hosting; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.ModelsBuilder.Configuration; -using Umbraco.Web.Cache; - -namespace Umbraco.ModelsBuilder.Umbraco -{ - public sealed class OutOfDateModelsStatus - { - internal static void Install() - { - // just be sure - if (UmbracoConfig.For.ModelsBuilder().FlagOutOfDateModels == false) - return; - - ContentTypeCacheRefresher.CacheUpdated += (sender, args) => Write(); - DataTypeCacheRefresher.CacheUpdated += (sender, args) => Write(); - } - - private static string GetFlagPath() - { - var modelsDirectory = UmbracoConfig.For.ModelsBuilder().ModelsDirectory; - if (!Directory.Exists(modelsDirectory)) - Directory.CreateDirectory(modelsDirectory); - return Path.Combine(modelsDirectory, "ood.flag"); - } - - private static 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() - { - if (UmbracoConfig.For.ModelsBuilder().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 static bool IsOutOfDate - { - get - { - if (UmbracoConfig.For.ModelsBuilder().FlagOutOfDateModels == false) return false; - var path = GetFlagPath(); - return path != null && File.Exists(path); - } - } - } -} diff --git a/src/Umbraco.ModelsBuilder/Umbraco/PublishedModelUtility.cs b/src/Umbraco.ModelsBuilder/Umbraco/PublishedModelUtility.cs deleted file mode 100644 index c70e8a3b65..0000000000 --- a/src/Umbraco.ModelsBuilder/Umbraco/PublishedModelUtility.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System; -using System.Linq; -using System.Linq.Expressions; -using Umbraco.Web.Composing; -using Umbraco.Core.Models.PublishedContent; - -namespace Umbraco.ModelsBuilder.Umbraco -{ - public static class PublishedModelUtility - { - // looks safer but probably useless... ppl should not call these methods directly - // and if they do... they have to take care about not doing stupid things - - //public static PublishedPropertyType GetModelPropertyType2(Expression> selector) - // where T : PublishedContentModel - //{ - // var type = typeof (T); - // var s1 = type.GetField("ModelTypeAlias", BindingFlags.Public | BindingFlags.Static); - // var alias = (s1.IsLiteral && s1.IsInitOnly && s1.FieldType == typeof(string)) ? (string)s1.GetValue(null) : null; - // var s2 = type.GetField("ModelItemType", BindingFlags.Public | BindingFlags.Static); - // var itemType = (s2.IsLiteral && s2.IsInitOnly && s2.FieldType == typeof(PublishedItemType)) ? (PublishedItemType)s2.GetValue(null) : 0; - - // var contentType = PublishedContentType.Get(itemType, alias); - // // etc... - //} - - public static PublishedContentType GetModelContentType(PublishedItemType itemType, string alias) - { - var facade = Current.UmbracoContext.PublishedSnapshot; // fixme inject! - switch (itemType) - { - case PublishedItemType.Content: - return facade.Content.GetContentType(alias); - case PublishedItemType.Media: - return facade.Media.GetContentType(alias); - case PublishedItemType.Member: - return facade.Members.GetContentType(alias); - default: - throw new ArgumentOutOfRangeException(nameof(itemType)); - } - } - - public static PublishedPropertyType GetModelPropertyType(PublishedContentType contentType, Expression> selector) - //where TModel : PublishedContentModel // fixme PublishedContentModel _or_ PublishedElementModel - { - // fixme therefore, missing a check on TModel here - - var expr = selector.Body as MemberExpression; - - if (expr == null) - throw new ArgumentException("Not a property expression.", nameof(selector)); - - // there _is_ a risk that contentType and T do not match - // see note above : accepted risk... - - var attr = expr.Member - .GetCustomAttributes(typeof (ImplementPropertyTypeAttribute), false) - .OfType() - .SingleOrDefault(); - - if (string.IsNullOrWhiteSpace(attr?.Alias)) - throw new InvalidOperationException($"Could not figure out property alias for property \"{expr.Member.Name}\"."); - - return contentType.GetPropertyType(attr.Alias); - } - } -} diff --git a/src/Umbraco.ModelsBuilder/Umbraco/PureLiveModelFactory.cs b/src/Umbraco.ModelsBuilder/Umbraco/PureLiveModelFactory.cs deleted file mode 100644 index 9558c0140e..0000000000 --- a/src/Umbraco.ModelsBuilder/Umbraco/PureLiveModelFactory.cs +++ /dev/null @@ -1,602 +0,0 @@ -using System; -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.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 File = System.IO.File; - -namespace Umbraco.ModelsBuilder.Umbraco -{ - internal class PureLiveModelFactory : IPublishedModelFactory, 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 FileSystemWatcher _watcher; - private int _ver, _skipver; - private readonly int _debugLevel; - private BuildManager _theBuildManager; - private readonly Lazy _umbracoServices; - 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) - { - _umbracoServices = umbracoServices; - _logger = logger; - _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; - if (!Directory.Exists(modelsDirectory)) - Directory.CreateDirectory(modelsDirectory); - - // BEWARE! if the watcher is not properly released then for some reason the - // BuildManager will start confusing types - using a 'registered object' here - // though we should probably plug into Umbraco's MainDom - which is internal - HostingEnvironment.RegisterObject(this); - _watcher = new FileSystemWatcher(modelsDirectory); - _watcher.Changed += WatcherOnChanged; - _watcher.EnableRaisingEvents = true; - - // get it here, this need to be fast - _debugLevel = UmbracoConfig.For.ModelsBuilder().DebugLevel; - } - - #region IPublishedModelFactory - - public IPublishedElement CreateModel(IPublishedElement element) - { - // get models, rebuilding them if needed - var infos = EnsureModels()?.ModelInfos; - if (infos == null) - return element; - - // be case-insensitive - var contentTypeAlias = element.ContentType.Alias; - - // lookup model constructor (else null) - infos.TryGetValue(contentTypeAlias, out ModelInfo info); - - // create model - return info == null ? element : info.Ctor(element); - } - - // this runs only once the factory is ready - // NOT when building models - public Type MapModelType(Type type) - { - var infos = EnsureModels(); - return ModelType.Map(type, infos.ModelTypeMap); - } - - // this runs only once the factory is ready - // NOT when building models - public IList CreateModelList(string alias) - { - var infos = EnsureModels(); - - // fail fast - if (infos == null) - return new List(); - - if (!infos.ModelInfos.TryGetValue(alias, out var modelInfo)) - return new List(); - - var ctor = modelInfo.ListCtor; - if (ctor != null) return ctor(); - - var listType = typeof(List<>).MakeGenericType(modelInfo.ModelType); - ctor = modelInfo.ListCtor = ReflectionUtilities.EmitCtor>(declaring: listType); - return ctor(); - } - - #endregion - - #region Compilation - - // deadlock note - // - // when RazorBuildProvider_CodeGenerationStarted runs, the thread has Monitor.Enter-ed the BuildManager - // singleton instance, through a call to CompilationLock.GetLock in BuildManager.GetVPathBuildResultInternal, - // and now wants to lock _locker. - // when EnsureModels runs, the thread locks _locker and then wants BuildManager to compile, which in turns - // requires that the BuildManager can Monitor.Enter-ed itself. - // so: - // - // T1 - needs to ensure models, locks _locker - // T2 - needs to compile a view, locks BuildManager - // hits RazorBuildProvider_CodeGenerationStarted - // wants to lock _locker, wait - // T1 - needs to compile models, using BuildManager - // wants to lock itself, wait - // - // - // until ASP.NET kills the long-running request (thread abort) - // - // problem is, we *want* to suspend views compilation while the models assembly is being changed else we - // end up with views compiled and cached with the old assembly, while models come from the new assembly, - // which gives more YSOD. so we *have* to lock _locker in RazorBuildProvider_CodeGenerationStarted. - // - // one "easy" solution consists in locking the BuildManager *before* _locker in EnsureModels, thus ensuring - // we always lock in the same order, and getting rid of deadlocks - but that requires having access to the - // current BuildManager instance, which is BuildManager.TheBuildManager, which is an internal property. - // - // well, that's what we are doing in this class' TheBuildManager property, using reflection. - - private void RazorBuildProvider_CodeGenerationStarted(object sender, EventArgs e) - { - try - { - _locker.EnterReadLock(); - - // just be safe - can happen if the first view is not an Umbraco view, - // or if something went wrong and we don't have an assembly at all - if (_modelsAssembly == null) return; - - if (_debugLevel > 0) - _logger.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 - // compilation as in some environments (could not figure which/why) the BuildManager - // would not re-compile the views when the models assembly is rebuilt. - provider.AssemblyBuilder.AddAssemblyReference(_modelsAssembly); - provider.AddVirtualPathDependency(ProjVirt); - } - finally - { - if (_locker.IsReadLockHeld) - _locker.ExitReadLock(); - } - } - - // tells the factory that it should build a new generation of models - private void ResetModels() - { - _logger.Logger.Debug("Resetting models."); - - try - { - _locker.EnterWriteLock(); - - _hasModels = false; - _pendingRebuild = true; - } - finally - { - if (_locker.IsWriteLockHeld) - _locker.ExitWriteLock(); - } - } - - // gets "the" build manager - private BuildManager TheBuildManager - { - get - { - if (_theBuildManager != null) return _theBuildManager; - 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); - return _theBuildManager; - } - } - - // ensure that the factory is running with the lastest generation of models - internal Infos EnsureModels() - { - if (_debugLevel > 0) - _logger.Logger.Debug("Ensuring models."); - - // don't use an upgradeable lock here because only 1 thread at a time could enter it - try - { - _locker.EnterReadLock(); - if (_hasModels) - return _infos; - } - finally - { - if (_locker.IsReadLockHeld) - _locker.ExitReadLock(); - } - - var buildManagerLocked = false; - try - { - // always take the BuildManager lock *before* taking the _locker lock - // to avoid possible deadlock situations (see notes above) - Monitor.Enter(TheBuildManager, ref buildManagerLocked); - - _locker.EnterUpgradeableReadLock(); - - if (_hasModels) return _infos; - - _locker.EnterWriteLock(); - - // we don't have models, - // either they haven't been loaded from the cache yet - // or they have been reseted and are pending a rebuild - - using (_logger.DebugDuration("Get models.", "Got models.")) - { - try - { - var assembly = GetModelsAssembly(_pendingRebuild); - - // the one below can be used to simulate an issue with BuildManager, ie it will register - // the models with the factory but NOT with the BuildManager, which will not recompile views. - // this is for U4-8043 which is an obvious issue but I cannot replicate - //_modelsAssembly = _modelsAssembly ?? assembly; - - // the one below is the normal one - _modelsAssembly = assembly; - - var types = assembly.ExportedTypes.Where(x => x.Inherits() || x.Inherits()); - _infos = RegisterModels(types); - ModelsGenerationError.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); - } - finally - { - _modelsAssembly = null; - _infos = new Infos { ModelInfos = null, ModelTypeMap = new Dictionary() }; - } - } - - // don't even try again - _hasModels = true; - } - - return _infos; - } - finally - { - if (_locker.IsWriteLockHeld) - _locker.ExitWriteLock(); - if (_locker.IsUpgradeableReadLockHeld) - _locker.ExitUpgradeableReadLock(); - if (buildManagerLocked) - Monitor.Exit(TheBuildManager); - } - } - - private Assembly GetModelsAssembly(bool forceRebuild) - { - var modelsDirectory = UmbracoConfig.For.ModelsBuilder().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 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"); - - // caching the generated models speeds up booting - // currentHash hashes both the types & the user's partials - - if (!forceRebuild) - { - _logger.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."); - forceRebuild = true; - } - } - else - { - _logger.Logger.Debug("Could not find cached models."); - forceRebuild = true; - } - } - - Assembly assembly; - if (forceRebuild == false) - { - // try to load the dll directly (avoid rebuilding) - if (File.Exists(dllPathFile)) - { - var dllPath = File.ReadAllText(dllPathFile); - if (File.Exists(dllPath)) - { - assembly = Assembly.LoadFile(dllPath); - var attr = assembly.GetCustomAttribute(); - if (attr != null && attr.PureLive && attr.SourceHash == currentHash) - { - // if we were to resume at that revision, then _ver would keep increasing - // and that is probably a bad idea - so, we'll always rebuild starting at - // ver 1, but we remember we want to skip that one - so we never end up - // with the "same but different" version of the assembly in memory - _skipver = assembly.GetName().Version.Revision; - - _logger.Logger.Debug("Loading cached models (dll)."); - return assembly; - } - } - } - - // mmust 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); - if (match.Success) - { - text = text.Replace(match.Value, "AssemblyVersion(\"0.0.0." + _ver + "\")"); - File.WriteAllText(projFile, text); - } - - // generate a marker file that will be a dependency - // see note in RazorBuildProvider_CodeGenerationStarted - // NO: using all.generated.cs as a dependency - //File.WriteAllText(Path.Combine(modelsDirectory, "models.dep"), "VER:" + _ver); - - _ver++; - assembly = BuildManager.GetCompiledAssembly(ProjVirt); - File.WriteAllText(dllPathFile, assembly.Location); - - _logger.Logger.Debug("Loading cached models (source)."); - return assembly; - } - - // need to rebuild - _logger.Logger.Debug("Rebuilding models."); - - // generate code, save - var code = GenerateModelsCode(ourFiles, 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}"")] -[assembly:System.Reflection.AssemblyVersion(""0.0.0.{ver}"")]"); - File.WriteAllText(modelsSrcFile, code); - - // generate proj, save - ourFiles["models.generated.cs"] = code; - var proj = GenerateModelsProj(ourFiles); - File.WriteAllText(projFile, proj); - - // compile and register - assembly = BuildManager.GetCompiledAssembly(ProjVirt); - File.WriteAllText(dllPathFile, assembly.Location); - - // assuming we can write and it's not going to cause exceptions... - File.WriteAllText(modelsHashFile, currentHash); - - _logger.Logger.Debug("Done rebuilding."); - return assembly; - } - - private static Infos RegisterModels(IEnumerable types) - { - var ctorArgTypes = new[] { typeof (IPublishedElement) }; - var modelInfos = new Dictionary(StringComparer.InvariantCultureIgnoreCase); - var map = new Dictionary(); - - foreach (var type in types) - { - ConstructorInfo constructor = null; - Type parameterType = null; - - foreach (var ctor in type.GetConstructors()) - { - var parms = ctor.GetParameters(); - 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."); - constructor = ctor; - parameterType = parms[0].ParameterType; - } - } - - if (constructor == null) - throw new InvalidOperationException($"Type {type.FullName} is missing a public constructor with one argument of type, or implementing, IPropertySet."); - - var attribute = type.GetCustomAttribute(false); - var typeName = attribute == null ? type.Name : attribute.ContentTypeAlias; - - if (modelInfos.TryGetValue(typeName, out ModelInfo 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); - 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)); - - modelInfos[typeName] = new ModelInfo { ParameterType = parameterType, Ctor = func, ModelType = type }; - map[typeName] = type; - } - - return new Infos { ModelInfos = modelInfos.Count > 0 ? modelInfos : null, ModelTypeMap = map }; - } - - private static string GenerateModelsCode(IDictionary ourFiles, IList typeModels) - { - var modelsDirectory = UmbracoConfig.For.ModelsBuilder().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 codeBuilder = new StringBuilder(); - builder.Generate(codeBuilder, builder.GetModelsToGenerate()); - var code = codeBuilder.ToString(); - - return code; - } - - private static readonly Regex UsingRegex = new Regex("^using(.*);", RegexOptions.Compiled | RegexOptions.Multiline); - private static readonly Regex AattrRegex = new Regex("^\\[assembly:(.*)\\]", RegexOptions.Compiled | RegexOptions.Multiline); - - private static string GenerateModelsProj(IDictionary files) - { - // ideally we would generate a CSPROJ file but then we'd need a BuildProvider for csproj - // trying to keep things simple for the time being, just write everything to one big file - - // group all 'using' at the top of the file (else fails) - var usings = new List(); - foreach (var k in files.Keys.ToList()) - files[k] = UsingRegex.Replace(files[k], m => - { - usings.Add(m.Groups[1].Value); - return string.Empty; - }); - - // group all '[assembly:...]' at the top of the file (else fails) - var aattrs = new List(); - foreach (var k in files.Keys.ToList()) - files[k] = AattrRegex.Replace(files[k], m => - { - aattrs.Add(m.Groups[1].Value); - return string.Empty; - }); - - var text = new StringBuilder(); - foreach (var u in usings.Distinct()) - { - text.Append("using "); - text.Append(u); - text.Append(";\r\n"); - } - foreach (var a in aattrs) - { - text.Append("[assembly:"); - text.Append(a); - text.Append("]\r\n"); - } - text.Append("\r\n\r\n"); - foreach (var f in files) - { - text.Append("// FILE: "); - text.Append(f.Key); - text.Append("\r\n\r\n"); - text.Append(f.Value); - text.Append("\r\n\r\n\r\n"); - } - text.Append("// EOF\r\n"); - - return text.ToString(); - } - - internal class Infos - { - public Dictionary ModelTypeMap { get; set; } - public Dictionary ModelInfos { get; set; } - } - - internal class ModelInfo - { - public Type ParameterType { get; set; } - public Func Ctor { get; set; } - public Type ModelType { get; set; } - public Func ListCtor { get; set; } - } - - #endregion - - #region Watching - - private void WatcherOnChanged(object sender, FileSystemEventArgs args) - { - var changed = args.Name; - - // don't reset when our files change because we are building! - // - // comment it out, and always ignore our files, because it seems that some - // race conditions can occur on slow Cloud filesystems and then we keep - // rebuilding - - //if (_building && OurFiles.Contains(changed)) - //{ - // //_logger.Logger.Info("Ignoring files self-changes."); - // return; - //} - - // always ignore our own file changes - if (OurFiles.Contains(changed)) - return; - - _logger.Logger.Info("Detected files changes."); - - ResetModels(); - } - - public void Stop(bool immediate) - { - _watcher.EnableRaisingEvents = false; - _watcher.Dispose(); - HostingEnvironment.UnregisterObject(this); - } - - #endregion - } -} diff --git a/src/Umbraco.ModelsBuilder/Umbraco/UmbracoServices.cs b/src/Umbraco.ModelsBuilder/Umbraco/UmbracoServices.cs deleted file mode 100644 index f0347d9194..0000000000 --- a/src/Umbraco.ModelsBuilder/Umbraco/UmbracoServices.cs +++ /dev/null @@ -1,236 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Core; -using Umbraco.Core.Configuration; -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; - -namespace Umbraco.ModelsBuilder.Umbraco -{ - public class UmbracoServices - { - private readonly IContentTypeService _contentTypeService; - private readonly IMediaTypeService _mediaTypeService; - private readonly IMemberTypeService _memberTypeService; - private readonly IPublishedContentTypeFactory _publishedContentTypeFactory; - - public UmbracoServices(IContentTypeService contentTypeService, IMediaTypeService mediaTypeService, IMemberTypeService memberTypeService, IPublishedContentTypeFactory publishedContentTypeFactory) - { - _contentTypeService = contentTypeService; - _mediaTypeService = mediaTypeService; - _memberTypeService = memberTypeService; - _publishedContentTypeFactory = publishedContentTypeFactory; - } - - #region Services - - public IList GetAllTypes() - { - var types = new List(); - - 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())); - - return EnsureDistinctAliases(types); - } - - public IList GetContentTypes() - { - var contentTypes = _contentTypeService.GetAll().Cast().ToArray(); - return GetTypes(PublishedItemType.Content, contentTypes); // aliases have to be unique here - } - - public IList GetMediaTypes() - { - var contentTypes = _mediaTypeService.GetAll().Cast().ToArray(); - return GetTypes(PublishedItemType.Media, contentTypes); // aliases have to be unique here - } - - public IList GetMemberTypes() - { - var memberTypes = _memberTypeService.GetAll().Cast().ToArray(); - return GetTypes(PublishedItemType.Member, memberTypes); // aliases have to be unique here - } - - 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."); - } - } - - private IList GetTypes(PublishedItemType itemType, IContentTypeComposition[] contentTypes) - { - var typeModels = new List(); - var uniqueTypes = new HashSet(); - - // get the types and the properties - foreach (var contentType in contentTypes) - { - var typeModel = new TypeModel - { - Id = contentType.Id, - Alias = contentType.Alias, - ClrName = GetClrName(contentType.Name, contentType.Alias), - ParentId = contentType.ParentId, - - Name = contentType.Name, - Description = contentType.Description - }; - - // 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}\"."); - 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; - } - break; - case PublishedItemType.Media: - typeModel.ItemType = TypeModel.ItemTypes.Media; - break; - case PublishedItemType.Member: - typeModel.ItemType = TypeModel.ItemTypes.Member; - break; - default: - throw new InvalidOperationException(string.Format("Unsupported PublishedItemType \"{0}\".", itemType)); - } - - typeModels.Add(typeModel); - - foreach (var propertyType in contentType.PropertyTypes) - { - var propertyModel = new PropertyModel - { - Alias = propertyType.Alias, - ClrName = GetClrName(propertyType.Name, propertyType.Alias), - - Name = propertyType.Name, - Description = propertyType.Description - }; - - var publishedPropertyType = publishedContentType.GetPropertyType(propertyType.Alias); - if (publishedPropertyType == null) - throw new Exception($"Panic: could not get published property type {contentType.Alias}.{propertyType.Alias}."); - - propertyModel.ModelClrType = publishedPropertyType.ModelClrType; - - typeModel.Properties.Add(propertyModel); - } - } - - // wire the base types - foreach (var typeModel in typeModels.Where(x => x.ParentId > 0)) - { - typeModel.BaseType = typeModels.SingleOrDefault(x => x.Id == typeModel.ParentId); - // Umbraco 7.4 introduces content types containers, so even though ParentId > 0, the parent might - // not be a content type - here we assume that BaseType being null while ParentId > 0 means that - // the parent is a container (and we don't check). - typeModel.IsParent = typeModel.BaseType != null; - } - - // discover mixins - 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."); - - IEnumerable compositionTypes; - var contentTypeAsMedia = contentType as IMediaType; - var contentTypeAsContent = contentType as IContentType; - var contentTypeAsMember = contentType as IMemberType; - 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)); - - 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 (compositionType.Id == contentType.ParentId) continue; - - // add to mixins - typeModel.MixinTypes.Add(compositionModel); - - // mark as mixin - as well as parents - compositionModel.IsMixin = true; - while ((compositionModel = compositionModel.BaseType) != null) - compositionModel.IsMixin = true; - } - } - - return typeModels; - } - - internal static IList EnsureDistinctAliases(IList typeModels) - { - 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; - } - - #endregion - } -} 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.sln b/src/umbraco.sln index 7313e06e49..e603960782 100644 --- a/src/umbraco.sln +++ b/src/umbraco.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27004.2005 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29009.5 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Umbraco.Web.UI", "Umbraco.Web.UI\Umbraco.Web.UI.csproj", "{4C4C194C-B5E4-4991-8F87-4373E24CC19F}" EndProject @@ -103,6 +103,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "IssueTemplates", "IssueTemp ..\.github\ISSUE_TEMPLATE\5_Security_issue.md = ..\.github\ISSUE_TEMPLATE\5_Security_issue.md EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Umbraco.ModelsBuilder", "Umbraco.ModelsBuilder\Umbraco.ModelsBuilder.csproj", "{52AC0BA8-A60E-4E36-897B-E8B97A54ED1C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -135,6 +137,10 @@ Global {3A33ADC9-C6C0-4DB1-A613-A9AF0210DF3D}.Debug|Any CPU.Build.0 = Debug|Any CPU {3A33ADC9-C6C0-4DB1-A613-A9AF0210DF3D}.Release|Any CPU.ActiveCfg = Release|Any CPU {3A33ADC9-C6C0-4DB1-A613-A9AF0210DF3D}.Release|Any CPU.Build.0 = Release|Any CPU + {52AC0BA8-A60E-4E36-897B-E8B97A54ED1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {52AC0BA8-A60E-4E36-897B-E8B97A54ED1C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {52AC0BA8-A60E-4E36-897B-E8B97A54ED1C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {52AC0BA8-A60E-4E36-897B-E8B97A54ED1C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From ee83c02424492d60079e0f364b303f829936c04c Mon Sep 17 00:00:00 2001 From: Stephan Date: Mon, 24 Jun 2019 11:58:36 +0200 Subject: [PATCH 02/49] Import slimmed down ModelsBuilder --- src/Umbraco.ModelsBuilder/Api/ApiVersion.cs | 36 + src/Umbraco.ModelsBuilder/Building/Builder.cs | 236 ++++++ .../Building/PropertyModel.cs | 61 ++ .../Building/TextBuilder.cs | 579 +++++++++++++++ .../Building/TextHeaderWriter.cs | 26 + .../Building/TypeModel.cs | 205 ++++++ .../ConfigsExtensions.cs | 20 + .../Configuration/ClrNameSource.cs | 28 + .../Configuration/Config.cs | 224 ++++++ .../Configuration/ModelsMode.cs | 36 + .../Configuration/ModelsModeExtensions.cs | 37 + .../Dashboard/BuilderDashboardHelper.cs | 65 ++ .../EnumerableExtensions.cs | 33 + .../ImplementPropertyTypeAttribute.cs | 19 + .../ModelsBuilderAssemblyAttribute.cs | 23 + .../Properties/AssemblyInfo.cs | 30 +- .../PublishedElementExtensions.cs | 51 ++ .../PureLiveAssemblyAttribute.cs | 15 + .../ReferencedAssemblies.cs | 179 +++++ src/Umbraco.ModelsBuilder/TypeExtensions.cs | 22 + .../Umbraco.ModelsBuilder.csproj | 53 ++ .../Umbraco/HashCombiner.cs | 38 + .../Umbraco/HashHelper.cs | 42 ++ .../Umbraco/LiveModelsProvider.cs | 133 ++++ .../ModelsBuilderBackOfficeController.cs | 184 +++++ .../Umbraco/ModelsBuilderComponent.cs | 186 +++++ .../Umbraco/ModelsBuilderComposer.cs | 60 ++ .../Umbraco/ModelsBuilderInitializer.cs | 28 + .../Umbraco/ModelsGenerationError.cs | 62 ++ .../Umbraco/OutOfDateModelsStatus.cs | 57 ++ .../Umbraco/PublishedModelUtility.cs | 67 ++ .../Umbraco/PureLiveModelFactory.cs | 677 ++++++++++++++++++ .../Umbraco/UmbracoServices.cs | 198 +++++ .../Validation/ContentTypeModelValidator.cs | 93 +++ src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 4 + 35 files changed, 3779 insertions(+), 28 deletions(-) create mode 100644 src/Umbraco.ModelsBuilder/Api/ApiVersion.cs create mode 100644 src/Umbraco.ModelsBuilder/Building/Builder.cs create mode 100644 src/Umbraco.ModelsBuilder/Building/PropertyModel.cs create mode 100644 src/Umbraco.ModelsBuilder/Building/TextBuilder.cs create mode 100644 src/Umbraco.ModelsBuilder/Building/TextHeaderWriter.cs create mode 100644 src/Umbraco.ModelsBuilder/Building/TypeModel.cs create mode 100644 src/Umbraco.ModelsBuilder/ConfigsExtensions.cs create mode 100644 src/Umbraco.ModelsBuilder/Configuration/ClrNameSource.cs create mode 100644 src/Umbraco.ModelsBuilder/Configuration/Config.cs create mode 100644 src/Umbraco.ModelsBuilder/Configuration/ModelsMode.cs create mode 100644 src/Umbraco.ModelsBuilder/Configuration/ModelsModeExtensions.cs create mode 100644 src/Umbraco.ModelsBuilder/Dashboard/BuilderDashboardHelper.cs create mode 100644 src/Umbraco.ModelsBuilder/EnumerableExtensions.cs create mode 100644 src/Umbraco.ModelsBuilder/ImplementPropertyTypeAttribute.cs create mode 100644 src/Umbraco.ModelsBuilder/ModelsBuilderAssemblyAttribute.cs create mode 100644 src/Umbraco.ModelsBuilder/PublishedElementExtensions.cs create mode 100644 src/Umbraco.ModelsBuilder/PureLiveAssemblyAttribute.cs create mode 100644 src/Umbraco.ModelsBuilder/ReferencedAssemblies.cs create mode 100644 src/Umbraco.ModelsBuilder/TypeExtensions.cs create mode 100644 src/Umbraco.ModelsBuilder/Umbraco/HashCombiner.cs create mode 100644 src/Umbraco.ModelsBuilder/Umbraco/HashHelper.cs create mode 100644 src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProvider.cs create mode 100644 src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderBackOfficeController.cs create mode 100644 src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderComponent.cs create mode 100644 src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderComposer.cs create mode 100644 src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderInitializer.cs create mode 100644 src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerationError.cs create mode 100644 src/Umbraco.ModelsBuilder/Umbraco/OutOfDateModelsStatus.cs create mode 100644 src/Umbraco.ModelsBuilder/Umbraco/PublishedModelUtility.cs create mode 100644 src/Umbraco.ModelsBuilder/Umbraco/PureLiveModelFactory.cs create mode 100644 src/Umbraco.ModelsBuilder/Umbraco/UmbracoServices.cs create mode 100644 src/Umbraco.ModelsBuilder/Validation/ContentTypeModelValidator.cs diff --git a/src/Umbraco.ModelsBuilder/Api/ApiVersion.cs b/src/Umbraco.ModelsBuilder/Api/ApiVersion.cs new file mode 100644 index 0000000000..a7f2db397e --- /dev/null +++ b/src/Umbraco.ModelsBuilder/Api/ApiVersion.cs @@ -0,0 +1,36 @@ +using System; +using System.Reflection; +using Semver; + +namespace Umbraco.ModelsBuilder.Api +{ + /// + /// 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/Building/Builder.cs b/src/Umbraco.ModelsBuilder/Building/Builder.cs new file mode 100644 index 0000000000..fd5eccde5b --- /dev/null +++ b/src/Umbraco.ModelsBuilder/Building/Builder.cs @@ -0,0 +1,236 @@ +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.Composing; +using Umbraco.Core.Configuration; +using Umbraco.ModelsBuilder.Configuration; + +namespace Umbraco.ModelsBuilder.Building +{ + // NOTE + // The idea was to have different types of builder, because I wanted to experiment with + // building code with CodeDom. Turns out more complicated than I thought and maybe not + // worth it at the moment, to we're using TextBuilder and its Generate method is specific. + // + // Keeping the code as-is for the time being... + + /// + /// Provides a base class for all builders. + /// + internal abstract class Builder + { + private readonly IList _typeModels; + + protected Dictionary ModelsMap { get; } = new Dictionary(); + + private static Config Config => Current.Configs.ModelsBuilder(); + + // the list of assemblies that will be 'using' by default + protected readonly IList TypesUsing = new List + { + "System", + "System.Collections.Generic", + "System.Linq.Expressions", + "System.Web", + "Umbraco.Core.Models", + "Umbraco.Core.Models.PublishedContent", + "Umbraco.Web", + "Umbraco.ModelsBuilder", + "Umbraco.ModelsBuilder.Umbraco", + }; + + /// + /// Gets or sets a value indicating the namespace to use for the models. + /// + /// May be overriden by code attributes. + public string ModelsNamespace { get; set; } + + /// + /// Gets the list of assemblies to add to the set of 'using' assemblies in each model file. + /// + public IList Using => TypesUsing; + + /// + /// Gets the list of models to generate. + /// + /// The models to generate, ie those that are not ignored. + public IEnumerable GetModelsToGenerate() + { + return _typeModels; + } + + /// + /// Gets the list of all models. + /// + /// Includes those that are ignored. + internal IList TypeModels => _typeModels; + + /// + /// Initializes a new instance of the class with a list of models to generate + /// and the result of code parsing. + /// + /// The list of models to generate. + protected Builder(IList typeModels) + : this(typeModels, null) + { } + + /// + /// Initializes a new instance of the class with a list of models to generate, + /// the result of code parsing, and a models namespace. + /// + /// The list of models to generate. + /// The models namespace. + protected Builder(IList typeModels, string modelsNamespace) + { + _typeModels = typeModels ?? throw new ArgumentNullException(nameof(typeModels)); + + // can be null or empty, we'll manage + ModelsNamespace = modelsNamespace; + + // but we want it to prepare + Prepare(); + } + + // for unit tests only + protected Builder() + { } + + /// + /// 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() + { + TypeModel.MapModelTypes(_typeModels, ModelsNamespace); + + 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 + // things, and then they should pay attention to the generation error log - there's no magic here + // for the last one, don't throw in purelive, see comment + + // ensure we have no duplicates type names + 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) + 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) + { + 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}\"." + + $" CSharp does not support using the same name for the property with alias \"{xx.Alias}\"." + + " Consider using an attribute to assign a different name to the property."); + + // for purelive, will we generate a commented out properties with an error message, + // instead of throwing, because then it kills the sites and ppl don't understand why + xx.AddError($"The class {typeModel.ClrName} cannot implement this property, because" + + $" CSharp does not support naming the property with alias \"{xx.Alias}\" with the same name as content type with alias \"{typeModel.Alias}\"." + + " Consider using an attribute to assign a different name to the property."); + + // will not be implemented on interface nor class + // note: we will still create the static getter, and implement the property on other classes... + } + } + + // ensure we have no collision between base types + // NO: we may want to define a base class in a partial, on a model that has a parent + // we are NOT checking that the defined base type does maintain the inheritance chain + //foreach (var xx in _typeModels.Where(x => !x.IsContentIgnored).Where(x => x.BaseType != null && x.HasBase)) + // throw new InvalidOperationException(string.Format("Type alias \"{0}\" has more than one parent class.", + // xx.Alias)); + + // discover interfaces that need to be declared / implemented + foreach (var typeModel in _typeModels) + { + // 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.CollectImplems(parentImplems, typeModel.BaseType); + + // interfaces we must declare we implement (initially empty) + // ie this type's mixins, except those that have been removed, + // and except those that are already declared at the parent level + // in other words, DeclaringInterfaces is "local mixins" + var declaring = typeModel.MixinTypes + .Except(parentImplems); + typeModel.DeclaringInterfaces.AddRange(declaring); + + // interfaces we must actually implement (initially empty) + // if we declare we implement a mixin interface, we must actually implement + // its properties, all recursively (ie if the mixin interface implements...) + // so, starting with local mixins, we collect all the (non-removed) types above them + var mixinImplems = new List(); + foreach (var i in typeModel.DeclaringInterfaces) + TypeModel.CollectImplems(mixinImplems, i); + // and then we remove from that list anything that is already declared at the parent level + typeModel.ImplementingInterfaces.AddRange(mixinImplems.Except(parentImplems)); + } + + // ensure elements don't inherit from non-elements + foreach (var typeModel in _typeModels.Where(x => x.IsElement)) + { + if (typeModel.BaseType != null && !typeModel.BaseType.IsElement) + throw new Exception($"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 Exception($"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."); + } + } + + // looking for a simple symbol eg 'Umbraco' or 'String' + // expecting to match eg 'Umbraco' or 'System.String' + // returns true if either + // - more than 1 symbol is found (explicitely ambiguous) + // - 1 symbol is found BUT not matching (implicitely ambiguous) + protected bool IsAmbiguousSymbol(string symbol, string match) + { + // 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 + return true; + } + + internal string ModelsNamespaceForTests; + + public string GetModelsNamespace() + { + if (ModelsNamespaceForTests != null) + return ModelsNamespaceForTests; + + // 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 Config.ModelsNamespace; + } + + protected string GetModelsBaseClassName(TypeModel type) + { + // default + return type.IsElement ? "PublishedElementModel" : "PublishedContentModel"; + } + } +} diff --git a/src/Umbraco.ModelsBuilder/Building/PropertyModel.cs b/src/Umbraco.ModelsBuilder/Building/PropertyModel.cs new file mode 100644 index 0000000000..5f2545ec2a --- /dev/null +++ b/src/Umbraco.ModelsBuilder/Building/PropertyModel.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; + +namespace Umbraco.ModelsBuilder.Building +{ + /// + /// Represents a model property. + /// + public class PropertyModel + { + /// + /// Gets the alias of the property. + /// + public string Alias; + + /// + /// Gets the name of the property. + /// + public string Name; + + /// + /// Gets the description of the property. + /// + public string Description; + + /// + /// Gets the clr name of the property. + /// + /// This is just the local name eg "Price". + public string ClrName; + + /// + /// Gets the Model Clr type of the property values. + /// + /// As indicated by the PublishedPropertyType, ie by the IPropertyValueConverter + /// if any, else object. May include some ModelType that will need to be mapped. + public Type ModelClrType; + + /// + /// Gets the CLR type name of the property values. + /// + public string ClrTypeName; + + /// + /// Gets the generation errors for the property. + /// + /// This should be null, unless something prevents the property from being + /// generated, and then the value should explain what. This can be used to generate + /// commented out code eg in PureLive. + public List Errors; + + /// + /// Adds an error. + /// + public void AddError(string error) + { + if (Errors == null) Errors = new List(); + Errors.Add(error); + } + } +} diff --git a/src/Umbraco.ModelsBuilder/Building/TextBuilder.cs b/src/Umbraco.ModelsBuilder/Building/TextBuilder.cs new file mode 100644 index 0000000000..e15fdfca6e --- /dev/null +++ b/src/Umbraco.ModelsBuilder/Building/TextBuilder.cs @@ -0,0 +1,579 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; +using Umbraco.ModelsBuilder.Api; +using Umbraco.ModelsBuilder.Configuration; + +namespace Umbraco.ModelsBuilder.Building +{ + /// + /// Implements a builder that works by writing text. + /// + internal class TextBuilder : Builder + { + /// + /// 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. + public TextBuilder(IList typeModels) + : base(typeModels) + { } + + /// + /// Initializes a new instance of the class with a list of models to generate, + /// the result of code parsing, and a models namespace. + /// + /// The list of models to generate. + /// The models namespace. + public TextBuilder(IList typeModels, string modelsNamespace) + : base(typeModels, modelsNamespace) + { } + + // internal for unit tests only + internal TextBuilder() + { } + + private static Config Config => Current.Configs.ModelsBuilder(); + + /// + /// Outputs a generated model to a string builder. + /// + /// The string builder. + /// The model to generate. + public void Generate(StringBuilder sb, TypeModel typeModel) + { + WriteHeader(sb); + + foreach (var t in TypesUsing) + sb.AppendFormat("using {0};\n", t); + + sb.Append("\n"); + sb.AppendFormat("namespace {0}\n", GetModelsNamespace()); + sb.Append("{\n"); + + WriteContentType(sb, typeModel); + + sb.Append("}\n"); + } + + /// + /// Outputs generated models to a string builder. + /// + /// The string builder. + /// The models to generate. + public void Generate(StringBuilder sb, IEnumerable typeModels) + { + WriteHeader(sb); + + foreach (var t in TypesUsing) + sb.AppendFormat("using {0};\n", t); + + // assembly attributes marker + sb.Append("\n//ASSATTR\n"); + + sb.Append("\n"); + sb.AppendFormat("namespace {0}\n", GetModelsNamespace()); + sb.Append("{\n"); + + foreach (var typeModel in typeModels) + { + WriteContentType(sb, typeModel); + sb.Append("\n"); + } + + sb.Append("}\n"); + } + + /// + /// Outputs an "auto-generated" header to a string builder. + /// + /// The string builder. + public static void WriteHeader(StringBuilder sb) + { + 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; + + if (type.IsMixin) + { + // write the interface declaration + 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.HasBase ? null : (type.IsElement ? "PublishedElement" : "PublishedContent")) + : type.BaseType.ClrName; + if (implements != null) + sb.AppendFormat(" : I{0}", implements); + + // write the mixins + sep = implements == null ? ":" : ","; + foreach (var mixinType in type.DeclaringInterfaces.OrderBy(x => x.ClrName)) + { + sb.AppendFormat("{0} I{1}", sep, mixinType.ClrName); + sep = ","; + } + + sb.Append("\n\t{\n"); + + // write the properties - only the local (non-ignored) ones, we're an interface + var more = false; + foreach (var prop in type.Properties.OrderBy(x => x.ClrName)) + { + if (more) sb.Append("\n"); + more = true; + WriteInterfaceProperty(sb, prop); + } + + sb.Append("\t}\n\n"); + } + + // write the class declaration + if (!string.IsNullOrWhiteSpace(type.Name)) + sb.AppendFormat("\t/// {0}\n", XmlCommentString(type.Name)); + // cannot do it now. see note in ImplementContentTypeAttribute + //if (!type.HasImplement) + // sb.AppendFormat("\t[ImplementContentType(\"{0}\")]\n", type.Alias); + sb.AppendFormat("\t[PublishedModel(\"{0}\")]\n", type.Alias); + sb.AppendFormat("\tpublic partial class {0}", type.ClrName); + var inherits = type.HasBase + ? null // has its own base already + : (type.BaseType == null + ? GetModelsBaseClassName(type) + : type.BaseType.ClrName); + if (inherits != null) + sb.AppendFormat(" : {0}", inherits); + + sep = inherits == null ? ":" : ","; + if (type.IsMixin) + { + // if it's a mixin it implements its own interface + sb.AppendFormat("{0} I{1}", sep, type.ClrName); + } + else + { + // write the mixins, if any, as interfaces + // only if not a mixin because otherwise the interface already has them already + foreach (var mixinType in type.DeclaringInterfaces.OrderBy(x => x.ClrName)) + { + sb.AppendFormat("{0} I{1}", sep, mixinType.ClrName); + sep = ","; + } + } + + // begin class body + sb.Append("\n\t{\n"); + + // write the constants & static methods + // 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); + WriteGeneratedCodeAttribute(sb, "\t\t"); + sb.Append("\t\tpublic new static PublishedContentType GetModelContentType()\n"); + sb.Append("\t\t\t=> PublishedModelUtility.GetModelContentType(ModelItemType, ModelTypeAlias);\n"); + WriteGeneratedCodeAttribute(sb, "\t\t"); + sb.AppendFormat("\t\tpublic static PublishedPropertyType 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 + 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"); + WriteContentTypeProperties(sb, type); + + // close the class declaration + sb.Append("\t}\n"); + } + + private void WriteContentTypeProperties(StringBuilder sb, TypeModel type) + { + var staticMixinGetters = true; + + // write the properties + 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 + // and the parent defines its own properties. need to write the mixins properties + // since the mixins are only interfaces and we have to provide an implementation. + + // write the mixins properties + foreach (var mixinType in type.ImplementingInterfaces.OrderBy(x => x.ClrName)) + foreach (var prop in mixinType.Properties.OrderBy(x => x.ClrName)) + if (staticMixinGetters) + WriteMixinProperty(sb, prop, mixinType.ClrName); + else + WriteProperty(sb, mixinType, prop); + } + + private void WriteMixinProperty(StringBuilder sb, PropertyModel property, string mixinClrName) + { + sb.Append("\n"); + + // Adds xml summary to each property containing + // property name and property description + if (!string.IsNullOrWhiteSpace(property.Name) || !string.IsNullOrWhiteSpace(property.Description)) + { + sb.Append("\t\t///\n"); + + if (!string.IsNullOrWhiteSpace(property.Description)) + sb.AppendFormat("\t\t/// {0}: {1}\n", XmlCommentString(property.Name), XmlCommentString(property.Description)); + else + sb.AppendFormat("\t\t/// {0}\n", XmlCommentString(property.Name)); + + sb.Append("\t\t///\n"); + } + + WriteGeneratedCodeAttribute(sb, "\t\t"); + sb.AppendFormat("\t\t[ImplementPropertyType(\"{0}\")]\n", property.Alias); + + sb.Append("\t\tpublic "); + WriteClrType(sb, property.ClrTypeName); + + sb.AppendFormat(" {0} => ", + property.ClrName); + WriteNonGenericClrType(sb, GetModelsNamespace() + "." + mixinClrName); + sb.AppendFormat(".{0}(this);\n", + MixinStaticGetterName(property.ClrName)); + } + + private static string MixinStaticGetterName(string clrName) + { + return string.Format("Get{0}", clrName); + } + + private void WriteProperty(StringBuilder sb, TypeModel type, PropertyModel property, string mixinClrName = null) + { + var mixinStatic = mixinClrName != null; + + sb.Append("\n"); + + if (property.Errors != null) + { + sb.Append("\t\t/*\n"); + sb.Append("\t\t * THIS PROPERTY CANNOT BE IMPLEMENTED, BECAUSE:\n"); + sb.Append("\t\t *\n"); + var first = true; + foreach (var error in property.Errors) + { + if (first) first = false; + else sb.Append("\t\t *\n"); + foreach (var s in SplitError(error)) + { + sb.Append("\t\t * "); + sb.Append(s); + sb.Append("\n"); + } + } + sb.Append("\t\t *\n"); + sb.Append("\n"); + } + + // Adds xml summary to each property containing + // property name and property description + if (!string.IsNullOrWhiteSpace(property.Name) || !string.IsNullOrWhiteSpace(property.Description)) + { + sb.Append("\t\t///\n"); + + if (!string.IsNullOrWhiteSpace(property.Description)) + sb.AppendFormat("\t\t/// {0}: {1}\n", XmlCommentString(property.Name), XmlCommentString(property.Description)); + else + sb.AppendFormat("\t\t/// {0}\n", XmlCommentString(property.Name)); + + sb.Append("\t\t///\n"); + } + + WriteGeneratedCodeAttribute(sb, "\t\t"); + sb.AppendFormat("\t\t[ImplementPropertyType(\"{0}\")]\n", property.Alias); + + if (mixinStatic) + { + sb.Append("\t\tpublic "); + WriteClrType(sb, property.ClrTypeName); + sb.AppendFormat(" {0} => {1}(this);\n", + property.ClrName, MixinStaticGetterName(property.ClrName)); + } + else + { + sb.Append("\t\tpublic "); + WriteClrType(sb, property.ClrTypeName); + sb.AppendFormat(" {0} => this.Value", + property.ClrName); + if (property.ModelClrType != typeof(object)) + { + sb.Append("<"); + WriteClrType(sb, property.ClrTypeName); + sb.Append(">"); + } + sb.AppendFormat("(\"{0}\");\n", + property.Alias); + } + + if (property.Errors != null) + { + sb.Append("\n"); + sb.Append("\t\t *\n"); + sb.Append("\t\t */\n"); + } + + if (!mixinStatic) return; + + var mixinStaticGetterName = MixinStaticGetterName(property.ClrName); + + 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", + mixinStaticGetterName, mixinClrName); + if (property.ModelClrType != typeof(object)) + { + sb.Append("<"); + WriteClrType(sb, property.ClrTypeName); + sb.Append(">"); + } + sb.AppendFormat("(\"{0}\");\n", + property.Alias); + } + + private static IEnumerable SplitError(string error) + { + var p = 0; + while (p < error.Length) + { + var n = p + 50; + while (n < error.Length && error[n] != ' ') n++; + if (n >= error.Length) break; + yield return error.Substring(p, n - p); + p = n + 1; + } + if (p < error.Length) + yield return error.Substring(p); + } + + private void WriteInterfaceProperty(StringBuilder sb, PropertyModel property) + { + if (property.Errors != null) + { + sb.Append("\t\t/*\n"); + sb.Append("\t\t * THIS PROPERTY CANNOT BE IMPLEMENTED, BECAUSE:\n"); + sb.Append("\t\t *\n"); + var first = true; + foreach (var error in property.Errors) + { + if (first) first = false; + else sb.Append("\t\t *\n"); + foreach (var s in SplitError(error)) + { + sb.Append("\t\t * "); + sb.Append(s); + sb.Append("\n"); + } + } + sb.Append("\t\t *\n"); + sb.Append("\n"); + } + + 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", + property.ClrName); + + if (property.Errors != null) + { + sb.Append("\n"); + sb.Append("\t\t *\n"); + sb.Append("\t\t */\n"); + } + } + + // internal for unit tests + internal void WriteClrType(StringBuilder sb, Type type) + { + var s = type.ToString(); + + if (type.IsGenericType) + { + var p = s.IndexOf('`'); + WriteNonGenericClrType(sb, s.Substring(0, p)); + sb.Append("<"); + var args = type.GetGenericArguments(); + for (var i = 0; i < args.Length; i++) + { + if (i > 0) sb.Append(", "); + WriteClrType(sb, args[i]); + } + sb.Append(">"); + } + else + { + WriteNonGenericClrType(sb, s); + } + } + + internal void WriteClrType(StringBuilder sb, string type) + { + var p = type.IndexOf('<'); + if (type.Contains('<')) + { + WriteNonGenericClrType(sb, type.Substring(0, p)); + sb.Append("<"); + var args = type.Substring(p + 1).TrimEnd('>').Split(','); // fixme will NOT work with nested generic types + for (var i = 0; i < args.Length; i++) + { + if (i > 0) sb.Append(", "); + WriteClrType(sb, args[i]); + } + sb.Append(">"); + } + else + { + WriteNonGenericClrType(sb, type); + } + } + + private void WriteNonGenericClrType(StringBuilder sb, string s) + { + // map model types + s = Regex.Replace(s, @"\{(.*)\}\[\*\]", m => ModelsMap[m.Groups[1].Value + "[]"]); + + // takes care eg of "System.Int32" vs. "int" + if (TypesMap.TryGetValue(s, out string typeName)) + { + sb.Append(typeName); + return; + } + + // if full type name matches a using clause, strip + // so if we want Umbraco.Core.Models.IPublishedContent + // and using Umbraco.Core.Models, then we just need IPublishedContent + typeName = s; + string typeUsing = null; + var p = typeName.LastIndexOf('.'); + if (p > 0) + { + var x = typeName.Substring(0, p); + if (Using.Contains(x)) + { + 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 + typeName = typeName.Replace("+", "."); + + // symbol to test is the first part of the name + // so if type name is Foo.Bar.Nil we want to ensure that Foo is not ambiguous + p = typeName.IndexOf('.'); + var symbol = p > 0 ? typeName.Substring(0, p) : typeName; + + // what we should find - WITHOUT any generic thing - just the type + // no 'using' = the exact symbol + // a 'using' = using.symbol + var match = typeUsing == null ? symbol : (typeUsing + "." + symbol); + + // if not ambiguous, be happy + if (!IsAmbiguousSymbol(symbol, match)) + { + sb.Append(typeName); + return; + } + + // symbol is ambiguous + // if no 'using', must prepend global:: + if (typeUsing == null) + { + sb.Append("global::"); + sb.Append(s.Replace("+", ".")); + return; + } + + // could fullname be non-ambiguous? + // note: all-or-nothing, not trying to segment the using clause + typeName = s.Replace("+", "."); + p = typeName.IndexOf('.'); + symbol = typeName.Substring(0, p); + match = symbol; + + // still ambiguous, must prepend global:: + if (IsAmbiguousSymbol(symbol, match)) + sb.Append("global::"); + + sb.Append(typeName); + } + + private static string XmlCommentString(string s) + { + return s.Replace('<', '{').Replace('>', '}').Replace('\r', ' ').Replace('\n', ' '); + } + + 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" } + }; + } +} diff --git a/src/Umbraco.ModelsBuilder/Building/TextHeaderWriter.cs b/src/Umbraco.ModelsBuilder/Building/TextHeaderWriter.cs new file mode 100644 index 0000000000..d165f03907 --- /dev/null +++ b/src/Umbraco.ModelsBuilder/Building/TextHeaderWriter.cs @@ -0,0 +1,26 @@ +using System.Text; +using Umbraco.ModelsBuilder.Api; + +namespace Umbraco.ModelsBuilder.Building +{ + public static class TextHeaderWriter + { + /// + /// Outputs an "auto-generated" header to a string builder. + /// + /// The string builder. + public static void WriteHeader(StringBuilder sb) + { + sb.Append("//------------------------------------------------------------------------------\n"); + sb.Append("// \n"); + sb.Append("// This code was generated by a tool.\n"); + sb.Append("//\n"); + sb.AppendFormat("// Umbraco.ModelsBuilder v{0}\n", ApiVersion.Current.Version); + sb.Append("//\n"); + sb.Append("// Changes to this file will be lost if the code is regenerated.\n"); + sb.Append("// \n"); + sb.Append("//------------------------------------------------------------------------------\n"); + sb.Append("\n"); + } + } +} diff --git a/src/Umbraco.ModelsBuilder/Building/TypeModel.cs b/src/Umbraco.ModelsBuilder/Building/TypeModel.cs new file mode 100644 index 0000000000..941894774e --- /dev/null +++ b/src/Umbraco.ModelsBuilder/Building/TypeModel.cs @@ -0,0 +1,205 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Models.PublishedContent; + +namespace Umbraco.ModelsBuilder.Building +{ + /// + /// Represents a model. + /// + public class TypeModel + { + /// + /// Gets the unique identifier of the corresponding content type. + /// + public int Id; + + /// + /// Gets the alias of the model. + /// + public string Alias; + + /// + /// Gets the name of the content type. + /// + public string Name; + + /// + /// Gets the description of the content type. + /// + public string Description; + + /// + /// Gets the clr name of the model. + /// + /// This is the complete name eg "Foo.Bar.MyContent". + public string ClrName; + + /// + /// Gets the unique identifier of the parent. + /// + /// The parent can either be a base content type, or a content types container. If the content + /// type does not have a base content type, then returns -1. + public int ParentId; + + /// + /// Gets the base model. + /// + /// + /// If the content type does not have a base content type, then returns null. + /// The current model inherits from its base model. + /// + public TypeModel BaseType; // the parent type in Umbraco (type inherits its properties) + + /// + /// Gets the list of properties that are defined by this model. + /// + /// These are only those property that are defined locally by this model, + /// and the list does not contain properties inherited from base models or from mixins. + public readonly List Properties = new List(); + + /// + /// Gets the mixin models. + /// + /// The current model implements mixins. + public readonly List MixinTypes = new List(); + + /// + /// Gets the list of interfaces that this model needs to declare it implements. + /// + /// Some of these interfaces may actually be implemented by a base model + /// that this model inherits from. + public readonly List DeclaringInterfaces = new List(); + + /// + /// Gets the list of interfaces that this model needs to actually implement. + /// + public readonly List ImplementingInterfaces = new List(); + + /// + /// Gets the list of existing static mixin method candidates. + /// + public readonly List StaticMixinMethods = new List(); + + /// + /// Gets a value indicating whether this model has a base class. + /// + /// Can be either because the content type has a base content type declared in Umbraco, + /// or because the existing user's code declares a base class for this model. + public bool HasBase; + + /// + /// Gets a value indicating whether this model is used as a mixin by another model. + /// + public bool IsMixin; + + /// + /// Gets a value indicating whether this model is the base model of another model. + /// + public bool IsParent; + + /// + /// Gets a value indicating whether the type is an element. + /// + public bool IsElement => ItemType == ItemTypes.Element; + + /// + /// Represents the different model item types. + /// + public enum ItemTypes + { + /// + /// Element. + /// + Element, + + /// + /// Content. + /// + Content, + + /// + /// Media. + /// + Media, + + /// + /// Member. + /// + Member + } + + private ItemTypes _itemType; + + /// + /// Gets or sets the model item type. + /// + public ItemTypes ItemType + { + get { return _itemType; } + set + { + switch (value) + { + case ItemTypes.Element: + case ItemTypes.Content: + case ItemTypes.Media: + case ItemTypes.Member: + _itemType = value; + break; + default: + throw new ArgumentException("value"); + } + } + } + + /// + /// Recursively collects all types inherited, or implemented as interfaces, by a specified type. + /// + /// The collection. + /// The type. + /// Includes the specified type. + internal static void CollectImplems(ICollection types, TypeModel type) + { + if (types.Contains(type) == false) + types.Add(type); + if (type.BaseType != null) + CollectImplems(types, type.BaseType); + foreach (var mixin in type.MixinTypes) + CollectImplems(types, mixin); + } + + /// + /// Enumerates the base models starting from the current model up. + /// + /// Indicates whether the enumeration should start with the current model + /// or from its base model. + /// The base models. + public IEnumerable EnumerateBaseTypes(bool andSelf = false) + { + var typeModel = andSelf ? this : BaseType; + while (typeModel != null) + { + yield return typeModel; + 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/ConfigsExtensions.cs b/src/Umbraco.ModelsBuilder/ConfigsExtensions.cs new file mode 100644 index 0000000000..dc0b136422 --- /dev/null +++ b/src/Umbraco.ModelsBuilder/ConfigsExtensions.cs @@ -0,0 +1,20 @@ +using Umbraco.Core.Configuration; +using Umbraco.ModelsBuilder.Configuration; + +namespace Umbraco.ModelsBuilder +{ + /// + /// 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 Config ModelsBuilder(this Configs configs) + => configs.GetConfig(); + } +} \ No newline at end of file diff --git a/src/Umbraco.ModelsBuilder/Configuration/ClrNameSource.cs b/src/Umbraco.ModelsBuilder/Configuration/ClrNameSource.cs new file mode 100644 index 0000000000..d195846411 --- /dev/null +++ b/src/Umbraco.ModelsBuilder/Configuration/ClrNameSource.cs @@ -0,0 +1,28 @@ +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/Config.cs b/src/Umbraco.ModelsBuilder/Configuration/Config.cs new file mode 100644 index 0000000000..8fdc10d5dc --- /dev/null +++ b/src/Umbraco.ModelsBuilder/Configuration/Config.cs @@ -0,0 +1,224 @@ +using System; +using System.Configuration; +using System.IO; +using System.Web.Configuration; +using System.Web.Hosting; +using Umbraco.Core; + +namespace Umbraco.ModelsBuilder.Configuration +{ + /// + /// Represents the models builder configuration. + /// + public class Config + { + internal const string DefaultModelsNamespace = "Umbraco.Web.PublishedModels"; + internal const string DefaultModelsDirectory = "~/App_Data/Models"; + + /// + /// Initializes a new instance of the class. + /// + public Config() + { + const string prefix = "Umbraco.ModelsBuilder."; + + // giant kill switch, default: false + // must be explicitely set to true for anything else to happen + Enable = ConfigurationManager.AppSettings[prefix + "Enable"] == "true"; + + // ensure defaults are initialized for tests + ModelsNamespace = DefaultModelsNamespace; + ModelsDirectory = HostingEnvironment.IsHosted + ? HostingEnvironment.MapPath(DefaultModelsDirectory) + : DefaultModelsDirectory.TrimStart("~/"); + DebugLevel = 0; + + // stop here, everything is false + if (!Enable) return; + + // mode + var modelsMode = ConfigurationManager.AppSettings[prefix + "ModelsMode"]; + if (!string.IsNullOrWhiteSpace(modelsMode)) + { + switch (modelsMode) + { + case nameof(ModelsMode.Nothing): + ModelsMode = ModelsMode.Nothing; + break; + case nameof(ModelsMode.PureLive): + ModelsMode = ModelsMode.PureLive; + break; + case nameof(ModelsMode.AppData): + ModelsMode = ModelsMode.AppData; + break; + case nameof(ModelsMode.LiveAppData): + ModelsMode = ModelsMode.LiveAppData; + break; + default: + throw new ConfigurationErrorsException($"ModelsMode \"{modelsMode}\" is not a valid mode." + + " Note that modes are case-sensitive. Possible values are: " + string.Join(", ", Enum.GetNames(typeof(ModelsMode)))); + } + } + + // default: false + AcceptUnsafeModelsDirectory = ConfigurationManager.AppSettings[prefix + "AcceptUnsafeModelsDirectory"].InvariantEquals("true"); + + // default: true + EnableFactory = !ConfigurationManager.AppSettings[prefix + "EnableFactory"].InvariantEquals("false"); + FlagOutOfDateModels = !ConfigurationManager.AppSettings[prefix + "FlagOutOfDateModels"].InvariantEquals("false"); + + // default: initialized above with DefaultModelsNamespace const + var value = ConfigurationManager.AppSettings[prefix + "ModelsNamespace"]; + if (!string.IsNullOrWhiteSpace(value)) + ModelsNamespace = value; + + // default: initialized above with DefaultModelsDirectory const + value = ConfigurationManager.AppSettings[prefix + "ModelsDirectory"]; + if (!string.IsNullOrWhiteSpace(value)) + { + var root = HostingEnvironment.IsHosted + ? HostingEnvironment.MapPath("~/") + : Directory.GetCurrentDirectory(); + if (root == null) + throw new ConfigurationErrorsException("Could not determine root directory."); + + // GetModelsDirectory will ensure that the path is safe + ModelsDirectory = GetModelsDirectory(root, value, AcceptUnsafeModelsDirectory); + } + + // default: 0 + value = ConfigurationManager.AppSettings[prefix + "DebugLevel"]; + if (!string.IsNullOrWhiteSpace(value)) + { + int debugLevel; + if (!int.TryParse(value, out debugLevel)) + throw new ConfigurationErrorsException($"Invalid debug level \"{value}\"."); + DebugLevel = debugLevel; + } + + // not flagging if not generating, or live (incl. pure) + if (ModelsMode == ModelsMode.Nothing || ModelsMode.IsLive()) + FlagOutOfDateModels = false; + } + + /// + /// Initializes a new instance of the class. + /// + public Config( + bool enable = false, + ModelsMode modelsMode = ModelsMode.Nothing, + string modelsNamespace = null, + bool enableFactory = true, + bool flagOutOfDateModels = true, + string modelsDirectory = null, + bool acceptUnsafeModelsDirectory = false, + int debugLevel = 0) + { + Enable = enable; + ModelsMode = modelsMode; + + ModelsNamespace = string.IsNullOrWhiteSpace(modelsNamespace) ? DefaultModelsNamespace : modelsNamespace; + EnableFactory = enableFactory; + FlagOutOfDateModels = flagOutOfDateModels; + ModelsDirectory = string.IsNullOrWhiteSpace(modelsDirectory) ? DefaultModelsDirectory : modelsDirectory; + AcceptUnsafeModelsDirectory = acceptUnsafeModelsDirectory; + DebugLevel = debugLevel; + } + + // internal for tests + internal static string GetModelsDirectory(string root, string config, bool acceptUnsafe) + { + // making sure it is safe, ie under the website root, + // unless AcceptUnsafeModelsDirectory and then everything is OK. + + if (!Path.IsPathRooted(root)) + throw new ConfigurationErrorsException($"Root is not rooted \"{root}\"."); + + if (config.StartsWith("~/")) + { + var dir = Path.Combine(root, config.TrimStart("~/")); + + // sanitize - GetFullPath will take care of any relative + // segments in path, eg '../../foo.tmp' - it may throw a SecurityException + // if the combined path reaches illegal parts of the filesystem + dir = Path.GetFullPath(dir); + root = Path.GetFullPath(root); + + if (!dir.StartsWith(root) && !acceptUnsafe) + throw new ConfigurationErrorsException($"Invalid models directory \"{config}\"."); + + return dir; + } + + if (acceptUnsafe) + return Path.GetFullPath(config); + + throw new ConfigurationErrorsException($"Invalid models directory \"{config}\"."); + } + + /// + /// Gets a value indicating whether the whole models experience is enabled. + /// + /// + /// If this is false then absolutely nothing happens. + /// Default value is false which means that unless we have this setting, nothing happens. + /// + public bool Enable { get; } + + /// + /// Gets the models mode. + /// + public ModelsMode ModelsMode { get; } + + /// + /// Gets a value indicating whether system.web/compilation/@debug is true. + /// + public bool IsDebug + { + get + { + var section = (CompilationSection) ConfigurationManager.GetSection("system.web/compilation"); + return section != null && section.Debug; + } + } + + /// + /// Gets the models namespace. + /// + /// That value could be overriden by other (attribute in user's code...). Return default if no value was supplied. + public string ModelsNamespace { get; } + + /// + /// Gets a value indicating whether we should enable the models factory. + /// + /// Default value is true because no factory is enabled by default in Umbraco. + public bool EnableFactory { get; } + + /// + /// Gets a value indicating whether we should flag out-of-date models. + /// + /// Models become out-of-date when data types or content types are updated. When this + /// setting is activated the ~/App_Data/Models/ood.txt file is then created. When models are + /// generated through the dashboard, the files is cleared. Default value is false. + public bool FlagOutOfDateModels { get; } + + /// + /// Gets the models directory. + /// + /// Default is ~/App_Data/Models but that can be changed. + public string ModelsDirectory { get; } + + /// + /// Gets a value indicating whether to accept an unsafe value for ModelsDirectory. + /// + /// An unsafe value is an absolute path, or a relative path pointing outside + /// of the website root. + public bool AcceptUnsafeModelsDirectory { get; } + + /// + /// Gets a value indicating the debug log level. + /// + /// 0 means minimal (safe on live site), anything else means more and more details (maybe not safe). + public int DebugLevel { get; } + } +} diff --git a/src/Umbraco.ModelsBuilder/Configuration/ModelsMode.cs b/src/Umbraco.ModelsBuilder/Configuration/ModelsMode.cs new file mode 100644 index 0000000000..cc36099bc5 --- /dev/null +++ b/src/Umbraco.ModelsBuilder/Configuration/ModelsMode.cs @@ -0,0 +1,36 @@ +namespace Umbraco.ModelsBuilder.Configuration +{ + /// + /// Defines the models generation modes. + /// + public enum ModelsMode + { + /// + /// Do not generate models. + /// + Nothing = 0, // default value + + /// + /// Generate models in memory. + /// When: a content type change occurs. + /// + /// The app does not restart. Models are available in views exclusively. + PureLive, + + /// + /// Generate models in AppData. + /// When: generation is triggered. + /// + /// Generation can be triggered from the dashboard. The app does not restart. + /// Models are not compiled and thus are not available to the project. + AppData, + + /// + /// Generate models in AppData. + /// When: a content type change occurs, or generation is triggered. + /// + /// 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 + } +} diff --git a/src/Umbraco.ModelsBuilder/Configuration/ModelsModeExtensions.cs b/src/Umbraco.ModelsBuilder/Configuration/ModelsModeExtensions.cs new file mode 100644 index 0000000000..f40d7973b8 --- /dev/null +++ b/src/Umbraco.ModelsBuilder/Configuration/ModelsModeExtensions.cs @@ -0,0 +1,37 @@ +namespace Umbraco.ModelsBuilder.Configuration +{ + /// + /// Provides extensions for the enumeration. + /// + public static class ModelsModeExtensions + { + /// + /// Gets a value indicating whether the mode is LiveAnything or PureLive. + /// + public static bool IsLive(this ModelsMode modelsMode) + { + return + modelsMode == ModelsMode.PureLive + || modelsMode == ModelsMode.LiveAppData; + } + + /// + /// Gets a value indicating whether the mode is LiveAnything but not PureLive. + /// + public static bool IsLiveNotPure(this ModelsMode modelsMode) + { + return + modelsMode == ModelsMode.LiveAppData; + } + + /// + /// Gets a value indicating whether the mode supports explicit generation (as opposed to pure live). + /// + public static bool SupportsExplicitGeneration(this ModelsMode modelsMode) + { + return + modelsMode == ModelsMode.AppData + || modelsMode == ModelsMode.LiveAppData; + } + } +} diff --git a/src/Umbraco.ModelsBuilder/Dashboard/BuilderDashboardHelper.cs b/src/Umbraco.ModelsBuilder/Dashboard/BuilderDashboardHelper.cs new file mode 100644 index 0000000000..75c318faab --- /dev/null +++ b/src/Umbraco.ModelsBuilder/Dashboard/BuilderDashboardHelper.cs @@ -0,0 +1,65 @@ +using System.Text; +using Umbraco.Core.Composing; +using Umbraco.ModelsBuilder.Configuration; +using Umbraco.ModelsBuilder.Umbraco; + +namespace Umbraco.ModelsBuilder.Dashboard +{ + internal static class BuilderDashboardHelper + { + private static Config Config => Current.Configs.ModelsBuilder(); + + public static bool CanGenerate() + { + return Config.ModelsMode.SupportsExplicitGeneration(); + } + + public static bool AreModelsOutOfDate() + { + return OutOfDateModelsStatus.IsOutOfDate; + } + + public static string LastError() + { + return ModelsGenerationError.GetLastError(); + } + + public static string Text() + { + var config = Config; + + if (!config.Enable) + return "Version: " + Api.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(Api.ApiVersion.Current.Version); + sb.Append("
 
"); + + sb.Append("ModelsBuilder is enabled, with the following configuration:"); + + sb.Append("
    "); + + sb.Append("
  • The models factory is "); + sb.Append(config.EnableFactory || config.ModelsMode == ModelsMode.PureLive + ? "enabled" + : "not enabled. Umbraco will not use models"); + sb.Append(".
  • "); + + sb.Append(config.ModelsMode != ModelsMode.Nothing + ? $"
  • {config.ModelsMode} models are enabled.
  • " + : "
  • No models mode is specified: models will not be generated.
  • "); + + sb.Append($"
  • Models namespace is {config.ModelsNamespace}.
  • "); + + sb.Append("
  • Tracking of out-of-date models is "); + sb.Append(config.FlagOutOfDateModels ? "enabled" : "not enabled"); + sb.Append(".
  • "); + + sb.Append("
"); + + return sb.ToString(); + } + } +} diff --git a/src/Umbraco.ModelsBuilder/EnumerableExtensions.cs b/src/Umbraco.ModelsBuilder/EnumerableExtensions.cs new file mode 100644 index 0000000000..da77bfa958 --- /dev/null +++ b/src/Umbraco.ModelsBuilder/EnumerableExtensions.cs @@ -0,0 +1,33 @@ +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/ImplementPropertyTypeAttribute.cs b/src/Umbraco.ModelsBuilder/ImplementPropertyTypeAttribute.cs new file mode 100644 index 0000000000..a3dba740c1 --- /dev/null +++ b/src/Umbraco.ModelsBuilder/ImplementPropertyTypeAttribute.cs @@ -0,0 +1,19 @@ +using System; + +namespace Umbraco.ModelsBuilder +{ + /// + /// Indicates that a property implements a given property alias. + /// + /// And therefore it should not be generated. + [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)] + public sealed class ImplementPropertyTypeAttribute : Attribute + { + public ImplementPropertyTypeAttribute(string alias) + { + Alias = alias; + } + + public string Alias { get; private set; } + } +} diff --git a/src/Umbraco.ModelsBuilder/ModelsBuilderAssemblyAttribute.cs b/src/Umbraco.ModelsBuilder/ModelsBuilderAssemblyAttribute.cs new file mode 100644 index 0000000000..ed956852f8 --- /dev/null +++ b/src/Umbraco.ModelsBuilder/ModelsBuilderAssemblyAttribute.cs @@ -0,0 +1,23 @@ +using System; + +namespace Umbraco.ModelsBuilder +{ + /// + /// Indicates that an Assembly is a Models Builder assembly. + /// + [AttributeUsage(AttributeTargets.Assembly /*, AllowMultiple = false, Inherited = false*/)] + public sealed class ModelsBuilderAssemblyAttribute : Attribute + { + /// + /// Gets or sets a value indicating whether the assembly is a PureLive assembly. + /// + /// A Models Builder assembly can be either PureLive or normal Dll. + public bool PureLive { get; set; } + + /// + /// Gets or sets a hash value representing the state of the custom source code files + /// and the Umbraco content types that were used to generate and compile the assembly. + /// + public string SourceHash { get; set; } + } +} diff --git a/src/Umbraco.ModelsBuilder/Properties/AssemblyInfo.cs b/src/Umbraco.ModelsBuilder/Properties/AssemblyInfo.cs index 8ec221bffb..b576807bb7 100644 --- a/src/Umbraco.ModelsBuilder/Properties/AssemblyInfo.cs +++ b/src/Umbraco.ModelsBuilder/Properties/AssemblyInfo.cs @@ -1,36 +1,10 @@ using System.Reflection; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. [assembly: AssemblyTitle("Umbraco.ModelsBuilder")] -[assembly: AssemblyDescription("")] +[assembly: AssemblyDescription("Umbraco ModelsBuilder")] [assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Umbraco.ModelsBuilder")] -[assembly: AssemblyCopyright("Copyright © 2019")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] +[assembly: AssemblyProduct("Umbraco CMS")] -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. [assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("52ac0ba8-a60e-4e36-897b-e8b97a54ed1c")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/Umbraco.ModelsBuilder/PublishedElementExtensions.cs b/src/Umbraco.ModelsBuilder/PublishedElementExtensions.cs new file mode 100644 index 0000000000..1211c4fdad --- /dev/null +++ b/src/Umbraco.ModelsBuilder/PublishedElementExtensions.cs @@ -0,0 +1,51 @@ +using System; +using System.Linq.Expressions; +using System.Reflection; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.ModelsBuilder; + +// same namespace as original Umbraco.Web PublishedElementExtensions +// ReSharper disable once CheckNamespace +namespace Umbraco.Web +{ + /// + /// Provides extension methods to models. + /// + public static class PublishedElementExtensions + { + /// + /// Gets the value of a property. + /// + 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, 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) + throw new ArgumentException("Not a proper lambda expression (lambda).", nameof(property)); + + var lambda = (LambdaExpression) property; + var lambdaBody = lambda.Body; + + if (lambdaBody.NodeType != ExpressionType.MemberAccess) + throw new ArgumentException("Not a proper lambda expression (body).", nameof(property)); + + var memberExpression = (MemberExpression) lambdaBody; + if (memberExpression.Expression.NodeType != ExpressionType.Parameter) + throw new ArgumentException("Not a proper lambda expression (member).", nameof(property)); + + var member = memberExpression.Member; + + var attribute = member.GetCustomAttribute(); + if (attribute == null) + throw new InvalidOperationException("Property is not marked with ImplementPropertyType attribute."); + + return attribute.Alias; + } + } +} diff --git a/src/Umbraco.ModelsBuilder/PureLiveAssemblyAttribute.cs b/src/Umbraco.ModelsBuilder/PureLiveAssemblyAttribute.cs new file mode 100644 index 0000000000..dfe369dc21 --- /dev/null +++ b/src/Umbraco.ModelsBuilder/PureLiveAssemblyAttribute.cs @@ -0,0 +1,15 @@ +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/ReferencedAssemblies.cs b/src/Umbraco.ModelsBuilder/ReferencedAssemblies.cs new file mode 100644 index 0000000000..e0a20eb9d6 --- /dev/null +++ b/src/Umbraco.ModelsBuilder/ReferencedAssemblies.cs @@ -0,0 +1,179 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Web.Compilation; +using System.Web.Hosting; +using Umbraco.Core; + +namespace Umbraco.ModelsBuilder +{ + internal static class ReferencedAssemblies + { + private static readonly Lazy> LazyLocations; + + static ReferencedAssemblies() + { + LazyLocations = new Lazy>(() => HostingEnvironment.IsHosted + ? GetAllReferencedAssembliesLocationFromBuildManager() + : GetAllReferencedAssembliesFromDomain()); + } + + /// + /// Gets the assembly locations of all the referenced assemblies, that + /// are not dynamic, and have a non-null nor empty location. + /// + public static IEnumerable Locations => LazyLocations.Value; + + public static Assembly GetNetStandardAssembly(List assemblies) + { + if (assemblies == null) + assemblies = BuildManager.GetReferencedAssemblies().Cast().ToList(); + + // 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() + { + 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) + .Distinct() + .ToList(); + } + + // non-hosted, do our best + private static IEnumerable GetAllReferencedAssembliesFromDomain() + { + //TODO: This method has bugs since I've been stuck in an infinite loop with it, though this shouldn't + // execute while in the web application anyways. + + var assemblies = new List(); + var tmp1 = new List(); + var failed = new List(); + foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies() + .Where(x => x.IsDynamic == false) + .Where(x => !string.IsNullOrWhiteSpace(x.Location))) // though... IsDynamic should be enough? + { + assemblies.Add(assembly); + tmp1.Add(assembly); + } + + // fixme - AssemblyUtility questions + // - should we also load everything that's in the same directory? + // - do we want to load in the current app domain? + // - if this runs within Umbraco then we have already loaded them all? + + while (tmp1.Count > 0) + { + var tmp2 = tmp1 + .SelectMany(x => x.GetReferencedAssemblies()) + .Distinct() + .Where(x => assemblies.All(xx => x.FullName != xx.FullName)) // we don't have it already + .Where(x => failed.All(xx => x.FullName != xx.FullName)) // it hasn't failed already + .ToArray(); + tmp1.Clear(); + foreach (var assemblyName in tmp2) + { + try + { + var assembly = AppDomain.CurrentDomain.Load(assemblyName); + assemblies.Add(assembly); + tmp1.Add(assembly); + } + catch + { + failed.Add(assemblyName); + } + } + } + return assemblies.Select(x => x.Location).Distinct(); + } + + + // ---- + + 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) + { + try + { + return AppDomain.CurrentDomain.Load(name); + } + catch (Exception) + { + //Console.WriteLine(name); + return null; + } + } + } +} diff --git a/src/Umbraco.ModelsBuilder/TypeExtensions.cs b/src/Umbraco.ModelsBuilder/TypeExtensions.cs new file mode 100644 index 0000000000..d3b3ff6b4e --- /dev/null +++ b/src/Umbraco.ModelsBuilder/TypeExtensions.cs @@ -0,0 +1,22 @@ +using System; + +namespace Umbraco.ModelsBuilder +{ + internal static class TypeExtensions + { + /// + /// Creates a generic instance of a generic type with the proper actual type of an object. + /// + /// A generic type such as Something{} + /// An object whose type is used as generic type param. + /// Arguments for the constructor. + /// A generic instance of the generic type with the proper type. + /// Usage... typeof (Something{}).CreateGenericInstance(object1, object2, object3) will return + /// a Something{Type1} if object1.GetType() is Type1. + public static object CreateGenericInstance(this Type genericType, object typeParmObj, params object[] ctorArgs) + { + var type = genericType.MakeGenericType(typeParmObj.GetType()); + return Activator.CreateInstance(type, ctorArgs); + } + } +} diff --git a/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj b/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj index d1d3ca205d..5b0272a526 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj +++ b/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj @@ -32,7 +32,10 @@ + + + @@ -41,7 +44,57 @@ + + Properties\SolutionInfo.cs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {31785BC3-256C-4613-B2F5-A1B0BDDED8C1} + Umbraco.Core + + + {651e1350-91b6-44b7-bd60-7207006d7003} + Umbraco.Web + + + + + 5.2.7 + \ No newline at end of file diff --git a/src/Umbraco.ModelsBuilder/Umbraco/HashCombiner.cs b/src/Umbraco.ModelsBuilder/Umbraco/HashCombiner.cs new file mode 100644 index 0000000000..e11662eb24 --- /dev/null +++ b/src/Umbraco.ModelsBuilder/Umbraco/HashCombiner.cs @@ -0,0 +1,38 @@ +using System; +using System.Globalization; + +namespace Umbraco.ModelsBuilder.Umbraco +{ + // because, of course, it's internal in Umbraco + // see also System.Web.Util.HashCodeCombiner + class HashCombiner + { + private long _combinedHash = 5381L; + + public void Add(int i) + { + _combinedHash = ((_combinedHash << 5) + _combinedHash) ^ i; + } + + public void Add(object o) + { + Add(o.GetHashCode()); + } + + public void Add(DateTime d) + { + Add(d.GetHashCode()); + } + + public void Add(string s) + { + if (s == null) return; + Add((StringComparer.InvariantCulture).GetHashCode(s)); + } + + public string GetCombinedHashCode() + { + return _combinedHash.ToString("x", CultureInfo.InvariantCulture); + } + } +} diff --git a/src/Umbraco.ModelsBuilder/Umbraco/HashHelper.cs b/src/Umbraco.ModelsBuilder/Umbraco/HashHelper.cs new file mode 100644 index 0000000000..e4a0705ec0 --- /dev/null +++ b/src/Umbraco.ModelsBuilder/Umbraco/HashHelper.cs @@ -0,0 +1,42 @@ +using System.Collections.Generic; +using System.Linq; +using Umbraco.ModelsBuilder.Building; + +namespace Umbraco.ModelsBuilder.Umbraco +{ + class HashHelper + { + public static string Hash(IEnumerable typeModels) + { + var hash = new HashCombiner(); + + // see Umbraco.ModelsBuilder.Umbraco.Application for what's important to hash + // ie what comes from Umbraco (not computed by ModelsBuilder) and makes a difference + + foreach (var typeModel in typeModels.OrderBy(x => x.Alias)) + { + hash.Add("--- CONTENT TYPE MODEL ---"); + hash.Add(typeModel.Id); + hash.Add(typeModel.Alias); + hash.Add(typeModel.ClrName); + hash.Add(typeModel.ParentId); + hash.Add(typeModel.Name); + hash.Add(typeModel.Description); + hash.Add(typeModel.ItemType.ToString()); + hash.Add("MIXINS:" + string.Join(",", typeModel.MixinTypes.OrderBy(x => x.Id).Select(x => x.Id))); + + foreach (var prop in typeModel.Properties.OrderBy(x => x.Alias)) + { + hash.Add("--- PROPERTY ---"); + hash.Add(prop.Alias); + hash.Add(prop.ClrName); + hash.Add(prop.Name); + hash.Add(prop.Description); + hash.Add(prop.ModelClrType.ToString()); // see ModelType tests, want ToString() not FullName + } + } + + return hash.GetCombinedHashCode(); + } + } +} diff --git a/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProvider.cs b/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProvider.cs new file mode 100644 index 0000000000..194e7de695 --- /dev/null +++ b/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProvider.cs @@ -0,0 +1,133 @@ +using System; +using System.Threading; +using System.Web; +using System.Web.Hosting; +using Umbraco.Core.Composing; +using Umbraco.Core.Logging; +using Umbraco.ModelsBuilder.Configuration; +using Umbraco.ModelsBuilder.Umbraco; +using Umbraco.Web.Cache; + +// will install only if configuration says it needs to be installed +[assembly: PreApplicationStartMethod(typeof(LiveModelsProviderModule), "Install")] + +namespace Umbraco.ModelsBuilder.Umbraco +{ + // supports LiveDll and LiveAppData - but not PureLive + public sealed class LiveModelsProvider + { + private static UmbracoServices _umbracoServices; + private static Mutex _mutex; + private static int _req; + + private static Config Config => Current.Configs.ModelsBuilder(); + + // we do not manage pure live here + internal static bool IsEnabled => Config.ModelsMode.IsLiveNotPure(); + + 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 = Config.ModelsDirectory; + var modelsNamespace = Config.ModelsNamespace; + + 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 + ModelsBuilderBackOfficeController.GenerateModels(_umbracoServices, modelsDirectory, modelsNamespace); + } + } + + // have to do this because it's the only way to subscribe to EndRequest, + // module is installed by assembly attribute at the top of this file + public class LiveModelsProviderModule : IHttpModule + { + public void Init(HttpApplication app) + { + app.EndRequest += LiveModelsProvider.GenerateModelsIfRequested; + } + + public void Dispose() + { + // nothing + } + + public static void Install() + { + // always - don't read config in PreApplicationStartMethod + HttpApplication.RegisterModule(typeof(LiveModelsProviderModule)); + } + } +} diff --git a/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderBackOfficeController.cs b/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderBackOfficeController.cs new file mode 100644 index 0000000000..b1e6fbb1fb --- /dev/null +++ b/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderBackOfficeController.cs @@ -0,0 +1,184 @@ +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.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.Settings)] + public class ModelsBuilderBackOfficeController : UmbracoAuthorizedJsonController + { + private readonly UmbracoServices _umbracoServices; + private readonly Config _config; + + public ModelsBuilderBackOfficeController(UmbracoServices umbracoServices, Config config) + { + _umbracoServices = umbracoServices; + _config = config; + } + + // 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 modelsDirectory = config.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); + + 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 settings 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 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 = BuilderDashboardHelper.Text(), + CanGenerate = BuilderDashboardHelper.CanGenerate(), + OutOfDateModels = BuilderDashboardHelper.AreModelsOutOfDate(), + LastError = BuilderDashboardHelper.LastError(), + }; + } + + private void GenerateModels(string modelsDirectory) + { + GenerateModels(_umbracoServices, modelsDirectory, _config.ModelsNamespace); + } + + internal static void GenerateModels(UmbracoServices umbracoServices, string modelsDirectory, string modelsNamespace) + { + if (!Directory.Exists(modelsDirectory)) + Directory.CreateDirectory(modelsDirectory); + + foreach (var file in Directory.GetFiles(modelsDirectory, "*.generated.cs")) + File.Delete(file); + + var typeModels = umbracoServices.GetAllTypes(); + + var builder = new TextBuilder(typeModels, modelsNamespace); + + foreach (var typeModel in builder.GetModelsToGenerate()) + { + var sb = new StringBuilder(); + builder.Generate(sb, typeModel); + var filename = Path.Combine(modelsDirectory, typeModel.ClrName + ".generated.cs"); + File.WriteAllText(filename, sb.ToString()); + } + + // the idea was to calculate the current hash and to add it as an extra file to the compilation, + // in order to be able to detect whether a DLL is consistent with an environment - however the + // environment *might not* contain the local partial files, and thus it could be impossible to + // calculate the hash. So... maybe that's not a good idea after all? + /* + var currentHash = HashHelper.Hash(ourFiles, typeModels); + ourFiles["models.hash.cs"] = $@"using Umbraco.ModelsBuilder; +[assembly:ModelsBuilderAssembly(SourceHash = ""{currentHash}"")] +"; + */ + + OutOfDateModelsStatus.Clear(); + } + + [DataContract] + internal class BuildResult + { + [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/Umbraco/ModelsBuilderComponent.cs b/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderComponent.cs new file mode 100644 index 0000000000..70d55368ac --- /dev/null +++ b/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderComponent.cs @@ -0,0 +1,186 @@ +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.Configuration; +using Umbraco.Web; +using Umbraco.Web.JavaScript; +using Umbraco.Web.Mvc; + +namespace Umbraco.ModelsBuilder.Umbraco +{ + public class ModelsBuilderComponent : IComponent + { + private readonly UmbracoServices _umbracoServices; + + private readonly Config _config; + + public ModelsBuilderComponent(UmbracoServices umbracoServices, Config config) + { + _umbracoServices = umbracoServices; + _config = config; + } + + 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; + + // fixme LiveModelsProvider should not be static + if (_config.ModelsMode.IsLiveNotPure()) + LiveModelsProvider.Install(_umbracoServices); + + // fixme OutOfDateModelsStatus should not be static + if (_config.FlagOutOfDateModels) + OutOfDateModelsStatus.Install(); + } + + public void Terminate() + { } + + private void InstallServerVars() + { + // 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(); + }; + } + + 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/Umbraco/ModelsBuilderComposer.cs b/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderComposer.cs new file mode 100644 index 0000000000..3dae2f86ab --- /dev/null +++ b/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderComposer.cs @@ -0,0 +1,60 @@ +using System.Linq; +using Umbraco.Core; +using Umbraco.Core.Composing; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.ModelsBuilder.Configuration; +using Umbraco.Web.PublishedCache.NuCache; + +namespace Umbraco.ModelsBuilder.Umbraco +{ + [ComposeBefore(typeof(NuCacheComposer))] + [RuntimeLevel(MinLevel = RuntimeLevel.Run)] + public sealed class ModelsBuilderComposer : ComponentComposer, ICoreComposer + { + public override void Compose(Composition composition) + { + base.Compose(composition); + + composition.Register(Lifetime.Singleton); + composition.Configs.Add(() => new Config()); + + if (composition.Configs.ModelsBuilder().ModelsMode == ModelsMode.PureLive) + ComposeForLiveModels(composition); + else if (composition.Configs.ModelsBuilder().EnableFactory) + ComposeForDefaultModelsFactory(composition); + } + + 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/Umbraco/ModelsBuilderInitializer.cs b/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderInitializer.cs new file mode 100644 index 0000000000..9bd662da37 --- /dev/null +++ b/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderInitializer.cs @@ -0,0 +1,28 @@ +using System.Web; +using System.Web.Compilation; +using Umbraco.ModelsBuilder.Umbraco; + +[assembly: PreApplicationStartMethod(typeof(ModelsBuilderInitializer), "Initialize")] + +namespace Umbraco.ModelsBuilder.Umbraco +{ + 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/Umbraco/ModelsGenerationError.cs b/src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerationError.cs new file mode 100644 index 0000000000..a7b437df57 --- /dev/null +++ b/src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerationError.cs @@ -0,0 +1,62 @@ +using System; +using System.IO; +using System.Text; +using Umbraco.Core.Composing; +using Umbraco.ModelsBuilder.Configuration; + +namespace Umbraco.ModelsBuilder.Umbraco +{ + internal static class ModelsGenerationError + { + private static Config Config => Current.Configs.ModelsBuilder(); + + public static void Clear() + { + var errFile = GetErrFile(); + if (errFile == null) return; + + // "If the file to be deleted does not exist, no exception is thrown." + File.Delete(errFile); + } + + public static void Report(string message, Exception e) + { + var errFile = GetErrFile(); + if (errFile == null) return; + + var sb = new StringBuilder(); + sb.Append(message); + sb.Append("\r\n"); + sb.Append(e.Message); + sb.Append("\r\n\r\n"); + sb.Append(e.StackTrace); + sb.Append("\r\n"); + + File.WriteAllText(errFile, sb.ToString()); + } + + public static string GetLastError() + { + var errFile = GetErrFile(); + if (errFile == null) return null; + + try + { + return File.ReadAllText(errFile); + } + catch // accepted + { + return null; + } + } + + private static string GetErrFile() + { + var modelsDirectory = Config.ModelsDirectory; + if (!Directory.Exists(modelsDirectory)) + return null; + + return Path.Combine(modelsDirectory, "models.err"); + } + } +} diff --git a/src/Umbraco.ModelsBuilder/Umbraco/OutOfDateModelsStatus.cs b/src/Umbraco.ModelsBuilder/Umbraco/OutOfDateModelsStatus.cs new file mode 100644 index 0000000000..142cc7578a --- /dev/null +++ b/src/Umbraco.ModelsBuilder/Umbraco/OutOfDateModelsStatus.cs @@ -0,0 +1,57 @@ +using System.IO; +using Umbraco.Core.Composing; +using Umbraco.ModelsBuilder.Configuration; +using Umbraco.Web.Cache; + +namespace Umbraco.ModelsBuilder.Umbraco +{ + public sealed class OutOfDateModelsStatus + { + private static Config Config => Current.Configs.ModelsBuilder(); + + internal static void Install() + { + // just be sure + if (Config.FlagOutOfDateModels == false) + return; + + ContentTypeCacheRefresher.CacheUpdated += (sender, args) => Write(); + DataTypeCacheRefresher.CacheUpdated += (sender, args) => Write(); + } + + private static string GetFlagPath() + { + var modelsDirectory = Config.ModelsDirectory; + if (!Directory.Exists(modelsDirectory)) + Directory.CreateDirectory(modelsDirectory); + return Path.Combine(modelsDirectory, "ood.flag"); + } + + private static 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() + { + if (Config.FlagOutOfDateModels == false) return; + var path = GetFlagPath(); + if (path == null || !File.Exists(path)) return; + File.Delete(path); + } + + public static bool IsEnabled => Config.FlagOutOfDateModels; + + public static bool IsOutOfDate + { + get + { + if (Config.FlagOutOfDateModels == false) return false; + var path = GetFlagPath(); + return path != null && File.Exists(path); + } + } + } +} diff --git a/src/Umbraco.ModelsBuilder/Umbraco/PublishedModelUtility.cs b/src/Umbraco.ModelsBuilder/Umbraco/PublishedModelUtility.cs new file mode 100644 index 0000000000..c70e8a3b65 --- /dev/null +++ b/src/Umbraco.ModelsBuilder/Umbraco/PublishedModelUtility.cs @@ -0,0 +1,67 @@ +using System; +using System.Linq; +using System.Linq.Expressions; +using Umbraco.Web.Composing; +using Umbraco.Core.Models.PublishedContent; + +namespace Umbraco.ModelsBuilder.Umbraco +{ + public static class PublishedModelUtility + { + // looks safer but probably useless... ppl should not call these methods directly + // and if they do... they have to take care about not doing stupid things + + //public static PublishedPropertyType GetModelPropertyType2(Expression> selector) + // where T : PublishedContentModel + //{ + // var type = typeof (T); + // var s1 = type.GetField("ModelTypeAlias", BindingFlags.Public | BindingFlags.Static); + // var alias = (s1.IsLiteral && s1.IsInitOnly && s1.FieldType == typeof(string)) ? (string)s1.GetValue(null) : null; + // var s2 = type.GetField("ModelItemType", BindingFlags.Public | BindingFlags.Static); + // var itemType = (s2.IsLiteral && s2.IsInitOnly && s2.FieldType == typeof(PublishedItemType)) ? (PublishedItemType)s2.GetValue(null) : 0; + + // var contentType = PublishedContentType.Get(itemType, alias); + // // etc... + //} + + public static PublishedContentType GetModelContentType(PublishedItemType itemType, string alias) + { + var facade = Current.UmbracoContext.PublishedSnapshot; // fixme inject! + switch (itemType) + { + case PublishedItemType.Content: + return facade.Content.GetContentType(alias); + case PublishedItemType.Media: + return facade.Media.GetContentType(alias); + case PublishedItemType.Member: + return facade.Members.GetContentType(alias); + default: + throw new ArgumentOutOfRangeException(nameof(itemType)); + } + } + + public static PublishedPropertyType GetModelPropertyType(PublishedContentType contentType, Expression> selector) + //where TModel : PublishedContentModel // fixme PublishedContentModel _or_ PublishedElementModel + { + // fixme therefore, missing a check on TModel here + + var expr = selector.Body as MemberExpression; + + if (expr == null) + throw new ArgumentException("Not a property expression.", nameof(selector)); + + // there _is_ a risk that contentType and T do not match + // see note above : accepted risk... + + var attr = expr.Member + .GetCustomAttributes(typeof (ImplementPropertyTypeAttribute), false) + .OfType() + .SingleOrDefault(); + + if (string.IsNullOrWhiteSpace(attr?.Alias)) + throw new InvalidOperationException($"Could not figure out property alias for property \"{expr.Member.Name}\"."); + + return contentType.GetPropertyType(attr.Alias); + } + } +} diff --git a/src/Umbraco.ModelsBuilder/Umbraco/PureLiveModelFactory.cs b/src/Umbraco.ModelsBuilder/Umbraco/PureLiveModelFactory.cs new file mode 100644 index 0000000000..05f748a588 --- /dev/null +++ b/src/Umbraco.ModelsBuilder/Umbraco/PureLiveModelFactory.cs @@ -0,0 +1,677 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +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.Composing; +using Umbraco.Core.Logging; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Web.Cache; +using Umbraco.ModelsBuilder.Building; +using Umbraco.ModelsBuilder.Configuration; +using File = System.IO.File; + +namespace Umbraco.ModelsBuilder.Umbraco +{ + 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 IProfilingLogger _logger; + private readonly FileSystemWatcher _watcher; + private int _ver, _skipver; + private readonly int _debugLevel; + private BuildManager _theBuildManager; + private readonly Lazy _umbracoServices; + 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" }; + + private readonly Config _config; + + public PureLiveModelFactory(Lazy umbracoServices, IProfilingLogger logger, Config config) + { + _umbracoServices = umbracoServices; + _logger = logger; + _config = config; + _ver = 1; // zero is for when we had no version + _skipver = -1; // nothing to skip + + RazorBuildProvider.CodeGenerationStarted += RazorBuildProvider_CodeGenerationStarted; + + if (!HostingEnvironment.IsHosted) return; + + var modelsDirectory = _config.ModelsDirectory; + if (!Directory.Exists(modelsDirectory)) + Directory.CreateDirectory(modelsDirectory); + + // BEWARE! if the watcher is not properly released then for some reason the + // BuildManager will start confusing types - using a 'registered object' here + // though we should probably plug into Umbraco's MainDom - which is internal + HostingEnvironment.RegisterObject(this); + _watcher = new FileSystemWatcher(modelsDirectory); + _watcher.Changed += WatcherOnChanged; + _watcher.EnableRaisingEvents = true; + + // get it here, this need to be fast + _debugLevel = _config.DebugLevel; + } + + #region ILivePublishedModelFactory + + /// + public object SyncRoot { get; } = new object(); + + /// + public void Refresh() + { + ResetModels(); + EnsureModels(); + } + + #endregion + + #region IPublishedModelFactory + + public IPublishedElement CreateModel(IPublishedElement element) + { + // get models, rebuilding them if needed + var infos = EnsureModels()?.ModelInfos; + if (infos == null) + return element; + + // be case-insensitive + var contentTypeAlias = element.ContentType.Alias; + + // lookup model constructor (else null) + infos.TryGetValue(contentTypeAlias, out ModelInfo info); + + // create model + return info == null ? element : info.Ctor(element); + } + + // this runs only once the factory is ready + // NOT when building models + public Type MapModelType(Type type) + { + var infos = EnsureModels(); + return ModelType.Map(type, infos.ModelTypeMap); + } + + // this runs only once the factory is ready + // NOT when building models + public IList CreateModelList(string alias) + { + var infos = EnsureModels(); + + // fail fast + if (infos == null) + return new List(); + + if (!infos.ModelInfos.TryGetValue(alias, out var modelInfo)) + return new List(); + + var ctor = modelInfo.ListCtor; + if (ctor != null) return ctor(); + + var listType = typeof(List<>).MakeGenericType(modelInfo.ModelType); + ctor = modelInfo.ListCtor = ReflectionUtilities.EmitConstructor>(declaring: listType); + return ctor(); + } + + #endregion + + #region Compilation + + // deadlock note + // + // when RazorBuildProvider_CodeGenerationStarted runs, the thread has Monitor.Enter-ed the BuildManager + // singleton instance, through a call to CompilationLock.GetLock in BuildManager.GetVPathBuildResultInternal, + // and now wants to lock _locker. + // when EnsureModels runs, the thread locks _locker and then wants BuildManager to compile, which in turns + // requires that the BuildManager can Monitor.Enter-ed itself. + // so: + // + // T1 - needs to ensure models, locks _locker + // T2 - needs to compile a view, locks BuildManager + // hits RazorBuildProvider_CodeGenerationStarted + // wants to lock _locker, wait + // T1 - needs to compile models, using BuildManager + // wants to lock itself, wait + // + // + // until ASP.NET kills the long-running request (thread abort) + // + // problem is, we *want* to suspend views compilation while the models assembly is being changed else we + // end up with views compiled and cached with the old assembly, while models come from the new assembly, + // which gives more YSOD. so we *have* to lock _locker in RazorBuildProvider_CodeGenerationStarted. + // + // one "easy" solution consists in locking the BuildManager *before* _locker in EnsureModels, thus ensuring + // we always lock in the same order, and getting rid of deadlocks - but that requires having access to the + // current BuildManager instance, which is BuildManager.TheBuildManager, which is an internal property. + // + // well, that's what we are doing in this class' TheBuildManager property, using reflection. + + private void RazorBuildProvider_CodeGenerationStarted(object sender, EventArgs e) + { + try + { + _locker.EnterReadLock(); + + // just be safe - can happen if the first view is not an Umbraco view, + // or if something went wrong and we don't have an assembly at all + if (_modelsAssembly == null) return; + + if (_debugLevel > 0) + _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 + // compilation as in some environments (could not figure which/why) the BuildManager + // would not re-compile the views when the models assembly is rebuilt. + provider.AssemblyBuilder.AddAssemblyReference(_modelsAssembly); + provider.AddVirtualPathDependency(ProjVirt); + } + finally + { + if (_locker.IsReadLockHeld) + _locker.ExitReadLock(); + } + } + + // tells the factory that it should build a new generation of models + private void ResetModels() + { + _logger.Debug("Resetting models."); + + try + { + _locker.EnterWriteLock(); + + _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 + { + if (_locker.IsWriteLockHeld) + _locker.ExitWriteLock(); + } + } + + // gets "the" build manager + private BuildManager TheBuildManager + { + get + { + if (_theBuildManager != null) return _theBuildManager; + 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); + return _theBuildManager; + } + } + + // ensure that the factory is running with the lastest generation of models + internal Infos EnsureModels() + { + if (_debugLevel > 0) + _logger.Debug("Ensuring models."); + + // don't use an upgradeable lock here because only 1 thread at a time could enter it + try + { + _locker.EnterReadLock(); + if (_hasModels) + return _infos; + } + finally + { + if (_locker.IsReadLockHeld) + _locker.ExitReadLock(); + } + + var buildManagerLocked = false; + try + { + // always take the BuildManager lock *before* taking the _locker lock + // to avoid possible deadlock situations (see notes above) + Monitor.Enter(TheBuildManager, ref buildManagerLocked); + + _locker.EnterUpgradeableReadLock(); + + if (_hasModels) return _infos; + + _locker.EnterWriteLock(); + + // we don't have models, + // either they haven't been loaded from the cache yet + // or they have been reseted and are pending a rebuild + + using (_logger.DebugDuration("Get models.", "Got models.")) + { + try + { + var assembly = GetModelsAssembly(_pendingRebuild); + + // the one below can be used to simulate an issue with BuildManager, ie it will register + // the models with the factory but NOT with the BuildManager, which will not recompile views. + // this is for U4-8043 which is an obvious issue but I cannot replicate + //_modelsAssembly = _modelsAssembly ?? assembly; + + // the one below is the normal one + _modelsAssembly = assembly; + + var types = assembly.ExportedTypes.Where(x => x.Inherits() || x.Inherits()); + _infos = RegisterModels(types); + ModelsGenerationError.Clear(); + } + catch (Exception e) + { + try + { + _logger.Error("Failed to build models.", e); + _logger.Warn("Running without models."); // be explicit + ModelsGenerationError.Report("Failed to build PureLive models.", e); + } + finally + { + _modelsAssembly = null; + _infos = new Infos { ModelInfos = null, ModelTypeMap = new Dictionary() }; + } + } + + // don't even try again + _hasModels = true; + } + + return _infos; + } + finally + { + if (_locker.IsWriteLockHeld) + _locker.ExitWriteLock(); + if (_locker.IsUpgradeableReadLockHeld) + _locker.ExitUpgradeableReadLock(); + if (buildManagerLocked) + Monitor.Exit(TheBuildManager); + } + } + + private Assembly GetModelsAssembly(bool forceRebuild) + { + var modelsDirectory = _config.ModelsDirectory; + if (!Directory.Exists(modelsDirectory)) + Directory.CreateDirectory(modelsDirectory); + + var typeModels = UmbracoServices.GetAllTypes(); + var currentHash = HashHelper.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"); + var dllPathFile = Path.Combine(modelsDirectory, "all.dll.path"); + + // caching the generated models speeds up booting + // currentHash hashes both the types & the user's partials + + if (!forceRebuild) + { + _logger.Debug("Looking for cached models."); + if (File.Exists(modelsHashFile) && File.Exists(projFile)) + { + var cachedHash = File.ReadAllText(modelsHashFile); + if (currentHash != cachedHash) + { + _logger.Debug("Found obsolete cached models."); + forceRebuild = true; + } + + // else cachedHash matches currentHash, we can try to load an existing dll + } + else + { + _logger.Debug("Could not find cached models."); + forceRebuild = true; + } + } + + Assembly assembly; + 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); + 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(); + if (attr != null && attr.PureLive && attr.SourceHash == currentHash) + { + // if we were to resume at that revision, then _ver would keep increasing + // and that is probably a bad idea - so, we'll always rebuild starting at + // ver 1, but we remember we want to skip that one - so we never end up + // with the "same but different" version of the assembly in memory + _skipver = assembly.GetName().Version.Revision; + + _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?)."); + } + + // 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); + if (match.Success) + { + text = text.Replace(match.Value, "AssemblyVersion(\"0.0.0." + _ver + "\")"); + File.WriteAllText(projFile, text); + } + + // generate a marker file that will be a dependency + // see note in RazorBuildProvider_CodeGenerationStarted + // NO: using all.generated.cs as a dependency + //File.WriteAllText(Path.Combine(modelsDirectory, "models.dep"), "VER:" + _ver); + + _ver++; + try + { + assembly = BuildManager.GetCompiledAssembly(ProjVirt); + File.WriteAllText(dllPathFile, assembly.Location); + } + catch + { + ClearOnFailingToCompile(dllPathFile, modelsHashFile, projFile); + throw; + } + + _logger.Debug("Loading cached models (source)."); + return assembly; + } + + // need to rebuild + _logger.Debug("Rebuilding models."); + + // generate code, save + 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}"")] +[assembly:System.Reflection.AssemblyVersion(""0.0.0.{ver}"")]"); + File.WriteAllText(modelsSrcFile, code); + + // generate proj, save + var projFiles = new Dictionary + { + { "models.generated.cs", code } + }; + var proj = GenerateModelsProj(projFiles); + File.WriteAllText(projFile, proj); + + // compile and register + try + { + assembly = BuildManager.GetCompiledAssembly(ProjVirt); + File.WriteAllText(dllPathFile, assembly.Location); + File.WriteAllText(modelsHashFile, currentHash); + } + catch + { + ClearOnFailingToCompile(dllPathFile, modelsHashFile, projFile); + throw; + } + + _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 modelInfos = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + var map = new Dictionary(); + + foreach (var type in types) + { + ConstructorInfo constructor = null; + Type parameterType = null; + + foreach (var ctor in type.GetConstructors()) + { + var parms = ctor.GetParameters(); + 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."); + constructor = ctor; + parameterType = parms[0].ParameterType; + } + } + + if (constructor == null) + throw new InvalidOperationException($"Type {type.FullName} is missing a public constructor with one argument of type, or implementing, IPropertySet."); + + var attribute = type.GetCustomAttribute(false); + var typeName = attribute == null ? type.Name : attribute.ContentTypeAlias; + + if (modelInfos.TryGetValue(typeName, out ModelInfo 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); + 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)); + + modelInfos[typeName] = new ModelInfo { ParameterType = parameterType, Ctor = func, ModelType = type }; + map[typeName] = type; + } + + return new Infos { ModelInfos = modelInfos.Count > 0 ? modelInfos : null, ModelTypeMap = map }; + } + + private string GenerateModelsCode(IList typeModels) + { + var modelsDirectory = _config.ModelsDirectory; + if (!Directory.Exists(modelsDirectory)) + Directory.CreateDirectory(modelsDirectory); + + foreach (var file in Directory.GetFiles(modelsDirectory, "*.generated.cs")) + File.Delete(file); + + var builder = new TextBuilder(typeModels, _config.ModelsNamespace); + + var codeBuilder = new StringBuilder(); + builder.Generate(codeBuilder, builder.GetModelsToGenerate()); + var code = codeBuilder.ToString(); + + return code; + } + + private static readonly Regex UsingRegex = new Regex("^using(.*);", RegexOptions.Compiled | RegexOptions.Multiline); + private static readonly Regex AattrRegex = new Regex("^\\[assembly:(.*)\\]", RegexOptions.Compiled | RegexOptions.Multiline); + + private static string GenerateModelsProj(IDictionary files) + { + // ideally we would generate a CSPROJ file but then we'd need a BuildProvider for csproj + // trying to keep things simple for the time being, just write everything to one big file + + // group all 'using' at the top of the file (else fails) + var usings = new List(); + foreach (var k in files.Keys.ToList()) + files[k] = UsingRegex.Replace(files[k], m => + { + usings.Add(m.Groups[1].Value); + return string.Empty; + }); + + // group all '[assembly:...]' at the top of the file (else fails) + var aattrs = new List(); + foreach (var k in files.Keys.ToList()) + files[k] = AattrRegex.Replace(files[k], m => + { + aattrs.Add(m.Groups[1].Value); + return string.Empty; + }); + + var text = new StringBuilder(); + foreach (var u in usings.Distinct()) + { + text.Append("using "); + text.Append(u); + text.Append(";\r\n"); + } + foreach (var a in aattrs) + { + text.Append("[assembly:"); + text.Append(a); + text.Append("]\r\n"); + } + text.Append("\r\n\r\n"); + foreach (var f in files) + { + text.Append("// FILE: "); + text.Append(f.Key); + text.Append("\r\n\r\n"); + text.Append(f.Value); + text.Append("\r\n\r\n\r\n"); + } + text.Append("// EOF\r\n"); + + return text.ToString(); + } + + internal class Infos + { + public Dictionary ModelTypeMap { get; set; } + public Dictionary ModelInfos { get; set; } + } + + internal class ModelInfo + { + public Type ParameterType { get; set; } + public Func Ctor { get; set; } + public Type ModelType { get; set; } + public Func ListCtor { get; set; } + } + + #endregion + + #region Watching + + private void WatcherOnChanged(object sender, FileSystemEventArgs args) + { + var changed = args.Name; + + // don't reset when our files change because we are building! + // + // comment it out, and always ignore our files, because it seems that some + // race conditions can occur on slow Cloud filesystems and then we keep + // rebuilding + + //if (_building && OurFiles.Contains(changed)) + //{ + // //_logger.Info("Ignoring files self-changes."); + // return; + //} + + // always ignore our own file changes + if (OurFiles.Contains(changed)) + return; + + _logger.Info("Detected files changes."); + + lock (SyncRoot) // don't reset while being locked + { + ResetModels(); + } + } + + public void Stop(bool immediate) + { + _watcher.EnableRaisingEvents = false; + _watcher.Dispose(); + HostingEnvironment.UnregisterObject(this); + } + + #endregion + } +} diff --git a/src/Umbraco.ModelsBuilder/Umbraco/UmbracoServices.cs b/src/Umbraco.ModelsBuilder/Umbraco/UmbracoServices.cs new file mode 100644 index 0000000000..67ef10be1a --- /dev/null +++ b/src/Umbraco.ModelsBuilder/Umbraco/UmbracoServices.cs @@ -0,0 +1,198 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core; +using Umbraco.Core.Composing; +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; + +namespace Umbraco.ModelsBuilder.Umbraco +{ + public class UmbracoServices + { + private readonly IContentTypeService _contentTypeService; + private readonly IMediaTypeService _mediaTypeService; + private readonly IMemberTypeService _memberTypeService; + private readonly IPublishedContentTypeFactory _publishedContentTypeFactory; + + public UmbracoServices(IContentTypeService contentTypeService, IMediaTypeService mediaTypeService, IMemberTypeService memberTypeService, IPublishedContentTypeFactory publishedContentTypeFactory) + { + _contentTypeService = contentTypeService; + _mediaTypeService = mediaTypeService; + _memberTypeService = memberTypeService; + _publishedContentTypeFactory = publishedContentTypeFactory; + } + + private static Config Config => Current.Configs.ModelsBuilder(); + + #region Services + + public IList GetAllTypes() + { + var types = new List(); + + 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())); + + return EnsureDistinctAliases(types); + } + + public IList GetContentTypes() + { + var contentTypes = _contentTypeService.GetAll().Cast().ToArray(); + return GetTypes(PublishedItemType.Content, contentTypes); // aliases have to be unique here + } + + public IList GetMediaTypes() + { + var contentTypes = _mediaTypeService.GetAll().Cast().ToArray(); + return GetTypes(PublishedItemType.Media, contentTypes); // aliases have to be unique here + } + + public IList GetMemberTypes() + { + var memberTypes = _memberTypeService.GetAll().Cast().ToArray(); + return GetTypes(PublishedItemType.Member, memberTypes); // aliases have to be unique here + } + + public static string GetClrName(string name, string alias) + { + // ModelsBuilder's legacy - but not ideal + return alias.ToCleanString(CleanStringType.ConvertCase | CleanStringType.PascalCase); + } + + private IList GetTypes(PublishedItemType itemType, IContentTypeComposition[] contentTypes) + { + var typeModels = new List(); + var uniqueTypes = new HashSet(); + + // get the types and the properties + foreach (var contentType in contentTypes) + { + var typeModel = new TypeModel + { + Id = contentType.Id, + Alias = contentType.Alias, + ClrName = GetClrName(contentType.Name, contentType.Alias), + ParentId = contentType.ParentId, + + Name = contentType.Name, + Description = contentType.Description + }; + + // 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}\"."); + uniqueTypes.Add(typeModel.ClrName); + + var publishedContentType = _publishedContentTypeFactory.CreateContentType(contentType); + switch (itemType) + { + case PublishedItemType.Content: + typeModel.ItemType = publishedContentType.ItemType == PublishedItemType.Element + ? TypeModel.ItemTypes.Element + : TypeModel.ItemTypes.Content; + break; + case PublishedItemType.Media: + typeModel.ItemType = publishedContentType.ItemType == PublishedItemType.Element + ? TypeModel.ItemTypes.Element + : TypeModel.ItemTypes.Media; + break; + case PublishedItemType.Member: + typeModel.ItemType = publishedContentType.ItemType == PublishedItemType.Element + ? TypeModel.ItemTypes.Element + : TypeModel.ItemTypes.Member; + break; + default: + throw new InvalidOperationException(string.Format("Unsupported PublishedItemType \"{0}\".", itemType)); + } + + typeModels.Add(typeModel); + + foreach (var propertyType in contentType.PropertyTypes) + { + var propertyModel = new PropertyModel + { + Alias = propertyType.Alias, + ClrName = GetClrName(propertyType.Name, propertyType.Alias), + + Name = propertyType.Name, + Description = propertyType.Description + }; + + var publishedPropertyType = publishedContentType.GetPropertyType(propertyType.Alias); + if (publishedPropertyType == null) + throw new Exception($"Panic: could not get published property type {contentType.Alias}.{propertyType.Alias}."); + + propertyModel.ModelClrType = publishedPropertyType.ModelClrType; + + typeModel.Properties.Add(propertyModel); + } + } + + // wire the base types + foreach (var typeModel in typeModels.Where(x => x.ParentId > 0)) + { + typeModel.BaseType = typeModels.SingleOrDefault(x => x.Id == typeModel.ParentId); + // Umbraco 7.4 introduces content types containers, so even though ParentId > 0, the parent might + // not be a content type - here we assume that BaseType being null while ParentId > 0 means that + // the parent is a container (and we don't check). + typeModel.IsParent = typeModel.BaseType != null; + } + + // discover mixins + 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."); + + IEnumerable compositionTypes; + var contentTypeAsMedia = contentType as IMediaType; + var contentTypeAsContent = contentType as IContentType; + var contentTypeAsMember = contentType as IMemberType; + 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)); + + 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 (compositionType.Id == contentType.ParentId) continue; + + // add to mixins + typeModel.MixinTypes.Add(compositionModel); + + // mark as mixin - as well as parents + compositionModel.IsMixin = true; + while ((compositionModel = compositionModel.BaseType) != null) + compositionModel.IsMixin = true; + } + } + + return typeModels; + } + + internal static IList EnsureDistinctAliases(IList typeModels) + { + 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; + } + + #endregion + } +} diff --git a/src/Umbraco.ModelsBuilder/Validation/ContentTypeModelValidator.cs b/src/Umbraco.ModelsBuilder/Validation/ContentTypeModelValidator.cs new file mode 100644 index 0000000000..3d2ce600c2 --- /dev/null +++ b/src/Umbraco.ModelsBuilder/Validation/ContentTypeModelValidator.cs @@ -0,0 +1,93 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using Umbraco.Core; +using Umbraco.Core.Composing; +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 + { + private static Config Config => Current.Configs.ModelsBuilder(); + + protected override IEnumerable Validate(TModel model) + { + //don't do anything if we're not enabled + if (Config.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.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 1a1946b01a..e52cb253cf 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -117,6 +117,10 @@ Umbraco.Examine {07FBC26B-2927-4A22-8D96-D644C667FECC} + + {52ac0ba8-a60e-4e36-897b-e8b97a54ed1c} + Umbraco.ModelsBuilder + {651e1350-91b6-44b7-bd60-7207006d7003} Umbraco.Web From f3643747b37335a7804cceb89a4407012a3a4915 Mon Sep 17 00:00:00 2001 From: Stephan Date: Mon, 24 Jun 2019 14:33:15 +0200 Subject: [PATCH 03/49] Publicize IEditorValidator --- src/Umbraco.Web/Editors/EditorValidatorOfT.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web/Editors/EditorValidatorOfT.cs b/src/Umbraco.Web/Editors/EditorValidatorOfT.cs index 4ca008cf0d..715a49179e 100644 --- a/src/Umbraco.Web/Editors/EditorValidatorOfT.cs +++ b/src/Umbraco.Web/Editors/EditorValidatorOfT.cs @@ -8,7 +8,7 @@ namespace Umbraco.Web.Editors /// Provides a base class for implementations. /// /// The validated object type. - internal abstract class EditorValidator : IEditorValidator + public abstract class EditorValidator : IEditorValidator { public Type ModelType => typeof (T); @@ -16,4 +16,4 @@ namespace Umbraco.Web.Editors protected abstract IEnumerable Validate(T model); } -} \ No newline at end of file +} From 834aeee107ff48e7cdfcb5bb6edb778ab8711f4e Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 26 Jun 2019 22:38:32 +0200 Subject: [PATCH 04/49] Fix slimmed down ModelsBuilder --- build/NuSpecs/UmbracoCms.Web.nuspec | 3 +++ build/NuSpecs/UmbracoCms.nuspec | 1 - src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/build/NuSpecs/UmbracoCms.Web.nuspec b/build/NuSpecs/UmbracoCms.Web.nuspec index 614a816f3f..89ff42f853 100644 --- a/build/NuSpecs/UmbracoCms.Web.nuspec +++ b/build/NuSpecs/UmbracoCms.Web.nuspec @@ -51,14 +51,17 @@ + + + diff --git a/build/NuSpecs/UmbracoCms.nuspec b/build/NuSpecs/UmbracoCms.nuspec index 0d18ac59fa..62fd620faa 100644 --- a/build/NuSpecs/UmbracoCms.nuspec +++ b/build/NuSpecs/UmbracoCms.nuspec @@ -25,7 +25,6 @@ not want this to happen as the alpha of the next major is, really, the next major already. --> - diff --git a/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj b/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj index 5b0272a526..207d822fbb 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj +++ b/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj @@ -29,6 +29,7 @@ TRACE prompt 4 + bin\Release\Umbraco.ModelsBuilder.xml From b3867d9d550b07f350c02b202730c72ade1195a6 Mon Sep 17 00:00:00 2001 From: Stephan Date: Thu, 27 Jun 2019 09:16:21 +0200 Subject: [PATCH 05/49] Post-merge fixes --- src/Umbraco.ModelsBuilder/Umbraco/PublishedModelUtility.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.ModelsBuilder/Umbraco/PublishedModelUtility.cs b/src/Umbraco.ModelsBuilder/Umbraco/PublishedModelUtility.cs index c70e8a3b65..7bbae927d5 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/PublishedModelUtility.cs +++ b/src/Umbraco.ModelsBuilder/Umbraco/PublishedModelUtility.cs @@ -24,7 +24,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,7 +40,7 @@ namespace Umbraco.ModelsBuilder.Umbraco } } - public static PublishedPropertyType GetModelPropertyType(PublishedContentType contentType, Expression> selector) + public static IPublishedPropertyType GetModelPropertyType(PublishedContentType contentType, Expression> selector) //where TModel : PublishedContentModel // fixme PublishedContentModel _or_ PublishedElementModel { // fixme therefore, missing a check on TModel here From 86cf3b275f2385dc398cc50bdb1e4e1ded0a38a2 Mon Sep 17 00:00:00 2001 From: Stephan Date: Thu, 27 Jun 2019 13:40:40 +0200 Subject: [PATCH 06/49] Some more things need to be public --- .../Validation/ContentTypeModelValidator.cs | 8 ++++---- src/Umbraco.Web/Editors/EditorValidatorCollection.cs | 2 +- .../Editors/EditorValidatorCollectionBuilder.cs | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.ModelsBuilder/Validation/ContentTypeModelValidator.cs b/src/Umbraco.ModelsBuilder/Validation/ContentTypeModelValidator.cs index 3d2ce600c2..dc4649f364 100644 --- a/src/Umbraco.ModelsBuilder/Validation/ContentTypeModelValidator.cs +++ b/src/Umbraco.ModelsBuilder/Validation/ContentTypeModelValidator.cs @@ -14,24 +14,24 @@ 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 + public 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 + public 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 + public class MemberTypeModelValidator : ContentTypeModelValidatorBase { } - internal abstract class ContentTypeModelValidatorBase : EditorValidator + public abstract class ContentTypeModelValidatorBase : EditorValidator where TModel: ContentTypeSave where TProperty: PropertyTypeBasic { diff --git a/src/Umbraco.Web/Editors/EditorValidatorCollection.cs b/src/Umbraco.Web/Editors/EditorValidatorCollection.cs index 6fc6bb5de2..0e42b0027c 100644 --- a/src/Umbraco.Web/Editors/EditorValidatorCollection.cs +++ b/src/Umbraco.Web/Editors/EditorValidatorCollection.cs @@ -6,7 +6,7 @@ using Umbraco.Core.Composing; namespace Umbraco.Web.Editors { - internal class EditorValidatorCollection : BuilderCollectionBase + public class EditorValidatorCollection : BuilderCollectionBase { public EditorValidatorCollection(IEnumerable items) : base(items) diff --git a/src/Umbraco.Web/Editors/EditorValidatorCollectionBuilder.cs b/src/Umbraco.Web/Editors/EditorValidatorCollectionBuilder.cs index b3b7bab1a5..994a813cf4 100644 --- a/src/Umbraco.Web/Editors/EditorValidatorCollectionBuilder.cs +++ b/src/Umbraco.Web/Editors/EditorValidatorCollectionBuilder.cs @@ -2,7 +2,7 @@ namespace Umbraco.Web.Editors { - internal class EditorValidatorCollectionBuilder : LazyCollectionBuilderBase + public class EditorValidatorCollectionBuilder : LazyCollectionBuilderBase { protected override EditorValidatorCollectionBuilder This => this; } From dd909ebde43e28d83e3f7ea16abdf640d58a6e33 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 22 Oct 2019 09:44:51 +0200 Subject: [PATCH 07/49] AB3267 - Use interface instead of concrete class --- src/Umbraco.ModelsBuilder/Building/TextBuilder.cs | 4 ++-- .../Umbraco/PublishedModelUtility.cs | 11 ++++++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.ModelsBuilder/Building/TextBuilder.cs b/src/Umbraco.ModelsBuilder/Building/TextBuilder.cs index e15fdfca6e..7121dad1a9 100644 --- a/src/Umbraco.ModelsBuilder/Building/TextBuilder.cs +++ b/src/Umbraco.ModelsBuilder/Building/TextBuilder.cs @@ -199,10 +199,10 @@ namespace Umbraco.ModelsBuilder.Building sb.AppendFormat("\t\tpublic new const PublishedItemType ModelItemType = PublishedItemType.{0};\n", itemType); WriteGeneratedCodeAttribute(sb, "\t\t"); - sb.Append("\t\tpublic new static PublishedContentType GetModelContentType()\n"); + sb.Append("\t\tpublic new static IPublishedContentType GetModelContentType()\n"); sb.Append("\t\t\t=> PublishedModelUtility.GetModelContentType(ModelItemType, ModelTypeAlias);\n"); WriteGeneratedCodeAttribute(sb, "\t\t"); - sb.AppendFormat("\t\tpublic static PublishedPropertyType GetModelPropertyType(Expression> selector)\n", + 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"); diff --git a/src/Umbraco.ModelsBuilder/Umbraco/PublishedModelUtility.cs b/src/Umbraco.ModelsBuilder/Umbraco/PublishedModelUtility.cs index 7bbae927d5..44b7aae0c0 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/PublishedModelUtility.cs +++ b/src/Umbraco.ModelsBuilder/Umbraco/PublishedModelUtility.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel; using System.Linq; using System.Linq.Expressions; using Umbraco.Web.Composing; @@ -40,7 +41,15 @@ namespace Umbraco.ModelsBuilder.Umbraco } } - public static IPublishedPropertyType GetModelPropertyType(PublishedContentType contentType, Expression> selector) + [Obsolete("Use the overload taking IPublishedContentType instead")] + [EditorBrowsable(EditorBrowsableState.Never)] + public static IPublishedPropertyType GetModelPropertyType(PublishedContentType contentType, + Expression> selector) + { + return GetModelPropertyType((IPublishedContentType)contentType, selector); + } + + public static IPublishedPropertyType GetModelPropertyType(IPublishedContentType contentType, Expression> selector) //where TModel : PublishedContentModel // fixme PublishedContentModel _or_ PublishedElementModel { // fixme therefore, missing a check on TModel here From 8c9557322566431f5d74002fbcd8b91c1b7c2a42 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 22 Oct 2019 14:22:43 +0200 Subject: [PATCH 08/49] AB3267 - Removed unused classes and set some internal --- .../Implement/ContentTypeRepositoryBase.cs | 7 ++-- .../Building/TextHeaderWriter.cs | 2 +- .../Configuration/ClrNameSource.cs | 28 ---------------- .../EnumerableExtensions.cs | 33 ------------------- .../Umbraco.ModelsBuilder.csproj | 6 ++-- 5 files changed, 8 insertions(+), 68 deletions(-) delete mode 100644 src/Umbraco.ModelsBuilder/Configuration/ClrNameSource.cs delete mode 100644 src/Umbraco.ModelsBuilder/EnumerableExtensions.cs 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.ModelsBuilder/Building/TextHeaderWriter.cs b/src/Umbraco.ModelsBuilder/Building/TextHeaderWriter.cs index d165f03907..27fcb42b35 100644 --- a/src/Umbraco.ModelsBuilder/Building/TextHeaderWriter.cs +++ b/src/Umbraco.ModelsBuilder/Building/TextHeaderWriter.cs @@ -3,7 +3,7 @@ using Umbraco.ModelsBuilder.Api; namespace Umbraco.ModelsBuilder.Building { - public static class TextHeaderWriter + internal static class TextHeaderWriter { /// /// Outputs an "auto-generated" header to a string builder. 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/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/Umbraco.ModelsBuilder.csproj b/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj index 2791d420db..4dbe606a66 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj +++ b/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj @@ -55,12 +55,10 @@ - - @@ -93,6 +91,10 @@ + + {29aa69d9-b597-4395-8d42-43b1263c240a} + Umbraco.Abstractions + {31785BC3-256C-4613-B2F5-A1B0BDDED8C1} Umbraco.Core From 407089c67add300bb34223d5787cbc5abd4c186a Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 23 Oct 2019 08:38:24 +0200 Subject: [PATCH 09/49] Updated Microsoft.CodeAnalysis.CSharp to match the rest of umbraco --- src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj b/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj index 4dbe606a66..e8fca43e04 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj +++ b/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj @@ -82,7 +82,7 @@ - 2.8.0 + 2.10.0 1.0.0-beta2-19324-01 From a9b88e4520db25557f7e5cfb9bb652f4b7d74e52 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 23 Oct 2019 09:41:12 +0200 Subject: [PATCH 10/49] AB3267 - Removed outdated comments --- src/Umbraco.ModelsBuilder/Building/Builder.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Umbraco.ModelsBuilder/Building/Builder.cs b/src/Umbraco.ModelsBuilder/Building/Builder.cs index fd5eccde5b..1d14155d62 100644 --- a/src/Umbraco.ModelsBuilder/Building/Builder.cs +++ b/src/Umbraco.ModelsBuilder/Building/Builder.cs @@ -57,7 +57,7 @@ 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; @@ -102,10 +102,6 @@ namespace Umbraco.ModelsBuilder.Building /// /// 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() { TypeModel.MapModelTypes(_typeModels, ModelsNamespace); From b5ae96e8ef2e6d6d8e33ff698402177462fb9045 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 23 Oct 2019 09:41:35 +0200 Subject: [PATCH 11/49] AB3267 - Specify language version to make azure pipeline happy --- src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj b/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj index e8fca43e04..20b0f7fcf3 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj +++ b/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj @@ -12,6 +12,7 @@ v4.7.2 512 true + latast true From 3905265aba8086f925a03c2dae01c15afc177987 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 23 Oct 2019 09:52:41 +0200 Subject: [PATCH 12/49] AB3267 - Specify language version to make azure pipeline happy --- src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj b/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj index 20b0f7fcf3..a16579e60d 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj +++ b/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj @@ -12,7 +12,7 @@ v4.7.2 512 true - latast + 7.3 true From 25d9b9efd1702fbcbc6d44abc7bf98724bdde9ef Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 23 Oct 2019 13:53:06 +0200 Subject: [PATCH 13/49] AB3267 - Made Umbraco core internals visible for Umbraco.ModelsBuilder and uses PanicException instead of just Exception --- src/Umbraco.Core/Properties/AssemblyInfo.cs | 1 + .../Umbraco/ModelsBuilderBackOfficeController.cs | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Properties/AssemblyInfo.cs b/src/Umbraco.Core/Properties/AssemblyInfo.cs index afd602cfc9..139af8725d 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")] [assembly: InternalsVisibleTo("Umbraco.Tests")] [assembly: InternalsVisibleTo("Umbraco.Tests.Benchmarks")] diff --git a/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderBackOfficeController.cs b/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderBackOfficeController.cs index b1e6fbb1fb..a3ff89595c 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderBackOfficeController.cs +++ b/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderBackOfficeController.cs @@ -6,6 +6,7 @@ using System.Net.Http; using System.Runtime.Serialization; using System.Text; using System.Web.Hosting; +using Umbraco.Core.Exceptions; using Umbraco.ModelsBuilder.Building; using Umbraco.ModelsBuilder.Configuration; using Umbraco.ModelsBuilder.Dashboard; @@ -30,7 +31,7 @@ namespace Umbraco.ModelsBuilder.Umbraco public ModelsBuilderBackOfficeController(UmbracoServices umbracoServices, Config config) { - _umbracoServices = umbracoServices; + //_umbracoServices = umbracoServices; _config = config; } @@ -54,7 +55,7 @@ namespace Umbraco.ModelsBuilder.Umbraco var bin = HostingEnvironment.MapPath("~/bin"); if (bin == null) - throw new Exception("Panic: bin is null."); + throw new PanicException("bin is null."); // EnableDllModels will recycle the app domain - but this request will end properly GenerateModels(modelsDirectory); From 43343166c554fa6160579ceb463cd9ff157ba96c Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 23 Oct 2019 14:11:24 +0200 Subject: [PATCH 14/49] AB3267 - refactored new Exception(..) to most explicit exceptions --- src/Umbraco.ModelsBuilder/Building/Builder.cs | 4 ++-- .../Umbraco/LiveModelsProvider.cs | 3 ++- .../Umbraco/ModelsBuilderComponent.cs | 10 +++++----- src/Umbraco.ModelsBuilder/Umbraco/UmbracoServices.cs | 11 ++++++----- 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/Umbraco.ModelsBuilder/Building/Builder.cs b/src/Umbraco.ModelsBuilder/Building/Builder.cs index 1d14155d62..f65d8e9e7c 100644 --- a/src/Umbraco.ModelsBuilder/Building/Builder.cs +++ b/src/Umbraco.ModelsBuilder/Building/Builder.cs @@ -186,11 +186,11 @@ namespace Umbraco.ModelsBuilder.Building foreach (var typeModel in _typeModels.Where(x => x.IsElement)) { if (typeModel.BaseType != null && !typeModel.BaseType.IsElement) - throw new Exception($"Cannot generate model for type '{typeModel.Alias}' because it is an element type, but its parent type '{typeModel.BaseType.Alias}' is not."); + 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 Exception($"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."); + 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."); } } diff --git a/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProvider.cs b/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProvider.cs index 194e7de695..d8839a1297 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProvider.cs +++ b/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProvider.cs @@ -3,6 +3,7 @@ using System.Threading; using System.Web; using System.Web.Hosting; using Umbraco.Core.Composing; +using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; using Umbraco.ModelsBuilder.Configuration; using Umbraco.ModelsBuilder.Umbraco; @@ -103,7 +104,7 @@ namespace Umbraco.ModelsBuilder.Umbraco var bin = HostingEnvironment.MapPath("~/bin"); if (bin == null) - throw new Exception("Panic: bin is null."); + throw new PanicException("Panic: bin is null."); // EnableDllModels will recycle the app domain - but this request will end properly ModelsBuilderBackOfficeController.GenerateModels(_umbracoServices, modelsDirectory, modelsNamespace); diff --git a/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderComponent.cs b/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderComponent.cs index 70d55368ac..4a02ccf82f 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderComponent.cs +++ b/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderComponent.cs @@ -56,17 +56,17 @@ namespace Umbraco.ModelsBuilder.Umbraco ServerVariablesParser.Parsing += (sender, serverVars) => { if (!serverVars.ContainsKey("umbracoUrls")) - throw new Exception("Missing umbracoUrls."); + throw new ArgumentException("Missing umbracoUrls."); var umbracoUrlsObject = serverVars["umbracoUrls"]; if (umbracoUrlsObject == null) - throw new Exception("Null umbracoUrls"); + throw new ArgumentException("Null umbracoUrls"); if (!(umbracoUrlsObject is Dictionary umbracoUrls)) - throw new Exception("Invalid umbracoUrls"); + throw new ArgumentException("Invalid umbracoUrls"); if (!serverVars.ContainsKey("umbracoPlugins")) - throw new Exception("Missing umbracoPlugins."); + throw new ArgumentException("Missing umbracoPlugins."); if (!(serverVars["umbracoPlugins"] is Dictionary umbracoPlugins)) - throw new Exception("Invalid 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())); diff --git a/src/Umbraco.ModelsBuilder/Umbraco/UmbracoServices.cs b/src/Umbraco.ModelsBuilder/Umbraco/UmbracoServices.cs index 67ef10be1a..32f0703bac 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/UmbracoServices.cs +++ b/src/Umbraco.ModelsBuilder/Umbraco/UmbracoServices.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using Umbraco.Core; using Umbraco.Core.Composing; +using Umbraco.Core.Exceptions; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Services; @@ -88,7 +89,7 @@ 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); var publishedContentType = _publishedContentTypeFactory.CreateContentType(contentType); @@ -128,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; @@ -150,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; @@ -159,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; From c5bee7753744431236935806417ae7dad62533e7 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 23 Oct 2019 14:40:52 +0200 Subject: [PATCH 15/49] AB3267 - removed option to disable modelsbuilder - dont make sense for v8, and everything is broken when if you did it. --- build/NuSpecs/tools/Web.config.install.xdt | 1 - .../Configuration/Config.cs | 18 ---------- .../Dashboard/BuilderDashboardHelper.cs | 3 -- .../ModelsBuilderBackOfficeController.cs | 2 +- .../Umbraco/ModelsBuilderComponent.cs | 5 ++- .../Validation/ContentTypeModelValidator.cs | 34 ++++++++----------- src/Umbraco.Web.UI/web.Template.config | 1 - 7 files changed, 18 insertions(+), 46 deletions(-) diff --git a/build/NuSpecs/tools/Web.config.install.xdt b/build/NuSpecs/tools/Web.config.install.xdt index 2b79f95c70..6579717a60 100644 --- a/build/NuSpecs/tools/Web.config.install.xdt +++ b/build/NuSpecs/tools/Web.config.install.xdt @@ -14,7 +14,6 @@ - diff --git a/src/Umbraco.ModelsBuilder/Configuration/Config.cs b/src/Umbraco.ModelsBuilder/Configuration/Config.cs index 8fdc10d5dc..cf98e383eb 100644 --- a/src/Umbraco.ModelsBuilder/Configuration/Config.cs +++ b/src/Umbraco.ModelsBuilder/Configuration/Config.cs @@ -22,10 +22,6 @@ namespace Umbraco.ModelsBuilder.Configuration { const string prefix = "Umbraco.ModelsBuilder."; - // giant kill switch, default: false - // must be explicitely set to true for anything else to happen - Enable = ConfigurationManager.AppSettings[prefix + "Enable"] == "true"; - // ensure defaults are initialized for tests ModelsNamespace = DefaultModelsNamespace; ModelsDirectory = HostingEnvironment.IsHosted @@ -33,9 +29,6 @@ namespace Umbraco.ModelsBuilder.Configuration : DefaultModelsDirectory.TrimStart("~/"); DebugLevel = 0; - // stop here, everything is false - if (!Enable) return; - // mode var modelsMode = ConfigurationManager.AppSettings[prefix + "ModelsMode"]; if (!string.IsNullOrWhiteSpace(modelsMode)) @@ -105,7 +98,6 @@ namespace Umbraco.ModelsBuilder.Configuration /// Initializes a new instance of the class. /// public Config( - bool enable = false, ModelsMode modelsMode = ModelsMode.Nothing, string modelsNamespace = null, bool enableFactory = true, @@ -114,7 +106,6 @@ namespace Umbraco.ModelsBuilder.Configuration bool acceptUnsafeModelsDirectory = false, int debugLevel = 0) { - Enable = enable; ModelsMode = modelsMode; ModelsNamespace = string.IsNullOrWhiteSpace(modelsNamespace) ? DefaultModelsNamespace : modelsNamespace; @@ -156,15 +147,6 @@ namespace Umbraco.ModelsBuilder.Configuration throw new ConfigurationErrorsException($"Invalid models directory \"{config}\"."); } - /// - /// Gets a value indicating whether the whole models experience is enabled. - /// - /// - /// If this is false then absolutely nothing happens. - /// Default value is false which means that unless we have this setting, nothing happens. - /// - public bool Enable { get; } - /// /// Gets the models mode. /// diff --git a/src/Umbraco.ModelsBuilder/Dashboard/BuilderDashboardHelper.cs b/src/Umbraco.ModelsBuilder/Dashboard/BuilderDashboardHelper.cs index 75c318faab..ee9c384f9d 100644 --- a/src/Umbraco.ModelsBuilder/Dashboard/BuilderDashboardHelper.cs +++ b/src/Umbraco.ModelsBuilder/Dashboard/BuilderDashboardHelper.cs @@ -28,9 +28,6 @@ namespace Umbraco.ModelsBuilder.Dashboard { var config = Config; - if (!config.Enable) - return "Version: " + Api.ApiVersion.Current.Version + "
 
ModelsBuilder is disabled
(the .Enable key is missing, or its value is not 'true')."; - var sb = new StringBuilder(); sb.Append("Version: "); diff --git a/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderBackOfficeController.cs b/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderBackOfficeController.cs index a3ff89595c..88bfb7d7c2 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderBackOfficeController.cs +++ b/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderBackOfficeController.cs @@ -97,7 +97,7 @@ namespace Umbraco.ModelsBuilder.Umbraco { return new Dashboard { - Enable = _config.Enable, + Enable = true, Text = BuilderDashboardHelper.Text(), CanGenerate = BuilderDashboardHelper.CanGenerate(), OutOfDateModels = BuilderDashboardHelper.AreModelsOutOfDate(), diff --git a/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderComponent.cs b/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderComponent.cs index 4a02ccf82f..35f953d3f1 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderComponent.cs +++ b/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderComponent.cs @@ -35,8 +35,7 @@ namespace Umbraco.ModelsBuilder.Umbraco ContentModelBinder.ModelBindingException += ContentModelBinder_ModelBindingException; - if (_config.Enable) - FileService.SavingTemplate += FileService_SavingTemplate; + FileService.SavingTemplate += FileService_SavingTemplate; // fixme LiveModelsProvider should not be static if (_config.ModelsMode.IsLiveNotPure()) @@ -80,7 +79,7 @@ namespace Umbraco.ModelsBuilder.Umbraco { var settings = new Dictionary { - {"enabled", _config.Enable} + {"enabled", true} }; return settings; diff --git a/src/Umbraco.ModelsBuilder/Validation/ContentTypeModelValidator.cs b/src/Umbraco.ModelsBuilder/Validation/ContentTypeModelValidator.cs index dc4649f364..4b38de0168 100644 --- a/src/Umbraco.ModelsBuilder/Validation/ContentTypeModelValidator.cs +++ b/src/Umbraco.ModelsBuilder/Validation/ContentTypeModelValidator.cs @@ -39,32 +39,28 @@ namespace Umbraco.ModelsBuilder.Validation protected override IEnumerable Validate(TModel model) { - //don't do anything if we're not enabled - if (Config.Enable) + var properties = model.Groups.SelectMany(x => x.Properties) + .Where(x => x.Inherited == false) + .ToArray(); + + foreach (var prop in properties) { - var properties = model.Groups.SelectMany(x => x.Properties) - .Where(x => x.Inherited == false) - .ToArray(); + var propertyGroup = model.Groups.Single(x => x.Properties.Contains(prop)); - 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[] + 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); + //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; - } + var validationResult = ValidateProperty(prop, groupIndex, propertyIndex); + if (validationResult != null) + { + yield return validationResult; } } } diff --git a/src/Umbraco.Web.UI/web.Template.config b/src/Umbraco.Web.UI/web.Template.config index f0abbfde52..8759fc6459 100644 --- a/src/Umbraco.Web.UI/web.Template.config +++ b/src/Umbraco.Web.UI/web.Template.config @@ -47,7 +47,6 @@ - From 43cd1268caed58c7a6cc2e312774411b681ef84b Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 28 Oct 2019 18:02:52 +1100 Subject: [PATCH 16/49] Moves classes/namespaces, reduces statics, reduces usages of "Current", imports some unit tests from orig MB. --- src/SolutionInfo.cs | 3 + .../BackOffice/DashboardReport.cs | 58 +++ .../ModelsBuilderBackOfficeController.cs | 73 +-- src/Umbraco.ModelsBuilder/Building/Builder.cs | 24 +- .../Building/TextBuilder.cs | 26 +- .../Building/TypeModel.cs | 8 +- .../ModelsBuilderComponent.cs | 56 ++- .../ModelsBuilderComposer.cs | 7 +- .../ModelsBuilderInitializer.cs | 6 +- .../ConfigsExtensions.cs | 4 +- .../Configuration/IModelsBuilderConfig.cs | 14 + .../{Config.cs => ModelsBuilderConfig.cs} | 25 +- .../Configuration/ModelsMode.cs | 2 +- .../Dashboard/BuilderDashboardHelper.cs | 62 --- .../{Umbraco => }/HashCombiner.cs | 8 +- .../Umbraco.ModelsBuilder.csproj | 26 +- .../Umbraco/LiveModelsProvider.cs | 77 ++- .../Umbraco/LiveModelsProviderModule.cs | 44 ++ .../{HashHelper.cs => ModelsBuilderHasher.cs} | 2 +- .../Umbraco/ModelsGenerationError.cs | 19 +- .../Umbraco/ModelsGenerator.cs | 63 +++ .../Umbraco/OutOfDateModelsStatus.cs | 7 +- .../Umbraco/PureLiveModelFactory.cs | 14 +- .../Umbraco/UmbracoServices.cs | 2 - .../Validation/ContentTypeModelValidator.cs | 2 +- .../ModelsBuilder/BuilderTests.cs | 443 ++++++++++++++++++ .../ModelsBuilder/ConfigTests.cs | 49 ++ .../ModelsBuilder/StringExtensions.cs | 16 + .../ModelsBuilder/UmbracoApplicationTests.cs | 63 +++ src/Umbraco.Tests/Umbraco.Tests.csproj | 8 + 30 files changed, 923 insertions(+), 288 deletions(-) create mode 100644 src/Umbraco.ModelsBuilder/BackOffice/DashboardReport.cs rename src/Umbraco.ModelsBuilder/{Umbraco => BackOffice}/ModelsBuilderBackOfficeController.cs (64%) rename src/Umbraco.ModelsBuilder/{Umbraco => Compose}/ModelsBuilderComponent.cs (81%) rename src/Umbraco.ModelsBuilder/{Umbraco => Compose}/ModelsBuilderComposer.cs (89%) rename src/Umbraco.ModelsBuilder/{Umbraco => Compose}/ModelsBuilderInitializer.cs (88%) create mode 100644 src/Umbraco.ModelsBuilder/Configuration/IModelsBuilderConfig.cs rename src/Umbraco.ModelsBuilder/Configuration/{Config.cs => ModelsBuilderConfig.cs} (90%) delete mode 100644 src/Umbraco.ModelsBuilder/Dashboard/BuilderDashboardHelper.cs rename src/Umbraco.ModelsBuilder/{Umbraco => }/HashCombiner.cs (76%) create mode 100644 src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProviderModule.cs rename src/Umbraco.ModelsBuilder/Umbraco/{HashHelper.cs => ModelsBuilderHasher.cs} (97%) create mode 100644 src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerator.cs create mode 100644 src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs create mode 100644 src/Umbraco.Tests/ModelsBuilder/ConfigTests.cs create mode 100644 src/Umbraco.Tests/ModelsBuilder/StringExtensions.cs create mode 100644 src/Umbraco.Tests/ModelsBuilder/UmbracoApplicationTests.cs diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index bf3a271d32..93921e07a2 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -1,5 +1,6 @@ using System.Reflection; using System.Resources; +using System.Runtime.CompilerServices; [assembly: AssemblyCompany("Umbraco")] [assembly: AssemblyCopyright("Copyright © Umbraco 2019")] @@ -20,3 +21,5 @@ using System.Resources; // these are FYI and changed automatically [assembly: AssemblyFileVersion("8.3.0")] [assembly: AssemblyInformationalVersion("8.3.0")] + +[assembly: InternalsVisibleTo("Umbraco.Tests")] diff --git a/src/Umbraco.ModelsBuilder/BackOffice/DashboardReport.cs b/src/Umbraco.ModelsBuilder/BackOffice/DashboardReport.cs new file mode 100644 index 0000000000..57afeaf069 --- /dev/null +++ b/src/Umbraco.ModelsBuilder/BackOffice/DashboardReport.cs @@ -0,0 +1,58 @@ +using System.Text; +using Umbraco.Core.Composing; +using Umbraco.ModelsBuilder.Configuration; +using Umbraco.ModelsBuilder.Umbraco; + +namespace Umbraco.ModelsBuilder.BackOffice +{ + internal class DashboardReport + { + private readonly IModelsBuilderConfig _config; + private readonly ModelsGenerator _modelsGenerator; + + public DashboardReport(IModelsBuilderConfig config, ModelsGenerator modelsGenerator) + { + _config = config; + _modelsGenerator = modelsGenerator; + } + + public bool CanGenerate() => _config.ModelsMode.SupportsExplicitGeneration(); + + public bool AreModelsOutOfDate() => OutOfDateModelsStatus.IsOutOfDate; + + public string LastError() => _modelsGenerator.GetLastError(); + + public string Text() + { + var sb = new StringBuilder(); + + sb.Append("Version: "); + sb.Append(Api.ApiVersion.Current.Version); + sb.Append("
 
"); + + sb.Append("ModelsBuilder is enabled, with the following configuration:"); + + sb.Append("
    "); + + sb.Append("
  • The models factory is "); + sb.Append(_config.EnableFactory || _config.ModelsMode == ModelsMode.PureLive + ? "enabled" + : "not enabled. Umbraco will not use models"); + sb.Append(".
  • "); + + sb.Append(_config.ModelsMode != ModelsMode.Nothing + ? $"
  • {_config.ModelsMode} models are enabled.
  • " + : "
  • No models mode is specified: models will not be generated.
  • "); + + sb.Append($"
  • Models namespace is {_config.ModelsNamespace}.
  • "); + + sb.Append("
  • Tracking of out-of-date models is "); + sb.Append(_config.FlagOutOfDateModels ? "enabled" : "not enabled"); + sb.Append(".
  • "); + + sb.Append("
"); + + return sb.ToString(); + } + } +} diff --git a/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderBackOfficeController.cs b/src/Umbraco.ModelsBuilder/BackOffice/ModelsBuilderBackOfficeController.cs similarity index 64% rename from src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderBackOfficeController.cs rename to src/Umbraco.ModelsBuilder/BackOffice/ModelsBuilderBackOfficeController.cs index 88bfb7d7c2..561da0a3d6 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderBackOfficeController.cs +++ b/src/Umbraco.ModelsBuilder/BackOffice/ModelsBuilderBackOfficeController.cs @@ -9,11 +9,11 @@ using System.Web.Hosting; using Umbraco.Core.Exceptions; using Umbraco.ModelsBuilder.Building; using Umbraco.ModelsBuilder.Configuration; -using Umbraco.ModelsBuilder.Dashboard; +using Umbraco.ModelsBuilder.Umbraco; using Umbraco.Web.Editors; using Umbraco.Web.WebApi.Filters; -namespace Umbraco.ModelsBuilder.Umbraco +namespace Umbraco.ModelsBuilder.BackOffice { /// /// API controller for use in the Umbraco back office with Angular resources @@ -26,13 +26,16 @@ namespace Umbraco.ModelsBuilder.Umbraco [UmbracoApplicationAuthorize(Core.Constants.Applications.Settings)] public class ModelsBuilderBackOfficeController : UmbracoAuthorizedJsonController { - private readonly UmbracoServices _umbracoServices; - private readonly Config _config; + private readonly IModelsBuilderConfig _config; + private readonly ModelsGenerator _modelGenerator; + private readonly DashboardReport _dashboardReport; - public ModelsBuilderBackOfficeController(UmbracoServices umbracoServices, Config config) + public ModelsBuilderBackOfficeController(IModelsBuilderConfig config, ModelsGenerator modelsGenerator) { //_umbracoServices = umbracoServices; _config = config; + _modelGenerator = modelsGenerator; + _dashboardReport = new DashboardReport(config, modelsGenerator); } // invoked by the dashboard @@ -51,20 +54,17 @@ namespace Umbraco.ModelsBuilder.Umbraco return Request.CreateResponse(HttpStatusCode.OK, result2, Configuration.Formatters.JsonFormatter); } - var modelsDirectory = config.ModelsDirectory; - var bin = HostingEnvironment.MapPath("~/bin"); if (bin == null) throw new PanicException("bin is null."); // EnableDllModels will recycle the app domain - but this request will end properly - GenerateModels(modelsDirectory); - - ModelsGenerationError.Clear(); + _modelGenerator.GenerateModels(); + _modelGenerator.ClearErrors(); } catch (Exception e) { - ModelsGenerationError.Report("Failed to build models.", e); + _modelGenerator.ReportError("Failed to build models.", e); } return Request.CreateResponse(HttpStatusCode.OK, GetDashboardResult(), Configuration.Formatters.JsonFormatter); @@ -76,9 +76,9 @@ namespace Umbraco.ModelsBuilder.Umbraco public HttpResponseMessage GetModelsOutOfDateStatus() { var status = OutOfDateModelsStatus.IsEnabled - ? (OutOfDateModelsStatus.IsOutOfDate + ? OutOfDateModelsStatus.IsOutOfDate ? new OutOfDateStatus { Status = OutOfDateType.OutOfDate } - : new OutOfDateStatus { Status = OutOfDateType.Current }) + : new OutOfDateStatus { Status = OutOfDateType.Current } : new OutOfDateStatus { Status = OutOfDateType.Unknown }; return Request.CreateResponse(HttpStatusCode.OK, status, Configuration.Formatters.JsonFormatter); @@ -98,52 +98,13 @@ namespace Umbraco.ModelsBuilder.Umbraco return new Dashboard { Enable = true, - Text = BuilderDashboardHelper.Text(), - CanGenerate = BuilderDashboardHelper.CanGenerate(), - OutOfDateModels = BuilderDashboardHelper.AreModelsOutOfDate(), - LastError = BuilderDashboardHelper.LastError(), + Text = _dashboardReport.Text(), + CanGenerate = _dashboardReport.CanGenerate(), + OutOfDateModels = _dashboardReport.AreModelsOutOfDate(), + LastError = _dashboardReport.LastError(), }; } - private void GenerateModels(string modelsDirectory) - { - GenerateModels(_umbracoServices, modelsDirectory, _config.ModelsNamespace); - } - - internal static void GenerateModels(UmbracoServices umbracoServices, string modelsDirectory, string modelsNamespace) - { - if (!Directory.Exists(modelsDirectory)) - Directory.CreateDirectory(modelsDirectory); - - foreach (var file in Directory.GetFiles(modelsDirectory, "*.generated.cs")) - File.Delete(file); - - var typeModels = umbracoServices.GetAllTypes(); - - var builder = new TextBuilder(typeModels, modelsNamespace); - - foreach (var typeModel in builder.GetModelsToGenerate()) - { - var sb = new StringBuilder(); - builder.Generate(sb, typeModel); - var filename = Path.Combine(modelsDirectory, typeModel.ClrName + ".generated.cs"); - File.WriteAllText(filename, sb.ToString()); - } - - // the idea was to calculate the current hash and to add it as an extra file to the compilation, - // in order to be able to detect whether a DLL is consistent with an environment - however the - // environment *might not* contain the local partial files, and thus it could be impossible to - // calculate the hash. So... maybe that's not a good idea after all? - /* - var currentHash = HashHelper.Hash(ourFiles, typeModels); - ourFiles["models.hash.cs"] = $@"using Umbraco.ModelsBuilder; -[assembly:ModelsBuilderAssembly(SourceHash = ""{currentHash}"")] -"; - */ - - OutOfDateModelsStatus.Clear(); - } - [DataContract] internal class BuildResult { diff --git a/src/Umbraco.ModelsBuilder/Building/Builder.cs b/src/Umbraco.ModelsBuilder/Building/Builder.cs index f65d8e9e7c..fa05b9d9a1 100644 --- a/src/Umbraco.ModelsBuilder/Building/Builder.cs +++ b/src/Umbraco.ModelsBuilder/Building/Builder.cs @@ -23,12 +23,11 @@ namespace Umbraco.ModelsBuilder.Building /// internal abstract class Builder { + private readonly IList _typeModels; protected Dictionary ModelsMap { get; } = new Dictionary(); - private static Config Config => Current.Configs.ModelsBuilder(); - // the list of assemblies that will be 'using' by default protected readonly IList TypesUsing = new List { @@ -69,27 +68,20 @@ namespace Umbraco.ModelsBuilder.Building /// Includes those that are ignored. internal IList TypeModels => _typeModels; - /// - /// Initializes a new instance of the class with a list of models to generate - /// and the result of code parsing. - /// - /// The list of models to generate. - protected Builder(IList typeModels) - : this(typeModels, null) - { } - /// /// Initializes a new instance of the class with a list of models to generate, /// the result of code parsing, and a models namespace. /// /// The list of models to generate. /// The models namespace. - protected Builder(IList typeModels, string modelsNamespace) + protected Builder(IModelsBuilderConfig config, IList typeModels) { _typeModels = typeModels ?? throw new ArgumentNullException(nameof(typeModels)); + Config = config ?? throw new ArgumentNullException(nameof(config)); + // can be null or empty, we'll manage - ModelsNamespace = modelsNamespace; + ModelsNamespace = Config.ModelsNamespace; // but we want it to prepare Prepare(); @@ -99,6 +91,8 @@ namespace Umbraco.ModelsBuilder.Building protected Builder() { } + protected IModelsBuilderConfig Config { get; } + /// /// Prepares generation by processing the result of code parsing. /// @@ -204,6 +198,8 @@ namespace Umbraco.ModelsBuilder.Building // cannot figure out is a symbol is ambiguous without Roslyn // so... let's say everything is ambiguous - code won't be // pretty but it'll work + + // Essentially this means that a `global::` syntax will be output for the generated models return true; } @@ -220,7 +216,7 @@ namespace Umbraco.ModelsBuilder.Building // default // fixme - should NOT reference config here, should make ModelsNamespace mandatory - return Config.ModelsNamespace; + return string.IsNullOrWhiteSpace(Config.ModelsNamespace) ? ModelsBuilderConfig.DefaultModelsNamespace : Config.ModelsNamespace; } protected string GetModelsBaseClassName(TypeModel type) diff --git a/src/Umbraco.ModelsBuilder/Building/TextBuilder.cs b/src/Umbraco.ModelsBuilder/Building/TextBuilder.cs index 7121dad1a9..79a64bd1ed 100644 --- a/src/Umbraco.ModelsBuilder/Building/TextBuilder.cs +++ b/src/Umbraco.ModelsBuilder/Building/TextBuilder.cs @@ -20,31 +20,19 @@ namespace Umbraco.ModelsBuilder.Building /// and the result of code parsing. /// /// The list of models to generate. - public TextBuilder(IList typeModels) - : base(typeModels) - { } - - /// - /// Initializes a new instance of the class with a list of models to generate, - /// the result of code parsing, and a models namespace. - /// - /// The list of models to generate. - /// The models namespace. - public TextBuilder(IList typeModels, string modelsNamespace) - : base(typeModels, modelsNamespace) + public TextBuilder(IModelsBuilderConfig config, IList typeModels) + : base(config, typeModels) { } // internal for unit tests only internal TextBuilder() { } - private static Config Config => Current.Configs.ModelsBuilder(); - /// - /// Outputs a generated model to a string builder. - /// - /// The string builder. - /// The model to generate. + /// Outputs a generated model to a string builder. + /// + /// The string builder. + /// The model to generate. public void Generate(StringBuilder sb, TypeModel typeModel) { WriteHeader(sb); @@ -354,7 +342,7 @@ namespace Umbraco.ModelsBuilder.Building var mixinStaticGetterName = MixinStaticGetterName(property.ClrName); - if (type.StaticMixinMethods.Contains(mixinStaticGetterName)) return; + //if (type.StaticMixinMethods.Contains(mixinStaticGetterName)) return; sb.Append("\n"); diff --git a/src/Umbraco.ModelsBuilder/Building/TypeModel.cs b/src/Umbraco.ModelsBuilder/Building/TypeModel.cs index 941894774e..06b5e7848a 100644 --- a/src/Umbraco.ModelsBuilder/Building/TypeModel.cs +++ b/src/Umbraco.ModelsBuilder/Building/TypeModel.cs @@ -77,10 +77,10 @@ namespace Umbraco.ModelsBuilder.Building /// public readonly List ImplementingInterfaces = new List(); - /// - /// Gets the list of existing static mixin method candidates. - /// - public readonly List StaticMixinMethods = new List(); + ///// + ///// Gets the list of existing static mixin method candidates. + ///// + //public readonly List StaticMixinMethods = new List(); //TODO: Do we need this? it isn't used /// /// Gets a value indicating whether this model has a base class. diff --git a/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderComponent.cs b/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderComponent.cs similarity index 81% rename from src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderComponent.cs rename to src/Umbraco.ModelsBuilder/Compose/ModelsBuilderComponent.cs index 35f953d3f1..6a9b8e1115 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderComponent.cs +++ b/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderComponent.cs @@ -8,23 +8,25 @@ using Umbraco.Core.Composing; using Umbraco.Core.IO; using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; +using Umbraco.ModelsBuilder.BackOffice; using Umbraco.ModelsBuilder.Configuration; +using Umbraco.ModelsBuilder.Umbraco; using Umbraco.Web; using Umbraco.Web.JavaScript; using Umbraco.Web.Mvc; -namespace Umbraco.ModelsBuilder.Umbraco +namespace Umbraco.ModelsBuilder.Compose { public class ModelsBuilderComponent : IComponent { - private readonly UmbracoServices _umbracoServices; - private readonly Config _config; + private readonly IModelsBuilderConfig _config; + private readonly LiveModelsProvider _liveModelsProvider; - public ModelsBuilderComponent(UmbracoServices umbracoServices, Config config) + public ModelsBuilderComponent(IModelsBuilderConfig config, LiveModelsProvider liveModelsProvider) { - _umbracoServices = umbracoServices; _config = config; + _liveModelsProvider = liveModelsProvider; } public void Initialize() @@ -39,7 +41,7 @@ namespace Umbraco.ModelsBuilder.Umbraco // fixme LiveModelsProvider should not be static if (_config.ModelsMode.IsLiveNotPure()) - LiveModelsProvider.Install(_umbracoServices); + _liveModelsProvider.Install(); // fixme OutOfDateModelsStatus should not be static if (_config.FlagOutOfDateModels) @@ -105,7 +107,6 @@ namespace Umbraco.ModelsBuilder.Umbraco throw new InvalidOperationException("The additionalData key: ContentTypeAlias was not found"); foreach (var template in e.SavedEntities) - { // if it is in fact a new entity (not been saved yet) and the "CreateTemplateForContentType" key // is found, then it means a new template is being created based on the creation of a document type if (!template.HasIdentity && string.IsNullOrWhiteSpace(template.Content)) @@ -129,7 +130,6 @@ namespace Umbraco.ModelsBuilder.Umbraco //set the template content to the new markup template.Content = markup; } - } } private void ContentModelBinder_ModelBindingException(object sender, ContentModelBinder.ModelBindingArgs args) @@ -154,32 +154,30 @@ namespace Umbraco.ModelsBuilder.Umbraco } // both are ModelsBuilder types - var pureSource = sourceAttr.PureLive; - var pureModel = modelAttr.PureLive; + var pureSource = sourceAttr.PureLive; + var pureModel = modelAttr.PureLive; - if (sourceAttr.PureLive || modelAttr.PureLive) - { - if (pureSource == false || pureModel == false) - { + if (sourceAttr.PureLive || modelAttr.PureLive) + if (pureSource == false || pureModel == false) + { // only one is pure - report, but better not restart (loops?) - args.Message.Append(pureSource - ? " The content model is PureLive, but the view model is not." - : " The view model is PureLive, but the content model is not."); - args.Message.Append(" The application is in an unstable state and should be restarted."); - } - else - { + args.Message.Append(pureSource + ? " The content model is PureLive, but the view model is not." + : " The view model is PureLive, but the content model is not."); + args.Message.Append(" The application is in an unstable state and should be restarted."); + } + else + { // both are pure - report, and if different versions, restart // if same version... makes no sense... and better not restart (loops?) - var sourceVersion = args.SourceType.Assembly.GetName().Version; + var sourceVersion = args.SourceType.Assembly.GetName().Version; var modelVersion = args.ModelType.Assembly.GetName().Version; - args.Message.Append(" Both view and content models are PureLive, with "); - args.Message.Append(sourceVersion == modelVersion - ? "same version. The application is in an unstable state and should be restarted." - : "different versions. The application is in an unstable state and is going to be restarted."); - args.Restart = sourceVersion != modelVersion; - } - } + args.Message.Append(" Both view and content models are PureLive, with "); + args.Message.Append(sourceVersion == modelVersion + ? "same version. The application is in an unstable state and should be restarted." + : "different versions. The application is in an unstable state and is going to be restarted."); + args.Restart = sourceVersion != modelVersion; + } } } } diff --git a/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderComposer.cs b/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderComposer.cs similarity index 89% rename from src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderComposer.cs rename to src/Umbraco.ModelsBuilder/Compose/ModelsBuilderComposer.cs index 3dae2f86ab..71250b2eb6 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderComposer.cs +++ b/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderComposer.cs @@ -3,9 +3,10 @@ using Umbraco.Core; using Umbraco.Core.Composing; using Umbraco.Core.Models.PublishedContent; using Umbraco.ModelsBuilder.Configuration; +using Umbraco.ModelsBuilder.Umbraco; using Umbraco.Web.PublishedCache.NuCache; -namespace Umbraco.ModelsBuilder.Umbraco +namespace Umbraco.ModelsBuilder.Compose { [ComposeBefore(typeof(NuCacheComposer))] [RuntimeLevel(MinLevel = RuntimeLevel.Run)] @@ -16,7 +17,9 @@ namespace Umbraco.ModelsBuilder.Umbraco base.Compose(composition); composition.Register(Lifetime.Singleton); - composition.Configs.Add(() => new Config()); + composition.Configs.Add(() => new ModelsBuilderConfig()); + composition.RegisterUnique(); + composition.RegisterUnique(); if (composition.Configs.ModelsBuilder().ModelsMode == ModelsMode.PureLive) ComposeForLiveModels(composition); diff --git a/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderInitializer.cs b/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderInitializer.cs similarity index 88% rename from src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderInitializer.cs rename to src/Umbraco.ModelsBuilder/Compose/ModelsBuilderInitializer.cs index 9bd662da37..63f2336ebf 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderInitializer.cs +++ b/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderInitializer.cs @@ -1,12 +1,12 @@ using System.Web; using System.Web.Compilation; -using Umbraco.ModelsBuilder.Umbraco; +using Umbraco.ModelsBuilder.Compose; [assembly: PreApplicationStartMethod(typeof(ModelsBuilderInitializer), "Initialize")] -namespace Umbraco.ModelsBuilder.Umbraco +namespace Umbraco.ModelsBuilder.Compose { - public static class ModelsBuilderInitializer + internal static class ModelsBuilderInitializer { public static void Initialize() { diff --git a/src/Umbraco.ModelsBuilder/ConfigsExtensions.cs b/src/Umbraco.ModelsBuilder/ConfigsExtensions.cs index dc0b136422..c989be5aca 100644 --- a/src/Umbraco.ModelsBuilder/ConfigsExtensions.cs +++ b/src/Umbraco.ModelsBuilder/ConfigsExtensions.cs @@ -14,7 +14,7 @@ namespace Umbraco.ModelsBuilder /// Getting the models builder configuration freezes its state, /// and any attempt at modifying the configuration using the Setup method /// will be ignored. - public static Config ModelsBuilder(this Configs configs) - => configs.GetConfig(); + public static ModelsBuilderConfig ModelsBuilder(this Configs configs) + => configs.GetConfig(); } } \ No newline at end of file diff --git a/src/Umbraco.ModelsBuilder/Configuration/IModelsBuilderConfig.cs b/src/Umbraco.ModelsBuilder/Configuration/IModelsBuilderConfig.cs new file mode 100644 index 0000000000..3bca389f2f --- /dev/null +++ b/src/Umbraco.ModelsBuilder/Configuration/IModelsBuilderConfig.cs @@ -0,0 +1,14 @@ +namespace Umbraco.ModelsBuilder.Configuration +{ + public interface IModelsBuilderConfig + { + bool AcceptUnsafeModelsDirectory { get; } + int DebugLevel { get; } + bool EnableFactory { get; } + bool FlagOutOfDateModels { get; } + bool IsDebug { get; } + string ModelsDirectory { get; } + ModelsMode ModelsMode { get; } + string ModelsNamespace { get; } + } +} diff --git a/src/Umbraco.ModelsBuilder/Configuration/Config.cs b/src/Umbraco.ModelsBuilder/Configuration/ModelsBuilderConfig.cs similarity index 90% rename from src/Umbraco.ModelsBuilder/Configuration/Config.cs rename to src/Umbraco.ModelsBuilder/Configuration/ModelsBuilderConfig.cs index cf98e383eb..caff5001a4 100644 --- a/src/Umbraco.ModelsBuilder/Configuration/Config.cs +++ b/src/Umbraco.ModelsBuilder/Configuration/ModelsBuilderConfig.cs @@ -4,29 +4,28 @@ using System.IO; using System.Web.Configuration; using System.Web.Hosting; using Umbraco.Core; +using Umbraco.Core.IO; namespace Umbraco.ModelsBuilder.Configuration { /// /// Represents the models builder configuration. /// - public class Config + public class ModelsBuilderConfig : IModelsBuilderConfig { - internal const string DefaultModelsNamespace = "Umbraco.Web.PublishedModels"; - internal const string DefaultModelsDirectory = "~/App_Data/Models"; + public const string DefaultModelsNamespace = "Umbraco.Web.PublishedModels"; + public const string DefaultModelsDirectory = "~/App_Data/Models"; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public Config() + public ModelsBuilderConfig() { const string prefix = "Umbraco.ModelsBuilder."; // ensure defaults are initialized for tests ModelsNamespace = DefaultModelsNamespace; - ModelsDirectory = HostingEnvironment.IsHosted - ? HostingEnvironment.MapPath(DefaultModelsDirectory) - : DefaultModelsDirectory.TrimStart("~/"); + ModelsDirectory = IOHelper.MapPath("~/"); DebugLevel = 0; // mode @@ -69,9 +68,7 @@ namespace Umbraco.ModelsBuilder.Configuration value = ConfigurationManager.AppSettings[prefix + "ModelsDirectory"]; if (!string.IsNullOrWhiteSpace(value)) { - var root = HostingEnvironment.IsHosted - ? HostingEnvironment.MapPath("~/") - : Directory.GetCurrentDirectory(); + var root = IOHelper.MapPath("~/"); if (root == null) throw new ConfigurationErrorsException("Could not determine root directory."); @@ -95,9 +92,9 @@ namespace Umbraco.ModelsBuilder.Configuration } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public Config( + public ModelsBuilderConfig( ModelsMode modelsMode = ModelsMode.Nothing, string modelsNamespace = null, bool enableFactory = true, @@ -159,7 +156,7 @@ namespace Umbraco.ModelsBuilder.Configuration { get { - var section = (CompilationSection) ConfigurationManager.GetSection("system.web/compilation"); + var section = (CompilationSection)ConfigurationManager.GetSection("system.web/compilation"); return section != null && section.Debug; } } diff --git a/src/Umbraco.ModelsBuilder/Configuration/ModelsMode.cs b/src/Umbraco.ModelsBuilder/Configuration/ModelsMode.cs index cc36099bc5..1f1d65f4f1 100644 --- a/src/Umbraco.ModelsBuilder/Configuration/ModelsMode.cs +++ b/src/Umbraco.ModelsBuilder/Configuration/ModelsMode.cs @@ -8,7 +8,7 @@ /// /// Do not generate models. /// - Nothing = 0, // default value + Nothing = 0, // default value //TODO: This doesn't make sense since we cannot actualy disable MB since Umbraco would die /// /// Generate models in memory. diff --git a/src/Umbraco.ModelsBuilder/Dashboard/BuilderDashboardHelper.cs b/src/Umbraco.ModelsBuilder/Dashboard/BuilderDashboardHelper.cs deleted file mode 100644 index ee9c384f9d..0000000000 --- a/src/Umbraco.ModelsBuilder/Dashboard/BuilderDashboardHelper.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System.Text; -using Umbraco.Core.Composing; -using Umbraco.ModelsBuilder.Configuration; -using Umbraco.ModelsBuilder.Umbraco; - -namespace Umbraco.ModelsBuilder.Dashboard -{ - internal static class BuilderDashboardHelper - { - private static Config Config => Current.Configs.ModelsBuilder(); - - public static bool CanGenerate() - { - return Config.ModelsMode.SupportsExplicitGeneration(); - } - - public static bool AreModelsOutOfDate() - { - return OutOfDateModelsStatus.IsOutOfDate; - } - - public static string LastError() - { - return ModelsGenerationError.GetLastError(); - } - - public static string Text() - { - var config = Config; - - var sb = new StringBuilder(); - - sb.Append("Version: "); - sb.Append(Api.ApiVersion.Current.Version); - sb.Append("
 
"); - - sb.Append("ModelsBuilder is enabled, with the following configuration:"); - - sb.Append("
    "); - - sb.Append("
  • The models factory is "); - sb.Append(config.EnableFactory || config.ModelsMode == ModelsMode.PureLive - ? "enabled" - : "not enabled. Umbraco will not use models"); - sb.Append(".
  • "); - - sb.Append(config.ModelsMode != ModelsMode.Nothing - ? $"
  • {config.ModelsMode} models are enabled.
  • " - : "
  • No models mode is specified: models will not be generated.
  • "); - - sb.Append($"
  • Models namespace is {config.ModelsNamespace}.
  • "); - - sb.Append("
  • Tracking of out-of-date models is "); - sb.Append(config.FlagOutOfDateModels ? "enabled" : "not enabled"); - sb.Append(".
  • "); - - sb.Append("
"); - - return sb.ToString(); - } - } -} diff --git a/src/Umbraco.ModelsBuilder/Umbraco/HashCombiner.cs b/src/Umbraco.ModelsBuilder/HashCombiner.cs similarity index 76% rename from src/Umbraco.ModelsBuilder/Umbraco/HashCombiner.cs rename to src/Umbraco.ModelsBuilder/HashCombiner.cs index e11662eb24..51e02e93c1 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/HashCombiner.cs +++ b/src/Umbraco.ModelsBuilder/HashCombiner.cs @@ -1,17 +1,17 @@ using System; using System.Globalization; -namespace Umbraco.ModelsBuilder.Umbraco +namespace Umbraco.ModelsBuilder { // because, of course, it's internal in Umbraco // see also System.Web.Util.HashCodeCombiner - class HashCombiner + internal class HashCombiner { private long _combinedHash = 5381L; public void Add(int i) { - _combinedHash = ((_combinedHash << 5) + _combinedHash) ^ i; + _combinedHash = (_combinedHash << 5) + _combinedHash ^ i; } public void Add(object o) @@ -27,7 +27,7 @@ namespace Umbraco.ModelsBuilder.Umbraco public void Add(string s) { if (s == null) return; - Add((StringComparer.InvariantCulture).GetHashCode(s)); + Add(StringComparer.InvariantCulture.GetHashCode(s)); } public string GetCombinedHashCode() diff --git a/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj b/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj index a16579e60d..6945cc4b31 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj +++ b/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj @@ -56,10 +56,11 @@ - + + - + @@ -67,13 +68,15 @@ - - + - - - - + + + + + + + @@ -92,10 +95,6 @@ - - {29aa69d9-b597-4395-8d42-43b1263c240a} - Umbraco.Abstractions - {31785BC3-256C-4613-B2F5-A1B0BDDED8C1} Umbraco.Core @@ -110,5 +109,8 @@ 5.2.7 + + + \ No newline at end of file diff --git a/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProvider.cs b/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProvider.cs index d8839a1297..146e837dd9 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProvider.cs +++ b/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProvider.cs @@ -2,43 +2,46 @@ using System.Threading; using System.Web; using System.Web.Hosting; -using Umbraco.Core.Composing; using Umbraco.Core.Exceptions; +using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.ModelsBuilder.Configuration; using Umbraco.ModelsBuilder.Umbraco; using Umbraco.Web.Cache; -// will install only if configuration says it needs to be installed -[assembly: PreApplicationStartMethod(typeof(LiveModelsProviderModule), "Install")] - namespace Umbraco.ModelsBuilder.Umbraco { // supports LiveDll and LiveAppData - but not PureLive public sealed class LiveModelsProvider { - private static UmbracoServices _umbracoServices; private static Mutex _mutex; private static int _req; - - private static Config Config => Current.Configs.ModelsBuilder(); + private readonly ILogger _logger; + private readonly IModelsBuilderConfig _config; + private readonly ModelsGenerator _modelGenerator; // we do not manage pure live here - internal static bool IsEnabled => Config.ModelsMode.IsLiveNotPure(); + internal bool IsEnabled => _config.ModelsMode.IsLiveNotPure(); - internal static void Install(UmbracoServices umbracoServices) + public LiveModelsProvider(ILogger logger, IModelsBuilderConfig config, ModelsGenerator modelGenerator) + { + _logger = logger; + _config = config ?? throw new ArgumentNullException(nameof(config)); + _modelGenerator = modelGenerator; + } + + internal void Install() { // just be sure if (!IsEnabled) return; - _umbracoServices = umbracoServices; - // initialize mutex // ApplicationId will look like "/LM/W3SVC/1/Root/AppName" // name is system-wide and must be less than 260 chars var name = HostingEnvironment.ApplicationID + "/UmbracoLiveModelsProvider"; - _mutex = new Mutex(false, name); + + _mutex = new Mutex(false, name); //TODO: Replace this with MainDom? Seems we now have 2x implementations of almost the same thing // anything changes, and we want to re-generate models. ContentTypeCacheRefresher.CacheUpdated += RequestModelsGeneration; @@ -57,14 +60,14 @@ namespace Umbraco.ModelsBuilder.Umbraco // need to be generated. Could be by another request. Anyway. We could // have collisions but... you know the risk. - private static void RequestModelsGeneration(object sender, EventArgs args) + private void RequestModelsGeneration(object sender, EventArgs args) { //HttpContext.Current.Items[this] = true; - Current.Logger.Debug("Requested to generate models."); + _logger.Debug("Requested to generate models."); Interlocked.Exchange(ref _req, 1); } - public static void GenerateModelsIfRequested(object sender, EventArgs args) + public void GenerateModelsIfRequested(object sender, EventArgs args) { //if (HttpContext.Current.Items[this] == null) return; if (Interlocked.Exchange(ref _req, 0) == 0) return; @@ -74,22 +77,22 @@ namespace Umbraco.ModelsBuilder.Umbraco try { - Current.Logger.Debug("Generate models..."); + _logger.Debug("Generate models..."); const int timeout = 2*60*1000; // 2 mins _mutex.WaitOne(timeout); // wait until it is safe, and acquire - Current.Logger.Info("Generate models now."); + _logger.Info("Generate models now."); GenerateModels(); - ModelsGenerationError.Clear(); - Current.Logger.Info("Generated."); + _modelGenerator.ClearErrors(); + _logger.Info("Generated."); } catch (TimeoutException) { - Current.Logger.Warn("Timeout, models were NOT generated."); + _logger.Warn("Timeout, models were NOT generated."); } catch (Exception e) { - ModelsGenerationError.Report("Failed to build Live models.", e); - Current.Logger.Error("Failed to generate models.", e); + _modelGenerator.ReportError("Failed to build Live models.", e); + _logger.Error("Failed to generate models.", e); } finally { @@ -97,38 +100,16 @@ namespace Umbraco.ModelsBuilder.Umbraco } } - private static void GenerateModels() + private void GenerateModels() { - var modelsDirectory = Config.ModelsDirectory; - var modelsNamespace = Config.ModelsNamespace; - - var bin = HostingEnvironment.MapPath("~/bin"); + var bin = IOHelper.MapPath("~/bin"); if (bin == null) throw new PanicException("Panic: bin is null."); // EnableDllModels will recycle the app domain - but this request will end properly - ModelsBuilderBackOfficeController.GenerateModels(_umbracoServices, modelsDirectory, modelsNamespace); - } - } - - // have to do this because it's the only way to subscribe to EndRequest, - // module is installed by assembly attribute at the top of this file - public class LiveModelsProviderModule : IHttpModule - { - public void Init(HttpApplication app) - { - app.EndRequest += LiveModelsProvider.GenerateModelsIfRequested; + _modelGenerator.GenerateModels(); } - public void Dispose() - { - // nothing - } - - public static void Install() - { - // always - don't read config in PreApplicationStartMethod - HttpApplication.RegisterModule(typeof(LiveModelsProviderModule)); - } + } } diff --git a/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProviderModule.cs b/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProviderModule.cs new file mode 100644 index 0000000000..1dadbd41b6 --- /dev/null +++ b/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProviderModule.cs @@ -0,0 +1,44 @@ +using System; +using System.Web; +using Umbraco.Core; +using Umbraco.Core.Composing; +using Umbraco.ModelsBuilder.Umbraco; + +// will install only if configuration says it needs to be installed +[assembly: PreApplicationStartMethod(typeof(LiveModelsProviderModule), "Install")] + +namespace Umbraco.ModelsBuilder.Umbraco +{ + // have to do this because it's the only way to subscribe to EndRequest, + // module is installed by assembly attribute at the top of this file + public class LiveModelsProviderModule : IHttpModule + { + private static LiveModelsProvider _liveModelsProvider; + + public void Init(HttpApplication app) + { + app.EndRequest += App_EndRequest; + } + + private void App_EndRequest(object sender, EventArgs e) + { + // here we're using "Current." since we're in a module, it is possible in a round about way to inject into a module but for now we'll just use Current + if (_liveModelsProvider == null) + _liveModelsProvider = Current.Factory.GetInstance(); + + if (_liveModelsProvider.IsEnabled) + _liveModelsProvider.GenerateModelsIfRequested(sender, e); + } + + public void Dispose() + { + // nothing + } + + public static void Install() + { + // always - don't read config in PreApplicationStartMethod + HttpApplication.RegisterModule(typeof(LiveModelsProviderModule)); + } + } +} diff --git a/src/Umbraco.ModelsBuilder/Umbraco/HashHelper.cs b/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderHasher.cs similarity index 97% rename from src/Umbraco.ModelsBuilder/Umbraco/HashHelper.cs rename to src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderHasher.cs index e4a0705ec0..3354b4040d 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/HashHelper.cs +++ b/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderHasher.cs @@ -4,7 +4,7 @@ using Umbraco.ModelsBuilder.Building; namespace Umbraco.ModelsBuilder.Umbraco { - class HashHelper + internal class ModelsBuilderHasher { public static string Hash(IEnumerable typeModels) { diff --git a/src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerationError.cs b/src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerationError.cs index a7b437df57..a7cb1e11c3 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerationError.cs +++ b/src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerationError.cs @@ -6,11 +6,16 @@ using Umbraco.ModelsBuilder.Configuration; namespace Umbraco.ModelsBuilder.Umbraco { - internal static class ModelsGenerationError + internal class ModelsGenerationError { - private static Config Config => Current.Configs.ModelsBuilder(); + private readonly IModelsBuilderConfig _config; - public static void Clear() + public ModelsGenerationError(IModelsBuilderConfig config) + { + _config = config; + } + + public void Clear() { var errFile = GetErrFile(); if (errFile == null) return; @@ -19,7 +24,7 @@ namespace Umbraco.ModelsBuilder.Umbraco File.Delete(errFile); } - public static void Report(string message, Exception e) + public void Report(string message, Exception e) { var errFile = GetErrFile(); if (errFile == null) return; @@ -35,7 +40,7 @@ namespace Umbraco.ModelsBuilder.Umbraco File.WriteAllText(errFile, sb.ToString()); } - public static string GetLastError() + public string GetLastError() { var errFile = GetErrFile(); if (errFile == null) return null; @@ -50,9 +55,9 @@ namespace Umbraco.ModelsBuilder.Umbraco } } - private static string GetErrFile() + private string GetErrFile() { - var modelsDirectory = Config.ModelsDirectory; + var modelsDirectory = _config.ModelsDirectory; if (!Directory.Exists(modelsDirectory)) return null; diff --git a/src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerator.cs b/src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerator.cs new file mode 100644 index 0000000000..4e3607c156 --- /dev/null +++ b/src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerator.cs @@ -0,0 +1,63 @@ +using System; +using System.IO; +using System.Text; +using System.Web; +using Umbraco.ModelsBuilder.Building; +using Umbraco.ModelsBuilder.Configuration; +using Umbraco.ModelsBuilder.Umbraco; + +namespace Umbraco.ModelsBuilder.Umbraco +{ + public class ModelsGenerator + { + private readonly UmbracoServices _umbracoService; + private readonly IModelsBuilderConfig _config; + private readonly ModelsGenerationError _errors; + + public ModelsGenerator(UmbracoServices umbracoService, IModelsBuilderConfig config) + { + _umbracoService = umbracoService; + _config = config; + _errors = new ModelsGenerationError(config); + } + + internal void GenerateModels() + { + if (!Directory.Exists(_config.ModelsDirectory)) + Directory.CreateDirectory(_config.ModelsDirectory); + + foreach (var file in Directory.GetFiles(_config.ModelsDirectory, "*.generated.cs")) + File.Delete(file); + + var typeModels = _umbracoService.GetAllTypes(); + + var builder = new TextBuilder(_config, typeModels); + + foreach (var typeModel in builder.GetModelsToGenerate()) + { + var sb = new StringBuilder(); + builder.Generate(sb, typeModel); + var filename = Path.Combine(_config.ModelsDirectory, typeModel.ClrName + ".generated.cs"); + File.WriteAllText(filename, sb.ToString()); + } + + // the idea was to calculate the current hash and to add it as an extra file to the compilation, + // in order to be able to detect whether a DLL is consistent with an environment - however the + // environment *might not* contain the local partial files, and thus it could be impossible to + // calculate the hash. So... maybe that's not a good idea after all? + /* + var currentHash = HashHelper.Hash(ourFiles, typeModels); + ourFiles["models.hash.cs"] = $@"using Umbraco.ModelsBuilder; +[assembly:ModelsBuilderAssembly(SourceHash = ""{currentHash}"")] +"; + */ + + OutOfDateModelsStatus.Clear(); + } + + internal void ClearErrors() => _errors.Clear(); + internal void ReportError(string message, Exception e) => _errors.Report(message, e); + internal string GetLastError() => _errors.GetLastError(); + + } +} diff --git a/src/Umbraco.ModelsBuilder/Umbraco/OutOfDateModelsStatus.cs b/src/Umbraco.ModelsBuilder/Umbraco/OutOfDateModelsStatus.cs index 142cc7578a..3326379871 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/OutOfDateModelsStatus.cs +++ b/src/Umbraco.ModelsBuilder/Umbraco/OutOfDateModelsStatus.cs @@ -7,7 +7,12 @@ namespace Umbraco.ModelsBuilder.Umbraco { public sealed class OutOfDateModelsStatus { - private static Config Config => Current.Configs.ModelsBuilder(); + public OutOfDateModelsStatus() + { + + } + + private static ModelsBuilderConfig Config => Current.Configs.ModelsBuilder(); internal static void Install() { diff --git a/src/Umbraco.ModelsBuilder/Umbraco/PureLiveModelFactory.cs b/src/Umbraco.ModelsBuilder/Umbraco/PureLiveModelFactory.cs index 05f748a588..0f888cc428 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/PureLiveModelFactory.cs +++ b/src/Umbraco.ModelsBuilder/Umbraco/PureLiveModelFactory.cs @@ -42,13 +42,15 @@ namespace Umbraco.ModelsBuilder.Umbraco private const string ProjVirt = "~/App_Data/Models/all.generated.cs"; private static readonly string[] OurFiles = { "models.hash", "models.generated.cs", "all.generated.cs", "all.dll.path", "models.err" }; - private readonly Config _config; + private readonly IModelsBuilderConfig _config; + private readonly ModelsGenerator _modelGenerator; - public PureLiveModelFactory(Lazy umbracoServices, IProfilingLogger logger, Config config) + public PureLiveModelFactory(Lazy umbracoServices, IProfilingLogger logger, IModelsBuilderConfig config, ModelsGenerator modelGenerator) { _umbracoServices = umbracoServices; _logger = logger; _config = config; + _modelGenerator = modelGenerator; _ver = 1; // zero is for when we had no version _skipver = -1; // nothing to skip @@ -292,7 +294,7 @@ namespace Umbraco.ModelsBuilder.Umbraco var types = assembly.ExportedTypes.Where(x => x.Inherits() || x.Inherits()); _infos = RegisterModels(types); - ModelsGenerationError.Clear(); + _modelGenerator.ClearErrors(); } catch (Exception e) { @@ -300,7 +302,7 @@ namespace Umbraco.ModelsBuilder.Umbraco { _logger.Error("Failed to build models.", e); _logger.Warn("Running without models."); // be explicit - ModelsGenerationError.Report("Failed to build PureLive models.", e); + _modelGenerator.ReportError("Failed to build PureLive models.", e); } finally { @@ -333,7 +335,7 @@ namespace Umbraco.ModelsBuilder.Umbraco Directory.CreateDirectory(modelsDirectory); var typeModels = UmbracoServices.GetAllTypes(); - var currentHash = HashHelper.Hash(typeModels); + var currentHash = ModelsBuilderHasher.Hash(typeModels); var modelsHashFile = Path.Combine(modelsDirectory, "models.hash"); var modelsSrcFile = Path.Combine(modelsDirectory, "models.generated.cs"); var projFile = Path.Combine(modelsDirectory, "all.generated.cs"); @@ -557,7 +559,7 @@ namespace Umbraco.ModelsBuilder.Umbraco foreach (var file in Directory.GetFiles(modelsDirectory, "*.generated.cs")) File.Delete(file); - var builder = new TextBuilder(typeModels, _config.ModelsNamespace); + var builder = new TextBuilder(_config, typeModels); var codeBuilder = new StringBuilder(); builder.Generate(codeBuilder, builder.GetModelsToGenerate()); diff --git a/src/Umbraco.ModelsBuilder/Umbraco/UmbracoServices.cs b/src/Umbraco.ModelsBuilder/Umbraco/UmbracoServices.cs index 32f0703bac..410349096a 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/UmbracoServices.cs +++ b/src/Umbraco.ModelsBuilder/Umbraco/UmbracoServices.cs @@ -28,8 +28,6 @@ namespace Umbraco.ModelsBuilder.Umbraco _publishedContentTypeFactory = publishedContentTypeFactory; } - private static Config Config => Current.Configs.ModelsBuilder(); - #region Services public IList GetAllTypes() diff --git a/src/Umbraco.ModelsBuilder/Validation/ContentTypeModelValidator.cs b/src/Umbraco.ModelsBuilder/Validation/ContentTypeModelValidator.cs index 4b38de0168..fdae56be0d 100644 --- a/src/Umbraco.ModelsBuilder/Validation/ContentTypeModelValidator.cs +++ b/src/Umbraco.ModelsBuilder/Validation/ContentTypeModelValidator.cs @@ -35,7 +35,7 @@ namespace Umbraco.ModelsBuilder.Validation where TModel: ContentTypeSave where TProperty: PropertyTypeBasic { - private static Config Config => Current.Configs.ModelsBuilder(); + private static ModelsBuilderConfig Config => Current.Configs.ModelsBuilder(); protected override IEnumerable Validate(TModel model) { diff --git a/src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs b/src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs new file mode 100644 index 0000000000..18be0a37a3 --- /dev/null +++ b/src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs @@ -0,0 +1,443 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.CodeAnalysis; +using Moq; +using NUnit.Framework; +using Umbraco.Core.Composing; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.ModelsBuilder.Api; +using Umbraco.ModelsBuilder.Building; +using Umbraco.ModelsBuilder.Configuration; + +namespace Umbraco.ModelsBuilder.Tests +{ + [TestFixture] + public class BuilderTests + { + [SetUp] + public void Setup() + { + Current.Reset(); + Current.UnlockConfigs(); + Current.Configs.Add(() => new ModelsBuilderConfig()); + } + + [Test] + public void GenerateSimpleType() + { + // Umbraco returns nice, pascal-cased names + + var type1 = new TypeModel + { + Id = 1, + Alias = "type1", + ClrName = "Type1", + ParentId = 0, + BaseType = null, + ItemType = TypeModel.ItemTypes.Content, + }; + type1.Properties.Add(new PropertyModel + { + Alias = "prop1", + ClrName = "Prop1", + ModelClrType = typeof(string), + }); + + var types = new[] { type1 }; + + var code = new Dictionary + { + }; + + var builder = new TextBuilder(Mock.Of(), types); + var btypes = builder.TypeModels; + + var sb = new StringBuilder(); + builder.Generate(sb, builder.GetModelsToGenerate().First()); + var gen = sb.ToString(); + + var version = ApiVersion.Current.Version; + var expected = @"//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Umbraco.ModelsBuilder v" + version + @" +// +// Changes to this file will be lost if the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Web; +using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Web; +using Umbraco.ModelsBuilder; +using Umbraco.ModelsBuilder.Umbraco; + +namespace Umbraco.Web.PublishedModels +{ + [PublishedModel(""type1"")] + public partial class Type1 : PublishedContentModel + { + // helpers +#pragma warning disable 0109 // new is redundant + [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder"", """ + version + @""")] + public new const string ModelTypeAlias = ""type1""; + [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder"", """ + version + @""")] + public new const PublishedItemType ModelItemType = PublishedItemType.Content; + [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder"", """ + version + @""")] + public new static IPublishedContentType GetModelContentType() + => PublishedModelUtility.GetModelContentType(ModelItemType, ModelTypeAlias); + [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder"", """ + version + @""")] + public static IPublishedPropertyType GetModelPropertyType(Expression> selector) + => PublishedModelUtility.GetModelPropertyType(GetModelContentType(), selector); +#pragma warning restore 0109 + + // ctor + public Type1(IPublishedContent content) + : base(content) + { } + + // properties + + [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder"", """ + version + @""")] + [ImplementPropertyType(""prop1"")] + public string Prop1 => this.Value(""prop1""); + } +} +"; + Console.WriteLine(gen); + Assert.AreEqual(expected.ClearLf(), gen); + } + + [Test] + public void GenerateSimpleType_Ambiguous_Issue() + { + // Umbraco returns nice, pascal-cased names + + var type1 = new TypeModel + { + Id = 1, + Alias = "type1", + ClrName = "Type1", + ParentId = 0, + BaseType = null, + ItemType = TypeModel.ItemTypes.Content, + }; + type1.Properties.Add(new PropertyModel + { + Alias = "foo", + ClrName = "Foo", + ModelClrType = typeof(IEnumerable<>).MakeGenericType(ModelType.For("foo")), + }); + + var type2 = new TypeModel + { + Id = 2, + Alias = "foo", + ClrName = "Foo", + ParentId = 0, + BaseType = null, + ItemType = TypeModel.ItemTypes.Element, + }; + + var types = new[] { type1, type2 }; + + var code = new Dictionary + { + { "code", @" +namespace Umbraco.Web.PublishedModels +{ + public partial class Foo + { + } +} +" } + }; + + var builder = new TextBuilder(Mock.Of(), types); + var btypes = builder.TypeModels; + + builder.ModelsNamespace = "Umbraco.Web.PublishedModels"; + + var sb1 = new StringBuilder(); + builder.Generate(sb1, builder.GetModelsToGenerate().Skip(1).First()); + var gen1 = sb1.ToString(); + Console.WriteLine(gen1); + + var sb = new StringBuilder(); + builder.Generate(sb, builder.GetModelsToGenerate().First()); + var gen = sb.ToString(); + + var version = ApiVersion.Current.Version; + var expected = @"//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Umbraco.ModelsBuilder v" + version + @" +// +// Changes to this file will be lost if the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Web; +using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Web; +using Umbraco.ModelsBuilder; +using Umbraco.ModelsBuilder.Umbraco; + +namespace Umbraco.Web.PublishedModels +{ + [PublishedModel(""type1"")] + public partial class Type1 : PublishedContentModel + { + // helpers +#pragma warning disable 0109 // new is redundant + [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder"", """ + version + @""")] + public new const string ModelTypeAlias = ""type1""; + [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder"", """ + version + @""")] + public new const PublishedItemType ModelItemType = PublishedItemType.Content; + [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder"", """ + version + @""")] + public new static IPublishedContentType GetModelContentType() + => PublishedModelUtility.GetModelContentType(ModelItemType, ModelTypeAlias); + [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder"", """ + version + @""")] + public static IPublishedPropertyType GetModelPropertyType(Expression> selector) + => PublishedModelUtility.GetModelPropertyType(GetModelContentType(), selector); +#pragma warning restore 0109 + + // ctor + public Type1(IPublishedContent content) + : base(content) + { } + + // properties + + [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder"", """ + version + @""")] + [ImplementPropertyType(""foo"")] + public global::System.Collections.Generic.IEnumerable Foo => this.Value>(""foo""); + } +} +"; + Console.WriteLine(gen); + Assert.AreEqual(expected.ClearLf(), gen); + } + + [Test] + public void GenerateAmbiguous() + { + // NOTE: since + + var type1 = new TypeModel + { + Id = 1, + Alias = "type1", + ClrName = "Type1", + ParentId = 0, + BaseType = null, + ItemType = TypeModel.ItemTypes.Content, + IsMixin = true, + }; + type1.Properties.Add(new PropertyModel + { + Alias = "prop1", + ClrName = "Prop1", + ModelClrType = typeof(IPublishedContent), + }); + type1.Properties.Add(new PropertyModel + { + Alias = "prop2", + ClrName = "Prop2", + ModelClrType = typeof(System.Text.StringBuilder), + }); + type1.Properties.Add(new PropertyModel + { + Alias = "prop3", + ClrName = "Prop3", + ModelClrType = typeof(global::Umbraco.Core.IO.FileSecurityException), + }); + var types = new[] { type1 }; + + var code = new Dictionary + { + }; + + var builder = new TextBuilder(Mock.Of(), types); + builder.ModelsNamespace = "Umbraco.ModelsBuilder.Models"; // forces conflict with Umbraco.ModelsBuilder.Umbraco + var btypes = builder.TypeModels; + + var sb = new StringBuilder(); + foreach (var model in builder.GetModelsToGenerate()) + builder.Generate(sb, model); + var gen = sb.ToString(); + + Console.WriteLine(gen); + + Assert.IsTrue(gen.Contains(" global::Umbraco.Core.Models.PublishedContent.IPublishedContent Prop1")); + Assert.IsTrue(gen.Contains(" global::System.Text.StringBuilder Prop2")); + Assert.IsTrue(gen.Contains(" global::Umbraco.Core.IO.FileSecurityException Prop3")); + } + + [TestCase("int", typeof(int))] + [TestCase("global::System.Collections.Generic.IEnumerable", typeof(IEnumerable))] + [TestCase("global::Umbraco.ModelsBuilder.Tests.BuilderTestsClass1", typeof(BuilderTestsClass1))] + [TestCase("global::Umbraco.ModelsBuilder.Tests.BuilderTests.Class1", typeof(Class1))] + public void WriteClrType(string expected, Type input) + { + // note - these assertions differ from the original tests in MB because in the embedded version, the result of Builder.IsAmbiguousSymbol is always true + // which means global:: syntax will be applied to most things + + var builder = new TextBuilder(); + builder.ModelsNamespaceForTests = "ModelsNamespace"; + var sb = new StringBuilder(); + builder.WriteClrType(sb, input); + Assert.AreEqual(expected, sb.ToString()); + } + + [TestCase("int", typeof(int))] + [TestCase("global::System.Collections.Generic.IEnumerable", typeof(IEnumerable))] + [TestCase("global::Umbraco.ModelsBuilder.Tests.BuilderTestsClass1", typeof(BuilderTestsClass1))] + [TestCase("global::Umbraco.ModelsBuilder.Tests.BuilderTests.Class1", typeof(Class1))] + public void WriteClrTypeUsing(string expected, Type input) + { + // note - these assertions differ from the original tests in MB because in the embedded version, the result of Builder.IsAmbiguousSymbol is always true + // which means global:: syntax will be applied to most things + + var builder = new TextBuilder(); + builder.Using.Add("Umbraco.ModelsBuilder.Tests"); + builder.ModelsNamespaceForTests = "ModelsNamespace"; + var sb = new StringBuilder(); + builder.WriteClrType(sb, input); + Assert.AreEqual(expected, sb.ToString()); + } + + [Test] + public void WriteClrType_WithUsing() + { + var builder = new TextBuilder(); + builder.Using.Add("System.Text"); + builder.ModelsNamespaceForTests = "Umbraco.ModelsBuilder.Tests.Models"; + var sb = new StringBuilder(); + builder.WriteClrType(sb, typeof(StringBuilder)); + + // note - these assertions differ from the original tests in MB because in the embedded version, the result of Builder.IsAmbiguousSymbol is always true + // which means global:: syntax will be applied to most things + + Assert.AreEqual("global::System.Text.StringBuilder", sb.ToString()); + } + + [Test] + public void WriteClrTypeAnother_WithoutUsing() + { + var builder = new TextBuilder(); + builder.ModelsNamespaceForTests = "Umbraco.ModelsBuilder.Tests.Models"; + var sb = new StringBuilder(); + builder.WriteClrType(sb, typeof(StringBuilder)); + Assert.AreEqual("global::System.Text.StringBuilder", sb.ToString()); + } + + [Test] + public void WriteClrType_Ambiguous1() + { + var builder = new TextBuilder(); + builder.Using.Add("System.Text"); + builder.Using.Add("Umbraco.ModelsBuilder.Tests"); + builder.ModelsNamespaceForTests = "SomeRandomNamespace"; + var sb = new StringBuilder(); + builder.WriteClrType(sb, typeof(System.Text.ASCIIEncoding)); + + // note - these assertions differ from the original tests in MB because in the embedded version, the result of Builder.IsAmbiguousSymbol is always true + // which means global:: syntax will be applied to most things + + Assert.AreEqual("global::System.Text.ASCIIEncoding", sb.ToString()); + } + + [Test] + public void WriteClrType_Ambiguous() + { + var builder = new TextBuilder(); + builder.Using.Add("System.Text"); + builder.Using.Add("Umbraco.ModelsBuilder.Tests"); + builder.ModelsNamespaceForTests = "SomeBorkedNamespace"; + var sb = new StringBuilder(); + builder.WriteClrType(sb, typeof(System.Text.ASCIIEncoding)); + + // note - these assertions differ from the original tests in MB because in the embedded version, the result of Builder.IsAmbiguousSymbol is always true + // which means global:: syntax will be applied to most things + + Assert.AreEqual("global::System.Text.ASCIIEncoding", sb.ToString()); + } + + [Test] + public void WriteClrType_Ambiguous2() + { + var builder = new TextBuilder(); + builder.Using.Add("System.Text"); + builder.Using.Add("Umbraco.ModelsBuilder.Tests"); + builder.ModelsNamespaceForTests = "SomeRandomNamespace"; + var sb = new StringBuilder(); + builder.WriteClrType(sb, typeof(ASCIIEncoding)); + + // note - these assertions differ from the original tests in MB because in the embedded version, the result of Builder.IsAmbiguousSymbol is always true + // which means global:: syntax will be applied to most things + + Assert.AreEqual("global::Umbraco.ModelsBuilder.Tests.ASCIIEncoding", sb.ToString()); + } + + [Test] + public void WriteClrType_AmbiguousNot() + { + var builder = new TextBuilder(); + builder.Using.Add("System.Text"); + builder.Using.Add("Umbraco.ModelsBuilder.Tests"); + builder.ModelsNamespaceForTests = "Umbraco.ModelsBuilder.Tests.Models"; + var sb = new StringBuilder(); + builder.WriteClrType(sb, typeof(ASCIIEncoding)); + + // note - these assertions differ from the original tests in MB because in the embedded version, the result of Builder.IsAmbiguousSymbol is always true + // which means global:: syntax will be applied to most things + + Assert.AreEqual("global::Umbraco.ModelsBuilder.Tests.ASCIIEncoding", sb.ToString()); + } + + [Test] + public void WriteClrType_AmbiguousWithNested() + { + var builder = new TextBuilder(); + builder.Using.Add("System.Text"); + builder.Using.Add("Umbraco.ModelsBuilder.Tests"); + builder.ModelsNamespaceForTests = "SomeRandomNamespace"; + var sb = new StringBuilder(); + builder.WriteClrType(sb, typeof(ASCIIEncoding.Nested)); + + // note - these assertions differ from the original tests in MB because in the embedded version, the result of Builder.IsAmbiguousSymbol is always true + // which means global:: syntax will be applied to most things + + Assert.AreEqual("global::Umbraco.ModelsBuilder.Tests.ASCIIEncoding.Nested", sb.ToString()); + } + + public class Class1 { } + } + + // make it public to be ambiguous (see above) + public class ASCIIEncoding + { + // can we handle nested types? + public class Nested { } + } + + class BuilderTestsClass1 {} +} + +namespace SomeBorkedNamespace +{ + public class System { } +} diff --git a/src/Umbraco.Tests/ModelsBuilder/ConfigTests.cs b/src/Umbraco.Tests/ModelsBuilder/ConfigTests.cs new file mode 100644 index 0000000000..58215707f7 --- /dev/null +++ b/src/Umbraco.Tests/ModelsBuilder/ConfigTests.cs @@ -0,0 +1,49 @@ +using System.Configuration; +using NUnit.Framework; +using Umbraco.ModelsBuilder.Configuration; + +namespace Umbraco.ModelsBuilder.Tests +{ + [TestFixture] + public class ModelsBuilderConfigTests + { + [Test] + public void Test1() + { + var config = new ModelsBuilderConfig(modelsNamespace: "test1"); + Assert.AreEqual("test1", config.ModelsNamespace); + } + + [Test] + public void Test2() + { + var config = new ModelsBuilderConfig(modelsNamespace: "test2"); + Assert.AreEqual("test2", config.ModelsNamespace); + } + + [Test] + public void DefaultModelsNamespace() + { + var config = new ModelsBuilderConfig(); + Assert.AreEqual(ModelsBuilderConfig.DefaultModelsNamespace, config.ModelsNamespace); + } + + [TestCase("c:/path/to/root", "~/dir/models", false, "c:\\path\\to\\root\\dir\\models")] + [TestCase("c:/path/to/root", "~/../../dir/models", true, "c:\\path\\dir\\models")] + [TestCase("c:/path/to/root", "c:/another/path/to/elsewhere", true, "c:\\another\\path\\to\\elsewhere")] + public void GetModelsDirectoryTests(string root, string config, bool acceptUnsafe, string expected) + { + Assert.AreEqual(expected, ModelsBuilderConfig.GetModelsDirectory(root, config, acceptUnsafe)); + } + + [TestCase("c:/path/to/root", "~/../../dir/models", false)] + [TestCase("c:/path/to/root", "c:/another/path/to/elsewhere", false)] + public void GetModelsDirectoryThrowsTests(string root, string config, bool acceptUnsafe) + { + Assert.Throws(() => + { + var modelsDirectory = ModelsBuilderConfig.GetModelsDirectory(root, config, acceptUnsafe); + }); + } + } +} diff --git a/src/Umbraco.Tests/ModelsBuilder/StringExtensions.cs b/src/Umbraco.Tests/ModelsBuilder/StringExtensions.cs new file mode 100644 index 0000000000..13a256aa14 --- /dev/null +++ b/src/Umbraco.Tests/ModelsBuilder/StringExtensions.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Umbraco.ModelsBuilder.Tests +{ + public static class StringExtensions + { + public static string ClearLf(this string s) + { + return s.Replace("\r", ""); + } + } +} diff --git a/src/Umbraco.Tests/ModelsBuilder/UmbracoApplicationTests.cs b/src/Umbraco.Tests/ModelsBuilder/UmbracoApplicationTests.cs new file mode 100644 index 0000000000..12fa777e69 --- /dev/null +++ b/src/Umbraco.Tests/ModelsBuilder/UmbracoApplicationTests.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using NUnit.Framework; +using Umbraco.ModelsBuilder.Building; +using Umbraco.ModelsBuilder.Umbraco; + +namespace Umbraco.ModelsBuilder.Tests +{ + [TestFixture] + public class UmbracoApplicationTests + { + //[Test] + //public void Test() + //{ + // // start and terminate + // using (var app = Application.GetApplication(TestOptions.ConnectionString, TestOptions.DatabaseProvider)) + // { } + + // // start and terminate + // using (var app = Application.GetApplication(TestOptions.ConnectionString, TestOptions.DatabaseProvider)) + // { } + + // // start, use and terminate + // using (var app = Application.GetApplication(TestOptions.ConnectionString, TestOptions.DatabaseProvider)) + // { + // var types = app.GetContentTypes(); + // } + //} + + [Test] + public void ThrowsOnDuplicateAliases() + { + var typeModels = new List + { + new TypeModel { ItemType = TypeModel.ItemTypes.Content, Alias = "content1" }, + new TypeModel { ItemType = TypeModel.ItemTypes.Content, Alias = "content2" }, + new TypeModel { ItemType = TypeModel.ItemTypes.Media, Alias = "media1" }, + new TypeModel { ItemType = TypeModel.ItemTypes.Media, Alias = "media2" }, + new TypeModel { ItemType = TypeModel.ItemTypes.Member, Alias = "member1" }, + new TypeModel { ItemType = TypeModel.ItemTypes.Member, Alias = "member2" }, + }; + + Assert.AreEqual(6, UmbracoServices.EnsureDistinctAliases(typeModels).Count); + + typeModels.Add(new TypeModel { ItemType = TypeModel.ItemTypes.Media, Alias = "content1" }); + + try + { + UmbracoServices.EnsureDistinctAliases(typeModels); + } + catch (NotSupportedException e) + { + Console.WriteLine(e.Message); + return; + } + + Assert.Fail("Expected NotSupportedException."); + } + } +} diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 39826fcc38..ebf081160b 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -134,6 +134,10 @@ + + + + @@ -549,6 +553,10 @@ {31785BC3-256C-4613-B2F5-A1B0BDDED8C1} Umbraco.Core + + {52ac0ba8-a60e-4e36-897b-e8b97a54ed1c} + Umbraco.ModelsBuilder + {651E1350-91B6-44B7-BD60-7207006D7003} Umbraco.Web From 6368a0a8d61181bc589c1c8bee0c8bb470ba15d8 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 28 Oct 2019 19:08:42 +1100 Subject: [PATCH 17/49] Fixes circular ref, last static gone, slight perf improvement in module --- .../BackOffice/DashboardReport.cs | 12 ++++---- .../ModelsBuilderBackOfficeController.cs | 16 +++++++---- .../Compose/ModelsBuilderComponent.cs | 8 +++--- .../Compose/ModelsBuilderComposer.cs | 2 ++ .../Compose/ModelsBuilderInitializer.cs | 2 +- .../ConfigsExtensions.cs | 6 ++-- .../Configuration/ModelsBuilderConfig.cs | 2 +- .../Umbraco/LiveModelsProvider.cs | 8 ++++-- .../Umbraco/LiveModelsProviderModule.cs | 7 +++-- .../Umbraco/ModelsGenerationError.cs | 2 +- .../Umbraco/ModelsGenerator.cs | 13 +++------ .../Umbraco/OutOfDateModelsStatus.cs | 28 +++++++++---------- .../Umbraco/PublishedModelUtility.cs | 8 ------ .../Umbraco/PureLiveModelFactory.cs | 13 +++++---- .../Validation/ContentTypeModelValidator.cs | 2 -- 15 files changed, 64 insertions(+), 65 deletions(-) diff --git a/src/Umbraco.ModelsBuilder/BackOffice/DashboardReport.cs b/src/Umbraco.ModelsBuilder/BackOffice/DashboardReport.cs index 57afeaf069..85f7030eff 100644 --- a/src/Umbraco.ModelsBuilder/BackOffice/DashboardReport.cs +++ b/src/Umbraco.ModelsBuilder/BackOffice/DashboardReport.cs @@ -8,19 +8,21 @@ namespace Umbraco.ModelsBuilder.BackOffice internal class DashboardReport { private readonly IModelsBuilderConfig _config; - private readonly ModelsGenerator _modelsGenerator; + private readonly OutOfDateModelsStatus _outOfDateModels; + private readonly ModelsGenerationError _mbErrors; - public DashboardReport(IModelsBuilderConfig config, ModelsGenerator modelsGenerator) + public DashboardReport(IModelsBuilderConfig config, OutOfDateModelsStatus outOfDateModels, ModelsGenerationError mbErrors) { _config = config; - _modelsGenerator = modelsGenerator; + _outOfDateModels = outOfDateModels; + _mbErrors = mbErrors; } public bool CanGenerate() => _config.ModelsMode.SupportsExplicitGeneration(); - public bool AreModelsOutOfDate() => OutOfDateModelsStatus.IsOutOfDate; + public bool AreModelsOutOfDate() => _outOfDateModels.IsOutOfDate; - public string LastError() => _modelsGenerator.GetLastError(); + public string LastError() => _mbErrors.GetLastError(); public string Text() { diff --git a/src/Umbraco.ModelsBuilder/BackOffice/ModelsBuilderBackOfficeController.cs b/src/Umbraco.ModelsBuilder/BackOffice/ModelsBuilderBackOfficeController.cs index 561da0a3d6..6092c9678b 100644 --- a/src/Umbraco.ModelsBuilder/BackOffice/ModelsBuilderBackOfficeController.cs +++ b/src/Umbraco.ModelsBuilder/BackOffice/ModelsBuilderBackOfficeController.cs @@ -28,14 +28,18 @@ namespace Umbraco.ModelsBuilder.BackOffice { private readonly IModelsBuilderConfig _config; private readonly ModelsGenerator _modelGenerator; + private readonly OutOfDateModelsStatus _outOfDateModels; + private readonly ModelsGenerationError _mbErrors; private readonly DashboardReport _dashboardReport; - public ModelsBuilderBackOfficeController(IModelsBuilderConfig config, ModelsGenerator modelsGenerator) + public ModelsBuilderBackOfficeController(IModelsBuilderConfig config, ModelsGenerator modelsGenerator, OutOfDateModelsStatus outOfDateModels, ModelsGenerationError mbErrors) { //_umbracoServices = umbracoServices; _config = config; _modelGenerator = modelsGenerator; - _dashboardReport = new DashboardReport(config, modelsGenerator); + _outOfDateModels = outOfDateModels; + _mbErrors = mbErrors; + _dashboardReport = new DashboardReport(config, outOfDateModels, mbErrors); } // invoked by the dashboard @@ -60,11 +64,11 @@ namespace Umbraco.ModelsBuilder.BackOffice // EnableDllModels will recycle the app domain - but this request will end properly _modelGenerator.GenerateModels(); - _modelGenerator.ClearErrors(); + _mbErrors.Clear(); } catch (Exception e) { - _modelGenerator.ReportError("Failed to build models.", e); + _mbErrors.Report("Failed to build models.", e); } return Request.CreateResponse(HttpStatusCode.OK, GetDashboardResult(), Configuration.Formatters.JsonFormatter); @@ -75,8 +79,8 @@ namespace Umbraco.ModelsBuilder.BackOffice [System.Web.Http.HttpGet] // use the http one, not mvc, with api controllers! public HttpResponseMessage GetModelsOutOfDateStatus() { - var status = OutOfDateModelsStatus.IsEnabled - ? OutOfDateModelsStatus.IsOutOfDate + var status = _outOfDateModels.IsEnabled + ? _outOfDateModels.IsOutOfDate ? new OutOfDateStatus { Status = OutOfDateType.OutOfDate } : new OutOfDateStatus { Status = OutOfDateType.Current } : new OutOfDateStatus { Status = OutOfDateType.Unknown }; diff --git a/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderComponent.cs b/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderComponent.cs index 6a9b8e1115..6deeda40a6 100644 --- a/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderComponent.cs +++ b/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderComponent.cs @@ -22,11 +22,13 @@ namespace Umbraco.ModelsBuilder.Compose private readonly IModelsBuilderConfig _config; private readonly LiveModelsProvider _liveModelsProvider; + private readonly OutOfDateModelsStatus _outOfDateModels; - public ModelsBuilderComponent(IModelsBuilderConfig config, LiveModelsProvider liveModelsProvider) + public ModelsBuilderComponent(IModelsBuilderConfig config, LiveModelsProvider liveModelsProvider, OutOfDateModelsStatus outOfDateModels) { _config = config; _liveModelsProvider = liveModelsProvider; + _outOfDateModels = outOfDateModels; } public void Initialize() @@ -39,13 +41,11 @@ namespace Umbraco.ModelsBuilder.Compose FileService.SavingTemplate += FileService_SavingTemplate; - // fixme LiveModelsProvider should not be static if (_config.ModelsMode.IsLiveNotPure()) _liveModelsProvider.Install(); - // fixme OutOfDateModelsStatus should not be static if (_config.FlagOutOfDateModels) - OutOfDateModelsStatus.Install(); + _outOfDateModels.Install(); } public void Terminate() diff --git a/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderComposer.cs b/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderComposer.cs index 71250b2eb6..f2b09903f3 100644 --- a/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderComposer.cs +++ b/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderComposer.cs @@ -20,6 +20,8 @@ namespace Umbraco.ModelsBuilder.Compose composition.Configs.Add(() => new ModelsBuilderConfig()); composition.RegisterUnique(); composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); if (composition.Configs.ModelsBuilder().ModelsMode == ModelsMode.PureLive) ComposeForLiveModels(composition); diff --git a/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderInitializer.cs b/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderInitializer.cs index 63f2336ebf..6eb8bbb328 100644 --- a/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderInitializer.cs +++ b/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderInitializer.cs @@ -6,7 +6,7 @@ using Umbraco.ModelsBuilder.Compose; namespace Umbraco.ModelsBuilder.Compose { - internal static class ModelsBuilderInitializer + public static class ModelsBuilderInitializer { public static void Initialize() { diff --git a/src/Umbraco.ModelsBuilder/ConfigsExtensions.cs b/src/Umbraco.ModelsBuilder/ConfigsExtensions.cs index c989be5aca..09314bc3f3 100644 --- a/src/Umbraco.ModelsBuilder/ConfigsExtensions.cs +++ b/src/Umbraco.ModelsBuilder/ConfigsExtensions.cs @@ -14,7 +14,7 @@ namespace Umbraco.ModelsBuilder /// Getting the models builder configuration freezes its state, /// and any attempt at modifying the configuration using the Setup method /// will be ignored. - public static ModelsBuilderConfig ModelsBuilder(this Configs configs) - => configs.GetConfig(); + public static IModelsBuilderConfig ModelsBuilder(this Configs configs) + => configs.GetConfig(); } -} \ No newline at end of file +} diff --git a/src/Umbraco.ModelsBuilder/Configuration/ModelsBuilderConfig.cs b/src/Umbraco.ModelsBuilder/Configuration/ModelsBuilderConfig.cs index caff5001a4..48a4423054 100644 --- a/src/Umbraco.ModelsBuilder/Configuration/ModelsBuilderConfig.cs +++ b/src/Umbraco.ModelsBuilder/Configuration/ModelsBuilderConfig.cs @@ -25,7 +25,7 @@ namespace Umbraco.ModelsBuilder.Configuration // ensure defaults are initialized for tests ModelsNamespace = DefaultModelsNamespace; - ModelsDirectory = IOHelper.MapPath("~/"); + ModelsDirectory = IOHelper.MapPath(DefaultModelsDirectory); DebugLevel = 0; // mode diff --git a/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProvider.cs b/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProvider.cs index 146e837dd9..19a725f85a 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProvider.cs +++ b/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProvider.cs @@ -19,15 +19,17 @@ namespace Umbraco.ModelsBuilder.Umbraco 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) + 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() @@ -82,7 +84,7 @@ namespace Umbraco.ModelsBuilder.Umbraco _mutex.WaitOne(timeout); // wait until it is safe, and acquire _logger.Info("Generate models now."); GenerateModels(); - _modelGenerator.ClearErrors(); + _mbErrors.Clear(); _logger.Info("Generated."); } catch (TimeoutException) @@ -91,7 +93,7 @@ namespace Umbraco.ModelsBuilder.Umbraco } catch (Exception e) { - _modelGenerator.ReportError("Failed to build Live models.", e); + _mbErrors.Report("Failed to build Live models.", e); _logger.Error("Failed to generate models.", e); } finally diff --git a/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProviderModule.cs b/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProviderModule.cs index 1dadbd41b6..aae01ea75c 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProviderModule.cs +++ b/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProviderModule.cs @@ -22,11 +22,14 @@ namespace Umbraco.ModelsBuilder.Umbraco 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.GetInstance(); + _liveModelsProvider = Current.Factory.TryGetInstance(); // will be null in upgrade mode - if (_liveModelsProvider.IsEnabled) + if (_liveModelsProvider?.IsEnabled ?? false) _liveModelsProvider.GenerateModelsIfRequested(sender, e); } diff --git a/src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerationError.cs b/src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerationError.cs index a7cb1e11c3..3f96d2ccdf 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerationError.cs +++ b/src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerationError.cs @@ -6,7 +6,7 @@ using Umbraco.ModelsBuilder.Configuration; namespace Umbraco.ModelsBuilder.Umbraco { - internal class ModelsGenerationError + public sealed class ModelsGenerationError { private readonly IModelsBuilderConfig _config; diff --git a/src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerator.cs b/src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerator.cs index 4e3607c156..5ef86b50ca 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerator.cs +++ b/src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerator.cs @@ -12,13 +12,13 @@ namespace Umbraco.ModelsBuilder.Umbraco { private readonly UmbracoServices _umbracoService; private readonly IModelsBuilderConfig _config; - private readonly ModelsGenerationError _errors; + private readonly OutOfDateModelsStatus _outOfDateModels; - public ModelsGenerator(UmbracoServices umbracoService, IModelsBuilderConfig config) + public ModelsGenerator(UmbracoServices umbracoService, IModelsBuilderConfig config, OutOfDateModelsStatus outOfDateModels) { _umbracoService = umbracoService; _config = config; - _errors = new ModelsGenerationError(config); + _outOfDateModels = outOfDateModels; } internal void GenerateModels() @@ -52,12 +52,7 @@ namespace Umbraco.ModelsBuilder.Umbraco "; */ - OutOfDateModelsStatus.Clear(); + _outOfDateModels.Clear(); } - - internal void ClearErrors() => _errors.Clear(); - internal void ReportError(string message, Exception e) => _errors.Report(message, e); - internal string GetLastError() => _errors.GetLastError(); - } } diff --git a/src/Umbraco.ModelsBuilder/Umbraco/OutOfDateModelsStatus.cs b/src/Umbraco.ModelsBuilder/Umbraco/OutOfDateModelsStatus.cs index 3326379871..a72ccd2fa2 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/OutOfDateModelsStatus.cs +++ b/src/Umbraco.ModelsBuilder/Umbraco/OutOfDateModelsStatus.cs @@ -7,53 +7,53 @@ namespace Umbraco.ModelsBuilder.Umbraco { public sealed class OutOfDateModelsStatus { - public OutOfDateModelsStatus() - { + private readonly IModelsBuilderConfig _config; + public OutOfDateModelsStatus(IModelsBuilderConfig config) + { + _config = config; } - private static ModelsBuilderConfig Config => Current.Configs.ModelsBuilder(); - - internal static void Install() + internal void Install() { // just be sure - if (Config.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 = Config.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 (Config.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 => Config.FlagOutOfDateModels; + public bool IsEnabled => _config.FlagOutOfDateModels; - public static bool IsOutOfDate + public bool IsOutOfDate { get { - if (Config.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/Umbraco/PublishedModelUtility.cs b/src/Umbraco.ModelsBuilder/Umbraco/PublishedModelUtility.cs index 44b7aae0c0..2a8022bef8 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/PublishedModelUtility.cs +++ b/src/Umbraco.ModelsBuilder/Umbraco/PublishedModelUtility.cs @@ -41,14 +41,6 @@ namespace Umbraco.ModelsBuilder.Umbraco } } - [Obsolete("Use the overload taking IPublishedContentType instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public static IPublishedPropertyType GetModelPropertyType(PublishedContentType contentType, - Expression> selector) - { - return GetModelPropertyType((IPublishedContentType)contentType, selector); - } - public static IPublishedPropertyType GetModelPropertyType(IPublishedContentType contentType, Expression> selector) //where TModel : PublishedContentModel // fixme PublishedContentModel _or_ PublishedElementModel { diff --git a/src/Umbraco.ModelsBuilder/Umbraco/PureLiveModelFactory.cs b/src/Umbraco.ModelsBuilder/Umbraco/PureLiveModelFactory.cs index 0f888cc428..464ce92ef5 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/PureLiveModelFactory.cs +++ b/src/Umbraco.ModelsBuilder/Umbraco/PureLiveModelFactory.cs @@ -35,7 +35,7 @@ namespace Umbraco.ModelsBuilder.Umbraco 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); @@ -43,14 +43,14 @@ namespace Umbraco.ModelsBuilder.Umbraco private static readonly string[] OurFiles = { "models.hash", "models.generated.cs", "all.generated.cs", "all.dll.path", "models.err" }; private readonly IModelsBuilderConfig _config; - private readonly ModelsGenerator _modelGenerator; + private readonly ModelsGenerationError _errors; - public PureLiveModelFactory(Lazy umbracoServices, IProfilingLogger logger, IModelsBuilderConfig config, ModelsGenerator modelGenerator) + public PureLiveModelFactory(Lazy umbracoServices, IProfilingLogger logger, IModelsBuilderConfig config) { _umbracoServices = umbracoServices; _logger = logger; _config = config; - _modelGenerator = modelGenerator; + _errors = new ModelsGenerationError(config); _ver = 1; // zero is for when we had no version _skipver = -1; // nothing to skip @@ -294,7 +294,7 @@ namespace Umbraco.ModelsBuilder.Umbraco var types = assembly.ExportedTypes.Where(x => x.Inherits() || x.Inherits()); _infos = RegisterModels(types); - _modelGenerator.ClearErrors(); + _errors.Clear(); } catch (Exception e) { @@ -302,7 +302,7 @@ namespace Umbraco.ModelsBuilder.Umbraco { _logger.Error("Failed to build models.", e); _logger.Warn("Running without models."); // be explicit - _modelGenerator.ReportError("Failed to build PureLive models.", e); + _errors.Report("Failed to build PureLive models.", e); } finally { @@ -536,6 +536,7 @@ namespace Umbraco.ModelsBuilder.Umbraco 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 !! + // 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); diff --git a/src/Umbraco.ModelsBuilder/Validation/ContentTypeModelValidator.cs b/src/Umbraco.ModelsBuilder/Validation/ContentTypeModelValidator.cs index fdae56be0d..d22d146553 100644 --- a/src/Umbraco.ModelsBuilder/Validation/ContentTypeModelValidator.cs +++ b/src/Umbraco.ModelsBuilder/Validation/ContentTypeModelValidator.cs @@ -35,8 +35,6 @@ namespace Umbraco.ModelsBuilder.Validation where TModel: ContentTypeSave where TProperty: PropertyTypeBasic { - private static ModelsBuilderConfig Config => Current.Configs.ModelsBuilder(); - protected override IEnumerable Validate(TModel model) { var properties = model.Groups.SelectMany(x => x.Properties) From 51f89410cebf8f52a9dfcf4bde928b511bf4cb5d Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 28 Oct 2019 19:35:18 +1100 Subject: [PATCH 18/49] notes --- src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj | 4 +--- src/Umbraco.ModelsBuilder/Umbraco/PublishedModelUtility.cs | 6 ++++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj b/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj index 6945cc4b31..027ed6665c 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj +++ b/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj @@ -109,8 +109,6 @@ 5.2.7 - - - + \ No newline at end of file diff --git a/src/Umbraco.ModelsBuilder/Umbraco/PublishedModelUtility.cs b/src/Umbraco.ModelsBuilder/Umbraco/PublishedModelUtility.cs index 2a8022bef8..1db15fa9b1 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/PublishedModelUtility.cs +++ b/src/Umbraco.ModelsBuilder/Umbraco/PublishedModelUtility.cs @@ -7,6 +7,12 @@ using Umbraco.Core.Models.PublishedContent; namespace Umbraco.ModelsBuilder.Umbraco { + /// + /// 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 From adbd2f0ccaa992abb41d6ceca7726eac9eb5f154 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 28 Oct 2019 21:53:32 +1100 Subject: [PATCH 19/49] Changes namespaces, removes legacy PureLiveAssemblyAttribute --- .../{Api => }/ApiVersion.cs | 2 +- .../ContentTypeModelValidator.cs | 16 +++++------- .../BackOffice/DashboardReport.cs | 3 +-- .../ModelsBuilderBackOfficeController.cs | 1 - src/Umbraco.ModelsBuilder/Building/Builder.cs | 3 +-- .../{Umbraco => Building}/ModelsGenerator.cs | 4 +-- .../Building/TextBuilder.cs | 1 - .../Building/TextHeaderWriter.cs | 1 - .../TypeModelHasher.cs} | 5 ++-- .../Compose/ModelsBuilderComponent.cs | 1 - .../Compose/ModelsBuilderComposer.cs | 2 +- .../{Umbraco => }/LiveModelsProvider.cs | 10 ++++---- .../{Umbraco => }/LiveModelsProviderModule.cs | 4 +-- .../{Umbraco => }/ModelsGenerationError.cs | 2 +- .../{Umbraco => }/OutOfDateModelsStatus.cs | 2 +- .../{Umbraco => }/PublishedModelUtility.cs | 6 ++--- .../PureLiveAssemblyAttribute.cs | 15 ----------- .../{Umbraco => }/PureLiveModelFactory.cs | 25 ++++++++----------- .../Umbraco.ModelsBuilder.csproj | 23 ++++++++--------- .../{Umbraco => }/UmbracoServices.cs | 6 ++--- .../ModelsBuilder/BuilderTests.cs | 3 --- .../ModelsBuilder/UmbracoApplicationTests.cs | 1 - 22 files changed, 49 insertions(+), 87 deletions(-) rename src/Umbraco.ModelsBuilder/{Api => }/ApiVersion.cs (97%) rename src/Umbraco.ModelsBuilder/{Validation => BackOffice}/ContentTypeModelValidator.cs (90%) rename src/Umbraco.ModelsBuilder/{Umbraco => Building}/ModelsGenerator.cs (94%) rename src/Umbraco.ModelsBuilder/{Umbraco/ModelsBuilderHasher.cs => Building/TypeModelHasher.cs} (92%) rename src/Umbraco.ModelsBuilder/{Umbraco => }/LiveModelsProvider.cs (95%) rename src/Umbraco.ModelsBuilder/{Umbraco => }/LiveModelsProviderModule.cs (95%) rename src/Umbraco.ModelsBuilder/{Umbraco => }/ModelsGenerationError.cs (97%) rename src/Umbraco.ModelsBuilder/{Umbraco => }/OutOfDateModelsStatus.cs (97%) rename src/Umbraco.ModelsBuilder/{Umbraco => }/PublishedModelUtility.cs (92%) delete mode 100644 src/Umbraco.ModelsBuilder/PureLiveAssemblyAttribute.cs rename src/Umbraco.ModelsBuilder/{Umbraco => }/PureLiveModelFactory.cs (96%) rename src/Umbraco.ModelsBuilder/{Umbraco => }/UmbracoServices.cs (98%) diff --git a/src/Umbraco.ModelsBuilder/Api/ApiVersion.cs b/src/Umbraco.ModelsBuilder/ApiVersion.cs similarity index 97% rename from src/Umbraco.ModelsBuilder/Api/ApiVersion.cs rename to src/Umbraco.ModelsBuilder/ApiVersion.cs index a7f2db397e..67d0ec5c4c 100644 --- a/src/Umbraco.ModelsBuilder/Api/ApiVersion.cs +++ b/src/Umbraco.ModelsBuilder/ApiVersion.cs @@ -2,7 +2,7 @@ using System.Reflection; using Semver; -namespace Umbraco.ModelsBuilder.Api +namespace Umbraco.ModelsBuilder { /// /// Manages API version handshake between client and server. diff --git a/src/Umbraco.ModelsBuilder/Validation/ContentTypeModelValidator.cs b/src/Umbraco.ModelsBuilder/BackOffice/ContentTypeModelValidator.cs similarity index 90% rename from src/Umbraco.ModelsBuilder/Validation/ContentTypeModelValidator.cs rename to src/Umbraco.ModelsBuilder/BackOffice/ContentTypeModelValidator.cs index d22d146553..1971c62c9f 100644 --- a/src/Umbraco.ModelsBuilder/Validation/ContentTypeModelValidator.cs +++ b/src/Umbraco.ModelsBuilder/BackOffice/ContentTypeModelValidator.cs @@ -8,12 +8,12 @@ using Umbraco.ModelsBuilder.Configuration; using Umbraco.Web.Editors; using Umbraco.Web.Models.ContentEditing; -namespace Umbraco.ModelsBuilder.Validation +namespace Umbraco.ModelsBuilder.BackOffice { /// - /// Used to validate the aliases for the content type when MB is enabled to ensure that - /// no illegal aliases are used - /// + /// Used to validate the aliases for the content type when MB is enabled to ensure that + /// no illegal aliases are used + /// public class ContentTypeModelValidator : ContentTypeModelValidatorBase { } @@ -32,8 +32,8 @@ namespace Umbraco.ModelsBuilder.Validation { } public abstract class ContentTypeModelValidatorBase : EditorValidator - where TModel: ContentTypeSave - where TProperty: PropertyTypeBasic + where TModel : ContentTypeSave + where TProperty : PropertyTypeBasic { protected override IEnumerable Validate(TModel model) { @@ -57,9 +57,7 @@ namespace Umbraco.ModelsBuilder.Validation var validationResult = ValidateProperty(prop, groupIndex, propertyIndex); if (validationResult != null) - { yield return validationResult; - } } } @@ -73,13 +71,11 @@ namespace Umbraco.ModelsBuilder.Validation 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.ModelsBuilder/BackOffice/DashboardReport.cs b/src/Umbraco.ModelsBuilder/BackOffice/DashboardReport.cs index 85f7030eff..64fb217fce 100644 --- a/src/Umbraco.ModelsBuilder/BackOffice/DashboardReport.cs +++ b/src/Umbraco.ModelsBuilder/BackOffice/DashboardReport.cs @@ -1,7 +1,6 @@ using System.Text; using Umbraco.Core.Composing; using Umbraco.ModelsBuilder.Configuration; -using Umbraco.ModelsBuilder.Umbraco; namespace Umbraco.ModelsBuilder.BackOffice { @@ -29,7 +28,7 @@ namespace Umbraco.ModelsBuilder.BackOffice var sb = new StringBuilder(); sb.Append("Version: "); - sb.Append(Api.ApiVersion.Current.Version); + sb.Append(ApiVersion.Current.Version); sb.Append("
 
"); sb.Append("ModelsBuilder is enabled, with the following configuration:"); diff --git a/src/Umbraco.ModelsBuilder/BackOffice/ModelsBuilderBackOfficeController.cs b/src/Umbraco.ModelsBuilder/BackOffice/ModelsBuilderBackOfficeController.cs index 6092c9678b..e4efca70bd 100644 --- a/src/Umbraco.ModelsBuilder/BackOffice/ModelsBuilderBackOfficeController.cs +++ b/src/Umbraco.ModelsBuilder/BackOffice/ModelsBuilderBackOfficeController.cs @@ -9,7 +9,6 @@ using System.Web.Hosting; using Umbraco.Core.Exceptions; using Umbraco.ModelsBuilder.Building; using Umbraco.ModelsBuilder.Configuration; -using Umbraco.ModelsBuilder.Umbraco; using Umbraco.Web.Editors; using Umbraco.Web.WebApi.Filters; diff --git a/src/Umbraco.ModelsBuilder/Building/Builder.cs b/src/Umbraco.ModelsBuilder/Building/Builder.cs index fa05b9d9a1..885e51133b 100644 --- a/src/Umbraco.ModelsBuilder/Building/Builder.cs +++ b/src/Umbraco.ModelsBuilder/Building/Builder.cs @@ -38,8 +38,7 @@ namespace Umbraco.ModelsBuilder.Building "Umbraco.Core.Models", "Umbraco.Core.Models.PublishedContent", "Umbraco.Web", - "Umbraco.ModelsBuilder", - "Umbraco.ModelsBuilder.Umbraco", + "Umbraco.ModelsBuilder" }; /// diff --git a/src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerator.cs b/src/Umbraco.ModelsBuilder/Building/ModelsGenerator.cs similarity index 94% rename from src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerator.cs rename to src/Umbraco.ModelsBuilder/Building/ModelsGenerator.cs index 5ef86b50ca..6f6872e8ff 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerator.cs +++ b/src/Umbraco.ModelsBuilder/Building/ModelsGenerator.cs @@ -2,11 +2,9 @@ using System.IO; using System.Text; using System.Web; -using Umbraco.ModelsBuilder.Building; using Umbraco.ModelsBuilder.Configuration; -using Umbraco.ModelsBuilder.Umbraco; -namespace Umbraco.ModelsBuilder.Umbraco +namespace Umbraco.ModelsBuilder.Building { public class ModelsGenerator { diff --git a/src/Umbraco.ModelsBuilder/Building/TextBuilder.cs b/src/Umbraco.ModelsBuilder/Building/TextBuilder.cs index 79a64bd1ed..56349f16a8 100644 --- a/src/Umbraco.ModelsBuilder/Building/TextBuilder.cs +++ b/src/Umbraco.ModelsBuilder/Building/TextBuilder.cs @@ -5,7 +5,6 @@ using System.Text; using System.Text.RegularExpressions; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; -using Umbraco.ModelsBuilder.Api; using Umbraco.ModelsBuilder.Configuration; namespace Umbraco.ModelsBuilder.Building diff --git a/src/Umbraco.ModelsBuilder/Building/TextHeaderWriter.cs b/src/Umbraco.ModelsBuilder/Building/TextHeaderWriter.cs index 27fcb42b35..7244a2966d 100644 --- a/src/Umbraco.ModelsBuilder/Building/TextHeaderWriter.cs +++ b/src/Umbraco.ModelsBuilder/Building/TextHeaderWriter.cs @@ -1,5 +1,4 @@ using System.Text; -using Umbraco.ModelsBuilder.Api; namespace Umbraco.ModelsBuilder.Building { diff --git a/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderHasher.cs b/src/Umbraco.ModelsBuilder/Building/TypeModelHasher.cs similarity index 92% rename from src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderHasher.cs rename to src/Umbraco.ModelsBuilder/Building/TypeModelHasher.cs index 3354b4040d..2099c09415 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/ModelsBuilderHasher.cs +++ b/src/Umbraco.ModelsBuilder/Building/TypeModelHasher.cs @@ -1,10 +1,9 @@ using System.Collections.Generic; using System.Linq; -using Umbraco.ModelsBuilder.Building; -namespace Umbraco.ModelsBuilder.Umbraco +namespace Umbraco.ModelsBuilder.Building { - internal class ModelsBuilderHasher + internal class TypeModelHasher { public static string Hash(IEnumerable typeModels) { diff --git a/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderComponent.cs b/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderComponent.cs index 6deeda40a6..a68315606e 100644 --- a/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderComponent.cs +++ b/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderComponent.cs @@ -10,7 +10,6 @@ using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; using Umbraco.ModelsBuilder.BackOffice; using Umbraco.ModelsBuilder.Configuration; -using Umbraco.ModelsBuilder.Umbraco; using Umbraco.Web; using Umbraco.Web.JavaScript; using Umbraco.Web.Mvc; diff --git a/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderComposer.cs b/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderComposer.cs index f2b09903f3..fdc4253490 100644 --- a/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderComposer.cs +++ b/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderComposer.cs @@ -2,8 +2,8 @@ using Umbraco.Core; using Umbraco.Core.Composing; using Umbraco.Core.Models.PublishedContent; +using Umbraco.ModelsBuilder.Building; using Umbraco.ModelsBuilder.Configuration; -using Umbraco.ModelsBuilder.Umbraco; using Umbraco.Web.PublishedCache.NuCache; namespace Umbraco.ModelsBuilder.Compose diff --git a/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProvider.cs b/src/Umbraco.ModelsBuilder/LiveModelsProvider.cs similarity index 95% rename from src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProvider.cs rename to src/Umbraco.ModelsBuilder/LiveModelsProvider.cs index 19a725f85a..1e77ff3059 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProvider.cs +++ b/src/Umbraco.ModelsBuilder/LiveModelsProvider.cs @@ -5,13 +5,13 @@ using System.Web.Hosting; using Umbraco.Core.Exceptions; using Umbraco.Core.IO; using Umbraco.Core.Logging; +using Umbraco.ModelsBuilder.Building; using Umbraco.ModelsBuilder.Configuration; -using Umbraco.ModelsBuilder.Umbraco; using Umbraco.Web.Cache; -namespace Umbraco.ModelsBuilder.Umbraco +namespace Umbraco.ModelsBuilder { - // supports LiveDll and LiveAppData - but not PureLive + // supports LiveAppData - but not PureLive public sealed class LiveModelsProvider { private static Mutex _mutex; @@ -80,7 +80,7 @@ namespace Umbraco.ModelsBuilder.Umbraco try { _logger.Debug("Generate models..."); - const int timeout = 2*60*1000; // 2 mins + const int timeout = 2 * 60 * 1000; // 2 mins _mutex.WaitOne(timeout); // wait until it is safe, and acquire _logger.Info("Generate models now."); GenerateModels(); @@ -112,6 +112,6 @@ namespace Umbraco.ModelsBuilder.Umbraco _modelGenerator.GenerateModels(); } - + } } diff --git a/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProviderModule.cs b/src/Umbraco.ModelsBuilder/LiveModelsProviderModule.cs similarity index 95% rename from src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProviderModule.cs rename to src/Umbraco.ModelsBuilder/LiveModelsProviderModule.cs index aae01ea75c..9d50c4ab42 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/LiveModelsProviderModule.cs +++ b/src/Umbraco.ModelsBuilder/LiveModelsProviderModule.cs @@ -2,12 +2,12 @@ using System.Web; using Umbraco.Core; using Umbraco.Core.Composing; -using Umbraco.ModelsBuilder.Umbraco; +using Umbraco.ModelsBuilder; // will install only if configuration says it needs to be installed [assembly: PreApplicationStartMethod(typeof(LiveModelsProviderModule), "Install")] -namespace Umbraco.ModelsBuilder.Umbraco +namespace Umbraco.ModelsBuilder { // 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 diff --git a/src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerationError.cs b/src/Umbraco.ModelsBuilder/ModelsGenerationError.cs similarity index 97% rename from src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerationError.cs rename to src/Umbraco.ModelsBuilder/ModelsGenerationError.cs index 3f96d2ccdf..8264fecc87 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/ModelsGenerationError.cs +++ b/src/Umbraco.ModelsBuilder/ModelsGenerationError.cs @@ -4,7 +4,7 @@ using System.Text; using Umbraco.Core.Composing; using Umbraco.ModelsBuilder.Configuration; -namespace Umbraco.ModelsBuilder.Umbraco +namespace Umbraco.ModelsBuilder { public sealed class ModelsGenerationError { diff --git a/src/Umbraco.ModelsBuilder/Umbraco/OutOfDateModelsStatus.cs b/src/Umbraco.ModelsBuilder/OutOfDateModelsStatus.cs similarity index 97% rename from src/Umbraco.ModelsBuilder/Umbraco/OutOfDateModelsStatus.cs rename to src/Umbraco.ModelsBuilder/OutOfDateModelsStatus.cs index a72ccd2fa2..3b43ed771e 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/OutOfDateModelsStatus.cs +++ b/src/Umbraco.ModelsBuilder/OutOfDateModelsStatus.cs @@ -3,7 +3,7 @@ using Umbraco.Core.Composing; using Umbraco.ModelsBuilder.Configuration; using Umbraco.Web.Cache; -namespace Umbraco.ModelsBuilder.Umbraco +namespace Umbraco.ModelsBuilder { public sealed class OutOfDateModelsStatus { diff --git a/src/Umbraco.ModelsBuilder/Umbraco/PublishedModelUtility.cs b/src/Umbraco.ModelsBuilder/PublishedModelUtility.cs similarity index 92% rename from src/Umbraco.ModelsBuilder/Umbraco/PublishedModelUtility.cs rename to src/Umbraco.ModelsBuilder/PublishedModelUtility.cs index 1db15fa9b1..44af532a0b 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/PublishedModelUtility.cs +++ b/src/Umbraco.ModelsBuilder/PublishedModelUtility.cs @@ -5,7 +5,7 @@ using System.Linq.Expressions; using Umbraco.Web.Composing; using Umbraco.Core.Models.PublishedContent; -namespace Umbraco.ModelsBuilder.Umbraco +namespace Umbraco.ModelsBuilder { /// /// This is called from within the generated model classes @@ -48,7 +48,7 @@ namespace Umbraco.ModelsBuilder.Umbraco } public static IPublishedPropertyType GetModelPropertyType(IPublishedContentType contentType, Expression> selector) - //where TModel : PublishedContentModel // fixme PublishedContentModel _or_ PublishedElementModel + //where TModel : PublishedContentModel // fixme PublishedContentModel _or_ PublishedElementModel { // fixme therefore, missing a check on TModel here @@ -61,7 +61,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/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/Umbraco/PureLiveModelFactory.cs b/src/Umbraco.ModelsBuilder/PureLiveModelFactory.cs similarity index 96% rename from src/Umbraco.ModelsBuilder/Umbraco/PureLiveModelFactory.cs rename to src/Umbraco.ModelsBuilder/PureLiveModelFactory.cs index 464ce92ef5..eb82b4323f 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/PureLiveModelFactory.cs +++ b/src/Umbraco.ModelsBuilder/PureLiveModelFactory.cs @@ -21,7 +21,7 @@ using Umbraco.ModelsBuilder.Building; using Umbraco.ModelsBuilder.Configuration; using File = System.IO.File; -namespace Umbraco.ModelsBuilder.Umbraco +namespace Umbraco.ModelsBuilder { internal class PureLiveModelFactory : ILivePublishedModelFactory, IRegisteredObject { @@ -101,7 +101,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); @@ -234,10 +234,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; } } @@ -335,7 +335,7 @@ namespace Umbraco.ModelsBuilder.Umbraco Directory.CreateDirectory(modelsDirectory); var typeModels = UmbracoServices.GetAllTypes(); - var currentHash = ModelsBuilderHasher.Hash(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"); @@ -456,8 +456,7 @@ namespace Umbraco.ModelsBuilder.Umbraco // 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); @@ -505,7 +504,7 @@ namespace Umbraco.ModelsBuilder.Umbraco 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(); @@ -517,7 +516,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."); @@ -532,17 +531,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 !! // Yes .. DynamicMethod is uber slow - var meth = new DynamicMethod(string.Empty, typeof (IPublishedElement), ctorArgTypes, type.Module, true); + 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; @@ -663,9 +662,7 @@ namespace Umbraco.ModelsBuilder.Umbraco _logger.Info("Detected files changes."); lock (SyncRoot) // don't reset while being locked - { ResetModels(); - } } public void Stop(bool immediate) diff --git a/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj b/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj index 027ed6665c..46ec0c7d2c 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj +++ b/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj @@ -49,7 +49,7 @@ Properties\SolutionInfo.cs - + @@ -65,24 +65,23 @@ - - - - + + + - + - - - - - - + + + + + + diff --git a/src/Umbraco.ModelsBuilder/Umbraco/UmbracoServices.cs b/src/Umbraco.ModelsBuilder/UmbracoServices.cs similarity index 98% rename from src/Umbraco.ModelsBuilder/Umbraco/UmbracoServices.cs rename to src/Umbraco.ModelsBuilder/UmbracoServices.cs index 410349096a..725e5b18c4 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco/UmbracoServices.cs +++ b/src/Umbraco.ModelsBuilder/UmbracoServices.cs @@ -11,9 +11,9 @@ using Umbraco.Core.Strings; using Umbraco.ModelsBuilder.Building; using Umbraco.ModelsBuilder.Configuration; -namespace Umbraco.ModelsBuilder.Umbraco +namespace Umbraco.ModelsBuilder { - public class UmbracoServices + public sealed class UmbracoServices { private readonly IContentTypeService _contentTypeService; private readonly IMediaTypeService _mediaTypeService; @@ -184,11 +184,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.Tests/ModelsBuilder/BuilderTests.cs b/src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs index 18be0a37a3..83d9a9f4b0 100644 --- a/src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs +++ b/src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs @@ -7,7 +7,6 @@ using Moq; using NUnit.Framework; using Umbraco.Core.Composing; using Umbraco.Core.Models.PublishedContent; -using Umbraco.ModelsBuilder.Api; using Umbraco.ModelsBuilder.Building; using Umbraco.ModelsBuilder.Configuration; @@ -77,7 +76,6 @@ using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Web; using Umbraco.ModelsBuilder; -using Umbraco.ModelsBuilder.Umbraco; namespace Umbraco.Web.PublishedModels { @@ -193,7 +191,6 @@ using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Web; using Umbraco.ModelsBuilder; -using Umbraco.ModelsBuilder.Umbraco; namespace Umbraco.Web.PublishedModels { diff --git a/src/Umbraco.Tests/ModelsBuilder/UmbracoApplicationTests.cs b/src/Umbraco.Tests/ModelsBuilder/UmbracoApplicationTests.cs index 12fa777e69..d6d44474c3 100644 --- a/src/Umbraco.Tests/ModelsBuilder/UmbracoApplicationTests.cs +++ b/src/Umbraco.Tests/ModelsBuilder/UmbracoApplicationTests.cs @@ -5,7 +5,6 @@ using System.Text; using System.Threading.Tasks; using NUnit.Framework; using Umbraco.ModelsBuilder.Building; -using Umbraco.ModelsBuilder.Umbraco; namespace Umbraco.ModelsBuilder.Tests { From d7ca1933cd85b9fdb5ae88d641e51b6b7eb3893d Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 28 Oct 2019 21:59:08 +1100 Subject: [PATCH 20/49] fixes tests --- src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs b/src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs index 83d9a9f4b0..e9824b9799 100644 --- a/src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs +++ b/src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs @@ -15,13 +15,13 @@ namespace Umbraco.ModelsBuilder.Tests [TestFixture] public class BuilderTests { - [SetUp] - public void Setup() - { - Current.Reset(); - Current.UnlockConfigs(); - Current.Configs.Add(() => new ModelsBuilderConfig()); - } + // [SetUp] + // public void Setup() + // { + // Current.Reset(); + // Current.UnlockConfigs(); + // Current.Configs.Add(() => new ModelsBuilderConfig()); + // } [Test] public void GenerateSimpleType() From 35192eefe53dfd096efb7c5791a00580df650b4e Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 28 Oct 2019 22:12:08 +1100 Subject: [PATCH 21/49] cleanup --- src/Umbraco.ModelsBuilder/Building/Builder.cs | 7 ++++--- src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs | 7 ------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.ModelsBuilder/Building/Builder.cs b/src/Umbraco.ModelsBuilder/Building/Builder.cs index 885e51133b..bba19c3b9f 100644 --- a/src/Umbraco.ModelsBuilder/Building/Builder.cs +++ b/src/Umbraco.ModelsBuilder/Building/Builder.cs @@ -213,9 +213,10 @@ namespace Umbraco.ModelsBuilder.Building if (!string.IsNullOrWhiteSpace(ModelsNamespace)) return ModelsNamespace; - // default - // fixme - should NOT reference config here, should make ModelsNamespace mandatory - return string.IsNullOrWhiteSpace(Config.ModelsNamespace) ? ModelsBuilderConfig.DefaultModelsNamespace : Config.ModelsNamespace; + // use configured else fallback to default + return string.IsNullOrWhiteSpace(Config.ModelsNamespace) + ? ModelsBuilderConfig.DefaultModelsNamespace + : Config.ModelsNamespace; } protected string GetModelsBaseClassName(TypeModel type) diff --git a/src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs b/src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs index e9824b9799..99012643aa 100644 --- a/src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs +++ b/src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs @@ -15,13 +15,6 @@ namespace Umbraco.ModelsBuilder.Tests [TestFixture] public class BuilderTests { - // [SetUp] - // public void Setup() - // { - // Current.Reset(); - // Current.UnlockConfigs(); - // Current.Configs.Add(() => new ModelsBuilderConfig()); - // } [Test] public void GenerateSimpleType() From c10ad86c43938cf8b76325f1b5164bec9eaf3420 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 28 Oct 2019 22:23:06 +1100 Subject: [PATCH 22/49] more cleanup --- src/Umbraco.ModelsBuilder/LiveModelsProvider.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Umbraco.ModelsBuilder/LiveModelsProvider.cs b/src/Umbraco.ModelsBuilder/LiveModelsProvider.cs index 1e77ff3059..0dfe503fce 100644 --- a/src/Umbraco.ModelsBuilder/LiveModelsProvider.cs +++ b/src/Umbraco.ModelsBuilder/LiveModelsProvider.cs @@ -104,10 +104,6 @@ namespace Umbraco.ModelsBuilder private void GenerateModels() { - var bin = IOHelper.MapPath("~/bin"); - if (bin == null) - throw new PanicException("Panic: bin is null."); - // EnableDllModels will recycle the app domain - but this request will end properly _modelGenerator.GenerateModels(); } From 3b6abbb9368b0a93e6ef720e0a5d4bd0c8885a95 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 29 Oct 2019 00:25:03 +1100 Subject: [PATCH 23/49] Renames project: Umbraco.ModelsBuilder.Embedded and namespaces since we need a different assembly, updates nuspec, changes file path of MB app_plugins --- build/NuSpecs/UmbracoCms.Web.nuspec | 6 +++--- build/NuSpecs/UmbracoCms.nuspec | 1 + src/Umbraco.Core/Properties/AssemblyInfo.cs | 2 +- .../ApiVersion.cs | 2 +- .../BackOffice/ContentTypeModelValidator.cs | 4 +--- .../BackOffice/DashboardReport.cs | 5 ++--- .../ModelsBuilderBackOfficeController.cs | 9 +++------ .../Building/Builder.cs | 10 ++-------- .../Building/ModelsGenerator.cs | 8 +++----- .../Building/PropertyModel.cs | 2 +- .../Building/TextBuilder.cs | 6 ++---- .../Building/TextHeaderWriter.cs | 2 +- .../Building/TypeModel.cs | 2 +- .../Building/TypeModelHasher.cs | 2 +- .../Compose/ModelsBuilderComponent.cs | 6 +++--- .../Compose/ModelsBuilderComposer.cs | 8 +++++--- .../Compose/ModelsBuilderInitializer.cs | 4 ++-- .../ConfigsExtensions.cs | 4 ++-- .../Configuration/IModelsBuilderConfig.cs | 2 +- .../Configuration/ModelsBuilderConfig.cs | 3 +-- .../Configuration/ModelsMode.cs | 2 +- .../Configuration/ModelsModeExtensions.cs | 2 +- .../HashCombiner.cs | 2 +- .../ImplementPropertyTypeAttribute.cs | 2 +- .../LiveModelsProvider.cs | 9 +++------ .../LiveModelsProviderModule.cs | 4 ++-- .../ModelsBuilderAssemblyAttribute.cs | 2 +- .../ModelsGenerationError.cs | 5 ++--- .../OutOfDateModelsStatus.cs | 5 ++--- .../Properties/AssemblyInfo.cs | 0 .../PublishedElementExtensions.cs | 1 + .../PublishedModelUtility.cs | 5 ++--- .../PureLiveModelFactory.cs | 8 +++----- .../ReferencedAssemblies.cs | 2 +- .../TypeExtensions.cs | 2 +- .../Umbraco.ModelsBuilder.Embedded.csproj} | 6 +++--- .../UmbracoServices.cs | 6 ++---- .../ModelsBuilder/BuilderTests.cs | 20 ++++++++----------- .../ModelsBuilder/ConfigTests.cs | 4 ++-- .../ModelsBuilder/StringExtensions.cs | 8 +------- .../ModelsBuilder/UmbracoApplicationTests.cs | 8 +++----- src/Umbraco.Tests/Umbraco.Tests.csproj | 4 ++-- .../modelsbuilder.controller.js | 0 .../modelsbuilder.html | 0 .../modelsbuilder.resource.js | 0 .../package.manifest | 0 src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 14 +++++-------- src/umbraco.sln | 3 +-- 48 files changed, 86 insertions(+), 126 deletions(-) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/ApiVersion.cs (96%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/BackOffice/ContentTypeModelValidator.cs (96%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/BackOffice/DashboardReport.cs (94%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/BackOffice/ModelsBuilderBackOfficeController.cs (96%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/Building/Builder.cs (97%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/Building/ModelsGenerator.cs (93%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/Building/PropertyModel.cs (97%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/Building/TextBuilder.cs (99%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/Building/TextHeaderWriter.cs (95%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/Building/TypeModel.cs (99%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/Building/TypeModelHasher.cs (96%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/Compose/ModelsBuilderComponent.cs (98%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/Compose/ModelsBuilderComposer.cs (94%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/Compose/ModelsBuilderInitializer.cs (91%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/ConfigsExtensions.cs (87%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/Configuration/IModelsBuilderConfig.cs (86%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/Configuration/ModelsBuilderConfig.cs (99%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/Configuration/ModelsMode.cs (95%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/Configuration/ModelsModeExtensions.cs (95%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/HashCombiner.cs (95%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/ImplementPropertyTypeAttribute.cs (92%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/LiveModelsProvider.cs (95%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/LiveModelsProviderModule.cs (95%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/ModelsBuilderAssemblyAttribute.cs (95%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/ModelsGenerationError.cs (93%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/OutOfDateModelsStatus.cs (94%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/Properties/AssemblyInfo.cs (100%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/PublishedElementExtensions.cs (98%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/PublishedModelUtility.cs (98%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/PureLiveModelFactory.cs (99%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/ReferencedAssemblies.cs (99%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/TypeExtensions.cs (96%) rename src/{Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj => Umbraco.ModelsBuilder.Embedded/Umbraco.ModelsBuilder.Embedded.csproj} (95%) rename src/{Umbraco.ModelsBuilder => Umbraco.ModelsBuilder.Embedded}/UmbracoServices.cs (98%) rename src/Umbraco.Web.UI/App_Plugins/{ModelsBuilder => UmbModelsBuilder}/modelsbuilder.controller.js (100%) rename src/Umbraco.Web.UI/App_Plugins/{ModelsBuilder => UmbModelsBuilder}/modelsbuilder.html (100%) rename src/Umbraco.Web.UI/App_Plugins/{ModelsBuilder => UmbModelsBuilder}/modelsbuilder.resource.js (100%) rename src/Umbraco.Web.UI/App_Plugins/{ModelsBuilder => UmbModelsBuilder}/package.manifest (100%) diff --git a/build/NuSpecs/UmbracoCms.Web.nuspec b/build/NuSpecs/UmbracoCms.Web.nuspec index b6cbbbdb81..658d2f0672 100644 --- a/build/NuSpecs/UmbracoCms.Web.nuspec +++ b/build/NuSpecs/UmbracoCms.Web.nuspec @@ -52,17 +52,17 @@ - + - + - + diff --git a/build/NuSpecs/UmbracoCms.nuspec b/build/NuSpecs/UmbracoCms.nuspec index a6b06d9964..d0bd4a2688 100644 --- a/build/NuSpecs/UmbracoCms.nuspec +++ b/build/NuSpecs/UmbracoCms.nuspec @@ -42,6 +42,7 @@ + diff --git a/src/Umbraco.Core/Properties/AssemblyInfo.cs b/src/Umbraco.Core/Properties/AssemblyInfo.cs index 139af8725d..87e0732d47 100644 --- a/src/Umbraco.Core/Properties/AssemblyInfo.cs +++ b/src/Umbraco.Core/Properties/AssemblyInfo.cs @@ -14,7 +14,7 @@ using System.Runtime.InteropServices; [assembly: InternalsVisibleTo("Umbraco.Web")] [assembly: InternalsVisibleTo("Umbraco.Web.UI")] [assembly: InternalsVisibleTo("Umbraco.Examine")] -[assembly: InternalsVisibleTo("Umbraco.ModelsBuilder")] +[assembly: InternalsVisibleTo("Umbraco.ModelsBuilder.Embedded")] [assembly: InternalsVisibleTo("Umbraco.Tests")] [assembly: InternalsVisibleTo("Umbraco.Tests.Benchmarks")] diff --git a/src/Umbraco.ModelsBuilder/ApiVersion.cs b/src/Umbraco.ModelsBuilder.Embedded/ApiVersion.cs similarity index 96% rename from src/Umbraco.ModelsBuilder/ApiVersion.cs rename to src/Umbraco.ModelsBuilder.Embedded/ApiVersion.cs index 67d0ec5c4c..22347edd60 100644 --- a/src/Umbraco.ModelsBuilder/ApiVersion.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/ApiVersion.cs @@ -2,7 +2,7 @@ using System.Reflection; using Semver; -namespace Umbraco.ModelsBuilder +namespace Umbraco.ModelsBuilder.Embedded { /// /// Manages API version handshake between client and server. diff --git a/src/Umbraco.ModelsBuilder/BackOffice/ContentTypeModelValidator.cs b/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ContentTypeModelValidator.cs similarity index 96% rename from src/Umbraco.ModelsBuilder/BackOffice/ContentTypeModelValidator.cs rename to src/Umbraco.ModelsBuilder.Embedded/BackOffice/ContentTypeModelValidator.cs index 1971c62c9f..f351721e56 100644 --- a/src/Umbraco.ModelsBuilder/BackOffice/ContentTypeModelValidator.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ContentTypeModelValidator.cs @@ -2,13 +2,11 @@ using System.ComponentModel.DataAnnotations; using System.Linq; using Umbraco.Core; -using Umbraco.Core.Composing; using Umbraco.Core.Models.PublishedContent; -using Umbraco.ModelsBuilder.Configuration; using Umbraco.Web.Editors; using Umbraco.Web.Models.ContentEditing; -namespace Umbraco.ModelsBuilder.BackOffice +namespace Umbraco.ModelsBuilder.Embedded.BackOffice { /// /// Used to validate the aliases for the content type when MB is enabled to ensure that diff --git a/src/Umbraco.ModelsBuilder/BackOffice/DashboardReport.cs b/src/Umbraco.ModelsBuilder.Embedded/BackOffice/DashboardReport.cs similarity index 94% rename from src/Umbraco.ModelsBuilder/BackOffice/DashboardReport.cs rename to src/Umbraco.ModelsBuilder.Embedded/BackOffice/DashboardReport.cs index 64fb217fce..e4c45aaf3e 100644 --- a/src/Umbraco.ModelsBuilder/BackOffice/DashboardReport.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/BackOffice/DashboardReport.cs @@ -1,8 +1,7 @@ using System.Text; -using Umbraco.Core.Composing; -using Umbraco.ModelsBuilder.Configuration; +using Umbraco.ModelsBuilder.Embedded.Configuration; -namespace Umbraco.ModelsBuilder.BackOffice +namespace Umbraco.ModelsBuilder.Embedded.BackOffice { internal class DashboardReport { diff --git a/src/Umbraco.ModelsBuilder/BackOffice/ModelsBuilderBackOfficeController.cs b/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ModelsBuilderBackOfficeController.cs similarity index 96% rename from src/Umbraco.ModelsBuilder/BackOffice/ModelsBuilderBackOfficeController.cs rename to src/Umbraco.ModelsBuilder.Embedded/BackOffice/ModelsBuilderBackOfficeController.cs index e4efca70bd..0842c3586a 100644 --- a/src/Umbraco.ModelsBuilder/BackOffice/ModelsBuilderBackOfficeController.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ModelsBuilderBackOfficeController.cs @@ -1,18 +1,15 @@ 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.Exceptions; -using Umbraco.ModelsBuilder.Building; -using Umbraco.ModelsBuilder.Configuration; +using Umbraco.ModelsBuilder.Embedded.Building; +using Umbraco.ModelsBuilder.Embedded.Configuration; using Umbraco.Web.Editors; using Umbraco.Web.WebApi.Filters; -namespace Umbraco.ModelsBuilder.BackOffice +namespace Umbraco.ModelsBuilder.Embedded.BackOffice { /// /// API controller for use in the Umbraco back office with Angular resources diff --git a/src/Umbraco.ModelsBuilder/Building/Builder.cs b/src/Umbraco.ModelsBuilder.Embedded/Building/Builder.cs similarity index 97% rename from src/Umbraco.ModelsBuilder/Building/Builder.cs rename to src/Umbraco.ModelsBuilder.Embedded/Building/Builder.cs index bba19c3b9f..82b1b0adec 100644 --- a/src/Umbraco.ModelsBuilder/Building/Builder.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Building/Builder.cs @@ -1,15 +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.Composing; -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 diff --git a/src/Umbraco.ModelsBuilder/Building/ModelsGenerator.cs b/src/Umbraco.ModelsBuilder.Embedded/Building/ModelsGenerator.cs similarity index 93% rename from src/Umbraco.ModelsBuilder/Building/ModelsGenerator.cs rename to src/Umbraco.ModelsBuilder.Embedded/Building/ModelsGenerator.cs index 6f6872e8ff..8a3bc5a5b5 100644 --- a/src/Umbraco.ModelsBuilder/Building/ModelsGenerator.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Building/ModelsGenerator.cs @@ -1,10 +1,8 @@ -using System; -using System.IO; +using System.IO; using System.Text; -using System.Web; -using Umbraco.ModelsBuilder.Configuration; +using Umbraco.ModelsBuilder.Embedded.Configuration; -namespace Umbraco.ModelsBuilder.Building +namespace Umbraco.ModelsBuilder.Embedded.Building { public class ModelsGenerator { diff --git a/src/Umbraco.ModelsBuilder/Building/PropertyModel.cs b/src/Umbraco.ModelsBuilder.Embedded/Building/PropertyModel.cs similarity index 97% rename from src/Umbraco.ModelsBuilder/Building/PropertyModel.cs rename to src/Umbraco.ModelsBuilder.Embedded/Building/PropertyModel.cs index 5f2545ec2a..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. diff --git a/src/Umbraco.ModelsBuilder/Building/TextBuilder.cs b/src/Umbraco.ModelsBuilder.Embedded/Building/TextBuilder.cs similarity index 99% rename from src/Umbraco.ModelsBuilder/Building/TextBuilder.cs rename to src/Umbraco.ModelsBuilder.Embedded/Building/TextBuilder.cs index 56349f16a8..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.Composing; -using Umbraco.Core.Configuration; -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. diff --git a/src/Umbraco.ModelsBuilder/Building/TextHeaderWriter.cs b/src/Umbraco.ModelsBuilder.Embedded/Building/TextHeaderWriter.cs similarity index 95% rename from src/Umbraco.ModelsBuilder/Building/TextHeaderWriter.cs rename to src/Umbraco.ModelsBuilder.Embedded/Building/TextHeaderWriter.cs index 7244a2966d..a93df97806 100644 --- a/src/Umbraco.ModelsBuilder/Building/TextHeaderWriter.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Building/TextHeaderWriter.cs @@ -1,6 +1,6 @@ using System.Text; -namespace Umbraco.ModelsBuilder.Building +namespace Umbraco.ModelsBuilder.Embedded.Building { internal static class TextHeaderWriter { diff --git a/src/Umbraco.ModelsBuilder/Building/TypeModel.cs b/src/Umbraco.ModelsBuilder.Embedded/Building/TypeModel.cs similarity index 99% rename from src/Umbraco.ModelsBuilder/Building/TypeModel.cs rename to src/Umbraco.ModelsBuilder.Embedded/Building/TypeModel.cs index 06b5e7848a..95356cf3ff 100644 --- a/src/Umbraco.ModelsBuilder/Building/TypeModel.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Building/TypeModel.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Linq; using Umbraco.Core.Models.PublishedContent; -namespace Umbraco.ModelsBuilder.Building +namespace Umbraco.ModelsBuilder.Embedded.Building { /// /// Represents a model. diff --git a/src/Umbraco.ModelsBuilder/Building/TypeModelHasher.cs b/src/Umbraco.ModelsBuilder.Embedded/Building/TypeModelHasher.cs similarity index 96% rename from src/Umbraco.ModelsBuilder/Building/TypeModelHasher.cs rename to src/Umbraco.ModelsBuilder.Embedded/Building/TypeModelHasher.cs index 2099c09415..ff2893d097 100644 --- a/src/Umbraco.ModelsBuilder/Building/TypeModelHasher.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Building/TypeModelHasher.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Linq; -namespace Umbraco.ModelsBuilder.Building +namespace Umbraco.ModelsBuilder.Embedded.Building { internal class TypeModelHasher { diff --git a/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderComponent.cs b/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComponent.cs similarity index 98% rename from src/Umbraco.ModelsBuilder/Compose/ModelsBuilderComponent.cs rename to src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComponent.cs index a68315606e..401a5a8931 100644 --- a/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderComponent.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComponent.cs @@ -8,13 +8,13 @@ using Umbraco.Core.Composing; using Umbraco.Core.IO; using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; -using Umbraco.ModelsBuilder.BackOffice; -using Umbraco.ModelsBuilder.Configuration; +using Umbraco.ModelsBuilder.Embedded.BackOffice; +using Umbraco.ModelsBuilder.Embedded.Configuration; using Umbraco.Web; using Umbraco.Web.JavaScript; using Umbraco.Web.Mvc; -namespace Umbraco.ModelsBuilder.Compose +namespace Umbraco.ModelsBuilder.Embedded.Compose { public class ModelsBuilderComponent : IComponent { diff --git a/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderComposer.cs b/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComposer.cs similarity index 94% rename from src/Umbraco.ModelsBuilder/Compose/ModelsBuilderComposer.cs rename to src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComposer.cs index fdc4253490..8b3f8db534 100644 --- a/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderComposer.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComposer.cs @@ -2,11 +2,11 @@ using Umbraco.Core; using Umbraco.Core.Composing; using Umbraco.Core.Models.PublishedContent; -using Umbraco.ModelsBuilder.Building; -using Umbraco.ModelsBuilder.Configuration; +using Umbraco.ModelsBuilder.Embedded.Building; +using Umbraco.ModelsBuilder.Embedded.Configuration; using Umbraco.Web.PublishedCache.NuCache; -namespace Umbraco.ModelsBuilder.Compose +namespace Umbraco.ModelsBuilder.Embedded.Compose { [ComposeBefore(typeof(NuCacheComposer))] [RuntimeLevel(MinLevel = RuntimeLevel.Run)] @@ -16,6 +16,8 @@ namespace Umbraco.ModelsBuilder.Compose { base.Compose(composition); + + composition.Register(Lifetime.Singleton); composition.Configs.Add(() => new ModelsBuilderConfig()); composition.RegisterUnique(); diff --git a/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderInitializer.cs b/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderInitializer.cs similarity index 91% rename from src/Umbraco.ModelsBuilder/Compose/ModelsBuilderInitializer.cs rename to src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderInitializer.cs index 6eb8bbb328..a86669b135 100644 --- a/src/Umbraco.ModelsBuilder/Compose/ModelsBuilderInitializer.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderInitializer.cs @@ -1,10 +1,10 @@ using System.Web; using System.Web.Compilation; -using Umbraco.ModelsBuilder.Compose; +using Umbraco.ModelsBuilder.Embedded.Compose; [assembly: PreApplicationStartMethod(typeof(ModelsBuilderInitializer), "Initialize")] -namespace Umbraco.ModelsBuilder.Compose +namespace Umbraco.ModelsBuilder.Embedded.Compose { public static class ModelsBuilderInitializer { diff --git a/src/Umbraco.ModelsBuilder/ConfigsExtensions.cs b/src/Umbraco.ModelsBuilder.Embedded/ConfigsExtensions.cs similarity index 87% rename from src/Umbraco.ModelsBuilder/ConfigsExtensions.cs rename to src/Umbraco.ModelsBuilder.Embedded/ConfigsExtensions.cs index 09314bc3f3..e2b91a8974 100644 --- a/src/Umbraco.ModelsBuilder/ConfigsExtensions.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/ConfigsExtensions.cs @@ -1,7 +1,7 @@ using Umbraco.Core.Configuration; -using Umbraco.ModelsBuilder.Configuration; +using Umbraco.ModelsBuilder.Embedded.Configuration; -namespace Umbraco.ModelsBuilder +namespace Umbraco.ModelsBuilder.Embedded { /// /// Provides extension methods for the class. diff --git a/src/Umbraco.ModelsBuilder/Configuration/IModelsBuilderConfig.cs b/src/Umbraco.ModelsBuilder.Embedded/Configuration/IModelsBuilderConfig.cs similarity index 86% rename from src/Umbraco.ModelsBuilder/Configuration/IModelsBuilderConfig.cs rename to src/Umbraco.ModelsBuilder.Embedded/Configuration/IModelsBuilderConfig.cs index 3bca389f2f..d8a81f0458 100644 --- a/src/Umbraco.ModelsBuilder/Configuration/IModelsBuilderConfig.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Configuration/IModelsBuilderConfig.cs @@ -1,4 +1,4 @@ -namespace Umbraco.ModelsBuilder.Configuration +namespace Umbraco.ModelsBuilder.Embedded.Configuration { public interface IModelsBuilderConfig { diff --git a/src/Umbraco.ModelsBuilder/Configuration/ModelsBuilderConfig.cs b/src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsBuilderConfig.cs similarity index 99% rename from src/Umbraco.ModelsBuilder/Configuration/ModelsBuilderConfig.cs rename to src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsBuilderConfig.cs index 48a4423054..881c4f97b8 100644 --- a/src/Umbraco.ModelsBuilder/Configuration/ModelsBuilderConfig.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsBuilderConfig.cs @@ -2,11 +2,10 @@ using System.Configuration; using System.IO; using System.Web.Configuration; -using System.Web.Hosting; using Umbraco.Core; using Umbraco.Core.IO; -namespace Umbraco.ModelsBuilder.Configuration +namespace Umbraco.ModelsBuilder.Embedded.Configuration { /// /// Represents the models builder configuration. diff --git a/src/Umbraco.ModelsBuilder/Configuration/ModelsMode.cs b/src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsMode.cs similarity index 95% rename from src/Umbraco.ModelsBuilder/Configuration/ModelsMode.cs rename to src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsMode.cs index 1f1d65f4f1..145508991a 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. diff --git a/src/Umbraco.ModelsBuilder/Configuration/ModelsModeExtensions.cs b/src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsModeExtensions.cs similarity index 95% rename from src/Umbraco.ModelsBuilder/Configuration/ModelsModeExtensions.cs rename to src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsModeExtensions.cs index f40d7973b8..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. diff --git a/src/Umbraco.ModelsBuilder/HashCombiner.cs b/src/Umbraco.ModelsBuilder.Embedded/HashCombiner.cs similarity index 95% rename from src/Umbraco.ModelsBuilder/HashCombiner.cs rename to src/Umbraco.ModelsBuilder.Embedded/HashCombiner.cs index 51e02e93c1..1c1fca6f73 100644 --- a/src/Umbraco.ModelsBuilder/HashCombiner.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/HashCombiner.cs @@ -1,7 +1,7 @@ using System; using System.Globalization; -namespace Umbraco.ModelsBuilder +namespace Umbraco.ModelsBuilder.Embedded { // because, of course, it's internal in Umbraco // see also System.Web.Util.HashCodeCombiner diff --git a/src/Umbraco.ModelsBuilder/ImplementPropertyTypeAttribute.cs b/src/Umbraco.ModelsBuilder.Embedded/ImplementPropertyTypeAttribute.cs similarity index 92% rename from src/Umbraco.ModelsBuilder/ImplementPropertyTypeAttribute.cs rename to src/Umbraco.ModelsBuilder.Embedded/ImplementPropertyTypeAttribute.cs index a3dba740c1..0359c49654 100644 --- a/src/Umbraco.ModelsBuilder/ImplementPropertyTypeAttribute.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/ImplementPropertyTypeAttribute.cs @@ -1,6 +1,6 @@ using System; -namespace Umbraco.ModelsBuilder +namespace Umbraco.ModelsBuilder.Embedded { /// /// Indicates that a property implements a given property alias. diff --git a/src/Umbraco.ModelsBuilder/LiveModelsProvider.cs b/src/Umbraco.ModelsBuilder.Embedded/LiveModelsProvider.cs similarity index 95% rename from src/Umbraco.ModelsBuilder/LiveModelsProvider.cs rename to src/Umbraco.ModelsBuilder.Embedded/LiveModelsProvider.cs index 0dfe503fce..333181f27c 100644 --- a/src/Umbraco.ModelsBuilder/LiveModelsProvider.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/LiveModelsProvider.cs @@ -1,15 +1,12 @@ using System; using System.Threading; -using System.Web; using System.Web.Hosting; -using Umbraco.Core.Exceptions; -using Umbraco.Core.IO; using Umbraco.Core.Logging; -using Umbraco.ModelsBuilder.Building; -using Umbraco.ModelsBuilder.Configuration; +using Umbraco.ModelsBuilder.Embedded.Building; +using Umbraco.ModelsBuilder.Embedded.Configuration; using Umbraco.Web.Cache; -namespace Umbraco.ModelsBuilder +namespace Umbraco.ModelsBuilder.Embedded { // supports LiveAppData - but not PureLive public sealed class LiveModelsProvider diff --git a/src/Umbraco.ModelsBuilder/LiveModelsProviderModule.cs b/src/Umbraco.ModelsBuilder.Embedded/LiveModelsProviderModule.cs similarity index 95% rename from src/Umbraco.ModelsBuilder/LiveModelsProviderModule.cs rename to src/Umbraco.ModelsBuilder.Embedded/LiveModelsProviderModule.cs index 9d50c4ab42..37b39124c3 100644 --- a/src/Umbraco.ModelsBuilder/LiveModelsProviderModule.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/LiveModelsProviderModule.cs @@ -2,12 +2,12 @@ using System.Web; using Umbraco.Core; using Umbraco.Core.Composing; -using Umbraco.ModelsBuilder; +using Umbraco.ModelsBuilder.Embedded; // will install only if configuration says it needs to be installed [assembly: PreApplicationStartMethod(typeof(LiveModelsProviderModule), "Install")] -namespace Umbraco.ModelsBuilder +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 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/ModelsGenerationError.cs b/src/Umbraco.ModelsBuilder.Embedded/ModelsGenerationError.cs similarity index 93% rename from src/Umbraco.ModelsBuilder/ModelsGenerationError.cs rename to src/Umbraco.ModelsBuilder.Embedded/ModelsGenerationError.cs index 8264fecc87..a692f633a5 100644 --- a/src/Umbraco.ModelsBuilder/ModelsGenerationError.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/ModelsGenerationError.cs @@ -1,10 +1,9 @@ using System; using System.IO; using System.Text; -using Umbraco.Core.Composing; -using Umbraco.ModelsBuilder.Configuration; +using Umbraco.ModelsBuilder.Embedded.Configuration; -namespace Umbraco.ModelsBuilder +namespace Umbraco.ModelsBuilder.Embedded { public sealed class ModelsGenerationError { diff --git a/src/Umbraco.ModelsBuilder/OutOfDateModelsStatus.cs b/src/Umbraco.ModelsBuilder.Embedded/OutOfDateModelsStatus.cs similarity index 94% rename from src/Umbraco.ModelsBuilder/OutOfDateModelsStatus.cs rename to src/Umbraco.ModelsBuilder.Embedded/OutOfDateModelsStatus.cs index 3b43ed771e..5425c31c77 100644 --- a/src/Umbraco.ModelsBuilder/OutOfDateModelsStatus.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/OutOfDateModelsStatus.cs @@ -1,9 +1,8 @@ using System.IO; -using Umbraco.Core.Composing; -using Umbraco.ModelsBuilder.Configuration; +using Umbraco.ModelsBuilder.Embedded.Configuration; using Umbraco.Web.Cache; -namespace Umbraco.ModelsBuilder +namespace Umbraco.ModelsBuilder.Embedded { public sealed class OutOfDateModelsStatus { diff --git a/src/Umbraco.ModelsBuilder/Properties/AssemblyInfo.cs b/src/Umbraco.ModelsBuilder.Embedded/Properties/AssemblyInfo.cs similarity index 100% rename from src/Umbraco.ModelsBuilder/Properties/AssemblyInfo.cs rename to src/Umbraco.ModelsBuilder.Embedded/Properties/AssemblyInfo.cs diff --git a/src/Umbraco.ModelsBuilder/PublishedElementExtensions.cs b/src/Umbraco.ModelsBuilder.Embedded/PublishedElementExtensions.cs similarity index 98% rename from src/Umbraco.ModelsBuilder/PublishedElementExtensions.cs rename to src/Umbraco.ModelsBuilder.Embedded/PublishedElementExtensions.cs index 1211c4fdad..29429ba74f 100644 --- a/src/Umbraco.ModelsBuilder/PublishedElementExtensions.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/PublishedElementExtensions.cs @@ -3,6 +3,7 @@ using System.Linq.Expressions; using System.Reflection; using Umbraco.Core.Models.PublishedContent; using Umbraco.ModelsBuilder; +using Umbraco.ModelsBuilder.Embedded; // same namespace as original Umbraco.Web PublishedElementExtensions // ReSharper disable once CheckNamespace diff --git a/src/Umbraco.ModelsBuilder/PublishedModelUtility.cs b/src/Umbraco.ModelsBuilder.Embedded/PublishedModelUtility.cs similarity index 98% rename from src/Umbraco.ModelsBuilder/PublishedModelUtility.cs rename to src/Umbraco.ModelsBuilder.Embedded/PublishedModelUtility.cs index 44af532a0b..8a6ed83ce9 100644 --- a/src/Umbraco.ModelsBuilder/PublishedModelUtility.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/PublishedModelUtility.cs @@ -1,11 +1,10 @@ using System; -using System.ComponentModel; using System.Linq; using System.Linq.Expressions; -using Umbraco.Web.Composing; using Umbraco.Core.Models.PublishedContent; +using Umbraco.Web.Composing; -namespace Umbraco.ModelsBuilder +namespace Umbraco.ModelsBuilder.Embedded { /// /// This is called from within the generated model classes diff --git a/src/Umbraco.ModelsBuilder/PureLiveModelFactory.cs b/src/Umbraco.ModelsBuilder.Embedded/PureLiveModelFactory.cs similarity index 99% rename from src/Umbraco.ModelsBuilder/PureLiveModelFactory.cs rename to src/Umbraco.ModelsBuilder.Embedded/PureLiveModelFactory.cs index eb82b4323f..8e8a19c729 100644 --- a/src/Umbraco.ModelsBuilder/PureLiveModelFactory.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/PureLiveModelFactory.cs @@ -13,15 +13,13 @@ using System.Web.Compilation; using System.Web.Hosting; using System.Web.WebPages.Razor; using Umbraco.Core; -using Umbraco.Core.Composing; using Umbraco.Core.Logging; 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 +namespace Umbraco.ModelsBuilder.Embedded { internal class PureLiveModelFactory : ILivePublishedModelFactory, IRegisteredObject { diff --git a/src/Umbraco.ModelsBuilder/ReferencedAssemblies.cs b/src/Umbraco.ModelsBuilder.Embedded/ReferencedAssemblies.cs similarity index 99% rename from src/Umbraco.ModelsBuilder/ReferencedAssemblies.cs rename to src/Umbraco.ModelsBuilder.Embedded/ReferencedAssemblies.cs index e0a20eb9d6..4ccc1afc93 100644 --- a/src/Umbraco.ModelsBuilder/ReferencedAssemblies.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/ReferencedAssemblies.cs @@ -6,7 +6,7 @@ using System.Web.Compilation; using System.Web.Hosting; using Umbraco.Core; -namespace Umbraco.ModelsBuilder +namespace Umbraco.ModelsBuilder.Embedded { internal static class ReferencedAssemblies { 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 95% rename from src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj rename to src/Umbraco.ModelsBuilder.Embedded/Umbraco.ModelsBuilder.Embedded.csproj index 46ec0c7d2c..a9a925d9cf 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj +++ b/src/Umbraco.ModelsBuilder.Embedded/Umbraco.ModelsBuilder.Embedded.csproj @@ -7,8 +7,8 @@ {52AC0BA8-A60E-4E36-897B-E8B97A54ED1C} Library Properties - Umbraco.ModelsBuilder - Umbraco.ModelsBuilder + Umbraco.ModelsBuilder.Embedded + Umbraco.ModelsBuilder.Embedded v4.7.2 512 true @@ -30,7 +30,7 @@ TRACE prompt 4 - bin\Release\Umbraco.ModelsBuilder.xml + bin\Release\Umbraco.ModelsBuilder.Embedded.xml diff --git a/src/Umbraco.ModelsBuilder/UmbracoServices.cs b/src/Umbraco.ModelsBuilder.Embedded/UmbracoServices.cs similarity index 98% rename from src/Umbraco.ModelsBuilder/UmbracoServices.cs rename to src/Umbraco.ModelsBuilder.Embedded/UmbracoServices.cs index 725e5b18c4..cd9f4802f2 100644 --- a/src/Umbraco.ModelsBuilder/UmbracoServices.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/UmbracoServices.cs @@ -2,16 +2,14 @@ using System.Collections.Generic; using System.Linq; using Umbraco.Core; -using Umbraco.Core.Composing; 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 +namespace Umbraco.ModelsBuilder.Embedded { public sealed class UmbracoServices { diff --git a/src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs b/src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs index 99012643aa..fd381178cc 100644 --- a/src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs +++ b/src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs @@ -2,15 +2,14 @@ using System.Collections.Generic; using System.Linq; using System.Text; -using Microsoft.CodeAnalysis; using Moq; using NUnit.Framework; -using Umbraco.Core.Composing; using Umbraco.Core.Models.PublishedContent; -using Umbraco.ModelsBuilder.Building; -using Umbraco.ModelsBuilder.Configuration; +using Umbraco.ModelsBuilder.Embedded; +using Umbraco.ModelsBuilder.Embedded.Building; +using Umbraco.ModelsBuilder.Embedded.Configuration; -namespace Umbraco.ModelsBuilder.Tests +namespace Umbraco.Tests.ModelsBuilder { [TestFixture] public class BuilderTests @@ -246,7 +245,7 @@ namespace Umbraco.Web.PublishedModels { Alias = "prop2", ClrName = "Prop2", - ModelClrType = typeof(System.Text.StringBuilder), + ModelClrType = typeof(global::System.Text.StringBuilder), }); type1.Properties.Add(new PropertyModel { @@ -342,7 +341,7 @@ namespace Umbraco.Web.PublishedModels builder.Using.Add("Umbraco.ModelsBuilder.Tests"); builder.ModelsNamespaceForTests = "SomeRandomNamespace"; var sb = new StringBuilder(); - builder.WriteClrType(sb, typeof(System.Text.ASCIIEncoding)); + 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 @@ -358,7 +357,7 @@ namespace Umbraco.Web.PublishedModels builder.Using.Add("Umbraco.ModelsBuilder.Tests"); builder.ModelsNamespaceForTests = "SomeBorkedNamespace"; var sb = new StringBuilder(); - builder.WriteClrType(sb, typeof(System.Text.ASCIIEncoding)); + 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 @@ -417,7 +416,7 @@ namespace Umbraco.Web.PublishedModels public class Class1 { } } - // make it public to be ambiguous (see above) +// make it public to be ambiguous (see above) public class ASCIIEncoding { // can we handle nested types? @@ -425,9 +424,6 @@ namespace Umbraco.Web.PublishedModels } class BuilderTestsClass1 {} -} -namespace SomeBorkedNamespace -{ public class System { } } diff --git a/src/Umbraco.Tests/ModelsBuilder/ConfigTests.cs b/src/Umbraco.Tests/ModelsBuilder/ConfigTests.cs index 58215707f7..5e122ad0fa 100644 --- a/src/Umbraco.Tests/ModelsBuilder/ConfigTests.cs +++ b/src/Umbraco.Tests/ModelsBuilder/ConfigTests.cs @@ -1,8 +1,8 @@ using System.Configuration; using NUnit.Framework; -using Umbraco.ModelsBuilder.Configuration; +using Umbraco.ModelsBuilder.Embedded.Configuration; -namespace Umbraco.ModelsBuilder.Tests +namespace Umbraco.Tests.ModelsBuilder { [TestFixture] public class ModelsBuilderConfigTests diff --git a/src/Umbraco.Tests/ModelsBuilder/StringExtensions.cs b/src/Umbraco.Tests/ModelsBuilder/StringExtensions.cs index 13a256aa14..361d104911 100644 --- a/src/Umbraco.Tests/ModelsBuilder/StringExtensions.cs +++ b/src/Umbraco.Tests/ModelsBuilder/StringExtensions.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Umbraco.ModelsBuilder.Tests +namespace Umbraco.Tests.ModelsBuilder { public static class StringExtensions { diff --git a/src/Umbraco.Tests/ModelsBuilder/UmbracoApplicationTests.cs b/src/Umbraco.Tests/ModelsBuilder/UmbracoApplicationTests.cs index d6d44474c3..4d2ae0e6c6 100644 --- a/src/Umbraco.Tests/ModelsBuilder/UmbracoApplicationTests.cs +++ b/src/Umbraco.Tests/ModelsBuilder/UmbracoApplicationTests.cs @@ -1,12 +1,10 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using NUnit.Framework; -using Umbraco.ModelsBuilder.Building; +using Umbraco.ModelsBuilder.Embedded; +using Umbraco.ModelsBuilder.Embedded.Building; -namespace Umbraco.ModelsBuilder.Tests +namespace Umbraco.Tests.ModelsBuilder { [TestFixture] public class UmbracoApplicationTests diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index ebf081160b..83fff814a4 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -553,9 +553,9 @@ {31785BC3-256C-4613-B2F5-A1B0BDDED8C1} Umbraco.Core - + {52ac0ba8-a60e-4e36-897b-e8b97a54ed1c} - Umbraco.ModelsBuilder + Umbraco.ModelsBuilder.Embedded {651E1350-91B6-44B7-BD60-7207006D7003} diff --git a/src/Umbraco.Web.UI/App_Plugins/ModelsBuilder/modelsbuilder.controller.js b/src/Umbraco.Web.UI/App_Plugins/UmbModelsBuilder/modelsbuilder.controller.js similarity index 100% rename from src/Umbraco.Web.UI/App_Plugins/ModelsBuilder/modelsbuilder.controller.js rename to src/Umbraco.Web.UI/App_Plugins/UmbModelsBuilder/modelsbuilder.controller.js diff --git a/src/Umbraco.Web.UI/App_Plugins/ModelsBuilder/modelsbuilder.html b/src/Umbraco.Web.UI/App_Plugins/UmbModelsBuilder/modelsbuilder.html similarity index 100% rename from src/Umbraco.Web.UI/App_Plugins/ModelsBuilder/modelsbuilder.html rename to src/Umbraco.Web.UI/App_Plugins/UmbModelsBuilder/modelsbuilder.html diff --git a/src/Umbraco.Web.UI/App_Plugins/ModelsBuilder/modelsbuilder.resource.js b/src/Umbraco.Web.UI/App_Plugins/UmbModelsBuilder/modelsbuilder.resource.js similarity index 100% rename from src/Umbraco.Web.UI/App_Plugins/ModelsBuilder/modelsbuilder.resource.js rename to src/Umbraco.Web.UI/App_Plugins/UmbModelsBuilder/modelsbuilder.resource.js diff --git a/src/Umbraco.Web.UI/App_Plugins/ModelsBuilder/package.manifest b/src/Umbraco.Web.UI/App_Plugins/UmbModelsBuilder/package.manifest similarity index 100% rename from src/Umbraco.Web.UI/App_Plugins/ModelsBuilder/package.manifest rename to src/Umbraco.Web.UI/App_Plugins/UmbModelsBuilder/package.manifest diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 1db4fabbe1..99c1c994d9 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -12,7 +12,7 @@ {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} OnBuildSuccess true - 44319 + 44331 enabled disabled false @@ -118,10 +118,6 @@ Umbraco.Examine {07FBC26B-2927-4A22-8D96-D644C667FECC} - - {52ac0ba8-a60e-4e36-897b-e8b97a54ed1c} - Umbraco.ModelsBuilder - {651e1350-91b6-44b7-bd60-7207006d7003} Umbraco.Web @@ -144,9 +140,9 @@ True Settings.settings - - - + + + @@ -172,7 +168,7 @@ - + ClientDependency.config Designer diff --git a/src/umbraco.sln b/src/umbraco.sln index 1d5aa9a60e..ba9df633bb 100644 --- a/src/umbraco.sln +++ b/src/umbraco.sln @@ -1,6 +1,5 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 -VisualStudioVersion = 16.0.29009.5 VisualStudioVersion = 16.0.29209.152 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Umbraco.Web.UI", "Umbraco.Web.UI\Umbraco.Web.UI.csproj", "{4C4C194C-B5E4-4991-8F87-4373E24CC19F}" @@ -103,7 +102,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "IssueTemplates", "IssueTemp ..\.github\ISSUE_TEMPLATE\5_Security_issue.md = ..\.github\ISSUE_TEMPLATE\5_Security_issue.md EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Umbraco.ModelsBuilder", "Umbraco.ModelsBuilder\Umbraco.ModelsBuilder.csproj", "{52AC0BA8-A60E-4E36-897B-E8B97A54ED1C}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Umbraco.ModelsBuilder.Embedded", "Umbraco.ModelsBuilder.Embedded\Umbraco.ModelsBuilder.Embedded.csproj", "{52AC0BA8-A60E-4E36-897B-E8B97A54ED1C}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution From cd8930871118af37948f72135b02fd1ceb67598e Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 29 Oct 2019 01:14:10 +1100 Subject: [PATCH 24/49] Ensures embedded MB is enabled on the UI proj, disables embedded MB if the legacy MB is detected --- ...cs => ModelsBuilderDashboardController.cs} | 4 +- .../DisableModelsBuilderManifestFilter.cs | 17 ++++++++ .../Compose/DisabledModelsBuilderComponent.cs | 30 ++++++++++++++ .../Compose/ModelsBuilderComponent.cs | 5 ++- .../Compose/ModelsBuilderComposer.cs | 40 +++++++++++++++++-- .../Umbraco.ModelsBuilder.Embedded.csproj | 4 +- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 4 ++ src/Umbraco.Web/Properties/AssemblyInfo.cs | 2 +- 8 files changed, 96 insertions(+), 10 deletions(-) rename src/Umbraco.ModelsBuilder.Embedded/BackOffice/{ModelsBuilderBackOfficeController.cs => ModelsBuilderDashboardController.cs} (95%) create mode 100644 src/Umbraco.ModelsBuilder.Embedded/Compose/DisableModelsBuilderManifestFilter.cs create mode 100644 src/Umbraco.ModelsBuilder.Embedded/Compose/DisabledModelsBuilderComponent.cs diff --git a/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ModelsBuilderBackOfficeController.cs b/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ModelsBuilderDashboardController.cs similarity index 95% rename from src/Umbraco.ModelsBuilder.Embedded/BackOffice/ModelsBuilderBackOfficeController.cs rename to src/Umbraco.ModelsBuilder.Embedded/BackOffice/ModelsBuilderDashboardController.cs index 0842c3586a..44260d6719 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ModelsBuilderBackOfficeController.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ModelsBuilderDashboardController.cs @@ -20,7 +20,7 @@ namespace Umbraco.ModelsBuilder.Embedded.BackOffice /// global WebApi formatters being changed since this is always forced to only return Angular JSON Specific formats. /// [UmbracoApplicationAuthorize(Core.Constants.Applications.Settings)] - public class ModelsBuilderBackOfficeController : UmbracoAuthorizedJsonController + public class ModelsBuilderDashboardController : UmbracoAuthorizedJsonController { private readonly IModelsBuilderConfig _config; private readonly ModelsGenerator _modelGenerator; @@ -28,7 +28,7 @@ namespace Umbraco.ModelsBuilder.Embedded.BackOffice private readonly ModelsGenerationError _mbErrors; private readonly DashboardReport _dashboardReport; - public ModelsBuilderBackOfficeController(IModelsBuilderConfig config, ModelsGenerator modelsGenerator, OutOfDateModelsStatus outOfDateModels, ModelsGenerationError mbErrors) + public ModelsBuilderDashboardController(IModelsBuilderConfig config, ModelsGenerator modelsGenerator, OutOfDateModelsStatus outOfDateModels, ModelsGenerationError mbErrors) { //_umbracoServices = umbracoServices; _config = config; diff --git a/src/Umbraco.ModelsBuilder.Embedded/Compose/DisableModelsBuilderManifestFilter.cs b/src/Umbraco.ModelsBuilder.Embedded/Compose/DisableModelsBuilderManifestFilter.cs new file mode 100644 index 0000000000..617c5ced9b --- /dev/null +++ b/src/Umbraco.ModelsBuilder.Embedded/Compose/DisableModelsBuilderManifestFilter.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using Umbraco.Core.Manifest; + +namespace Umbraco.ModelsBuilder.Embedded.Compose +{ + /// + /// Removes the built in embedded models builder manifest from being loaded + /// + internal class DisableModelsBuilderManifestFilter : IManifestFilter + { + public void Filter(List manifests) + { + manifests.RemoveAll(x => x.Source.EndsWith("App_Plugins\\UmbModelsBuilder\\package.manifest", StringComparison.InvariantCultureIgnoreCase)); + } + } +} 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 index 401a5a8931..be73393994 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComponent.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComponent.cs @@ -16,7 +16,8 @@ using Umbraco.Web.Mvc; namespace Umbraco.ModelsBuilder.Embedded.Compose { - public class ModelsBuilderComponent : IComponent + + internal class ModelsBuilderComponent : IComponent { private readonly IModelsBuilderConfig _config; @@ -71,7 +72,7 @@ namespace Umbraco.ModelsBuilder.Embedded.Compose 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()); + umbracoUrls["modelsBuilderBaseUrl"] = urlHelper.GetUmbracoApiServiceBaseUrl(controller => controller.BuildModels()); umbracoPlugins["modelsBuilder"] = GetModelsBuilderSettings(); }; } diff --git a/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComposer.cs b/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComposer.cs index 8b3f8db534..cf70adb6fe 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComposer.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComposer.cs @@ -1,23 +1,33 @@ 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.PublishedCache.NuCache; +using Umbraco.Web.Features; namespace Umbraco.ModelsBuilder.Embedded.Compose { + + [ComposeBefore(typeof(NuCacheComposer))] [RuntimeLevel(MinLevel = RuntimeLevel.Run)] - public sealed class ModelsBuilderComposer : ComponentComposer, ICoreComposer + public sealed class ModelsBuilderComposer : ICoreComposer { - public override void Compose(Composition composition) + public void Compose(Composition composition) { - base.Compose(composition); + var isLegacyModelsBuilderInstalled = IsLegacyModelsBuilderInstalled(); - + if (isLegacyModelsBuilderInstalled) + { + ComposeForLegacyModelsBuilder(composition); + return; + } + composition.Components().Append(); composition.Register(Lifetime.Singleton); composition.Configs.Add(() => new ModelsBuilderConfig()); composition.RegisterUnique(); @@ -31,6 +41,28 @@ namespace Umbraco.ModelsBuilder.Embedded.Compose 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 legacy ModelsBuilder was detected."); + composition.Components().Append(); + composition.ManifestFilters().Append(); + } + private void ComposeForDefaultModelsFactory(Composition composition) { composition.RegisterUnique(factory => diff --git a/src/Umbraco.ModelsBuilder.Embedded/Umbraco.ModelsBuilder.Embedded.csproj b/src/Umbraco.ModelsBuilder.Embedded/Umbraco.ModelsBuilder.Embedded.csproj index a9a925d9cf..4c22566c05 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/Umbraco.ModelsBuilder.Embedded.csproj +++ b/src/Umbraco.ModelsBuilder.Embedded/Umbraco.ModelsBuilder.Embedded.csproj @@ -55,6 +55,8 @@ + + @@ -71,7 +73,7 @@ - + diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 99c1c994d9..ceec3ee927 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -118,6 +118,10 @@ Umbraco.Examine {07FBC26B-2927-4A22-8D96-D644C667FECC} + + {52ac0ba8-a60e-4e36-897b-e8b97a54ed1c} + Umbraco.ModelsBuilder.Embedded + {651e1350-91b6-44b7-bd60-7207006d7003} Umbraco.Web diff --git a/src/Umbraco.Web/Properties/AssemblyInfo.cs b/src/Umbraco.Web/Properties/AssemblyInfo.cs index 9f5abb99b5..ce2cbf0282 100644 --- a/src/Umbraco.Web/Properties/AssemblyInfo.cs +++ b/src/Umbraco.Web/Properties/AssemblyInfo.cs @@ -12,7 +12,7 @@ using System.Runtime.InteropServices; // Umbraco Cms [assembly: InternalsVisibleTo("Umbraco.Web.UI")] - +[assembly: InternalsVisibleTo("Umbraco.ModelsBuilder.Embedded")] [assembly: InternalsVisibleTo("Umbraco.Tests")] [assembly: InternalsVisibleTo("Umbraco.Tests.Benchmarks")] From 2c9939e51699d0c32bcaad9e4fc9cd27da8e6da1 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 29 Oct 2019 11:02:18 +1100 Subject: [PATCH 25/49] restores the MB enable flag --- build/NuSpecs/tools/Web.config.install.xdt | 1 + .../BackOffice/ContentTypeModelValidator.cs | 70 +----------------- .../ContentTypeModelValidatorBase.cs | 71 +++++++++++++++++++ .../BackOffice/DashboardReport.cs | 3 + .../BackOffice/MediaTypeModelValidator.cs | 17 +++++ .../BackOffice/MemberTypeModelValidator.cs | 17 +++++ .../ModelsBuilderDashboardController.cs | 2 +- .../Compose/ModelsBuilderComponent.cs | 5 +- .../Configuration/IModelsBuilderConfig.cs | 1 + .../Configuration/ModelsBuilderConfig.cs | 18 +++++ .../Configuration/ModelsMode.cs | 2 +- .../Umbraco.ModelsBuilder.Embedded.csproj | 3 + src/Umbraco.Web.UI/web.Template.config | 1 + src/Umbraco.Web/Editors/IEditorValidator.cs | 4 +- 14 files changed, 141 insertions(+), 74 deletions(-) create mode 100644 src/Umbraco.ModelsBuilder.Embedded/BackOffice/ContentTypeModelValidatorBase.cs create mode 100644 src/Umbraco.ModelsBuilder.Embedded/BackOffice/MediaTypeModelValidator.cs create mode 100644 src/Umbraco.ModelsBuilder.Embedded/BackOffice/MemberTypeModelValidator.cs diff --git a/build/NuSpecs/tools/Web.config.install.xdt b/build/NuSpecs/tools/Web.config.install.xdt index 6579717a60..2b79f95c70 100644 --- a/build/NuSpecs/tools/Web.config.install.xdt +++ b/build/NuSpecs/tools/Web.config.install.xdt @@ -14,6 +14,7 @@ + diff --git a/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ContentTypeModelValidator.cs b/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ContentTypeModelValidator.cs index f351721e56..1fdb64c62a 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ContentTypeModelValidator.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ContentTypeModelValidator.cs @@ -1,9 +1,4 @@ -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Linq; -using Umbraco.Core; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Web.Editors; +using Umbraco.ModelsBuilder.Embedded.Configuration; using Umbraco.Web.Models.ContentEditing; namespace Umbraco.ModelsBuilder.Embedded.BackOffice @@ -12,70 +7,11 @@ 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 - { } - - /// - /// Used to validate the aliases for the content type when MB is enabled to ensure that - /// no illegal aliases are used - /// - public class MediaTypeModelValidator : ContentTypeModelValidatorBase - { } - - /// - /// Used to validate the aliases for the content type when MB is enabled to ensure that - /// no illegal aliases are used - /// - public class MemberTypeModelValidator : ContentTypeModelValidatorBase - { } - - public abstract class ContentTypeModelValidatorBase : EditorValidator - where TModel : ContentTypeSave - where TProperty : PropertyTypeBasic { - protected override IEnumerable Validate(TModel model) + public ContentTypeModelValidator(IModelsBuilderConfig config) : base(config) { - 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.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 index e4c45aaf3e..25ddc838e8 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/BackOffice/DashboardReport.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/BackOffice/DashboardReport.cs @@ -24,6 +24,9 @@ namespace Umbraco.ModelsBuilder.Embedded.BackOffice 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: "); 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 index 44260d6719..1d9de265e9 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ModelsBuilderDashboardController.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ModelsBuilderDashboardController.cs @@ -97,7 +97,7 @@ namespace Umbraco.ModelsBuilder.Embedded.BackOffice { return new Dashboard { - Enable = true, + Enable = _config.Enable, Text = _dashboardReport.Text(), CanGenerate = _dashboardReport.CanGenerate(), OutOfDateModels = _dashboardReport.AreModelsOutOfDate(), diff --git a/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComponent.cs b/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComponent.cs index be73393994..0e41c9ac62 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComponent.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComponent.cs @@ -39,7 +39,8 @@ namespace Umbraco.ModelsBuilder.Embedded.Compose ContentModelBinder.ModelBindingException += ContentModelBinder_ModelBindingException; - FileService.SavingTemplate += FileService_SavingTemplate; + if (_config.Enable) + FileService.SavingTemplate += FileService_SavingTemplate; if (_config.ModelsMode.IsLiveNotPure()) _liveModelsProvider.Install(); @@ -81,7 +82,7 @@ namespace Umbraco.ModelsBuilder.Embedded.Compose { var settings = new Dictionary { - {"enabled", true} + {"enabled", _config.Enable} }; return settings; diff --git a/src/Umbraco.ModelsBuilder.Embedded/Configuration/IModelsBuilderConfig.cs b/src/Umbraco.ModelsBuilder.Embedded/Configuration/IModelsBuilderConfig.cs index d8a81f0458..7e96aec60e 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/Configuration/IModelsBuilderConfig.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Configuration/IModelsBuilderConfig.cs @@ -2,6 +2,7 @@ { public interface IModelsBuilderConfig { + bool Enable { get; } bool AcceptUnsafeModelsDirectory { get; } int DebugLevel { get; } bool EnableFactory { get; } diff --git a/src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsBuilderConfig.cs b/src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsBuilderConfig.cs index 881c4f97b8..c6bccdcf87 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsBuilderConfig.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsBuilderConfig.cs @@ -22,11 +22,18 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration { const string prefix = "Umbraco.ModelsBuilder."; + // giant kill switch, default: false + // must be explicitely set to true for anything else to happen + Enable = ConfigurationManager.AppSettings[prefix + "Enable"] == "true"; + // ensure defaults are initialized for tests ModelsNamespace = DefaultModelsNamespace; ModelsDirectory = IOHelper.MapPath(DefaultModelsDirectory); DebugLevel = 0; + // stop here, everything is false + if (!Enable) return; + // mode var modelsMode = ConfigurationManager.AppSettings[prefix + "ModelsMode"]; if (!string.IsNullOrWhiteSpace(modelsMode)) @@ -94,6 +101,7 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration /// Initializes a new instance of the class. ///
public ModelsBuilderConfig( + bool enable = false, ModelsMode modelsMode = ModelsMode.Nothing, string modelsNamespace = null, bool enableFactory = true, @@ -102,6 +110,7 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration bool acceptUnsafeModelsDirectory = false, int debugLevel = 0) { + Enable = enable; ModelsMode = modelsMode; ModelsNamespace = string.IsNullOrWhiteSpace(modelsNamespace) ? DefaultModelsNamespace : modelsNamespace; @@ -143,6 +152,15 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration throw new ConfigurationErrorsException($"Invalid models directory \"{config}\"."); } + /// + /// Gets a value indicating whether the whole models experience is enabled. + /// + /// + /// If this is false then absolutely nothing happens. + /// Default value is false which means that unless we have this setting, nothing happens. + /// + public bool Enable { get; } + /// /// Gets the models mode. /// diff --git a/src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsMode.cs b/src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsMode.cs index 145508991a..e0286fdab1 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsMode.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsMode.cs @@ -8,7 +8,7 @@ /// /// Do not generate models. /// - Nothing = 0, // default value //TODO: This doesn't make sense since we cannot actualy disable MB since Umbraco would die + Nothing = 0, // default value /// /// Generate models in memory. diff --git a/src/Umbraco.ModelsBuilder.Embedded/Umbraco.ModelsBuilder.Embedded.csproj b/src/Umbraco.ModelsBuilder.Embedded/Umbraco.ModelsBuilder.Embedded.csproj index 4c22566c05..c09703ecfb 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/Umbraco.ModelsBuilder.Embedded.csproj +++ b/src/Umbraco.ModelsBuilder.Embedded/Umbraco.ModelsBuilder.Embedded.csproj @@ -50,6 +50,9 @@ Properties\SolutionInfo.cs + + + diff --git a/src/Umbraco.Web.UI/web.Template.config b/src/Umbraco.Web.UI/web.Template.config index 8759fc6459..f0abbfde52 100644 --- a/src/Umbraco.Web.UI/web.Template.config +++ b/src/Umbraco.Web.UI/web.Template.config @@ -47,6 +47,7 @@ + diff --git a/src/Umbraco.Web/Editors/IEditorValidator.cs b/src/Umbraco.Web/Editors/IEditorValidator.cs index d469d9d9eb..2d655e3506 100644 --- a/src/Umbraco.Web/Editors/IEditorValidator.cs +++ b/src/Umbraco.Web/Editors/IEditorValidator.cs @@ -14,9 +14,7 @@ namespace Umbraco.Web.Editors // initialized with all IEditorValidator instances // // validation is used exclusively in ContentTypeControllerBase - // the whole thing is internal at the moment, never released - // and, there are no IEditorValidator implementation in Core - // so... this all mechanism is basically useless + // currently the only implementations are for Models Builder. /// /// Provides a general object validator. From 0aa8711988d7040fb5ea73acb014323e3d53423d Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 29 Oct 2019 11:38:06 +1100 Subject: [PATCH 26/49] Ensures that the MB version is added to the models hash to ensure that if the MB version changes, the purelive models are regenerated --- src/Umbraco.ModelsBuilder.Embedded/Building/Builder.cs | 2 +- .../Building/TypeModelHasher.cs | 3 +++ src/Umbraco.ModelsBuilder.Embedded/UmbracoServices.cs | 4 ++++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.ModelsBuilder.Embedded/Building/Builder.cs b/src/Umbraco.ModelsBuilder.Embedded/Building/Builder.cs index 82b1b0adec..ffd56d4312 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/Building/Builder.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Building/Builder.cs @@ -32,7 +32,7 @@ namespace Umbraco.ModelsBuilder.Embedded.Building "Umbraco.Core.Models", "Umbraco.Core.Models.PublishedContent", "Umbraco.Web", - "Umbraco.ModelsBuilder" + "Umbraco.ModelsBuilder.Embedded" }; /// diff --git a/src/Umbraco.ModelsBuilder.Embedded/Building/TypeModelHasher.cs b/src/Umbraco.ModelsBuilder.Embedded/Building/TypeModelHasher.cs index ff2893d097..2f14bec875 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/Building/TypeModelHasher.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Building/TypeModelHasher.cs @@ -35,6 +35,9 @@ namespace Umbraco.ModelsBuilder.Embedded.Building } } + // 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/UmbracoServices.cs b/src/Umbraco.ModelsBuilder.Embedded/UmbracoServices.cs index cd9f4802f2..5ede5f45e9 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/UmbracoServices.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/UmbracoServices.cs @@ -32,6 +32,10 @@ namespace Umbraco.ModelsBuilder.Embedded { 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())); From 33cd7f6a982f4c76ab186d5fb6b4c43000d855ea Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 29 Oct 2019 11:55:16 +1100 Subject: [PATCH 27/49] oops, added the internal visible to attribute to the wrong file --- src/SolutionInfo.cs | 3 --- src/Umbraco.ModelsBuilder.Embedded/Properties/AssemblyInfo.cs | 3 +++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index 93921e07a2..bf3a271d32 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -1,6 +1,5 @@ using System.Reflection; using System.Resources; -using System.Runtime.CompilerServices; [assembly: AssemblyCompany("Umbraco")] [assembly: AssemblyCopyright("Copyright © Umbraco 2019")] @@ -21,5 +20,3 @@ using System.Runtime.CompilerServices; // these are FYI and changed automatically [assembly: AssemblyFileVersion("8.3.0")] [assembly: AssemblyInformationalVersion("8.3.0")] - -[assembly: InternalsVisibleTo("Umbraco.Tests")] diff --git a/src/Umbraco.ModelsBuilder.Embedded/Properties/AssemblyInfo.cs b/src/Umbraco.ModelsBuilder.Embedded/Properties/AssemblyInfo.cs index b576807bb7..68c149adde 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/Properties/AssemblyInfo.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Properties/AssemblyInfo.cs @@ -1,4 +1,5 @@ using System.Reflection; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; [assembly: AssemblyTitle("Umbraco.ModelsBuilder")] @@ -8,3 +9,5 @@ using System.Runtime.InteropServices; [assembly: ComVisible(false)] [assembly: Guid("52ac0ba8-a60e-4e36-897b-e8b97a54ed1c")] + +[assembly: InternalsVisibleTo("Umbraco.Tests")] From df2a65424eb6e21ce34d5b200fa12bb0828f758b Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 29 Oct 2019 12:16:56 +1100 Subject: [PATCH 28/49] notes --- src/Umbraco.ModelsBuilder.Embedded/LiveModelsProviderModule.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.ModelsBuilder.Embedded/LiveModelsProviderModule.cs b/src/Umbraco.ModelsBuilder.Embedded/LiveModelsProviderModule.cs index 37b39124c3..678ff241b0 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/LiveModelsProviderModule.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/LiveModelsProviderModule.cs @@ -27,7 +27,7 @@ namespace Umbraco.ModelsBuilder.Embedded // 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 + _liveModelsProvider = Current.Factory.TryGetInstance(); // will be null in upgrade mode or if embedded MB is disabled if (_liveModelsProvider?.IsEnabled ?? false) _liveModelsProvider.GenerateModelsIfRequested(sender, e); From 63cf79a758fe739d2e97af918262fd9bf29fb504 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 29 Oct 2019 11:01:44 +0100 Subject: [PATCH 29/49] AB3348 - Clean up unused usings, removed unused method... --- .../BackOffice/DashboardReport.cs | 1 - .../ModelsBuilderBackOfficeController.cs | 3 --- src/Umbraco.ModelsBuilder/Building/Builder.cs | 8 +------- .../Building/ModelsGenerator.cs | 4 +--- .../Building/TextBuilder.cs | 3 +-- .../ConfigsExtensions.cs | 2 +- .../Configuration/ModelsBuilderConfig.cs | 1 - .../LiveModelsProvider.cs | 3 --- .../ModelsGenerationError.cs | 1 - .../OutOfDateModelsStatus.cs | 1 - .../PublishedModelUtility.cs | 1 - .../PureLiveModelFactory.cs | 2 -- .../ReferencedAssemblies.cs | 20 ------------------- src/Umbraco.ModelsBuilder/UmbracoServices.cs | 2 -- 14 files changed, 4 insertions(+), 48 deletions(-) diff --git a/src/Umbraco.ModelsBuilder/BackOffice/DashboardReport.cs b/src/Umbraco.ModelsBuilder/BackOffice/DashboardReport.cs index 64fb217fce..a43ebdb927 100644 --- a/src/Umbraco.ModelsBuilder/BackOffice/DashboardReport.cs +++ b/src/Umbraco.ModelsBuilder/BackOffice/DashboardReport.cs @@ -1,5 +1,4 @@ using System.Text; -using Umbraco.Core.Composing; using Umbraco.ModelsBuilder.Configuration; namespace Umbraco.ModelsBuilder.BackOffice diff --git a/src/Umbraco.ModelsBuilder/BackOffice/ModelsBuilderBackOfficeController.cs b/src/Umbraco.ModelsBuilder/BackOffice/ModelsBuilderBackOfficeController.cs index e4efca70bd..3963ad09f2 100644 --- a/src/Umbraco.ModelsBuilder/BackOffice/ModelsBuilderBackOfficeController.cs +++ b/src/Umbraco.ModelsBuilder/BackOffice/ModelsBuilderBackOfficeController.cs @@ -1,10 +1,7 @@ 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.Exceptions; using Umbraco.ModelsBuilder.Building; diff --git a/src/Umbraco.ModelsBuilder/Building/Builder.cs b/src/Umbraco.ModelsBuilder/Building/Builder.cs index bba19c3b9f..f7917cbf22 100644 --- a/src/Umbraco.ModelsBuilder/Building/Builder.cs +++ b/src/Umbraco.ModelsBuilder/Building/Builder.cs @@ -1,12 +1,6 @@ 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.Composing; -using Umbraco.Core.Configuration; using Umbraco.ModelsBuilder.Configuration; namespace Umbraco.ModelsBuilder.Building @@ -71,8 +65,8 @@ namespace Umbraco.ModelsBuilder.Building /// Initializes a new instance of the class with a list of models to generate, /// the result of code parsing, and a models namespace. /// + /// /// The list of models to generate. - /// The models namespace. protected Builder(IModelsBuilderConfig config, IList typeModels) { _typeModels = typeModels ?? throw new ArgumentNullException(nameof(typeModels)); diff --git a/src/Umbraco.ModelsBuilder/Building/ModelsGenerator.cs b/src/Umbraco.ModelsBuilder/Building/ModelsGenerator.cs index 6f6872e8ff..9b1c36b42c 100644 --- a/src/Umbraco.ModelsBuilder/Building/ModelsGenerator.cs +++ b/src/Umbraco.ModelsBuilder/Building/ModelsGenerator.cs @@ -1,7 +1,5 @@ -using System; -using System.IO; +using System.IO; using System.Text; -using System.Web; using Umbraco.ModelsBuilder.Configuration; namespace Umbraco.ModelsBuilder.Building diff --git a/src/Umbraco.ModelsBuilder/Building/TextBuilder.cs b/src/Umbraco.ModelsBuilder/Building/TextBuilder.cs index 56349f16a8..317a9f27d7 100644 --- a/src/Umbraco.ModelsBuilder/Building/TextBuilder.cs +++ b/src/Umbraco.ModelsBuilder/Building/TextBuilder.cs @@ -3,8 +3,6 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Text.RegularExpressions; -using Umbraco.Core.Composing; -using Umbraco.Core.Configuration; using Umbraco.ModelsBuilder.Configuration; namespace Umbraco.ModelsBuilder.Building @@ -18,6 +16,7 @@ namespace Umbraco.ModelsBuilder.Building /// 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. public TextBuilder(IModelsBuilderConfig config, IList typeModels) : base(config, typeModels) diff --git a/src/Umbraco.ModelsBuilder/ConfigsExtensions.cs b/src/Umbraco.ModelsBuilder/ConfigsExtensions.cs index 09314bc3f3..b3605fc541 100644 --- a/src/Umbraco.ModelsBuilder/ConfigsExtensions.cs +++ b/src/Umbraco.ModelsBuilder/ConfigsExtensions.cs @@ -4,7 +4,7 @@ using Umbraco.ModelsBuilder.Configuration; namespace Umbraco.ModelsBuilder { /// - /// Provides extension methods for the class. + /// Provides extension methods for the class. /// public static class ConfigsExtensions { diff --git a/src/Umbraco.ModelsBuilder/Configuration/ModelsBuilderConfig.cs b/src/Umbraco.ModelsBuilder/Configuration/ModelsBuilderConfig.cs index 48a4423054..e67dcc9157 100644 --- a/src/Umbraco.ModelsBuilder/Configuration/ModelsBuilderConfig.cs +++ b/src/Umbraco.ModelsBuilder/Configuration/ModelsBuilderConfig.cs @@ -2,7 +2,6 @@ using System.Configuration; using System.IO; using System.Web.Configuration; -using System.Web.Hosting; using Umbraco.Core; using Umbraco.Core.IO; diff --git a/src/Umbraco.ModelsBuilder/LiveModelsProvider.cs b/src/Umbraco.ModelsBuilder/LiveModelsProvider.cs index 0dfe503fce..14a38a3291 100644 --- a/src/Umbraco.ModelsBuilder/LiveModelsProvider.cs +++ b/src/Umbraco.ModelsBuilder/LiveModelsProvider.cs @@ -1,9 +1,6 @@ using System; using System.Threading; -using System.Web; using System.Web.Hosting; -using Umbraco.Core.Exceptions; -using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.ModelsBuilder.Building; using Umbraco.ModelsBuilder.Configuration; diff --git a/src/Umbraco.ModelsBuilder/ModelsGenerationError.cs b/src/Umbraco.ModelsBuilder/ModelsGenerationError.cs index 8264fecc87..8585b2094a 100644 --- a/src/Umbraco.ModelsBuilder/ModelsGenerationError.cs +++ b/src/Umbraco.ModelsBuilder/ModelsGenerationError.cs @@ -1,7 +1,6 @@ using System; using System.IO; using System.Text; -using Umbraco.Core.Composing; using Umbraco.ModelsBuilder.Configuration; namespace Umbraco.ModelsBuilder diff --git a/src/Umbraco.ModelsBuilder/OutOfDateModelsStatus.cs b/src/Umbraco.ModelsBuilder/OutOfDateModelsStatus.cs index 3b43ed771e..a394b2611a 100644 --- a/src/Umbraco.ModelsBuilder/OutOfDateModelsStatus.cs +++ b/src/Umbraco.ModelsBuilder/OutOfDateModelsStatus.cs @@ -1,5 +1,4 @@ using System.IO; -using Umbraco.Core.Composing; using Umbraco.ModelsBuilder.Configuration; using Umbraco.Web.Cache; diff --git a/src/Umbraco.ModelsBuilder/PublishedModelUtility.cs b/src/Umbraco.ModelsBuilder/PublishedModelUtility.cs index 44af532a0b..3400b2dd7f 100644 --- a/src/Umbraco.ModelsBuilder/PublishedModelUtility.cs +++ b/src/Umbraco.ModelsBuilder/PublishedModelUtility.cs @@ -1,5 +1,4 @@ using System; -using System.ComponentModel; using System.Linq; using System.Linq.Expressions; using Umbraco.Web.Composing; diff --git a/src/Umbraco.ModelsBuilder/PureLiveModelFactory.cs b/src/Umbraco.ModelsBuilder/PureLiveModelFactory.cs index eb82b4323f..c050ae291a 100644 --- a/src/Umbraco.ModelsBuilder/PureLiveModelFactory.cs +++ b/src/Umbraco.ModelsBuilder/PureLiveModelFactory.cs @@ -13,10 +13,8 @@ using System.Web.Compilation; using System.Web.Hosting; using System.Web.WebPages.Razor; using Umbraco.Core; -using Umbraco.Core.Composing; using Umbraco.Core.Logging; using Umbraco.Core.Models.PublishedContent; -using Umbraco.Web.Cache; using Umbraco.ModelsBuilder.Building; using Umbraco.ModelsBuilder.Configuration; using File = System.IO.File; diff --git a/src/Umbraco.ModelsBuilder/ReferencedAssemblies.cs b/src/Umbraco.ModelsBuilder/ReferencedAssemblies.cs index e0a20eb9d6..e1e503d372 100644 --- a/src/Umbraco.ModelsBuilder/ReferencedAssemblies.cs +++ b/src/Umbraco.ModelsBuilder/ReferencedAssemblies.cs @@ -142,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) { diff --git a/src/Umbraco.ModelsBuilder/UmbracoServices.cs b/src/Umbraco.ModelsBuilder/UmbracoServices.cs index 725e5b18c4..f21f75b5b1 100644 --- a/src/Umbraco.ModelsBuilder/UmbracoServices.cs +++ b/src/Umbraco.ModelsBuilder/UmbracoServices.cs @@ -2,14 +2,12 @@ using System.Collections.Generic; using System.Linq; using Umbraco.Core; -using Umbraco.Core.Composing; 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; namespace Umbraco.ModelsBuilder { From 4862ae03aea9758e9fade2b46e55e760336ff078 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 29 Oct 2019 11:19:32 +0100 Subject: [PATCH 30/49] AB3289 - Fixed broken tests --- .../ModelsBuilder/BuilderTests.cs | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs b/src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs index fd381178cc..e1c3ecc891 100644 --- a/src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs +++ b/src/Umbraco.Tests/ModelsBuilder/BuilderTests.cs @@ -67,7 +67,7 @@ using System.Web; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Web; -using Umbraco.ModelsBuilder; +using Umbraco.ModelsBuilder.Embedded; namespace Umbraco.Web.PublishedModels { @@ -182,7 +182,7 @@ using System.Web; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Web; -using Umbraco.ModelsBuilder; +using Umbraco.ModelsBuilder.Embedded; namespace Umbraco.Web.PublishedModels { @@ -223,7 +223,7 @@ namespace Umbraco.Web.PublishedModels [Test] public void GenerateAmbiguous() { - // NOTE: since + // NOTE: since var type1 = new TypeModel { @@ -277,8 +277,8 @@ namespace Umbraco.Web.PublishedModels [TestCase("int", typeof(int))] [TestCase("global::System.Collections.Generic.IEnumerable", typeof(IEnumerable))] - [TestCase("global::Umbraco.ModelsBuilder.Tests.BuilderTestsClass1", typeof(BuilderTestsClass1))] - [TestCase("global::Umbraco.ModelsBuilder.Tests.BuilderTests.Class1", typeof(Class1))] + [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 @@ -293,15 +293,15 @@ namespace Umbraco.Web.PublishedModels [TestCase("int", typeof(int))] [TestCase("global::System.Collections.Generic.IEnumerable", typeof(IEnumerable))] - [TestCase("global::Umbraco.ModelsBuilder.Tests.BuilderTestsClass1", typeof(BuilderTestsClass1))] - [TestCase("global::Umbraco.ModelsBuilder.Tests.BuilderTests.Class1", typeof(Class1))] + [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.ModelsBuilder.Tests"); + builder.Using.Add("Umbraco.Tests.ModelsBuilder"); builder.ModelsNamespaceForTests = "ModelsNamespace"; var sb = new StringBuilder(); builder.WriteClrType(sb, input); @@ -313,7 +313,7 @@ namespace Umbraco.Web.PublishedModels { var builder = new TextBuilder(); builder.Using.Add("System.Text"); - builder.ModelsNamespaceForTests = "Umbraco.ModelsBuilder.Tests.Models"; + builder.ModelsNamespaceForTests = "Umbraco.Tests.ModelsBuilder.Models"; var sb = new StringBuilder(); builder.WriteClrType(sb, typeof(StringBuilder)); @@ -327,7 +327,7 @@ namespace Umbraco.Web.PublishedModels public void WriteClrTypeAnother_WithoutUsing() { var builder = new TextBuilder(); - builder.ModelsNamespaceForTests = "Umbraco.ModelsBuilder.Tests.Models"; + builder.ModelsNamespaceForTests = "Umbraco.Tests.ModelsBuilder.Models"; var sb = new StringBuilder(); builder.WriteClrType(sb, typeof(StringBuilder)); Assert.AreEqual("global::System.Text.StringBuilder", sb.ToString()); @@ -338,7 +338,7 @@ namespace Umbraco.Web.PublishedModels { var builder = new TextBuilder(); builder.Using.Add("System.Text"); - builder.Using.Add("Umbraco.ModelsBuilder.Tests"); + builder.Using.Add("Umbraco.Tests.ModelsBuilder"); builder.ModelsNamespaceForTests = "SomeRandomNamespace"; var sb = new StringBuilder(); builder.WriteClrType(sb, typeof(global::System.Text.ASCIIEncoding)); @@ -354,7 +354,7 @@ namespace Umbraco.Web.PublishedModels { var builder = new TextBuilder(); builder.Using.Add("System.Text"); - builder.Using.Add("Umbraco.ModelsBuilder.Tests"); + builder.Using.Add("Umbraco.Tests.ModelsBuilder"); builder.ModelsNamespaceForTests = "SomeBorkedNamespace"; var sb = new StringBuilder(); builder.WriteClrType(sb, typeof(global::System.Text.ASCIIEncoding)); @@ -370,7 +370,7 @@ namespace Umbraco.Web.PublishedModels { var builder = new TextBuilder(); builder.Using.Add("System.Text"); - builder.Using.Add("Umbraco.ModelsBuilder.Tests"); + builder.Using.Add("Umbraco.Tests.ModelsBuilder"); builder.ModelsNamespaceForTests = "SomeRandomNamespace"; var sb = new StringBuilder(); builder.WriteClrType(sb, typeof(ASCIIEncoding)); @@ -378,7 +378,7 @@ namespace Umbraco.Web.PublishedModels // note - these assertions differ from the original tests in MB because in the embedded version, the result of Builder.IsAmbiguousSymbol is always true // which means global:: syntax will be applied to most things - Assert.AreEqual("global::Umbraco.ModelsBuilder.Tests.ASCIIEncoding", sb.ToString()); + Assert.AreEqual("global::Umbraco.Tests.ModelsBuilder.ASCIIEncoding", sb.ToString()); } [Test] @@ -386,15 +386,15 @@ namespace Umbraco.Web.PublishedModels { var builder = new TextBuilder(); builder.Using.Add("System.Text"); - builder.Using.Add("Umbraco.ModelsBuilder.Tests"); - builder.ModelsNamespaceForTests = "Umbraco.ModelsBuilder.Tests.Models"; + 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.ModelsBuilder.Tests.ASCIIEncoding", sb.ToString()); + Assert.AreEqual("global::Umbraco.Tests.ModelsBuilder.ASCIIEncoding", sb.ToString()); } [Test] @@ -402,7 +402,7 @@ namespace Umbraco.Web.PublishedModels { var builder = new TextBuilder(); builder.Using.Add("System.Text"); - builder.Using.Add("Umbraco.ModelsBuilder.Tests"); + builder.Using.Add("Umbraco.Tests.ModelsBuilder"); builder.ModelsNamespaceForTests = "SomeRandomNamespace"; var sb = new StringBuilder(); builder.WriteClrType(sb, typeof(ASCIIEncoding.Nested)); @@ -410,7 +410,7 @@ namespace Umbraco.Web.PublishedModels // note - these assertions differ from the original tests in MB because in the embedded version, the result of Builder.IsAmbiguousSymbol is always true // which means global:: syntax will be applied to most things - Assert.AreEqual("global::Umbraco.ModelsBuilder.Tests.ASCIIEncoding.Nested", sb.ToString()); + Assert.AreEqual("global::Umbraco.Tests.ModelsBuilder.ASCIIEncoding.Nested", sb.ToString()); } public class Class1 { } From 17e28b6840813aff12e2347afc921dd78172c10f Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 29 Oct 2019 14:28:54 +0100 Subject: [PATCH 31/49] AB3389 - Move the stuff from App_Plugins/ModelsBuilder into Umbraco.Web.UI.Client and handle dashboard as code. --- build/NuSpecs/UmbracoCms.nuspec | 1 - .../DisableModelsBuilderManifestFilter.cs | 17 ----------------- .../Compose/ModelsBuilderComposer.cs | 5 +++-- .../ModelsBuilderDashboard.cs | 19 +++++++++++++++++++ .../Umbraco.ModelsBuilder.Embedded.csproj | 2 +- .../modelsbuildermanagement.resource.js} | 4 ++-- .../services/contenttypehelper.service.js | 4 ++-- .../modelsbuildermanagement.controller.js} | 12 ++++++------ .../settings/modelsbuildermanagement.html} | 2 +- .../UmbModelsBuilder/package.manifest | 18 ------------------ src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 8 +------- 11 files changed, 35 insertions(+), 57 deletions(-) delete mode 100644 src/Umbraco.ModelsBuilder.Embedded/Compose/DisableModelsBuilderManifestFilter.cs create mode 100644 src/Umbraco.ModelsBuilder.Embedded/ModelsBuilderDashboard.cs rename src/{Umbraco.Web.UI/App_Plugins/UmbModelsBuilder/modelsbuilder.resource.js => Umbraco.Web.UI.Client/src/common/resources/modelsbuildermanagement.resource.js} (80%) rename src/{Umbraco.Web.UI/App_Plugins/UmbModelsBuilder/modelsbuilder.controller.js => Umbraco.Web.UI.Client/src/views/dashboard/settings/modelsbuildermanagement.controller.js} (59%) rename src/{Umbraco.Web.UI/App_Plugins/UmbModelsBuilder/modelsbuilder.html => Umbraco.Web.UI.Client/src/views/dashboard/settings/modelsbuildermanagement.html} (94%) delete mode 100644 src/Umbraco.Web.UI/App_Plugins/UmbModelsBuilder/package.manifest diff --git a/build/NuSpecs/UmbracoCms.nuspec b/build/NuSpecs/UmbracoCms.nuspec index d0bd4a2688..a6b06d9964 100644 --- a/build/NuSpecs/UmbracoCms.nuspec +++ b/build/NuSpecs/UmbracoCms.nuspec @@ -42,7 +42,6 @@ - diff --git a/src/Umbraco.ModelsBuilder.Embedded/Compose/DisableModelsBuilderManifestFilter.cs b/src/Umbraco.ModelsBuilder.Embedded/Compose/DisableModelsBuilderManifestFilter.cs deleted file mode 100644 index 617c5ced9b..0000000000 --- a/src/Umbraco.ModelsBuilder.Embedded/Compose/DisableModelsBuilderManifestFilter.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Collections.Generic; -using Umbraco.Core.Manifest; - -namespace Umbraco.ModelsBuilder.Embedded.Compose -{ - /// - /// Removes the built in embedded models builder manifest from being loaded - /// - internal class DisableModelsBuilderManifestFilter : IManifestFilter - { - public void Filter(List manifests) - { - manifests.RemoveAll(x => x.Source.EndsWith("App_Plugins\\UmbModelsBuilder\\package.manifest", StringComparison.InvariantCultureIgnoreCase)); - } - } -} diff --git a/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComposer.cs b/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComposer.cs index cf70adb6fe..c6924e3abe 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComposer.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComposer.cs @@ -6,6 +6,7 @@ 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; @@ -58,9 +59,9 @@ namespace Umbraco.ModelsBuilder.Embedded.Compose private void ComposeForLegacyModelsBuilder(Composition composition) { - composition.Logger.Info("ModelsBuilder.Embedded is disabled, the legacy ModelsBuilder was detected."); + composition.Logger.Info("ModelsBuilder.Embedded is disabled, the external ModelsBuilder was detected."); composition.Components().Append(); - composition.ManifestFilters().Append(); + composition.Dashboards().Remove(); } private void ComposeForDefaultModelsFactory(Composition composition) 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.Embedded/Umbraco.ModelsBuilder.Embedded.csproj b/src/Umbraco.ModelsBuilder.Embedded/Umbraco.ModelsBuilder.Embedded.csproj index c09703ecfb..75121a635d 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/Umbraco.ModelsBuilder.Embedded.csproj +++ b/src/Umbraco.ModelsBuilder.Embedded/Umbraco.ModelsBuilder.Embedded.csproj @@ -59,7 +59,6 @@ - @@ -68,6 +67,7 @@ + diff --git a/src/Umbraco.Web.UI/App_Plugins/UmbModelsBuilder/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/UmbModelsBuilder/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/UmbModelsBuilder/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/App_Plugins/UmbModelsBuilder/modelsbuilder.controller.js b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/modelsbuildermanagement.controller.js similarity index 59% rename from src/Umbraco.Web.UI/App_Plugins/UmbModelsBuilder/modelsbuilder.controller.js rename to src/Umbraco.Web.UI.Client/src/views/dashboard/settings/modelsbuildermanagement.controller.js index b0e0c303cf..423a20d864 100644 --- a/src/Umbraco.Web.UI/App_Plugins/UmbModelsBuilder/modelsbuilder.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/modelsbuildermanagement.controller.js @@ -1,4 +1,4 @@ -function modelsBuilderController($scope, $http, umbRequestHelper, modelsBuilderResource) { +function modelsBuilderManagementController($scope, $http, umbRequestHelper, modelsBuilderManagementResource) { var vm = this; @@ -9,8 +9,8 @@ function generate() { vm.generating = true; umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("modelsBuilderBaseUrl", "BuildModels")), - 'Failed to generate.') + $http.post(umbRequestHelper.getApiUrl("modelsBuilderBaseUrl", "BuildModels")), + 'Failed to generate.') .then(function (result) { vm.generating = false; vm.dashboard = result; @@ -19,7 +19,7 @@ function reload() { vm.loading = true; - modelsBuilderResource.getDashboard().then(function (result) { + modelsBuilderManagementResource.getDashboard().then(function (result) { vm.dashboard = result; vm.loading = false; }); @@ -27,7 +27,7 @@ function init() { vm.loading = true; - modelsBuilderResource.getDashboard().then(function (result) { + modelsBuilderManagementResource.getDashboard().then(function (result) { vm.dashboard = result; vm.loading = false; }); @@ -35,4 +35,4 @@ init(); } -angular.module("umbraco").controller("Umbraco.Dashboard.ModelsBuilderController", modelsBuilderController); \ No newline at end of file +angular.module("umbraco").controller("Umbraco.Dashboard.ModelsBuilderManagementController", modelsBuilderManagementController); diff --git a/src/Umbraco.Web.UI/App_Plugins/UmbModelsBuilder/modelsbuilder.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/modelsbuildermanagement.html similarity index 94% rename from src/Umbraco.Web.UI/App_Plugins/UmbModelsBuilder/modelsbuilder.html rename to src/Umbraco.Web.UI.Client/src/views/dashboard/settings/modelsbuildermanagement.html index 0c10c33e39..1ba86bf72b 100644 --- a/src/Umbraco.Web.UI/App_Plugins/UmbModelsBuilder/modelsbuilder.html +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/modelsbuildermanagement.html @@ -1,4 +1,4 @@ -
+
diff --git a/src/Umbraco.Web.UI/App_Plugins/UmbModelsBuilder/package.manifest b/src/Umbraco.Web.UI/App_Plugins/UmbModelsBuilder/package.manifest deleted file mode 100644 index 5e64177046..0000000000 --- a/src/Umbraco.Web.UI/App_Plugins/UmbModelsBuilder/package.manifest +++ /dev/null @@ -1,18 +0,0 @@ -{ - // array of files we want to inject into the application - "javascript": [ - "~/App_Plugins/ModelsBuilder/modelsbuilder.controller.js", - "~/App_Plugins/ModelsBuilder/modelsbuilder.resource.js" - ], - - // models builder dashboard - "dashboards": [ - { - "alias": "settingsModelsBuilder", - "name": "Models Builder", - "view": "/App_Plugins/ModelsBuilder/modelsbuilder.html", - "sections": [ "settings" ], - "weight": 40 - } - ] -} \ No newline at end of file diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index ceec3ee927..c556ffe773 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -80,6 +80,7 @@ + @@ -104,9 +105,6 @@ - - 8.1.0 - @@ -144,9 +142,6 @@ True Settings.settings - - - @@ -172,7 +167,6 @@ - ClientDependency.config Designer From 7d08040f34678c5662cdcc88bae9a3f467c37f3c Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 29 Oct 2019 14:29:58 +0100 Subject: [PATCH 32/49] AB3289 - Do not use the nuget package as default --- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index ceec3ee927..85300aceb2 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -104,9 +104,6 @@ - - 8.1.0 - From a00824484d46aba619d2e877fd8b031fba5de694 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 29 Oct 2019 14:43:43 +0100 Subject: [PATCH 33/49] Revert "AB3289 - Do not use the nuget package as default" This reverts commit 7d08040f --- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 85300aceb2..ceec3ee927 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -104,6 +104,9 @@ + + 8.1.0 + From 065e3c1ba3644fd1390853bec94eec4e2f11e43d Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 1 Nov 2019 11:33:15 +1100 Subject: [PATCH 34/49] removes the MB package --- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 555e11f4bc..0091d60545 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -81,7 +81,7 @@ - + @@ -111,9 +111,6 @@ runtime; build; native; contentfiles; analyzers all - - 8.1.0 - @@ -432,4 +429,4 @@ - + \ No newline at end of file From 41486c7ed9734b722e101681a48ef9dd82a9abbc Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 5 Nov 2019 09:35:57 +0100 Subject: [PATCH 35/49] AB3613 - Only enumerate the folder if it exists --- src/Umbraco.Web/Editors/TourController.cs | 36 +++++++++++++---------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/src/Umbraco.Web/Editors/TourController.cs b/src/Umbraco.Web/Editors/TourController.cs index 166e5a894d..25b4f7e9fc 100644 --- a/src/Umbraco.Web/Editors/TourController.cs +++ b/src/Umbraco.Web/Editors/TourController.cs @@ -51,29 +51,35 @@ namespace Umbraco.Web.Editors } //collect all tour files in packages - foreach (var plugin in Directory.EnumerateDirectories(IOHelper.MapPath(SystemDirectories.AppPlugins))) + var appPlugins = IOHelper.MapPath(SystemDirectories.AppPlugins); + if (Directory.Exists(appPlugins)) { - var pluginName = Path.GetFileName(plugin.TrimEnd('\\')); - var pluginFilters = _filters.Where(x => x.PluginName != null && x.PluginName.IsMatch(pluginName)).ToList(); - - //If there is any filter applied to match the plugin only (no file or tour alias) then ignore the plugin entirely - var isPluginFiltered = pluginFilters.Any(x => x.TourFileName == null && x.TourAlias == null); - if (isPluginFiltered) continue; - - //combine matched package filters with filters not specific to a package - var combinedFilters = nonPluginFilters.Concat(pluginFilters).ToList(); - - foreach (var backofficeDir in Directory.EnumerateDirectories(plugin, "backoffice")) + foreach (var plugin in Directory.EnumerateDirectories(appPlugins)) { - foreach (var tourDir in Directory.EnumerateDirectories(backofficeDir, "tours")) + var pluginName = Path.GetFileName(plugin.TrimEnd('\\')); + var pluginFilters = _filters.Where(x => x.PluginName != null && x.PluginName.IsMatch(pluginName)) + .ToList(); + + //If there is any filter applied to match the plugin only (no file or tour alias) then ignore the plugin entirely + var isPluginFiltered = pluginFilters.Any(x => x.TourFileName == null && x.TourAlias == null); + if (isPluginFiltered) continue; + + //combine matched package filters with filters not specific to a package + var combinedFilters = nonPluginFilters.Concat(pluginFilters).ToList(); + + foreach (var backofficeDir in Directory.EnumerateDirectories(plugin, "backoffice")) { - foreach (var tourFile in Directory.EnumerateFiles(tourDir, "*.json")) + foreach (var tourDir in Directory.EnumerateDirectories(backofficeDir, "tours")) { - TryParseTourFile(tourFile, result, combinedFilters, aliasOnlyFilters, pluginName); + foreach (var tourFile in Directory.EnumerateFiles(tourDir, "*.json")) + { + TryParseTourFile(tourFile, result, combinedFilters, aliasOnlyFilters, pluginName); + } } } } } + //Get all allowed sections for the current user var allowedSections = user.AllowedSections.ToList(); From 5db6ce2b8e7cc1a28c5951cc5f1371cbdd3dd6ff Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 8 Nov 2019 12:20:57 +1100 Subject: [PATCH 36/49] fixes some comments --- src/Umbraco.Web/Editors/MacrosController.cs | 1 - src/Umbraco.Web/Install/FilePermissionHelper.cs | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web/Editors/MacrosController.cs b/src/Umbraco.Web/Editors/MacrosController.cs index 3cb161e547..2e68925ce4 100644 --- a/src/Umbraco.Web/Editors/MacrosController.cs +++ b/src/Umbraco.Web/Editors/MacrosController.cs @@ -323,7 +323,6 @@ namespace Umbraco.Web.Editors /// Finds partial view files in app plugin folders. ///
/// - /// The . /// private IEnumerable FindPartialViewFilesInPluginFolders() { diff --git a/src/Umbraco.Web/Install/FilePermissionHelper.cs b/src/Umbraco.Web/Install/FilePermissionHelper.cs index ede9008514..af62b4fd9e 100644 --- a/src/Umbraco.Web/Install/FilePermissionHelper.cs +++ b/src/Umbraco.Web/Install/FilePermissionHelper.cs @@ -45,8 +45,8 @@ namespace Umbraco.Web.Install /// /// This will test the directories for write access /// - /// - /// + /// + /// /// /// If this is false, the easiest way to test for write access is to write a temp file, however some folder will cause /// an App Domain restart if a file is written to the folder, so in that case we need to use the ACL APIs which aren't as From 0680f104b5d0158a00665848ddf791b80ff009c5 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 26 Nov 2019 12:39:12 +0100 Subject: [PATCH 37/49] Added "rc" to AssemblyInformationalVersion --- src/SolutionInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index 5614cb8c41..7fa0b5a49d 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -19,4 +19,4 @@ using System.Resources; // these are FYI and changed automatically [assembly: AssemblyFileVersion("8.4.0")] -[assembly: AssemblyInformationalVersion("8.4.0")] +[assembly: AssemblyInformationalVersion("8.4.0-rc")] From 2ec5a8101aba2593d1b62f285874119f36c30f77 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 2 Dec 2019 17:15:47 +1100 Subject: [PATCH 38/49] fixes binary breaking change introduced --- src/Umbraco.Web/Cache/ContentCacheRefresher.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Umbraco.Web/Cache/ContentCacheRefresher.cs b/src/Umbraco.Web/Cache/ContentCacheRefresher.cs index d9a6518493..df332b5168 100644 --- a/src/Umbraco.Web/Cache/ContentCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/ContentCacheRefresher.cs @@ -139,6 +139,13 @@ namespace Umbraco.Web.Cache public class JsonPayload { + [Obsolete("Use the constructor specifying a GUID instead, using this constructor will result in not refreshing all caches")] + public JsonPayload(int id, TreeChangeTypes changeTypes) + { + Id = id; + ChangeTypes = changeTypes; + } + public JsonPayload(int id, Guid? key, TreeChangeTypes changeTypes) { Id = id; From 5616dcb807eccfed372acba959ebcbeecff6ee2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 3 Dec 2019 09:28:41 +0100 Subject: [PATCH 39/49] moved hideLabel to control-header --- .../src/views/components/property/umb-property.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 @@
-
+
{{inheritsFrom}} -