committing changes, notes, etc... so far
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
namespace Umbraco.Core.Models.PublishedContent
|
||||
namespace Umbraco.Core.Models.PublishedContent
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
@@ -11,15 +11,6 @@
|
||||
/// </summary>
|
||||
object SyncRoot { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes the factory.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>This will typically re-compiled models/classes into a new DLL that are used to populate the cache.</para>
|
||||
/// <para>This is called prior to refreshing the cache.</para>
|
||||
/// </remarks>
|
||||
void Refresh();
|
||||
|
||||
/// <summary>
|
||||
/// Tells the factory that it should build a new generation of models
|
||||
/// </summary>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
@@ -13,48 +13,25 @@ namespace Umbraco.Core
|
||||
/// <summary>
|
||||
/// Returns true if the current <see cref="IPublishedModelFactory"/> is an implementation of <see cref="ILivePublishedModelFactory"/>
|
||||
/// </summary>
|
||||
/// <param name="factory"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsLiveFactory(this IPublishedModelFactory factory) => factory is ILivePublishedModelFactory;
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the current <see cref="IPublishedModelFactory"/> is an implementation of <see cref="ILivePublishedModelFactory2"/> and is enabled
|
||||
/// </summary>
|
||||
/// <param name="factory"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsLiveFactoryEnabled(this IPublishedModelFactory factory)
|
||||
{
|
||||
if (factory is ILivePublishedModelFactory liveFactory)
|
||||
{
|
||||
return liveFactory.Enabled;
|
||||
}
|
||||
|
||||
// if it's not ILivePublishedModelFactory we can't determine if it's enabled or not so return true
|
||||
return true;
|
||||
}
|
||||
|
||||
[Obsolete("This method is no longer used or necessary and will be removed from future")]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public static void WithSafeLiveFactory(this IPublishedModelFactory factory, Action action)
|
||||
{
|
||||
if (factory is ILivePublishedModelFactory liveFactory)
|
||||
{
|
||||
lock (liveFactory.SyncRoot)
|
||||
{
|
||||
//Call refresh on the live factory to re-compile the models
|
||||
liveFactory.Refresh();
|
||||
action();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
action();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a flag to reset the ModelsBuilder models if the <see cref="IPublishedModelFactory"/> is <see cref="ILivePublishedModelFactory"/>
|
||||
/// </summary>
|
||||
/// <param name="factory"></param>
|
||||
/// <param name="action"></param>
|
||||
/// <remarks>
|
||||
/// This does not recompile the pure live models, only sets a flag to tell models builder to recompile when they are requested.
|
||||
/// </remarks>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
@@ -54,6 +54,7 @@ namespace Umbraco.Core.Runtime
|
||||
|
||||
#endregion
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Acquire(IApplicationShutdownRegistry hostingEnvironment)
|
||||
{
|
||||
_hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment));
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Extensions;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Core.Composing;
|
||||
@@ -22,6 +26,7 @@ namespace Umbraco.ModelsBuilder.Embedded.DependencyInjection
|
||||
/// </summary>
|
||||
public static IUmbracoBuilder AddModelsBuilder(this IUmbracoBuilder builder)
|
||||
{
|
||||
builder.AddRazorProjectEngine();
|
||||
builder.Services.AddSingleton<UmbracoServices>();
|
||||
builder.Services.AddSingleton<ModelsBuilderNotificationHandler>();
|
||||
builder.Services.AddUnique<ModelsGenerator>();
|
||||
@@ -77,5 +82,169 @@ namespace Umbraco.ModelsBuilder.Embedded.DependencyInjection
|
||||
builder.Services.AddSingleton<DisableModelsBuilderNotificationHandler>();
|
||||
return builder;
|
||||
}
|
||||
|
||||
private static IUmbracoBuilder AddRazorProjectEngine(this IUmbracoBuilder builder)
|
||||
{
|
||||
// TODO: This is super nasty, but we can at least tinker with this for now
|
||||
// this pre-builds the container just so we can extract the default RazorProjectEngine
|
||||
// in order to extract out all features and re-add them to ours
|
||||
|
||||
// Since we cannot construct the razor engine like netcore does:
|
||||
// https://github.com/dotnet/aspnetcore/blob/336e05577cd8bec2000ffcada926189199e4cef0/src/Mvc/Mvc.Razor.RuntimeCompilation/src/DependencyInjection/RazorRuntimeCompilationMvcCoreBuilderExtensions.cs#L86
|
||||
// because many things are internal we need to resort to this which is to get the default RazorProjectEngine
|
||||
// that is nornally created and use that to create our custom one while ensuring all of the razor features
|
||||
// that we can't really add ourselves are there.
|
||||
// Pretty much all methods, even thnigs like SetCSharpLanguageVersion are actually adding razor features.
|
||||
|
||||
//var internalServicesBuilder = new ServiceCollection();
|
||||
//internalServicesBuilder.AddControllersWithViews().AddRazorRuntimeCompilation();
|
||||
//var internalServices = internalServicesBuilder.BuildServiceProvider();
|
||||
//var defaultRazorProjectEngine = internalServices.GetRequiredService<RazorProjectEngine>();
|
||||
|
||||
ServiceProvider internalServices = builder.Services.BuildServiceProvider();
|
||||
RazorProjectEngine defaultRazorProjectEngine = internalServices.GetRequiredService<RazorProjectEngine>();
|
||||
|
||||
builder.Services.AddSingleton(s =>
|
||||
{
|
||||
RazorProjectFileSystem fileSystem = s.GetRequiredService<RazorProjectFileSystem>();
|
||||
|
||||
// Create the project engine
|
||||
var projectEngine = RazorProjectEngine.Create(RazorConfiguration.Default, fileSystem, builder =>
|
||||
{
|
||||
// replace all features with the defaults
|
||||
builder.Features.Clear();
|
||||
|
||||
foreach (IRazorEngineFeature f in defaultRazorProjectEngine.EngineFeatures)
|
||||
{
|
||||
builder.Features.Add(f);
|
||||
}
|
||||
|
||||
foreach (IRazorProjectEngineFeature f in defaultRazorProjectEngine.ProjectFeatures)
|
||||
{
|
||||
builder.Features.Add(f);
|
||||
}
|
||||
|
||||
// The razor engine only supports one instance of IMetadataReferenceFeature
|
||||
// so we need to jump through some hoops to allow multiple by using a wrapper.
|
||||
// so get the current ones, remove them from the list, create a wrapper of them and
|
||||
// our custom one and then add it back.
|
||||
var metadataReferenceFeatures = builder.Features.OfType<IMetadataReferenceFeature>().ToList();
|
||||
foreach (IMetadataReferenceFeature m in metadataReferenceFeatures)
|
||||
{
|
||||
builder.Features.Remove(m);
|
||||
}
|
||||
|
||||
// add our custom one to the list
|
||||
metadataReferenceFeatures.Add(new PureLiveMetadataReferenceFeature(s.GetRequiredService<PureLiveModelFactory>()));
|
||||
|
||||
// now add them to our wrapper and back into the features
|
||||
builder.Features.Add(new MetadataReferenceFeatureWrapper(metadataReferenceFeatures));
|
||||
|
||||
//RazorExtensions.Register(builder);
|
||||
|
||||
//// Roslyn + TagHelpers infrastructure
|
||||
//// TODO: These are internal...
|
||||
//var referenceManager = s.GetRequiredService<RazorReferenceManager>();
|
||||
//builder.Features.Add(new LazyMetadataReferenceFeature(referenceManager));
|
||||
|
||||
//builder.Features.Add(new CompilationTagHelperFeature());
|
||||
|
||||
//// TagHelperDescriptorProviders (actually do tag helper discovery)
|
||||
//builder.Features.Add(new DefaultTagHelperDescriptorProvider());
|
||||
//builder.Features.Add(new ViewComponentTagHelperDescriptorProvider());
|
||||
//builder.SetCSharpLanguageVersion(csharpCompiler.ParseOptions.LanguageVersion);
|
||||
});
|
||||
|
||||
return projectEngine;
|
||||
});
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wraps multiple <see cref="IMetadataReferenceFeature"/>
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This is required because the razor engine only supports a single IMetadataReferenceFeature but their APIs don't state this,
|
||||
/// this is purely down to them doing this 'First' call: https://github.com/dotnet/aspnetcore/blob/b795ac3546eb3e2f47a01a64feb3020794ca33bb/src/Razor/Microsoft.CodeAnalysis.Razor/src/CompilationTagHelperFeature.cs#L37
|
||||
/// So in order to have multiple, we need to have a wrapper.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public class MetadataReferenceFeatureWrapper : IMetadataReferenceFeature
|
||||
{
|
||||
private readonly IReadOnlyList<IMetadataReferenceFeature> _metadataReferenceFeatures;
|
||||
private RazorEngine _engine;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MetadataReferenceFeatureWrapper"/> class.
|
||||
/// </summary>
|
||||
public MetadataReferenceFeatureWrapper(IEnumerable<IMetadataReferenceFeature> metadataReferenceFeatures)
|
||||
=> _metadataReferenceFeatures = metadataReferenceFeatures.ToList();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IReadOnlyList<MetadataReference> References
|
||||
=> _metadataReferenceFeatures.SelectMany(x => x.References).ToList();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public RazorEngine Engine
|
||||
{
|
||||
get => _engine;
|
||||
set
|
||||
{
|
||||
_engine = value;
|
||||
foreach (IMetadataReferenceFeature feature in _metadataReferenceFeatures)
|
||||
{
|
||||
feature.Engine = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A custom <see cref="IMetadataReferenceFeature"/> that will dynamically resolve a reference for razor based on the current PureLive assembly.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The default implementation of IMetadataReferenceFeature is https://github.com/dotnet/aspnetcore/blob/master/src/Mvc/Mvc.Razor.RuntimeCompilation/src/LazyMetadataReferenceFeature.cs
|
||||
/// which uses a ReferenceManager https://github.com/dotnet/aspnetcore/blob/master/src/Mvc/Mvc.Razor.RuntimeCompilation/src/RazorReferenceManager.cs
|
||||
/// to resolve it's references. This is done using ApplicationParts which would be nice and simple to use if we could, but the Razor engine ONLY works
|
||||
/// with application part assemblies that have physical files with physical paths. We don't want to load in our PureLive assemblies on physical paths because
|
||||
/// those files will be locked. Instead we load them in via bytes but this is not supported and we'll get an exception if we add them to application parts.
|
||||
/// The other problem with LazyMetadataReferenceFeature is that it doesn't support dynamic assemblies, it will just check what in application parts once and
|
||||
/// that's it which will not work for us in Pure Live.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
internal class PureLiveMetadataReferenceFeature : IMetadataReferenceFeature
|
||||
{
|
||||
// TODO: Even though I was hoping this would work and this does allow you to return a metadata reference dynamically at runtime, it doesn't make any
|
||||
// difference because the CSharpCompiler for razor only loads in it's references one time based on the initial reference checks:
|
||||
// https://github.com/dotnet/aspnetcore/blob/100ab02ea0214d49535fa56f33a77acd61fe039c/src/Mvc/Mvc.Razor.RuntimeCompilation/src/CSharpCompiler.cs#L84
|
||||
// Since ReferenceManager resolves them once lazily and that's it.
|
||||
|
||||
private readonly PureLiveModelFactory _pureLiveModelFactory;
|
||||
|
||||
public PureLiveMetadataReferenceFeature(PureLiveModelFactory pureLiveModelFactory) => _pureLiveModelFactory = pureLiveModelFactory;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IReadOnlyList<MetadataReference> References
|
||||
{
|
||||
get
|
||||
{
|
||||
// TODO: This won't really work based on how the CSharp compiler works
|
||||
//if (_pureLiveModelFactory.CurrentModelsMetadataReference != null)
|
||||
//{
|
||||
// //var reference = MetadataReference.CreateFromStream(null);
|
||||
// //reference.
|
||||
// return new[] { _pureLiveModelFactory.CurrentModelsMetadataReference };
|
||||
//}
|
||||
|
||||
return Array.Empty<MetadataReference>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public RazorEngine Engine { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Configuration.Models;
|
||||
using Umbraco.Core.Hosting;
|
||||
|
||||
namespace Umbraco.ModelsBuilder.Embedded
|
||||
{
|
||||
internal class NonRecursivePhysicalFileProvider : PhysicalFileProvider, IFileProvider
|
||||
{
|
||||
private static readonly char[] s_pathSeparators = new char[2]
|
||||
{
|
||||
Path.DirectorySeparatorChar,
|
||||
Path.AltDirectorySeparatorChar
|
||||
};
|
||||
|
||||
public NonRecursivePhysicalFileProvider(string root)
|
||||
: base(root)
|
||||
{
|
||||
}
|
||||
|
||||
IDirectoryContents IFileProvider.GetDirectoryContents(string subpath) => IsRoot(subpath) ? GetDirectoryContents(subpath) : null;
|
||||
|
||||
IFileInfo IFileProvider.GetFileInfo(string subpath) => IsRoot(subpath) ? GetFileInfo(subpath) : null;
|
||||
|
||||
IChangeToken IFileProvider.Watch(string filter) => IsRoot(filter) ? Watch(filter) : NullChangeToken.Singleton;
|
||||
|
||||
private bool IsRoot(string path) => !s_pathSeparators.Any(x => path.Contains(x));
|
||||
}
|
||||
|
||||
public class ModelsBuilderRazorRuntimeCompilationOptions : IConfigureOptions<MvcRazorRuntimeCompilationOptions>
|
||||
{
|
||||
private readonly ModelsBuilderSettings _config;
|
||||
private readonly IHostingEnvironment _hostingEnvironment;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ModelsBuilderRazorRuntimeCompilationOptions"/> class.
|
||||
/// </summary>
|
||||
public ModelsBuilderRazorRuntimeCompilationOptions(
|
||||
IOptions<ModelsBuilderSettings> config,
|
||||
IHostingEnvironment hostingEnvironment)
|
||||
{
|
||||
_config = config.Value;
|
||||
_hostingEnvironment = hostingEnvironment;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Configure(MvcRazorRuntimeCompilationOptions options)
|
||||
{
|
||||
//RazorProjectEngine.Create()
|
||||
|
||||
// TODO: Not sure this is going to be possible :/
|
||||
// See https://stackoverflow.com/questions/58685966/adding-assemblies-types-to-be-made-available-to-razor-page-at-runtime
|
||||
// See https://github.com/dotnet/aspnetcore/blob/master/src/Mvc/Mvc.Razor.RuntimeCompilation/src/MvcRazorRuntimeCompilationOptions.cs
|
||||
// See https://github.com/dotnet/aspnetcore/blob/b795ac3546eb3e2f47a01a64feb3020794ca33bb/src/Mvc/Mvc.Razor.RuntimeCompilation/src/RazorReferenceManager.cs
|
||||
// See https://github.com/dotnet/aspnetcore/blob/114f0f6d1ef1d777fb93d90c87ac506027c55ea0/src/Mvc/Mvc.Razor.RuntimeCompilation/src/RuntimeViewCompiler.cs#L26
|
||||
|
||||
// This is where the RazorProjectEngine gets created
|
||||
// https://github.com/dotnet/aspnetcore/blob/336e05577cd8bec2000ffcada926189199e4cef0/src/Mvc/Mvc.Razor.RuntimeCompilation/src/DependencyInjection/RazorRuntimeCompilationMvcCoreBuilderExtensions.cs#L86
|
||||
// In theory, it seems like
|
||||
|
||||
//MetadataReference ref;
|
||||
//var modelsDirectory = _config.ModelsDirectoryAbsolute(_hostingEnvironment);
|
||||
|
||||
// From what I can tell, we can specify a file provider here for all razor files
|
||||
// that need to be watched which will recompiled when they are changed.
|
||||
|
||||
// TODO: Should be constants and or shared with our RazorViewEngineOptions classes
|
||||
options.FileProviders.Add(new NonRecursivePhysicalFileProvider(_hostingEnvironment.MapPathContentRoot("~/Views")));
|
||||
options.FileProviders.Add(new PhysicalFileProvider(_hostingEnvironment.MapPathContentRoot("~/Views/Partials")));
|
||||
options.FileProviders.Add(new PhysicalFileProvider(_hostingEnvironment.MapPathContentRoot("~/Views/MacroPartials")));
|
||||
options.FileProviders.Add(new PhysicalFileProvider(_hostingEnvironment.MapPathContentRoot("~/App_Plugins")));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,9 +5,13 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using System.Reflection.PortableExecutable;
|
||||
using System.Runtime.Loader;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationParts;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Core;
|
||||
@@ -34,7 +38,7 @@ namespace Umbraco.ModelsBuilder.Embedded
|
||||
private int _skipver;
|
||||
private readonly int _debugLevel;
|
||||
private RoslynCompiler _roslynCompiler;
|
||||
private UmbracoAssemblyLoadContext _currentAssemblyLoadContext;
|
||||
//private UmbracoAssemblyLoadContext _currentAssemblyLoadContext;
|
||||
private readonly Lazy<UmbracoServices> _umbracoServices; // fixme: this is because of circular refs :(
|
||||
private static readonly Regex s_assemblyVersionRegex = new Regex("AssemblyVersion\\(\"[0-9]+.[0-9]+.[0-9]+.[0-9]+\"\\)", RegexOptions.Compiled);
|
||||
private static readonly string[] s_ourFiles = { "models.hash", "models.generated.cs", "all.generated.cs", "all.dll.path", "models.err", "Compiled" };
|
||||
@@ -43,6 +47,7 @@ namespace Umbraco.ModelsBuilder.Embedded
|
||||
private readonly IApplicationShutdownRegistry _hostingLifetime;
|
||||
private readonly ModelsGenerationError _errors;
|
||||
private readonly IPublishedValueFallback _publishedValueFallback;
|
||||
private readonly ApplicationPartManager _applicationPartManager;
|
||||
private static readonly Regex s_usingRegex = new Regex("^using(.*);", RegexOptions.Compiled | RegexOptions.Multiline);
|
||||
private static readonly Regex s_aattrRegex = new Regex("^\\[assembly:(.*)\\]", RegexOptions.Compiled | RegexOptions.Multiline);
|
||||
|
||||
@@ -53,7 +58,8 @@ namespace Umbraco.ModelsBuilder.Embedded
|
||||
IOptions<ModelsBuilderSettings> config,
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
IApplicationShutdownRegistry hostingLifetime,
|
||||
IPublishedValueFallback publishedValueFallback)
|
||||
IPublishedValueFallback publishedValueFallback,
|
||||
ApplicationPartManager applicationPartManager)
|
||||
{
|
||||
_umbracoServices = umbracoServices;
|
||||
_profilingLogger = profilingLogger;
|
||||
@@ -62,6 +68,7 @@ namespace Umbraco.ModelsBuilder.Embedded
|
||||
_hostingEnvironment = hostingEnvironment;
|
||||
_hostingLifetime = hostingLifetime;
|
||||
_publishedValueFallback = publishedValueFallback;
|
||||
_applicationPartManager = applicationPartManager;
|
||||
_errors = new ModelsGenerationError(config, _hostingEnvironment);
|
||||
_ver = 1; // zero is for when we had no version
|
||||
_skipver = -1; // nothing to skip
|
||||
@@ -90,10 +97,22 @@ namespace Umbraco.ModelsBuilder.Embedded
|
||||
|
||||
private UmbracoServices UmbracoServices => _umbracoServices.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the currently loaded pure live models assembly
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Can be null
|
||||
/// </remarks>
|
||||
public Assembly CurrentModelsAssembly { get; private set; }
|
||||
|
||||
public MetadataReference CurrentModelsMetadataReference { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public object SyncRoot { get; } = new object();
|
||||
|
||||
// gets the RoslynCompiler
|
||||
/// <summary>
|
||||
/// Gets the RoslynCompiler
|
||||
/// </summary>
|
||||
private RoslynCompiler RoslynCompiler
|
||||
{
|
||||
get
|
||||
@@ -111,13 +130,6 @@ namespace Umbraco.ModelsBuilder.Embedded
|
||||
/// <inheritdoc />
|
||||
public bool Enabled => _config.Enable;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Refresh()
|
||||
{
|
||||
ResetModels();
|
||||
EnsureModels();
|
||||
}
|
||||
|
||||
public IPublishedElement CreateModel(IPublishedElement element)
|
||||
{
|
||||
// get models, rebuilding them if needed
|
||||
@@ -263,6 +275,9 @@ namespace Umbraco.ModelsBuilder.Embedded
|
||||
var modelsHashFile = Path.Combine(modelsDirectory, "models.hash");
|
||||
var dllPathFile = Path.Combine(modelsDirectory, "all.dll.path");
|
||||
|
||||
// TODO: Remove the old application part
|
||||
var parts = _applicationPartManager.ApplicationParts;
|
||||
|
||||
if (File.Exists(dllPathFile))
|
||||
{
|
||||
File.Delete(dllPathFile);
|
||||
@@ -307,15 +322,8 @@ namespace Umbraco.ModelsBuilder.Embedded
|
||||
}
|
||||
}
|
||||
|
||||
var roslynLocked = false;
|
||||
try
|
||||
{
|
||||
// TODO: DO NOT LOCK ON RoslynCompiler
|
||||
|
||||
// always take the BuildManager lock *before* taking the _locker lock
|
||||
// to avoid possible deadlock situations (see notes above)
|
||||
Monitor.Enter(RoslynCompiler, ref roslynLocked);
|
||||
|
||||
_locker.EnterUpgradeableReadLock();
|
||||
|
||||
if (_hasModels)
|
||||
@@ -328,17 +336,18 @@ namespace Umbraco.ModelsBuilder.Embedded
|
||||
// we don't have models,
|
||||
// either they haven't been loaded from the cache yet
|
||||
// or they have been reseted and are pending a rebuild
|
||||
|
||||
using (_profilingLogger.DebugDuration<PureLiveModelFactory>("Get models.", "Got models."))
|
||||
{
|
||||
try
|
||||
{
|
||||
Assembly assembly = GetModelsAssembly(_pendingRebuild);
|
||||
// TODO: We may have to copy this to a temp place?
|
||||
// string assemblyName = Path.GetRandomFileName();
|
||||
|
||||
Assembly assembly = GetModelsAssembly(_pendingRebuild, out MetadataReference metadataReference);
|
||||
|
||||
CurrentModelsAssembly = assembly;
|
||||
CurrentModelsMetadataReference = metadataReference;
|
||||
|
||||
// the one below can be used to simulate an issue with BuildManager, ie it will register
|
||||
// the models with the factory but NOT with the BuildManager, which will not recompile views.
|
||||
// this is for U4-8043 which is an obvious issue but I cannot replicate
|
||||
// _modelsAssembly = _modelsAssembly ?? assembly;
|
||||
IEnumerable<Type> types = assembly.ExportedTypes.Where(x => x.Inherits<PublishedContentModel>() || x.Inherits<PublishedElementModel>());
|
||||
_infos = RegisterModels(types);
|
||||
_errors.Clear();
|
||||
@@ -353,6 +362,7 @@ namespace Umbraco.ModelsBuilder.Embedded
|
||||
}
|
||||
finally
|
||||
{
|
||||
CurrentModelsAssembly = null;
|
||||
_infos = new Infos { ModelInfos = null, ModelTypeMap = new Dictionary<string, Type>() };
|
||||
}
|
||||
}
|
||||
@@ -374,38 +384,71 @@ namespace Umbraco.ModelsBuilder.Embedded
|
||||
{
|
||||
_locker.ExitUpgradeableReadLock();
|
||||
}
|
||||
|
||||
if (roslynLocked)
|
||||
{
|
||||
Monitor.Exit(RoslynCompiler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Assembly ReloadAssembly(string pathToAssembly)
|
||||
private static MetadataReference CreateMetadataReference(string path)
|
||||
{
|
||||
// If there's a current AssemblyLoadContext, unload it before creating a new one.
|
||||
if (!(_currentAssemblyLoadContext is null))
|
||||
using (FileStream stream = File.OpenRead(path))
|
||||
{
|
||||
_currentAssemblyLoadContext.Unload();
|
||||
GC.Collect();
|
||||
GC.WaitForPendingFinalizers();
|
||||
}
|
||||
var moduleMetadata = ModuleMetadata.CreateFromStream(stream, PEStreamOptions.PrefetchMetadata);
|
||||
var assemblyMetadata = AssemblyMetadata.Create(moduleMetadata);
|
||||
|
||||
// We must create a new assembly load context
|
||||
// as long as theres a reference to the assembly load context we can't delete the assembly it loaded
|
||||
_currentAssemblyLoadContext = new UmbracoAssemblyLoadContext();
|
||||
|
||||
// Use filestream to load in the new assembly, otherwise it'll be locked
|
||||
// See https://www.strathweb.com/2019/01/collectible-assemblies-in-net-core-3-0/ for more info
|
||||
using (var fs = new FileStream(pathToAssembly, FileMode.Open, FileAccess.Read))
|
||||
{
|
||||
return _currentAssemblyLoadContext.LoadFromStream(fs);
|
||||
return assemblyMetadata.GetReference(filePath: path);
|
||||
}
|
||||
}
|
||||
|
||||
private Assembly GetModelsAssembly(bool forceRebuild)
|
||||
private Assembly ReloadAssembly(string pathToAssembly, out MetadataReference metadataReference)
|
||||
{
|
||||
// TODO: We should look into removing the old application part
|
||||
|
||||
//// If there's a current AssemblyLoadContext, unload it before creating a new one.
|
||||
//if (!(_currentAssemblyLoadContext is null))
|
||||
//{
|
||||
// _currentAssemblyLoadContext.Unload();
|
||||
// GC.Collect();
|
||||
// GC.WaitForPendingFinalizers();
|
||||
//}
|
||||
|
||||
//// We must create a new assembly load context
|
||||
//// as long as theres a reference to the assembly load context we can't delete the assembly it loaded
|
||||
//_currentAssemblyLoadContext = new UmbracoAssemblyLoadContext();
|
||||
|
||||
// Need to work on a temp file so that it's not locked
|
||||
var tempFile = Path.GetTempFileName();
|
||||
File.Copy(pathToAssembly, tempFile, true);
|
||||
// Load it in
|
||||
Assembly assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(tempFile);
|
||||
// Create a metadata ref, TODO: This is actually not required and doesn't really work so we can remove that
|
||||
metadataReference = CreateMetadataReference(tempFile);
|
||||
|
||||
// Add the assembly to the application parts - this is required because this is how
|
||||
// the razor ReferenceManager resolves what to load, see
|
||||
// https://github.com/dotnet/aspnetcore/blob/master/src/Mvc/Mvc.Razor.RuntimeCompilation/src/RazorReferenceManager.cs#L53
|
||||
var partFactory = ApplicationPartFactory.GetApplicationPartFactory(assembly);
|
||||
foreach (ApplicationPart applicationPart in partFactory.GetApplicationParts(assembly))
|
||||
{
|
||||
_applicationPartManager.ApplicationParts.Add(applicationPart);
|
||||
}
|
||||
|
||||
return assembly;
|
||||
|
||||
//// Use filestream to load in the new assembly, otherwise it'll be locked
|
||||
//// See https://www.strathweb.com/2019/01/collectible-assemblies-in-net-core-3-0/ for more info
|
||||
//using (var fs = new FileStream(pathToAssembly, FileMode.Open, FileAccess.Read))
|
||||
//{
|
||||
// Assembly assembly = _currentAssemblyLoadContext.LoadFromStream(fs);
|
||||
// //fs.Position = 0;
|
||||
// //metadataReference = MetadataReference.CreateFromStream(fs, filePath: null);
|
||||
// //metadataReference = CreateMetadataReference(fs, pathToAssembly);
|
||||
// return assembly;
|
||||
//}
|
||||
}
|
||||
|
||||
private Assembly GetModelsAssembly(bool forceRebuild, out MetadataReference metadataReference)
|
||||
{
|
||||
metadataReference = null;
|
||||
|
||||
var modelsDirectory = _config.ModelsDirectoryAbsolute(_hostingEnvironment);
|
||||
if (!Directory.Exists(modelsDirectory))
|
||||
{
|
||||
@@ -458,7 +501,7 @@ namespace Umbraco.ModelsBuilder.Embedded
|
||||
|
||||
if (File.Exists(dllPath) && !File.Exists(dllPath + ".delete"))
|
||||
{
|
||||
assembly = ReloadAssembly(dllPath);
|
||||
assembly = ReloadAssembly(dllPath, out MetadataReference mdr);
|
||||
|
||||
ModelsBuilderAssemblyAttribute attr = assembly.GetCustomAttribute<ModelsBuilderAssemblyAttribute>();
|
||||
if (attr != null && attr.PureLive && attr.SourceHash == currentHash)
|
||||
@@ -470,6 +513,7 @@ namespace Umbraco.ModelsBuilder.Embedded
|
||||
_skipver = assembly.GetName().Version.Revision;
|
||||
|
||||
_logger.LogDebug("Loading cached models (dll).");
|
||||
metadataReference = mdr;
|
||||
return assembly;
|
||||
}
|
||||
|
||||
@@ -504,7 +548,7 @@ namespace Umbraco.ModelsBuilder.Embedded
|
||||
{
|
||||
var assemblyPath = GetOutputAssemblyPath(currentHash);
|
||||
RoslynCompiler.CompileToFile(projFile, assemblyPath);
|
||||
assembly = ReloadAssembly(assemblyPath);
|
||||
assembly = ReloadAssembly(assemblyPath, out metadataReference);
|
||||
File.WriteAllText(dllPathFile, assembly.Location);
|
||||
File.WriteAllText(modelsHashFile, currentHash);
|
||||
TryDeleteUnusedAssemblies(dllPathFile);
|
||||
@@ -547,7 +591,7 @@ namespace Umbraco.ModelsBuilder.Embedded
|
||||
{
|
||||
var assemblyPath = GetOutputAssemblyPath(currentHash);
|
||||
RoslynCompiler.CompileToFile(projFile, assemblyPath);
|
||||
assembly = ReloadAssembly(assemblyPath);
|
||||
assembly = ReloadAssembly(assemblyPath, out metadataReference);
|
||||
File.WriteAllText(dllPathFile, assemblyPath);
|
||||
File.WriteAllText(modelsHashFile, currentHash);
|
||||
TryDeleteUnusedAssemblies(dllPathFile);
|
||||
@@ -778,12 +822,16 @@ namespace Umbraco.ModelsBuilder.Embedded
|
||||
|
||||
// always ignore our own file changes
|
||||
if (s_ourFiles.Contains(changed))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.LogInformation("Detected files changes.");
|
||||
|
||||
lock (SyncRoot) // don't reset while being locked
|
||||
{
|
||||
ResetModels();
|
||||
}
|
||||
}
|
||||
|
||||
public void Stop(bool immediate)
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
|
||||
namespace Umbraco.ModelsBuilder.Embedded
|
||||
{
|
||||
@@ -28,7 +28,7 @@ namespace Umbraco.ModelsBuilder.Embedded
|
||||
// Making it kind of a waste to convert the Assembly types into MetadataReference
|
||||
// every time GetCompiledAssembly is called, so that's why I do it in the ctor
|
||||
_refs = new List<MetadataReference>();
|
||||
foreach(var assembly in referenceAssemblies.Where(x => !x.IsDynamic && !string.IsNullOrWhiteSpace(x.Location)).Distinct())
|
||||
foreach (var assembly in referenceAssemblies.Where(x => !x.IsDynamic && !string.IsNullOrWhiteSpace(x.Location)).Distinct())
|
||||
{
|
||||
_refs.Add(MetadataReference.CreateFromFile(assembly.Location));
|
||||
};
|
||||
@@ -54,13 +54,15 @@ namespace Umbraco.ModelsBuilder.Embedded
|
||||
|
||||
var syntaxTree = SyntaxFactory.ParseSyntaxTree(sourceText, _parseOptions);
|
||||
|
||||
var compilation = CSharpCompilation.Create("ModelsGeneratedAssembly",
|
||||
var compilation = CSharpCompilation.Create(
|
||||
"ModelsGeneratedAssembly",
|
||||
new[] { syntaxTree },
|
||||
references: _refs,
|
||||
options: new CSharpCompilationOptions(_outputKind,
|
||||
optimizationLevel: OptimizationLevel.Release,
|
||||
// Not entirely certain that assemblyIdentityComparer is nececary?
|
||||
assemblyIdentityComparer: DesktopAssemblyIdentityComparer.Default));
|
||||
options: new CSharpCompilationOptions(
|
||||
_outputKind,
|
||||
optimizationLevel: OptimizationLevel.Release,
|
||||
// Not entirely certain that assemblyIdentityComparer is nececary?
|
||||
assemblyIdentityComparer: DesktopAssemblyIdentityComparer.Default));
|
||||
|
||||
compilation.Emit(savePath);
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="3.1.8" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.7.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core;
|
||||
@@ -11,6 +11,7 @@ using Umbraco.ModelsBuilder.Embedded.Building;
|
||||
|
||||
namespace Umbraco.ModelsBuilder.Embedded
|
||||
{
|
||||
|
||||
public sealed class UmbracoServices
|
||||
{
|
||||
private readonly IContentTypeService _contentTypeService;
|
||||
|
||||
@@ -28,8 +28,8 @@ namespace Umbraco.Web.Website.DependencyInjection
|
||||
.Add(builder.TypeLoader.GetSurfaceControllers());
|
||||
|
||||
// Configure MVC startup options for custom view locations
|
||||
builder.Services.AddTransient<IConfigureOptions<RazorViewEngineOptions>, RenderRazorViewEngineOptionsSetup>();
|
||||
builder.Services.AddTransient<IConfigureOptions<RazorViewEngineOptions>, PluginRazorViewEngineOptionsSetup>();
|
||||
builder.Services.ConfigureOptions<RenderRazorViewEngineOptionsSetup>();
|
||||
builder.Services.ConfigureOptions<PluginRazorViewEngineOptionsSetup>();
|
||||
|
||||
// Wraps all existing view engines in a ProfilerViewEngine
|
||||
builder.Services.AddTransient<IConfigureOptions<MvcViewOptions>, ProfilingViewEngineWrapperMvcViewOptionsSetup>();
|
||||
|
||||
Reference in New Issue
Block a user