diff --git a/src/Umbraco.Core/Models/PublishedContent/ILivePublishedModelFactory.cs b/src/Umbraco.Core/Models/PublishedContent/ILivePublishedModelFactory.cs
index 913a2311a4..091893fb72 100644
--- a/src/Umbraco.Core/Models/PublishedContent/ILivePublishedModelFactory.cs
+++ b/src/Umbraco.Core/Models/PublishedContent/ILivePublishedModelFactory.cs
@@ -1,4 +1,4 @@
-namespace Umbraco.Core.Models.PublishedContent
+namespace Umbraco.Core.Models.PublishedContent
{
///
@@ -11,15 +11,6 @@
///
object SyncRoot { get; }
- ///
- /// Refreshes the factory.
- ///
- ///
- /// This will typically re-compiled models/classes into a new DLL that are used to populate the cache.
- /// This is called prior to refreshing the cache.
- ///
- void Refresh();
-
///
/// Tells the factory that it should build a new generation of models
///
diff --git a/src/Umbraco.Core/PublishedModelFactoryExtensions.cs b/src/Umbraco.Core/PublishedModelFactoryExtensions.cs
index a877d6a7f5..db18bfd8a9 100644
--- a/src/Umbraco.Core/PublishedModelFactoryExtensions.cs
+++ b/src/Umbraco.Core/PublishedModelFactoryExtensions.cs
@@ -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
///
/// Returns true if the current is an implementation of
///
- ///
- ///
public static bool IsLiveFactory(this IPublishedModelFactory factory) => factory is ILivePublishedModelFactory;
///
/// Returns true if the current is an implementation of and is enabled
///
- ///
- ///
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();
- }
- }
-
///
/// Sets a flag to reset the ModelsBuilder models if the is
///
- ///
- ///
///
/// This does not recompile the pure live models, only sets a flag to tell models builder to recompile when they are requested.
///
diff --git a/src/Umbraco.Core/Runtime/MainDom.cs b/src/Umbraco.Core/Runtime/MainDom.cs
index f1f6ee3afc..c378f4a58b 100644
--- a/src/Umbraco.Core/Runtime/MainDom.cs
+++ b/src/Umbraco.Core/Runtime/MainDom.cs
@@ -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
+ ///
public bool Acquire(IApplicationShutdownRegistry hostingEnvironment)
{
_hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment));
diff --git a/src/Umbraco.ModelsBuilder.Embedded/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.ModelsBuilder.Embedded/DependencyInjection/UmbracoBuilderExtensions.cs
index 85f15942dc..bb0b966195 100644
--- a/src/Umbraco.ModelsBuilder.Embedded/DependencyInjection/UmbracoBuilderExtensions.cs
+++ b/src/Umbraco.ModelsBuilder.Embedded/DependencyInjection/UmbracoBuilderExtensions.cs
@@ -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
///
public static IUmbracoBuilder AddModelsBuilder(this IUmbracoBuilder builder)
{
+ builder.AddRazorProjectEngine();
builder.Services.AddSingleton();
builder.Services.AddSingleton();
builder.Services.AddUnique();
@@ -77,5 +82,169 @@ namespace Umbraco.ModelsBuilder.Embedded.DependencyInjection
builder.Services.AddSingleton();
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();
+
+ ServiceProvider internalServices = builder.Services.BuildServiceProvider();
+ RazorProjectEngine defaultRazorProjectEngine = internalServices.GetRequiredService();
+
+ builder.Services.AddSingleton(s =>
+ {
+ RazorProjectFileSystem fileSystem = s.GetRequiredService();
+
+ // 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().ToList();
+ foreach (IMetadataReferenceFeature m in metadataReferenceFeatures)
+ {
+ builder.Features.Remove(m);
+ }
+
+ // add our custom one to the list
+ metadataReferenceFeatures.Add(new PureLiveMetadataReferenceFeature(s.GetRequiredService()));
+
+ // 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();
+ //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;
+ }
+ }
+
+ ///
+ /// Wraps multiple
+ ///
+ ///
+ ///
+ /// 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.
+ ///
+ ///
+ public class MetadataReferenceFeatureWrapper : IMetadataReferenceFeature
+ {
+ private readonly IReadOnlyList _metadataReferenceFeatures;
+ private RazorEngine _engine;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public MetadataReferenceFeatureWrapper(IEnumerable metadataReferenceFeatures)
+ => _metadataReferenceFeatures = metadataReferenceFeatures.ToList();
+
+ ///
+ public IReadOnlyList References
+ => _metadataReferenceFeatures.SelectMany(x => x.References).ToList();
+
+ ///
+ public RazorEngine Engine
+ {
+ get => _engine;
+ set
+ {
+ _engine = value;
+ foreach (IMetadataReferenceFeature feature in _metadataReferenceFeatures)
+ {
+ feature.Engine = value;
+ }
+ }
+ }
+ }
+
+ ///
+ /// A custom that will dynamically resolve a reference for razor based on the current PureLive assembly.
+ ///
+ ///
+ ///
+ /// 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.
+ ///
+ ///
+ 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;
+
+ ///
+ public IReadOnlyList 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();
+ }
+ }
+
+ ///
+ public RazorEngine Engine { get; set; }
}
}
diff --git a/src/Umbraco.ModelsBuilder.Embedded/ModelsBuilderRazorRuntimeCompilationOptions.cs b/src/Umbraco.ModelsBuilder.Embedded/ModelsBuilderRazorRuntimeCompilationOptions.cs
new file mode 100644
index 0000000000..1e7eb7bf54
--- /dev/null
+++ b/src/Umbraco.ModelsBuilder.Embedded/ModelsBuilderRazorRuntimeCompilationOptions.cs
@@ -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
+ {
+ private readonly ModelsBuilderSettings _config;
+ private readonly IHostingEnvironment _hostingEnvironment;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ModelsBuilderRazorRuntimeCompilationOptions(
+ IOptions config,
+ IHostingEnvironment hostingEnvironment)
+ {
+ _config = config.Value;
+ _hostingEnvironment = hostingEnvironment;
+ }
+
+ ///
+ 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")));
+ }
+ }
+}
diff --git a/src/Umbraco.ModelsBuilder.Embedded/PureLiveModelFactory.cs b/src/Umbraco.ModelsBuilder.Embedded/PureLiveModelFactory.cs
index dddef525ff..6433accaef 100644
--- a/src/Umbraco.ModelsBuilder.Embedded/PureLiveModelFactory.cs
+++ b/src/Umbraco.ModelsBuilder.Embedded/PureLiveModelFactory.cs
@@ -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; // 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 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;
+ ///
+ /// Gets the currently loaded pure live models assembly
+ ///
+ ///
+ /// Can be null
+ ///
+ public Assembly CurrentModelsAssembly { get; private set; }
+
+ public MetadataReference CurrentModelsMetadataReference { get; private set; }
+
///
public object SyncRoot { get; } = new object();
- // gets the RoslynCompiler
+ ///
+ /// Gets the RoslynCompiler
+ ///
private RoslynCompiler RoslynCompiler
{
get
@@ -111,13 +130,6 @@ namespace Umbraco.ModelsBuilder.Embedded
///
public bool Enabled => _config.Enable;
- ///
- 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("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 types = assembly.ExportedTypes.Where(x => x.Inherits() || x.Inherits());
_infos = RegisterModels(types);
_errors.Clear();
@@ -353,6 +362,7 @@ namespace Umbraco.ModelsBuilder.Embedded
}
finally
{
+ CurrentModelsAssembly = null;
_infos = new Infos { ModelInfos = null, ModelTypeMap = new Dictionary() };
}
}
@@ -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();
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)
diff --git a/src/Umbraco.ModelsBuilder.Embedded/RoslynCompiler.cs b/src/Umbraco.ModelsBuilder.Embedded/RoslynCompiler.cs
index 7f1443b156..d735770774 100644
--- a/src/Umbraco.ModelsBuilder.Embedded/RoslynCompiler.cs
+++ b/src/Umbraco.ModelsBuilder.Embedded/RoslynCompiler.cs
@@ -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();
- 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);
diff --git a/src/Umbraco.ModelsBuilder.Embedded/Umbraco.ModelsBuilder.Embedded.csproj b/src/Umbraco.ModelsBuilder.Embedded/Umbraco.ModelsBuilder.Embedded.csproj
index f2b264dcb0..1fe149eb11 100644
--- a/src/Umbraco.ModelsBuilder.Embedded/Umbraco.ModelsBuilder.Embedded.csproj
+++ b/src/Umbraco.ModelsBuilder.Embedded/Umbraco.ModelsBuilder.Embedded.csproj
@@ -11,6 +11,7 @@
+
diff --git a/src/Umbraco.ModelsBuilder.Embedded/UmbracoServices.cs b/src/Umbraco.ModelsBuilder.Embedded/UmbracoServices.cs
index 8763da86a6..76803abe1f 100644
--- a/src/Umbraco.ModelsBuilder.Embedded/UmbracoServices.cs
+++ b/src/Umbraco.ModelsBuilder.Embedded/UmbracoServices.cs
@@ -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;
diff --git a/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs
index ca2f9e6161..ca6c2da6e8 100644
--- a/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs
+++ b/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs
@@ -28,8 +28,8 @@ namespace Umbraco.Web.Website.DependencyInjection
.Add(builder.TypeLoader.GetSurfaceControllers());
// Configure MVC startup options for custom view locations
- builder.Services.AddTransient, RenderRazorViewEngineOptionsSetup>();
- builder.Services.AddTransient, PluginRazorViewEngineOptionsSetup>();
+ builder.Services.ConfigureOptions();
+ builder.Services.ConfigureOptions();
// Wraps all existing view engines in a ProfilerViewEngine
builder.Services.AddTransient, ProfilingViewEngineWrapperMvcViewOptionsSetup>();