committing changes, notes, etc... so far

This commit is contained in:
Shannon
2021-01-14 23:14:35 +11:00
parent 2201a5a590
commit 7f1e711481
10 changed files with 369 additions and 97 deletions

View File

@@ -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>

View File

@@ -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>

View File

@@ -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));

View File

@@ -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; }
}
}

View File

@@ -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")));
}
}
}

View File

@@ -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)

View File

@@ -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);

View File

@@ -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>

View File

@@ -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;

View File

@@ -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>();