Renames project: Umbraco.ModelsBuilder.Embedded and namespaces since we need a different assembly, updates nuspec, changes file path of MB app_plugins
This commit is contained in:
222
src/Umbraco.ModelsBuilder.Embedded/Building/Builder.cs
Normal file
222
src/Umbraco.ModelsBuilder.Embedded/Building/Builder.cs
Normal file
@@ -0,0 +1,222 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.ModelsBuilder.Embedded.Configuration;
|
||||
|
||||
namespace Umbraco.ModelsBuilder.Embedded.Building
|
||||
{
|
||||
// NOTE
|
||||
// The idea was to have different types of builder, because I wanted to experiment with
|
||||
// building code with CodeDom. Turns out more complicated than I thought and maybe not
|
||||
// worth it at the moment, to we're using TextBuilder and its Generate method is specific.
|
||||
//
|
||||
// Keeping the code as-is for the time being...
|
||||
|
||||
/// <summary>
|
||||
/// Provides a base class for all builders.
|
||||
/// </summary>
|
||||
internal abstract class Builder
|
||||
{
|
||||
|
||||
private readonly IList<TypeModel> _typeModels;
|
||||
|
||||
protected Dictionary<string, string> ModelsMap { get; } = new Dictionary<string, string>();
|
||||
|
||||
// the list of assemblies that will be 'using' by default
|
||||
protected readonly IList<string> TypesUsing = new List<string>
|
||||
{
|
||||
"System",
|
||||
"System.Collections.Generic",
|
||||
"System.Linq.Expressions",
|
||||
"System.Web",
|
||||
"Umbraco.Core.Models",
|
||||
"Umbraco.Core.Models.PublishedContent",
|
||||
"Umbraco.Web",
|
||||
"Umbraco.ModelsBuilder"
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating the namespace to use for the models.
|
||||
/// </summary>
|
||||
/// <remarks>May be overriden by code attributes.</remarks>
|
||||
public string ModelsNamespace { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of assemblies to add to the set of 'using' assemblies in each model file.
|
||||
/// </summary>
|
||||
public IList<string> Using => TypesUsing;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of models to generate.
|
||||
/// </summary>
|
||||
/// <returns>The models to generate</returns>
|
||||
public IEnumerable<TypeModel> GetModelsToGenerate()
|
||||
{
|
||||
return _typeModels;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of all models.
|
||||
/// </summary>
|
||||
/// <remarks>Includes those that are ignored.</remarks>
|
||||
internal IList<TypeModel> TypeModels => _typeModels;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Builder"/> class with a list of models to generate,
|
||||
/// the result of code parsing, and a models namespace.
|
||||
/// </summary>
|
||||
/// <param name="typeModels">The list of models to generate.</param>
|
||||
/// <param name="modelsNamespace">The models namespace.</param>
|
||||
protected Builder(IModelsBuilderConfig config, IList<TypeModel> typeModels)
|
||||
{
|
||||
_typeModels = typeModels ?? throw new ArgumentNullException(nameof(typeModels));
|
||||
|
||||
Config = config ?? throw new ArgumentNullException(nameof(config));
|
||||
|
||||
// can be null or empty, we'll manage
|
||||
ModelsNamespace = Config.ModelsNamespace;
|
||||
|
||||
// but we want it to prepare
|
||||
Prepare();
|
||||
}
|
||||
|
||||
// for unit tests only
|
||||
protected Builder()
|
||||
{ }
|
||||
|
||||
protected IModelsBuilderConfig Config { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Prepares generation by processing the result of code parsing.
|
||||
/// </summary>
|
||||
private void Prepare()
|
||||
{
|
||||
TypeModel.MapModelTypes(_typeModels, ModelsNamespace);
|
||||
|
||||
var pureLive = Config.ModelsMode == ModelsMode.PureLive;
|
||||
|
||||
// for the first two of these two tests,
|
||||
// always throw, even in purelive: cannot happen unless ppl start fidling with attributes to rename
|
||||
// things, and then they should pay attention to the generation error log - there's no magic here
|
||||
// for the last one, don't throw in purelive, see comment
|
||||
|
||||
// ensure we have no duplicates type names
|
||||
foreach (var xx in _typeModels.GroupBy(x => x.ClrName).Where(x => x.Count() > 1))
|
||||
throw new InvalidOperationException($"Type name \"{xx.Key}\" is used"
|
||||
+ $" for types with alias {string.Join(", ", xx.Select(x => x.ItemType + ":\"" + x.Alias + "\""))}. Names have to be unique."
|
||||
+ " Consider using an attribute to assign different names to conflicting types.");
|
||||
|
||||
// ensure we have no duplicates property names
|
||||
foreach (var typeModel in _typeModels)
|
||||
foreach (var xx in typeModel.Properties.GroupBy(x => x.ClrName).Where(x => x.Count() > 1))
|
||||
throw new InvalidOperationException($"Property name \"{xx.Key}\" in type {typeModel.ItemType}:\"{typeModel.Alias}\""
|
||||
+ $" is used for properties with alias {string.Join(", ", xx.Select(x => "\"" + x.Alias + "\""))}. Names have to be unique."
|
||||
+ " Consider using an attribute to assign different names to conflicting properties.");
|
||||
|
||||
// ensure content & property type don't have identical name (csharp hates it)
|
||||
foreach (var typeModel in _typeModels)
|
||||
{
|
||||
foreach (var xx in typeModel.Properties.Where(x => x.ClrName == typeModel.ClrName))
|
||||
{
|
||||
if (!pureLive)
|
||||
throw new InvalidOperationException($"The model class for content type with alias \"{typeModel.Alias}\" is named \"{xx.ClrName}\"."
|
||||
+ $" CSharp does not support using the same name for the property with alias \"{xx.Alias}\"."
|
||||
+ " Consider using an attribute to assign a different name to the property.");
|
||||
|
||||
// for purelive, will we generate a commented out properties with an error message,
|
||||
// instead of throwing, because then it kills the sites and ppl don't understand why
|
||||
xx.AddError($"The class {typeModel.ClrName} cannot implement this property, because"
|
||||
+ $" CSharp does not support naming the property with alias \"{xx.Alias}\" with the same name as content type with alias \"{typeModel.Alias}\"."
|
||||
+ " Consider using an attribute to assign a different name to the property.");
|
||||
|
||||
// will not be implemented on interface nor class
|
||||
// note: we will still create the static getter, and implement the property on other classes...
|
||||
}
|
||||
}
|
||||
|
||||
// ensure we have no collision between base types
|
||||
// NO: we may want to define a base class in a partial, on a model that has a parent
|
||||
// we are NOT checking that the defined base type does maintain the inheritance chain
|
||||
//foreach (var xx in _typeModels.Where(x => !x.IsContentIgnored).Where(x => x.BaseType != null && x.HasBase))
|
||||
// throw new InvalidOperationException(string.Format("Type alias \"{0}\" has more than one parent class.",
|
||||
// xx.Alias));
|
||||
|
||||
// discover interfaces that need to be declared / implemented
|
||||
foreach (var typeModel in _typeModels)
|
||||
{
|
||||
// collect all the (non-removed) types implemented at parent level
|
||||
// ie the parent content types and the mixins content types, recursively
|
||||
var parentImplems = new List<TypeModel>();
|
||||
if (typeModel.BaseType != null)
|
||||
TypeModel.CollectImplems(parentImplems, typeModel.BaseType);
|
||||
|
||||
// interfaces we must declare we implement (initially empty)
|
||||
// ie this type's mixins, except those that have been removed,
|
||||
// and except those that are already declared at the parent level
|
||||
// in other words, DeclaringInterfaces is "local mixins"
|
||||
var declaring = typeModel.MixinTypes
|
||||
.Except(parentImplems);
|
||||
typeModel.DeclaringInterfaces.AddRange(declaring);
|
||||
|
||||
// interfaces we must actually implement (initially empty)
|
||||
// if we declare we implement a mixin interface, we must actually implement
|
||||
// its properties, all recursively (ie if the mixin interface implements...)
|
||||
// so, starting with local mixins, we collect all the (non-removed) types above them
|
||||
var mixinImplems = new List<TypeModel>();
|
||||
foreach (var i in typeModel.DeclaringInterfaces)
|
||||
TypeModel.CollectImplems(mixinImplems, i);
|
||||
// and then we remove from that list anything that is already declared at the parent level
|
||||
typeModel.ImplementingInterfaces.AddRange(mixinImplems.Except(parentImplems));
|
||||
}
|
||||
|
||||
// ensure elements don't inherit from non-elements
|
||||
foreach (var typeModel in _typeModels.Where(x => x.IsElement))
|
||||
{
|
||||
if (typeModel.BaseType != null && !typeModel.BaseType.IsElement)
|
||||
throw new InvalidOperationException($"Cannot generate model for type '{typeModel.Alias}' because it is an element type, but its parent type '{typeModel.BaseType.Alias}' is not.");
|
||||
|
||||
var errs = typeModel.MixinTypes.Where(x => !x.IsElement).ToList();
|
||||
if (errs.Count > 0)
|
||||
throw new InvalidOperationException($"Cannot generate model for type '{typeModel.Alias}' because it is an element type, but it is composed of {string.Join(", ", errs.Select(x => "'" + x.Alias + "'"))} which {(errs.Count == 1 ? "is" : "are")} not.");
|
||||
}
|
||||
}
|
||||
|
||||
// looking for a simple symbol eg 'Umbraco' or 'String'
|
||||
// expecting to match eg 'Umbraco' or 'System.String'
|
||||
// returns true if either
|
||||
// - more than 1 symbol is found (explicitely ambiguous)
|
||||
// - 1 symbol is found BUT not matching (implicitely ambiguous)
|
||||
protected bool IsAmbiguousSymbol(string symbol, string match)
|
||||
{
|
||||
// cannot figure out is a symbol is ambiguous without Roslyn
|
||||
// so... let's say everything is ambiguous - code won't be
|
||||
// pretty but it'll work
|
||||
|
||||
// Essentially this means that a `global::` syntax will be output for the generated models
|
||||
return true;
|
||||
}
|
||||
|
||||
internal string ModelsNamespaceForTests;
|
||||
|
||||
public string GetModelsNamespace()
|
||||
{
|
||||
if (ModelsNamespaceForTests != null)
|
||||
return ModelsNamespaceForTests;
|
||||
|
||||
// if builder was initialized with a namespace, use that one
|
||||
if (!string.IsNullOrWhiteSpace(ModelsNamespace))
|
||||
return ModelsNamespace;
|
||||
|
||||
// use configured else fallback to default
|
||||
return string.IsNullOrWhiteSpace(Config.ModelsNamespace)
|
||||
? ModelsBuilderConfig.DefaultModelsNamespace
|
||||
: Config.ModelsNamespace;
|
||||
}
|
||||
|
||||
protected string GetModelsBaseClassName(TypeModel type)
|
||||
{
|
||||
// default
|
||||
return type.IsElement ? "PublishedElementModel" : "PublishedContentModel";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Umbraco.ModelsBuilder.Embedded.Configuration;
|
||||
|
||||
namespace Umbraco.ModelsBuilder.Embedded.Building
|
||||
{
|
||||
public class ModelsGenerator
|
||||
{
|
||||
private readonly UmbracoServices _umbracoService;
|
||||
private readonly IModelsBuilderConfig _config;
|
||||
private readonly OutOfDateModelsStatus _outOfDateModels;
|
||||
|
||||
public ModelsGenerator(UmbracoServices umbracoService, IModelsBuilderConfig config, OutOfDateModelsStatus outOfDateModels)
|
||||
{
|
||||
_umbracoService = umbracoService;
|
||||
_config = config;
|
||||
_outOfDateModels = outOfDateModels;
|
||||
}
|
||||
|
||||
internal void GenerateModels()
|
||||
{
|
||||
if (!Directory.Exists(_config.ModelsDirectory))
|
||||
Directory.CreateDirectory(_config.ModelsDirectory);
|
||||
|
||||
foreach (var file in Directory.GetFiles(_config.ModelsDirectory, "*.generated.cs"))
|
||||
File.Delete(file);
|
||||
|
||||
var typeModels = _umbracoService.GetAllTypes();
|
||||
|
||||
var builder = new TextBuilder(_config, typeModels);
|
||||
|
||||
foreach (var typeModel in builder.GetModelsToGenerate())
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
builder.Generate(sb, typeModel);
|
||||
var filename = Path.Combine(_config.ModelsDirectory, typeModel.ClrName + ".generated.cs");
|
||||
File.WriteAllText(filename, sb.ToString());
|
||||
}
|
||||
|
||||
// the idea was to calculate the current hash and to add it as an extra file to the compilation,
|
||||
// in order to be able to detect whether a DLL is consistent with an environment - however the
|
||||
// environment *might not* contain the local partial files, and thus it could be impossible to
|
||||
// calculate the hash. So... maybe that's not a good idea after all?
|
||||
/*
|
||||
var currentHash = HashHelper.Hash(ourFiles, typeModels);
|
||||
ourFiles["models.hash.cs"] = $@"using Umbraco.ModelsBuilder;
|
||||
[assembly:ModelsBuilderAssembly(SourceHash = ""{currentHash}"")]
|
||||
";
|
||||
*/
|
||||
|
||||
_outOfDateModels.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
61
src/Umbraco.ModelsBuilder.Embedded/Building/PropertyModel.cs
Normal file
61
src/Umbraco.ModelsBuilder.Embedded/Building/PropertyModel.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Umbraco.ModelsBuilder.Embedded.Building
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a model property.
|
||||
/// </summary>
|
||||
public class PropertyModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the alias of the property.
|
||||
/// </summary>
|
||||
public string Alias;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the property.
|
||||
/// </summary>
|
||||
public string Name;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the description of the property.
|
||||
/// </summary>
|
||||
public string Description;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the clr name of the property.
|
||||
/// </summary>
|
||||
/// <remarks>This is just the local name eg "Price".</remarks>
|
||||
public string ClrName;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Model Clr type of the property values.
|
||||
/// </summary>
|
||||
/// <remarks>As indicated by the <c>PublishedPropertyType</c>, ie by the <c>IPropertyValueConverter</c>
|
||||
/// if any, else <c>object</c>. May include some ModelType that will need to be mapped.</remarks>
|
||||
public Type ModelClrType;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the CLR type name of the property values.
|
||||
/// </summary>
|
||||
public string ClrTypeName;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the generation errors for the property.
|
||||
/// </summary>
|
||||
/// <remarks>This should be null, unless something prevents the property from being
|
||||
/// generated, and then the value should explain what. This can be used to generate
|
||||
/// commented out code eg in PureLive.</remarks>
|
||||
public List<string> Errors;
|
||||
|
||||
/// <summary>
|
||||
/// Adds an error.
|
||||
/// </summary>
|
||||
public void AddError(string error)
|
||||
{
|
||||
if (Errors == null) Errors = new List<string>();
|
||||
Errors.Add(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
564
src/Umbraco.ModelsBuilder.Embedded/Building/TextBuilder.cs
Normal file
564
src/Umbraco.ModelsBuilder.Embedded/Building/TextBuilder.cs
Normal file
@@ -0,0 +1,564 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using Umbraco.ModelsBuilder.Embedded.Configuration;
|
||||
|
||||
namespace Umbraco.ModelsBuilder.Embedded.Building
|
||||
{
|
||||
/// <summary>
|
||||
/// Implements a builder that works by writing text.
|
||||
/// </summary>
|
||||
internal class TextBuilder : Builder
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TextBuilder"/> class with a list of models to generate
|
||||
/// and the result of code parsing.
|
||||
/// </summary>
|
||||
/// <param name="typeModels">The list of models to generate.</param>
|
||||
public TextBuilder(IModelsBuilderConfig config, IList<TypeModel> typeModels)
|
||||
: base(config, typeModels)
|
||||
{ }
|
||||
|
||||
// internal for unit tests only
|
||||
internal TextBuilder()
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Outputs a generated model to a string builder.
|
||||
/// </summary>
|
||||
/// <param name="sb">The string builder.</param>
|
||||
/// <param name="typeModel">The model to generate.</param>
|
||||
public void Generate(StringBuilder sb, TypeModel typeModel)
|
||||
{
|
||||
WriteHeader(sb);
|
||||
|
||||
foreach (var t in TypesUsing)
|
||||
sb.AppendFormat("using {0};\n", t);
|
||||
|
||||
sb.Append("\n");
|
||||
sb.AppendFormat("namespace {0}\n", GetModelsNamespace());
|
||||
sb.Append("{\n");
|
||||
|
||||
WriteContentType(sb, typeModel);
|
||||
|
||||
sb.Append("}\n");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Outputs generated models to a string builder.
|
||||
/// </summary>
|
||||
/// <param name="sb">The string builder.</param>
|
||||
/// <param name="typeModels">The models to generate.</param>
|
||||
public void Generate(StringBuilder sb, IEnumerable<TypeModel> typeModels)
|
||||
{
|
||||
WriteHeader(sb);
|
||||
|
||||
foreach (var t in TypesUsing)
|
||||
sb.AppendFormat("using {0};\n", t);
|
||||
|
||||
// assembly attributes marker
|
||||
sb.Append("\n//ASSATTR\n");
|
||||
|
||||
sb.Append("\n");
|
||||
sb.AppendFormat("namespace {0}\n", GetModelsNamespace());
|
||||
sb.Append("{\n");
|
||||
|
||||
foreach (var typeModel in typeModels)
|
||||
{
|
||||
WriteContentType(sb, typeModel);
|
||||
sb.Append("\n");
|
||||
}
|
||||
|
||||
sb.Append("}\n");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Outputs an "auto-generated" header to a string builder.
|
||||
/// </summary>
|
||||
/// <param name="sb">The string builder.</param>
|
||||
public static void WriteHeader(StringBuilder sb)
|
||||
{
|
||||
TextHeaderWriter.WriteHeader(sb);
|
||||
}
|
||||
|
||||
// writes an attribute that identifies code generated by a tool
|
||||
// (helps reduce warnings, tools such as FxCop use it)
|
||||
// see https://github.com/zpqrtbnk/Zbu.ModelsBuilder/issues/107
|
||||
// see https://docs.microsoft.com/en-us/dotnet/api/system.codedom.compiler.generatedcodeattribute
|
||||
// see https://blogs.msdn.microsoft.com/codeanalysis/2007/04/27/correct-usage-of-the-compilergeneratedattribute-and-the-generatedcodeattribute/
|
||||
//
|
||||
// note that the blog post above clearly states that "Nor should it be applied at the type level if the type being generated is a partial class."
|
||||
// and since our models are partial classes, we have to apply the attribute against the individual members, not the class itself.
|
||||
//
|
||||
private static void WriteGeneratedCodeAttribute(StringBuilder sb, string tabs)
|
||||
{
|
||||
sb.AppendFormat("{0}[global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"Umbraco.ModelsBuilder\", \"{1}\")]\n", tabs, ApiVersion.Current.Version);
|
||||
}
|
||||
|
||||
private void WriteContentType(StringBuilder sb, TypeModel type)
|
||||
{
|
||||
string sep;
|
||||
|
||||
if (type.IsMixin)
|
||||
{
|
||||
// write the interface declaration
|
||||
sb.AppendFormat("\t// Mixin Content Type with alias \"{0}\"\n", type.Alias);
|
||||
if (!string.IsNullOrWhiteSpace(type.Name))
|
||||
sb.AppendFormat("\t/// <summary>{0}</summary>\n", XmlCommentString(type.Name));
|
||||
sb.AppendFormat("\tpublic partial interface I{0}", type.ClrName);
|
||||
var implements = type.BaseType == null
|
||||
? (type.HasBase ? null : (type.IsElement ? "PublishedElement" : "PublishedContent"))
|
||||
: type.BaseType.ClrName;
|
||||
if (implements != null)
|
||||
sb.AppendFormat(" : I{0}", implements);
|
||||
|
||||
// write the mixins
|
||||
sep = implements == null ? ":" : ",";
|
||||
foreach (var mixinType in type.DeclaringInterfaces.OrderBy(x => x.ClrName))
|
||||
{
|
||||
sb.AppendFormat("{0} I{1}", sep, mixinType.ClrName);
|
||||
sep = ",";
|
||||
}
|
||||
|
||||
sb.Append("\n\t{\n");
|
||||
|
||||
// write the properties - only the local (non-ignored) ones, we're an interface
|
||||
var more = false;
|
||||
foreach (var prop in type.Properties.OrderBy(x => x.ClrName))
|
||||
{
|
||||
if (more) sb.Append("\n");
|
||||
more = true;
|
||||
WriteInterfaceProperty(sb, prop);
|
||||
}
|
||||
|
||||
sb.Append("\t}\n\n");
|
||||
}
|
||||
|
||||
// write the class declaration
|
||||
if (!string.IsNullOrWhiteSpace(type.Name))
|
||||
sb.AppendFormat("\t/// <summary>{0}</summary>\n", XmlCommentString(type.Name));
|
||||
// cannot do it now. see note in ImplementContentTypeAttribute
|
||||
//if (!type.HasImplement)
|
||||
// sb.AppendFormat("\t[ImplementContentType(\"{0}\")]\n", type.Alias);
|
||||
sb.AppendFormat("\t[PublishedModel(\"{0}\")]\n", type.Alias);
|
||||
sb.AppendFormat("\tpublic partial class {0}", type.ClrName);
|
||||
var inherits = type.HasBase
|
||||
? null // has its own base already
|
||||
: (type.BaseType == null
|
||||
? GetModelsBaseClassName(type)
|
||||
: type.BaseType.ClrName);
|
||||
if (inherits != null)
|
||||
sb.AppendFormat(" : {0}", inherits);
|
||||
|
||||
sep = inherits == null ? ":" : ",";
|
||||
if (type.IsMixin)
|
||||
{
|
||||
// if it's a mixin it implements its own interface
|
||||
sb.AppendFormat("{0} I{1}", sep, type.ClrName);
|
||||
}
|
||||
else
|
||||
{
|
||||
// write the mixins, if any, as interfaces
|
||||
// only if not a mixin because otherwise the interface already has them already
|
||||
foreach (var mixinType in type.DeclaringInterfaces.OrderBy(x => x.ClrName))
|
||||
{
|
||||
sb.AppendFormat("{0} I{1}", sep, mixinType.ClrName);
|
||||
sep = ",";
|
||||
}
|
||||
}
|
||||
|
||||
// begin class body
|
||||
sb.Append("\n\t{\n");
|
||||
|
||||
// write the constants & static methods
|
||||
// as 'new' since parent has its own - or maybe not - disable warning
|
||||
sb.Append("\t\t// helpers\n");
|
||||
sb.Append("#pragma warning disable 0109 // new is redundant\n");
|
||||
WriteGeneratedCodeAttribute(sb, "\t\t");
|
||||
sb.AppendFormat("\t\tpublic new const string ModelTypeAlias = \"{0}\";\n",
|
||||
type.Alias);
|
||||
var itemType = type.IsElement ? TypeModel.ItemTypes.Content : type.ItemType; // fixme
|
||||
WriteGeneratedCodeAttribute(sb, "\t\t");
|
||||
sb.AppendFormat("\t\tpublic new const PublishedItemType ModelItemType = PublishedItemType.{0};\n",
|
||||
itemType);
|
||||
WriteGeneratedCodeAttribute(sb, "\t\t");
|
||||
sb.Append("\t\tpublic new static IPublishedContentType GetModelContentType()\n");
|
||||
sb.Append("\t\t\t=> PublishedModelUtility.GetModelContentType(ModelItemType, ModelTypeAlias);\n");
|
||||
WriteGeneratedCodeAttribute(sb, "\t\t");
|
||||
sb.AppendFormat("\t\tpublic static IPublishedPropertyType GetModelPropertyType<TValue>(Expression<Func<{0}, TValue>> selector)\n",
|
||||
type.ClrName);
|
||||
sb.Append("\t\t\t=> PublishedModelUtility.GetModelPropertyType(GetModelContentType(), selector);\n");
|
||||
sb.Append("#pragma warning restore 0109\n\n");
|
||||
|
||||
// write the ctor
|
||||
sb.AppendFormat("\t\t// ctor\n\t\tpublic {0}(IPublished{1} content)\n\t\t\t: base(content)\n\t\t{{ }}\n\n",
|
||||
type.ClrName, type.IsElement ? "Element" : "Content");
|
||||
|
||||
// write the properties
|
||||
sb.Append("\t\t// properties\n");
|
||||
WriteContentTypeProperties(sb, type);
|
||||
|
||||
// close the class declaration
|
||||
sb.Append("\t}\n");
|
||||
}
|
||||
|
||||
private void WriteContentTypeProperties(StringBuilder sb, TypeModel type)
|
||||
{
|
||||
var staticMixinGetters = true;
|
||||
|
||||
// write the properties
|
||||
foreach (var prop in type.Properties.OrderBy(x => x.ClrName))
|
||||
WriteProperty(sb, type, prop, staticMixinGetters && type.IsMixin ? type.ClrName : null);
|
||||
|
||||
// no need to write the parent properties since we inherit from the parent
|
||||
// and the parent defines its own properties. need to write the mixins properties
|
||||
// since the mixins are only interfaces and we have to provide an implementation.
|
||||
|
||||
// write the mixins properties
|
||||
foreach (var mixinType in type.ImplementingInterfaces.OrderBy(x => x.ClrName))
|
||||
foreach (var prop in mixinType.Properties.OrderBy(x => x.ClrName))
|
||||
if (staticMixinGetters)
|
||||
WriteMixinProperty(sb, prop, mixinType.ClrName);
|
||||
else
|
||||
WriteProperty(sb, mixinType, prop);
|
||||
}
|
||||
|
||||
private void WriteMixinProperty(StringBuilder sb, PropertyModel property, string mixinClrName)
|
||||
{
|
||||
sb.Append("\n");
|
||||
|
||||
// Adds xml summary to each property containing
|
||||
// property name and property description
|
||||
if (!string.IsNullOrWhiteSpace(property.Name) || !string.IsNullOrWhiteSpace(property.Description))
|
||||
{
|
||||
sb.Append("\t\t///<summary>\n");
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(property.Description))
|
||||
sb.AppendFormat("\t\t/// {0}: {1}\n", XmlCommentString(property.Name), XmlCommentString(property.Description));
|
||||
else
|
||||
sb.AppendFormat("\t\t/// {0}\n", XmlCommentString(property.Name));
|
||||
|
||||
sb.Append("\t\t///</summary>\n");
|
||||
}
|
||||
|
||||
WriteGeneratedCodeAttribute(sb, "\t\t");
|
||||
sb.AppendFormat("\t\t[ImplementPropertyType(\"{0}\")]\n", property.Alias);
|
||||
|
||||
sb.Append("\t\tpublic ");
|
||||
WriteClrType(sb, property.ClrTypeName);
|
||||
|
||||
sb.AppendFormat(" {0} => ",
|
||||
property.ClrName);
|
||||
WriteNonGenericClrType(sb, GetModelsNamespace() + "." + mixinClrName);
|
||||
sb.AppendFormat(".{0}(this);\n",
|
||||
MixinStaticGetterName(property.ClrName));
|
||||
}
|
||||
|
||||
private static string MixinStaticGetterName(string clrName)
|
||||
{
|
||||
return string.Format("Get{0}", clrName);
|
||||
}
|
||||
|
||||
private void WriteProperty(StringBuilder sb, TypeModel type, PropertyModel property, string mixinClrName = null)
|
||||
{
|
||||
var mixinStatic = mixinClrName != null;
|
||||
|
||||
sb.Append("\n");
|
||||
|
||||
if (property.Errors != null)
|
||||
{
|
||||
sb.Append("\t\t/*\n");
|
||||
sb.Append("\t\t * THIS PROPERTY CANNOT BE IMPLEMENTED, BECAUSE:\n");
|
||||
sb.Append("\t\t *\n");
|
||||
var first = true;
|
||||
foreach (var error in property.Errors)
|
||||
{
|
||||
if (first) first = false;
|
||||
else sb.Append("\t\t *\n");
|
||||
foreach (var s in SplitError(error))
|
||||
{
|
||||
sb.Append("\t\t * ");
|
||||
sb.Append(s);
|
||||
sb.Append("\n");
|
||||
}
|
||||
}
|
||||
sb.Append("\t\t *\n");
|
||||
sb.Append("\n");
|
||||
}
|
||||
|
||||
// Adds xml summary to each property containing
|
||||
// property name and property description
|
||||
if (!string.IsNullOrWhiteSpace(property.Name) || !string.IsNullOrWhiteSpace(property.Description))
|
||||
{
|
||||
sb.Append("\t\t///<summary>\n");
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(property.Description))
|
||||
sb.AppendFormat("\t\t/// {0}: {1}\n", XmlCommentString(property.Name), XmlCommentString(property.Description));
|
||||
else
|
||||
sb.AppendFormat("\t\t/// {0}\n", XmlCommentString(property.Name));
|
||||
|
||||
sb.Append("\t\t///</summary>\n");
|
||||
}
|
||||
|
||||
WriteGeneratedCodeAttribute(sb, "\t\t");
|
||||
sb.AppendFormat("\t\t[ImplementPropertyType(\"{0}\")]\n", property.Alias);
|
||||
|
||||
if (mixinStatic)
|
||||
{
|
||||
sb.Append("\t\tpublic ");
|
||||
WriteClrType(sb, property.ClrTypeName);
|
||||
sb.AppendFormat(" {0} => {1}(this);\n",
|
||||
property.ClrName, MixinStaticGetterName(property.ClrName));
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append("\t\tpublic ");
|
||||
WriteClrType(sb, property.ClrTypeName);
|
||||
sb.AppendFormat(" {0} => this.Value",
|
||||
property.ClrName);
|
||||
if (property.ModelClrType != typeof(object))
|
||||
{
|
||||
sb.Append("<");
|
||||
WriteClrType(sb, property.ClrTypeName);
|
||||
sb.Append(">");
|
||||
}
|
||||
sb.AppendFormat("(\"{0}\");\n",
|
||||
property.Alias);
|
||||
}
|
||||
|
||||
if (property.Errors != null)
|
||||
{
|
||||
sb.Append("\n");
|
||||
sb.Append("\t\t *\n");
|
||||
sb.Append("\t\t */\n");
|
||||
}
|
||||
|
||||
if (!mixinStatic) return;
|
||||
|
||||
var mixinStaticGetterName = MixinStaticGetterName(property.ClrName);
|
||||
|
||||
//if (type.StaticMixinMethods.Contains(mixinStaticGetterName)) return;
|
||||
|
||||
sb.Append("\n");
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(property.Name))
|
||||
sb.AppendFormat("\t\t/// <summary>Static getter for {0}</summary>\n", XmlCommentString(property.Name));
|
||||
|
||||
WriteGeneratedCodeAttribute(sb, "\t\t");
|
||||
sb.Append("\t\tpublic static ");
|
||||
WriteClrType(sb, property.ClrTypeName);
|
||||
sb.AppendFormat(" {0}(I{1} that) => that.Value",
|
||||
mixinStaticGetterName, mixinClrName);
|
||||
if (property.ModelClrType != typeof(object))
|
||||
{
|
||||
sb.Append("<");
|
||||
WriteClrType(sb, property.ClrTypeName);
|
||||
sb.Append(">");
|
||||
}
|
||||
sb.AppendFormat("(\"{0}\");\n",
|
||||
property.Alias);
|
||||
}
|
||||
|
||||
private static IEnumerable<string> SplitError(string error)
|
||||
{
|
||||
var p = 0;
|
||||
while (p < error.Length)
|
||||
{
|
||||
var n = p + 50;
|
||||
while (n < error.Length && error[n] != ' ') n++;
|
||||
if (n >= error.Length) break;
|
||||
yield return error.Substring(p, n - p);
|
||||
p = n + 1;
|
||||
}
|
||||
if (p < error.Length)
|
||||
yield return error.Substring(p);
|
||||
}
|
||||
|
||||
private void WriteInterfaceProperty(StringBuilder sb, PropertyModel property)
|
||||
{
|
||||
if (property.Errors != null)
|
||||
{
|
||||
sb.Append("\t\t/*\n");
|
||||
sb.Append("\t\t * THIS PROPERTY CANNOT BE IMPLEMENTED, BECAUSE:\n");
|
||||
sb.Append("\t\t *\n");
|
||||
var first = true;
|
||||
foreach (var error in property.Errors)
|
||||
{
|
||||
if (first) first = false;
|
||||
else sb.Append("\t\t *\n");
|
||||
foreach (var s in SplitError(error))
|
||||
{
|
||||
sb.Append("\t\t * ");
|
||||
sb.Append(s);
|
||||
sb.Append("\n");
|
||||
}
|
||||
}
|
||||
sb.Append("\t\t *\n");
|
||||
sb.Append("\n");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(property.Name))
|
||||
sb.AppendFormat("\t\t/// <summary>{0}</summary>\n", XmlCommentString(property.Name));
|
||||
WriteGeneratedCodeAttribute(sb, "\t\t");
|
||||
sb.Append("\t\t");
|
||||
WriteClrType(sb, property.ClrTypeName);
|
||||
sb.AppendFormat(" {0} {{ get; }}\n",
|
||||
property.ClrName);
|
||||
|
||||
if (property.Errors != null)
|
||||
{
|
||||
sb.Append("\n");
|
||||
sb.Append("\t\t *\n");
|
||||
sb.Append("\t\t */\n");
|
||||
}
|
||||
}
|
||||
|
||||
// internal for unit tests
|
||||
internal void WriteClrType(StringBuilder sb, Type type)
|
||||
{
|
||||
var s = type.ToString();
|
||||
|
||||
if (type.IsGenericType)
|
||||
{
|
||||
var p = s.IndexOf('`');
|
||||
WriteNonGenericClrType(sb, s.Substring(0, p));
|
||||
sb.Append("<");
|
||||
var args = type.GetGenericArguments();
|
||||
for (var i = 0; i < args.Length; i++)
|
||||
{
|
||||
if (i > 0) sb.Append(", ");
|
||||
WriteClrType(sb, args[i]);
|
||||
}
|
||||
sb.Append(">");
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteNonGenericClrType(sb, s);
|
||||
}
|
||||
}
|
||||
|
||||
internal void WriteClrType(StringBuilder sb, string type)
|
||||
{
|
||||
var p = type.IndexOf('<');
|
||||
if (type.Contains('<'))
|
||||
{
|
||||
WriteNonGenericClrType(sb, type.Substring(0, p));
|
||||
sb.Append("<");
|
||||
var args = type.Substring(p + 1).TrimEnd('>').Split(','); // fixme will NOT work with nested generic types
|
||||
for (var i = 0; i < args.Length; i++)
|
||||
{
|
||||
if (i > 0) sb.Append(", ");
|
||||
WriteClrType(sb, args[i]);
|
||||
}
|
||||
sb.Append(">");
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteNonGenericClrType(sb, type);
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteNonGenericClrType(StringBuilder sb, string s)
|
||||
{
|
||||
// map model types
|
||||
s = Regex.Replace(s, @"\{(.*)\}\[\*\]", m => ModelsMap[m.Groups[1].Value + "[]"]);
|
||||
|
||||
// takes care eg of "System.Int32" vs. "int"
|
||||
if (TypesMap.TryGetValue(s, out string typeName))
|
||||
{
|
||||
sb.Append(typeName);
|
||||
return;
|
||||
}
|
||||
|
||||
// if full type name matches a using clause, strip
|
||||
// so if we want Umbraco.Core.Models.IPublishedContent
|
||||
// and using Umbraco.Core.Models, then we just need IPublishedContent
|
||||
typeName = s;
|
||||
string typeUsing = null;
|
||||
var p = typeName.LastIndexOf('.');
|
||||
if (p > 0)
|
||||
{
|
||||
var x = typeName.Substring(0, p);
|
||||
if (Using.Contains(x))
|
||||
{
|
||||
typeName = typeName.Substring(p + 1);
|
||||
typeUsing = x;
|
||||
}
|
||||
else if (x == ModelsNamespace) // that one is used by default
|
||||
{
|
||||
typeName = typeName.Substring(p + 1);
|
||||
typeUsing = ModelsNamespace;
|
||||
}
|
||||
}
|
||||
|
||||
// nested types *after* using
|
||||
typeName = typeName.Replace("+", ".");
|
||||
|
||||
// symbol to test is the first part of the name
|
||||
// so if type name is Foo.Bar.Nil we want to ensure that Foo is not ambiguous
|
||||
p = typeName.IndexOf('.');
|
||||
var symbol = p > 0 ? typeName.Substring(0, p) : typeName;
|
||||
|
||||
// what we should find - WITHOUT any generic <T> thing - just the type
|
||||
// no 'using' = the exact symbol
|
||||
// a 'using' = using.symbol
|
||||
var match = typeUsing == null ? symbol : (typeUsing + "." + symbol);
|
||||
|
||||
// if not ambiguous, be happy
|
||||
if (!IsAmbiguousSymbol(symbol, match))
|
||||
{
|
||||
sb.Append(typeName);
|
||||
return;
|
||||
}
|
||||
|
||||
// symbol is ambiguous
|
||||
// if no 'using', must prepend global::
|
||||
if (typeUsing == null)
|
||||
{
|
||||
sb.Append("global::");
|
||||
sb.Append(s.Replace("+", "."));
|
||||
return;
|
||||
}
|
||||
|
||||
// could fullname be non-ambiguous?
|
||||
// note: all-or-nothing, not trying to segment the using clause
|
||||
typeName = s.Replace("+", ".");
|
||||
p = typeName.IndexOf('.');
|
||||
symbol = typeName.Substring(0, p);
|
||||
match = symbol;
|
||||
|
||||
// still ambiguous, must prepend global::
|
||||
if (IsAmbiguousSymbol(symbol, match))
|
||||
sb.Append("global::");
|
||||
|
||||
sb.Append(typeName);
|
||||
}
|
||||
|
||||
private static string XmlCommentString(string s)
|
||||
{
|
||||
return s.Replace('<', '{').Replace('>', '}').Replace('\r', ' ').Replace('\n', ' ');
|
||||
}
|
||||
|
||||
private static readonly IDictionary<string, string> TypesMap = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ "System.Int16", "short" },
|
||||
{ "System.Int32", "int" },
|
||||
{ "System.Int64", "long" },
|
||||
{ "System.String", "string" },
|
||||
{ "System.Object", "object" },
|
||||
{ "System.Boolean", "bool" },
|
||||
{ "System.Void", "void" },
|
||||
{ "System.Char", "char" },
|
||||
{ "System.Byte", "byte" },
|
||||
{ "System.UInt16", "ushort" },
|
||||
{ "System.UInt32", "uint" },
|
||||
{ "System.UInt64", "ulong" },
|
||||
{ "System.SByte", "sbyte" },
|
||||
{ "System.Single", "float" },
|
||||
{ "System.Double", "double" },
|
||||
{ "System.Decimal", "decimal" }
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using System.Text;
|
||||
|
||||
namespace Umbraco.ModelsBuilder.Embedded.Building
|
||||
{
|
||||
internal static class TextHeaderWriter
|
||||
{
|
||||
/// <summary>
|
||||
/// Outputs an "auto-generated" header to a string builder.
|
||||
/// </summary>
|
||||
/// <param name="sb">The string builder.</param>
|
||||
public static void WriteHeader(StringBuilder sb)
|
||||
{
|
||||
sb.Append("//------------------------------------------------------------------------------\n");
|
||||
sb.Append("// <auto-generated>\n");
|
||||
sb.Append("// This code was generated by a tool.\n");
|
||||
sb.Append("//\n");
|
||||
sb.AppendFormat("// Umbraco.ModelsBuilder v{0}\n", ApiVersion.Current.Version);
|
||||
sb.Append("//\n");
|
||||
sb.Append("// Changes to this file will be lost if the code is regenerated.\n");
|
||||
sb.Append("// </auto-generated>\n");
|
||||
sb.Append("//------------------------------------------------------------------------------\n");
|
||||
sb.Append("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
205
src/Umbraco.ModelsBuilder.Embedded/Building/TypeModel.cs
Normal file
205
src/Umbraco.ModelsBuilder.Embedded/Building/TypeModel.cs
Normal file
@@ -0,0 +1,205 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
|
||||
namespace Umbraco.ModelsBuilder.Embedded.Building
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a model.
|
||||
/// </summary>
|
||||
public class TypeModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the unique identifier of the corresponding content type.
|
||||
/// </summary>
|
||||
public int Id;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the alias of the model.
|
||||
/// </summary>
|
||||
public string Alias;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the content type.
|
||||
/// </summary>
|
||||
public string Name;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the description of the content type.
|
||||
/// </summary>
|
||||
public string Description;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the clr name of the model.
|
||||
/// </summary>
|
||||
/// <remarks>This is the complete name eg "Foo.Bar.MyContent".</remarks>
|
||||
public string ClrName;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the unique identifier of the parent.
|
||||
/// </summary>
|
||||
/// <remarks>The parent can either be a base content type, or a content types container. If the content
|
||||
/// type does not have a base content type, then returns <c>-1</c>.</remarks>
|
||||
public int ParentId;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the base model.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>If the content type does not have a base content type, then returns <c>null</c>.</para>
|
||||
/// <para>The current model inherits from its base model.</para>
|
||||
/// </remarks>
|
||||
public TypeModel BaseType; // the parent type in Umbraco (type inherits its properties)
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of properties that are defined by this model.
|
||||
/// </summary>
|
||||
/// <remarks>These are only those property that are defined locally by this model,
|
||||
/// and the list does not contain properties inherited from base models or from mixins.</remarks>
|
||||
public readonly List<PropertyModel> Properties = new List<PropertyModel>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the mixin models.
|
||||
/// </summary>
|
||||
/// <remarks>The current model implements mixins.</remarks>
|
||||
public readonly List<TypeModel> MixinTypes = new List<TypeModel>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of interfaces that this model needs to declare it implements.
|
||||
/// </summary>
|
||||
/// <remarks>Some of these interfaces may actually be implemented by a base model
|
||||
/// that this model inherits from.</remarks>
|
||||
public readonly List<TypeModel> DeclaringInterfaces = new List<TypeModel>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of interfaces that this model needs to actually implement.
|
||||
/// </summary>
|
||||
public readonly List<TypeModel> ImplementingInterfaces = new List<TypeModel>();
|
||||
|
||||
///// <summary>
|
||||
///// Gets the list of existing static mixin method candidates.
|
||||
///// </summary>
|
||||
//public readonly List<string> StaticMixinMethods = new List<string>(); //TODO: Do we need this? it isn't used
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this model has a base class.
|
||||
/// </summary>
|
||||
/// <remarks>Can be either because the content type has a base content type declared in Umbraco,
|
||||
/// or because the existing user's code declares a base class for this model.</remarks>
|
||||
public bool HasBase;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this model is used as a mixin by another model.
|
||||
/// </summary>
|
||||
public bool IsMixin;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this model is the base model of another model.
|
||||
/// </summary>
|
||||
public bool IsParent;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the type is an element.
|
||||
/// </summary>
|
||||
public bool IsElement => ItemType == ItemTypes.Element;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the different model item types.
|
||||
/// </summary>
|
||||
public enum ItemTypes
|
||||
{
|
||||
/// <summary>
|
||||
/// Element.
|
||||
/// </summary>
|
||||
Element,
|
||||
|
||||
/// <summary>
|
||||
/// Content.
|
||||
/// </summary>
|
||||
Content,
|
||||
|
||||
/// <summary>
|
||||
/// Media.
|
||||
/// </summary>
|
||||
Media,
|
||||
|
||||
/// <summary>
|
||||
/// Member.
|
||||
/// </summary>
|
||||
Member
|
||||
}
|
||||
|
||||
private ItemTypes _itemType;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the model item type.
|
||||
/// </summary>
|
||||
public ItemTypes ItemType
|
||||
{
|
||||
get { return _itemType; }
|
||||
set
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case ItemTypes.Element:
|
||||
case ItemTypes.Content:
|
||||
case ItemTypes.Media:
|
||||
case ItemTypes.Member:
|
||||
_itemType = value;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException("value");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recursively collects all types inherited, or implemented as interfaces, by a specified type.
|
||||
/// </summary>
|
||||
/// <param name="types">The collection.</param>
|
||||
/// <param name="type">The type.</param>
|
||||
/// <remarks>Includes the specified type.</remarks>
|
||||
internal static void CollectImplems(ICollection<TypeModel> types, TypeModel type)
|
||||
{
|
||||
if (types.Contains(type) == false)
|
||||
types.Add(type);
|
||||
if (type.BaseType != null)
|
||||
CollectImplems(types, type.BaseType);
|
||||
foreach (var mixin in type.MixinTypes)
|
||||
CollectImplems(types, mixin);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enumerates the base models starting from the current model up.
|
||||
/// </summary>
|
||||
/// <param name="andSelf">Indicates whether the enumeration should start with the current model
|
||||
/// or from its base model.</param>
|
||||
/// <returns>The base models.</returns>
|
||||
public IEnumerable<TypeModel> EnumerateBaseTypes(bool andSelf = false)
|
||||
{
|
||||
var typeModel = andSelf ? this : BaseType;
|
||||
while (typeModel != null)
|
||||
{
|
||||
yield return typeModel;
|
||||
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,41 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Umbraco.ModelsBuilder.Embedded.Building
|
||||
{
|
||||
internal class TypeModelHasher
|
||||
{
|
||||
public static string Hash(IEnumerable<TypeModel> typeModels)
|
||||
{
|
||||
var hash = new HashCombiner();
|
||||
|
||||
// see Umbraco.ModelsBuilder.Umbraco.Application for what's important to hash
|
||||
// ie what comes from Umbraco (not computed by ModelsBuilder) and makes a difference
|
||||
|
||||
foreach (var typeModel in typeModels.OrderBy(x => x.Alias))
|
||||
{
|
||||
hash.Add("--- CONTENT TYPE MODEL ---");
|
||||
hash.Add(typeModel.Id);
|
||||
hash.Add(typeModel.Alias);
|
||||
hash.Add(typeModel.ClrName);
|
||||
hash.Add(typeModel.ParentId);
|
||||
hash.Add(typeModel.Name);
|
||||
hash.Add(typeModel.Description);
|
||||
hash.Add(typeModel.ItemType.ToString());
|
||||
hash.Add("MIXINS:" + string.Join(",", typeModel.MixinTypes.OrderBy(x => x.Id).Select(x => x.Id)));
|
||||
|
||||
foreach (var prop in typeModel.Properties.OrderBy(x => x.Alias))
|
||||
{
|
||||
hash.Add("--- PROPERTY ---");
|
||||
hash.Add(prop.Alias);
|
||||
hash.Add(prop.ClrName);
|
||||
hash.Add(prop.Name);
|
||||
hash.Add(prop.Description);
|
||||
hash.Add(prop.ModelClrType.ToString()); // see ModelType tests, want ToString() not FullName
|
||||
}
|
||||
}
|
||||
|
||||
return hash.GetCombinedHashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user