Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b3867d9d55 | |||
| 834aeee107 | |||
| f3643747b3 | |||
| ee83c02424 | |||
| 355a5e0eb4 |
@@ -51,14 +51,17 @@
|
|||||||
<file src="$BuildTmp$\WebApp\bin\Umbraco.Web.dll" target="lib\net472\Umbraco.Web.dll" />
|
<file src="$BuildTmp$\WebApp\bin\Umbraco.Web.dll" target="lib\net472\Umbraco.Web.dll" />
|
||||||
<file src="$BuildTmp$\WebApp\bin\Umbraco.Web.UI.dll" target="lib\net472\Umbraco.Web.UI.dll" />
|
<file src="$BuildTmp$\WebApp\bin\Umbraco.Web.UI.dll" target="lib\net472\Umbraco.Web.UI.dll" />
|
||||||
<file src="$BuildTmp$\WebApp\bin\Umbraco.Examine.dll" target="lib\net472\Umbraco.Examine.dll" />
|
<file src="$BuildTmp$\WebApp\bin\Umbraco.Examine.dll" target="lib\net472\Umbraco.Examine.dll" />
|
||||||
|
<file src="$BuildTmp$\WebApp\bin\Umbraco.ModelsBuilder.dll" target="lib\net472\Umbraco.ModelsBuilder.dll" />
|
||||||
|
|
||||||
<!-- docs -->
|
<!-- docs -->
|
||||||
<file src="$BuildTmp$\WebApp\bin\Umbraco.Web.xml" target="lib\net472\Umbraco.Web.xml" />
|
<file src="$BuildTmp$\WebApp\bin\Umbraco.Web.xml" target="lib\net472\Umbraco.Web.xml" />
|
||||||
<file src="$BuildTmp$\WebApp\bin\Umbraco.Web.UI.xml" target="lib\net472\Umbraco.Web.UI.xml" />
|
<file src="$BuildTmp$\WebApp\bin\Umbraco.Web.UI.xml" target="lib\net472\Umbraco.Web.UI.xml" />
|
||||||
<file src="$BuildTmp$\WebApp\bin\Umbraco.Examine.xml" target="lib\net472\Umbraco.Examine.xml" />
|
<file src="$BuildTmp$\WebApp\bin\Umbraco.Examine.xml" target="lib\net472\Umbraco.Examine.xml" />
|
||||||
|
<file src="$BuildTmp$\WebApp\bin\Umbraco.ModelsBuilder.xml" target="lib\net472\Umbraco.ModelsBuilder.xml" />
|
||||||
|
|
||||||
<!-- symbols -->
|
<!-- symbols -->
|
||||||
<file src="$BuildTmp$\bin\Umbraco.Web.pdb" target="lib" />
|
<file src="$BuildTmp$\bin\Umbraco.Web.pdb" target="lib" />
|
||||||
<file src="$BuildTmp$\bin\Umbraco.Examine.pdb" target="lib" />
|
<file src="$BuildTmp$\bin\Umbraco.Examine.pdb" target="lib" />
|
||||||
|
<file src="$BuildTmp$\bin\Umbraco.ModelsBuilder.pdb" target="lib" />
|
||||||
</files>
|
</files>
|
||||||
</package>
|
</package>
|
||||||
|
|||||||
@@ -25,7 +25,6 @@
|
|||||||
not want this to happen as the alpha of the next major is, really, the next major already.
|
not want this to happen as the alpha of the next major is, really, the next major already.
|
||||||
-->
|
-->
|
||||||
<dependency id="Microsoft.AspNet.SignalR.Core" version="[2.4.0, 2.999999)" />
|
<dependency id="Microsoft.AspNet.SignalR.Core" version="[2.4.0, 2.999999)" />
|
||||||
<dependency id="Umbraco.ModelsBuilder.Ui" version="[8.1.0]" />
|
|
||||||
<dependency id="ImageProcessor.Web" version="[4.10.0.100,4.999999)" />
|
<dependency id="ImageProcessor.Web" version="[4.10.0.100,4.999999)" />
|
||||||
<dependency id="ImageProcessor.Web.Config" version="[2.5.0.100,2.999999)" />
|
<dependency id="ImageProcessor.Web.Config" version="[2.5.0.100,2.999999)" />
|
||||||
<dependency id="Microsoft.CodeDom.Providers.DotNetCompilerPlatform" version="[2.0.1,2.999999)" />
|
<dependency id="Microsoft.CodeDom.Providers.DotNetCompilerPlatform" version="[2.0.1,2.999999)" />
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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<string, string> GetModels(Dictionary<string, string> 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<IDictionary<string, string>>(_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<string, string>("grant_type", "password"),
|
|
||||||
new KeyValuePair<string, string>("userName", _user),
|
|
||||||
new KeyValuePair<string, string>("password", _password),
|
|
||||||
});
|
|
||||||
|
|
||||||
var result = client.PostAsync(_url + UmbracoOAuthTokenUrl, formData).Result;
|
|
||||||
|
|
||||||
EnsureSuccess(result);
|
|
||||||
|
|
||||||
var token = result.Content.ReadAsAsync<TokenData>(_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", "%");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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<string, string> GetModels(UmbracoServices umbracoServices, string modelsNamespace, IDictionary<string, string> files)
|
|
||||||
{
|
|
||||||
var typeModels = umbracoServices.GetAllTypes();
|
|
||||||
|
|
||||||
var parseResult = new CodeParser().ParseWithReferencedAssemblies(files);
|
|
||||||
var builder = new TextBuilder(typeModels, parseResult, modelsNamespace);
|
|
||||||
|
|
||||||
var models = new Dictionary<string, string>();
|
|
||||||
foreach (var typeModel in builder.GetModelsToGenerate())
|
|
||||||
{
|
|
||||||
var sb = new StringBuilder();
|
|
||||||
builder.Generate(sb, typeModel);
|
|
||||||
models[typeModel.ClrName] = sb.ToString();
|
|
||||||
}
|
|
||||||
return models;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using Semver;
|
||||||
|
|
||||||
namespace Umbraco.ModelsBuilder.Api
|
namespace Umbraco.ModelsBuilder.Api
|
||||||
{
|
{
|
||||||
@@ -8,81 +9,28 @@ namespace Umbraco.ModelsBuilder.Api
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class ApiVersion
|
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
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="ApiVersion"/> class.
|
/// Initializes a new instance of the <see cref="ApiVersion"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="executingVersion">The currently executing version.</param>
|
/// <param name="executingVersion">The currently executing version.</param>
|
||||||
/// <param name="minClientVersionSupportedByServer">The min client version supported by the server.</param>
|
/// <exception cref="ArgumentNullException"></exception>
|
||||||
/// <param name="minServerVersionSupportingClient">An opt min server version supporting the client.</param>
|
internal ApiVersion(SemVersion executingVersion)
|
||||||
internal ApiVersion(Version executingVersion, Version minClientVersionSupportedByServer, Version minServerVersionSupportingClient = null)
|
|
||||||
{
|
{
|
||||||
if (executingVersion == null) throw new ArgumentNullException(nameof(executingVersion));
|
Version = executingVersion ?? throw new ArgumentNullException(nameof(executingVersion));
|
||||||
if (minClientVersionSupportedByServer == null) throw new ArgumentNullException(nameof(minClientVersionSupportedByServer));
|
|
||||||
|
|
||||||
Version = executingVersion;
|
|
||||||
MinClientVersionSupportedByServer = minClientVersionSupportedByServer;
|
|
||||||
MinServerVersionSupportingClient = minServerVersionSupportingClient;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static SemVersion CurrentAssemblyVersion
|
||||||
|
=> SemVersion.Parse(Assembly.GetExecutingAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the currently executing API version.
|
/// Gets the currently executing API version.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static ApiVersion Current { get; }
|
public static ApiVersion Current { get; }
|
||||||
= new ApiVersion(Assembly.GetExecutingAssembly().GetName().Version,
|
= new ApiVersion(CurrentAssemblyVersion);
|
||||||
MinClientVersionSupportedByServerConst, MinServerVersionSupportingClientConst);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the executing version of the API.
|
/// Gets the executing version of the API.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Version Version { get; }
|
public SemVersion Version { get; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the min client version supported by the server.
|
|
||||||
/// </summary>
|
|
||||||
public Version MinClientVersionSupportedByServer { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the min server version supporting the client.
|
|
||||||
/// </summary>
|
|
||||||
public Version MinServerVersionSupportingClient { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether the API server is compatible with a client.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="clientVersion">The client version.</param>
|
|
||||||
/// <param name="minServerVersionSupportingClient">An opt min server version supporting the client.</param>
|
|
||||||
/// <remarks>
|
|
||||||
/// <para>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 .</para>
|
|
||||||
/// </remarks>
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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<string, string> Files { get; set; }
|
|
||||||
|
|
||||||
public override bool IsValid => base.IsValid && Files != 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<HttpResponseMessage> CheckVersion(Version clientVersion, Version minServerVersionSupportingClient)
|
|
||||||
{
|
|
||||||
if (clientVersion == null)
|
|
||||||
return Attempt<HttpResponseMessage>.Fail(Request.CreateResponse(HttpStatusCode.Forbidden,
|
|
||||||
$"API version conflict: client version (<null>) 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<HttpResponseMessage>.If(isOk, response);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,6 +5,7 @@ using System.Text;
|
|||||||
using Microsoft.CodeAnalysis;
|
using Microsoft.CodeAnalysis;
|
||||||
using Microsoft.CodeAnalysis.CSharp;
|
using Microsoft.CodeAnalysis.CSharp;
|
||||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
|
using Umbraco.Core.Composing;
|
||||||
using Umbraco.Core.Configuration;
|
using Umbraco.Core.Configuration;
|
||||||
using Umbraco.ModelsBuilder.Configuration;
|
using Umbraco.ModelsBuilder.Configuration;
|
||||||
|
|
||||||
@@ -25,7 +26,8 @@ namespace Umbraco.ModelsBuilder.Building
|
|||||||
private readonly IList<TypeModel> _typeModels;
|
private readonly IList<TypeModel> _typeModels;
|
||||||
|
|
||||||
protected Dictionary<string, string> ModelsMap { get; } = new Dictionary<string, string>();
|
protected Dictionary<string, string> ModelsMap { get; } = new Dictionary<string, string>();
|
||||||
protected ParseResult ParseResult { get; }
|
|
||||||
|
private static Config Config => Current.Configs.ModelsBuilder();
|
||||||
|
|
||||||
// the list of assemblies that will be 'using' by default
|
// the list of assemblies that will be 'using' by default
|
||||||
protected readonly IList<string> TypesUsing = new List<string>
|
protected readonly IList<string> TypesUsing = new List<string>
|
||||||
@@ -58,7 +60,7 @@ namespace Umbraco.ModelsBuilder.Building
|
|||||||
/// <returns>The models to generate, ie those that are not ignored.</returns>
|
/// <returns>The models to generate, ie those that are not ignored.</returns>
|
||||||
public IEnumerable<TypeModel> GetModelsToGenerate()
|
public IEnumerable<TypeModel> GetModelsToGenerate()
|
||||||
{
|
{
|
||||||
return _typeModels.Where(x => !x.IsContentIgnored);
|
return _typeModels;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -72,27 +74,25 @@ namespace Umbraco.ModelsBuilder.Building
|
|||||||
/// and the result of code parsing.
|
/// and the result of code parsing.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="typeModels">The list of models to generate.</param>
|
/// <param name="typeModels">The list of models to generate.</param>
|
||||||
/// <param name="parseResult">The result of code parsing.</param>
|
protected Builder(IList<TypeModel> typeModels)
|
||||||
protected Builder(IList<TypeModel> typeModels, ParseResult parseResult)
|
: this(typeModels, null)
|
||||||
{
|
{ }
|
||||||
_typeModels = typeModels ?? throw new ArgumentNullException(nameof(typeModels));
|
|
||||||
ParseResult = parseResult ?? throw new ArgumentNullException(nameof(parseResult));
|
|
||||||
|
|
||||||
Prepare();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="Builder"/> class with a list of models to generate,
|
/// Initializes a new instance of the <see cref="Builder"/> class with a list of models to generate,
|
||||||
/// the result of code parsing, and a models namespace.
|
/// the result of code parsing, and a models namespace.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="typeModels">The list of models to generate.</param>
|
/// <param name="typeModels">The list of models to generate.</param>
|
||||||
/// <param name="parseResult">The result of code parsing.</param>
|
|
||||||
/// <param name="modelsNamespace">The models namespace.</param>
|
/// <param name="modelsNamespace">The models namespace.</param>
|
||||||
protected Builder(IList<TypeModel> typeModels, ParseResult parseResult, string modelsNamespace)
|
protected Builder(IList<TypeModel> typeModels, string modelsNamespace)
|
||||||
: this(typeModels, parseResult)
|
|
||||||
{
|
{
|
||||||
|
_typeModels = typeModels ?? throw new ArgumentNullException(nameof(typeModels));
|
||||||
|
|
||||||
// can be null or empty, we'll manage
|
// can be null or empty, we'll manage
|
||||||
ModelsNamespace = modelsNamespace;
|
ModelsNamespace = modelsNamespace;
|
||||||
|
|
||||||
|
// but we want it to prepare
|
||||||
|
Prepare();
|
||||||
}
|
}
|
||||||
|
|
||||||
// for unit tests only
|
// for unit tests only
|
||||||
@@ -108,49 +108,9 @@ namespace Umbraco.ModelsBuilder.Building
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
private void Prepare()
|
private void Prepare()
|
||||||
{
|
{
|
||||||
var pureLive = UmbracoConfig.For.ModelsBuilder().ModelsMode == ModelsMode.PureLive;
|
TypeModel.MapModelTypes(_typeModels, ModelsNamespace);
|
||||||
|
|
||||||
// mark IsContentIgnored models that we discovered should be ignored
|
var pureLive = Config.ModelsMode == ModelsMode.PureLive;
|
||||||
// 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,
|
// for the first two of these two tests,
|
||||||
// always throw, even in purelive: cannot happen unless ppl start fidling with attributes to rename
|
// always throw, even in purelive: cannot happen unless ppl start fidling with attributes to rename
|
||||||
@@ -158,22 +118,22 @@ namespace Umbraco.ModelsBuilder.Building
|
|||||||
// for the last one, don't throw in purelive, see comment
|
// for the last one, don't throw in purelive, see comment
|
||||||
|
|
||||||
// ensure we have no duplicates type names
|
// ensure we have no duplicates type names
|
||||||
foreach (var xx in _typeModels.Where(x => !x.IsContentIgnored).GroupBy(x => x.ClrName).Where(x => x.Count() > 1))
|
foreach (var xx in _typeModels.GroupBy(x => x.ClrName).Where(x => x.Count() > 1))
|
||||||
throw new InvalidOperationException($"Type name \"{xx.Key}\" is used"
|
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."
|
+ $" 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.");
|
+ " Consider using an attribute to assign different names to conflicting types.");
|
||||||
|
|
||||||
// ensure we have no duplicates property names
|
// ensure we have no duplicates property names
|
||||||
foreach (var typeModel in _typeModels.Where(x => !x.IsContentIgnored))
|
foreach (var typeModel in _typeModels)
|
||||||
foreach (var xx in typeModel.Properties.Where(x => !x.IsIgnored).GroupBy(x => x.ClrName).Where(x => x.Count() > 1))
|
foreach (var xx in typeModel.Properties.GroupBy(x => x.ClrName).Where(x => x.Count() > 1))
|
||||||
throw new InvalidOperationException($"Property name \"{xx.Key}\" in type {typeModel.ItemType}:\"{typeModel.Alias}\""
|
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."
|
+ $" 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.");
|
+ " Consider using an attribute to assign different names to conflicting properties.");
|
||||||
|
|
||||||
// ensure content & property type don't have identical name (csharp hates it)
|
// ensure content & property type don't have identical name (csharp hates it)
|
||||||
foreach (var typeModel in _typeModels.Where(x => !x.IsContentIgnored))
|
foreach (var typeModel in _typeModels)
|
||||||
{
|
{
|
||||||
foreach (var xx in typeModel.Properties.Where(x => !x.IsIgnored && x.ClrName == typeModel.ClrName))
|
foreach (var xx in typeModel.Properties.Where(x => x.ClrName == typeModel.ClrName))
|
||||||
{
|
{
|
||||||
if (!pureLive)
|
if (!pureLive)
|
||||||
throw new InvalidOperationException($"The model class for content type with alias \"{typeModel.Alias}\" is named \"{xx.ClrName}\"."
|
throw new InvalidOperationException($"The model class for content type with alias \"{typeModel.Alias}\" is named \"{xx.ClrName}\"."
|
||||||
@@ -204,7 +164,7 @@ namespace Umbraco.ModelsBuilder.Building
|
|||||||
// collect all the (non-removed) types implemented at parent level
|
// collect all the (non-removed) types implemented at parent level
|
||||||
// ie the parent content types and the mixins content types, recursively
|
// ie the parent content types and the mixins content types, recursively
|
||||||
var parentImplems = new List<TypeModel>();
|
var parentImplems = new List<TypeModel>();
|
||||||
if (typeModel.BaseType != null && !typeModel.BaseType.IsContentIgnored)
|
if (typeModel.BaseType != null)
|
||||||
TypeModel.CollectImplems(parentImplems, typeModel.BaseType);
|
TypeModel.CollectImplems(parentImplems, typeModel.BaseType);
|
||||||
|
|
||||||
// interfaces we must declare we implement (initially empty)
|
// interfaces we must declare we implement (initially empty)
|
||||||
@@ -212,7 +172,6 @@ namespace Umbraco.ModelsBuilder.Building
|
|||||||
// and except those that are already declared at the parent level
|
// and except those that are already declared at the parent level
|
||||||
// in other words, DeclaringInterfaces is "local mixins"
|
// in other words, DeclaringInterfaces is "local mixins"
|
||||||
var declaring = typeModel.MixinTypes
|
var declaring = typeModel.MixinTypes
|
||||||
.Where(x => !x.IsContentIgnored)
|
|
||||||
.Except(parentImplems);
|
.Except(parentImplems);
|
||||||
typeModel.DeclaringInterfaces.AddRange(declaring);
|
typeModel.DeclaringInterfaces.AddRange(declaring);
|
||||||
|
|
||||||
@@ -227,43 +186,16 @@ namespace Umbraco.ModelsBuilder.Building
|
|||||||
typeModel.ImplementingInterfaces.AddRange(mixinImplems.Except(parentImplems));
|
typeModel.ImplementingInterfaces.AddRange(mixinImplems.Except(parentImplems));
|
||||||
}
|
}
|
||||||
|
|
||||||
// register using types
|
// ensure elements don't inherit from non-elements
|
||||||
foreach (var usingNamespace in ParseResult.UsingNamespaces)
|
foreach (var typeModel in _typeModels.Where(x => x.IsElement))
|
||||||
{
|
{
|
||||||
if (!TypesUsing.Contains(usingNamespace))
|
if (typeModel.BaseType != null && !typeModel.BaseType.IsElement)
|
||||||
TypesUsing.Add(usingNamespace);
|
throw new Exception($"Cannot generate model for type '{typeModel.Alias}' because it is an element type, but its parent type '{typeModel.BaseType.Alias}' is not.");
|
||||||
|
|
||||||
|
var errs = typeModel.MixinTypes.Where(x => !x.IsElement).ToList();
|
||||||
|
if (errs.Count > 0)
|
||||||
|
throw new Exception($"Cannot generate model for type '{typeModel.Alias}' because it is an element type, but it is composed of {string.Join(", ", errs.Select(x => "'" + x.Alias + "'"))} which {(errs.Count == 1 ? "is" : "are")} not.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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<string, string> { { "code", codeBuilder.ToString() } }, out trees);
|
|
||||||
var tree = trees[0];
|
|
||||||
_ambiguousSymbolsModel = compilation.GetSemanticModel(tree);
|
|
||||||
|
|
||||||
var namespaceSyntax = tree.GetRoot().DescendantNodes().OfType<NamespaceDeclarationSyntax>().First();
|
|
||||||
//var namespaceSymbol = model.GetDeclaredSymbol(namespaceSyntax);
|
|
||||||
_ambiguousSymbolsPos = namespaceSyntax.OpenBraceToken.SpanStart;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// looking for a simple symbol eg 'Umbraco' or 'String'
|
// looking for a simple symbol eg 'Umbraco' or 'String'
|
||||||
@@ -273,20 +205,10 @@ namespace Umbraco.ModelsBuilder.Building
|
|||||||
// - 1 symbol is found BUT not matching (implicitely ambiguous)
|
// - 1 symbol is found BUT not matching (implicitely ambiguous)
|
||||||
protected bool IsAmbiguousSymbol(string symbol, string match)
|
protected bool IsAmbiguousSymbol(string symbol, string match)
|
||||||
{
|
{
|
||||||
if (_ambiguousSymbolsModel == null)
|
// cannot figure out is a symbol is ambiguous without Roslyn
|
||||||
PrepareAmbiguousSymbols();
|
// so... let's say everything is ambiguous - code won't be
|
||||||
if (_ambiguousSymbolsModel == null)
|
// pretty but it'll work
|
||||||
throw new Exception("Could not prepare ambiguous symbols.");
|
return true;
|
||||||
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;
|
internal string ModelsNamespaceForTests;
|
||||||
@@ -296,25 +218,17 @@ namespace Umbraco.ModelsBuilder.Building
|
|||||||
if (ModelsNamespaceForTests != null)
|
if (ModelsNamespaceForTests != null)
|
||||||
return ModelsNamespaceForTests;
|
return ModelsNamespaceForTests;
|
||||||
|
|
||||||
// code attribute overrides everything
|
|
||||||
if (ParseResult.HasModelsNamespace)
|
|
||||||
return ParseResult.ModelsNamespace;
|
|
||||||
|
|
||||||
// if builder was initialized with a namespace, use that one
|
// if builder was initialized with a namespace, use that one
|
||||||
if (!string.IsNullOrWhiteSpace(ModelsNamespace))
|
if (!string.IsNullOrWhiteSpace(ModelsNamespace))
|
||||||
return ModelsNamespace;
|
return ModelsNamespace;
|
||||||
|
|
||||||
// default
|
// default
|
||||||
// fixme - should NOT reference config here, should make ModelsNamespace mandatory
|
// fixme - should NOT reference config here, should make ModelsNamespace mandatory
|
||||||
return UmbracoConfig.For.ModelsBuilder().ModelsNamespace;
|
return Config.ModelsNamespace;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected string GetModelsBaseClassName(TypeModel type)
|
protected string GetModelsBaseClassName(TypeModel type)
|
||||||
{
|
{
|
||||||
// code attribute overrides everything
|
|
||||||
if (ParseResult.HasModelsBaseClassName)
|
|
||||||
return ParseResult.ModelsBaseClassName;
|
|
||||||
|
|
||||||
// default
|
// default
|
||||||
return type.IsElement ? "PublishedElementModel" : "PublishedContentModel";
|
return type.IsElement ? "PublishedElementModel" : "PublishedContentModel";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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...
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Implements a builder that works by using CodeDom
|
|
||||||
/// </summary>
|
|
||||||
internal class CodeDomBuilder : Builder
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="CodeDomBuilder"/> class with a list of models to generate.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="typeModels">The list of models to generate.</param>
|
|
||||||
public CodeDomBuilder(IList<TypeModel> typeModels)
|
|
||||||
: base(typeModels, null)
|
|
||||||
{ }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Outputs a generated model to a code namespace.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="ns">The code namespace.</param>
|
|
||||||
/// <param name="typeModel">The model to generate.</param>
|
|
||||||
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[] // <T>
|
|
||||||
{
|
|
||||||
new CodeTypeReference(propertyModel.ModelClrType)
|
|
||||||
}),
|
|
||||||
new CodeExpression[] // ("alias")
|
|
||||||
{
|
|
||||||
new CodePrimitiveExpression(propertyModel.Alias)
|
|
||||||
})));
|
|
||||||
c.Members.Add(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Implements code parsing.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>Parses user's code and look for generator's instructions.</remarks>
|
|
||||||
internal class CodeParser
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Parses a set of file.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="files">A set of (filename,content) representing content to parse.</param>
|
|
||||||
/// <returns>The result of the code parsing.</returns>
|
|
||||||
/// <remarks>The set of files is a dictionary of name, content.</remarks>
|
|
||||||
public ParseResult Parse(IDictionary<string, string> files)
|
|
||||||
{
|
|
||||||
return Parse(files, Enumerable.Empty<PortableExecutableReference>());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Parses a set of file.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="files">A set of (filename,content) representing content to parse.</param>
|
|
||||||
/// <param name="references">Assemblies to reference in compilations.</param>
|
|
||||||
/// <returns>The result of the code parsing.</returns>
|
|
||||||
/// <remarks>The set of files is a dictionary of name, content.</remarks>
|
|
||||||
public ParseResult Parse(IDictionary<string, string> files, IEnumerable<PortableExecutableReference> 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<string, string> 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<ClassDeclarationSyntax>();
|
|
||||||
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<InterfaceDeclarationSyntax>();
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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<PortableExecutableReference> References { get; set; }
|
|
||||||
|
|
||||||
public bool Debug { get; set; }
|
|
||||||
|
|
||||||
// gets a compilation
|
|
||||||
public CSharpCompilation GetCompilation(string assemblyName, IDictionary<string, string> 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<string, string> 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<string, string> 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<string, string> 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<string, string> { { path, code } }, stream);
|
|
||||||
return Assembly.Load(stream.GetBuffer());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// compiles files into a stream
|
|
||||||
public void Compile(string assemblyName, IDictionary<string, string> 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<string, string> { { path, code } }, stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ThrowExceptionFromDiagnostic(IDictionary<string, string> 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,275 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace Umbraco.ModelsBuilder.Building
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Contains the result of a code parsing.
|
|
||||||
/// </summary>
|
|
||||||
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<string> _ignoredContent
|
|
||||||
= new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);
|
|
||||||
//private readonly HashSet<string> _ignoredMixin
|
|
||||||
// = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);
|
|
||||||
//private readonly HashSet<string> _ignoredMixinProperties
|
|
||||||
// = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);
|
|
||||||
private readonly Dictionary<string, string> _renamedContent
|
|
||||||
= new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase);
|
|
||||||
private readonly HashSet<string> _withImplementContent
|
|
||||||
= new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);
|
|
||||||
private readonly Dictionary<string, HashSet<string>> _ignoredProperty
|
|
||||||
= new Dictionary<string, HashSet<string>>();
|
|
||||||
private readonly Dictionary<string, Dictionary<string, string>> _renamedProperty
|
|
||||||
= new Dictionary<string, Dictionary<string, string>>();
|
|
||||||
private readonly Dictionary<string, string> _contentBase
|
|
||||||
= new Dictionary<string, string>();
|
|
||||||
private readonly Dictionary<string, string[]> _contentInterfaces
|
|
||||||
= new Dictionary<string, string[]>();
|
|
||||||
private readonly List<string> _usingNamespaces
|
|
||||||
= new List<string>();
|
|
||||||
private readonly Dictionary<string, List<StaticMixinMethodInfo>> _staticMixins
|
|
||||||
= new Dictionary<string, List<StaticMixinMethodInfo>>();
|
|
||||||
private readonly HashSet<string> _withCtor
|
|
||||||
= new HashSet<string>(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<string> ignores;
|
|
||||||
if (!_ignoredProperty.TryGetValue(contentName, out ignores))
|
|
||||||
ignores = _ignoredProperty[contentName] = new HashSet<string>(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<string, string> renames;
|
|
||||||
if (!_renamedProperty.TryGetValue(contentName, out renames))
|
|
||||||
renames = _renamedProperty[contentName] = new Dictionary<string, string>(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<string> 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<StaticMixinMethodInfo>();
|
|
||||||
|
|
||||||
_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<string> 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<string> 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<string, string> 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<string> UsingNamespaces
|
|
||||||
{
|
|
||||||
get { return _usingNamespaces; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerable<string> StaticMixinMethods(string contentName)
|
|
||||||
{
|
|
||||||
return _staticMixins.ContainsKey(contentName)
|
|
||||||
? _staticMixins[contentName].Select(x => x.MethodName)
|
|
||||||
: Enumerable.Empty<string>() ;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool HasCtor(string contentName)
|
|
||||||
{
|
|
||||||
return _withCtor.Contains(contentName);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -41,11 +41,6 @@ namespace Umbraco.ModelsBuilder.Building
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string ClrTypeName;
|
public string ClrTypeName;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether this property should be excluded from generation.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsIgnored;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the generation errors for the property.
|
/// Gets the generation errors for the property.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -3,8 +3,9 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
using Umbraco.Core.Composing;
|
||||||
using Umbraco.Core.Configuration;
|
using Umbraco.Core.Configuration;
|
||||||
using Umbraco.Core.Models.PublishedContent;
|
using Umbraco.ModelsBuilder.Api;
|
||||||
using Umbraco.ModelsBuilder.Configuration;
|
using Umbraco.ModelsBuilder.Configuration;
|
||||||
|
|
||||||
namespace Umbraco.ModelsBuilder.Building
|
namespace Umbraco.ModelsBuilder.Building
|
||||||
@@ -19,9 +20,8 @@ namespace Umbraco.ModelsBuilder.Building
|
|||||||
/// and the result of code parsing.
|
/// and the result of code parsing.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="typeModels">The list of models to generate.</param>
|
/// <param name="typeModels">The list of models to generate.</param>
|
||||||
/// <param name="parseResult">The result of code parsing.</param>
|
public TextBuilder(IList<TypeModel> typeModels)
|
||||||
public TextBuilder(IList<TypeModel> typeModels, ParseResult parseResult)
|
: base(typeModels)
|
||||||
: base(typeModels, parseResult)
|
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -29,21 +29,22 @@ namespace Umbraco.ModelsBuilder.Building
|
|||||||
/// the result of code parsing, and a models namespace.
|
/// the result of code parsing, and a models namespace.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="typeModels">The list of models to generate.</param>
|
/// <param name="typeModels">The list of models to generate.</param>
|
||||||
/// <param name="parseResult">The result of code parsing.</param>
|
|
||||||
/// <param name="modelsNamespace">The models namespace.</param>
|
/// <param name="modelsNamespace">The models namespace.</param>
|
||||||
public TextBuilder(IList<TypeModel> typeModels, ParseResult parseResult, string modelsNamespace)
|
public TextBuilder(IList<TypeModel> typeModels, string modelsNamespace)
|
||||||
: base(typeModels, parseResult, modelsNamespace)
|
: base(typeModels, modelsNamespace)
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
// internal for unit tests only
|
// internal for unit tests only
|
||||||
internal TextBuilder()
|
internal TextBuilder()
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
|
private static Config Config => Current.Configs.ModelsBuilder();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Outputs a generated model to a string builder.
|
/// Outputs a generated model to a string builder.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sb">The string builder.</param>
|
/// <param name="sb">The string builder.</param>
|
||||||
/// <param name="typeModel">The model to generate.</param>
|
/// <param name="typeModel">The model to generate.</param>
|
||||||
public void Generate(StringBuilder sb, TypeModel typeModel)
|
public void Generate(StringBuilder sb, TypeModel typeModel)
|
||||||
{
|
{
|
||||||
WriteHeader(sb);
|
WriteHeader(sb);
|
||||||
@@ -97,6 +98,20 @@ namespace Umbraco.ModelsBuilder.Building
|
|||||||
TextHeaderWriter.WriteHeader(sb);
|
TextHeaderWriter.WriteHeader(sb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// writes an attribute that identifies code generated by a tool
|
||||||
|
// (helps reduce warnings, tools such as FxCop use it)
|
||||||
|
// see https://github.com/zpqrtbnk/Zbu.ModelsBuilder/issues/107
|
||||||
|
// see https://docs.microsoft.com/en-us/dotnet/api/system.codedom.compiler.generatedcodeattribute
|
||||||
|
// see https://blogs.msdn.microsoft.com/codeanalysis/2007/04/27/correct-usage-of-the-compilergeneratedattribute-and-the-generatedcodeattribute/
|
||||||
|
//
|
||||||
|
// note that the blog post above clearly states that "Nor should it be applied at the type level if the type being generated is a partial class."
|
||||||
|
// and since our models are partial classes, we have to apply the attribute against the individual members, not the class itself.
|
||||||
|
//
|
||||||
|
private static void WriteGeneratedCodeAttribute(StringBuilder sb, string tabs)
|
||||||
|
{
|
||||||
|
sb.AppendFormat("{0}[global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"Umbraco.ModelsBuilder\", \"{1}\")]\n", tabs, ApiVersion.Current.Version);
|
||||||
|
}
|
||||||
|
|
||||||
private void WriteContentType(StringBuilder sb, TypeModel type)
|
private void WriteContentType(StringBuilder sb, TypeModel type)
|
||||||
{
|
{
|
||||||
string sep;
|
string sep;
|
||||||
@@ -104,11 +119,11 @@ namespace Umbraco.ModelsBuilder.Building
|
|||||||
if (type.IsMixin)
|
if (type.IsMixin)
|
||||||
{
|
{
|
||||||
// write the interface declaration
|
// write the interface declaration
|
||||||
sb.AppendFormat("\t// Mixin content Type {0} with alias \"{1}\"\n", type.Id, type.Alias);
|
sb.AppendFormat("\t// Mixin Content Type with alias \"{0}\"\n", type.Alias);
|
||||||
if (!string.IsNullOrWhiteSpace(type.Name))
|
if (!string.IsNullOrWhiteSpace(type.Name))
|
||||||
sb.AppendFormat("\t/// <summary>{0}</summary>\n", XmlCommentString(type.Name));
|
sb.AppendFormat("\t/// <summary>{0}</summary>\n", XmlCommentString(type.Name));
|
||||||
sb.AppendFormat("\tpublic partial interface I{0}", type.ClrName);
|
sb.AppendFormat("\tpublic partial interface I{0}", type.ClrName);
|
||||||
var implements = type.BaseType == null || type.BaseType.IsContentIgnored
|
var implements = type.BaseType == null
|
||||||
? (type.HasBase ? null : (type.IsElement ? "PublishedElement" : "PublishedContent"))
|
? (type.HasBase ? null : (type.IsElement ? "PublishedElement" : "PublishedContent"))
|
||||||
: type.BaseType.ClrName;
|
: type.BaseType.ClrName;
|
||||||
if (implements != null)
|
if (implements != null)
|
||||||
@@ -126,7 +141,7 @@ namespace Umbraco.ModelsBuilder.Building
|
|||||||
|
|
||||||
// write the properties - only the local (non-ignored) ones, we're an interface
|
// write the properties - only the local (non-ignored) ones, we're an interface
|
||||||
var more = false;
|
var more = false;
|
||||||
foreach (var prop in type.Properties.Where(x => !x.IsIgnored).OrderBy(x => x.ClrName))
|
foreach (var prop in type.Properties.OrderBy(x => x.ClrName))
|
||||||
{
|
{
|
||||||
if (more) sb.Append("\n");
|
if (more) sb.Append("\n");
|
||||||
more = true;
|
more = true;
|
||||||
@@ -137,8 +152,6 @@ namespace Umbraco.ModelsBuilder.Building
|
|||||||
}
|
}
|
||||||
|
|
||||||
// write the class declaration
|
// 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))
|
if (!string.IsNullOrWhiteSpace(type.Name))
|
||||||
sb.AppendFormat("\t/// <summary>{0}</summary>\n", XmlCommentString(type.Name));
|
sb.AppendFormat("\t/// <summary>{0}</summary>\n", XmlCommentString(type.Name));
|
||||||
// cannot do it now. see note in ImplementContentTypeAttribute
|
// cannot do it now. see note in ImplementContentTypeAttribute
|
||||||
@@ -148,7 +161,7 @@ namespace Umbraco.ModelsBuilder.Building
|
|||||||
sb.AppendFormat("\tpublic partial class {0}", type.ClrName);
|
sb.AppendFormat("\tpublic partial class {0}", type.ClrName);
|
||||||
var inherits = type.HasBase
|
var inherits = type.HasBase
|
||||||
? null // has its own base already
|
? null // has its own base already
|
||||||
: (type.BaseType == null || type.BaseType.IsContentIgnored
|
: (type.BaseType == null
|
||||||
? GetModelsBaseClassName(type)
|
? GetModelsBaseClassName(type)
|
||||||
: type.BaseType.ClrName);
|
: type.BaseType.ClrName);
|
||||||
if (inherits != null)
|
if (inherits != null)
|
||||||
@@ -178,22 +191,25 @@ namespace Umbraco.ModelsBuilder.Building
|
|||||||
// as 'new' since parent has its own - or maybe not - disable warning
|
// as 'new' since parent has its own - or maybe not - disable warning
|
||||||
sb.Append("\t\t// helpers\n");
|
sb.Append("\t\t// helpers\n");
|
||||||
sb.Append("#pragma warning disable 0109 // new is redundant\n");
|
sb.Append("#pragma warning disable 0109 // new is redundant\n");
|
||||||
|
WriteGeneratedCodeAttribute(sb, "\t\t");
|
||||||
sb.AppendFormat("\t\tpublic new const string ModelTypeAlias = \"{0}\";\n",
|
sb.AppendFormat("\t\tpublic new const string ModelTypeAlias = \"{0}\";\n",
|
||||||
type.Alias);
|
type.Alias);
|
||||||
var itemType = type.IsElement ? TypeModel.ItemTypes.Content : type.ItemType; // fixme
|
var itemType = type.IsElement ? TypeModel.ItemTypes.Content : type.ItemType; // fixme
|
||||||
|
WriteGeneratedCodeAttribute(sb, "\t\t");
|
||||||
sb.AppendFormat("\t\tpublic new const PublishedItemType ModelItemType = PublishedItemType.{0};\n",
|
sb.AppendFormat("\t\tpublic new const PublishedItemType ModelItemType = PublishedItemType.{0};\n",
|
||||||
itemType);
|
itemType);
|
||||||
|
WriteGeneratedCodeAttribute(sb, "\t\t");
|
||||||
sb.Append("\t\tpublic new static PublishedContentType GetModelContentType()\n");
|
sb.Append("\t\tpublic new static PublishedContentType GetModelContentType()\n");
|
||||||
sb.Append("\t\t\t=> PublishedModelUtility.GetModelContentType(ModelItemType, ModelTypeAlias);\n");
|
sb.Append("\t\t\t=> PublishedModelUtility.GetModelContentType(ModelItemType, ModelTypeAlias);\n");
|
||||||
|
WriteGeneratedCodeAttribute(sb, "\t\t");
|
||||||
sb.AppendFormat("\t\tpublic static PublishedPropertyType GetModelPropertyType<TValue>(Expression<Func<{0}, TValue>> selector)\n",
|
sb.AppendFormat("\t\tpublic static PublishedPropertyType GetModelPropertyType<TValue>(Expression<Func<{0}, TValue>> selector)\n",
|
||||||
type.ClrName);
|
type.ClrName);
|
||||||
sb.Append("\t\t\t=> PublishedModelUtility.GetModelPropertyType(GetModelContentType(), selector);\n");
|
sb.Append("\t\t\t=> PublishedModelUtility.GetModelPropertyType(GetModelContentType(), selector);\n");
|
||||||
sb.Append("#pragma warning restore 0109\n\n");
|
sb.Append("#pragma warning restore 0109\n\n");
|
||||||
|
|
||||||
// write the ctor
|
// 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",
|
||||||
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");
|
||||||
type.ClrName, type.IsElement ? "Element" : "Content");
|
|
||||||
|
|
||||||
// write the properties
|
// write the properties
|
||||||
sb.Append("\t\t// properties\n");
|
sb.Append("\t\t// properties\n");
|
||||||
@@ -205,10 +221,10 @@ namespace Umbraco.ModelsBuilder.Building
|
|||||||
|
|
||||||
private void WriteContentTypeProperties(StringBuilder sb, TypeModel type)
|
private void WriteContentTypeProperties(StringBuilder sb, TypeModel type)
|
||||||
{
|
{
|
||||||
var staticMixinGetters = UmbracoConfig.For.ModelsBuilder().StaticMixinGetters;
|
var staticMixinGetters = true;
|
||||||
|
|
||||||
// write the properties
|
// write the properties
|
||||||
foreach (var prop in type.Properties.Where(x => !x.IsIgnored).OrderBy(x => x.ClrName))
|
foreach (var prop in type.Properties.OrderBy(x => x.ClrName))
|
||||||
WriteProperty(sb, type, prop, staticMixinGetters && type.IsMixin ? type.ClrName : null);
|
WriteProperty(sb, type, prop, staticMixinGetters && type.IsMixin ? type.ClrName : null);
|
||||||
|
|
||||||
// no need to write the parent properties since we inherit from the parent
|
// no need to write the parent properties since we inherit from the parent
|
||||||
@@ -217,7 +233,7 @@ namespace Umbraco.ModelsBuilder.Building
|
|||||||
|
|
||||||
// write the mixins properties
|
// write the mixins properties
|
||||||
foreach (var mixinType in type.ImplementingInterfaces.OrderBy(x => x.ClrName))
|
foreach (var mixinType in type.ImplementingInterfaces.OrderBy(x => x.ClrName))
|
||||||
foreach (var prop in mixinType.Properties.Where(x => !x.IsIgnored).OrderBy(x => x.ClrName))
|
foreach (var prop in mixinType.Properties.OrderBy(x => x.ClrName))
|
||||||
if (staticMixinGetters)
|
if (staticMixinGetters)
|
||||||
WriteMixinProperty(sb, prop, mixinType.ClrName);
|
WriteMixinProperty(sb, prop, mixinType.ClrName);
|
||||||
else
|
else
|
||||||
@@ -242,6 +258,7 @@ namespace Umbraco.ModelsBuilder.Building
|
|||||||
sb.Append("\t\t///</summary>\n");
|
sb.Append("\t\t///</summary>\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WriteGeneratedCodeAttribute(sb, "\t\t");
|
||||||
sb.AppendFormat("\t\t[ImplementPropertyType(\"{0}\")]\n", property.Alias);
|
sb.AppendFormat("\t\t[ImplementPropertyType(\"{0}\")]\n", property.Alias);
|
||||||
|
|
||||||
sb.Append("\t\tpublic ");
|
sb.Append("\t\tpublic ");
|
||||||
@@ -256,7 +273,7 @@ namespace Umbraco.ModelsBuilder.Building
|
|||||||
|
|
||||||
private static string MixinStaticGetterName(string clrName)
|
private static string MixinStaticGetterName(string clrName)
|
||||||
{
|
{
|
||||||
return string.Format(UmbracoConfig.For.ModelsBuilder().StaticMixinGetterPattern, clrName);
|
return string.Format("Get{0}", clrName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void WriteProperty(StringBuilder sb, TypeModel type, PropertyModel property, string mixinClrName = null)
|
private void WriteProperty(StringBuilder sb, TypeModel type, PropertyModel property, string mixinClrName = null)
|
||||||
@@ -300,6 +317,7 @@ namespace Umbraco.ModelsBuilder.Building
|
|||||||
sb.Append("\t\t///</summary>\n");
|
sb.Append("\t\t///</summary>\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WriteGeneratedCodeAttribute(sb, "\t\t");
|
||||||
sb.AppendFormat("\t\t[ImplementPropertyType(\"{0}\")]\n", property.Alias);
|
sb.AppendFormat("\t\t[ImplementPropertyType(\"{0}\")]\n", property.Alias);
|
||||||
|
|
||||||
if (mixinStatic)
|
if (mixinStatic)
|
||||||
@@ -343,6 +361,7 @@ namespace Umbraco.ModelsBuilder.Building
|
|||||||
if (!string.IsNullOrWhiteSpace(property.Name))
|
if (!string.IsNullOrWhiteSpace(property.Name))
|
||||||
sb.AppendFormat("\t\t/// <summary>Static getter for {0}</summary>\n", XmlCommentString(property.Name));
|
sb.AppendFormat("\t\t/// <summary>Static getter for {0}</summary>\n", XmlCommentString(property.Name));
|
||||||
|
|
||||||
|
WriteGeneratedCodeAttribute(sb, "\t\t");
|
||||||
sb.Append("\t\tpublic static ");
|
sb.Append("\t\tpublic static ");
|
||||||
WriteClrType(sb, property.ClrTypeName);
|
WriteClrType(sb, property.ClrTypeName);
|
||||||
sb.AppendFormat(" {0}(I{1} that) => that.Value",
|
sb.AppendFormat(" {0}(I{1} that) => that.Value",
|
||||||
@@ -397,6 +416,7 @@ namespace Umbraco.ModelsBuilder.Building
|
|||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(property.Name))
|
if (!string.IsNullOrWhiteSpace(property.Name))
|
||||||
sb.AppendFormat("\t\t/// <summary>{0}</summary>\n", XmlCommentString(property.Name));
|
sb.AppendFormat("\t\t/// <summary>{0}</summary>\n", XmlCommentString(property.Name));
|
||||||
|
WriteGeneratedCodeAttribute(sb, "\t\t");
|
||||||
sb.Append("\t\t");
|
sb.Append("\t\t");
|
||||||
WriteClrType(sb, property.ClrTypeName);
|
WriteClrType(sb, property.ClrTypeName);
|
||||||
sb.AppendFormat(" {0} {{ get; }}\n",
|
sb.AppendFormat(" {0} {{ get; }}\n",
|
||||||
@@ -461,7 +481,7 @@ namespace Umbraco.ModelsBuilder.Building
|
|||||||
s = Regex.Replace(s, @"\{(.*)\}\[\*\]", m => ModelsMap[m.Groups[1].Value + "[]"]);
|
s = Regex.Replace(s, @"\{(.*)\}\[\*\]", m => ModelsMap[m.Groups[1].Value + "[]"]);
|
||||||
|
|
||||||
// takes care eg of "System.Int32" vs. "int"
|
// takes care eg of "System.Int32" vs. "int"
|
||||||
if (TypesMap.TryGetValue(s.ToLowerInvariant(), out string typeName))
|
if (TypesMap.TryGetValue(s, out string typeName))
|
||||||
{
|
{
|
||||||
sb.Append(typeName);
|
sb.Append(typeName);
|
||||||
return;
|
return;
|
||||||
@@ -481,6 +501,11 @@ namespace Umbraco.ModelsBuilder.Building
|
|||||||
typeName = typeName.Substring(p + 1);
|
typeName = typeName.Substring(p + 1);
|
||||||
typeUsing = x;
|
typeUsing = x;
|
||||||
}
|
}
|
||||||
|
else if (x == ModelsNamespace) // that one is used by default
|
||||||
|
{
|
||||||
|
typeName = typeName.Substring(p + 1);
|
||||||
|
typeUsing = ModelsNamespace;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// nested types *after* using
|
// nested types *after* using
|
||||||
@@ -531,24 +556,24 @@ namespace Umbraco.ModelsBuilder.Building
|
|||||||
return s.Replace('<', '{').Replace('>', '}').Replace('\r', ' ').Replace('\n', ' ');
|
return s.Replace('<', '{').Replace('>', '}').Replace('\r', ' ').Replace('\n', ' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly IDictionary<string, string> TypesMap = new Dictionary<string, string>
|
private static readonly IDictionary<string, string> TypesMap = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||||
{
|
{
|
||||||
{ "system.int16", "short" },
|
{ "System.Int16", "short" },
|
||||||
{ "system.int32", "int" },
|
{ "System.Int32", "int" },
|
||||||
{ "system.int64", "long" },
|
{ "System.Int64", "long" },
|
||||||
{ "system.string", "string" },
|
{ "System.String", "string" },
|
||||||
{ "system.object", "object" },
|
{ "System.Object", "object" },
|
||||||
{ "system.boolean", "bool" },
|
{ "System.Boolean", "bool" },
|
||||||
{ "system.void", "void" },
|
{ "System.Void", "void" },
|
||||||
{ "system.char", "char" },
|
{ "System.Char", "char" },
|
||||||
{ "system.byte", "byte" },
|
{ "System.Byte", "byte" },
|
||||||
{ "system.uint16", "ushort" },
|
{ "System.UInt16", "ushort" },
|
||||||
{ "system.uint32", "uint" },
|
{ "System.UInt32", "uint" },
|
||||||
{ "system.uint64", "ulong" },
|
{ "System.UInt64", "ulong" },
|
||||||
{ "system.sbyte", "sbyte" },
|
{ "System.SByte", "sbyte" },
|
||||||
{ "system.single", "float" },
|
{ "System.Single", "float" },
|
||||||
{ "system.double", "double" },
|
{ "System.Double", "double" },
|
||||||
{ "system.decimal", "decimal" }
|
{ "System.Decimal", "decimal" }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Umbraco.Core.Models.PublishedContent;
|
||||||
|
|
||||||
namespace Umbraco.ModelsBuilder.Building
|
namespace Umbraco.ModelsBuilder.Building
|
||||||
{
|
{
|
||||||
@@ -88,16 +89,6 @@ namespace Umbraco.ModelsBuilder.Building
|
|||||||
/// or because the existing user's code declares a base class for this model.</remarks>
|
/// or because the existing user's code declares a base class for this model.</remarks>
|
||||||
public bool HasBase;
|
public bool HasBase;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether this model has been renamed.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsRenamed;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether this model has [ImplementContentType] already.
|
|
||||||
/// </summary>
|
|
||||||
public bool HasImplement;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether this model is used as a mixin by another model.
|
/// Gets a value indicating whether this model is used as a mixin by another model.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -108,16 +99,6 @@ namespace Umbraco.ModelsBuilder.Building
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsParent;
|
public bool IsParent;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether this model should be excluded from generation.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsContentIgnored;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether the ctor is already defined in a partial.
|
|
||||||
/// </summary>
|
|
||||||
public bool HasCtor;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether the type is an element.
|
/// Gets a value indicating whether the type is an element.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -181,11 +162,11 @@ namespace Umbraco.ModelsBuilder.Building
|
|||||||
/// <remarks>Includes the specified type.</remarks>
|
/// <remarks>Includes the specified type.</remarks>
|
||||||
internal static void CollectImplems(ICollection<TypeModel> types, TypeModel type)
|
internal static void CollectImplems(ICollection<TypeModel> types, TypeModel type)
|
||||||
{
|
{
|
||||||
if (!type.IsContentIgnored && types.Contains(type) == false)
|
if (types.Contains(type) == false)
|
||||||
types.Add(type);
|
types.Add(type);
|
||||||
if (type.BaseType != null && !type.BaseType.IsContentIgnored)
|
if (type.BaseType != null)
|
||||||
CollectImplems(types, type.BaseType);
|
CollectImplems(types, type.BaseType);
|
||||||
foreach (var mixin in type.MixinTypes.Where(x => !x.IsContentIgnored))
|
foreach (var mixin in type.MixinTypes)
|
||||||
CollectImplems(types, mixin);
|
CollectImplems(types, mixin);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,5 +185,21 @@ namespace Umbraco.ModelsBuilder.Building
|
|||||||
typeModel = typeModel.BaseType;
|
typeModel = typeModel.BaseType;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Maps ModelType.
|
||||||
|
/// </summary>
|
||||||
|
public static void MapModelTypes(IList<TypeModel> typeModels, string ns)
|
||||||
|
{
|
||||||
|
var hasNs = !string.IsNullOrWhiteSpace(ns);
|
||||||
|
var map = typeModels.ToDictionary(x => x.Alias, x => hasNs ? (ns + "." + x.ClrName) : x.ClrName);
|
||||||
|
foreach (var typeModel in typeModels)
|
||||||
|
{
|
||||||
|
foreach (var propertyModel in typeModel.Properties)
|
||||||
|
{
|
||||||
|
propertyModel.ClrTypeName = ModelType.MapToName(propertyModel.ModelClrType, map);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
using Umbraco.Core.Configuration;
|
||||||
|
using Umbraco.ModelsBuilder.Configuration;
|
||||||
|
|
||||||
|
namespace Umbraco.ModelsBuilder
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides extension methods for the <see cref="UmbracoConfig"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public static class ConfigsExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the models builder configuration.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Getting the models builder configuration freezes its state,
|
||||||
|
/// and any attempt at modifying the configuration using the Setup method
|
||||||
|
/// will be ignored.</remarks>
|
||||||
|
public static Config ModelsBuilder(this Configs configs)
|
||||||
|
=> configs.GetConfig<Config>();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Configuration;
|
using System.Configuration;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Reflection;
|
|
||||||
using System.Web.Configuration;
|
using System.Web.Configuration;
|
||||||
using System.Web.Hosting;
|
using System.Web.Hosting;
|
||||||
using Microsoft.CodeAnalysis.CSharp;
|
|
||||||
using Umbraco.Core;
|
using Umbraco.Core;
|
||||||
|
|
||||||
namespace Umbraco.ModelsBuilder.Configuration
|
namespace Umbraco.ModelsBuilder.Configuration
|
||||||
@@ -14,40 +12,13 @@ namespace Umbraco.ModelsBuilder.Configuration
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class Config
|
public class Config
|
||||||
{
|
{
|
||||||
private static Config _value;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 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.
|
|
||||||
/// </summary>
|
|
||||||
internal static Config Value => _value ?? new Config();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sets the configuration programmatically.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="config">The configuration.</param>
|
|
||||||
/// <remarks>
|
|
||||||
/// <para>Once the configuration has been accessed via the UmbracoConfig extension,
|
|
||||||
/// it cannot be changed anymore, and using this method will achieve nothing.</para>
|
|
||||||
/// <para>For tests, see UmbracoConfigExtensions.ResetConfig().</para>
|
|
||||||
/// </remarks>
|
|
||||||
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 string DefaultModelsNamespace = "Umbraco.Web.PublishedModels";
|
||||||
internal const ClrNameSource DefaultClrNameSource = ClrNameSource.Alias; // for legacy reasons
|
|
||||||
internal const string DefaultModelsDirectory = "~/App_Data/Models";
|
internal const string DefaultModelsDirectory = "~/App_Data/Models";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="Config"/> class.
|
/// Initializes a new instance of the <see cref="Config"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private Config()
|
public Config()
|
||||||
{
|
{
|
||||||
const string prefix = "Umbraco.ModelsBuilder.";
|
const string prefix = "Umbraco.ModelsBuilder.";
|
||||||
|
|
||||||
@@ -56,10 +27,7 @@ namespace Umbraco.ModelsBuilder.Configuration
|
|||||||
Enable = ConfigurationManager.AppSettings[prefix + "Enable"] == "true";
|
Enable = ConfigurationManager.AppSettings[prefix + "Enable"] == "true";
|
||||||
|
|
||||||
// ensure defaults are initialized for tests
|
// ensure defaults are initialized for tests
|
||||||
StaticMixinGetterPattern = DefaultStaticMixinGetterPattern;
|
|
||||||
LanguageVersion = DefaultLanguageVersion;
|
|
||||||
ModelsNamespace = DefaultModelsNamespace;
|
ModelsNamespace = DefaultModelsNamespace;
|
||||||
ClrNameSource = DefaultClrNameSource;
|
|
||||||
ModelsDirectory = HostingEnvironment.IsHosted
|
ModelsDirectory = HostingEnvironment.IsHosted
|
||||||
? HostingEnvironment.MapPath(DefaultModelsDirectory)
|
? HostingEnvironment.MapPath(DefaultModelsDirectory)
|
||||||
: DefaultModelsDirectory.TrimStart("~/");
|
: DefaultModelsDirectory.TrimStart("~/");
|
||||||
@@ -80,12 +48,6 @@ namespace Umbraco.ModelsBuilder.Configuration
|
|||||||
case nameof(ModelsMode.PureLive):
|
case nameof(ModelsMode.PureLive):
|
||||||
ModelsMode = ModelsMode.PureLive;
|
ModelsMode = ModelsMode.PureLive;
|
||||||
break;
|
break;
|
||||||
case nameof(ModelsMode.Dll):
|
|
||||||
ModelsMode = ModelsMode.Dll;
|
|
||||||
break;
|
|
||||||
case nameof(ModelsMode.LiveDll):
|
|
||||||
ModelsMode = ModelsMode.LiveDll;
|
|
||||||
break;
|
|
||||||
case nameof(ModelsMode.AppData):
|
case nameof(ModelsMode.AppData):
|
||||||
ModelsMode = ModelsMode.AppData;
|
ModelsMode = ModelsMode.AppData;
|
||||||
break;
|
break;
|
||||||
@@ -94,17 +56,15 @@ namespace Umbraco.ModelsBuilder.Configuration
|
|||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new ConfigurationErrorsException($"ModelsMode \"{modelsMode}\" is not a valid mode."
|
throw new ConfigurationErrorsException($"ModelsMode \"{modelsMode}\" is not a valid mode."
|
||||||
+ " Note that modes are case-sensitive.");
|
+ " Note that modes are case-sensitive. Possible values are: " + string.Join(", ", Enum.GetNames(typeof(ModelsMode))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// default: false
|
// default: false
|
||||||
EnableApi = ConfigurationManager.AppSettings[prefix + "EnableApi"].InvariantEquals("true");
|
|
||||||
AcceptUnsafeModelsDirectory = ConfigurationManager.AppSettings[prefix + "AcceptUnsafeModelsDirectory"].InvariantEquals("true");
|
AcceptUnsafeModelsDirectory = ConfigurationManager.AppSettings[prefix + "AcceptUnsafeModelsDirectory"].InvariantEquals("true");
|
||||||
|
|
||||||
// default: true
|
// default: true
|
||||||
EnableFactory = !ConfigurationManager.AppSettings[prefix + "EnableFactory"].InvariantEquals("false");
|
EnableFactory = !ConfigurationManager.AppSettings[prefix + "EnableFactory"].InvariantEquals("false");
|
||||||
StaticMixinGetters = !ConfigurationManager.AppSettings[prefix + "StaticMixinGetters"].InvariantEquals("false");
|
|
||||||
FlagOutOfDateModels = !ConfigurationManager.AppSettings[prefix + "FlagOutOfDateModels"].InvariantEquals("false");
|
FlagOutOfDateModels = !ConfigurationManager.AppSettings[prefix + "FlagOutOfDateModels"].InvariantEquals("false");
|
||||||
|
|
||||||
// default: initialized above with DefaultModelsNamespace const
|
// default: initialized above with DefaultModelsNamespace const
|
||||||
@@ -112,45 +72,6 @@ namespace Umbraco.ModelsBuilder.Configuration
|
|||||||
if (!string.IsNullOrWhiteSpace(value))
|
if (!string.IsNullOrWhiteSpace(value))
|
||||||
ModelsNamespace = 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
|
// default: initialized above with DefaultModelsDirectory const
|
||||||
value = ConfigurationManager.AppSettings[prefix + "ModelsDirectory"];
|
value = ConfigurationManager.AppSettings[prefix + "ModelsDirectory"];
|
||||||
if (!string.IsNullOrWhiteSpace(value))
|
if (!string.IsNullOrWhiteSpace(value))
|
||||||
@@ -186,14 +107,9 @@ namespace Umbraco.ModelsBuilder.Configuration
|
|||||||
public Config(
|
public Config(
|
||||||
bool enable = false,
|
bool enable = false,
|
||||||
ModelsMode modelsMode = ModelsMode.Nothing,
|
ModelsMode modelsMode = ModelsMode.Nothing,
|
||||||
bool enableApi = true,
|
|
||||||
string modelsNamespace = null,
|
string modelsNamespace = null,
|
||||||
bool enableFactory = true,
|
bool enableFactory = true,
|
||||||
LanguageVersion languageVersion = DefaultLanguageVersion,
|
|
||||||
bool staticMixinGetters = true,
|
|
||||||
string staticMixinGetterPattern = null,
|
|
||||||
bool flagOutOfDateModels = true,
|
bool flagOutOfDateModels = true,
|
||||||
ClrNameSource clrNameSource = DefaultClrNameSource,
|
|
||||||
string modelsDirectory = null,
|
string modelsDirectory = null,
|
||||||
bool acceptUnsafeModelsDirectory = false,
|
bool acceptUnsafeModelsDirectory = false,
|
||||||
int debugLevel = 0)
|
int debugLevel = 0)
|
||||||
@@ -201,14 +117,9 @@ namespace Umbraco.ModelsBuilder.Configuration
|
|||||||
Enable = enable;
|
Enable = enable;
|
||||||
ModelsMode = modelsMode;
|
ModelsMode = modelsMode;
|
||||||
|
|
||||||
EnableApi = enableApi;
|
|
||||||
ModelsNamespace = string.IsNullOrWhiteSpace(modelsNamespace) ? DefaultModelsNamespace : modelsNamespace;
|
ModelsNamespace = string.IsNullOrWhiteSpace(modelsNamespace) ? DefaultModelsNamespace : modelsNamespace;
|
||||||
EnableFactory = enableFactory;
|
EnableFactory = enableFactory;
|
||||||
LanguageVersion = languageVersion;
|
|
||||||
StaticMixinGetters = staticMixinGetters;
|
|
||||||
StaticMixinGetterPattern = string.IsNullOrWhiteSpace(staticMixinGetterPattern) ? DefaultStaticMixinGetterPattern : staticMixinGetterPattern;
|
|
||||||
FlagOutOfDateModels = flagOutOfDateModels;
|
FlagOutOfDateModels = flagOutOfDateModels;
|
||||||
ClrNameSource = clrNameSource;
|
|
||||||
ModelsDirectory = string.IsNullOrWhiteSpace(modelsDirectory) ? DefaultModelsDirectory : modelsDirectory;
|
ModelsDirectory = string.IsNullOrWhiteSpace(modelsDirectory) ? DefaultModelsDirectory : modelsDirectory;
|
||||||
AcceptUnsafeModelsDirectory = acceptUnsafeModelsDirectory;
|
AcceptUnsafeModelsDirectory = acceptUnsafeModelsDirectory;
|
||||||
DebugLevel = debugLevel;
|
DebugLevel = debugLevel;
|
||||||
@@ -259,27 +170,6 @@ namespace Umbraco.ModelsBuilder.Configuration
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public ModelsMode ModelsMode { get; }
|
public ModelsMode ModelsMode { get; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether to serve the API.
|
|
||||||
/// </summary>
|
|
||||||
public bool ApiServer => EnableApi && ApiInstalled && IsDebug;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether to enable the API.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// <para>Default value is <c>true</c>.</para>
|
|
||||||
/// <para>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.</para>
|
|
||||||
/// </remarks>
|
|
||||||
public bool EnableApi { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether the API is installed.
|
|
||||||
/// </summary>
|
|
||||||
// fixme - this is now always true as the API is part of Core
|
|
||||||
public bool ApiInstalled => true;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether system.web/compilation/@debug is true.
|
/// Gets a value indicating whether system.web/compilation/@debug is true.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -304,24 +194,6 @@ namespace Umbraco.ModelsBuilder.Configuration
|
|||||||
/// <remarks>Default value is <c>true</c> because no factory is enabled by default in Umbraco.</remarks>
|
/// <remarks>Default value is <c>true</c> because no factory is enabled by default in Umbraco.</remarks>
|
||||||
public bool EnableFactory { get; }
|
public bool EnableFactory { get; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the Roslyn parser language version.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>Default value is <c>CSharp6</c>.</remarks>
|
|
||||||
public LanguageVersion LanguageVersion { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether to generate static mixin getters.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>Default value is <c>false</c> for backward compat reaons.</remarks>
|
|
||||||
public bool StaticMixinGetters { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the string pattern for mixin properties static getter name.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>Default value is "GetXxx". Standard string format.</remarks>
|
|
||||||
public string StaticMixinGetterPattern { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether we should flag out-of-date models.
|
/// Gets a value indicating whether we should flag out-of-date models.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -330,11 +202,6 @@ namespace Umbraco.ModelsBuilder.Configuration
|
|||||||
/// generated through the dashboard, the files is cleared. Default value is <c>false</c>.</remarks>
|
/// generated through the dashboard, the files is cleared. Default value is <c>false</c>.</remarks>
|
||||||
public bool FlagOutOfDateModels { get; }
|
public bool FlagOutOfDateModels { get; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the CLR name source.
|
|
||||||
/// </summary>
|
|
||||||
public ClrNameSource ClrNameSource { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the models directory.
|
/// Gets the models directory.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -31,22 +31,6 @@
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>Generation can be triggered from the dashboard. The app does not restart.
|
/// <remarks>Generation can be triggered from the dashboard. The app does not restart.
|
||||||
/// Models are not compiled and thus are not available to the project.</remarks>
|
/// Models are not compiled and thus are not available to the project.</remarks>
|
||||||
LiveAppData,
|
LiveAppData
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Generates models in AppData and compiles them into a Dll into ~/bin (the app restarts).
|
|
||||||
/// When: generation is triggered.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>Generation can be triggered from the dashboard. The app does restart. Models
|
|
||||||
/// are available to the entire project.</remarks>
|
|
||||||
Dll,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 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.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>Generation can be triggered from the dashboard. The app does restart. Models
|
|
||||||
/// are available to the entire project.</remarks>
|
|
||||||
LiveDll
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
{
|
{
|
||||||
return
|
return
|
||||||
modelsMode == ModelsMode.PureLive
|
modelsMode == ModelsMode.PureLive
|
||||||
|| modelsMode == ModelsMode.LiveDll
|
|
||||||
|| modelsMode == ModelsMode.LiveAppData;
|
|| modelsMode == ModelsMode.LiveAppData;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,18 +21,7 @@
|
|||||||
public static bool IsLiveNotPure(this ModelsMode modelsMode)
|
public static bool IsLiveNotPure(this ModelsMode modelsMode)
|
||||||
{
|
{
|
||||||
return
|
return
|
||||||
modelsMode == ModelsMode.LiveDll
|
modelsMode == ModelsMode.LiveAppData;
|
||||||
|| modelsMode == ModelsMode.LiveAppData;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether the mode is [Live]Dll.
|
|
||||||
/// </summary>
|
|
||||||
public static bool IsAnyDll(this ModelsMode modelsMode)
|
|
||||||
{
|
|
||||||
return
|
|
||||||
modelsMode == ModelsMode.Dll
|
|
||||||
|| modelsMode == ModelsMode.LiveDll;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -42,10 +30,8 @@
|
|||||||
public static bool SupportsExplicitGeneration(this ModelsMode modelsMode)
|
public static bool SupportsExplicitGeneration(this ModelsMode modelsMode)
|
||||||
{
|
{
|
||||||
return
|
return
|
||||||
modelsMode == ModelsMode.Dll
|
modelsMode == ModelsMode.AppData
|
||||||
|| modelsMode == ModelsMode.LiveDll
|
|
||||||
|| modelsMode == ModelsMode.AppData
|
|
||||||
|| modelsMode == ModelsMode.LiveAppData;
|
|| modelsMode == ModelsMode.LiveAppData;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
using System.Threading;
|
|
||||||
using Umbraco.Core.Configuration;
|
|
||||||
|
|
||||||
namespace Umbraco.ModelsBuilder.Configuration
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Provides extension methods for the <see cref="UmbracoConfig"/> class.
|
|
||||||
/// </summary>
|
|
||||||
public static class UmbracoConfigExtensions
|
|
||||||
{
|
|
||||||
private static Config _config;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the models builder configuration.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="umbracoConfig">The umbraco configuration.</param>
|
|
||||||
/// <returns>The models builder configuration.</returns>
|
|
||||||
/// <remarks>Getting the models builder configuration freezes its state,
|
|
||||||
/// and any attempt at modifying the configuration using the Setup method
|
|
||||||
/// will be ignored.</remarks>
|
|
||||||
public static Config ModelsBuilder(this 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
using System;
|
using System.Text;
|
||||||
using System.Text;
|
using Umbraco.Core.Composing;
|
||||||
using Umbraco.Core.Configuration;
|
|
||||||
using Umbraco.ModelsBuilder.Configuration;
|
using Umbraco.ModelsBuilder.Configuration;
|
||||||
using Umbraco.ModelsBuilder.Umbraco;
|
using Umbraco.ModelsBuilder.Umbraco;
|
||||||
|
|
||||||
@@ -8,14 +7,11 @@ namespace Umbraco.ModelsBuilder.Dashboard
|
|||||||
{
|
{
|
||||||
internal static class BuilderDashboardHelper
|
internal static class BuilderDashboardHelper
|
||||||
{
|
{
|
||||||
|
private static Config Config => Current.Configs.ModelsBuilder();
|
||||||
|
|
||||||
public static bool CanGenerate()
|
public static bool CanGenerate()
|
||||||
{
|
{
|
||||||
return UmbracoConfig.For.ModelsBuilder().ModelsMode.SupportsExplicitGeneration();
|
return Config.ModelsMode.SupportsExplicitGeneration();
|
||||||
}
|
|
||||||
|
|
||||||
public static bool GenerateCausesRestart()
|
|
||||||
{
|
|
||||||
return UmbracoConfig.For.ModelsBuilder().ModelsMode.IsAnyDll();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool AreModelsOutOfDate()
|
public static bool AreModelsOutOfDate()
|
||||||
@@ -30,12 +26,17 @@ namespace Umbraco.ModelsBuilder.Dashboard
|
|||||||
|
|
||||||
public static string Text()
|
public static string Text()
|
||||||
{
|
{
|
||||||
var config = UmbracoConfig.For.ModelsBuilder();
|
var config = Config;
|
||||||
|
|
||||||
if (!config.Enable)
|
if (!config.Enable)
|
||||||
return "ModelsBuilder is disabled<br />(the .Enable key is missing, or its value is not 'true').";
|
return "Version: " + Api.ApiVersion.Current.Version + "<br /> <br />ModelsBuilder is disabled<br />(the .Enable key is missing, or its value is not 'true').";
|
||||||
|
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
|
|
||||||
|
sb.Append("Version: ");
|
||||||
|
sb.Append(Api.ApiVersion.Current.Version);
|
||||||
|
sb.Append("<br /> <br />");
|
||||||
|
|
||||||
sb.Append("ModelsBuilder is enabled, with the following configuration:");
|
sb.Append("ModelsBuilder is enabled, with the following configuration:");
|
||||||
|
|
||||||
sb.Append("<ul>");
|
sb.Append("<ul>");
|
||||||
@@ -46,39 +47,12 @@ namespace Umbraco.ModelsBuilder.Dashboard
|
|||||||
: "not enabled. Umbraco will <em>not</em> use models");
|
: "not enabled. Umbraco will <em>not</em> use models");
|
||||||
sb.Append(".</li>");
|
sb.Append(".</li>");
|
||||||
|
|
||||||
sb.Append("<li>The <strong>API</strong> is ");
|
|
||||||
if (config.ApiInstalled && config.EnableApi)
|
|
||||||
{
|
|
||||||
sb.Append("installed and enabled");
|
|
||||||
if (!config.IsDebug) sb.Append(".<br />However, the API runs only with <em>debug</em> 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(".<br />");
|
|
||||||
if (!config.ApiServer)
|
|
||||||
sb.Append("External tools such as Visual Studio <em>cannot</em> use the API");
|
|
||||||
else
|
|
||||||
sb.Append("<span style=\"color:orange;font-weight:bold;\">The API endpoint is open on this server</span>");
|
|
||||||
sb.Append(".</li>");
|
|
||||||
|
|
||||||
sb.Append(config.ModelsMode != ModelsMode.Nothing
|
sb.Append(config.ModelsMode != ModelsMode.Nothing
|
||||||
? $"<li><strong>{config.ModelsMode} models</strong> are enabled.</li>"
|
? $"<li><strong>{config.ModelsMode} models</strong> are enabled.</li>"
|
||||||
: "<li>No models mode is specified: models will <em>not</em> be generated.</li>");
|
: "<li>No models mode is specified: models will <em>not</em> be generated.</li>");
|
||||||
|
|
||||||
sb.Append($"<li>Models namespace is {config.ModelsNamespace}.</li>");
|
sb.Append($"<li>Models namespace is {config.ModelsNamespace}.</li>");
|
||||||
|
|
||||||
sb.Append("<li>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(".</li>");
|
|
||||||
|
|
||||||
sb.Append("<li>Tracking of <strong>out-of-date models</strong> is ");
|
sb.Append("<li>Tracking of <strong>out-of-date models</strong> is ");
|
||||||
sb.Append(config.FlagOutOfDateModels ? "enabled" : "not enabled");
|
sb.Append(config.FlagOutOfDateModels ? "enabled" : "not enabled");
|
||||||
sb.Append(".</li>");
|
sb.Append(".</li>");
|
||||||
|
|||||||
@@ -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...
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Indicates that no model should be generated for a specified content type alias.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>When a content type is ignored, its descendants are also ignored.</remarks>
|
|
||||||
/// <remarks>Supports trailing wildcard eg "foo*".</remarks>
|
|
||||||
[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*/)
|
|
||||||
{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Umbraco.ModelsBuilder
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Indicates that no model should be generated for a specified property type alias.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>Supports trailing wildcard eg "foo*".</remarks>
|
|
||||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
|
|
||||||
public sealed class IgnorePropertyTypeAttribute : Attribute
|
|
||||||
{
|
|
||||||
public IgnorePropertyTypeAttribute(string alias)
|
|
||||||
{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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.
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Indicates that a (partial) class defines the model type for a specific alias.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>Though a model will be generated - so that is the way to register a rename.</remarks>
|
|
||||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
|
|
||||||
public sealed class ImplementContentTypeAttribute : Attribute
|
|
||||||
{
|
|
||||||
public ImplementContentTypeAttribute(string alias)
|
|
||||||
{ }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Umbraco.ModelsBuilder
|
namespace Umbraco.ModelsBuilder
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Umbraco.ModelsBuilder
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Indicates the default base class for models.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>Otherwise it is PublishedContentModel. Would make sense to inherit from PublishedContentModel.</remarks>
|
|
||||||
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false, Inherited = false)]
|
|
||||||
public sealed class ModelsBaseClassAttribute : Attribute
|
|
||||||
{
|
|
||||||
public ModelsBaseClassAttribute(Type type)
|
|
||||||
{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Umbraco.ModelsBuilder
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Indicates the models namespace.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>Will override anything else that might come from settings.</remarks>
|
|
||||||
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false, Inherited = false)]
|
|
||||||
public sealed class ModelsNamespaceAttribute : Attribute
|
|
||||||
{
|
|
||||||
public ModelsNamespaceAttribute(string modelsNamespace)
|
|
||||||
{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -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
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Indicates namespaces that should be used in generated models (in using clauses).
|
|
||||||
/// </summary>
|
|
||||||
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true, Inherited = false)]
|
|
||||||
public sealed class ModelsUsingAttribute : Attribute
|
|
||||||
{
|
|
||||||
public ModelsUsingAttribute(string usingNamespace)
|
|
||||||
{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -7,8 +7,4 @@ using System.Runtime.InteropServices;
|
|||||||
[assembly: AssemblyProduct("Umbraco CMS")]
|
[assembly: AssemblyProduct("Umbraco CMS")]
|
||||||
|
|
||||||
[assembly: ComVisible(false)]
|
[assembly: ComVisible(false)]
|
||||||
[assembly: Guid("7020a059-c0d1-43a0-8efd-23591a0c9af6")]
|
[assembly: Guid("52ac0ba8-a60e-4e36-897b-e8b97a54ed1c")]
|
||||||
|
|
||||||
// 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 = "~_~")]
|
|
||||||
|
|||||||
@@ -2,9 +2,11 @@
|
|||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using Umbraco.Core.Models.PublishedContent;
|
using Umbraco.Core.Models.PublishedContent;
|
||||||
using Umbraco.Web;
|
using Umbraco.ModelsBuilder;
|
||||||
|
|
||||||
namespace Umbraco.ModelsBuilder
|
// same namespace as original Umbraco.Web PublishedElementExtensions
|
||||||
|
// ReSharper disable once CheckNamespace
|
||||||
|
namespace Umbraco.Web
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Provides extension methods to models.
|
/// Provides extension methods to models.
|
||||||
@@ -14,13 +16,14 @@ namespace Umbraco.ModelsBuilder
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the value of a property.
|
/// Gets the value of a property.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static TValue Value<TModel, TValue>(this TModel model, Expression<Func<TModel, TValue>> property, string culture = ".", string segment = ".")
|
public static TValue Value<TModel, TValue>(this TModel model, Expression<Func<TModel, TValue>> property, string culture = null, string segment = null, Fallback fallback = default, TValue defaultValue = default)
|
||||||
where TModel : IPublishedElement
|
where TModel : IPublishedElement
|
||||||
{
|
{
|
||||||
var alias = GetAlias(model, property);
|
var alias = GetAlias(model, property);
|
||||||
return model.Value<TValue>(alias, culture, segment);
|
return model.Value<TValue>(alias, culture, segment, fallback, defaultValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fixme that one should be public so ppl can use it
|
||||||
private static string GetAlias<TModel, TValue>(TModel model, Expression<Func<TModel, TValue>> property)
|
private static string GetAlias<TModel, TValue>(TModel model, Expression<Func<TModel, TValue>> property)
|
||||||
{
|
{
|
||||||
if (property.NodeType != ExpressionType.Lambda)
|
if (property.NodeType != ExpressionType.Lambda)
|
||||||
|
|||||||
@@ -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<int, string>[] PreValues(this PublishedPropertyType propertyType)
|
|
||||||
//{
|
|
||||||
// return ApplicationContext.Current.Services.DataTypeService
|
|
||||||
// .GetPreValuesCollectionByDataTypeId(propertyType.DataType.Id)
|
|
||||||
// .PreValuesAsArray
|
|
||||||
// .Select(x => new KeyValuePair<int, string>(x.Id, x.Value))
|
|
||||||
// .ToArray();
|
|
||||||
//}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,7 +4,6 @@ using System.Linq;
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Web.Compilation;
|
using System.Web.Compilation;
|
||||||
using System.Web.Hosting;
|
using System.Web.Hosting;
|
||||||
using Microsoft.CodeAnalysis;
|
|
||||||
using Umbraco.Core;
|
using Umbraco.Core;
|
||||||
|
|
||||||
namespace Umbraco.ModelsBuilder
|
namespace Umbraco.ModelsBuilder
|
||||||
@@ -12,17 +11,12 @@ namespace Umbraco.ModelsBuilder
|
|||||||
internal static class ReferencedAssemblies
|
internal static class ReferencedAssemblies
|
||||||
{
|
{
|
||||||
private static readonly Lazy<IEnumerable<string>> LazyLocations;
|
private static readonly Lazy<IEnumerable<string>> LazyLocations;
|
||||||
private static readonly Lazy<IEnumerable<PortableExecutableReference>> LazyReferences;
|
|
||||||
|
|
||||||
static ReferencedAssemblies()
|
static ReferencedAssemblies()
|
||||||
{
|
{
|
||||||
LazyLocations = new Lazy<IEnumerable<string>>(() => HostingEnvironment.IsHosted
|
LazyLocations = new Lazy<IEnumerable<string>>(() => HostingEnvironment.IsHosted
|
||||||
? GetAllReferencedAssembliesLocationFromBuildManager()
|
? GetAllReferencedAssembliesLocationFromBuildManager()
|
||||||
: GetAllReferencedAssembliesFromDomain());
|
: GetAllReferencedAssembliesFromDomain());
|
||||||
|
|
||||||
LazyReferences = new Lazy<IEnumerable<PortableExecutableReference>>(() => Locations
|
|
||||||
.Select(x => MetadataReference.CreateFromFile(x))
|
|
||||||
.ToArray());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -31,19 +25,68 @@ namespace Umbraco.ModelsBuilder
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static IEnumerable<string> Locations => LazyLocations.Value;
|
public static IEnumerable<string> Locations => LazyLocations.Value;
|
||||||
|
|
||||||
/// <summary>
|
public static Assembly GetNetStandardAssembly(List<Assembly> assemblies)
|
||||||
/// Gets the metadata reference of all the referenced assemblies.
|
{
|
||||||
/// </summary>
|
if (assemblies == null)
|
||||||
public static IEnumerable<PortableExecutableReference> References => LazyReferences.Value;
|
assemblies = BuildManager.GetReferencedAssemblies().Cast<Assembly>().ToList();
|
||||||
|
|
||||||
// hosted, get referenced assemblies from the BuildManader and filter
|
// for some reason, netstandard is also missing from BuildManager.ReferencedAssemblies and yet, is part of
|
||||||
|
// the references that CSharpCompiler (above) receives - where is it coming from? cannot figure it out
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// so, resorting to an ugly trick
|
||||||
|
// we should have System.Reflection.Metadata around, and it should reference netstandard
|
||||||
|
var someAssembly = assemblies.First(x => x.FullName.StartsWith("System.Reflection.Metadata,"));
|
||||||
|
var netStandardAssemblyName = someAssembly.GetReferencedAssemblies().First(x => x.FullName.StartsWith("netstandard,"));
|
||||||
|
var netStandard = Assembly.Load(netStandardAssemblyName.FullName);
|
||||||
|
return netStandard;
|
||||||
|
}
|
||||||
|
catch { /* never mind */ }
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Assembly GetNetStandardAssembly()
|
||||||
|
{
|
||||||
|
// in PreApplicationStartMethod we cannot get BuildManager.Referenced assemblies, do it differently
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var someAssembly = Assembly.Load("System.Reflection.Metadata");
|
||||||
|
var netStandardAssemblyName = someAssembly.GetReferencedAssemblies().First(x => x.FullName.StartsWith("netstandard,"));
|
||||||
|
var netStandard = Assembly.Load(netStandardAssemblyName.FullName);
|
||||||
|
return netStandard;
|
||||||
|
}
|
||||||
|
catch { /* never mind */ }
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// hosted, get referenced assemblies from the BuildManager and filter
|
||||||
private static IEnumerable<string> GetAllReferencedAssembliesLocationFromBuildManager()
|
private static IEnumerable<string> GetAllReferencedAssembliesLocationFromBuildManager()
|
||||||
{
|
{
|
||||||
return BuildManager.GetReferencedAssemblies()
|
var assemblies = BuildManager.GetReferencedAssemblies().Cast<Assembly>().ToList();
|
||||||
.Cast<Assembly>()
|
|
||||||
|
assemblies.Add(typeof(ReferencedAssemblies).Assembly); // always include ourselves
|
||||||
|
|
||||||
|
// see https://github.com/aspnet/RoslynCodeDomProvider/blob/master/src/Microsoft.CodeDom.Providers.DotNetCompilerPlatform/CSharpCompiler.cs:
|
||||||
|
// mentions "Bug 913691: Explicitly add System.Runtime as a reference."
|
||||||
|
// and explicitly adds System.Runtime to references before invoking csc.exe
|
||||||
|
// so, doing the same here
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var systemRuntime = Assembly.Load("System.Runtime, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
|
||||||
|
assemblies.Add(systemRuntime);
|
||||||
|
}
|
||||||
|
catch { /* never mind */ }
|
||||||
|
|
||||||
|
// for some reason, netstandard is also missing from BuildManager.ReferencedAssemblies and yet, is part of
|
||||||
|
// the references that CSharpCompiler (above) receives - where is it coming from? cannot figure it out
|
||||||
|
var netStandard = GetNetStandardAssembly(assemblies);
|
||||||
|
if (netStandard != null) assemblies.Add(netStandard);
|
||||||
|
|
||||||
|
return assemblies
|
||||||
.Where(x => !x.IsDynamic && !x.Location.IsNullOrWhiteSpace())
|
.Where(x => !x.IsDynamic && !x.Location.IsNullOrWhiteSpace())
|
||||||
.Select(x => x.Location)
|
.Select(x => x.Location)
|
||||||
.And(typeof(ReferencedAssemblies).Assembly.Location) // always include ourselves
|
|
||||||
.Distinct()
|
.Distinct()
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
@@ -132,6 +175,5 @@ namespace Umbraco.ModelsBuilder
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Umbraco.ModelsBuilder
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Indicates a model name for a specified content alias.
|
|
||||||
/// </summary>
|
|
||||||
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true, Inherited = false)]
|
|
||||||
public sealed class RenameContentTypeAttribute : Attribute
|
|
||||||
{
|
|
||||||
public RenameContentTypeAttribute(string alias, string name)
|
|
||||||
{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Umbraco.ModelsBuilder
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Indicates a model name for a specified property alias.
|
|
||||||
/// </summary>
|
|
||||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
|
|
||||||
public sealed class RenamePropertyTypeAttribute : Attribute
|
|
||||||
{
|
|
||||||
public RenamePropertyTypeAttribute(string alias, string name)
|
|
||||||
{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,13 +4,14 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||||
<ProjectGuid>{7020A059-C0D1-43A0-8EFD-23591A0C9AF6}</ProjectGuid>
|
<ProjectGuid>{52AC0BA8-A60E-4E36-897B-E8B97A54ED1C}</ProjectGuid>
|
||||||
<OutputType>Library</OutputType>
|
<OutputType>Library</OutputType>
|
||||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||||
<RootNamespace>Umbraco.ModelsBuilder</RootNamespace>
|
<RootNamespace>Umbraco.ModelsBuilder</RootNamespace>
|
||||||
<AssemblyName>Umbraco.ModelsBuilder</AssemblyName>
|
<AssemblyName>Umbraco.ModelsBuilder</AssemblyName>
|
||||||
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
|
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
|
||||||
<FileAlignment>512</FileAlignment>
|
<FileAlignment>512</FileAlignment>
|
||||||
|
<Deterministic>true</Deterministic>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||||
<DebugSymbols>true</DebugSymbols>
|
<DebugSymbols>true</DebugSymbols>
|
||||||
@@ -32,9 +33,10 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Reference Include="System" />
|
<Reference Include="System" />
|
||||||
|
<Reference Include="System.ComponentModel.DataAnnotations" />
|
||||||
|
<Reference Include="System.Configuration" />
|
||||||
<Reference Include="System.Core" />
|
<Reference Include="System.Core" />
|
||||||
<Reference Include="System.Web" />
|
<Reference Include="System.Web" />
|
||||||
<Reference Include="System.Web.ApplicationServices" />
|
|
||||||
<Reference Include="System.Xml.Linq" />
|
<Reference Include="System.Xml.Linq" />
|
||||||
<Reference Include="System.Data.DataSetExtensions" />
|
<Reference Include="System.Data.DataSetExtensions" />
|
||||||
<Reference Include="Microsoft.CSharp" />
|
<Reference Include="Microsoft.CSharp" />
|
||||||
@@ -46,67 +48,43 @@
|
|||||||
<Compile Include="..\SolutionInfo.cs">
|
<Compile Include="..\SolutionInfo.cs">
|
||||||
<Link>Properties\SolutionInfo.cs</Link>
|
<Link>Properties\SolutionInfo.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="Api\ApiBasicAuthFilter.cs" />
|
|
||||||
<Compile Include="Api\ApiClient.cs" />
|
|
||||||
<Compile Include="Api\ApiHelper.cs" />
|
|
||||||
<Compile Include="Api\ApiVersion.cs" />
|
<Compile Include="Api\ApiVersion.cs" />
|
||||||
<Compile Include="Api\GetModelsData.cs" />
|
|
||||||
<Compile Include="Api\ModelsBuilderApiController.cs" />
|
|
||||||
<Compile Include="Api\TokenData.cs" />
|
|
||||||
<Compile Include="Api\ValidateClientVersionData.cs" />
|
|
||||||
<Compile Include="Building\Builder.cs" />
|
<Compile Include="Building\Builder.cs" />
|
||||||
<Compile Include="Building\CodeDomBuilder.cs" />
|
|
||||||
<Compile Include="Building\CodeParser.cs" />
|
|
||||||
<Compile Include="Building\Compiler.cs" />
|
|
||||||
<Compile Include="Building\CompilerException.cs" />
|
|
||||||
<Compile Include="Building\ParseResult.cs" />
|
|
||||||
<Compile Include="Building\PropertyModel.cs" />
|
<Compile Include="Building\PropertyModel.cs" />
|
||||||
<Compile Include="Building\TextBuilder.cs" />
|
<Compile Include="Building\TextBuilder.cs" />
|
||||||
<Compile Include="Building\TextHeaderWriter.cs" />
|
<Compile Include="Building\TextHeaderWriter.cs" />
|
||||||
<Compile Include="Building\TypeModel.cs" />
|
<Compile Include="Building\TypeModel.cs" />
|
||||||
|
<Compile Include="ConfigsExtensions.cs" />
|
||||||
<Compile Include="Configuration\ClrNameSource.cs" />
|
<Compile Include="Configuration\ClrNameSource.cs" />
|
||||||
<Compile Include="Configuration\Config.cs" />
|
<Compile Include="Configuration\Config.cs" />
|
||||||
<Compile Include="Configuration\ModelsMode.cs" />
|
<Compile Include="Configuration\ModelsMode.cs" />
|
||||||
<Compile Include="Configuration\ModelsModeExtensions.cs" />
|
<Compile Include="Configuration\ModelsModeExtensions.cs" />
|
||||||
<Compile Include="Configuration\UmbracoConfigExtensions.cs" />
|
|
||||||
<Compile Include="Dashboard\BuilderDashboardHelper.cs" />
|
<Compile Include="Dashboard\BuilderDashboardHelper.cs" />
|
||||||
<Compile Include="EnumerableExtensions.cs" />
|
<Compile Include="EnumerableExtensions.cs" />
|
||||||
<Compile Include="IgnoreContentTypeAttribute.cs" />
|
|
||||||
<Compile Include="IgnorePropertyTypeAttribute.cs" />
|
|
||||||
<Compile Include="ImplementContentTypeAttribute.cs" />
|
|
||||||
<Compile Include="ImplementPropertyTypeAttribute.cs" />
|
<Compile Include="ImplementPropertyTypeAttribute.cs" />
|
||||||
<Compile Include="ModelsBaseClassAttribute.cs" />
|
|
||||||
<Compile Include="ModelsBuilderAssemblyAttribute.cs" />
|
<Compile Include="ModelsBuilderAssemblyAttribute.cs" />
|
||||||
<Compile Include="ModelsNamespaceAttribute.cs" />
|
|
||||||
<Compile Include="ModelsUsingAttribute.cs" />
|
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
<Compile Include="PublishedElementExtensions.cs" />
|
<Compile Include="PublishedElementExtensions.cs" />
|
||||||
<Compile Include="PublishedPropertyTypeExtensions.cs" />
|
|
||||||
<Compile Include="PureLiveAssemblyAttribute.cs" />
|
<Compile Include="PureLiveAssemblyAttribute.cs" />
|
||||||
<Compile Include="ReferencedAssemblies.cs" />
|
<Compile Include="ReferencedAssemblies.cs" />
|
||||||
<Compile Include="RenameContentTypeAttribute.cs" />
|
|
||||||
<Compile Include="RenamePropertyTypeAttribute.cs" />
|
|
||||||
<Compile Include="TypeExtensions.cs" />
|
<Compile Include="TypeExtensions.cs" />
|
||||||
<Compile Include="Umbraco\UmbracoServices.cs" />
|
|
||||||
<Compile Include="Umbraco\HashCombiner.cs" />
|
<Compile Include="Umbraco\HashCombiner.cs" />
|
||||||
<Compile Include="Umbraco\HashHelper.cs" />
|
<Compile Include="Umbraco\HashHelper.cs" />
|
||||||
<Compile Include="Umbraco\LiveModelsProvider.cs" />
|
<Compile Include="Umbraco\LiveModelsProvider.cs" />
|
||||||
<Compile Include="Umbraco\ModelsBuilderBackOfficeController.cs" />
|
<Compile Include="Umbraco\ModelsBuilderBackOfficeController.cs" />
|
||||||
<Compile Include="Umbraco\ModelsBuilderComponent.cs" />
|
<Compile Include="Umbraco\ModelsBuilderComponent.cs" />
|
||||||
|
<Compile Include="Umbraco\ModelsBuilderComposer.cs" />
|
||||||
|
<Compile Include="Umbraco\ModelsBuilderInitializer.cs" />
|
||||||
<Compile Include="Umbraco\ModelsGenerationError.cs" />
|
<Compile Include="Umbraco\ModelsGenerationError.cs" />
|
||||||
<Compile Include="Umbraco\OutOfDateModelsStatus.cs" />
|
<Compile Include="Umbraco\OutOfDateModelsStatus.cs" />
|
||||||
<Compile Include="Umbraco\PublishedModelUtility.cs" />
|
<Compile Include="Umbraco\PublishedModelUtility.cs" />
|
||||||
<Compile Include="Umbraco\PureLiveModelFactory.cs" />
|
<Compile Include="Umbraco\PureLiveModelFactory.cs" />
|
||||||
|
<Compile Include="Umbraco\UmbracoServices.cs" />
|
||||||
<Compile Include="Validation\ContentTypeModelValidator.cs" />
|
<Compile Include="Validation\ContentTypeModelValidator.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp">
|
|
||||||
<Version>2.8.0</Version>
|
|
||||||
</PackageReference>
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Umbraco.Core\Umbraco.Core.csproj">
|
<ProjectReference Include="..\Umbraco.Core\Umbraco.Core.csproj">
|
||||||
<Project>{31785bc3-256c-4613-b2f5-a1b0bdded8c1}</Project>
|
<Project>{31785BC3-256C-4613-B2F5-A1B0BDDED8C1}</Project>
|
||||||
<Name>Umbraco.Core</Name>
|
<Name>Umbraco.Core</Name>
|
||||||
</ProjectReference>
|
</ProjectReference>
|
||||||
<ProjectReference Include="..\Umbraco.Web\Umbraco.Web.csproj">
|
<ProjectReference Include="..\Umbraco.Web\Umbraco.Web.csproj">
|
||||||
@@ -114,5 +92,10 @@
|
|||||||
<Name>Umbraco.Web</Name>
|
<Name>Umbraco.Web</Name>
|
||||||
</ProjectReference>
|
</ProjectReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.AspNet.Mvc">
|
||||||
|
<Version>5.2.7</Version>
|
||||||
|
</PackageReference>
|
||||||
|
</ItemGroup>
|
||||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
</Project>
|
</Project>
|
||||||
@@ -6,13 +6,10 @@ namespace Umbraco.ModelsBuilder.Umbraco
|
|||||||
{
|
{
|
||||||
class HashHelper
|
class HashHelper
|
||||||
{
|
{
|
||||||
public static string Hash(IDictionary<string, string> ourFiles, IEnumerable<TypeModel> typeModels)
|
public static string Hash(IEnumerable<TypeModel> typeModels)
|
||||||
{
|
{
|
||||||
var hash = new HashCombiner();
|
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
|
// see Umbraco.ModelsBuilder.Umbraco.Application for what's important to hash
|
||||||
// ie what comes from Umbraco (not computed by ModelsBuilder) and makes a difference
|
// ie what comes from Umbraco (not computed by ModelsBuilder) and makes a difference
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ using System.Threading;
|
|||||||
using System.Web;
|
using System.Web;
|
||||||
using System.Web.Hosting;
|
using System.Web.Hosting;
|
||||||
using Umbraco.Core.Composing;
|
using Umbraco.Core.Composing;
|
||||||
using Umbraco.Core.Configuration;
|
|
||||||
using Umbraco.Core.Logging;
|
using Umbraco.Core.Logging;
|
||||||
using Umbraco.ModelsBuilder.Configuration;
|
using Umbraco.ModelsBuilder.Configuration;
|
||||||
using Umbraco.ModelsBuilder.Umbraco;
|
using Umbraco.ModelsBuilder.Umbraco;
|
||||||
@@ -21,15 +20,10 @@ namespace Umbraco.ModelsBuilder.Umbraco
|
|||||||
private static Mutex _mutex;
|
private static Mutex _mutex;
|
||||||
private static int _req;
|
private static int _req;
|
||||||
|
|
||||||
internal static bool IsEnabled
|
private static Config Config => Current.Configs.ModelsBuilder();
|
||||||
{
|
|
||||||
get
|
// we do not manage pure live here
|
||||||
{
|
internal static bool IsEnabled => Config.ModelsMode.IsLiveNotPure();
|
||||||
var config = UmbracoConfig.For.ModelsBuilder();
|
|
||||||
return config.ModelsMode.IsLiveNotPure();
|
|
||||||
// we do not manage pure live here
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static void Install(UmbracoServices umbracoServices)
|
internal static void Install(UmbracoServices umbracoServices)
|
||||||
{
|
{
|
||||||
@@ -104,16 +98,15 @@ namespace Umbraco.ModelsBuilder.Umbraco
|
|||||||
|
|
||||||
private static void GenerateModels()
|
private static void GenerateModels()
|
||||||
{
|
{
|
||||||
var modelsDirectory = UmbracoConfig.For.ModelsBuilder().ModelsDirectory;
|
var modelsDirectory = Config.ModelsDirectory;
|
||||||
|
var modelsNamespace = Config.ModelsNamespace;
|
||||||
|
|
||||||
var bin = HostingEnvironment.MapPath("~/bin");
|
var bin = HostingEnvironment.MapPath("~/bin");
|
||||||
if (bin == null)
|
if (bin == null)
|
||||||
throw new Exception("Panic: bin is 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
|
// EnableDllModels will recycle the app domain - but this request will end properly
|
||||||
ModelsBuilderBackOfficeController.GenerateModels(_umbracoServices, modelsDirectory, config.ModelsMode.IsAnyDll() ? bin : null);
|
ModelsBuilderBackOfficeController.GenerateModels(_umbracoServices, modelsDirectory, modelsNamespace);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,9 +126,7 @@ namespace Umbraco.ModelsBuilder.Umbraco
|
|||||||
|
|
||||||
public static void Install()
|
public static void Install()
|
||||||
{
|
{
|
||||||
if (!LiveModelsProvider.IsEnabled)
|
// always - don't read config in PreApplicationStartMethod
|
||||||
return;
|
|
||||||
|
|
||||||
HttpApplication.RegisterModule(typeof(LiveModelsProviderModule));
|
HttpApplication.RegisterModule(typeof(LiveModelsProviderModule));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ using System.Net.Http;
|
|||||||
using System.Runtime.Serialization;
|
using System.Runtime.Serialization;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Web.Hosting;
|
using System.Web.Hosting;
|
||||||
using Umbraco.Core.Configuration;
|
|
||||||
using Umbraco.ModelsBuilder.Building;
|
using Umbraco.ModelsBuilder.Building;
|
||||||
using Umbraco.ModelsBuilder.Configuration;
|
using Umbraco.ModelsBuilder.Configuration;
|
||||||
using Umbraco.ModelsBuilder.Dashboard;
|
using Umbraco.ModelsBuilder.Dashboard;
|
||||||
@@ -23,38 +22,42 @@ namespace Umbraco.ModelsBuilder.Umbraco
|
|||||||
/// correct CSRF security is adhered to for angular and it also ensures that this controller is not subseptipal to
|
/// 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.
|
/// global WebApi formatters being changed since this is always forced to only return Angular JSON Specific formats.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[UmbracoApplicationAuthorize(Core.Constants.Applications.Developer)]
|
[UmbracoApplicationAuthorize(Core.Constants.Applications.Settings)]
|
||||||
public class ModelsBuilderBackOfficeController : UmbracoAuthorizedJsonController
|
public class ModelsBuilderBackOfficeController : UmbracoAuthorizedJsonController
|
||||||
{
|
{
|
||||||
private readonly UmbracoServices _umbracoServices;
|
private readonly UmbracoServices _umbracoServices;
|
||||||
|
private readonly Config _config;
|
||||||
|
|
||||||
public ModelsBuilderBackOfficeController(UmbracoServices umbracoServices)
|
public ModelsBuilderBackOfficeController(UmbracoServices umbracoServices, Config config)
|
||||||
{
|
{
|
||||||
_umbracoServices = umbracoServices;
|
_umbracoServices = umbracoServices;
|
||||||
|
_config = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
// invoked by the dashboard
|
// invoked by the dashboard
|
||||||
// requires that the user is logged into the backoffice and has access to the developer section
|
// requires that the user is logged into the backoffice and has access to the settings section
|
||||||
// beware! the name of the method appears in modelsbuilder.controller.js
|
// beware! the name of the method appears in modelsbuilder.controller.js
|
||||||
[System.Web.Http.HttpPost] // use the http one, not mvc, with api controllers!
|
[System.Web.Http.HttpPost] // use the http one, not mvc, with api controllers!
|
||||||
public HttpResponseMessage BuildModels()
|
public HttpResponseMessage BuildModels()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!UmbracoConfig.For.ModelsBuilder().ModelsMode.SupportsExplicitGeneration())
|
var config = _config;
|
||||||
|
|
||||||
|
if (!config.ModelsMode.SupportsExplicitGeneration())
|
||||||
{
|
{
|
||||||
var result2 = new BuildResult { Success = false, Message = "Models generation is not enabled." };
|
var result2 = new BuildResult { Success = false, Message = "Models generation is not enabled." };
|
||||||
return Request.CreateResponse(HttpStatusCode.OK, result2, Configuration.Formatters.JsonFormatter);
|
return Request.CreateResponse(HttpStatusCode.OK, result2, Configuration.Formatters.JsonFormatter);
|
||||||
}
|
}
|
||||||
|
|
||||||
var modelsDirectory = UmbracoConfig.For.ModelsBuilder().ModelsDirectory;
|
var modelsDirectory = config.ModelsDirectory;
|
||||||
|
|
||||||
var bin = HostingEnvironment.MapPath("~/bin");
|
var bin = HostingEnvironment.MapPath("~/bin");
|
||||||
if (bin == null)
|
if (bin == null)
|
||||||
throw new Exception("Panic: bin is null.");
|
throw new Exception("Panic: bin is null.");
|
||||||
|
|
||||||
// EnableDllModels will recycle the app domain - but this request will end properly
|
// EnableDllModels will recycle the app domain - but this request will end properly
|
||||||
GenerateModels(modelsDirectory, UmbracoConfig.For.ModelsBuilder().ModelsMode.IsAnyDll() ? bin : null);
|
GenerateModels(modelsDirectory);
|
||||||
|
|
||||||
ModelsGenerationError.Clear();
|
ModelsGenerationError.Clear();
|
||||||
}
|
}
|
||||||
@@ -67,7 +70,7 @@ namespace Umbraco.ModelsBuilder.Umbraco
|
|||||||
}
|
}
|
||||||
|
|
||||||
// invoked by the back-office
|
// invoked by the back-office
|
||||||
// requires that the user is logged into the backoffice and has access to the developer section
|
// requires that the user is logged into the backoffice and has access to the settings section
|
||||||
[System.Web.Http.HttpGet] // use the http one, not mvc, with api controllers!
|
[System.Web.Http.HttpGet] // use the http one, not mvc, with api controllers!
|
||||||
public HttpResponseMessage GetModelsOutOfDateStatus()
|
public HttpResponseMessage GetModelsOutOfDateStatus()
|
||||||
{
|
{
|
||||||
@@ -81,7 +84,7 @@ namespace Umbraco.ModelsBuilder.Umbraco
|
|||||||
}
|
}
|
||||||
|
|
||||||
// invoked by the back-office
|
// invoked by the back-office
|
||||||
// requires that the user is logged into the backoffice and has access to the developer section
|
// requires that the user is logged into the backoffice and has access to the settings section
|
||||||
// beware! the name of the method appears in modelsbuilder.controller.js
|
// beware! the name of the method appears in modelsbuilder.controller.js
|
||||||
[System.Web.Http.HttpGet] // use the http one, not mvc, with api controllers!
|
[System.Web.Http.HttpGet] // use the http one, not mvc, with api controllers!
|
||||||
public HttpResponseMessage GetDashboard()
|
public HttpResponseMessage GetDashboard()
|
||||||
@@ -93,21 +96,20 @@ namespace Umbraco.ModelsBuilder.Umbraco
|
|||||||
{
|
{
|
||||||
return new Dashboard
|
return new Dashboard
|
||||||
{
|
{
|
||||||
Enable = UmbracoConfig.For.ModelsBuilder().Enable,
|
Enable = _config.Enable,
|
||||||
Text = BuilderDashboardHelper.Text(),
|
Text = BuilderDashboardHelper.Text(),
|
||||||
CanGenerate = BuilderDashboardHelper.CanGenerate(),
|
CanGenerate = BuilderDashboardHelper.CanGenerate(),
|
||||||
GenerateCausesRestart = BuilderDashboardHelper.GenerateCausesRestart(),
|
|
||||||
OutOfDateModels = BuilderDashboardHelper.AreModelsOutOfDate(),
|
OutOfDateModels = BuilderDashboardHelper.AreModelsOutOfDate(),
|
||||||
LastError = BuilderDashboardHelper.LastError(),
|
LastError = BuilderDashboardHelper.LastError(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GenerateModels(string modelsDirectory, string bin)
|
private void GenerateModels(string modelsDirectory)
|
||||||
{
|
{
|
||||||
GenerateModels(_umbracoServices, modelsDirectory, bin);
|
GenerateModels(_umbracoServices, modelsDirectory, _config.ModelsNamespace);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static void GenerateModels(UmbracoServices umbracoServices, string modelsDirectory, string bin)
|
internal static void GenerateModels(UmbracoServices umbracoServices, string modelsDirectory, string modelsNamespace)
|
||||||
{
|
{
|
||||||
if (!Directory.Exists(modelsDirectory))
|
if (!Directory.Exists(modelsDirectory))
|
||||||
Directory.CreateDirectory(modelsDirectory);
|
Directory.CreateDirectory(modelsDirectory);
|
||||||
@@ -117,9 +119,7 @@ namespace Umbraco.ModelsBuilder.Umbraco
|
|||||||
|
|
||||||
var typeModels = umbracoServices.GetAllTypes();
|
var typeModels = umbracoServices.GetAllTypes();
|
||||||
|
|
||||||
var ourFiles = Directory.GetFiles(modelsDirectory, "*.cs").ToDictionary(x => x, File.ReadAllText);
|
var builder = new TextBuilder(typeModels, modelsNamespace);
|
||||||
var parseResult = new CodeParser().ParseWithReferencedAssemblies(ourFiles);
|
|
||||||
var builder = new TextBuilder(typeModels, parseResult, UmbracoConfig.For.ModelsBuilder().ModelsNamespace);
|
|
||||||
|
|
||||||
foreach (var typeModel in builder.GetModelsToGenerate())
|
foreach (var typeModel in builder.GetModelsToGenerate())
|
||||||
{
|
{
|
||||||
@@ -140,14 +140,6 @@ namespace Umbraco.ModelsBuilder.Umbraco
|
|||||||
";
|
";
|
||||||
*/
|
*/
|
||||||
|
|
||||||
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();
|
OutOfDateModelsStatus.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,8 +161,6 @@ namespace Umbraco.ModelsBuilder.Umbraco
|
|||||||
public string Text;
|
public string Text;
|
||||||
[DataMember(Name = "canGenerate")]
|
[DataMember(Name = "canGenerate")]
|
||||||
public bool CanGenerate;
|
public bool CanGenerate;
|
||||||
[DataMember(Name = "generateCausesRestart")]
|
|
||||||
public bool GenerateCausesRestart;
|
|
||||||
[DataMember(Name = "outOfDateModels")]
|
[DataMember(Name = "outOfDateModels")]
|
||||||
public bool OutOfDateModels;
|
public bool OutOfDateModels;
|
||||||
[DataMember(Name = "lastError")]
|
[DataMember(Name = "lastError")]
|
||||||
|
|||||||
@@ -1,95 +1,56 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Reflection;
|
||||||
using System.Web;
|
using System.Web;
|
||||||
using System.Web.Mvc;
|
using System.Web.Mvc;
|
||||||
using System.Web.Routing;
|
using System.Web.Routing;
|
||||||
using LightInject;
|
|
||||||
using Umbraco.Core;
|
|
||||||
using Umbraco.Core.Components;
|
|
||||||
using Umbraco.Core.Composing;
|
using Umbraco.Core.Composing;
|
||||||
using Umbraco.Core.Configuration;
|
|
||||||
using Umbraco.Core.IO;
|
using Umbraco.Core.IO;
|
||||||
using Umbraco.Core.Models.PublishedContent;
|
|
||||||
using Umbraco.Core.Services;
|
using Umbraco.Core.Services;
|
||||||
using Umbraco.Core.Services.Implement;
|
using Umbraco.Core.Services.Implement;
|
||||||
using Umbraco.ModelsBuilder.Api;
|
|
||||||
using Umbraco.ModelsBuilder.Configuration;
|
using Umbraco.ModelsBuilder.Configuration;
|
||||||
using Umbraco.Web;
|
using Umbraco.Web;
|
||||||
using Umbraco.Web.PublishedCache.NuCache;
|
using Umbraco.Web.JavaScript;
|
||||||
using Umbraco.Web.UI.JavaScript;
|
using Umbraco.Web.Mvc;
|
||||||
|
|
||||||
namespace Umbraco.ModelsBuilder.Umbraco
|
namespace Umbraco.ModelsBuilder.Umbraco
|
||||||
{
|
{
|
||||||
[RequiredComponent(typeof(NuCacheComponent))]
|
public class ModelsBuilderComponent : IComponent
|
||||||
[RuntimeLevel(MinLevel = RuntimeLevel.Run)]
|
|
||||||
public class ModelsBuilderComponent : UmbracoComponentBase, IUmbracoCoreComponent
|
|
||||||
{
|
{
|
||||||
public override void Compose(Composition composition)
|
private readonly UmbracoServices _umbracoServices;
|
||||||
|
|
||||||
|
private readonly Config _config;
|
||||||
|
|
||||||
|
public ModelsBuilderComponent(UmbracoServices umbracoServices, Config config)
|
||||||
{
|
{
|
||||||
base.Compose(composition);
|
_umbracoServices = umbracoServices;
|
||||||
|
_config = config;
|
||||||
composition.Container.Register<UmbracoServices>(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<IRuntimeState>().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)
|
public void Initialize()
|
||||||
{
|
{
|
||||||
var config = UmbracoConfig.For.ModelsBuilder();
|
// always setup the dashboard
|
||||||
|
// note: UmbracoApiController instances are automatically registered
|
||||||
|
InstallServerVars();
|
||||||
|
|
||||||
if (config.Enable)
|
ContentModelBinder.ModelBindingException += ContentModelBinder_ModelBindingException;
|
||||||
|
|
||||||
|
if (_config.Enable)
|
||||||
FileService.SavingTemplate += FileService_SavingTemplate;
|
FileService.SavingTemplate += FileService_SavingTemplate;
|
||||||
|
|
||||||
// fixme LiveModelsProvider should not be static
|
// fixme LiveModelsProvider should not be static
|
||||||
if (config.ModelsMode.IsLiveNotPure())
|
if (_config.ModelsMode.IsLiveNotPure())
|
||||||
LiveModelsProvider.Install(umbracoServices);
|
LiveModelsProvider.Install(_umbracoServices);
|
||||||
|
|
||||||
// fixme OutOfDateModelsStatus should not be static
|
// fixme OutOfDateModelsStatus should not be static
|
||||||
if (config.FlagOutOfDateModels)
|
if (_config.FlagOutOfDateModels)
|
||||||
OutOfDateModelsStatus.Install();
|
OutOfDateModelsStatus.Install();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ComposeForDefaultModelsFactory(IServiceContainer container)
|
public void Terminate()
|
||||||
{
|
{ }
|
||||||
container.RegisterSingleton<IPublishedModelFactory>(factory
|
|
||||||
=> new PublishedModelFactory(factory.GetInstance<TypeLoader>().GetTypes<PublishedContentModel>()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ComposeForLiveModels(IServiceContainer container)
|
private void InstallServerVars()
|
||||||
{
|
|
||||||
container.RegisterSingleton<IPublishedModelFactory, PureLiveModelFactory>();
|
|
||||||
|
|
||||||
// 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
|
// register our url - for the backoffice api
|
||||||
ServerVariablesParser.Parsing += (sender, serverVars) =>
|
ServerVariablesParser.Parsing += (sender, serverVars) =>
|
||||||
@@ -111,18 +72,15 @@ namespace Umbraco.ModelsBuilder.Umbraco
|
|||||||
var urlHelper = new UrlHelper(new RequestContext(new HttpContextWrapper(HttpContext.Current), new RouteData()));
|
var urlHelper = new UrlHelper(new RequestContext(new HttpContextWrapper(HttpContext.Current), new RouteData()));
|
||||||
|
|
||||||
umbracoUrls["modelsBuilderBaseUrl"] = urlHelper.GetUmbracoApiServiceBaseUrl<ModelsBuilderBackOfficeController>(controller => controller.BuildModels());
|
umbracoUrls["modelsBuilderBaseUrl"] = urlHelper.GetUmbracoApiServiceBaseUrl<ModelsBuilderBackOfficeController>(controller => controller.BuildModels());
|
||||||
umbracoPlugins["modelsBuilder"] = GetModelsBuilderSettings(level);
|
umbracoPlugins["modelsBuilder"] = GetModelsBuilderSettings();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private Dictionary<string, object> GetModelsBuilderSettings(RuntimeLevel level)
|
private Dictionary<string, object> GetModelsBuilderSettings()
|
||||||
{
|
{
|
||||||
if (level != RuntimeLevel.Run)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
var settings = new Dictionary<string, object>
|
var settings = new Dictionary<string, object>
|
||||||
{
|
{
|
||||||
{"enabled", UmbracoConfig.For.ModelsBuilder().Enable}
|
{"enabled", _config.Enable}
|
||||||
};
|
};
|
||||||
|
|
||||||
return settings;
|
return settings;
|
||||||
@@ -138,7 +96,7 @@ namespace Umbraco.ModelsBuilder.Umbraco
|
|||||||
{
|
{
|
||||||
// don't do anything if the factory is not enabled
|
// don't do anything if the factory is not enabled
|
||||||
// because, no factory = no models (even if generation is enabled)
|
// because, no factory = no models (even if generation is enabled)
|
||||||
if (!UmbracoConfig.For.ModelsBuilder().EnableFactory) return;
|
if (!_config.EnableFactory) return;
|
||||||
|
|
||||||
// don't do anything if this special key is not found
|
// don't do anything if this special key is not found
|
||||||
if (!e.AdditionalData.ContainsKey("CreateTemplateForContentType")) return;
|
if (!e.AdditionalData.ContainsKey("CreateTemplateForContentType")) return;
|
||||||
@@ -159,7 +117,7 @@ namespace Umbraco.ModelsBuilder.Umbraco
|
|||||||
var name = template.Name; // will be the name of the content type since we are creating
|
var name = template.Name; // will be the name of the content type since we are creating
|
||||||
var className = UmbracoServices.GetClrName(name, alias);
|
var className = UmbracoServices.GetClrName(name, alias);
|
||||||
|
|
||||||
var modelNamespace = UmbracoConfig.For.ModelsBuilder().ModelsNamespace;
|
var modelNamespace = _config.ModelsNamespace;
|
||||||
|
|
||||||
// we do not support configuring this at the moment, so just let Umbraco use its default value
|
// we do not support configuring this at the moment, so just let Umbraco use its default value
|
||||||
//var modelNamespaceAlias = ...;
|
//var modelNamespaceAlias = ...;
|
||||||
@@ -174,5 +132,55 @@ namespace Umbraco.ModelsBuilder.Umbraco
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ContentModelBinder_ModelBindingException(object sender, ContentModelBinder.ModelBindingArgs args)
|
||||||
|
{
|
||||||
|
var sourceAttr = args.SourceType.Assembly.GetCustomAttribute<ModelsBuilderAssemblyAttribute>();
|
||||||
|
var modelAttr = args.ModelType.Assembly.GetCustomAttribute<ModelsBuilderAssemblyAttribute>();
|
||||||
|
|
||||||
|
// if source or model is not a ModelsBuider type...
|
||||||
|
if (sourceAttr == null || modelAttr == null)
|
||||||
|
{
|
||||||
|
// if neither are ModelsBuilder types, give up entirely
|
||||||
|
if (sourceAttr == null && modelAttr == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// else report, but better not restart (loops?)
|
||||||
|
args.Message.Append(" The ");
|
||||||
|
args.Message.Append(sourceAttr == null ? "view model" : "source");
|
||||||
|
args.Message.Append(" is a ModelsBuilder type, but the ");
|
||||||
|
args.Message.Append(sourceAttr != null ? "view model" : "source");
|
||||||
|
args.Message.Append(" is not. The application is in an unstable state and should be restarted.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// both are ModelsBuilder types
|
||||||
|
var pureSource = sourceAttr.PureLive;
|
||||||
|
var pureModel = modelAttr.PureLive;
|
||||||
|
|
||||||
|
if (sourceAttr.PureLive || modelAttr.PureLive)
|
||||||
|
{
|
||||||
|
if (pureSource == false || pureModel == false)
|
||||||
|
{
|
||||||
|
// only one is pure - report, but better not restart (loops?)
|
||||||
|
args.Message.Append(pureSource
|
||||||
|
? " The content model is PureLive, but the view model is not."
|
||||||
|
: " The view model is PureLive, but the content model is not.");
|
||||||
|
args.Message.Append(" The application is in an unstable state and should be restarted.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// both are pure - report, and if different versions, restart
|
||||||
|
// if same version... makes no sense... and better not restart (loops?)
|
||||||
|
var sourceVersion = args.SourceType.Assembly.GetName().Version;
|
||||||
|
var modelVersion = args.ModelType.Assembly.GetName().Version;
|
||||||
|
args.Message.Append(" Both view and content models are PureLive, with ");
|
||||||
|
args.Message.Append(sourceVersion == modelVersion
|
||||||
|
? "same version. The application is in an unstable state and should be restarted."
|
||||||
|
: "different versions. The application is in an unstable state and is going to be restarted.");
|
||||||
|
args.Restart = sourceVersion != modelVersion;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using Umbraco.Core;
|
||||||
|
using Umbraco.Core.Composing;
|
||||||
|
using Umbraco.Core.Models.PublishedContent;
|
||||||
|
using Umbraco.ModelsBuilder.Configuration;
|
||||||
|
using Umbraco.Web.PublishedCache.NuCache;
|
||||||
|
|
||||||
|
namespace Umbraco.ModelsBuilder.Umbraco
|
||||||
|
{
|
||||||
|
[ComposeBefore(typeof(NuCacheComposer))]
|
||||||
|
[RuntimeLevel(MinLevel = RuntimeLevel.Run)]
|
||||||
|
public sealed class ModelsBuilderComposer : ComponentComposer<ModelsBuilderComponent>, ICoreComposer
|
||||||
|
{
|
||||||
|
public override void Compose(Composition composition)
|
||||||
|
{
|
||||||
|
base.Compose(composition);
|
||||||
|
|
||||||
|
composition.Register<UmbracoServices>(Lifetime.Singleton);
|
||||||
|
composition.Configs.Add(() => new Config());
|
||||||
|
|
||||||
|
if (composition.Configs.ModelsBuilder().ModelsMode == ModelsMode.PureLive)
|
||||||
|
ComposeForLiveModels(composition);
|
||||||
|
else if (composition.Configs.ModelsBuilder().EnableFactory)
|
||||||
|
ComposeForDefaultModelsFactory(composition);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ComposeForDefaultModelsFactory(Composition composition)
|
||||||
|
{
|
||||||
|
composition.RegisterUnique<IPublishedModelFactory>(factory =>
|
||||||
|
{
|
||||||
|
var typeLoader = factory.GetInstance<TypeLoader>();
|
||||||
|
var types = typeLoader
|
||||||
|
.GetTypes<PublishedElementModel>() // element models
|
||||||
|
.Concat(typeLoader.GetTypes<PublishedContentModel>()); // content models
|
||||||
|
return new PublishedModelFactory(types);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ComposeForLiveModels(Composition composition)
|
||||||
|
{
|
||||||
|
composition.RegisterUnique<IPublishedModelFactory, PureLiveModelFactory>();
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
using System.Web;
|
||||||
|
using System.Web.Compilation;
|
||||||
|
using Umbraco.ModelsBuilder.Umbraco;
|
||||||
|
|
||||||
|
[assembly: PreApplicationStartMethod(typeof(ModelsBuilderInitializer), "Initialize")]
|
||||||
|
|
||||||
|
namespace Umbraco.ModelsBuilder.Umbraco
|
||||||
|
{
|
||||||
|
public static class ModelsBuilderInitializer
|
||||||
|
{
|
||||||
|
public static void Initialize()
|
||||||
|
{
|
||||||
|
// for some reason, netstandard is missing from BuildManager.ReferencedAssemblies and yet, is part of
|
||||||
|
// the references that CSharpCompiler receives - in some cases eg when building views - but not when
|
||||||
|
// using BuildManager to build the PureLive models - where is it coming from? cannot figure it out
|
||||||
|
|
||||||
|
// so... cheating here
|
||||||
|
|
||||||
|
// this is equivalent to adding
|
||||||
|
// <add assembly="netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51" />
|
||||||
|
// to web.config system.web/compilation/assemblies
|
||||||
|
|
||||||
|
var netStandard = ReferencedAssemblies.GetNetStandardAssembly();
|
||||||
|
if (netStandard != null)
|
||||||
|
BuildManager.AddReferencedAssembly(netStandard);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +1,15 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Umbraco.Core.Configuration;
|
using Umbraco.Core.Composing;
|
||||||
using Umbraco.ModelsBuilder.Configuration;
|
using Umbraco.ModelsBuilder.Configuration;
|
||||||
|
|
||||||
namespace Umbraco.ModelsBuilder.Umbraco
|
namespace Umbraco.ModelsBuilder.Umbraco
|
||||||
{
|
{
|
||||||
internal static class ModelsGenerationError
|
internal static class ModelsGenerationError
|
||||||
{
|
{
|
||||||
|
private static Config Config => Current.Configs.ModelsBuilder();
|
||||||
|
|
||||||
public static void Clear()
|
public static void Clear()
|
||||||
{
|
{
|
||||||
var errFile = GetErrFile();
|
var errFile = GetErrFile();
|
||||||
@@ -50,7 +52,7 @@ namespace Umbraco.ModelsBuilder.Umbraco
|
|||||||
|
|
||||||
private static string GetErrFile()
|
private static string GetErrFile()
|
||||||
{
|
{
|
||||||
var modelsDirectory = UmbracoConfig.For.ModelsBuilder().ModelsDirectory;
|
var modelsDirectory = Config.ModelsDirectory;
|
||||||
if (!Directory.Exists(modelsDirectory))
|
if (!Directory.Exists(modelsDirectory))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
using System;
|
using System.IO;
|
||||||
using System.IO;
|
using Umbraco.Core.Composing;
|
||||||
using System.Web.Hosting;
|
|
||||||
using Umbraco.Core;
|
|
||||||
using Umbraco.Core.Configuration;
|
|
||||||
using Umbraco.ModelsBuilder.Configuration;
|
using Umbraco.ModelsBuilder.Configuration;
|
||||||
using Umbraco.Web.Cache;
|
using Umbraco.Web.Cache;
|
||||||
|
|
||||||
@@ -10,10 +7,12 @@ namespace Umbraco.ModelsBuilder.Umbraco
|
|||||||
{
|
{
|
||||||
public sealed class OutOfDateModelsStatus
|
public sealed class OutOfDateModelsStatus
|
||||||
{
|
{
|
||||||
|
private static Config Config => Current.Configs.ModelsBuilder();
|
||||||
|
|
||||||
internal static void Install()
|
internal static void Install()
|
||||||
{
|
{
|
||||||
// just be sure
|
// just be sure
|
||||||
if (UmbracoConfig.For.ModelsBuilder().FlagOutOfDateModels == false)
|
if (Config.FlagOutOfDateModels == false)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ContentTypeCacheRefresher.CacheUpdated += (sender, args) => Write();
|
ContentTypeCacheRefresher.CacheUpdated += (sender, args) => Write();
|
||||||
@@ -22,7 +21,7 @@ namespace Umbraco.ModelsBuilder.Umbraco
|
|||||||
|
|
||||||
private static string GetFlagPath()
|
private static string GetFlagPath()
|
||||||
{
|
{
|
||||||
var modelsDirectory = UmbracoConfig.For.ModelsBuilder().ModelsDirectory;
|
var modelsDirectory = Config.ModelsDirectory;
|
||||||
if (!Directory.Exists(modelsDirectory))
|
if (!Directory.Exists(modelsDirectory))
|
||||||
Directory.CreateDirectory(modelsDirectory);
|
Directory.CreateDirectory(modelsDirectory);
|
||||||
return Path.Combine(modelsDirectory, "ood.flag");
|
return Path.Combine(modelsDirectory, "ood.flag");
|
||||||
@@ -37,19 +36,19 @@ namespace Umbraco.ModelsBuilder.Umbraco
|
|||||||
|
|
||||||
public static void Clear()
|
public static void Clear()
|
||||||
{
|
{
|
||||||
if (UmbracoConfig.For.ModelsBuilder().FlagOutOfDateModels == false) return;
|
if (Config.FlagOutOfDateModels == false) return;
|
||||||
var path = GetFlagPath();
|
var path = GetFlagPath();
|
||||||
if (path == null || !File.Exists(path)) return;
|
if (path == null || !File.Exists(path)) return;
|
||||||
File.Delete(path);
|
File.Delete(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool IsEnabled => UmbracoConfig.For.ModelsBuilder().FlagOutOfDateModels;
|
public static bool IsEnabled => Config.FlagOutOfDateModels;
|
||||||
|
|
||||||
public static bool IsOutOfDate
|
public static bool IsOutOfDate
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (UmbracoConfig.For.ModelsBuilder().FlagOutOfDateModels == false) return false;
|
if (Config.FlagOutOfDateModels == false) return false;
|
||||||
var path = GetFlagPath();
|
var path = GetFlagPath();
|
||||||
return path != null && File.Exists(path);
|
return path != null && File.Exists(path);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ namespace Umbraco.ModelsBuilder.Umbraco
|
|||||||
// // etc...
|
// // etc...
|
||||||
//}
|
//}
|
||||||
|
|
||||||
public static PublishedContentType GetModelContentType(PublishedItemType itemType, string alias)
|
public static IPublishedContentType GetModelContentType(PublishedItemType itemType, string alias)
|
||||||
{
|
{
|
||||||
var facade = Current.UmbracoContext.PublishedSnapshot; // fixme inject!
|
var facade = Current.UmbracoContext.PublishedSnapshot; // fixme inject!
|
||||||
switch (itemType)
|
switch (itemType)
|
||||||
@@ -40,7 +40,7 @@ namespace Umbraco.ModelsBuilder.Umbraco
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static PublishedPropertyType GetModelPropertyType<TModel, TValue>(PublishedContentType contentType, Expression<Func<TModel, TValue>> selector)
|
public static IPublishedPropertyType GetModelPropertyType<TModel, TValue>(PublishedContentType contentType, Expression<Func<TModel, TValue>> selector)
|
||||||
//where TModel : PublishedContentModel // fixme PublishedContentModel _or_ PublishedElementModel
|
//where TModel : PublishedContentModel // fixme PublishedContentModel _or_ PublishedElementModel
|
||||||
{
|
{
|
||||||
// fixme therefore, missing a check on TModel here
|
// fixme therefore, missing a check on TModel here
|
||||||
|
|||||||
@@ -3,19 +3,18 @@ using System.Collections;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Linq.Expressions;
|
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Reflection.Emit;
|
using System.Reflection.Emit;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using System.Web;
|
||||||
using System.Web.Compilation;
|
using System.Web.Compilation;
|
||||||
using System.Web.Hosting;
|
using System.Web.Hosting;
|
||||||
using System.Web.WebPages.Razor;
|
using System.Web.WebPages.Razor;
|
||||||
using Umbraco.Core;
|
using Umbraco.Core;
|
||||||
using Umbraco.Core.Configuration;
|
using Umbraco.Core.Composing;
|
||||||
using Umbraco.Core.Logging;
|
using Umbraco.Core.Logging;
|
||||||
using Umbraco.Core.Models;
|
|
||||||
using Umbraco.Core.Models.PublishedContent;
|
using Umbraco.Core.Models.PublishedContent;
|
||||||
using Umbraco.Web.Cache;
|
using Umbraco.Web.Cache;
|
||||||
using Umbraco.ModelsBuilder.Building;
|
using Umbraco.ModelsBuilder.Building;
|
||||||
@@ -24,14 +23,14 @@ using File = System.IO.File;
|
|||||||
|
|
||||||
namespace Umbraco.ModelsBuilder.Umbraco
|
namespace Umbraco.ModelsBuilder.Umbraco
|
||||||
{
|
{
|
||||||
internal class PureLiveModelFactory : IPublishedModelFactory, IRegisteredObject
|
internal class PureLiveModelFactory : ILivePublishedModelFactory, IRegisteredObject
|
||||||
{
|
{
|
||||||
private Assembly _modelsAssembly;
|
private Assembly _modelsAssembly;
|
||||||
private Infos _infos = new Infos { ModelInfos = null, ModelTypeMap = new Dictionary<string, Type>() };
|
private Infos _infos = new Infos { ModelInfos = null, ModelTypeMap = new Dictionary<string, Type>() };
|
||||||
private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim();
|
private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim();
|
||||||
private volatile bool _hasModels; // volatile 'cos reading outside lock
|
private volatile bool _hasModels; // volatile 'cos reading outside lock
|
||||||
private bool _pendingRebuild;
|
private bool _pendingRebuild;
|
||||||
private readonly ProfilingLogger _logger;
|
private readonly IProfilingLogger _logger;
|
||||||
private readonly FileSystemWatcher _watcher;
|
private readonly FileSystemWatcher _watcher;
|
||||||
private int _ver, _skipver;
|
private int _ver, _skipver;
|
||||||
private readonly int _debugLevel;
|
private readonly int _debugLevel;
|
||||||
@@ -43,19 +42,21 @@ namespace Umbraco.ModelsBuilder.Umbraco
|
|||||||
private const string ProjVirt = "~/App_Data/Models/all.generated.cs";
|
private const string ProjVirt = "~/App_Data/Models/all.generated.cs";
|
||||||
private static readonly string[] OurFiles = { "models.hash", "models.generated.cs", "all.generated.cs", "all.dll.path", "models.err" };
|
private static readonly string[] OurFiles = { "models.hash", "models.generated.cs", "all.generated.cs", "all.dll.path", "models.err" };
|
||||||
|
|
||||||
public PureLiveModelFactory(Lazy<UmbracoServices> umbracoServices, ProfilingLogger logger)
|
private readonly Config _config;
|
||||||
|
|
||||||
|
public PureLiveModelFactory(Lazy<UmbracoServices> umbracoServices, IProfilingLogger logger, Config config)
|
||||||
{
|
{
|
||||||
_umbracoServices = umbracoServices;
|
_umbracoServices = umbracoServices;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
_config = config;
|
||||||
_ver = 1; // zero is for when we had no version
|
_ver = 1; // zero is for when we had no version
|
||||||
_skipver = -1; // nothing to skip
|
_skipver = -1; // nothing to skip
|
||||||
ContentTypeCacheRefresher.CacheUpdated += (sender, args) => ResetModels();
|
|
||||||
DataTypeCacheRefresher.CacheUpdated += (sender, args) => ResetModels();
|
|
||||||
RazorBuildProvider.CodeGenerationStarted += RazorBuildProvider_CodeGenerationStarted;
|
RazorBuildProvider.CodeGenerationStarted += RazorBuildProvider_CodeGenerationStarted;
|
||||||
|
|
||||||
if (!HostingEnvironment.IsHosted) return;
|
if (!HostingEnvironment.IsHosted) return;
|
||||||
|
|
||||||
var modelsDirectory = UmbracoConfig.For.ModelsBuilder().ModelsDirectory;
|
var modelsDirectory = _config.ModelsDirectory;
|
||||||
if (!Directory.Exists(modelsDirectory))
|
if (!Directory.Exists(modelsDirectory))
|
||||||
Directory.CreateDirectory(modelsDirectory);
|
Directory.CreateDirectory(modelsDirectory);
|
||||||
|
|
||||||
@@ -68,9 +69,23 @@ namespace Umbraco.ModelsBuilder.Umbraco
|
|||||||
_watcher.EnableRaisingEvents = true;
|
_watcher.EnableRaisingEvents = true;
|
||||||
|
|
||||||
// get it here, this need to be fast
|
// get it here, this need to be fast
|
||||||
_debugLevel = UmbracoConfig.For.ModelsBuilder().DebugLevel;
|
_debugLevel = _config.DebugLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region ILivePublishedModelFactory
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public object SyncRoot { get; } = new object();
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Refresh()
|
||||||
|
{
|
||||||
|
ResetModels();
|
||||||
|
EnsureModels();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region IPublishedModelFactory
|
#region IPublishedModelFactory
|
||||||
|
|
||||||
public IPublishedElement CreateModel(IPublishedElement element)
|
public IPublishedElement CreateModel(IPublishedElement element)
|
||||||
@@ -115,7 +130,7 @@ namespace Umbraco.ModelsBuilder.Umbraco
|
|||||||
if (ctor != null) return ctor();
|
if (ctor != null) return ctor();
|
||||||
|
|
||||||
var listType = typeof(List<>).MakeGenericType(modelInfo.ModelType);
|
var listType = typeof(List<>).MakeGenericType(modelInfo.ModelType);
|
||||||
ctor = modelInfo.ListCtor = ReflectionUtilities.EmitCtor<Func<IList>>(declaring: listType);
|
ctor = modelInfo.ListCtor = ReflectionUtilities.EmitConstructor<Func<IList>>(declaring: listType);
|
||||||
return ctor();
|
return ctor();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,7 +178,7 @@ namespace Umbraco.ModelsBuilder.Umbraco
|
|||||||
if (_modelsAssembly == null) return;
|
if (_modelsAssembly == null) return;
|
||||||
|
|
||||||
if (_debugLevel > 0)
|
if (_debugLevel > 0)
|
||||||
_logger.Logger.Debug<PureLiveModelFactory>("RazorBuildProvider.CodeGenerationStarted");
|
_logger.Debug<PureLiveModelFactory>("RazorBuildProvider.CodeGenerationStarted");
|
||||||
if (!(sender is RazorBuildProvider provider)) return;
|
if (!(sender is RazorBuildProvider provider)) return;
|
||||||
|
|
||||||
// add the assembly, and add a dependency to a text file that will change on each
|
// add the assembly, and add a dependency to a text file that will change on each
|
||||||
@@ -182,7 +197,7 @@ namespace Umbraco.ModelsBuilder.Umbraco
|
|||||||
// tells the factory that it should build a new generation of models
|
// tells the factory that it should build a new generation of models
|
||||||
private void ResetModels()
|
private void ResetModels()
|
||||||
{
|
{
|
||||||
_logger.Logger.Debug<PureLiveModelFactory>("Resetting models.");
|
_logger.Debug<PureLiveModelFactory>("Resetting models.");
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -190,6 +205,19 @@ namespace Umbraco.ModelsBuilder.Umbraco
|
|||||||
|
|
||||||
_hasModels = false;
|
_hasModels = false;
|
||||||
_pendingRebuild = true;
|
_pendingRebuild = true;
|
||||||
|
|
||||||
|
var modelsDirectory = _config.ModelsDirectory;
|
||||||
|
if (!Directory.Exists(modelsDirectory))
|
||||||
|
Directory.CreateDirectory(modelsDirectory);
|
||||||
|
|
||||||
|
// clear stuff
|
||||||
|
var modelsHashFile = Path.Combine(modelsDirectory, "models.hash");
|
||||||
|
//var modelsSrcFile = Path.Combine(modelsDirectory, "models.generated.cs");
|
||||||
|
//var projFile = Path.Combine(modelsDirectory, "all.generated.cs");
|
||||||
|
var dllPathFile = Path.Combine(modelsDirectory, "all.dll.path");
|
||||||
|
|
||||||
|
if (File.Exists(dllPathFile)) File.Delete(dllPathFile);
|
||||||
|
if (File.Exists(modelsHashFile)) File.Delete(modelsHashFile);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -216,7 +244,7 @@ namespace Umbraco.ModelsBuilder.Umbraco
|
|||||||
internal Infos EnsureModels()
|
internal Infos EnsureModels()
|
||||||
{
|
{
|
||||||
if (_debugLevel > 0)
|
if (_debugLevel > 0)
|
||||||
_logger.Logger.Debug<PureLiveModelFactory>("Ensuring models.");
|
_logger.Debug<PureLiveModelFactory>("Ensuring models.");
|
||||||
|
|
||||||
// don't use an upgradeable lock here because only 1 thread at a time could enter it
|
// don't use an upgradeable lock here because only 1 thread at a time could enter it
|
||||||
try
|
try
|
||||||
@@ -270,8 +298,8 @@ namespace Umbraco.ModelsBuilder.Umbraco
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_logger.Logger.Error<PureLiveModelFactory>("Failed to build models.", e);
|
_logger.Error<PureLiveModelFactory>("Failed to build models.", e);
|
||||||
_logger.Logger.Warn<PureLiveModelFactory>("Running without models."); // be explicit
|
_logger.Warn<PureLiveModelFactory>("Running without models."); // be explicit
|
||||||
ModelsGenerationError.Report("Failed to build PureLive models.", e);
|
ModelsGenerationError.Report("Failed to build PureLive models.", e);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
@@ -300,19 +328,12 @@ namespace Umbraco.ModelsBuilder.Umbraco
|
|||||||
|
|
||||||
private Assembly GetModelsAssembly(bool forceRebuild)
|
private Assembly GetModelsAssembly(bool forceRebuild)
|
||||||
{
|
{
|
||||||
var modelsDirectory = UmbracoConfig.For.ModelsBuilder().ModelsDirectory;
|
var modelsDirectory = _config.ModelsDirectory;
|
||||||
if (!Directory.Exists(modelsDirectory))
|
if (!Directory.Exists(modelsDirectory))
|
||||||
Directory.CreateDirectory(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<string, string>();
|
|
||||||
|
|
||||||
var typeModels = UmbracoServices.GetAllTypes();
|
var typeModels = UmbracoServices.GetAllTypes();
|
||||||
var currentHash = HashHelper.Hash(ourFiles, typeModels);
|
var currentHash = HashHelper.Hash(typeModels);
|
||||||
var modelsHashFile = Path.Combine(modelsDirectory, "models.hash");
|
var modelsHashFile = Path.Combine(modelsDirectory, "models.hash");
|
||||||
var modelsSrcFile = Path.Combine(modelsDirectory, "models.generated.cs");
|
var modelsSrcFile = Path.Combine(modelsDirectory, "models.generated.cs");
|
||||||
var projFile = Path.Combine(modelsDirectory, "all.generated.cs");
|
var projFile = Path.Combine(modelsDirectory, "all.generated.cs");
|
||||||
@@ -323,31 +344,48 @@ namespace Umbraco.ModelsBuilder.Umbraco
|
|||||||
|
|
||||||
if (!forceRebuild)
|
if (!forceRebuild)
|
||||||
{
|
{
|
||||||
_logger.Logger.Debug<PureLiveModelFactory>("Looking for cached models.");
|
_logger.Debug<PureLiveModelFactory>("Looking for cached models.");
|
||||||
if (File.Exists(modelsHashFile) && File.Exists(projFile))
|
if (File.Exists(modelsHashFile) && File.Exists(projFile))
|
||||||
{
|
{
|
||||||
var cachedHash = File.ReadAllText(modelsHashFile);
|
var cachedHash = File.ReadAllText(modelsHashFile);
|
||||||
if (currentHash != cachedHash)
|
if (currentHash != cachedHash)
|
||||||
{
|
{
|
||||||
_logger.Logger.Debug<PureLiveModelFactory>("Found obsolete cached models.");
|
_logger.Debug<PureLiveModelFactory>("Found obsolete cached models.");
|
||||||
forceRebuild = true;
|
forceRebuild = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// else cachedHash matches currentHash, we can try to load an existing dll
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_logger.Logger.Debug<PureLiveModelFactory>("Could not find cached models.");
|
_logger.Debug<PureLiveModelFactory>("Could not find cached models.");
|
||||||
forceRebuild = true;
|
forceRebuild = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Assembly assembly;
|
Assembly assembly;
|
||||||
if (forceRebuild == false)
|
if (!forceRebuild)
|
||||||
{
|
{
|
||||||
// try to load the dll directly (avoid rebuilding)
|
// try to load the dll directly (avoid rebuilding)
|
||||||
|
//
|
||||||
|
// ensure that the .dll file does not have a corresponding .dll.delete file
|
||||||
|
// as that would mean the the .dll file is going to be deleted and should not
|
||||||
|
// be re-used - that should not happen in theory, but better be safe
|
||||||
|
//
|
||||||
|
// ensure that the .dll file is in the current codegen directory - when IIS
|
||||||
|
// or Express does a full restart, it can switch to an entirely new codegen
|
||||||
|
// directory, and then we end up referencing a dll which is *not* in that
|
||||||
|
// directory, and BuildManager fails to instantiate views ("the view found
|
||||||
|
// at ... was not created").
|
||||||
|
//
|
||||||
if (File.Exists(dllPathFile))
|
if (File.Exists(dllPathFile))
|
||||||
{
|
{
|
||||||
var dllPath = File.ReadAllText(dllPathFile);
|
var dllPath = File.ReadAllText(dllPathFile);
|
||||||
if (File.Exists(dllPath))
|
var codegen = HttpRuntime.CodegenDir;
|
||||||
|
|
||||||
|
_logger.Debug<PureLiveModelFactory>($"Cached models dll at {dllPath}.");
|
||||||
|
|
||||||
|
if (File.Exists(dllPath) && !File.Exists(dllPath + ".delete") && dllPath.StartsWith(codegen))
|
||||||
{
|
{
|
||||||
assembly = Assembly.LoadFile(dllPath);
|
assembly = Assembly.LoadFile(dllPath);
|
||||||
var attr = assembly.GetCustomAttribute<ModelsBuilderAssemblyAttribute>();
|
var attr = assembly.GetCustomAttribute<ModelsBuilderAssemblyAttribute>();
|
||||||
@@ -359,13 +397,23 @@ namespace Umbraco.ModelsBuilder.Umbraco
|
|||||||
// with the "same but different" version of the assembly in memory
|
// with the "same but different" version of the assembly in memory
|
||||||
_skipver = assembly.GetName().Version.Revision;
|
_skipver = assembly.GetName().Version.Revision;
|
||||||
|
|
||||||
_logger.Logger.Debug<PureLiveModelFactory>("Loading cached models (dll).");
|
_logger.Debug<PureLiveModelFactory>("Loading cached models (dll).");
|
||||||
return assembly;
|
return assembly;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_logger.Debug<PureLiveModelFactory>("Cached models dll cannot be loaded (invalid assembly).");
|
||||||
}
|
}
|
||||||
|
else if (!File.Exists(dllPath))
|
||||||
|
_logger.Debug<PureLiveModelFactory>("Cached models dll does not exist.");
|
||||||
|
else if (File.Exists(dllPath + ".delete"))
|
||||||
|
_logger.Debug<PureLiveModelFactory>("Cached models dll is marked for deletion.");
|
||||||
|
else if (!dllPath.StartsWith(codegen))
|
||||||
|
_logger.Debug<PureLiveModelFactory>("Cached models dll is in a different codegen directory.");
|
||||||
|
else
|
||||||
|
_logger.Debug<PureLiveModelFactory>("Cached models dll cannot be loaded (why?).");
|
||||||
}
|
}
|
||||||
|
|
||||||
// mmust reset the version in the file else it would keep growing
|
// must reset the version in the file else it would keep growing
|
||||||
// loading cached modules only happens when the app restarts
|
// loading cached modules only happens when the app restarts
|
||||||
var text = File.ReadAllText(projFile);
|
var text = File.ReadAllText(projFile);
|
||||||
var match = AssemblyVersionRegex.Match(text);
|
var match = AssemblyVersionRegex.Match(text);
|
||||||
@@ -381,18 +429,26 @@ namespace Umbraco.ModelsBuilder.Umbraco
|
|||||||
//File.WriteAllText(Path.Combine(modelsDirectory, "models.dep"), "VER:" + _ver);
|
//File.WriteAllText(Path.Combine(modelsDirectory, "models.dep"), "VER:" + _ver);
|
||||||
|
|
||||||
_ver++;
|
_ver++;
|
||||||
assembly = BuildManager.GetCompiledAssembly(ProjVirt);
|
try
|
||||||
File.WriteAllText(dllPathFile, assembly.Location);
|
{
|
||||||
|
assembly = BuildManager.GetCompiledAssembly(ProjVirt);
|
||||||
|
File.WriteAllText(dllPathFile, assembly.Location);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
ClearOnFailingToCompile(dllPathFile, modelsHashFile, projFile);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
_logger.Logger.Debug<PureLiveModelFactory>("Loading cached models (source).");
|
_logger.Debug<PureLiveModelFactory>("Loading cached models (source).");
|
||||||
return assembly;
|
return assembly;
|
||||||
}
|
}
|
||||||
|
|
||||||
// need to rebuild
|
// need to rebuild
|
||||||
_logger.Logger.Debug<PureLiveModelFactory>("Rebuilding models.");
|
_logger.Debug<PureLiveModelFactory>("Rebuilding models.");
|
||||||
|
|
||||||
// generate code, save
|
// generate code, save
|
||||||
var code = GenerateModelsCode(ourFiles, typeModels);
|
var code = GenerateModelsCode(typeModels);
|
||||||
// add extra attributes,
|
// add extra attributes,
|
||||||
// PureLiveAssembly helps identifying Assemblies that contain PureLive models
|
// PureLiveAssembly helps identifying Assemblies that contain PureLive models
|
||||||
// AssemblyVersion is so that we have a different version for each rebuild
|
// AssemblyVersion is so that we have a different version for each rebuild
|
||||||
@@ -404,21 +460,47 @@ namespace Umbraco.ModelsBuilder.Umbraco
|
|||||||
File.WriteAllText(modelsSrcFile, code);
|
File.WriteAllText(modelsSrcFile, code);
|
||||||
|
|
||||||
// generate proj, save
|
// generate proj, save
|
||||||
ourFiles["models.generated.cs"] = code;
|
var projFiles = new Dictionary<string, string>
|
||||||
var proj = GenerateModelsProj(ourFiles);
|
{
|
||||||
|
{ "models.generated.cs", code }
|
||||||
|
};
|
||||||
|
var proj = GenerateModelsProj(projFiles);
|
||||||
File.WriteAllText(projFile, proj);
|
File.WriteAllText(projFile, proj);
|
||||||
|
|
||||||
// compile and register
|
// compile and register
|
||||||
assembly = BuildManager.GetCompiledAssembly(ProjVirt);
|
try
|
||||||
File.WriteAllText(dllPathFile, assembly.Location);
|
{
|
||||||
|
assembly = BuildManager.GetCompiledAssembly(ProjVirt);
|
||||||
|
File.WriteAllText(dllPathFile, assembly.Location);
|
||||||
|
File.WriteAllText(modelsHashFile, currentHash);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
ClearOnFailingToCompile(dllPathFile, modelsHashFile, projFile);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
// assuming we can write and it's not going to cause exceptions...
|
_logger.Debug<PureLiveModelFactory>("Done rebuilding.");
|
||||||
File.WriteAllText(modelsHashFile, currentHash);
|
|
||||||
|
|
||||||
_logger.Logger.Debug<PureLiveModelFactory>("Done rebuilding.");
|
|
||||||
return assembly;
|
return assembly;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ClearOnFailingToCompile(string dllPathFile, string modelsHashFile, string projFile)
|
||||||
|
{
|
||||||
|
_logger.Debug<PureLiveModelFactory>("Failed to compile.");
|
||||||
|
|
||||||
|
// the dll file reference still points to the previous dll, which is obsolete
|
||||||
|
// now and will be deleted by ASP.NET eventually, so better clear that reference.
|
||||||
|
// also touch the proj file to force views to recompile - don't delete as it's
|
||||||
|
// useful to have the source around for debugging.
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (File.Exists(dllPathFile)) File.Delete(dllPathFile);
|
||||||
|
if (File.Exists(modelsHashFile)) File.Delete(modelsHashFile);
|
||||||
|
if (File.Exists(projFile)) File.SetLastWriteTime(projFile, DateTime.Now);
|
||||||
|
}
|
||||||
|
catch { /* enough */ }
|
||||||
|
}
|
||||||
|
|
||||||
private static Infos RegisterModels(IEnumerable<Type> types)
|
private static Infos RegisterModels(IEnumerable<Type> types)
|
||||||
{
|
{
|
||||||
var ctorArgTypes = new[] { typeof (IPublishedElement) };
|
var ctorArgTypes = new[] { typeof (IPublishedElement) };
|
||||||
@@ -466,26 +548,16 @@ namespace Umbraco.ModelsBuilder.Umbraco
|
|||||||
return new Infos { ModelInfos = modelInfos.Count > 0 ? modelInfos : null, ModelTypeMap = map };
|
return new Infos { ModelInfos = modelInfos.Count > 0 ? modelInfos : null, ModelTypeMap = map };
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GenerateModelsCode(IDictionary<string, string> ourFiles, IList<TypeModel> typeModels)
|
private string GenerateModelsCode(IList<TypeModel> typeModels)
|
||||||
{
|
{
|
||||||
var modelsDirectory = UmbracoConfig.For.ModelsBuilder().ModelsDirectory;
|
var modelsDirectory = _config.ModelsDirectory;
|
||||||
if (!Directory.Exists(modelsDirectory))
|
if (!Directory.Exists(modelsDirectory))
|
||||||
Directory.CreateDirectory(modelsDirectory);
|
Directory.CreateDirectory(modelsDirectory);
|
||||||
|
|
||||||
foreach (var file in Directory.GetFiles(modelsDirectory, "*.generated.cs"))
|
foreach (var file in Directory.GetFiles(modelsDirectory, "*.generated.cs"))
|
||||||
File.Delete(file);
|
File.Delete(file);
|
||||||
|
|
||||||
var map = typeModels.ToDictionary(x => x.Alias, x => x.ClrName);
|
var builder = new TextBuilder(typeModels, _config.ModelsNamespace);
|
||||||
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();
|
var codeBuilder = new StringBuilder();
|
||||||
builder.Generate(codeBuilder, builder.GetModelsToGenerate());
|
builder.Generate(codeBuilder, builder.GetModelsToGenerate());
|
||||||
@@ -577,7 +649,7 @@ namespace Umbraco.ModelsBuilder.Umbraco
|
|||||||
|
|
||||||
//if (_building && OurFiles.Contains(changed))
|
//if (_building && OurFiles.Contains(changed))
|
||||||
//{
|
//{
|
||||||
// //_logger.Logger.Info<PureLiveModelFactory>("Ignoring files self-changes.");
|
// //_logger.Info<PureLiveModelFactory>("Ignoring files self-changes.");
|
||||||
// return;
|
// return;
|
||||||
//}
|
//}
|
||||||
|
|
||||||
@@ -585,9 +657,12 @@ namespace Umbraco.ModelsBuilder.Umbraco
|
|||||||
if (OurFiles.Contains(changed))
|
if (OurFiles.Contains(changed))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_logger.Logger.Info<PureLiveModelFactory>("Detected files changes.");
|
_logger.Info<PureLiveModelFactory>("Detected files changes.");
|
||||||
|
|
||||||
ResetModels();
|
lock (SyncRoot) // don't reset while being locked
|
||||||
|
{
|
||||||
|
ResetModels();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Stop(bool immediate)
|
public void Stop(bool immediate)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Umbraco.Core;
|
using Umbraco.Core;
|
||||||
using Umbraco.Core.Configuration;
|
using Umbraco.Core.Composing;
|
||||||
using Umbraco.Core.Models;
|
using Umbraco.Core.Models;
|
||||||
using Umbraco.Core.Models.PublishedContent;
|
using Umbraco.Core.Models.PublishedContent;
|
||||||
using Umbraco.Core.Services;
|
using Umbraco.Core.Services;
|
||||||
@@ -27,6 +27,8 @@ namespace Umbraco.ModelsBuilder.Umbraco
|
|||||||
_publishedContentTypeFactory = publishedContentTypeFactory;
|
_publishedContentTypeFactory = publishedContentTypeFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Config Config => Current.Configs.ModelsBuilder();
|
||||||
|
|
||||||
#region Services
|
#region Services
|
||||||
|
|
||||||
public IList<TypeModel> GetAllTypes()
|
public IList<TypeModel> GetAllTypes()
|
||||||
@@ -60,38 +62,8 @@ namespace Umbraco.ModelsBuilder.Umbraco
|
|||||||
|
|
||||||
public static string GetClrName(string name, string alias)
|
public static string GetClrName(string name, string alias)
|
||||||
{
|
{
|
||||||
// ideally we should just be able to re-use Umbraco's alias,
|
// ModelsBuilder's legacy - but not ideal
|
||||||
// just upper-casing the first letter, however in v7 for backward
|
return alias.ToCleanString(CleanStringType.ConvertCase | CleanStringType.PascalCase);
|
||||||
// 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<TypeModel> GetTypes(PublishedItemType itemType, IContentTypeComposition[] contentTypes)
|
private IList<TypeModel> GetTypes(PublishedItemType itemType, IContentTypeComposition[] contentTypes)
|
||||||
@@ -119,33 +91,23 @@ namespace Umbraco.ModelsBuilder.Umbraco
|
|||||||
throw new Exception($"Panic: duplicate type ClrName \"{typeModel.ClrName}\".");
|
throw new Exception($"Panic: duplicate type ClrName \"{typeModel.ClrName}\".");
|
||||||
uniqueTypes.Add(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);
|
var publishedContentType = _publishedContentTypeFactory.CreateContentType(contentType);
|
||||||
switch (itemType)
|
switch (itemType)
|
||||||
{
|
{
|
||||||
case PublishedItemType.Content:
|
case PublishedItemType.Content:
|
||||||
if (IsElement(publishedContentType))
|
typeModel.ItemType = publishedContentType.ItemType == PublishedItemType.Element
|
||||||
{
|
? TypeModel.ItemTypes.Element
|
||||||
typeModel.ItemType = TypeModel.ItemTypes.Element;
|
: TypeModel.ItemTypes.Content;
|
||||||
if (typeModel.ClrName.InvariantEndsWith("Element"))
|
|
||||||
typeModel.ClrName = typeModel.ClrName.Substring(0, typeModel.ClrName.Length - "Element".Length);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
typeModel.ItemType = TypeModel.ItemTypes.Content;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case PublishedItemType.Media:
|
case PublishedItemType.Media:
|
||||||
typeModel.ItemType = TypeModel.ItemTypes.Media;
|
typeModel.ItemType = publishedContentType.ItemType == PublishedItemType.Element
|
||||||
|
? TypeModel.ItemTypes.Element
|
||||||
|
: TypeModel.ItemTypes.Media;
|
||||||
break;
|
break;
|
||||||
case PublishedItemType.Member:
|
case PublishedItemType.Member:
|
||||||
typeModel.ItemType = TypeModel.ItemTypes.Member;
|
typeModel.ItemType = publishedContentType.ItemType == PublishedItemType.Element
|
||||||
|
? TypeModel.ItemTypes.Element
|
||||||
|
: TypeModel.ItemTypes.Member;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new InvalidOperationException(string.Format("Unsupported PublishedItemType \"{0}\".", itemType));
|
throw new InvalidOperationException(string.Format("Unsupported PublishedItemType \"{0}\".", itemType));
|
||||||
|
|||||||
@@ -2,8 +2,7 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Umbraco.Core;
|
using Umbraco.Core;
|
||||||
using Umbraco.Core.Configuration;
|
using Umbraco.Core.Composing;
|
||||||
using Umbraco.Core.Models;
|
|
||||||
using Umbraco.Core.Models.PublishedContent;
|
using Umbraco.Core.Models.PublishedContent;
|
||||||
using Umbraco.ModelsBuilder.Configuration;
|
using Umbraco.ModelsBuilder.Configuration;
|
||||||
using Umbraco.Web.Editors;
|
using Umbraco.Web.Editors;
|
||||||
@@ -12,37 +11,36 @@ using Umbraco.Web.Models.ContentEditing;
|
|||||||
namespace Umbraco.ModelsBuilder.Validation
|
namespace Umbraco.ModelsBuilder.Validation
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Used to validate the aliases for the content type when MB is enabled to ensure that
|
/// Used to validate the aliases for the content type when MB is enabled to ensure that
|
||||||
/// no illegal aliases are used
|
/// no illegal aliases are used
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class ContentTypeModelValidator : ContentTypeModelValidatorBase<DocumentTypeSave, PropertyTypeBasic>
|
internal class ContentTypeModelValidator : ContentTypeModelValidatorBase<DocumentTypeSave, PropertyTypeBasic>
|
||||||
{
|
{ }
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Used to validate the aliases for the content type when MB is enabled to ensure that
|
/// Used to validate the aliases for the content type when MB is enabled to ensure that
|
||||||
/// no illegal aliases are used
|
/// no illegal aliases are used
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class MediaTypeModelValidator : ContentTypeModelValidatorBase<MediaTypeSave, PropertyTypeBasic>
|
internal class MediaTypeModelValidator : ContentTypeModelValidatorBase<MediaTypeSave, PropertyTypeBasic>
|
||||||
{
|
{ }
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Used to validate the aliases for the content type when MB is enabled to ensure that
|
/// Used to validate the aliases for the content type when MB is enabled to ensure that
|
||||||
/// no illegal aliases are used
|
/// no illegal aliases are used
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class MemberTypeModelValidator : ContentTypeModelValidatorBase<MemberTypeSave, MemberPropertyTypeBasic>
|
internal class MemberTypeModelValidator : ContentTypeModelValidatorBase<MemberTypeSave, MemberPropertyTypeBasic>
|
||||||
{
|
{ }
|
||||||
}
|
|
||||||
|
|
||||||
internal abstract class ContentTypeModelValidatorBase<TModel, TProperty> : EditorValidator<TModel>
|
internal abstract class ContentTypeModelValidatorBase<TModel, TProperty> : EditorValidator<TModel>
|
||||||
where TModel: ContentTypeSave<TProperty>
|
where TModel: ContentTypeSave<TProperty>
|
||||||
where TProperty: PropertyTypeBasic
|
where TProperty: PropertyTypeBasic
|
||||||
{
|
{
|
||||||
|
private static Config Config => Current.Configs.ModelsBuilder();
|
||||||
|
|
||||||
protected override IEnumerable<ValidationResult> Validate(TModel model)
|
protected override IEnumerable<ValidationResult> Validate(TModel model)
|
||||||
{
|
{
|
||||||
//don't do anything if we're not enabled
|
//don't do anything if we're not enabled
|
||||||
if (UmbracoConfig.For.ModelsBuilder().Enable)
|
if (Config.Enable)
|
||||||
{
|
{
|
||||||
var properties = model.Groups.SelectMany(x => x.Properties)
|
var properties = model.Groups.SelectMany(x => x.Properties)
|
||||||
.Where(x => x.Inherited == false)
|
.Where(x => x.Inherited == false)
|
||||||
@@ -51,7 +49,7 @@ namespace Umbraco.ModelsBuilder.Validation
|
|||||||
foreach (var prop in properties)
|
foreach (var prop in properties)
|
||||||
{
|
{
|
||||||
var propertyGroup = model.Groups.Single(x => x.Properties.Contains(prop));
|
var propertyGroup = model.Groups.Single(x => x.Properties.Contains(prop));
|
||||||
|
|
||||||
if (model.Alias.ToLowerInvariant() == prop.Alias.ToLowerInvariant())
|
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[]
|
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[]
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -117,6 +117,10 @@
|
|||||||
<Name>Umbraco.Examine</Name>
|
<Name>Umbraco.Examine</Name>
|
||||||
<Project>{07FBC26B-2927-4A22-8D96-D644C667FECC}</Project>
|
<Project>{07FBC26B-2927-4A22-8D96-D644C667FECC}</Project>
|
||||||
</ProjectReference>
|
</ProjectReference>
|
||||||
|
<ProjectReference Include="..\Umbraco.ModelsBuilder\Umbraco.ModelsBuilder.csproj">
|
||||||
|
<Project>{52ac0ba8-a60e-4e36-897b-e8b97a54ed1c}</Project>
|
||||||
|
<Name>Umbraco.ModelsBuilder</Name>
|
||||||
|
</ProjectReference>
|
||||||
<ProjectReference Include="..\Umbraco.Web\Umbraco.Web.csproj">
|
<ProjectReference Include="..\Umbraco.Web\Umbraco.Web.csproj">
|
||||||
<Project>{651e1350-91b6-44b7-bd60-7207006d7003}</Project>
|
<Project>{651e1350-91b6-44b7-bd60-7207006d7003}</Project>
|
||||||
<Name>Umbraco.Web</Name>
|
<Name>Umbraco.Web</Name>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ namespace Umbraco.Web.Editors
|
|||||||
/// Provides a base class for <see cref="IEditorValidator"/> implementations.
|
/// Provides a base class for <see cref="IEditorValidator"/> implementations.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T">The validated object type.</typeparam>
|
/// <typeparam name="T">The validated object type.</typeparam>
|
||||||
internal abstract class EditorValidator<T> : IEditorValidator
|
public abstract class EditorValidator<T> : IEditorValidator
|
||||||
{
|
{
|
||||||
public Type ModelType => typeof (T);
|
public Type ModelType => typeof (T);
|
||||||
|
|
||||||
@@ -16,4 +16,4 @@ namespace Umbraco.Web.Editors
|
|||||||
|
|
||||||
protected abstract IEnumerable<ValidationResult> Validate(T model);
|
protected abstract IEnumerable<ValidationResult> Validate(T model);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-2
@@ -1,6 +1,6 @@
|
|||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio 15
|
# Visual Studio Version 16
|
||||||
VisualStudioVersion = 15.0.27004.2005
|
VisualStudioVersion = 16.0.29009.5
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
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}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Umbraco.Web.UI", "Umbraco.Web.UI\Umbraco.Web.UI.csproj", "{4C4C194C-B5E4-4991-8F87-4373E24CC19F}"
|
||||||
EndProject
|
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
|
..\.github\ISSUE_TEMPLATE\5_Security_issue.md = ..\.github\ISSUE_TEMPLATE\5_Security_issue.md
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Umbraco.ModelsBuilder", "Umbraco.ModelsBuilder\Umbraco.ModelsBuilder.csproj", "{52AC0BA8-A60E-4E36-897B-E8B97A54ED1C}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
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}.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.ActiveCfg = Release|Any CPU
|
||||||
{3A33ADC9-C6C0-4DB1-A613-A9AF0210DF3D}.Release|Any CPU.Build.0 = 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
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|||||||
Reference in New Issue
Block a user