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