Laden...

Templating mit Azure Functions

Erstellt von Hqrtz vor 2 Jahren Letzter Beitrag vor 2 Jahren 371 Views
H
Hqrtz Themenstarter:in
13 Beiträge seit 2021
vor 2 Jahren
Templating mit Azure Functions

Hallo

ich habe eine Azure Function erstellt, die RazorLight zum Templating eines Dokuments nutzt.
Dazu habe ich eine .cshtml Datei erstellt mit HTML-Code und habe eingestellt, dass die Datei ins OutputDirectory kopiert wird. Diese lese ich dann in einen string ein.


var engine = new RazorLightEngineBuilder()
	// required to have a default RazorLightProject type,
	// but not required to create a template from string.
	.UseEmbeddedResourcesProject(typeof(ViewModel))
	.SetOperatingAssembly(typeof(ViewModel).Assembly)
	.UseMemoryCachingProvider()
	.Build();

string template = var template = File.ReadAllText("./View.cshtml")
ViewModel model = new ViewModel {Name = "John Doe"};

string result = await engine.CompileRenderStringAsync("templateKey", template, model);


Ich hatte eig gehofft, dass das so mit dem OutputDirectory auch bei gepublishten Azure Funtions funktioniert. Ich würde nur ungern das ganze Templating direkt über einen String machen sondern das Template lieber in einer externen Datei schreiben, die ich dann einlese. Hat da jemand eine Idee wie ich sowas umsetzen kann?

RazorLight bietet ja auch eine Lösung mit einem File an:


var engine = new RazorLightEngineBuilder()
	.UseFileSystemProject("C:/RootFolder/With/YourTemplates")
	.UseMemoryCachingProvider()
	.Build();

var model = new {Name = "John Doe"};
string result = await engine.CompileRenderAsync("Subfolder/View.cshtml", model);

Aber hierbei wäre woeder das selbe Problem denke ich, dass der das nicht finden wird?

Vielen Dank schonmal

16.806 Beiträge seit 2008
vor 2 Jahren

Siehe Dokumentation von RazorLight:

  1. Templating Directory mit relativem! Pfad. Den bekommst Du über den Function Context.

string path = $"{ context.FunctionAppDirectory }/MyTemplates";
UseFilesystemProject(path)

context kommt dabei von ExecutionContext context als Function Parameter.
Prinzipiell kannst UseFilesystemProject auch weglassen und direkt den Pfad an der View anheften, dann musst die Engine nicht jedes mal erzeugen.

  1. cshtml in Deine Assembly einbetten

Aber wenn ich Dein Code so anschau, dann hast den einfach (unüberlegt?) von der RazorLight QuickStart kopiert.
Wie schon bei Deinem anderen Thema: Les die Doku ganz. Dafür ist sie da. 🙂

H
Hqrtz Themenstarter:in
13 Beiträge seit 2021
vor 2 Jahren

Also ich habe es jetzt so probiert:


[FunctionName("RenderTemplate")]
        public async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
            ILogger log, ExecutionContext executionContext)
        {
            log.LogInformation("C# HTTP trigger function processed a request.");

            string name = req.Query["name"];

            var engine = new RazorLightEngineBuilder()
                .UseEmbeddedResourcesProject(typeof(ModelClass))
                .SetOperatingAssembly(typeof(ModelClass).Assembly)
                .UseFileSystemProject(Directory.GetCurrentDirectory())
                .UseMemoryCachingProvider()
                .Build();

            log.LogInformation("context: " + executionContext.FunctionAppDirectory);
            log.LogInformation("Directory: " + Directory.GetCurrentDirectory());
            log.LogInformation("find View: " + Path.GetDirectoryName("View.cshtml"));

            var template = File.ReadAllText(@"./View.cshtml", System.Text.Encoding.UTF8);

            var name = myService.GetName();
            var model = new ModelClass
            {
                Name = name
            };

            string result = await engine.CompileRenderStringAsync("templateKey", template, model);

            return new OkObjectResult(result);
        }

Die Ausgabe im azure Portal lautet wie folgt:> Fehlermeldung:

