├── src ├── Our.ModelsBuilder.Extension │ ├── ItemTemplate │ │ ├── Models.mb │ │ ├── Models.ico │ │ └── Models.vstemplate │ ├── u32.png │ ├── Resources │ │ └── Package.ico │ ├── ItemTemplate.Etc │ │ ├── Models.png │ │ ├── Umbraco Models.zip │ │ └── Models.txt │ ├── icons.pkgdef │ ├── VisualStudio │ │ └── vsContextGuids.cs │ ├── debug.txt │ ├── Guids.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── GlobalSuppressions.cs │ ├── license.txt │ ├── GeneratorWindow.xaml │ ├── OptionsDialog.cs │ ├── source.extension.vsixmanifest │ ├── GeneratorWindow.xaml.cs │ └── ExtensionPackage.vsct ├── Our.ModelsBuilder │ ├── Options │ │ ├── IOptionsComposer.cs │ │ ├── ClrNameSource.cs │ │ ├── ContentTypes │ │ │ ├── FallbackStyle.cs │ │ │ └── PropertyStyle.cs │ │ ├── OptionsCompositionExtensions.cs │ │ ├── CodeOptions.cs │ │ ├── OptionsConfiguration.cs │ │ ├── ModelsModeExtensions.cs │ │ ├── ModelsMode.cs │ │ ├── CodeOptionsBuilder.cs │ │ └── ModelsBuilderOptions.cs │ ├── Building │ │ ├── ICodeModelBuilder.cs │ │ ├── ICodeModelDataSource.cs │ │ ├── CodeModelData.cs │ │ ├── PropertyModelExtensions.cs │ │ ├── ContentTypeKind.cs │ │ ├── CompilerException.cs │ │ ├── ICodeParser.cs │ │ ├── CodeModelDataSource.cs │ │ ├── TextHeaderWriter.cs │ │ ├── ICodeFactory.cs │ │ ├── ICodeWriter.cs │ │ ├── ContentTypeIdentity.cs │ │ ├── ContentTypesCodeModel.cs │ │ ├── ContentTypeMixinVisitor.cs │ │ ├── CodeFactory.cs │ │ ├── PropertyTypeModel.cs │ │ ├── CodeWriter.cs │ │ ├── CodeModelBuilder.cs │ │ └── Generator.cs │ ├── PropertyTypeModelInfo.cs │ ├── Umbraco │ │ ├── ModelTypeCollectionBuilder.cs │ │ ├── ModelTypeCollection.cs │ │ ├── LiveModelsProviderModule.cs │ │ ├── HashCombiner.cs │ │ ├── ModelsBuilderInitializer.cs │ │ ├── HashHelper.cs │ │ ├── ModelsGenerationError.cs │ │ ├── PublishedModelUtility.cs │ │ ├── OutOfDateModelsStatus.cs │ │ ├── ModelsBuilderComposer.cs │ │ └── LiveModelsProvider.cs │ ├── CompositionExtensions.cs │ ├── Validation │ │ ├── MediaTypeModelValidator.cs │ │ ├── MemberTypeModelValidator.cs │ │ ├── ContentTypeModelValidator.cs │ │ └── ContentTypeModelValidatorBase.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── ContentTypeModelInfo.cs │ ├── TypeModelItemTypesExtensions.cs │ ├── ModelsBuilderAssemblyAttribute.cs │ ├── ImplementPropertyTypeAttribute.cs │ ├── EnumerableExtensions.cs │ ├── TypeExtensions.cs │ ├── UmbracoExtensions │ │ ├── FallbackInfos.cs │ │ ├── FallbackValue.cs │ │ └── PublishedElementExtensions.cs │ └── Api │ │ └── ApiVersion.cs ├── Our.ModelsBuilder.Console │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── App.config │ └── Our.ModelsBuilder.Console.csproj ├── Our.ModelsBuilder.Tests │ ├── Testing │ │ ├── StringExtensions.cs │ │ └── TestsBase.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── Custom │ │ ├── CustomCodeModelBuilder.cs │ │ ├── ComposeConfigTests.cs │ │ └── CustomCodeFactory.cs │ ├── ExtensionsTests.cs │ ├── App.config │ ├── UmbracoInternals.cs │ ├── ConfigTests.cs │ ├── Compile │ │ ├── CSharpCodeProviderTests.cs │ │ └── CompilerTests.cs │ ├── UmbracoApplicationTests.cs │ ├── Write │ │ ├── WriteEdgeCasesTests.cs │ │ └── WriteClrTypeTests.cs │ ├── ApiTests.cs │ ├── DotNet │ │ ├── ExpressionTests.cs │ │ └── AppDomainTests.cs │ ├── ApiVersionTests.cs │ └── Our.ModelsBuilder.Tests.csproj ├── Our.ModelsBuilder.Web │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── Api │ │ ├── TokenData.cs │ │ ├── GetModelsData.cs │ │ ├── ValidateClientVersionData.cs │ │ └── ApiBasicAuthFilter.cs │ ├── Plugin │ │ ├── lang │ │ │ └── en-US.xml │ │ ├── modelsbuilder.controller.js │ │ ├── modelsbuilder.resource.js │ │ ├── modelsbuilder.html │ │ └── DashboardUtilities.cs │ ├── ApiRoute.cs │ ├── WebManifestFilter.cs │ ├── WebComponent.cs │ ├── Our.ModelsBuilder.Web.csproj │ └── WebComposer.cs ├── SolutionInfo.cs └── Our.ModelsBuilder.sln ├── etc ├── logo.ai └── logo.png ├── .nuget └── NuGet.Config ├── NuGet.config ├── .gitignore ├── license.txt └── readme.md /src/Our.ModelsBuilder.Extension/ItemTemplate/Models.mb: -------------------------------------------------------------------------------- 1 | // Models -------------------------------------------------------------------------------- /etc/logo.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelsbuilder/ModelsBuilder.Original/HEAD/etc/logo.ai -------------------------------------------------------------------------------- /etc/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelsbuilder/ModelsBuilder.Original/HEAD/etc/logo.png -------------------------------------------------------------------------------- /src/Our.ModelsBuilder.Extension/u32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelsbuilder/ModelsBuilder.Original/HEAD/src/Our.ModelsBuilder.Extension/u32.png -------------------------------------------------------------------------------- /src/Our.ModelsBuilder.Extension/Resources/Package.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelsbuilder/ModelsBuilder.Original/HEAD/src/Our.ModelsBuilder.Extension/Resources/Package.ico -------------------------------------------------------------------------------- /src/Our.ModelsBuilder.Extension/ItemTemplate/Models.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelsbuilder/ModelsBuilder.Original/HEAD/src/Our.ModelsBuilder.Extension/ItemTemplate/Models.ico -------------------------------------------------------------------------------- /src/Our.ModelsBuilder.Extension/ItemTemplate.Etc/Models.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelsbuilder/ModelsBuilder.Original/HEAD/src/Our.ModelsBuilder.Extension/ItemTemplate.Etc/Models.png -------------------------------------------------------------------------------- /.nuget/NuGet.Config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/Our.ModelsBuilder.Extension/ItemTemplate.Etc/Umbraco Models.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelsbuilder/ModelsBuilder.Original/HEAD/src/Our.ModelsBuilder.Extension/ItemTemplate.Etc/Umbraco Models.zip -------------------------------------------------------------------------------- /src/Our.ModelsBuilder.Extension/icons.pkgdef: -------------------------------------------------------------------------------- 1 | ; icons 2 | ; see also: https://github.com/madskristensen/FileIcons 3 | ; and use KnownMonitors Explorer 4 | 5 | [$RootKey$\ShellFileAssociations\.mb] 6 | "DefaultIconMoniker"="KnownMonikers.GenerateFile" -------------------------------------------------------------------------------- /src/Our.ModelsBuilder.Extension/VisualStudio/vsContextGuids.cs: -------------------------------------------------------------------------------- 1 | namespace Our.ModelsBuilder.Extension.VisualStudio 2 | { 3 | public class vsContextGuids 4 | { 5 | public const string vsContextGuidNetSdkProject = "{9A19103F-16F7-4668-BE54-9A1E7A4F7556}"; 6 | } 7 | } -------------------------------------------------------------------------------- /NuGet.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/Our.ModelsBuilder/Options/IOptionsComposer.cs: -------------------------------------------------------------------------------- 1 | using Our.ModelsBuilder.Umbraco; 2 | using Umbraco.Core.Composing; 3 | 4 | namespace Our.ModelsBuilder.Options 5 | { 6 | [ComposeAfter(typeof(ModelsBuilderComposer))] 7 | public interface IOptionsComposer : IComposer 8 | { } 9 | } 10 | -------------------------------------------------------------------------------- /src/Our.ModelsBuilder.Console/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | [assembly: AssemblyTitle("Our ModelsBuilder Console")] 5 | [assembly: AssemblyDescription("Our ModelsBuilder Console.")] 6 | [assembly: Guid("94a87170-d7db-466b-bfda-a7b1fe9790b1")] 7 | -------------------------------------------------------------------------------- /src/Our.ModelsBuilder.Tests/Testing/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Our.ModelsBuilder.Tests.Testing 2 | { 3 | public static class StringExtensions 4 | { 5 | public static string ClearLf(this string s) 6 | { 7 | return s.Replace("\r", ""); 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Our.ModelsBuilder.Console/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/Our.ModelsBuilder.Extension/ItemTemplate.Etc/Models.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | https://docs.microsoft.com/en-us/visualstudio/ide/how-to-create-item-templates?view=vs-2019 4 | 5 | icon exported from KnownMoniker explorer 6 | 7 | https://marketplace.visualstudio.com/items?itemName=MadsKristensen.FileIcons 8 | 9 | icon 10 | 11 | https://github.com/madskristensen/FileIcons -------------------------------------------------------------------------------- /src/Our.ModelsBuilder.Extension/debug.txt: -------------------------------------------------------------------------------- 1 | read http://stackoverflow.com/questions/17625752/cannot-run-vspackage-when-developing-on-multiple-machines 2 | 3 | the debug config of a package tool is saved in the .user file that is not versionned 4 | to enable it: 5 | edit the properties / Debug for the project 6 | set Start action to external program 7 | set Command line arguments to /rootsuffix Exp 8 | 9 | -------------------------------------------------------------------------------- /src/Our.ModelsBuilder.Web/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | [assembly: AssemblyTitle("Our ModelsBuilder Api")] 6 | [assembly: AssemblyDescription("Our ModelsBuilder Api.")] 7 | [assembly: Guid("2317be7f-1723-4512-b863-5b6835e583a2")] 8 | 9 | [assembly: InternalsVisibleTo("Our.ModelsBuilder.Tests")] -------------------------------------------------------------------------------- /src/Our.ModelsBuilder/Building/ICodeModelBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace Our.ModelsBuilder.Building 2 | { 3 | /// 4 | /// Builds a . 5 | /// 6 | public interface ICodeModelBuilder 7 | { 8 | /// 9 | /// Builds a . 10 | /// 11 | CodeModel Build(CodeModelData data); 12 | } 13 | } -------------------------------------------------------------------------------- /src/Our.ModelsBuilder/Building/ICodeModelDataSource.cs: -------------------------------------------------------------------------------- 1 | namespace Our.ModelsBuilder.Building 2 | { 3 | /// 4 | /// Provides . 5 | /// 6 | public interface ICodeModelDataSource 7 | { 8 | /// 9 | /// Gets . 10 | /// 11 | CodeModelData GetCodeModelData(); 12 | } 13 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # common 2 | bin 3 | obj 4 | pkg 5 | *.suo 6 | *.user 7 | packages 8 | refs 9 | *.snk 10 | .vs/ 11 | 12 | # temp 13 | *.pdb/ 14 | _ReSharper.Caches/* 15 | App_Data/ 16 | 17 | # build artifact that *has* to be there 18 | /src/ZpqrtBnk.ModelsBuilder.Extension/ItemTemplate.Etc/Umbraco Models.zip 19 | 20 | # build 21 | build/temp/ 22 | build/hooks/ 23 | build.tmp/ 24 | build.out/ 25 | 26 | # eof 27 | src/.idea 28 | -------------------------------------------------------------------------------- /src/Our.ModelsBuilder.Extension/Guids.cs: -------------------------------------------------------------------------------- 1 | // Guids.cs 2 | // MUST match guids.h 3 | 4 | using System; 5 | 6 | namespace Our.ModelsBuilder.Extension 7 | { 8 | static class GuidList 9 | { 10 | public const string PkgString = "6a4c1726-440f-4b2d-a2e5-711277da6099"; 11 | public const string CmdSetString = "fb40dc0b-2f75-404c-ba4e-dc1b90c41941"; 12 | 13 | public static readonly Guid CmdSet = new Guid(CmdSetString); 14 | }; 15 | } -------------------------------------------------------------------------------- /src/Our.ModelsBuilder.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | [assembly: AssemblyTitle("Our ModelsBuilder Tests")] 6 | [assembly: AssemblyDescription("Our ModelsBuilder Tests.")] 7 | [assembly: Guid("5957b45a-c2c9-4913-be3f-6677ebf50294")] 8 | 9 | // dynamic assembly that is built during tests - do not remove! 10 | [assembly: InternalsVisibleTo("Our.ModelsBuilder.RunTests")] 11 | -------------------------------------------------------------------------------- /src/Our.ModelsBuilder/Building/CodeModelData.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Our.ModelsBuilder.Building 4 | { 5 | /// 6 | /// Represents a source for code models. 7 | /// 8 | public class CodeModelData 9 | { 10 | /// 11 | /// Gets or sets the list of content type models. 12 | /// 13 | public List ContentTypes { get; set; } = new List(); 14 | } 15 | } -------------------------------------------------------------------------------- /src/Our.ModelsBuilder.Web/Api/TokenData.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | 3 | namespace Our.ModelsBuilder.Web.Api 4 | { 5 | [DataContract] 6 | class TokenData 7 | { 8 | [DataMember(Name = "access_token")] 9 | public string AccessToken { get; set; } 10 | 11 | [DataMember(Name = "token_type")] 12 | public string TokenType { get; set; } 13 | 14 | [DataMember(Name = "expires_in")] 15 | public int ExpiresIn { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Our.ModelsBuilder/PropertyTypeModelInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Our.ModelsBuilder 4 | { 5 | public class PropertyTypeModelInfo 6 | { 7 | public PropertyTypeModelInfo(string alias, string clrName, Type clrType) 8 | { 9 | Alias = alias; 10 | ClrName = clrName; 11 | ClrType = clrType; 12 | } 13 | 14 | public string Alias { get; } 15 | public string ClrName { get; } 16 | public Type ClrType { get; } 17 | } 18 | } -------------------------------------------------------------------------------- /src/Our.ModelsBuilder.Tests/Custom/CustomCodeModelBuilder.cs: -------------------------------------------------------------------------------- 1 | using Our.ModelsBuilder.Building; 2 | using Our.ModelsBuilder.Options; 3 | 4 | namespace Our.ModelsBuilder.Tests.Custom 5 | { 6 | public class CustomCodeModelBuilder : CodeModelBuilder 7 | { 8 | public CustomCodeModelBuilder(ModelsBuilderOptions options, CodeOptions codeOptions, ContentTypesCodeModelBuilder contentTypesCodeModelBuilder) 9 | : base(options, codeOptions, contentTypesCodeModelBuilder) 10 | { } 11 | } 12 | } -------------------------------------------------------------------------------- /src/Our.ModelsBuilder/Building/PropertyModelExtensions.cs: -------------------------------------------------------------------------------- 1 | using Umbraco.Core.Models; 2 | 3 | namespace Our.ModelsBuilder.Building 4 | { 5 | public static class PropertyModelExtensions 6 | { 7 | public static bool VariesByCulture(this PropertyTypeModel property) 8 | => (property.Variations & ContentVariation.Culture) > 0; 9 | 10 | public static bool VariesBySegment(this PropertyTypeModel property) 11 | => (property.Variations & ContentVariation.Segment) > 0; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Our.ModelsBuilder.Web/Api/GetModelsData.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Runtime.Serialization; 3 | 4 | namespace Our.ModelsBuilder.Web.Api 5 | { 6 | [DataContract] 7 | public class GetModelsData : ValidateClientVersionData 8 | { 9 | [DataMember] 10 | public string Namespace { get; set; } 11 | 12 | [DataMember] 13 | public IDictionary Files { get; set; } 14 | 15 | public override bool IsValid => base.IsValid && Files != null; 16 | } 17 | } -------------------------------------------------------------------------------- /src/Our.ModelsBuilder.Tests/Custom/ComposeConfigTests.cs: -------------------------------------------------------------------------------- 1 | using Our.ModelsBuilder.Options; 2 | using Umbraco.Core.Composing; 3 | namespace Our.ModelsBuilder.Tests.Custom 4 | { 5 | // ReSharper disable once UnusedMember.Global, reason: composer 6 | public class CustomConfigComposer : IOptionsComposer 7 | { 8 | public void Compose(Composition composition) 9 | { 10 | composition.ConfigureCodeOptions(optionsBuilder => optionsBuilder.ContentTypes.IgnoreContentType("blah")); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Our.ModelsBuilder.Extension/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics.CodeAnalysis; 3 | using System.Reflection; 4 | using System.Resources; 5 | 6 | [assembly: AssemblyTitle("Umbraco ModelsBuilder VisualStudio Extension")] 7 | [assembly: AssemblyDescription("Umbraco ModelsBuilder VisualStudio Extension.")] 8 | [assembly: CLSCompliant(false)] 9 | [assembly: NeutralResourcesLanguage("en-US")] 10 | // no guid 11 | 12 | [assembly:SuppressMessage("Microsoft.VisualStudio.Threading.Analyzers", "VSTHRD010", Justification = "Bah.")] -------------------------------------------------------------------------------- /src/Our.ModelsBuilder/Umbraco/ModelTypeCollectionBuilder.cs: -------------------------------------------------------------------------------- 1 | using Umbraco.Core.Composing; 2 | using Umbraco.Core.Models.PublishedContent; 3 | 4 | namespace Our.ModelsBuilder.Umbraco 5 | { 6 | /// 7 | /// Represents the collection builder for the . 8 | /// 9 | public class ModelTypeCollectionBuilder : TypeCollectionBuilderBase 10 | { 11 | /// 12 | protected override ModelTypeCollectionBuilder This => this; 13 | } 14 | } -------------------------------------------------------------------------------- /src/Our.ModelsBuilder.Extension/ItemTemplate/Models.vstemplate: -------------------------------------------------------------------------------- 1 | 2 | 3 | Models.mb 4 | Umbraco Models 5 | An Umbraco Models placeholder 6 | CSharp 7 | 10 8 | Models.ico 9 | 10 | 11 | Models.mb 12 | 13 | -------------------------------------------------------------------------------- /src/Our.ModelsBuilder/CompositionExtensions.cs: -------------------------------------------------------------------------------- 1 | using Our.ModelsBuilder.Umbraco; 2 | using Umbraco.Core.Composing; 3 | 4 | namespace Our.ModelsBuilder 5 | { 6 | /// 7 | /// Provides extension methods for the class. 8 | /// 9 | public static class CompositionExtensions 10 | { 11 | /// 12 | /// Gets the model types collection builder. 13 | /// 14 | public static ModelTypeCollectionBuilder ModelTypes(this Composition composition) 15 | => composition.WithCollectionBuilder(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Our.ModelsBuilder/Umbraco/ModelTypeCollection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Umbraco.Core.Composing; 4 | 5 | namespace Our.ModelsBuilder.Umbraco 6 | { 7 | /// 8 | /// Represents a collection of models for the PublishedModelFactory. 9 | /// 10 | public class ModelTypeCollection : BuilderCollectionBase 11 | { 12 | /// 13 | /// Initializes a new instance of the class. 14 | /// 15 | public ModelTypeCollection(IEnumerable items) 16 | : base(items) 17 | { } 18 | } 19 | } -------------------------------------------------------------------------------- /src/Our.ModelsBuilder.Tests/ExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using NUnit.Framework; 3 | 4 | namespace Our.ModelsBuilder.Tests 5 | { 6 | [TestFixture] 7 | public class ExtensionsTests 8 | { 9 | [Test] 10 | public void RemoveAll() 11 | { 12 | var list = new List 13 | { 14 | "a1", "z1", "a2", "z2", "a3", "z3" 15 | }; 16 | 17 | list.RemoveAll(x => x.StartsWith("a")); 18 | 19 | Assert.AreEqual(3, list.Count); 20 | Assert.IsTrue(list.TrueForAll(x => x.StartsWith("z"))); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Our.ModelsBuilder/Building/ContentTypeKind.cs: -------------------------------------------------------------------------------- 1 | namespace Our.ModelsBuilder.Building 2 | { 3 | /// 4 | /// Represents the different content type kinds. 5 | /// 6 | public enum ContentTypeKind 7 | { 8 | /// 9 | /// Element. 10 | /// 11 | Element, 12 | 13 | /// 14 | /// Content. 15 | /// 16 | Content, 17 | 18 | /// 19 | /// Media. 20 | /// 21 | Media, 22 | 23 | /// 24 | /// Member. 25 | /// 26 | Member 27 | } 28 | } -------------------------------------------------------------------------------- /src/Our.ModelsBuilder.Extension/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 | // This file is used by Code Analysis to maintain SuppressMessage 2 | // attributes that are applied to this project. Project-level 3 | // suppressions either have no target or are given a specific target 4 | // and scoped to a namespace, type, member, etc. 5 | // 6 | // To add a suppression to this file, right-click the message in the 7 | // Error List, point to "Suppress Message(s)", and click "In Project 8 | // Suppression File". You do not need to add suppressions to this 9 | // file manually. 10 | 11 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1017:MarkAssembliesWithComVisible")] 12 | -------------------------------------------------------------------------------- /src/Our.ModelsBuilder/Options/ClrNameSource.cs: -------------------------------------------------------------------------------- 1 | namespace Our.ModelsBuilder.Options 2 | { 3 | /// 4 | /// Defines the CLR name sources. 5 | /// 6 | public enum ClrNameSource 7 | { 8 | /// 9 | /// No source. 10 | /// 11 | Nothing = 0, 12 | 13 | /// 14 | /// Use the name as source. 15 | /// 16 | Name, 17 | 18 | /// 19 | /// Use the alias as source. 20 | /// 21 | Alias, 22 | 23 | /// 24 | /// Use the alias directly. 25 | /// 26 | RawAlias 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Our.ModelsBuilder/Validation/MediaTypeModelValidator.cs: -------------------------------------------------------------------------------- 1 | using Our.ModelsBuilder.Options; 2 | using Umbraco.Web.Models.ContentEditing; 3 | 4 | namespace Our.ModelsBuilder.Validation 5 | { 6 | /// 7 | /// Validates the media type aliases when ModelsBuilder is enabled. 8 | /// 9 | public class MediaTypeModelValidator : ContentTypeModelValidatorBase 10 | { 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// 14 | public MediaTypeModelValidator(ModelsBuilderOptions options) 15 | : base(options) 16 | { } 17 | } 18 | } -------------------------------------------------------------------------------- /src/Our.ModelsBuilder/Validation/MemberTypeModelValidator.cs: -------------------------------------------------------------------------------- 1 | using Our.ModelsBuilder.Options; 2 | using Umbraco.Web.Models.ContentEditing; 3 | 4 | namespace Our.ModelsBuilder.Validation 5 | { 6 | /// 7 | /// Validates the member type aliases when ModelsBuilder is enabled. 8 | /// 9 | public class MemberTypeModelValidator : ContentTypeModelValidatorBase 10 | { 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// 14 | public MemberTypeModelValidator(ModelsBuilderOptions options) 15 | : base(options) 16 | { } 17 | } 18 | } -------------------------------------------------------------------------------- /src/Our.ModelsBuilder/Validation/ContentTypeModelValidator.cs: -------------------------------------------------------------------------------- 1 | using Our.ModelsBuilder.Options; 2 | using Umbraco.Web.Models.ContentEditing; 3 | 4 | namespace Our.ModelsBuilder.Validation 5 | { 6 | /// 7 | /// Validates the content type aliases when ModelsBuilder is enabled. 8 | /// 9 | public class ContentTypeModelValidator : ContentTypeModelValidatorBase 10 | { 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// 14 | public ContentTypeModelValidator(ModelsBuilderOptions options) 15 | : base(options) 16 | { } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Our.ModelsBuilder.Tests/Testing/TestsBase.cs: -------------------------------------------------------------------------------- 1 | using Moq; 2 | using NUnit.Framework; 3 | using Umbraco.Core.Composing; 4 | using Umbraco.Core.Strings; 5 | 6 | namespace Our.ModelsBuilder.Tests.Testing 7 | { 8 | public abstract class TestsBase 9 | { 10 | [SetUp] 11 | public void SetUp() 12 | { 13 | Current.Reset(); 14 | 15 | // need a static IShortStringHelper ;( 16 | var shortStringHelper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig()); 17 | var factory = Mock.Of(); 18 | Mock.Get(factory).Setup(x => x.TryGetInstance(typeof(IShortStringHelper))).Returns(shortStringHelper); 19 | Current.Factory = factory; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /src/Our.ModelsBuilder/Building/CompilerException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Our.ModelsBuilder.Building 4 | { 5 | public class CompilerException : Exception 6 | { 7 | public CompilerException(string message) 8 | : base(message) 9 | { } 10 | 11 | public CompilerException(string message, string path, string sourceCode, int line) 12 | : base($"{message} (at {path}:line {line}).") 13 | { 14 | Path = path; 15 | SourceCode = sourceCode; 16 | Line = line; 17 | } 18 | 19 | public string Path { get; } = string.Empty; 20 | 21 | public string SourceCode { get; } = string.Empty; 22 | 23 | public int Line { get; } = -1; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Our.ModelsBuilder/Options/ContentTypes/FallbackStyle.cs: -------------------------------------------------------------------------------- 1 | namespace Our.ModelsBuilder.Options.ContentTypes 2 | { 3 | /// 4 | /// Defines the fallback generation styles. 5 | /// 6 | public enum FallbackStyle 7 | { 8 | /// 9 | /// Unknown. 10 | /// 11 | Unknown, // default value 12 | 13 | /// 14 | /// Does not generate fallback code. 15 | /// 16 | Nothing, 17 | 18 | /// 19 | /// Generates the classic fallback code. 20 | /// 21 | Classic, 22 | 23 | /// 24 | /// Generates the modern fallback code. 25 | /// 26 | Modern 27 | } 28 | } -------------------------------------------------------------------------------- /src/Our.ModelsBuilder/Building/ICodeParser.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Microsoft.CodeAnalysis; 3 | using Our.ModelsBuilder.Options; 4 | 5 | namespace Our.ModelsBuilder.Building 6 | { 7 | /// 8 | /// Parses code sources. 9 | /// 10 | public interface ICodeParser 11 | { 12 | /// 13 | /// Parses code sources. 14 | /// 15 | /// Sources. 16 | /// An options builder. 17 | /// Optional references. 18 | void Parse(IDictionary sources, CodeOptionsBuilder optionsBuilder, IEnumerable references = null); 19 | } 20 | } -------------------------------------------------------------------------------- /src/Our.ModelsBuilder/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | [assembly: AssemblyTitle("Our ModelsBuilder")] 6 | [assembly: AssemblyDescription("Our ModelsBuilder.")] 7 | [assembly: Guid("b59ebab2-bc7c-4a89-876f-7613684510e2")] 8 | 9 | [assembly: InternalsVisibleTo("Our.ModelsBuilder.Tests")] 10 | 11 | // dynamic assembly that is built during tests - do not remove! 12 | [assembly: InternalsVisibleTo("Our.ModelsBuilder.RunTests")] 13 | 14 | // code analysis 15 | // IDE1006 is broken, wants _value syntax for consts, etc - and it's even confusing ppl at MS, kill it 16 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "~_~")] 17 | -------------------------------------------------------------------------------- /src/Our.ModelsBuilder.Web/Plugin/lang/en-US.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | The Umbraco Community 11 | https://our.umbraco.com/ 12 | 13 | 14 | 15 | 16 | 17 | Models Builder 18 | 19 | -------------------------------------------------------------------------------- /src/Our.ModelsBuilder.Tests/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/Our.ModelsBuilder/ContentTypeModelInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace Our.ModelsBuilder 6 | { 7 | public class ContentTypeModelInfo 8 | { 9 | public ContentTypeModelInfo(string alias, string clrName, Type clrType, params PropertyTypeModelInfo[] properties) 10 | { 11 | Alias = alias; 12 | ClrName = clrName; 13 | ClrType = clrType; 14 | PropertyTypeInfos = properties; 15 | } 16 | 17 | public string Alias { get; } 18 | public string ClrName { get; } 19 | public Type ClrType { get; } 20 | 21 | public IReadOnlyCollection PropertyTypeInfos { get; } 22 | 23 | public PropertyTypeModelInfo PropertyTypeInfo(string alias) => PropertyTypeInfos.FirstOrDefault(x => x.Alias == alias); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Our.ModelsBuilder/Options/ContentTypes/PropertyStyle.cs: -------------------------------------------------------------------------------- 1 | namespace Our.ModelsBuilder.Options.ContentTypes 2 | { 3 | /// 4 | /// Defines the property generation styles. 5 | /// 6 | public enum PropertyStyle 7 | { 8 | /// 9 | /// Unknown. 10 | /// 11 | Unknown, // default value 12 | 13 | /// 14 | /// Generate class properties. 15 | /// 16 | Property, 17 | 18 | /// 19 | /// Generate class properties and extension methods. 20 | /// 21 | PropertyAndExtensionMethods, 22 | 23 | /// 24 | /// Generate extension methods. 25 | /// 26 | ExtensionMethods, 27 | 28 | /// 29 | /// Generate class methods. 30 | /// 31 | Methods 32 | } 33 | } -------------------------------------------------------------------------------- /src/Our.ModelsBuilder/Building/CodeModelDataSource.cs: -------------------------------------------------------------------------------- 1 | using Our.ModelsBuilder.Umbraco; 2 | 3 | namespace Our.ModelsBuilder.Building 4 | { 5 | /// 6 | /// Implements the default . 7 | /// 8 | public class CodeModelDataSource : ICodeModelDataSource 9 | { 10 | private readonly UmbracoServices _umbracoServices; 11 | 12 | /// 13 | /// Initializes a new instance of the class. 14 | /// 15 | public CodeModelDataSource(UmbracoServices umbracoServices) 16 | { 17 | _umbracoServices = umbracoServices; 18 | } 19 | 20 | public CodeModelData GetCodeModelData() 21 | { 22 | return new CodeModelData 23 | { 24 | ContentTypes = _umbracoServices.GetContentTypes() 25 | }; 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /src/Our.ModelsBuilder/TypeModelItemTypesExtensions.cs: -------------------------------------------------------------------------------- 1 | using Our.ModelsBuilder.Building; 2 | using Umbraco.Core.Models.PublishedContent; 3 | 4 | namespace Our.ModelsBuilder 5 | { 6 | public static class TypeModelItemTypesExtensions 7 | { 8 | public static PublishedItemType ToPublishedItemType(this ContentTypeKind contentTypeKind) 9 | { 10 | switch (contentTypeKind) 11 | { 12 | case ContentTypeKind.Content: 13 | return PublishedItemType.Content; 14 | case ContentTypeKind.Element: 15 | return PublishedItemType.Element; 16 | case ContentTypeKind.Media: 17 | return PublishedItemType.Media; 18 | case ContentTypeKind.Member: 19 | return PublishedItemType.Member; 20 | default: 21 | return PublishedItemType.Unknown; 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Our.ModelsBuilder/ModelsBuilderAssemblyAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Our.ModelsBuilder 4 | { 5 | /// 6 | /// Indicates that an Assembly is a Models Builder assembly. 7 | /// 8 | [AttributeUsage(AttributeTargets.Assembly /*, AllowMultiple = false, Inherited = false*/)] 9 | public sealed class ModelsBuilderAssemblyAttribute : Attribute 10 | { 11 | /// 12 | /// Gets or sets a value indicating whether the assembly is a PureLive assembly. 13 | /// 14 | /// A Models Builder assembly can be either PureLive or normal Dll. 15 | public bool PureLive { get; set; } 16 | 17 | /// 18 | /// Gets or sets a hash value representing the state of the custom source code files 19 | /// and the Umbraco content types that were used to generate and compile the assembly. 20 | /// 21 | public string SourceHash { get; set; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Our.ModelsBuilder/ImplementPropertyTypeAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Our.ModelsBuilder 4 | { 5 | /// 6 | /// Indicates that a property implements a given property alias. 7 | /// 8 | /// And therefore it should not be generated. 9 | [AttributeUsage(AttributeTargets.Property | AttributeTargets.Method, AllowMultiple = false, Inherited = false)] 10 | public sealed class ImplementPropertyTypeAttribute : Attribute 11 | { 12 | public ImplementPropertyTypeAttribute(string propertyTypeAlias) 13 | { 14 | PropertyTypeAlias = propertyTypeAlias; 15 | } 16 | 17 | public ImplementPropertyTypeAttribute(string contentTypeAlias, string propertyTypeAlias) 18 | : this(propertyTypeAlias) 19 | { 20 | ContentTypeAlias = contentTypeAlias; 21 | } 22 | 23 | public string ContentTypeAlias { get; } 24 | 25 | public string PropertyTypeAlias { get; } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Our.ModelsBuilder/Umbraco/LiveModelsProviderModule.cs: -------------------------------------------------------------------------------- 1 | using System.Web; 2 | using Our.ModelsBuilder.Umbraco; 3 | 4 | // will install only if configuration says it needs to be installed 5 | [assembly: PreApplicationStartMethod(typeof(LiveModelsProviderModule), "Install")] 6 | 7 | namespace Our.ModelsBuilder.Umbraco 8 | { 9 | // have to do this because it's the only way to subscribe to EndRequest, 10 | // module is installed by assembly attribute at the top of this file 11 | public class LiveModelsProviderModule : IHttpModule 12 | { 13 | public void Init(HttpApplication app) 14 | { 15 | app.EndRequest += LiveModelsProvider.GenerateModelsIfRequested; 16 | } 17 | 18 | public void Dispose() 19 | { 20 | // nothing 21 | } 22 | 23 | public static void Install() 24 | { 25 | // always - don't read config in PreApplicationStartMethod 26 | HttpApplication.RegisterModule(typeof(LiveModelsProviderModule)); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Our.ModelsBuilder/Umbraco/HashCombiner.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | 4 | namespace Our.ModelsBuilder.Umbraco 5 | { 6 | // because, of course, it's internal in Umbraco 7 | // see also System.Web.Util.HashCodeCombiner 8 | class HashCombiner 9 | { 10 | private long _combinedHash = 5381L; 11 | 12 | public void Add(int i) 13 | { 14 | _combinedHash = ((_combinedHash << 5) + _combinedHash) ^ i; 15 | } 16 | 17 | public void Add(object o) 18 | { 19 | Add(o.GetHashCode()); 20 | } 21 | 22 | public void Add(DateTime d) 23 | { 24 | Add(d.GetHashCode()); 25 | } 26 | 27 | public void Add(string s) 28 | { 29 | if (s == null) return; 30 | Add((StringComparer.InvariantCulture).GetHashCode(s)); 31 | } 32 | 33 | public string GetCombinedHashCode() 34 | { 35 | return _combinedHash.ToString("x", CultureInfo.InvariantCulture); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Our.ModelsBuilder/EnumerableExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Our.ModelsBuilder 5 | { 6 | public static class EnumerableExtensions 7 | { 8 | public static void RemoveAll(this IList list, Func predicate) 9 | { 10 | for (var i = 0; i < list.Count; i++) 11 | { 12 | if (predicate(list[i])) 13 | { 14 | list.RemoveAt(i--); // i-- is important here! 15 | } 16 | } 17 | } 18 | 19 | public static IEnumerable And(this IEnumerable enumerable, T item) 20 | { 21 | foreach (var x in enumerable) yield return x; 22 | yield return item; 23 | } 24 | 25 | public static IEnumerable AndIfNotNull(this IEnumerable enumerable, T item) 26 | where T : class 27 | { 28 | foreach (var x in enumerable) yield return x; 29 | if (item != null) 30 | yield return item; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Our.ModelsBuilder.Web/Plugin/modelsbuilder.controller.js: -------------------------------------------------------------------------------- 1 | function modelsBuilderController(modelsBuilderResource) { 2 | 3 | var vm = this; 4 | 5 | vm.reload = reload; 6 | vm.generate = generate; 7 | vm.dashboard = null; 8 | 9 | function generate() { 10 | vm.generating = true; 11 | modelsBuilderResource.buildModels().then(function (result) { 12 | vm.generating = false; 13 | vm.dashboard = result; 14 | }); 15 | } 16 | 17 | function reload() { 18 | vm.loading = true; 19 | modelsBuilderResource.getDashboard().then(function (result) { 20 | vm.dashboard = result; 21 | vm.loading = false; 22 | }); 23 | } 24 | 25 | function init() { 26 | vm.loading = true; 27 | modelsBuilderResource.getDashboard().then(function (result) { 28 | vm.dashboard = result; 29 | vm.loading = false; 30 | }); 31 | } 32 | 33 | init(); 34 | } 35 | angular.module("umbraco").controller("Our.ModelsBuilder.ModelsBuilderController", modelsBuilderController); -------------------------------------------------------------------------------- /src/Our.ModelsBuilder.Extension/license.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013-2016 Umbraco HQ 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /src/Our.ModelsBuilder/Building/TextHeaderWriter.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using Our.ModelsBuilder.Api; 3 | 4 | namespace Our.ModelsBuilder.Building 5 | { 6 | public static class TextHeaderWriter 7 | { 8 | /// 9 | /// Outputs an "auto-generated" header to a string builder. 10 | /// 11 | /// The string builder. 12 | public static void WriteHeader(StringBuilder sb) 13 | { 14 | sb.Append("//------------------------------------------------------------------------------\n"); 15 | sb.Append("// \n"); 16 | sb.Append("// This code was generated by a tool.\n"); 17 | sb.Append("//\n"); 18 | sb.AppendFormat("// Our.ModelsBuilder v{0}\n", ApiVersion.Current.Version); 19 | sb.Append("//\n"); 20 | sb.Append("// Changes to this file will be lost if the code is regenerated.\n"); 21 | sb.Append("// \n"); 22 | sb.Append("//------------------------------------------------------------------------------\n"); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Our.ModelsBuilder/TypeExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Our.ModelsBuilder 4 | { 5 | internal static class TypeExtensions 6 | { 7 | /// 8 | /// Creates a generic instance of a generic type with the proper actual type of an object. 9 | /// 10 | /// A generic type such as Something{} 11 | /// An object whose type is used as generic type param. 12 | /// Arguments for the constructor. 13 | /// A generic instance of the generic type with the proper type. 14 | /// Usage... typeof (Something{}).CreateGenericInstance(object1, object2, object3) will return 15 | /// a Something{Type1} if object1.GetType() is Type1. 16 | public static object CreateGenericInstance(this Type genericType, object typeParmObj, params object[] ctorArgs) 17 | { 18 | var type = genericType.MakeGenericType(typeParmObj.GetType()); 19 | return Activator.CreateInstance(type, ctorArgs); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013-2020 The Umbraco Community 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Our.ModelsBuilder/Building/ICodeFactory.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using Our.ModelsBuilder.Options; 3 | 4 | namespace Our.ModelsBuilder.Building 5 | { 6 | /// 7 | /// Creates the services required to generate models. 8 | /// 9 | public interface ICodeFactory 10 | { 11 | /// 12 | /// Creates a code model data source. 13 | /// 14 | /// 15 | ICodeModelDataSource CreateCodeModelDataSource(); 16 | 17 | /// 18 | /// Creates a code options builder. 19 | /// 20 | CodeOptionsBuilder CreateCodeOptionsBuilder(); 21 | 22 | /// 23 | /// Creates a code parser. 24 | /// 25 | ICodeParser CreateCodeParser(); 26 | 27 | /// 28 | /// Creates a code model builder. 29 | /// 30 | ICodeModelBuilder CreateCodeModelBuilder(ModelsBuilderOptions options, CodeOptions codeOptions); 31 | 32 | /// 33 | /// Creates a code writer. 34 | /// 35 | ICodeWriter CreateCodeWriter(CodeModel model, StringBuilder text = null); 36 | } 37 | } -------------------------------------------------------------------------------- /src/Our.ModelsBuilder/Options/OptionsCompositionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Our.ModelsBuilder.Options; 3 | using Umbraco.Core.Composing; 4 | 5 | // ReSharper disable once CheckNamespace, reason: extension method 6 | namespace Our.ModelsBuilder 7 | { 8 | /// 9 | /// Provides extension methods for the class. 10 | /// 11 | public static class OptionsCompositionExtensions 12 | { 13 | /// 14 | /// Configures ModelsBuilder options. 15 | /// 16 | public static Composition ConfigureOptions(this Composition composition, Action configure) 17 | { 18 | composition.Configs.GetConfig().AddConfigure(configure); 19 | return composition; 20 | } 21 | 22 | /// 23 | /// Configures ModelsBuilder code options. 24 | /// 25 | public static Composition ConfigureCodeOptions(this Composition composition, Action configure) 26 | { 27 | composition.Configs.GetConfig().AddConfigure(configure); 28 | return composition; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Our.ModelsBuilder/Building/ICodeWriter.cs: -------------------------------------------------------------------------------- 1 | namespace Our.ModelsBuilder.Building 2 | { 3 | /// 4 | /// Writes code. 5 | /// 6 | public interface ICodeWriter 7 | { 8 | /// 9 | /// Resets the code writer. 10 | /// 11 | void Reset(); 12 | 13 | /// 14 | /// Gets the written code. 15 | /// 16 | string Code { get; } 17 | 18 | /// 19 | /// Gets a code writer for writing content types. 20 | /// 21 | ContentTypesCodeWriter ContentTypesCodeWriter { get; } 22 | 23 | /// 24 | /// Gets a code writer for writing the infos class. 25 | /// 26 | InfosCodeWriter InfosCodeWriter { get; } 27 | 28 | /// 29 | /// Writes a single model file. 30 | /// 31 | void WriteModelFile(ContentTypeModel model); 32 | 33 | /// 34 | /// Writes the model infos file. 35 | /// 36 | void WriteModelInfosFile(); 37 | 38 | /// 39 | /// Writes everything in one single file. 40 | /// 41 | void WriteSingleFile(); 42 | } 43 | } -------------------------------------------------------------------------------- /src/Our.ModelsBuilder.Tests/UmbracoInternals.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Moq; 4 | using Umbraco.Core.Models; 5 | using Umbraco.Core.Models.PublishedContent; 6 | using Umbraco.Core.PropertyEditors; 7 | 8 | namespace Our.ModelsBuilder.Tests 9 | { 10 | class UmbracoInternals 11 | { 12 | public static PublishedPropertyType CreatePublishedPropertyType(string alias, int definition, string editor, ContentVariation variations = ContentVariation.Nothing) 13 | { 14 | var valueConverters = new PropertyValueConverterCollection(Enumerable.Empty()); 15 | var publishedModelFactory = Mock.Of(); 16 | var publishedContentTypeFactory = Mock.Of(); 17 | return new PublishedPropertyType(alias, definition, false, variations, valueConverters, publishedModelFactory, publishedContentTypeFactory); 18 | } 19 | 20 | public static PublishedContentType CreatePublishedContentType(int id, string alias, IEnumerable propertyTypes) 21 | { 22 | return new PublishedContentType(id, alias, PublishedItemType.Content, Enumerable.Empty(), propertyTypes, ContentVariation.Nothing); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Our.ModelsBuilder.Tests/ConfigTests.cs: -------------------------------------------------------------------------------- 1 | using System.Configuration; 2 | using NUnit.Framework; 3 | using Our.ModelsBuilder.Options; 4 | 5 | namespace Our.ModelsBuilder.Tests 6 | { 7 | [TestFixture] 8 | public class ConfigTests 9 | { 10 | [TestCase("c:/path/to/root", "~/dir/models", false, "c:\\path\\to\\root\\dir\\models")] 11 | [TestCase("c:/path/to/root", "~/../../dir/models", true, "c:\\path\\dir\\models")] 12 | [TestCase("c:/path/to/root", "c:/another/path/to/elsewhere", true, "c:\\another\\path\\to\\elsewhere")] 13 | public void GetModelsDirectoryTests(string root, string config, bool acceptUnsafe, string expected) 14 | { 15 | Assert.AreEqual(expected, OptionsWebConfigReader.GetModelsDirectory(root, config, acceptUnsafe)); 16 | } 17 | 18 | [TestCase("c:/path/to/root", "~/../../dir/models", false)] 19 | [TestCase("c:/path/to/root", "c:/another/path/to/elsewhere", false)] 20 | public void GetModelsDirectoryThrowsTests(string root, string config, bool acceptUnsafe) 21 | { 22 | Assert.Throws(() => 23 | { 24 | var modelsDirectory = OptionsWebConfigReader.GetModelsDirectory(root, config, acceptUnsafe); 25 | }); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Our.ModelsBuilder.Tests/Compile/CSharpCodeProviderTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using Our.ModelsBuilder.Building; 3 | using Our.ModelsBuilder.Tests.Testing; 4 | 5 | namespace Our.ModelsBuilder.Tests.Compile 6 | { 7 | [TestFixture] 8 | public class CSharpCodeProviderTests 9 | { 10 | [TestCase(true, "Foo")] 11 | [TestCase(true, "Foo_Bar")] 12 | [TestCase(true, "_1Foo")] 13 | [TestCase(false, "Foo Bar")] 14 | [TestCase(false, "1Foo")] 15 | public void IsValidIdentifierTests(bool expected, string value) 16 | { 17 | if (expected) 18 | AssertCode.IsValidTypeNameOrIdentifier(value, true); 19 | else 20 | AssertCode.IsInvalidTypeNameOrIdentifier(value, true); 21 | } 22 | 23 | [TestCase("Foo", "Foo")] 24 | [TestCase("foo", "foo")] 25 | [TestCase("FooBar", "FooBar")] 26 | [TestCase("Foo_Bar", "Foo Bar")] 27 | [TestCase("_1Foo", "1Foo")] 28 | [TestCase("Foo___", "Foo[!!")] 29 | public void ValidIdentifierTests(string expected, string value) 30 | { 31 | var valid = Compiler.CreateValidIdentifier(value); 32 | Assert.AreEqual(expected, valid); 33 | AssertCode.IsValidTypeNameOrIdentifier(valid, true); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/Our.ModelsBuilder/Umbraco/ModelsBuilderInitializer.cs: -------------------------------------------------------------------------------- 1 | using System.Web; 2 | using System.Web.Compilation; 3 | using Our.ModelsBuilder.Umbraco; 4 | 5 | [assembly: PreApplicationStartMethod(typeof(ModelsBuilderInitializer), "Initialize")] 6 | 7 | namespace Our.ModelsBuilder.Umbraco 8 | { 9 | public static class ModelsBuilderInitializer 10 | { 11 | // ReSharper disable once UnusedMember.Global, invoked by PreApplicationStartMethod 12 | public static void Initialize() 13 | { 14 | // for some reason, netstandard is missing from BuildManager.ReferencedAssemblies and yet, is part of 15 | // the references that CSharpCompiler receives - in some cases eg when building views - but not when 16 | // using BuildManager to build the PureLive models - where is it coming from? cannot figure it out 17 | 18 | // so... cheating here 19 | 20 | // this is equivalent to adding 21 | // 22 | // to web.config system.web/compilation/assemblies 23 | 24 | var netStandard = ReferencedAssemblies.GetNetStandardAssembly(); 25 | if (netStandard != null) 26 | BuildManager.AddReferencedAssembly(netStandard); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Our.ModelsBuilder.Extension/GeneratorWindow.xaml: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 10 | 11 | 12 |

