1

Razor components are normally loaded from .razor files. Behind the scenes, they implement the IComponent interface (or one of its implementations).

What I'd like to know is, can we build a Razor component, like one that would be loaded from a .razor file, from a string containing markup and possibly components?

An example would be:

var html = "<p>@Message</p>
@code {
  [Parameter] public string Message { get; set; }
}";

IComponent component = ParseMarkup(html);
5
  • 2
    My guess is that you would have to compile the string using the Razor compiler which seems a little heavy-handed. Is there a reason you need to do this? Commented May 31 at 13:28
  • I will be building a code generator and I will have templates as strings, possibly stored on a database. Commented May 31 at 13:34
  • 2
    Razor pages are pre-compiled in to C# classes, which are then compiled., so no can do. Commented May 31 at 20:45
  • @MrCakaShaunCurtis: I guess the code that compiles them must be available to devs, right? Will try to find it and post here. Commented Jun 1 at 10:21
  • Related to stackoverflow.com/questions/55740827/… Commented Jun 3 at 11:58

1 Answer 1

0

I implemented it like this:

public class RazorCompiler
{
    private static readonly string _assemblyPath = Path.GetDirectoryName(typeof(object).Assembly.Location)!;

    private static readonly MetadataReference[] _references =
    [
        MetadataReference.CreateFromFile(Assembly.GetExecutingAssembly().Location),
        MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
        MetadataReference.CreateFromFile(typeof(IComponent).Assembly.Location),
        MetadataReference.CreateFromFile(typeof(RazorCompiledItemAttribute).Assembly.Location),
        MetadataReference.CreateFromFile(Path.Combine(_assemblyPath, "mscorlib.dll")),
        MetadataReference.CreateFromFile(Path.Combine(_assemblyPath, "System.dll")),
        MetadataReference.CreateFromFile(Path.Combine(_assemblyPath, "System.Core.dll")),
        MetadataReference.CreateFromFile(Path.Combine(_assemblyPath, "System.Runtime.dll"))
    ];

    private static readonly RazorProjectFileSystem _projectFilesystem = RazorProjectFileSystem.Create(".");

    private static readonly RazorProjectEngine _engine = RazorProjectEngine.Create(RazorConfiguration.Default, _projectFilesystem);

    private static readonly CSharpCompilationOptions _compilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
        .WithOverflowChecks(true)
        .WithOptimizationLevel(OptimizationLevel.Release);

    public Assembly Compile(string sourceFile)
    {
        ArgumentException.ThrowIfNullOrEmpty(sourceFile);

        var item = _projectFilesystem.GetItem(sourceFile);

        var codeDocument = _engine.Process(item);

        var csDocument = codeDocument.GetCSharpDocument();

        var syntaxTree = CSharpSyntaxTree.ParseText(csDocument.GeneratedCode);

        var assemblyName = Path.GetRandomFileName();

        var compilation = CSharpCompilation.Create(assemblyName, [syntaxTree], _references, _compilationOptions);

        using var assemblyStrean = new MemoryStream();

        var result = compilation.Emit(assemblyStrean);

        if (!result.Success)
        {
            throw new InvalidOperationException(string.Join(Environment.NewLine, result.Diagnostics.Where(x => x.Severity == DiagnosticSeverity.Error).Select(x => x.GetMessage())));
        }

        var assembly = Assembly.Load(assemblyStrean.ToArray());

        return assembly;
    }
}

And it works like a charm! I just need to pick up the first Type exposed from the returned Assembly, which will implement IComponent, and it's done.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.