2021-05-09T08:51:17.524 [Information] Executing 'RenderTemplate' (Reason='This function was programmatically called via the host APIs.', Id=7f2d712b-2069-4320-a39b-f46050e61168)
2021-05-09T08:51:17.524 [Information] C# HTTP trigger function processed a request.
2021-05-09T08:51:17.528 [Information] context: C:\home\site\wwwroot
2021-05-09T08:51:17.528 [Information] Directory: C:\Program Files (x86)\SiteExtensions\Functions\3.0.15584\32bit
2021-05-09T08:51:17.528 [Information] find View:
2021-05-09T08:51:18.023 > Fehlermeldung:
Executed 'RenderTemplate' (Failed)Could not find file 'C:\Program Files (x86)\SiteExtensions\Functions\3.0.15584\32bit\View.cshtml'.

Also eig findet der doch über Directory.GetCurrentDirectory() genau den richtigen pfad wo der dann laut der Fehlermeldung die View sucht oder nicht?
Aber ich verstehe nicht warum der die View nicht finden kann...
Ich habe bei der View "Copy To Output Directory: Copy Always" eingestellt.

Meine Struktur sieht so aus, dass ich einfach alles direkt im Projekt erstellt habe. Also keinen extra View ordner oder so. Das würde ich dann machen, wenn es funktionieren sollte.

Und ich hatte es auch mit


 var engine = new RazorLightEngineBuilder()
                .UseEmbeddedResourcesProject(typeof(ModelClass))
                .SetOperatingAssembly(typeof(ModelClass).Assembly)
                .UseFileSystemProject(executionContext.FunctionAppDirectory)
                .UseMemoryCachingProvider()
                .Build();

probiert aber da der ja nicht im wwwroot ordner nach der view sucht, hat das auch nichts geholfen

16.806 Beiträge seit 2008
vor 2 Jahren

Weil das ein Krampf ist, was Du da zusammen puzzlest, wenn man Dir das direkt sagen darf.
Directory.GetCurrentDirectory() ist in allen Aspekten der falsche Weg. Keine Ahnung, wie Du darauf kommst. Vermutlich irgendwo mit einem anderen Zusammenhang her kopiert?

Beachtest Du meine Beiträge dann kommt das bei raus:


RazorLightEngine engine = new RazorLightEngineBuilder()
    .SetOperatingAssembly(Assembly.GetExecutingAssembly())
    .UseFileSystemProject(executionContext.FunctionAppDirectory)
    .UseMemoryCachingProvider()
    .Build();

Damit hast Du das Setup für eine Datei-basierte Templating Pipeline. Keine Ahnung wieso Du immer noch UseEmbeddedResourcesProject verwendest. Doku gelesen, wofür das ist?
Beachtest Du dann die Dokumentation von RazorLight, was ich bei Deinem Code leider nicht annähernd erkennen kann, dann sieht das Rendering nur noch so aus:


TestViewModel vm = new TestViewModel();
string result = await engine.CompileRenderAsync("Views/Test.cshtml", vm);

Wie man sieht liegt die Test.cshtml in einem Views Projektordner, die dann noch für das Deployment entsprechend gesetzt sein muss.


  <ItemGroup>
    <Content Include="Views\Test.cshtml">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </Content>
  </ItemGroup>

Am Ende kommt eine vollfunktionale Azure Function mit RazorLight raus.


public class Function1
{
    [FunctionName("Function1")]
    public async Task<IActionResult> Run(
        [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
        ExecutionContext executionContext)
    {
        RazorLightEngine engine = new RazorLightEngineBuilder()
            .SetOperatingAssembly(Assembly.GetExecutingAssembly())
            .UseFileSystemProject(executionContext.FunctionAppDirectory)
            .UseMemoryCachingProvider()
            .Build();

        TestViewModel vm = new TestViewModel();
        string result = await engine.CompileRenderAsync("Views/Test.cshtml", vm);

        return new OkObjectResult(result);
    }
}

Du musst eigentlich echt nur das machen, was in der Doku steht.
Du kannst fast alles 1:1 kopieren.

H
Hqrtz Themenstarter:in
13 Beiträge seit 2021
vor 2 Jahren

Ok super vielen Dank!! hat gerade funktioniert.

Ich habe Directory.GetCurrentDirectory genutzt weil es lokal funktioniert hat. Der ExecutionContext allerdings auch.
Nur beim Publishen hat der mir angezeigt das er in einem File nach der View sucht und die View da nicht findet. Und der Path wo der gesucht hat, war genau der, der bei GetCurentDirectory rauskam. Deswegen hatte ich es probiert.

Aber da es jetzt funktioniert hat solltest du jetzt hier deine Ruhe vor mir haben 😁 Hast heute aufjednefall deine Gute Tat für den Tag erfüllt 🙂