Models Builder

13 | 14 |
15 |
16 | 17 |
18 |

Models are out-of-date.

19 |
20 | 21 |
22 |
23 |

Generating models will restart the application.

24 |
25 |
26 | 29 |
30 |
31 | 32 |
33 | 34 |
35 | 36 |
37 | Last generation failed with the following error: 38 |
{{vm.dashboard.lastError}}
39 |
40 |
41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/Our.ModelsBuilder/Options/ModelsMode.cs: -------------------------------------------------------------------------------- 1 | namespace Our.ModelsBuilder.Options 2 | { 3 | /// 4 | /// Defines the models generation modes. 5 | /// 6 | public enum ModelsMode 7 | { 8 | /// 9 | /// Do not generate models. 10 | /// 11 | Nothing = 0, // default value 12 | 13 | /// 14 | /// Generate models in memory. 15 | /// When: a content type change occurs. 16 | /// 17 | /// The app does not restart. Models are available in views exclusively. 18 | PureLive, 19 | 20 | /// 21 | /// Generate models in AppData. 22 | /// When: generation is triggered. 23 | /// 24 | /// Generation can be triggered from the dashboard. The app does not restart. 25 | /// Models are not compiled and thus are not available to the project. 26 | AppData, 27 | 28 | /// 29 | /// Generate models in AppData. 30 | /// When: a content type change occurs, or generation is triggered. 31 | /// 32 | /// Generation can be triggered from the dashboard. The app does not restart. 33 | /// Models are not compiled and thus are not available to the project. 34 | LiveAppData, 35 | 36 | /// 37 | /// Generates models in AppData and compiles them into a Dll into ~/bin (the app restarts). 38 | /// When: generation is triggered. 39 | /// 40 | /// Generation can be triggered from the dashboard. The app does restart. Models 41 | /// are available to the entire project. 42 | Dll, 43 | 44 | /// 45 | /// Generates models in AppData and compiles them into a Dll into ~/bin (the app restarts). 46 | /// When: a content type change occurs, or generation is triggered. 47 | /// 48 | /// Generation can be triggered from the dashboard. The app does restart. Models 49 | /// are available to the entire project. 50 | LiveDll 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Our.ModelsBuilder.Web/Api/ValidateClientVersionData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | using Semver; 4 | 5 | namespace Our.ModelsBuilder.Web.Api 6 | { 7 | [DataContract] 8 | public class ValidateClientVersionData 9 | { 10 | // issues 32, 34... problems when serializing versions 11 | // 12 | // make sure System.Version objects are transfered as strings 13 | // depending on the JSON serializer version, it looks like versions are causing issues 14 | // see 15 | // http://stackoverflow.com/questions/13170386/why-system-version-in-json-string-does-not-deserialize-correctly 16 | // 17 | // if the class is marked with [DataContract] then only properties marked with [DataMember] 18 | // are serialized and the rest is ignored, see 19 | // http://www.asp.net/web-api/overview/formats-and-model-binding/json-and-xml-serialization 20 | 21 | [DataMember] 22 | public string ClientVersionString 23 | { 24 | get => VersionToString(ClientVersion); 25 | set => ClientVersion = ParseVersion(value, false, "client"); 26 | } 27 | 28 | [DataMember] 29 | public string MinServerVersionSupportingClientString 30 | { 31 | get => VersionToString(MinServerVersionSupportingClient); 32 | set => MinServerVersionSupportingClient = ParseVersion(value, true, "minServer"); 33 | } 34 | 35 | // not serialized 36 | public SemVersion ClientVersion { get; set; } 37 | public SemVersion MinServerVersionSupportingClient { get; set; } 38 | 39 | private static string VersionToString(SemVersion version) 40 | { 41 | return version?.ToString() ?? "0.0.0.0"; 42 | } 43 | 44 | private static SemVersion ParseVersion(string value, bool canBeNull, string name) 45 | { 46 | if (string.IsNullOrWhiteSpace(value) && canBeNull) 47 | return null; 48 | 49 | if (SemVersion.TryParse(value, out var version)) 50 | return version; 51 | 52 | throw new ArgumentException($"Failed to parse \"{value}\" as {name} version."); 53 | } 54 | 55 | public virtual bool IsValid => ClientVersion != null; 56 | } 57 | } -------------------------------------------------------------------------------- /src/Our.ModelsBuilder/Umbraco/ModelsBuilderComposer.cs: -------------------------------------------------------------------------------- 1 | using Our.ModelsBuilder.Building; 2 | using Our.ModelsBuilder.Options; 3 | using Umbraco.Core; 4 | using Umbraco.Core.Composing; 5 | using Umbraco.Core.Models.PublishedContent; 6 | using Umbraco.Web.PublishedCache.NuCache; 7 | 8 | namespace Our.ModelsBuilder.Umbraco 9 | { 10 | [ComposeBefore(typeof(NuCacheComposer))] 11 | [RuntimeLevel(MinLevel = RuntimeLevel.Run)] 12 | public sealed class ModelsBuilderComposer : ComponentComposer, ICoreComposer 13 | { 14 | public override void Compose(Composition composition) 15 | { 16 | base.Compose(composition); 17 | 18 | // compose umbraco & the code factory 19 | composition.Register(Lifetime.Singleton); 20 | composition.RegisterUnique(); 21 | 22 | // compose configuration of options 23 | composition.Configs.Add(() => new OptionsConfiguration()); 24 | composition.ConfigureOptions(OptionsWebConfigReader.ConfigureOptions); 25 | composition.Register(factory => factory.GetInstance().ModelsBuilderOptions, Lifetime.Singleton); 26 | 27 | // always discover model types in code 28 | // could be used with pure live, to provide some models, 29 | // and then pure live would not generate them 30 | composition.WithCollectionBuilder() 31 | .Add(composition.TypeLoader.GetTypes()) 32 | .Add(composition.TypeLoader.GetTypes()); 33 | 34 | // create the appropriate factory, depending on options 35 | composition.RegisterUnique(factory => 36 | { 37 | var options = factory.GetInstance(); 38 | 39 | if (options.ModelsMode == ModelsMode.PureLive) 40 | return factory.CreateInstance(); 41 | 42 | if (options.EnableFactory) 43 | return new PublishedModelFactory(factory.GetInstance()); 44 | 45 | return new NoopPublishedModelFactory(); 46 | }); 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /src/Our.ModelsBuilder/Options/CodeOptionsBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Our.ModelsBuilder.Options.ContentTypes; 3 | 4 | namespace Our.ModelsBuilder.Options 5 | { 6 | /// 7 | /// Builds the . 8 | /// 9 | public class CodeOptionsBuilder 10 | { 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// 14 | public CodeOptionsBuilder() 15 | { 16 | var contentTypesCodeOptions = new ContentTypesCodeOptions(); 17 | CodeOptions = new CodeOptions(contentTypesCodeOptions); 18 | ContentTypes = new ContentTypesCodeOptionsBuilder(contentTypesCodeOptions); 19 | } 20 | 21 | /// 22 | /// Initializes a new instance of the class. 23 | /// 24 | protected CodeOptionsBuilder(CodeOptions codeOptions, ContentTypesCodeOptionsBuilder contentTypesCodeOptionsBuilder) 25 | { 26 | CodeOptions = codeOptions ?? throw new ArgumentNullException(nameof(codeOptions)); 27 | ContentTypes = contentTypesCodeOptionsBuilder ?? throw new ArgumentNullException(nameof(contentTypesCodeOptionsBuilder)); 28 | } 29 | 30 | /// 31 | /// Gets the options builder for content types. 32 | /// 33 | public virtual ContentTypesCodeOptionsBuilder ContentTypes { get; } 34 | 35 | /// 36 | /// Gets the options. 37 | /// 38 | public virtual CodeOptions CodeOptions { get; } 39 | 40 | /// 41 | /// Sets the models namespace. 42 | /// 43 | /// The models namespace. 44 | public virtual void SetModelsNamespace(string modelsNamespace) 45 | { 46 | CodeOptions.ModelsNamespace = modelsNamespace; 47 | } 48 | /// 49 | /// Sets the assembly name. 50 | /// 51 | /// The models namespace. 52 | public virtual void SetAssemblyName(string assemblyName) 53 | { 54 | CodeOptions.CustomAssemblyName = assemblyName; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | #### Umbraco Models Builder 2 | 3 | Copyright (C) The Umbraco Community 2013-2020 4 | Distributed under the MIT license 5 | 6 | A tool that can generate a complete set of strongly-typed published content models for Umbraco. 7 | Models are available in controllers, views, anywhere. 8 | Runs either from the Umbraco UI, from the command line, or from Visual Studio. 9 | 10 | Requires Umbraco 7.1.4 or later. 11 | 12 | #### Documentation 13 | 14 | More infos, including a (hopefully) **complete documentation**, can be found in the [wiki](https://github.com/OurModelsBuilder/Our.ModelsBuilder/wiki). 15 | 16 | **WARNING** the documentation has not been updated for version 3 (Umbraco.ModelsBuilder) yet. 17 | 18 | #### Building 19 | 20 | Simply building the solution (in Visual Studio) either in Debug or Release does NOT build 21 | any NuGet package. Building in Debug mode does NOT build the VSIX package, but building in 22 | Release mode DOES build the VSIX package. 23 | 24 | **Important** before releasing a new version, ensure that Umbraco.ModelsBuilder.Api.ApiVersion 25 | contains the proper constants for API client/server version check. 26 | 27 | In order to build the NuGet package and the VSIX package, 28 | use the build.ps1 Powershell script: 29 | 30 | To build version 1.2.3.45 (aka release 1.2.3) 31 | build.ps1 1.2.3 45 32 | 33 | To build version 1.2.3.45 beta001 (aka pre-release 1.2.3-beta001) 34 | build.ps1 1.2.3 45 beta001 35 | 36 | The "45" number should be incremented each time we release, so that 37 | version 1.2.3-beta001 has assemblies with version 1.2.3.45 38 | version 1.2.3-beta002 has assemblies with version 1.2.3.46 39 | version 1.2.3 (final) has assemblies with version 1.2.3.47 40 | 41 | This will create directory build/Release/v1.2.3-whatever containing: 42 | - Umbraco.ModelsBuilder.1.2.3-whatever.nuget = main NuGet package 43 | - Umbraco.ModelsBuilder.Api.1.2.3-whatever.nuget = api server NuGet package 44 | - Umbraco.ModelsBuilder.CustomTool-1.2.3-whatever.vsix = Visual Studio Extension 45 | 46 | Note: we are not building an Umbraco package anymore. 47 | 48 | #### Projects 49 | 50 | *Umbraco.ModelsBuilder - the main project, installed on the website 51 | *Umbraco.ModelsBuilder.Api - the api server 52 | *Umbraco.ModelsBuilder.Console - a console tool 53 | *Umbraco.ModelsBuilder.CustomTool - the Visual Studio extension 54 | *Umbraco.ModelsBuilder.Tests - the tests suite 55 | 56 | Both .Console and .CustomTool require that the .Api is installed on the website (not installed by default). 57 | -------------------------------------------------------------------------------- /src/Our.ModelsBuilder.Tests/Write/WriteEdgeCasesTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NUnit.Framework; 3 | using Our.ModelsBuilder.Building; 4 | using Our.ModelsBuilder.Options; 5 | using Our.ModelsBuilder.Tests.Testing; 6 | using Umbraco.Core.Models.PublishedContent; 7 | 8 | namespace Our.ModelsBuilder.Tests.Write 9 | { 10 | [TestFixture] 11 | public class WriteEdgeCasesTests : TestsBase 12 | { 13 | [Test] 14 | public void WriteAmbiguousTypes() 15 | { 16 | var modelSource = new CodeModelData(); 17 | 18 | var type1 = new ContentTypeModel 19 | { 20 | Id = 1, 21 | Alias = "type1", 22 | ParentId = 0, 23 | BaseContentType = null, 24 | Kind = ContentTypeKind.Content, 25 | IsMixin = true, 26 | }; 27 | modelSource.ContentTypes.Add(type1); 28 | type1.Properties.Add(new PropertyTypeModel 29 | { 30 | Alias = "prop1", 31 | ContentType = type1, 32 | ValueType = typeof(IPublishedContent), 33 | }); 34 | type1.Properties.Add(new PropertyTypeModel 35 | { 36 | Alias = "prop2", 37 | ContentType = type1, 38 | ValueType = typeof(global::System.Text.StringBuilder), 39 | }); 40 | type1.Properties.Add(new PropertyTypeModel 41 | { 42 | Alias = "prop3", 43 | ContentType = type1, 44 | ValueType = typeof(global::Umbraco.Core.IO.FileSecurityException), 45 | }); 46 | 47 | var codeOptionsBuilder = new CodeOptionsBuilder(); 48 | 49 | // forces conflict with Our.ModelsBuilder.Umbraco 50 | codeOptionsBuilder.SetModelsNamespace("Our.ModelsBuilder.Models"); 51 | 52 | var modelBuilder = new CodeModelBuilder(new ModelsBuilderOptions(), codeOptionsBuilder.CodeOptions); 53 | var model = modelBuilder.Build(modelSource); 54 | 55 | var writer = new CodeWriter(model).ContentTypesCodeWriter; 56 | 57 | foreach (var typeModel in model.ContentTypes.ContentTypes) 58 | writer.WriteModel(typeModel); 59 | var generated = writer.Code; 60 | 61 | Console.WriteLine(generated); 62 | 63 | Assert.IsTrue(generated.Contains(" IPublishedContent Prop1")); 64 | Assert.IsTrue(generated.Contains(" System.Text.StringBuilder Prop2")); 65 | Assert.IsTrue(generated.Contains(" global::Umbraco.Core.IO.FileSecurityException Prop3")); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Our.ModelsBuilder.Tests/Compile/CompilerTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using Microsoft.CodeAnalysis.CSharp; 5 | using NUnit.Framework; 6 | using Our.ModelsBuilder.Building; 7 | 8 | namespace Our.ModelsBuilder.Tests.Compile 9 | { 10 | [TestFixture] 11 | public class CompilerTests 12 | { 13 | private string _tempDir; 14 | 15 | [SetUp] 16 | public void Setup() 17 | { 18 | _tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); 19 | Directory.CreateDirectory(_tempDir); 20 | } 21 | 22 | [TearDown] 23 | public void TearDown() 24 | { 25 | if (_tempDir != null && Directory.Exists(_tempDir)) 26 | Directory.Delete(_tempDir, true); 27 | } 28 | 29 | [Test] 30 | public void Compile() 31 | { 32 | const string code1 = @" 33 | using System; 34 | namespace Whatever 35 | { 36 | public class Something 37 | { 38 | public void DoSomething() 39 | { 40 | Console.WriteLine(""Hello!""); 41 | } 42 | } 43 | } 44 | "; 45 | 46 | var filepath = Path.Combine(_tempDir, "Whatever.dll"); 47 | if (File.Exists(filepath)) 48 | File.Delete(filepath); 49 | 50 | var compiler = new Compiler(); 51 | compiler.Compile("Whatever", new Dictionary{{"code", code1}}, _tempDir); 52 | Assert.IsTrue(File.Exists(filepath)); 53 | 54 | File.Delete(filepath); 55 | } 56 | 57 | [Test] 58 | public void CompileCSharp6() 59 | { 60 | // see https://roslyn.codeplex.com/wikipage?title=Language%20Feature%20Status&referringTitle=Documentation 61 | 62 | const string code1 = @" 63 | using System; 64 | namespace Whatever 65 | { 66 | public class Something 67 | { 68 | // auto-property initializer 69 | public int Value { get; set; } = 3; 70 | 71 | public void DoSomething() 72 | { 73 | Console.WriteLine(""Hello!""); 74 | } 75 | 76 | public void DoSomething(string[] args) 77 | { 78 | // conditional access 79 | var len = args?.Length ?? 0; 80 | } 81 | } 82 | } 83 | "; 84 | 85 | var filepath = Path.Combine(_tempDir, "Whatever.dll"); 86 | if (File.Exists(filepath)) 87 | File.Delete(filepath); 88 | 89 | var compiler = new Compiler(LanguageVersion.CSharp6); 90 | compiler.Compile("Whatever", new Dictionary { { "code", code1 } }, _tempDir); 91 | Assert.IsTrue(File.Exists(filepath)); 92 | 93 | File.Delete(filepath); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/Our.ModelsBuilder.Console/Our.ModelsBuilder.Console.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | v4.7.2 6 | {F0B757DF-0CF7-4AAF-8DD0-F46EDF25C3A3} 7 | Exe 8 | Our.ModelsBuilder.Console 9 | Our.ModelsBuilder.Console 10 | ..\ 11 | 12 | 13 | 14 | AnyCPU 15 | true 16 | full 17 | false 18 | bin\Debug\ 19 | DEBUG;TRACE 20 | prompt 21 | 4 22 | 23 | 24 | AnyCPU 25 | pdbonly 26 | true 27 | bin\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | Properties\SolutionInfo.cs 39 | 40 | 41 | 42 | 43 | 44 | 45 | Designer 46 | 47 | 48 | 49 | 50 | {2317be7f-1723-4512-b863-5b6835e583a2} 51 | Our.ModelsBuilder.Web 52 | 53 | 54 | {998fb014-303a-4146-b3e4-b927bab0210f} 55 | Our.ModelsBuilder 56 | 57 | 58 | 59 | 60 | 2.0.5 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /src/Our.ModelsBuilder.Tests/ApiTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using NUnit.Framework; 5 | using Our.ModelsBuilder.Web.Api; 6 | 7 | namespace Our.ModelsBuilder.Tests 8 | { 9 | [TestFixture] 10 | public class ApiTests 11 | { 12 | //[Test] 13 | //[Ignore("That API has been disabled.")] 14 | //public void GetTypeModels() 15 | //{ 16 | // // note - works only if the website does not reference types that are not 17 | // // referenced by the current test project! 18 | // var api = new ModelsBuilderApi("http://umbraco.local", "user", "password"); 19 | // var res = api.GetTypeModels(); 20 | //} 21 | 22 | [Test] 23 | [Ignore("Requires a proper endpoint.")] 24 | public void GetModels() 25 | { 26 | const string text1 = @" 27 | using Our.ModelsBuilder; 28 | 29 | namespace Umbraco.Demo3.Core.Models 30 | { 31 | //[RenamePropertyType(""issued"", ""DateIssued"")] 32 | public partial class NewsItem 33 | { 34 | } 35 | } 36 | "; 37 | const string text2 = @" 38 | using Our.ModelsBuilder; 39 | 40 | //[assembly:IgnoreContentType(""product"")] 41 | "; 42 | 43 | var api = new ApiClient("http://umbraco.local", "user", "password"); 44 | var ourFiles = new Dictionary 45 | { 46 | {"file1", text1}, 47 | {"file2", text2}, 48 | }; 49 | var res = api.GetModels(ourFiles, "Our.ModelsBuilder.Tests.Models"); 50 | 51 | foreach (var kvp in res) 52 | { 53 | Console.WriteLine("****"); 54 | Console.WriteLine(kvp.Key); 55 | Console.WriteLine("----"); 56 | Console.WriteLine(kvp.Value); 57 | } 58 | } 59 | 60 | [TestCase("a", "b")] 61 | [TestCase("a:b", "c:d")] 62 | [TestCase("%xx%a%b:c:d:e", "x:y%z%b")] 63 | public void TokenTests(string username, string password) 64 | { 65 | var separator = ":".ToCharArray(); 66 | 67 | // ApiClient code 68 | var token = Convert.ToBase64String(Encoding.UTF8.GetBytes(ApiClient.EncodeTokenElement(username) + ':' + ApiClient.EncodeTokenElement(password))); 69 | 70 | // ApiBasicAuthFilter code 71 | var credentials = Encoding.ASCII 72 | .GetString(Convert.FromBase64String(token)) 73 | .Split(separator); 74 | if (credentials.Length != 2) 75 | throw new Exception(); 76 | 77 | var username2 = ApiClient.DecodeTokenElement(credentials[0]); 78 | var password2 = ApiClient.DecodeTokenElement(credentials[1]); 79 | 80 | Assert.AreEqual(username, username2); 81 | Assert.AreEqual(password, password2); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/Our.ModelsBuilder.Web/WebComponent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Web; 4 | using System.Web.Mvc; 5 | using System.Web.Routing; 6 | using Our.ModelsBuilder.Options; 7 | using Our.ModelsBuilder.Web.Api; 8 | using Our.ModelsBuilder.Web.Umbraco; 9 | using Umbraco.Core.Composing; 10 | using Umbraco.Core.Configuration; 11 | using Umbraco.Web; 12 | using Umbraco.Web.JavaScript; 13 | 14 | namespace Our.ModelsBuilder.Web 15 | { 16 | public class WebComponent : IComponent 17 | { 18 | private readonly IGlobalSettings _globalSettings; 19 | private readonly ModelsBuilderOptions _options; 20 | 21 | public WebComponent(IGlobalSettings globalSettings, ModelsBuilderOptions options) 22 | { 23 | _globalSettings = globalSettings; 24 | _options = options; 25 | } 26 | 27 | public void Initialize() 28 | { 29 | InstallServerVars(); 30 | 31 | if (_options.IsApiServer) 32 | { 33 | ModelsBuilderApiController.Route(_globalSettings.GetUmbracoMvcArea()); 34 | } 35 | } 36 | 37 | public void Terminate() 38 | { } 39 | 40 | private void InstallServerVars() 41 | { 42 | // register our url - for the back-office api 43 | ServerVariablesParser.Parsing += (sender, serverVars) => 44 | { 45 | if (!serverVars.ContainsKey("umbracoUrls")) 46 | throw new Exception("Missing umbracoUrls."); 47 | var umbracoUrlsObject = serverVars["umbracoUrls"]; 48 | if (umbracoUrlsObject == null) 49 | throw new Exception("Null umbracoUrls"); 50 | if (!(umbracoUrlsObject is Dictionary umbracoUrls)) 51 | throw new Exception("Invalid umbracoUrls"); 52 | 53 | if (!serverVars.ContainsKey("umbracoPlugins")) 54 | throw new Exception("Missing umbracoPlugins."); 55 | if (!(serverVars["umbracoPlugins"] is Dictionary umbracoPlugins)) 56 | throw new Exception("Invalid umbracoPlugins"); 57 | 58 | if (HttpContext.Current == null) throw new InvalidOperationException("HttpContext is null"); 59 | var urlHelper = new UrlHelper(new RequestContext(new HttpContextWrapper(HttpContext.Current), new RouteData())); 60 | 61 | umbracoUrls["modelsBuilderBaseUrl"] = urlHelper.GetUmbracoApiServiceBaseUrl(controller => controller.BuildModels()); 62 | umbracoPlugins["modelsBuilder"] = new Dictionary 63 | { 64 | {"enabled", _options.Enable} 65 | }; 66 | 67 | // see modelsbuilder.resource.js 68 | // see Core's contenttypehelper.service.js service 69 | // also register the plugin as 'modelsBuilder' so the Core UI can see it, 70 | // and enhance 'Save' buttons with 'Save and Generate Models' 71 | umbracoPlugins["modelsBuilder"] = umbracoPlugins["modelsBuilder"]; // FIXME uh??? 72 | }; 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Our.ModelsBuilder.Web/Our.ModelsBuilder.Web.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | v4.7.2 6 | {2317BE7F-1723-4512-B863-5B6835E583A2} 7 | Library 8 | Our.ModelsBuilder.Web 9 | Our.ModelsBuilder.Web 10 | 11 | 12 | 13 | true 14 | full 15 | false 16 | bin\Debug\ 17 | DEBUG;TRACE 18 | prompt 19 | 4 20 | 21 | 22 | pdbonly 23 | true 24 | bin\Release\ 25 | TRACE 26 | prompt 27 | 4 28 | 29 | 30 | 31 | Properties\SolutionInfo.cs 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | {998fb014-303a-4146-b3e4-b927bab0210f} 50 | Our.ModelsBuilder 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 8.3.0-alpha.1 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /src/Our.ModelsBuilder/Building/PropertyTypeModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Umbraco.Core.Models; 4 | using Umbraco.Core.Models.PublishedContent; 5 | 6 | namespace Our.ModelsBuilder.Building 7 | { 8 | /// 9 | /// Represents a model property. 10 | /// 11 | public class PropertyTypeModel 12 | { 13 | #region Things that come from Umbraco 14 | 15 | /// 16 | /// Gets or sets the content type owning the property. 17 | /// 18 | public ContentTypeModel ContentType { get; set; } 19 | 20 | /// 21 | /// Gets or sets the alias of the property. 22 | /// 23 | public string Alias { get; set; } 24 | 25 | /// 26 | /// Gets or sets the name of the property. 27 | /// 28 | public string Name { get; set; } 29 | 30 | /// 31 | /// Gets or sets the description of the property. 32 | /// 33 | public string Description { get; set; } 34 | 35 | /// 36 | /// Gets or sets the content variation of the property. 37 | /// 38 | public ContentVariation Variations { get; set; } = ContentVariation.Nothing; 39 | 40 | /// 41 | /// Gets or sets the model Clr type of the property values (may be a true type, or a ). 42 | /// 43 | /// 44 | /// As indicated by the IPublishedPropertyType, ie by the IPropertyValueConverter 45 | /// if any, else object. May include some that will need to be mapped. 46 | /// The property contains the mapped name of the Clr type of values. 47 | /// 48 | public Type ValueType { get; set; } 49 | 50 | #endregion 51 | 52 | #region Things managed by ModelsBuilder 53 | 54 | /// 55 | /// Gets or sets a value indicating whether this property should be excluded from generation. 56 | /// 57 | public bool IsIgnored { get; set; } 58 | 59 | /// 60 | /// Gets or sets the Clr name of the property. 61 | /// 62 | /// This is the local name eg "Price". 63 | public string ClrName { get; set; } 64 | 65 | /// 66 | /// Gets the Clr type name of the property values. 67 | /// 68 | /// This is the full name eg "System.DateTime". 69 | public string ValueTypeClrFullName; 70 | 71 | /// 72 | /// Gets or sets the list of generation errors for the property. 73 | /// 74 | /// This should be null, unless something prevents the property from being 75 | /// generated, and then the value should explain what. This can be used to generate 76 | /// commented out code eg in PureLive. 77 | public List Errors { get; set; } 78 | 79 | /// 80 | /// Adds an error. 81 | /// 82 | public void AddError(string error) 83 | { 84 | if (Errors == null) Errors = new List(); 85 | Errors.Add(error); 86 | } 87 | 88 | #endregion 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/Our.ModelsBuilder/UmbracoExtensions/FallbackValue.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Umbraco.Web; 3 | 4 | // ReSharper disable once CheckNamespace, reason: extensions 5 | namespace Umbraco.Core.Models.PublishedContent 6 | { 7 | public class FallbackValue 8 | where TModel : IPublishedElement 9 | { 10 | private readonly FallbackInfos _fallbackInfos; 11 | private readonly List _fallbacks = new List(); 12 | private TValue _defaultValue; 13 | 14 | public FallbackValue(FallbackInfos fallbackInfos) 15 | { 16 | _fallbackInfos = fallbackInfos; 17 | } 18 | 19 | public static implicit operator TValue(FallbackValue fallbackValue) 20 | { 21 | var fallbackInfos = fallbackValue._fallbackInfos; 22 | var property = fallbackInfos.Model.GetProperty(fallbackInfos.PropertyAlias); 23 | 24 | // no need to test for a value on property, if we are running this fallback 25 | // code, we know that it is because there isn't a value - can directly try 26 | // the fallback provider - which can be invoked in two different ways, 27 | // depending on whether we are working on an IPublishedContent or an 28 | // IPublishedElement (which is the reason why there are two Value<>() overloads, 29 | // one for IPublishedContent and one for IPublishedElement) 30 | 31 | var fallback = fallbackInfos.PublishedValueFallback; 32 | var success = fallbackInfos.Model is IPublishedContent publishedContent 33 | ? fallback.TryGetValue(publishedContent, fallbackInfos.PropertyAlias, fallbackInfos.Culture, fallbackInfos.Segment, Fallback.To(fallbackValue._fallbacks.ToArray()), fallbackValue._defaultValue, out var value, out property) 34 | : fallback.TryGetValue(fallbackInfos.Model, fallbackInfos.PropertyAlias, fallbackInfos.Culture, fallbackInfos.Segment, Fallback.To(fallbackValue._fallbacks.ToArray()), fallbackValue._defaultValue, out value); 35 | 36 | if (success) 37 | return value; 38 | 39 | // we *have* to return something 40 | // see PublishedElementExtensions.Value method, there is no "try" here 41 | // so, repeat what that method would do if no fallback method was provided 42 | 43 | // if we have a property, at least let the converter return its own 44 | // vision of 'no value' (could be an empty enumerable) - otherwise, default 45 | return property == null ? default : property.Value(fallbackInfos.Culture, fallbackInfos.Segment); 46 | } 47 | 48 | public FallbackValue To(params int[] values) 49 | { 50 | _fallbacks.AddRange(values); 51 | return this; 52 | } 53 | 54 | public FallbackValue Ancestors() 55 | { 56 | _fallbacks.Add(Fallback.Ancestors); 57 | return this; 58 | } 59 | 60 | public FallbackValue Languages() 61 | { 62 | _fallbacks.Add(Fallback.Language); 63 | return this; 64 | } 65 | 66 | public FallbackValue Default(TValue defaultValue) 67 | { 68 | _defaultValue = defaultValue; 69 | _fallbacks.Add(Fallback.DefaultValue); 70 | return this; 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /src/Our.ModelsBuilder/Validation/ContentTypeModelValidatorBase.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.ComponentModel.DataAnnotations; 3 | using System.Linq; 4 | using Our.ModelsBuilder.Options; 5 | using Umbraco.Core; 6 | using Umbraco.Core.Models.PublishedContent; 7 | using Umbraco.Web.Editors; 8 | using Umbraco.Web.Models.ContentEditing; 9 | 10 | namespace Our.ModelsBuilder.Validation 11 | { 12 | public abstract class ContentTypeModelValidatorBase : EditorValidator 13 | where TModel: ContentTypeSave 14 | where TProperty: PropertyTypeBasic 15 | { 16 | private readonly ModelsBuilderOptions _options; 17 | 18 | protected ContentTypeModelValidatorBase(ModelsBuilderOptions options) 19 | { 20 | _options = options; 21 | } 22 | 23 | protected override IEnumerable Validate(TModel model) 24 | { 25 | //don't do anything if we're not enabled 26 | if (_options.Enable) 27 | { 28 | var properties = model.Groups.SelectMany(x => x.Properties) 29 | .Where(x => x.Inherited == false) 30 | .ToArray(); 31 | 32 | foreach (var prop in properties) 33 | { 34 | var propertyGroup = model.Groups.Single(x => x.Properties.Contains(prop)); 35 | 36 | if (model.Alias.ToLowerInvariant() == prop.Alias.ToLowerInvariant()) 37 | 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[] 38 | { 39 | $"Groups[{model.Groups.IndexOf(propertyGroup)}].Properties[{propertyGroup.Properties.IndexOf(prop)}].Alias" 40 | }); 41 | 42 | //we need to return the field name with an index so it's wired up correctly 43 | var groupIndex = model.Groups.IndexOf(propertyGroup); 44 | var propertyIndex = propertyGroup.Properties.IndexOf(prop); 45 | 46 | var validationResult = ValidateProperty(prop, groupIndex, propertyIndex); 47 | if (validationResult != null) 48 | { 49 | yield return validationResult; 50 | } 51 | } 52 | } 53 | } 54 | 55 | private ValidationResult ValidateProperty(PropertyTypeBasic property, int groupIndex, int propertyIndex) 56 | { 57 | //don't let them match any properties or methods in IPublishedContent 58 | //TODO: There are probably more! 59 | var reservedProperties = typeof(IPublishedContent).GetProperties().Select(x => x.Name).ToArray(); 60 | var reservedMethods = typeof(IPublishedContent).GetMethods().Select(x => x.Name).ToArray(); 61 | 62 | var alias = property.Alias; 63 | 64 | if (reservedProperties.InvariantContains(alias) || reservedMethods.InvariantContains(alias)) 65 | { 66 | return new ValidationResult( 67 | $"The alias {alias} is a reserved term and cannot be used", new[] 68 | { 69 | $"Groups[{groupIndex}].Properties[{propertyIndex}].Alias" 70 | }); 71 | } 72 | 73 | return null; 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /src/Our.ModelsBuilder.Tests/DotNet/ExpressionTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using System.Reflection; 4 | using NUnit.Framework; 5 | 6 | namespace Our.ModelsBuilder.Tests.DotNet 7 | { 8 | [TestFixture] 9 | public class ExpressionTests 10 | { 11 | [Test] 12 | public void Test() 13 | { 14 | var o = new ModelClass(); 15 | var mi = SelectProperty(o, x => x.ValueInt); 16 | } 17 | 18 | private MemberInfo SelectProperty(TModel model, Expression> property) 19 | { 20 | if (property.NodeType != ExpressionType.Lambda) 21 | throw new Exception("not a lambda: " + property.NodeType); 22 | 23 | var lambda = (LambdaExpression) property; 24 | var lambdaBody = lambda.Body; 25 | 26 | if (lambdaBody.NodeType != ExpressionType.MemberAccess) 27 | throw new Exception("not a member access: " + lambdaBody.NodeType); 28 | 29 | var member = (MemberExpression) lambdaBody; 30 | if (member.Expression.NodeType != ExpressionType.Parameter) 31 | throw new Exception("not a parameter: " + member.Expression.NodeType); 32 | 33 | return member.Member; 34 | } 35 | 36 | public static MemberInfo FindProperty(LambdaExpression lambda) 37 | { 38 | void Throw() 39 | { 40 | throw new ArgumentException($"Expression '{lambda}' must resolve to top-level member and not any child object's properties. Use a custom resolver on the child type or the AfterMap option instead.", nameof(lambda)); 41 | } 42 | 43 | Expression expr = lambda; 44 | var loop = true; 45 | while (loop) 46 | { 47 | switch (expr.NodeType) 48 | { 49 | case ExpressionType.Convert: 50 | expr = ((UnaryExpression) expr).Operand; 51 | break; 52 | case ExpressionType.Lambda: 53 | expr = ((LambdaExpression) expr).Body; 54 | break; 55 | //case ExpressionType.Call: 56 | // var callExpr = (MethodCallExpression) expr; 57 | // var method = callExpr.Method; 58 | // if (method.DeclaringType != typeof(NPocoSqlExtensions.Statics) || method.Name != "Alias" || !(callExpr.Arguments[1] is ConstantExpression aliasExpr)) 59 | // Throw(); 60 | // expr = callExpr.Arguments[0]; 61 | // alias = aliasExpr.Value.ToString(); 62 | // break; 63 | case ExpressionType.MemberAccess: 64 | var memberExpr = (MemberExpression) expr; 65 | if (memberExpr.Expression.NodeType != ExpressionType.Parameter && memberExpr.Expression.NodeType != ExpressionType.Convert) 66 | Throw(); 67 | return memberExpr.Member; 68 | default: 69 | loop = false; 70 | break; 71 | } 72 | } 73 | 74 | throw new Exception("Configuration for members is only supported for top-level individual members on a type."); 75 | } 76 | private class ModelClass 77 | { 78 | public int ValueInt { get; set; } 79 | public string ValueString { get; set; } 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/Our.ModelsBuilder.Web/Api/ApiBasicAuthFilter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Net; 4 | using System.Net.Http; 5 | using System.Text; 6 | using System.Web.Http.Controllers; 7 | using System.Web.Security; 8 | using Umbraco.Core; 9 | using Umbraco.Core.Composing; 10 | using Umbraco.Core.Models.Membership; 11 | 12 | namespace Our.ModelsBuilder.Web.Api 13 | { 14 | 15 | //TODO: This needs to be changed: 16 | // * Authentication cannot happen in a filter, only Authorization 17 | // * The filter must be an AuthorizationFilter, not an ActionFilter 18 | // * Authorization must be done using the Umbraco logic - it is very specific for claim checking for ASP.Net Identity 19 | // * Theoretically this shouldn't be required whatsoever because when we authenticate a request that has Basic Auth (i.e. for 20 | // VS to work, it will add the correct Claims to the Identity and it will automatically be authorized. 21 | // 22 | // we *do* have POC supporting ASP.NET identity, however they require some config on the server 23 | // we'll keep using this quick-and-dirty method for the time being 24 | 25 | public class ApiBasicAuthFilter : System.Web.Http.Filters.ActionFilterAttribute // use the http one, not mvc, with api controllers! 26 | { 27 | private static readonly char[] Separator = ":".ToCharArray(); 28 | private readonly string _section; 29 | 30 | public ApiBasicAuthFilter(string section) 31 | { 32 | _section = section; 33 | } 34 | 35 | public override void OnActionExecuting(HttpActionContext actionContext) 36 | { 37 | try 38 | { 39 | var user = Authenticate(actionContext.Request); 40 | if (user == null || !user.AllowedSections.Contains(_section)) 41 | { 42 | actionContext.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized); 43 | } 44 | //else 45 | //{ 46 | // // note - would that be a proper way to pass data to the controller? 47 | // // see http://stevescodingblog.co.uk/basic-authentication-with-asp-net-webapi/ 48 | // actionContext.ControllerContext.RouteData.Values["umbraco-user"] = user; 49 | //} 50 | 51 | base.OnActionExecuting(actionContext); 52 | } 53 | catch 54 | { 55 | actionContext.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized); 56 | } 57 | } 58 | 59 | private static IUser Authenticate(HttpRequestMessage request) 60 | { 61 | var ah = request.Headers.Authorization; 62 | if (ah == null || ah.Scheme != "Basic") 63 | return null; 64 | 65 | var token = ah.Parameter; 66 | var credentials = Encoding.ASCII 67 | .GetString(Convert.FromBase64String(token)) 68 | .Split(Separator); 69 | if (credentials.Length != 2) 70 | return null; 71 | 72 | var username = ApiClient.DecodeTokenElement(credentials[0]); 73 | var password = ApiClient.DecodeTokenElement(credentials[1]); 74 | 75 | var provider = Membership.Providers[Constants.Security.UserMembershipProviderName]; 76 | if (provider == null || !provider.ValidateUser(username, password)) 77 | return null; 78 | var user = Current.Services.UserService.GetByUsername(username); 79 | if (!user.IsApproved || user.IsLockedOut) 80 | return null; 81 | return user; 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /src/Our.ModelsBuilder.Extension/GeneratorWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Text; 4 | using System.Windows; 5 | using System.Windows.Controls; 6 | using System.Windows.Threading; 7 | using EnvDTE; 8 | using Microsoft.VisualStudio.PlatformUI; 9 | using Microsoft.VisualStudio.Shell; 10 | using Our.ModelsBuilder.Api; 11 | 12 | namespace Our.ModelsBuilder.Extension 13 | { 14 | /// 15 | /// Interaction logic for GeneratorWindow.xaml 16 | /// 17 | public partial class GeneratorWindow : DialogWindow 18 | { 19 | private readonly AsyncPackage _package; 20 | private readonly ProjectItem _sourceItem; 21 | 22 | private readonly StringBuilder _text = new StringBuilder(); 23 | private int _progress; 24 | 25 | public GeneratorWindow(AsyncPackage package, ProjectItem sourceItem) 26 | { 27 | _package = package; 28 | _sourceItem = sourceItem; 29 | 30 | InitializeComponent(); 31 | 32 | ButtonClose.IsEnabled = false; 33 | 34 | ProgressBar.Minimum = 0; 35 | ProgressBar.Maximum = 100; 36 | 37 | Text.IsReadOnly = true; 38 | Text.TextWrapping = TextWrapping.Wrap; 39 | Text.HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled; 40 | Text.VerticalScrollBarVisibility = ScrollBarVisibility.Visible; 41 | 42 | WriteLine("Models Builder " + ApiVersion.Current.Version); 43 | WriteLine(); 44 | } 45 | 46 | protected override void OnClosing(CancelEventArgs e) 47 | { 48 | base.OnClosing(e); 49 | 50 | if (_progress < 100) 51 | e.Cancel = true; 52 | } 53 | 54 | protected override void OnContentRendered(EventArgs e) 55 | { 56 | base.OnContentRendered(e); 57 | 58 | //var time = Stopwatch.StartNew(); 59 | var generator = new Generator(_package, _sourceItem); 60 | generator.Progressed += (message, percent) => // invoked on main thread 61 | { 62 | //Write($"[{time.ElapsedMilliseconds:000000}] "); 63 | WriteLine(message); 64 | Progress(percent); 65 | }; 66 | 67 | // not too proud of that async hackish code 68 | ThreadHelper.JoinableTaskFactory.Run(async () => 69 | { 70 | await generator.GenerateAsync(); 71 | }); 72 | } 73 | 74 | public void Write(string text) 75 | { 76 | _text.Append(text); 77 | Text.Text = _text.ToString(); 78 | Render(Text); 79 | } 80 | 81 | public void WriteLine(string text = null) 82 | { 83 | if (!string.IsNullOrWhiteSpace(text)) 84 | _text.Append(text); 85 | _text.AppendLine(); 86 | Text.Text = _text.ToString(); 87 | Text.ScrollToEnd(); 88 | Render(Text); 89 | } 90 | 91 | public void Progress(int progress) 92 | { 93 | if (progress <= _progress) 94 | return; 95 | 96 | ProgressBar.Value = progress; 97 | Render(ProgressBar); 98 | _progress = progress; 99 | 100 | if (progress == 100) 101 | ButtonClose.IsEnabled = true; 102 | } 103 | 104 | private void Close_OnClick(object sender, RoutedEventArgs e) 105 | { 106 | Close(); 107 | } 108 | 109 | // not too proud of that one, but how do you refresh the UI? 110 | // https://www.meziantou.net/refresh-a-wpf-control.htm 111 | // https://www.c-sharpcorner.com/article/update-ui-with-wpf-dispatcher-and-tpl/ 112 | private static void Render(DispatcherObject dispatcherObject) 113 | { 114 | #pragma warning disable VSTHRD001 // bah 115 | dispatcherObject.Dispatcher.Invoke(() => { }, DispatcherPriority.Render); 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/Our.ModelsBuilder.Web/Plugin/DashboardUtilities.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using Our.ModelsBuilder.Api; 3 | using Our.ModelsBuilder.Options; 4 | using Our.ModelsBuilder.Umbraco; 5 | using Umbraco.Core; 6 | using Umbraco.Core.Composing; 7 | using Umbraco.Core.Models.PublishedContent; 8 | 9 | namespace Our.ModelsBuilder.Web.Plugin 10 | { 11 | internal class DashboardUtilities 12 | { 13 | private readonly ModelsBuilderOptions _options; 14 | 15 | public DashboardUtilities(ModelsBuilderOptions options) 16 | { 17 | _options = options; 18 | } 19 | 20 | public bool CanGenerate() 21 | { 22 | return _options.ModelsMode.SupportsExplicitGeneration(); 23 | } 24 | 25 | public bool GenerateCausesRestart() 26 | { 27 | return _options.ModelsMode.IsAnyDll(); 28 | } 29 | 30 | public bool AreModelsOutOfDate() 31 | { 32 | return OutOfDateModelsStatus.IsOutOfDate; 33 | } 34 | 35 | public string LastError() 36 | { 37 | return ModelsGenerationError.GetLastError(); 38 | } 39 | 40 | public string Text() 41 | { 42 | if (!_options.Enable) 43 | return "Version: " + ApiVersion.Current.Version + "
 
ModelsBuilder is disabled
(the .Enable appSetting is missing, or its value is not 'true')."; 44 | 45 | var sb = new StringBuilder(); 46 | 47 | sb.Append("Version: "); 48 | sb.Append(ApiVersion.Current.Version); 49 | sb.Append("
 
"); 50 | 51 | sb.Append("ModelsBuilder is enabled, with the following configuration:"); 52 | 53 | sb.Append("
    "); 54 | 55 | sb.Append("
  • The models factory is "); 56 | sb.Append(_options.EnableFactory || _options.ModelsMode == ModelsMode.PureLive 57 | ? "enabled" 58 | : "not enabled. Umbraco will not use models"); 59 | if (_options.EnableFactory || _options.ModelsMode == ModelsMode.PureLive) 60 | { 61 | sb.Append(", of type "); 62 | sb.Append(Current.Factory.GetInstance().GetType().FullName); 63 | sb.Append(""); 64 | } 65 | sb.Append(".
  • "); 66 | 67 | sb.Append(_options.ModelsMode != ModelsMode.Nothing 68 | ? $"
  • {_options.ModelsMode} models are enabled.
  • " 69 | : "
  • No models mode is specified: models will not be generated.
  • "); 70 | 71 | sb.Append($"
  • Models namespace is {_options.ModelsNamespace} but may be overriden by attribute.
  • "); 72 | 73 | sb.Append("
  • Tracking of out-of-date models is "); 74 | sb.Append(_options.FlagOutOfDateModels ? "enabled" : "not enabled"); 75 | sb.Append(".
  • "); 76 | 77 | sb.Append("
  • The API is "); 78 | if (_options.EnableApi) 79 | { 80 | sb.Append("enabled"); 81 | if (!_options.IsDebug) sb.Append(".
    However, the API runs only with debug compilation mode"); 82 | } 83 | else sb.Append("not enabled"); 84 | sb.Append(". "); 85 | 86 | if (!_options.IsApiServer) 87 | sb.Append("External tools such as Visual Studio cannot use the API"); 88 | else 89 | sb.Append("The API endpoint is open on this server"); 90 | sb.Append(".
  • "); 91 | 92 | sb.Append("
  • BackOffice integrations are "); 93 | sb.Append(_options.EnableBackOffice ? "enabled (else, you would not see this dashboard)" : "disabled"); 94 | sb.Append(".
  • "); 95 | 96 | sb.Append("
"); 97 | 98 | return sb.ToString(); 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/Our.ModelsBuilder.Tests/ApiVersionTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NUnit.Framework; 3 | using Our.ModelsBuilder.Api; 4 | using Semver; 5 | 6 | namespace Our.ModelsBuilder.Tests 7 | { 8 | [TestFixture] 9 | public class ApiVersionTests 10 | { 11 | [Test] 12 | public void IsCompatibleTest() 13 | { 14 | // executing version 3.0.0, accepting connections from 2.0.0 15 | var av = new ApiVersion(new SemVersion(3, 0, 0), new SemVersion(2, 0, 0)); 16 | 17 | // server version 1.0.0 is not compatible (too old) 18 | Assert.IsFalse(av.IsCompatibleWith(new SemVersion(1, 0, 0))); 19 | 20 | // server version 2.0.0 or 3.0.0 is compatible 21 | Assert.IsTrue(av.IsCompatibleWith(new SemVersion(2, 0, 0))); 22 | Assert.IsTrue(av.IsCompatibleWith(new SemVersion(3, 0, 0))); 23 | 24 | // server version 4.0.0 is not compatible (too recent) 25 | Assert.IsFalse(av.IsCompatibleWith(new SemVersion(4, 0, 0))); 26 | 27 | // but can declare it is, indeed, compatible with version 2.0.0 or 3.0.0 28 | Assert.IsTrue(av.IsCompatibleWith(new SemVersion(4, 0, 0), new SemVersion(2, 0, 0))); 29 | Assert.IsTrue(av.IsCompatibleWith(new SemVersion(4, 0, 0), new SemVersion(3, 0, 0))); 30 | 31 | // but... 32 | Assert.IsFalse(av.IsCompatibleWith(new SemVersion(4, 0, 0), new SemVersion(3, 0, 1))); 33 | } 34 | 35 | [Test] 36 | public void CurrentIsCompatibleTest() 37 | { 38 | var av = ApiVersion.Current; 39 | 40 | // client version < MinClientVersionSupportedByServer are not supported 41 | Assert.IsFalse(av.IsCompatibleWith(GetPreviousVersion(av.MinClientVersionSupportedByServer))); 42 | 43 | // client version MinClientVersionSupportedByServer-Version are supported 44 | Assert.IsTrue(av.IsCompatibleWith(av.MinClientVersionSupportedByServer)); 45 | 46 | // client version > Version are not supported 47 | Assert.IsFalse(av.IsCompatibleWith(GetNextVersion(av.Version))); 48 | 49 | // unless client says so 50 | Assert.IsTrue(av.IsCompatibleWith(GetNextVersion(av.Version), av.Version)); 51 | } 52 | 53 | [Test] 54 | public void NextVersionTest() 55 | { 56 | Assert.AreEqual(new SemVersion(0, 0, 1), GetNextVersion(new SemVersion(0, 0, 0))); 57 | Assert.AreEqual(new SemVersion(1, 0, 1), GetNextVersion(new SemVersion(1, 0, 0))); 58 | Assert.AreEqual(new SemVersion(1, 0, 8), GetNextVersion(new SemVersion(1, 0, 7))); 59 | } 60 | 61 | [Test] 62 | public void PreviousVersionTest() 63 | { 64 | Assert.AreEqual(new SemVersion(0, 0, 0), GetPreviousVersion(new SemVersion(0, 0, 1))); 65 | Assert.AreEqual(new SemVersion(0, 0, 1), GetPreviousVersion(new SemVersion(0, 0, 2))); 66 | Assert.AreEqual(new SemVersion(0, 0, 999), GetPreviousVersion(new SemVersion(0, 1, 0))); 67 | Assert.AreEqual(new SemVersion(0, 0, 999), GetPreviousVersion(new SemVersion(0, 1, 0))); 68 | Assert.AreEqual(new SemVersion(0, 999, 999), GetPreviousVersion(new SemVersion(1, 0, 0))); 69 | Assert.AreEqual(new SemVersion(1, 999, 999), GetPreviousVersion(new SemVersion(2, 0, 0))); 70 | } 71 | 72 | private static SemVersion GetNextVersion(SemVersion version) 73 | { 74 | return new SemVersion(version.Major, version.Minor, version.Patch + 1); 75 | } 76 | 77 | private static SemVersion GetPreviousVersion(SemVersion version) 78 | { 79 | if (version.Prerelease != "") 80 | { 81 | var p = version.Prerelease.Split('.'); 82 | return new SemVersion(version.Major, version.Minor, version.Patch, p[0] + "." + (int.Parse(p[1]) - 1)); 83 | } 84 | if (version.Patch > 0) 85 | return new SemVersion(version.Major, version.Minor, version.Patch - 1); 86 | if (version.Minor > 0) 87 | return new SemVersion(version.Major, version.Minor - 1, 999); 88 | if (version.Major > 0) 89 | return new SemVersion(version.Major - 1, 999, 999); 90 | 91 | throw new ArgumentOutOfRangeException(nameof(version)); 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/Our.ModelsBuilder/Building/CodeWriter.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | 3 | namespace Our.ModelsBuilder.Building 4 | { 5 | /// 6 | /// Provides the default code writer. 7 | /// 8 | public class CodeWriter : ModelsCodeWriter, ICodeWriter 9 | { 10 | private ContentTypesCodeWriter _contentTypesContentTypesCodeWriter; 11 | private InfosCodeWriter _infosCodeWriter; 12 | 13 | /// 14 | /// Initializes a new instance of the class. 15 | /// 16 | public CodeWriter(CodeModel model, StringBuilder text = null) 17 | : base(model, text) 18 | { } 19 | 20 | /// 21 | public virtual ContentTypesCodeWriter ContentTypesCodeWriter 22 | => _contentTypesContentTypesCodeWriter ??= new ContentTypesCodeWriter(this); 23 | 24 | /// 25 | public virtual InfosCodeWriter InfosCodeWriter 26 | => _infosCodeWriter ??= new InfosCodeWriter(this); 27 | 28 | #region Write Complete Files 29 | 30 | /// 31 | /// Writes a using statement if it is not already defined by the code model. 32 | /// 33 | protected virtual void WriteUsing(string ns) 34 | { 35 | if (!CodeModel.Using.Contains(ns)) 36 | WriteIndentLine($"using {ns};"); 37 | } 38 | 39 | /// 40 | /// Writes the using statements defined by the code model. 41 | /// 42 | public virtual void WriteUsing() 43 | { 44 | foreach (var t in CodeModel.Using) 45 | WriteIndentLine($"using {t};"); 46 | } 47 | 48 | /// 49 | public virtual void WriteModelFile(ContentTypeModel model) 50 | { 51 | WriteFileHeader(); 52 | WriteLine(); 53 | 54 | WriteUsing(); 55 | WriteUsing("System.CodeDom.Compiler"); 56 | WriteLine(); 57 | 58 | WriteBlockStart($"namespace {CodeModel.ModelsNamespace}"); 59 | ContentTypesCodeWriter.WriteModel(model); 60 | WriteBlockEnd(); 61 | } 62 | 63 | /// 64 | public virtual void WriteSingleFile() 65 | { 66 | WriteFileHeader(); 67 | WriteLine(); 68 | 69 | WriteUsing(); 70 | WriteUsing("System"); 71 | WriteUsing("System.Linq"); 72 | WriteUsing("System"); 73 | WriteUsing("System.Linq"); 74 | WriteUsing("System.Collections.Generic"); 75 | WriteUsing("System.CodeDom.Compiler"); 76 | WriteUsing("Umbraco.Core.Models.PublishedContent"); 77 | WriteUsing("Our.ModelsBuilder"); 78 | WriteUsing("Our.ModelsBuilder.Umbraco"); 79 | WriteLine(); 80 | 81 | // assembly attributes marker 82 | WriteIndentLine("//ASSATTR"); 83 | WriteLine(); 84 | 85 | WriteBlockStart($"namespace {CodeModel.ModelsNamespace}"); 86 | ContentTypesCodeWriter.WriteModels(CodeModel.ContentTypes.ContentTypes); 87 | WriteBlockEnd(); 88 | 89 | WriteLine(); 90 | 91 | WriteBlockStart($"namespace {CodeModel.ModelInfosClassNamespace}"); 92 | InfosCodeWriter.WriteInfosClass(CodeModel); 93 | WriteBlockEnd(); 94 | } 95 | 96 | /// 97 | public virtual void WriteModelInfosFile() 98 | { 99 | WriteFileHeader(); 100 | WriteLine(); 101 | 102 | WriteUsing(); 103 | WriteUsing("System"); 104 | WriteUsing("System.Linq"); 105 | WriteUsing("System.Collections.Generic"); 106 | WriteUsing("System.CodeDom.Compiler"); 107 | WriteUsing("Umbraco.Core.Models.PublishedContent"); 108 | WriteUsing("Our.ModelsBuilder"); 109 | WriteUsing("Our.ModelsBuilder.Umbraco"); 110 | WriteLine(); 111 | 112 | WriteBlockStart($"namespace {CodeModel.ModelInfosClassNamespace}"); 113 | InfosCodeWriter.WriteInfosClass(CodeModel); 114 | WriteBlockEnd(); 115 | } 116 | 117 | #endregion 118 | } 119 | } -------------------------------------------------------------------------------- /src/Our.ModelsBuilder.Extension/ExtensionPackage.vsct: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 28 | 29 | 36 | 37 | 40 | 41 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 58 | 59 | 60 | 61 | 66 | 67 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /src/Our.ModelsBuilder.Tests/DotNet/AppDomainTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Reflection; 6 | using NUnit.Framework; 7 | 8 | namespace Our.ModelsBuilder.Tests.DotNet 9 | { 10 | [TestFixture] 11 | public class AppDomainTests 12 | { 13 | [Test] 14 | [Ignore("no idea what we're testing here?")] 15 | public void Test() 16 | { 17 | // read http://msdn.microsoft.com/en-us/library/ms173139%28v=vs.90%29.aspx 18 | 19 | // test executes in project/bin/Debug or /Release 20 | Console.WriteLine("FriendlyName " + AppDomain.CurrentDomain.FriendlyName); 21 | Console.WriteLine("BaseDirectory " + AppDomain.CurrentDomain.BaseDirectory); // project's bin 22 | Console.WriteLine("SearchPath " + AppDomain.CurrentDomain.RelativeSearchPath); 23 | Console.WriteLine("CodeBase " + Assembly.GetExecutingAssembly().CodeBase); 24 | Console.WriteLine("CurrentDirectory " + Directory.GetCurrentDirectory()); 25 | 26 | var domainSetup = new AppDomainSetup(); 27 | 28 | var bzzt = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "bzzt"); 29 | Console.WriteLine("Bzzt " + bzzt); 30 | if (Directory.Exists(bzzt)) 31 | Directory.Delete(bzzt, true); 32 | Directory.CreateDirectory(bzzt); 33 | var load = Path.Combine(bzzt, "Our.ModelsBuilder.Tests.dll"); // we want to load the copy! 34 | File.Copy("Our.ModelsBuilder.Tests.dll", load); 35 | 36 | // fixme - why do we want copies? why cant we load stuff from where we are? 37 | // because - we want Umbraco plugin whatever to discover those things properly! 38 | File.Copy("Our.ModelsBuilder.dll", Path.Combine(bzzt, "Our.ModelsBuilder.dll")); // REQUIRED if we load copies 39 | 40 | // fixme - notes 41 | // because we set a root dir to the app which is in appdata 42 | // then IOHelper.GetRootDirectorySafe returns that directory ie AppData/Zbu 43 | // and IOHelper.GetRootDirectoryBinFolder looks into ~/bin/debug, ~/bin/release, ~/bin then ~/ 44 | // and this is where TypeFinder.GetAllAssemblies will be looking into 45 | 46 | // app domain defaults to resharper's bin - FIXME how can it load our dll? 47 | // this makes sure it uses the right app base 48 | // with: directly look for the assembly in there 49 | // without: look for the assembly in resharper's bin first... then in there, why? 'cos it's the calling assembly! 50 | domainSetup.ApplicationBase = bzzt; 51 | // /bin rejected, outside appbase ... but anything else seems to be ignored? 52 | //domainSetup.PrivateBinPath = "bzzt"; // absolutely no idea what it does 53 | domainSetup.ApplicationName = "Test Application"; 54 | 55 | // fixme - then we MUST copy a bunch of binaries out there! 56 | var domain = AppDomain.CreateDomain("Test Domain", null, domainSetup); 57 | // the dll here is relative to the local domain path, not the remote one?! 58 | var remote = domain.CreateInstanceFromAndUnwrap(load, "Our.ModelsBuilder.Tests.RemoteObject") as RemoteObject; 59 | var sho = remote.GetSharedObjects(); 60 | Console.WriteLine(remote.GetAppDomainDetails()); 61 | //Console.WriteLine(domain.); 62 | AppDomain.Unload(domain); 63 | 64 | Assert.Throws(() => remote.GetAppDomainDetails()); 65 | 66 | var asho = sho.ToArray(); 67 | Assert.AreEqual(1, asho.Length); 68 | Assert.AreEqual("hello", asho[0].Value); 69 | } 70 | } 71 | 72 | // read 73 | // http://blogs.microsoft.co.il/sasha/2010/05/06/assembly-private-bin-path-pitfall/ 74 | // run fuslogvw.exe from an elevated Visual Studio command prompt 75 | 76 | [Serializable] 77 | public class SharedObject 78 | { 79 | public string Value { get; set; } 80 | } 81 | 82 | public class RemoteObject : MarshalByRefObject 83 | { 84 | public IEnumerable GetSharedObjects() 85 | { 86 | // in order for this to work... where should we look into? 87 | Assembly.Load("Our.ModelsBuilder"); 88 | 89 | return new[] { new SharedObject { Value = "hello" } }; 90 | } 91 | 92 | public string GetAppDomainDetails() 93 | { 94 | return AppDomain.CurrentDomain.FriendlyName 95 | + Environment.NewLine + AppDomain.CurrentDomain.BaseDirectory 96 | + Environment.NewLine + AppDomain.CurrentDomain.RelativeSearchPath 97 | + Environment.NewLine + Assembly.GetExecutingAssembly().CodeBase; 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/Our.ModelsBuilder/Umbraco/LiveModelsProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Web.Hosting; 4 | using Our.ModelsBuilder.Building; 5 | using Our.ModelsBuilder.Options; 6 | using Umbraco.Core.Composing; 7 | using Umbraco.Core.Logging; 8 | using Umbraco.Web.Cache; 9 | 10 | namespace Our.ModelsBuilder.Umbraco 11 | { 12 | // supports LiveDll and LiveAppData - but not PureLive 13 | //public class LiveModelsComponent 14 | public sealed class LiveModelsProvider // FIXME this should just be a component? 15 | { 16 | private static ICodeFactory _codeFactory; 17 | private static ModelsBuilderOptions _options; 18 | private static Mutex _mutex; 19 | private static int _req; 20 | 21 | // we do not manage pure live here 22 | internal static bool IsEnabled => _options.ModelsMode.IsLiveNotPure(); 23 | 24 | internal static void Install(ICodeFactory factory, ModelsBuilderOptions options) 25 | { 26 | // just be sure 27 | if (!IsEnabled) 28 | return; 29 | 30 | _codeFactory = factory; 31 | _options = options; 32 | 33 | // initialize mutex 34 | // ApplicationId will look like "/LM/W3SVC/1/Root/AppName" 35 | // name is system-wide and must be less than 260 chars 36 | var name = HostingEnvironment.ApplicationID + "/UmbracoLiveModelsProvider"; 37 | _mutex = new Mutex(false, name); 38 | 39 | // anything changes, and we want to re-generate models. 40 | ContentTypeCacheRefresher.CacheUpdated += RequestModelsGeneration; 41 | DataTypeCacheRefresher.CacheUpdated += RequestModelsGeneration; 42 | 43 | // at the end of a request since we're restarting the pool 44 | // NOTE - this does NOT trigger - see module below 45 | //umbracoApplication.EndRequest += GenerateModelsIfRequested; 46 | } 47 | 48 | // NOTE 49 | // Using HttpContext Items fails because CacheUpdated triggers within 50 | // some asynchronous backend task where we seem to have no HttpContext. 51 | 52 | // So we use a static (non request-bound) var to register that models 53 | // need to be generated. Could be by another request. Anyway. We could 54 | // have collisions but... you know the risk. 55 | 56 | private static void RequestModelsGeneration(object sender, EventArgs args) 57 | { 58 | //HttpContext.Current.Items[this] = true; 59 | Current.Logger.Debug("Requested to generate models."); 60 | Interlocked.Exchange(ref _req, 1); 61 | } 62 | 63 | public static void GenerateModelsIfRequested(object sender, EventArgs args) 64 | { 65 | //if (HttpContext.Current.Items[this] == null) return; 66 | if (Interlocked.Exchange(ref _req, 0) == 0) return; 67 | 68 | // cannot use a simple lock here because we don't want another AppDomain 69 | // to generate while we do... and there could be 2 AppDomains if the app restarts. 70 | 71 | try 72 | { 73 | Current.Logger.Debug("Generate models..."); 74 | const int timeout = 2*60*1000; // 2 mins 75 | _mutex.WaitOne(timeout); // wait until it is safe, and acquire 76 | Current.Logger.Info("Generate models now."); 77 | GenerateModels(); 78 | ModelsGenerationError.Clear(); 79 | Current.Logger.Info("Generated."); 80 | } 81 | catch (TimeoutException) 82 | { 83 | Current.Logger.Warn("Timeout, models were NOT generated."); 84 | } 85 | catch (Exception e) 86 | { 87 | ModelsGenerationError.Report("Failed to build Live models.", e); 88 | Current.Logger.Error("Failed to generate models.", e); 89 | } 90 | finally 91 | { 92 | _mutex.ReleaseMutex(); // release 93 | } 94 | } 95 | 96 | private static void GenerateModels() 97 | { 98 | var modelsDirectory = _options.ModelsDirectory; 99 | var modelsNamespace = _options.ModelsNamespace; 100 | 101 | var bin = HostingEnvironment.MapPath("~/bin"); 102 | if (bin == null) 103 | throw new Exception("Panic: bin is null."); 104 | 105 | // EnableDllModels will recycle the app domain - but this request will end properly 106 | var generator = new Generator(_codeFactory, _options); 107 | generator.GenerateModels(modelsDirectory, _options.ModelsMode.IsAnyDll() ? bin : null, modelsNamespace); 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/Our.ModelsBuilder.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29006.145 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution", "Solution", "{C7535949-9EEE-4711-8351-0CA8EB32DBE6}" 7 | ProjectSection(SolutionItems) = preProject 8 | ..\license.txt = ..\license.txt 9 | ..\readme.md = ..\readme.md 10 | SolutionInfo.cs = SolutionInfo.cs 11 | EndProjectSection 12 | EndProject 13 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{42C3FC1A-26BA-4FAB-9585-AC8C25449B8F}" 14 | ProjectSection(SolutionItems) = preProject 15 | ..\build\build-bootstrap.ps1 = ..\build\build-bootstrap.ps1 16 | ..\build\build.ps1 = ..\build\build.ps1 17 | EndProjectSection 18 | EndProject 19 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ModelsBuilder", "ModelsBuilder", "{1FFB7043-4B35-4A92-80FC-A2306511C8D5}" 20 | ProjectSection(SolutionItems) = preProject 21 | ..\build\Nuspecs\ModelsBuilder\web.config.install.xdt = ..\build\Nuspecs\ModelsBuilder\web.config.install.xdt 22 | ..\build\Nuspecs\ModelsBuilder\web.config.uninstall.xdt = ..\build\Nuspecs\ModelsBuilder\web.config.uninstall.xdt 23 | EndProjectSection 24 | EndProject 25 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Nuspecs", "Nuspecs", "{30368E85-95E0-4A26-AC5A-D8570DB16FDA}" 26 | ProjectSection(SolutionItems) = preProject 27 | ..\build\Nuspecs\logo.png = ..\build\Nuspecs\logo.png 28 | ..\build\Nuspecs\Our.ModelsBuilder.nuspec = ..\build\Nuspecs\Our.ModelsBuilder.nuspec 29 | ..\build\Nuspecs\Our.ModelsBuilder.Web.nuspec = ..\build\Nuspecs\Our.ModelsBuilder.Web.nuspec 30 | EndProjectSection 31 | EndProject 32 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Our.ModelsBuilder", "Our.ModelsBuilder\Our.ModelsBuilder.csproj", "{998FB014-303A-4146-B3E4-B927BAB0210F}" 33 | EndProject 34 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Our.ModelsBuilder.Tests", "Our.ModelsBuilder.Tests\Our.ModelsBuilder.Tests.csproj", "{CF597D44-04EA-4FD7-89C8-E4849D1355C8}" 35 | EndProject 36 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Our.ModelsBuilder.Console", "Our.ModelsBuilder.Console\Our.ModelsBuilder.Console.csproj", "{F0B757DF-0CF7-4AAF-8DD0-F46EDF25C3A3}" 37 | EndProject 38 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Our.ModelsBuilder.Web", "Our.ModelsBuilder.Web\Our.ModelsBuilder.Web.csproj", "{2317BE7F-1723-4512-B863-5B6835E583A2}" 39 | EndProject 40 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Our.ModelsBuilder.Extension", "Our.ModelsBuilder.Extension\Our.ModelsBuilder.Extension.csproj", "{0419A43D-78C2-46C9-95A9-2470F224D60F}" 41 | EndProject 42 | Global 43 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 44 | Debug|Any CPU = Debug|Any CPU 45 | Release|Any CPU = Release|Any CPU 46 | EndGlobalSection 47 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 48 | {998FB014-303A-4146-B3E4-B927BAB0210F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 49 | {998FB014-303A-4146-B3E4-B927BAB0210F}.Debug|Any CPU.Build.0 = Debug|Any CPU 50 | {998FB014-303A-4146-B3E4-B927BAB0210F}.Release|Any CPU.ActiveCfg = Release|Any CPU 51 | {998FB014-303A-4146-B3E4-B927BAB0210F}.Release|Any CPU.Build.0 = Release|Any CPU 52 | {CF597D44-04EA-4FD7-89C8-E4849D1355C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 53 | {CF597D44-04EA-4FD7-89C8-E4849D1355C8}.Debug|Any CPU.Build.0 = Debug|Any CPU 54 | {CF597D44-04EA-4FD7-89C8-E4849D1355C8}.Release|Any CPU.ActiveCfg = Release|Any CPU 55 | {CF597D44-04EA-4FD7-89C8-E4849D1355C8}.Release|Any CPU.Build.0 = Release|Any CPU 56 | {F0B757DF-0CF7-4AAF-8DD0-F46EDF25C3A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 57 | {F0B757DF-0CF7-4AAF-8DD0-F46EDF25C3A3}.Debug|Any CPU.Build.0 = Debug|Any CPU 58 | {F0B757DF-0CF7-4AAF-8DD0-F46EDF25C3A3}.Release|Any CPU.ActiveCfg = Release|Any CPU 59 | {2317BE7F-1723-4512-B863-5B6835E583A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 60 | {2317BE7F-1723-4512-B863-5B6835E583A2}.Debug|Any CPU.Build.0 = Debug|Any CPU 61 | {2317BE7F-1723-4512-B863-5B6835E583A2}.Release|Any CPU.ActiveCfg = Release|Any CPU 62 | {2317BE7F-1723-4512-B863-5B6835E583A2}.Release|Any CPU.Build.0 = Release|Any CPU 63 | {0419A43D-78C2-46C9-95A9-2470F224D60F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 64 | {0419A43D-78C2-46C9-95A9-2470F224D60F}.Debug|Any CPU.Build.0 = Debug|Any CPU 65 | {0419A43D-78C2-46C9-95A9-2470F224D60F}.Release|Any CPU.ActiveCfg = Release|Any CPU 66 | {0419A43D-78C2-46C9-95A9-2470F224D60F}.Release|Any CPU.Build.0 = Release|Any CPU 67 | EndGlobalSection 68 | GlobalSection(SolutionProperties) = preSolution 69 | HideSolutionNode = FALSE 70 | EndGlobalSection 71 | GlobalSection(NestedProjects) = preSolution 72 | {1FFB7043-4B35-4A92-80FC-A2306511C8D5} = {30368E85-95E0-4A26-AC5A-D8570DB16FDA} 73 | {30368E85-95E0-4A26-AC5A-D8570DB16FDA} = {42C3FC1A-26BA-4FAB-9585-AC8C25449B8F} 74 | EndGlobalSection 75 | GlobalSection(ExtensibilityGlobals) = postSolution 76 | SolutionGuid = {C58B1916-804E-47EC-BD71-E1FC6A3C9924} 77 | EndGlobalSection 78 | EndGlobal 79 | -------------------------------------------------------------------------------- /src/Our.ModelsBuilder/Building/CodeModelBuilder.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Our.ModelsBuilder.Options; 3 | 4 | namespace Our.ModelsBuilder.Building 5 | { 6 | /// 7 | /// Implements the default . 8 | /// 9 | public class CodeModelBuilder : ICodeModelBuilder 10 | { 11 | // FIXME but we still need ... to 'build' options vs to 'query' options 12 | // ICodeFactory / CodeFactory 13 | // provides the key services to generate models 14 | // CodeModelSourceProvider 15 | // provides a CodeModelSource 16 | // CodeModelSource 17 | // represents a source of models, i.e. raw from Umbraco 18 | // CodeOptionsBuilder 19 | // is passed to CodeParser 20 | // builds a CodeOptions 21 | // ICodeParser / CodeParser 22 | // parses existing code and updates the CodeOptionsBuilder 23 | // CodeOptions 24 | // provides options for building a code model 25 | // ICodeModelBuilder / CodeModelBuilder 26 | // + ContentTypesCodeModelBuilder 27 | // builds a code model, from a CodeModelSource, CodeOptions and a Configuration 28 | // CodeModel 29 | // provides a complete description of the code to be written 30 | // ICodeWriter / CodeWriter 31 | // + ContentTypesCodeWriter 32 | // writes code defined in a code model 33 | 34 | /// 35 | /// Initializes a new instance of the class. 36 | /// 37 | public CodeModelBuilder(ModelsBuilderOptions options, CodeOptions codeOptions) 38 | : this(options, codeOptions, new ContentTypesCodeModelBuilder(options, codeOptions)) 39 | { } 40 | 41 | /// 42 | /// Initializes a new instance of the class. 43 | /// 44 | protected CodeModelBuilder(ModelsBuilderOptions options, CodeOptions codeOptions, ContentTypesCodeModelBuilder contentTypes) 45 | { 46 | Options = options; 47 | CodeOptions = codeOptions; 48 | ContentTypes = contentTypes; 49 | } 50 | 51 | /// 52 | /// Gets the content types code model builder. 53 | /// 54 | protected ContentTypesCodeModelBuilder ContentTypes { get; } 55 | 56 | protected ModelsBuilderOptions Options { get; } 57 | 58 | protected CodeOptions CodeOptions { get; } 59 | 60 | /// 61 | public virtual CodeModel Build(CodeModelData data) 62 | { 63 | var model = new CodeModel(data, Options.LanguageVersion) 64 | { 65 | ModelsNamespace = GetModelsNamespace(), 66 | CustomAssemblyName = GetAssemblyName(), 67 | Using = GetUsing() 68 | }; 69 | 70 | // TODO: refactor using, have transform.Use("namespace") 71 | 72 | ContentTypes.Build(model); 73 | 74 | return model; 75 | } 76 | public virtual string GetAssemblyName() 77 | { 78 | // use namespace from code options... or from options 79 | var modelsNamespace = CodeOptions.HasCustomAssemblyName 80 | ? CodeOptions.CustomAssemblyName 81 | : Options.AssemblyName; 82 | 83 | // otherwise, use const 84 | if (string.IsNullOrWhiteSpace(modelsNamespace)) 85 | modelsNamespace = GetDefaultAssemblyName(); 86 | 87 | return modelsNamespace; 88 | } 89 | public virtual string GetDefaultAssemblyName() => null; 90 | 91 | protected virtual string GetDefaultModelsNamespace() => "Umbraco.Web.PublishedModels"; 92 | 93 | protected virtual string GetModelsNamespace() 94 | { 95 | // use namespace from code options... or from options 96 | var modelsNamespace = CodeOptions.HasModelsNamespace 97 | ? CodeOptions.ModelsNamespace 98 | : Options.ModelsNamespace; 99 | 100 | // otherwise, use const 101 | if (string.IsNullOrWhiteSpace(modelsNamespace)) 102 | modelsNamespace = GetDefaultModelsNamespace(); 103 | 104 | return modelsNamespace; 105 | } 106 | 107 | /// 108 | /// Gets the namespace to use in 'using' section. 109 | /// 110 | protected virtual ISet GetUsing() => new HashSet 111 | { 112 | // initialize with default values 113 | "System", 114 | "System.Collections.Generic", 115 | "System.Linq.Expressions", 116 | "System.Web", 117 | "Umbraco.Core.Models", 118 | "Umbraco.Core.Models.PublishedContent", 119 | "Umbraco.Web", 120 | "Our.ModelsBuilder", 121 | "Our.ModelsBuilder.Umbraco", 122 | }; 123 | 124 | } 125 | } -------------------------------------------------------------------------------- /src/Our.ModelsBuilder.Tests/Our.ModelsBuilder.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | v4.7.2 6 | {CF597D44-04EA-4FD7-89C8-E4849D1355C8} 7 | Library 8 | Our.ModelsBuilder.Tests 9 | Our.ModelsBuilder.Tests 10 | ..\ 11 | 12 | 13 | 14 | true 15 | full 16 | false 17 | bin\Debug\ 18 | DEBUG;TRACE 19 | prompt 20 | 4 21 | latest 22 | 23 | 24 | pdbonly 25 | true 26 | bin\Release\ 27 | TRACE 28 | prompt 29 | 4 30 | latest 31 | 32 | 33 | 34 | 4.13.1 35 | 36 | 37 | 3.12.0 38 | 39 | 40 | 3.15.1 41 | 42 | 43 | 8.3.1 44 | 45 | 46 | 47 | 48 | Properties\SolutionInfo.cs 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | Designer 87 | 88 | 89 | 90 | 91 | {2317be7f-1723-4512-b863-5b6835e583a2} 92 | Our.ModelsBuilder.Web 93 | 94 | 95 | {998fb014-303a-4146-b3e4-b927bab0210f} 96 | Our.ModelsBuilder 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /src/Our.ModelsBuilder/Api/ApiVersion.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using Newtonsoft.Json; 4 | using Semver; 5 | 6 | namespace Our.ModelsBuilder.Api 7 | { 8 | /// 9 | /// Manages API version handshake between client and server. 10 | /// 11 | [JsonObject(MemberSerialization.OptIn)] 12 | public class ApiVersion 13 | { 14 | #region Configure 15 | 16 | // indicate the minimum version of the client API that is supported by this server's API. 17 | // eg our (server) Version = 4.8 but we support connections from (client) VSIX down to version 3.2 18 | // => as a server, we accept connections from client down to version ... 19 | private static readonly SemVersion MinClientVersionSupportedByServerConst = SemVersion.Parse("4.0.0-alpha.0"); 20 | 21 | // indicate the minimum version of the server that can support the client API 22 | // eg our (client) Version = 4.8 and we know we're compatible with website server down to version 3.2 23 | // => as a client, we tell the server down to version ... that it should accept us 24 | private static readonly SemVersion MinServerVersionSupportingClientConst = SemVersion.Parse("4.0.0-alpha.0"); 25 | 26 | #endregion 27 | 28 | /// 29 | /// Initializes a new instance of the class. 30 | /// 31 | /// The currently executing version. 32 | /// The min client version supported by the server. 33 | /// An opt min server version supporting the client. 34 | /// 35 | internal ApiVersion(SemVersion executingVersion, SemVersion minClientVersionSupportedByServer, SemVersion minServerVersionSupportingClient = null) 36 | { 37 | Version = executingVersion ?? throw new ArgumentNullException(nameof(executingVersion)); 38 | MinClientVersionSupportedByServer = minClientVersionSupportedByServer ?? throw new ArgumentNullException(nameof(minClientVersionSupportedByServer)); 39 | MinServerVersionSupportingClient = minServerVersionSupportingClient; 40 | } 41 | 42 | private static SemVersion CurrentAssemblyVersion 43 | => SemVersion.Parse(Assembly.GetExecutingAssembly().GetCustomAttribute().InformationalVersion); 44 | 45 | /// 46 | /// Gets the currently executing API version. 47 | /// 48 | public static ApiVersion Current { get; } 49 | = new ApiVersion(CurrentAssemblyVersion, MinClientVersionSupportedByServerConst, MinServerVersionSupportingClientConst); 50 | 51 | /// 52 | /// Gets the executing version of the API. 53 | /// 54 | [JsonProperty("version")] 55 | public SemVersion Version { get; } 56 | 57 | /// 58 | /// Gets the min client version supported by the server. 59 | /// 60 | [JsonProperty("minClientVersionSupportedByServer")] 61 | public SemVersion MinClientVersionSupportedByServer { get; } 62 | 63 | /// 64 | /// Gets the min server version supporting the client. 65 | /// 66 | [JsonProperty("minServerVersionSupportingClient")] 67 | public SemVersion MinServerVersionSupportingClient { get; } 68 | 69 | /// 70 | /// Gets a value indicating whether the API server is compatible with a client. 71 | /// 72 | /// The client version. 73 | /// An opt min server version supporting the client. 74 | /// 75 | /// A client is compatible with a server if 76 | /// * the client version is greater-or-equal MinClientVersionSupportedByServer 77 | /// * the client version is lower-or-equal the server version 78 | /// unless MinServerVersionSupportingClient indicates that the server should support a more recent client 79 | /// 80 | /// 81 | public bool IsCompatibleWith(SemVersion clientVersion, SemVersion minServerVersionSupportingClient = null) 82 | { 83 | // client cannot be older than server's min supported version 84 | if (clientVersion < MinClientVersionSupportedByServer) 85 | return false; 86 | 87 | // if we know about this client (client is older than server), it is supported 88 | if (clientVersion <= Version) // if we know about this client (client older than server) 89 | return true; 90 | 91 | // if we don't know about this client (client is newer than server), 92 | // give server a chance to tell client it is, indeed, ok to support it 93 | return minServerVersionSupportingClient != null && minServerVersionSupportingClient <= Version; 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/Our.ModelsBuilder.Web/WebComposer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using Our.ModelsBuilder.Options; 4 | using Our.ModelsBuilder.Umbraco; 5 | using Our.ModelsBuilder.Validation; 6 | using Our.ModelsBuilder.Web.Api; 7 | using Umbraco.Core; 8 | using Umbraco.Core.Composing; 9 | using Umbraco.Web; 10 | using Umbraco.Web.Editors; 11 | using Umbraco.Web.WebApi; 12 | using Embedded = Umbraco.ModelsBuilder.Embedded; 13 | 14 | namespace Our.ModelsBuilder.Web 15 | { 16 | [Disable] 17 | public class NoopComposer : IComposer 18 | { 19 | public void Compose(Composition composition) 20 | { } 21 | } 22 | 23 | public class DisableUmbracoModelsBuilderAttribute : DisableAttribute 24 | { 25 | public DisableUmbracoModelsBuilderAttribute() 26 | { 27 | var field = typeof(DisableAttribute).GetField("k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance); 28 | if (field == null) throw new Exception("panic: cannot get DisableAttribute.DisableType backing field."); 29 | 30 | var type = Type.GetType("Umbraco.ModelsBuilder.Umbraco.ModelsBuilderComposer,Umbraco.ModelsBuilder", false); 31 | field.SetValue(this, type ?? typeof(NoopComposer)); 32 | } 33 | } 34 | 35 | // disable the original MB that shipped with the CMS and ppl may still have 36 | // do not reference Umbraco.ModelsBuilder - the type will be replaced at runtime 37 | // (see DisableUmbracoModelsBuilderAttribute class above) 38 | //[Disable(typeof(global::Umbraco.ModelsBuilder.Umbraco.ModelsBuilderComposer))] 39 | [DisableUmbracoModelsBuilder] 40 | 41 | // disable the embedded MB that ships with the CMS 42 | [Disable(typeof(Embedded.Compose.ModelsBuilderComposer))] 43 | 44 | // after our own Our ModelsBuilderComposer and options composers 45 | [ComposeAfter(typeof(ModelsBuilderComposer))] 46 | [ComposeAfter(typeof(IOptionsComposer))] 47 | 48 | [RuntimeLevel(MinLevel = RuntimeLevel.Run)] 49 | public class WebComposer : ComponentComposer, IUserComposer 50 | { 51 | public override void Compose(Composition composition) 52 | { 53 | base.Compose(composition); 54 | 55 | // remove the embedded dashboard 56 | composition.Dashboards().Remove(); 57 | 58 | // remove the embedded controller 59 | // (embedded code uses features via a component - convoluted 60 | composition.WithCollectionBuilder() 61 | .Remove(); 62 | 63 | // add our manifest, depending on configuration 64 | composition.ManifestFilters().Append(); 65 | 66 | // replaces the model validators 67 | // Core (in WebInitialComposer) registers with: 68 | // 69 | // composition.WithCollectionBuilder() 70 | // .Add(() => composition.TypeLoader.GetTypes()); 71 | // 72 | // so ours are already in there, but better be safe: clear the collection, 73 | // and then add exactly those that we want. 74 | 75 | composition.WithCollectionBuilder() 76 | .Clear(); 77 | 78 | // get the options 79 | // NOTE: after that point, the options are frozen 80 | var options = composition.Configs.GetConfig().ModelsBuilderOptions; 81 | 82 | if (options.EnableBackOffice) 83 | { 84 | composition.WithCollectionBuilder() 85 | .Add() 86 | .Add() 87 | .Add(); 88 | } 89 | 90 | // setup the API if enabled (and in debug mode) 91 | 92 | // the controller is hidden from type finder (to prevent it from being always and 93 | // automatically registered), which means that Umbraco.Web.Composing.CompositionExtensions 94 | // Controllers has *not* registered it into the container, and that it is not part of 95 | // UmbracoApiControllerTypeCollection (and won't get routed etc) 96 | 97 | // so... 98 | // register it in the container 99 | // do NOT add it to the collection - we will route it in the component, our way 100 | // fixme - explain why? 101 | 102 | if (options.IsApiServer) 103 | { 104 | // add the controller to the list of known controllers 105 | //composition.WithCollectionBuilder() 106 | // .Add(); 107 | 108 | // register the controller into the container 109 | composition.Register(typeof(ModelsBuilderApiController), Lifetime.Request); 110 | } 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/Our.ModelsBuilder.Tests/Write/WriteClrTypeTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using NUnit.Framework; 5 | using Our.ModelsBuilder.Building; 6 | using Our.ModelsBuilder.Options; 7 | using Our.ModelsBuilder.Options.ContentTypes; 8 | using Our.ModelsBuilder.Tests.Testing; 9 | 10 | namespace Our.ModelsBuilder.Tests.Write 11 | { 12 | [TestFixture] 13 | public class WriteClrTypeTests : TestsBase 14 | { 15 | [TestCase("int", typeof(int))] 16 | [TestCase("IEnumerable", typeof(IEnumerable))] 17 | [TestCase("Our.ModelsBuilder.Tests.BuilderTestsClass1", typeof(BuilderTestsClass1))] 18 | [TestCase("Our.ModelsBuilder.Tests.Write.WriteClrTypeTests.Class1", typeof(Class1))] 19 | public void WriteClrType(string expected, Type input) 20 | { 21 | var codeModelBuilder = new CodeModelBuilder(new ModelsBuilderOptions(), new CodeOptions(new ContentTypesCodeOptions())); 22 | var codeModel = codeModelBuilder.Build(new CodeModelData()); 23 | codeModel.ModelsNamespace = "ModelsNamespace"; 24 | 25 | var writer = new CodeWriter(codeModel); 26 | 27 | writer.WriteClrType(input); 28 | Assert.AreEqual(expected, writer.Code); 29 | } 30 | 31 | [TestCase("int", typeof(int))] 32 | [TestCase("IEnumerable", typeof(IEnumerable))] 33 | [TestCase("BuilderTestsClass1", typeof(BuilderTestsClass1))] 34 | [TestCase("WriteClrTypeTests.Class1", typeof(Class1))] 35 | public void WriteClrTypeUsing(string expected, Type input) 36 | { 37 | var codeModelBuilder = new CodeModelBuilder(new ModelsBuilderOptions(), new CodeOptions(new ContentTypesCodeOptions())); 38 | var codeModel = codeModelBuilder.Build(new CodeModelData()); 39 | codeModel.ModelsNamespace = "ModelsNamespace"; 40 | codeModel.Using.Add("Our.ModelsBuilder.Tests"); // BuilderTestsClass1 41 | codeModel.Using.Add("Our.ModelsBuilder.Tests.Write"); // WriteClrTypeTests.Class1 42 | 43 | var writer = new CodeWriter(codeModel); 44 | 45 | writer.WriteClrType(input); 46 | Assert.AreEqual(expected, writer.Code); 47 | } 48 | 49 | [TestCase(true, true, "Borked", typeof(global::System.Text.ASCIIEncoding), "System.Text.ASCIIEncoding")] 50 | [TestCase(true, false, "Borked", typeof(global::System.Text.ASCIIEncoding), "ASCIIEncoding")] 51 | [TestCase(false, true, "Borked", typeof(global::System.Text.ASCIIEncoding), "System.Text.ASCIIEncoding")] 52 | [TestCase(false, false, "Borked", typeof(global::System.Text.ASCIIEncoding), "System.Text.ASCIIEncoding")] 53 | 54 | [TestCase(true, true, "Our.ModelsBuilder.Tests", typeof(global::System.Text.ASCIIEncoding), "global::System.Text.ASCIIEncoding")] 55 | [TestCase(true, false, "Our.ModelsBuilder.Tests", typeof(global::System.Text.ASCIIEncoding), "global::System.Text.ASCIIEncoding")] 56 | [TestCase(false, true, "Our.ModelsBuilder.Tests", typeof(global::System.Text.ASCIIEncoding), "global::System.Text.ASCIIEncoding")] 57 | [TestCase(false, false, "Our.ModelsBuilder.Tests", typeof(global::System.Text.ASCIIEncoding), "global::System.Text.ASCIIEncoding")] 58 | 59 | [TestCase(true, true, "Borked", typeof(StringBuilder), "StringBuilder")] 60 | [TestCase(true, false, "Borked", typeof(StringBuilder), "StringBuilder")] 61 | [TestCase(false, true, "Borked", typeof(StringBuilder), "System.Text.StringBuilder")] // magic? using = not ambiguous 62 | [TestCase(false, false, "Borked", typeof(StringBuilder), "System.Text.StringBuilder")] 63 | 64 | [TestCase(true, true, "Our.ModelsBuilder.Tests", typeof(StringBuilder), "StringBuilder")] 65 | [TestCase(true, false, "Our.ModelsBuilder.Tests", typeof(StringBuilder), "StringBuilder")] 66 | [TestCase(false, true, "Our.ModelsBuilder.Tests", typeof(StringBuilder), "global::System.Text.StringBuilder")] // magic? in ns = ambiguous 67 | [TestCase(false, false, "Our.ModelsBuilder.Tests", typeof(StringBuilder), "global::System.Text.StringBuilder")] 68 | public void WriteClrType_Ambiguous_Ns(bool usingSystem, bool usingZb, string ns, Type type, string expected) 69 | { 70 | var codeModel = new CodeModel(new CodeModelData()) { ModelsNamespace = ns }; 71 | if (usingSystem) codeModel.Using.Add("System.Text"); 72 | if (usingZb) codeModel.Using.Add("Our.ModelsBuilder.Tests"); 73 | 74 | var writer = new CodeWriter(codeModel); 75 | 76 | writer.WriteClrType(type); 77 | 78 | Assert.AreEqual(expected, writer.Code); 79 | } 80 | 81 | [Test] 82 | public void WriteClrType_AmbiguousWithNested() 83 | { 84 | var codeModel = new CodeModel(new CodeModelData()) { ModelsNamespace = "SomeRandomNamespace" }; 85 | codeModel.Using.Add("System.Text"); 86 | codeModel.Using.Add("Our.ModelsBuilder.Tests"); 87 | 88 | var writer = new CodeWriter(codeModel); 89 | 90 | writer.WriteClrType(typeof(ASCIIEncoding.Nested)); 91 | 92 | // full type name is needed but not global:: 93 | Assert.AreEqual("Our.ModelsBuilder.Tests.ASCIIEncoding.Nested", writer.Code); 94 | } 95 | 96 | public class Class1 { } 97 | } 98 | } -------------------------------------------------------------------------------- /src/Our.ModelsBuilder/UmbracoExtensions/PublishedElementExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using System.Reflection; 4 | using Our.ModelsBuilder; 5 | using Umbraco.Core.Models.PublishedContent; 6 | 7 | // ReSharper disable once CheckNamespace, reason: extension methods 8 | namespace Umbraco.Web 9 | { 10 | /// 11 | /// Provides extension methods to models. 12 | /// 13 | public static class OurModelsBuilderPublishedElementExtensions // ensure name does not conflicts with Core's class 14 | { 15 | /// 16 | /// Gets the value of a property. 17 | /// 18 | /// The type of the content model. 19 | /// The type of the property value. 20 | /// The content model. 21 | /// The alias of the property. 22 | /// An optional culture. 23 | /// An optional segment. 24 | /// A fallback method. 25 | /// A value for the property. 26 | public static TValue Value(this TModel model, string alias, string culture = null, string segment = null, Func, TValue> fallback = null) 27 | where TModel : IPublishedElement 28 | { 29 | var property = model.GetProperty(alias); 30 | 31 | // if we have a property, and it has a value, return that value 32 | if (property != null && property.HasValue(culture, segment)) 33 | return property.Value(culture, segment); 34 | 35 | // else use the fallback method, if any 36 | if (fallback != default) 37 | return fallback(new FallbackInfos(model, alias, culture, segment)); 38 | 39 | // else... if we have a property, at least let the converter return its own 40 | // vision of 'no value' (could be an empty enumerable) - otherwise, default 41 | return property == null ? default : property.Value(culture, segment); 42 | } 43 | 44 | // note: the method below used to be provided by Core, but then when they embedded MB, they ran into 45 | // collision, and their "fix" consisted in renaming the method "ValueFor" - so we can provide it here. 46 | // see: https://github.com/umbraco/Umbraco-CMS/issues/7469 47 | 48 | /// 49 | /// Gets the value of a property. 50 | /// 51 | /// The type of the model. 52 | /// The type of the property. 53 | /// The model. 54 | /// An expression selecting the property. 55 | /// An optional culture. 56 | /// An optional segment. 57 | /// An optional fallback. 58 | /// /// An optional default value. 59 | /// The value of the property. 60 | public static TValue Value(this TModel model, Expression> property, string culture = null, string segment = null, Fallback fallback = default, TValue defaultValue = default) 61 | where TModel : IPublishedElement 62 | { 63 | var alias = GetAlias(model, property); 64 | return model.Value(alias, culture, segment, fallback, defaultValue); 65 | } 66 | 67 | /// 68 | /// Gets the alias of a property. 69 | /// 70 | /// The type of the model. 71 | /// The type of the property. 72 | /// The model. 73 | /// An expression selecting the property. 74 | /// The alias of the property. 75 | private static string GetAlias(TModel model, Expression> property) 76 | { 77 | if (property.NodeType != ExpressionType.Lambda) 78 | throw new ArgumentException("Not a proper lambda expression (lambda).", nameof(property)); 79 | 80 | var lambda = (LambdaExpression) property; 81 | var lambdaBody = lambda.Body; 82 | 83 | if (lambdaBody.NodeType != ExpressionType.MemberAccess) 84 | throw new ArgumentException("Not a proper lambda expression (body).", nameof(property)); 85 | 86 | var memberExpression = (MemberExpression)lambdaBody; 87 | if (memberExpression.Expression.NodeType != ExpressionType.Parameter) 88 | throw new ArgumentException("Not a proper lambda expression (member).", nameof(property)); 89 | 90 | var member = memberExpression.Member; 91 | 92 | var attribute = member.GetCustomAttribute(); 93 | if (attribute == null) 94 | throw new InvalidOperationException("Property is not marked with ImplementPropertyType attribute."); 95 | 96 | return attribute.PropertyTypeAlias; 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/Our.ModelsBuilder.Tests/Custom/CustomCodeFactory.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using Our.ModelsBuilder.Building; 3 | using Our.ModelsBuilder.Options; 4 | using Our.ModelsBuilder.Options.ContentTypes; 5 | 6 | namespace Our.ModelsBuilder.Tests.Custom 7 | { 8 | public class CustomCodeFactory : ICodeFactory 9 | { 10 | public ICodeModelDataSource CreateCodeModelDataSource() 11 | => new CustomCodeModelDataSource(); 12 | 13 | public CodeOptionsBuilder CreateCodeOptionsBuilder() 14 | => new CustomCodeOptionsBuilder(); 15 | 16 | public ICodeParser CreateCodeParser() 17 | => new CustomCodeParser(); 18 | 19 | public ICodeModelBuilder CreateCodeModelBuilder(ModelsBuilderOptions options, CodeOptions codeOptions) 20 | => new CustomCodeModelBuilderX(options, codeOptions); 21 | 22 | public ICodeWriter CreateCodeWriter(CodeModel model, StringBuilder text = null) 23 | => new CustomCodeWriter(model, text); 24 | } 25 | 26 | public class CustomCodeModelDataSource : ICodeModelDataSource 27 | { 28 | public CodeModelData GetCodeModelData() 29 | => new CodeModelData(); 30 | } 31 | 32 | // custom: support more options 33 | // 34 | public class CustomCodeOptionsBuilder : CodeOptionsBuilder 35 | { 36 | public CustomCodeOptionsBuilder() 37 | : this (new CustomCodeOptions()) 38 | { } 39 | 40 | private CustomCodeOptionsBuilder(CustomCodeOptions codeOptions) 41 | : this(codeOptions, new CustomContentTypesCodeOptionsBuilder(codeOptions.CustomContentTypes)) 42 | { } 43 | 44 | private CustomCodeOptionsBuilder(CustomCodeOptions codeOptions, CustomContentTypesCodeOptionsBuilder contentTypesCodeOptionsBuilder) 45 | : base(codeOptions, contentTypesCodeOptionsBuilder) 46 | { 47 | CustomOptions = codeOptions; 48 | CustomContentTypes = contentTypesCodeOptionsBuilder; 49 | } 50 | 51 | public CustomCodeOptions CustomOptions { get; } 52 | 53 | public CustomContentTypesCodeOptionsBuilder CustomContentTypes { get; } 54 | 55 | // nothing to override 56 | } 57 | 58 | // custom: support more options 59 | // 60 | public class CustomContentTypesCodeOptionsBuilder : ContentTypesCodeOptionsBuilder 61 | { 62 | public CustomContentTypesCodeOptionsBuilder(CustomContentTypesCodeOptions options) 63 | : base(options) 64 | { 65 | CustomOptions = options; 66 | } 67 | 68 | public CustomContentTypesCodeOptions CustomOptions { get; } 69 | 70 | // override! 71 | } 72 | 73 | // custom: support more options 74 | // 75 | public class CustomCodeOptions : CodeOptions 76 | { 77 | public CustomCodeOptions() 78 | : this(new CustomContentTypesCodeOptions()) 79 | { } 80 | 81 | private CustomCodeOptions(CustomContentTypesCodeOptions contentTypesCodeOptions) 82 | : base(contentTypesCodeOptions) 83 | { 84 | CustomContentTypes = contentTypesCodeOptions; 85 | } 86 | 87 | public CustomContentTypesCodeOptions CustomContentTypes { get; } 88 | 89 | // nothing to override 90 | } 91 | 92 | // custom: support more options 93 | // 94 | public class CustomContentTypesCodeOptions : ContentTypesCodeOptions 95 | { 96 | // nothing to override 97 | } 98 | 99 | public class CustomCodeModelBuilderX : CodeModelBuilder // FIXME name? 100 | { 101 | public CustomCodeModelBuilderX(ModelsBuilderOptions options, CodeOptions codeOptions) 102 | : base(options, codeOptions, new CustomContentTypesCodeModelBuilder(options, codeOptions)) 103 | { } 104 | 105 | // building the code model - the options we are getting may be custom options 106 | // override Build 107 | } 108 | 109 | public class CustomContentTypesCodeModelBuilder : ContentTypesCodeModelBuilder 110 | { 111 | public CustomContentTypesCodeModelBuilder(ModelsBuilderOptions options, CodeOptions codeOptions) 112 | : base(options, codeOptions) 113 | { } 114 | 115 | // tons of things we can override when building the code model 116 | 117 | // determines the Clr name of a content type 118 | protected override string GetClrName(ContentTypeModel contentTypeModel) 119 | { 120 | return "PREFIX_" + base.GetClrName(contentTypeModel) + "_SUFFIX"; 121 | } 122 | 123 | // determines the Clr name of a property type 124 | protected override string GetClrName(PropertyTypeModel propertyModel) 125 | { 126 | return "PREFIX_" + base.GetClrName(propertyModel) + "_SUFFIX"; 127 | } 128 | 129 | // the rest is more internal stuff - do we want to override it? 130 | } 131 | 132 | public class CustomCodeParser : CodeParser 133 | { } 134 | 135 | public class CustomCodeWriter : CodeWriter 136 | { 137 | // FIXME the custom types & infos writers should be in ctors too! 138 | 139 | public CustomCodeWriter(CodeModel model, StringBuilder text = null) 140 | : base(model, text) 141 | { } 142 | 143 | // override... 144 | } 145 | 146 | public class CustomContentTypesCodeWriter : ContentTypesCodeWriter 147 | { 148 | public CustomContentTypesCodeWriter(ModelsCodeWriter origin) 149 | : base(origin) 150 | { } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/Our.ModelsBuilder/Options/ModelsBuilderOptions.cs: -------------------------------------------------------------------------------- 1 | using System.Configuration; 2 | using System.Web.Configuration; 3 | using Microsoft.CodeAnalysis.CSharp; 4 | 5 | namespace Our.ModelsBuilder.Options 6 | { 7 | public class ModelsBuilderOptions 8 | { 9 | public class Defaults 10 | { 11 | public const bool Enable = false; 12 | public const bool EnableApi = false; 13 | public const bool EnableBackOffice = true; 14 | public const bool EnableFactory = true; 15 | public const ModelsMode ModelsMode = Options.ModelsMode.Nothing; 16 | public const bool AcceptUnsafeModelsDirectory = false; 17 | public const bool FlagOutOfDateModels = true; 18 | public const int DebugLevel = 0; 19 | public const LanguageVersion LanguageVersion = Microsoft.CodeAnalysis.CSharp.LanguageVersion.CSharp7_3; 20 | public const string ModelsNamespace = "Umbraco.Web.PublishedModels"; 21 | public const string ModelsDirectory = "~/App_Data/Models"; 22 | } 23 | 24 | /// 25 | /// Gets a value indicating whether the whole models experience is enabled. 26 | /// 27 | /// 28 | /// If this is false then absolutely nothing happens. 29 | /// Default value is false which means that unless we have this setting, nothing happens. 30 | /// 31 | public bool Enable { get; set; } = Defaults.Enable; 32 | 33 | /// 34 | /// Gets a value indicating whether to enable the API. 35 | /// 36 | /// 37 | /// Default value is true. 38 | /// The API is used by the Visual Studio extension and the console tool to talk to Umbraco 39 | /// and retrieve the content types. It needs to be enabled so the extension & tool can work. 40 | /// 41 | public bool EnableApi { get; set; } = Defaults.EnableApi; 42 | 43 | /// 44 | /// Gets a value indicating whether back-office integration is enabled. 45 | /// 46 | public bool EnableBackOffice { get; set; } = Defaults.EnableBackOffice; 47 | 48 | /// 49 | /// Gets a value indicating whether we should enable the models factory. 50 | /// 51 | /// Default value is true because no factory is enabled by default in Umbraco. 52 | public bool EnableFactory { get; set; } = Defaults.EnableFactory; 53 | 54 | /// 55 | /// Gets the models mode. 56 | /// 57 | public ModelsMode ModelsMode { get; set; } = Defaults.ModelsMode; 58 | 59 | /// 60 | /// Gets the models namespace. 61 | /// 62 | /// That value could be overriden by other (attribute in user's code...). Return default if no value was supplied. 63 | public string ModelsNamespace { get; set; } = Defaults.ModelsNamespace; 64 | public string AssemblyName { get; set; } = Defaults.ModelsNamespace; 65 | /// 66 | /// Gets the Roslyn parser language version. 67 | /// 68 | public LanguageVersion LanguageVersion { get; set; } = Defaults.LanguageVersion; 69 | 70 | /// 71 | /// Gets a value indicating whether we should flag out-of-date models. 72 | /// 73 | /// Models become out-of-date when data types or content types are updated. When this 74 | /// setting is activated the ~/App_Data/Models/ood.txt file is then created. When models are 75 | /// generated through the dashboard, the files is cleared. Default value is false. 76 | public bool FlagOutOfDateModels { get; set; } = Defaults.FlagOutOfDateModels; 77 | 78 | /// 79 | /// Gets the models directory. 80 | /// 81 | /// Default is ~/App_Data/Models but that can be changed. 82 | public string ModelsDirectory { get; set; } = Defaults.ModelsDirectory; 83 | 84 | /// 85 | /// Gets a value indicating whether to accept an unsafe value for ModelsDirectory. 86 | /// 87 | /// An unsafe value is an absolute path, or a relative path pointing outside 88 | /// of the website root. 89 | public bool AcceptUnsafeModelsDirectory { get; set; } = Defaults.AcceptUnsafeModelsDirectory; 90 | 91 | /// 92 | /// Gets a value indicating the debug log level. 93 | /// 94 | /// 0 means minimal (safe on live site), anything else means more and more details (maybe not safe). 95 | public int DebugLevel { get; set; } = Defaults.DebugLevel; 96 | 97 | /// 98 | /// Gets a value indicating whether to serve the API. 99 | /// 100 | public bool IsApiServer => EnableApi && IsDebug; 101 | 102 | /// 103 | /// Gets a value indicating whether system.web/compilation/@debug is true. 104 | /// 105 | public bool IsDebug 106 | { 107 | get 108 | { 109 | var section = (CompilationSection) ConfigurationManager.GetSection("system.web/compilation"); 110 | return section != null && section.Debug; 111 | } 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/Our.ModelsBuilder/Building/Generator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using Our.ModelsBuilder.Options; 6 | using Our.ModelsBuilder.Umbraco; 7 | 8 | namespace Our.ModelsBuilder.Building 9 | { 10 | public class Generator 11 | { 12 | private readonly ICodeFactory _codeFactory; 13 | private readonly ModelsBuilderOptions _options; 14 | 15 | public Generator(ICodeFactory codeFactory, ModelsBuilderOptions options) 16 | { 17 | _codeFactory = codeFactory; 18 | _options = options; 19 | } 20 | 21 | public void GenerateModels(string modelsDirectory, string modelsNamespace, string bin) 22 | { 23 | if (!Directory.Exists(modelsDirectory)) 24 | Directory.CreateDirectory(modelsDirectory); 25 | 26 | // delete all existing generated files 27 | foreach (var file in Directory.GetFiles(modelsDirectory, "*.generated.cs")) 28 | File.Delete(file); 29 | 30 | // get our (non-generated) files 31 | var files = Directory.GetFiles(modelsDirectory, "*.cs").ToDictionary(x => x, File.ReadAllText); 32 | 33 | var codeModel = CreateModels(modelsNamespace, files, (name, code) => 34 | { 35 | var filename = Path.Combine(modelsDirectory, name + ".generated.cs"); 36 | File.WriteAllText(filename, code); 37 | }); 38 | 39 | // the idea was to calculate the current hash and to add it as an extra file to the compilation, 40 | // in order to be able to detect whether a DLL is consistent with an environment - however the 41 | // environment *might not* contain the local partial files, and thus it could be impossible to 42 | // calculate the hash. So... maybe that's not a good idea after all? 43 | /* 44 | var currentHash = HashHelper.Hash(ourFiles, typeModels); 45 | ourFiles["models.hash.cs"] = $@"using Our.ModelsBuilder; 46 | [assembly:ModelsBuilderAssembly(SourceHash = ""{currentHash}"")] 47 | "; 48 | */ 49 | 50 | if (bin != null) 51 | { 52 | // build 53 | foreach (var file in Directory.GetFiles(modelsDirectory, "*.generated.cs")) 54 | files[file] = File.ReadAllText(file); 55 | var compiler = new Compiler(_options.LanguageVersion); 56 | // FIXME what is the name of the DLL as soon as we accept several namespaces = an option? 57 | compiler.Compile(codeModel.AssemblyName, files, bin); 58 | } 59 | 60 | OutOfDateModelsStatus.Clear(); 61 | } 62 | 63 | public Dictionary GetModels(string modelsNamespace, IDictionary files) 64 | { 65 | var generated = new Dictionary(); 66 | CreateModels(modelsNamespace, files, (name, code) => generated[name] = code); 67 | return generated; 68 | } 69 | 70 | private CodeModel CreateModels(string modelsNamespace, IDictionary sources, Action acceptModel) 71 | { 72 | // get model data from Umbraco, and create a code model (via all the steps) 73 | var modelData = _codeFactory.CreateCodeModelDataSource().GetCodeModelData(); 74 | var codeModel = CreateCodeModel(_codeFactory, sources, modelData, _options, modelsNamespace); 75 | 76 | // create a code writer 77 | var codeWriter = _codeFactory.CreateCodeWriter(codeModel); 78 | 79 | // write each model file 80 | foreach (var contentTypeModel in codeModel.ContentTypes.ContentTypes) 81 | { 82 | codeWriter.Reset(); 83 | codeWriter.WriteModelFile(contentTypeModel); 84 | 85 | // detect name collision 86 | if (contentTypeModel.ClrName == codeModel.ModelInfosClassName) 87 | throw new InvalidOperationException($"Collision, cannot use {codeModel.ModelInfosClassName} for both a content type and the infos class."); 88 | 89 | acceptModel(contentTypeModel.ClrName, codeWriter.Code); 90 | } 91 | 92 | // write the info files 93 | codeWriter.Reset(); 94 | codeWriter.WriteModelInfosFile(); 95 | acceptModel(codeModel.ModelInfosClassName, codeWriter.Code); 96 | 97 | return codeModel; 98 | } 99 | 100 | public static CodeModel CreateCodeModel(ICodeFactory codeFactory, IDictionary sources, CodeModelData modelData, ModelsBuilderOptions options, string modelsNamespace = null) 101 | { 102 | // create an option builder 103 | var optionsBuilder = codeFactory.CreateCodeOptionsBuilder(); 104 | 105 | // create a parser, and parse the (non-generated) files, updating the options builder 106 | var parser = codeFactory.CreateCodeParser(); 107 | parser.Parse(sources, optionsBuilder, ReferencedAssemblies.References); 108 | 109 | // apply namespace - may come from e.g. the Visual Studio extension - FIXME no? 110 | if (!string.IsNullOrWhiteSpace(modelsNamespace)) 111 | optionsBuilder.SetModelsNamespace(modelsNamespace); 112 | 113 | // create a code model builder, and build the code model 114 | var codeModelBuilder = codeFactory.CreateCodeModelBuilder(options, optionsBuilder.CodeOptions); 115 | return codeModelBuilder.Build(modelData); 116 | } 117 | } 118 | } 119 | --------------------------------------------------------------------------------