mirror of
https://github.com/radzenhq/radzen-blazor.git
synced 2026-07-08 18:16:08 +00:00
Two follow-up fixes for compiling demos in Edit Source / Playground: - CompilerService: raise the parse language version from C# 10 to Latest so demos using collection expressions and primary constructors compile (previously failed with CS8936). - linker.xml: preserve System.Math, DisplayAttribute and Radzen.Colors, which are referenced only by demo source compiled at runtime and were otherwise removed by trimming (Math.PI, [Display(Description=...)], @Colors.* respectively). Verified against a trimmed publish: demo compile failures drop from 20 to 5 out of 1052 (remaining 5 are a separate Razor type-inference codegen issue).
228 lines
7.7 KiB
C#
228 lines
7.7 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Dynamic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Net.Http;
|
|
using System.Reflection;
|
|
using System.Runtime.Loader;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.AspNetCore.Components;
|
|
using Microsoft.AspNetCore.Razor.Language;
|
|
using Microsoft.CodeAnalysis;
|
|
using Microsoft.CodeAnalysis.CSharp;
|
|
using Microsoft.CodeAnalysis.Razor;
|
|
using Radzen.Blazor;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using RadzenBlazorDemos.Shared;
|
|
using Microsoft.AspNetCore.Components.WebAssembly.Services;
|
|
using MetadataReferenceService.Abstractions.Types;
|
|
using MetadataReferenceService.BlazorWasm;
|
|
using System.ComponentModel;
|
|
|
|
namespace RadzenBlazorDemos
|
|
{
|
|
public class CompilerService : ICompilerService
|
|
{
|
|
class CollectibleAssemblyLoadContext : AssemblyLoadContext
|
|
{
|
|
public CollectibleAssemblyLoadContext() : base(isCollectible: true)
|
|
{
|
|
}
|
|
}
|
|
|
|
class InMemoryProjectItem : RazorProjectItem
|
|
{
|
|
private readonly string source;
|
|
private readonly string fileName;
|
|
|
|
public override string BasePath => fileName;
|
|
|
|
public override string FilePath => fileName;
|
|
|
|
public override string PhysicalPath => fileName;
|
|
|
|
public override bool Exists => true;
|
|
|
|
public override Stream Read()
|
|
{
|
|
return new MemoryStream(Encoding.UTF8.GetBytes(source));
|
|
}
|
|
|
|
private readonly string fileKind = FileKinds.Component;
|
|
|
|
public override string FileKind => fileKind;
|
|
|
|
public InMemoryProjectItem(string source)
|
|
{
|
|
this.source = source;
|
|
fileName = "DynamicComponent.razor";
|
|
fileKind = "component";
|
|
}
|
|
}
|
|
|
|
private CSharpCompilation compilation;
|
|
private RazorProjectEngine razorProjectEngine;
|
|
private CollectibleAssemblyLoadContext loadContext;
|
|
|
|
private AssemblyLoadContext GetLoadContext()
|
|
{
|
|
if (loadContext != null)
|
|
{
|
|
loadContext.Unload();
|
|
GC.Collect();
|
|
}
|
|
|
|
loadContext = new CollectibleAssemblyLoadContext();
|
|
|
|
return loadContext;;
|
|
}
|
|
private const string Imports = @"@using System.Net.Http
|
|
@using System
|
|
@using System.Collections.Generic
|
|
@using System.Linq
|
|
@using System.Threading.Tasks
|
|
@using Microsoft.AspNetCore.Components
|
|
@using Microsoft.AspNetCore.Components.Forms
|
|
@using Microsoft.AspNetCore.Components.Routing
|
|
@using Microsoft.AspNetCore.Components.Web
|
|
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
|
@using Microsoft.JSInterop
|
|
@using Radzen
|
|
@using Radzen.Blazor
|
|
@using RadzenBlazorDemos
|
|
@using RadzenBlazorDemos.Shared
|
|
@using RadzenBlazorDemos.Pages
|
|
";
|
|
private readonly BlazorWasmMetadataReferenceService metadataReferenceService;
|
|
|
|
public CompilerService(NavigationManager navigationManager)
|
|
{
|
|
metadataReferenceService = new BlazorWasmMetadataReferenceService(navigationManager);
|
|
}
|
|
|
|
private async Task<IEnumerable<MetadataReference>> GetReferencesAsync(IEnumerable<Assembly> assemblies)
|
|
{
|
|
var syncAssemblies = new List<Assembly>();
|
|
|
|
var asyncAssemblies = new List<Assembly>();
|
|
|
|
foreach (var assembly in assemblies)
|
|
{
|
|
if (string.IsNullOrEmpty(assembly.Location))
|
|
{
|
|
asyncAssemblies.Add(assembly);
|
|
}
|
|
else
|
|
{
|
|
syncAssemblies.Add(assembly);
|
|
}
|
|
}
|
|
|
|
var syncReferences = syncAssemblies.Select(a => MetadataReference.CreateFromFile(a.Location)).ToList();
|
|
|
|
var asyncReferences = await Task.WhenAll(asyncAssemblies.Select(assembly => metadataReferenceService.CreateAsync(new AssemblyDetails
|
|
{
|
|
Name = assembly.GetName().Name
|
|
})));
|
|
|
|
return syncReferences.Concat(asyncReferences).ToList();
|
|
}
|
|
|
|
private async Task InitializeAsync()
|
|
{
|
|
var assemblies = GetAssemblies();
|
|
|
|
var referenceAssemblies = await GetReferencesAsync(assemblies);
|
|
|
|
compilation = CSharpCompilation.Create(
|
|
"RadzenBlazorDemos.DynamicAssembly",
|
|
Array.Empty<SyntaxTree>(),
|
|
referenceAssemblies,
|
|
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
|
|
.WithNullableContextOptions(NullableContextOptions.Disable)
|
|
);
|
|
|
|
razorProjectEngine = RazorProjectEngine.Create(RazorConfiguration.Default, RazorProjectFileSystem.Create("/"), builder =>
|
|
{
|
|
builder.SetRootNamespace("RadzenBlazorDemos");
|
|
|
|
CompilerFeatures.Register(builder);
|
|
|
|
var metadataReferenceFeature = new DefaultMetadataReferenceFeature { References = referenceAssemblies.ToArray() };
|
|
|
|
builder.Features.Add(metadataReferenceFeature);
|
|
|
|
builder.Features.Add(new CompilationTagHelperFeature());
|
|
});
|
|
}
|
|
|
|
private static List<Assembly> GetAssemblies()
|
|
{
|
|
var referenceAssemblyRoots = new[]
|
|
{
|
|
typeof(System.Runtime.AssemblyTargetedPatchBandAttribute).Assembly, // System.Runtime
|
|
typeof(ComponentBase).Assembly,
|
|
typeof(RadzenButton).Assembly,
|
|
typeof(ExpandoObject).Assembly,
|
|
typeof(EventConsole).Assembly,
|
|
typeof(Component).Assembly
|
|
};
|
|
|
|
return referenceAssemblyRoots
|
|
.SelectMany(assembly => assembly.GetReferencedAssemblies().Append(assembly.GetName()))
|
|
.Select(Assembly.Load)
|
|
.Distinct()
|
|
.ToList();
|
|
}
|
|
|
|
public async Task<Type> CompileAsync(string source)
|
|
{
|
|
if (compilation == null)
|
|
{
|
|
await InitializeAsync();
|
|
}
|
|
|
|
var projectItem = new InMemoryProjectItem(source);
|
|
|
|
var codeDocument = razorProjectEngine.Process(RazorSourceDocument.ReadFrom(projectItem), FileKinds.Component,
|
|
new [] {RazorSourceDocument.Create(Imports, "_Imports.razor")}, null);
|
|
|
|
var csharpDocument = codeDocument.GetCSharpDocument();
|
|
|
|
var generatedCode = csharpDocument.GeneratedCode
|
|
.Replace("<Radzen.Group>", "<global::Radzen.Group>")
|
|
.Replace("#nullable enable", "#nullable disable")
|
|
.Replace("where TItem : , notnull", "where TItem : notnull");
|
|
|
|
var syntaxTree = CSharpSyntaxTree.ParseText(generatedCode, CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.Latest));
|
|
|
|
compilation = compilation.RemoveAllSyntaxTrees().AddSyntaxTrees(syntaxTree);
|
|
|
|
var errors = compilation.GetDiagnostics().Where(d => d.Severity == DiagnosticSeverity.Error);
|
|
|
|
if (errors.Any())
|
|
{
|
|
throw new ApplicationException(string.Join(Environment.NewLine, errors.Select(e => e.GetMessage())));
|
|
}
|
|
|
|
using var stream = new MemoryStream();
|
|
|
|
var emitResult = compilation.Emit(stream);
|
|
|
|
if (!emitResult.Success)
|
|
{
|
|
throw new ApplicationException(string.Join(Environment.NewLine, emitResult.Diagnostics.Select(d => d.GetMessage())));
|
|
}
|
|
|
|
stream.Seek(0, SeekOrigin.Begin);
|
|
|
|
var assembly = GetLoadContext().LoadFromStream(stream);
|
|
|
|
var type = assembly.GetType("RadzenBlazorDemos.DynamicComponent");
|
|
|
|
return type;
|
|
}
|
|
}
|
|
} |