RazorEngineCore icon indicating copy to clipboard operation
RazorEngineCore copied to clipboard

how can I reference other assemblies and use inject?

Open yamaritta opened this issue 4 years ago • 10 comments

I tried to use localization in razor:

@using Microsoft.Extensions.Localization

@inject IStringLocalizer Localizer

@{
   <h1>Localizer["Header"]<h1>;
}

I added reference to my project. Result: Unable to compile template: skvbsxxk.c3i(3,17): error CS0234: The type or namespace name 'Extensions' does not exist in the namespace 'Microsoft'

When I add any other reference I get the same result. Can I manage this?

yamaritta avatar Mar 04 '20 05:03 yamaritta

Also having issues making use of @using directives.

roja avatar Mar 04 '20 08:03 roja

If you need to inject something in text template or reference other assemblies, probably you are overengeneering. Its not a RazorEngine responsibility go get service instances for you.

Making Inject directive work would require IoC container to be built in. I would like to avoid that and keep engine as simple as possible.

Localizer

Intended use

public class MyTemplateBase : RazorEngineTemplateBase
{
    private IStringLocalizer localizer;

    public void Initialize(IStringLocalizer localizer)
    {
        this.localizer = localizer;
    }

    public string Localize(string key)
    {
        return this.localizer[key];
    }
}
string templateText = @"

    <h1>@Localize(""Header"")</h1>

";
RazorEngine razorEngine = new RazorEngine();
RazorEngineCompiledTemplate<MyTemplateBase> compiledTemplate = razorEngine.Compile<MyTemplateBase>(templateText);

string result = compiledTemplate.Run(instance =>
{
    instance.Initialize(this.localizerInstance); // get instance from whatever source
});

adoconnection avatar Mar 04 '20 15:03 adoconnection

Referencing assemblies

Usings in order to work require assemblies to be referenced on compilation phase

RazorEngine razorEngine = new RazorEngine();
RazorEngineCompiledTemplate compiledTemplate = razorEngine.Compile(templateText, builder =>
{
    builder.AddAssemblyReferenceByName("System.Security"); // by name
    builder.AddAssemblyReference(typeof(System.IO.File)); // by type
    builder.AddAssemblyReference(Assembly.Load("source")); // by reference
});

string result = compiledTemplate.Run(new { name = "Hello" });

adoconnection avatar Mar 04 '20 15:03 adoconnection

Maybe it is possible to use default DI for .net core? i.e https://github.com/toddams/RazorLight/blob/master/src/RazorLight/Extensions/ServiceCollectionExtensions.cs

yamaritta avatar Mar 05 '20 06:03 yamaritta

Can you tell what exactly are you building so I can understand better?

adoconnection avatar Mar 05 '20 10:03 adoconnection

  1. I use standard DI container in my Startup class.
  2. I have implementation of interface IStringLocalizer with registration in DI.

I would like to have possibility to to register engine in DI and use inject IStringLocalizer in my razor template without RazorEngineTemplateBase

yamaritta avatar Mar 11 '20 03:03 yamaritta

+ for adding injection. Here is my use case. I'm serving fragments to my pages using SSE and a JIT approach. A page has many "Views" (containers for HTML Content) that get populated on-demand based on current conditions. A View has HTML, JavaScript Modules, and CSS that get registered in the page when received. I have a set of builder services that generate parts of what I am sending.

// ViewComponent1.cshtml

@inject HTMLBuilder _html
@inject ESMBuilder _script

<viewPart>
    [static content]
    @await _html.generateViewContent(conditions) 
    [static content]
</viewPart>

<script type="module">
    @await _script.generateViewController(controller)
    @await _script.generateViewModel(modelClassDef)
    @await _script.generateViewModel(modelClassDef)
</script>

// C# on Server

    ... receive request
    //  business processes
    var controller = // determine controller
    var models = new[] { // determine models }

    var viewComponent = renderJitView(controller, models);
   
    // Send with SSE 
    await responseStream.WriteAsync("data: { component: viewComponent } \n\n");
    await responseStream.Body.FlushAsync();
   

With inject I could do deeper composition. (Made up RazorEngineCore syntax)

<layoutContainer>
    <viewPartContainer locator="/1/">
       @await RazorEngineCore.renderViewWithInjects(ViewComponent1, params1)
    </viewPartContainer>

    <viewPartContainer locator="/2/">
       @await RazorEngineCore.renderViewWithInjectss(ViewComponent2, params2)
    </viewPartContainer>
</layoutContainer>

What I am sending is not an empty or static template. The fields are generated dynamically. It is a fully populated view with a custom "ViewController" and multiple "custom" models that can be sent back to the server. Inject is what I use to compose these components in real time.

Randy-Buchholz avatar Sep 11 '20 20:09 Randy-Buchholz

@Randy-Buchholz How many builder services you have? My first thought is to make them avaiable by adding in TemplateBase class.

adoconnection avatar Sep 15 '20 14:09 adoconnection

There is no set number of builders. Each business domain provides a set of builders. For example, the Shipping domain might have builders for Ground Shipping and Air Shipping. If someone selects "Ground" a fragment is built and sent to the browser to populate a "Shipping" section of the page. "Ground" may have a builder for Carriers that would build fragments based on individual shippers.

What I am building is a framework to manage the creation and delivery of the fragments. I can render single-level Razor without problems and then put them together. But that gets messy.

This may not make a lot of sense without knowing more, but conceptually I'm doing something like this:

@inject Shipping _shipping
@inject Ground _delivery
@inject DHL _carrier

<container>
   @render(_shipping)
<container>

    // Injected into container
    <shipping>
        @render(_delivery)
    </shipping>

        // Injected into shipping
        <delivery>
            @render(_carrier)  
        <delivery>

            // Injected into delivery
            <carrier>
            </carrier> 

Right now I render each one individually and merge them

Randy-Buchholz avatar Sep 15 '20 18:09 Randy-Buchholz

Your point is clear.

Well primary issue for me is that @inject is not default Razor keyword, however all @entries got parsed and stored somewhere, I dont know how to access this information.

Generally speaking I do not want to tie RazorEngineCore to particular IoC container unless there is no other choice. If we were able to locate and process injects collection, it would be easy to come up with sutable solution

adoconnection avatar Sep 16 '20 10:09 adoconnection