Razor.Templating.Core icon indicating copy to clipboard operation
Razor.Templating.Core copied to clipboard

Razor View as an input string

Open gr8tushar opened this issue 3 years ago • 20 comments

Hi, This library looks great. However, I see that it might be a great to have a feature where in, instead of just reading the views from a folder, one should be able to pass it to the function. Use case: In case one would want to allow the user to edit the template, they same would not necessarily be save at the same place. It might be pulled from a blob, DB or any other location.

Thanks.

gr8tushar avatar Jul 20 '21 05:07 gr8tushar

Hi @gr8tushar, that sounds like an interesting use case. However, as far as I know, I doubt this can be achieved as this library is just an abstraction of ASP.NET Core Razor engine which expects the razor view file path as input. Anyhow, I'll still check the possibilities & let you know. Thanks :)

soundaranbu avatar Jul 21 '21 04:07 soundaranbu

Hi @soundaranbu, yup. This would be great if one would want to send out emails using console / background services via the templates customized in the web app / other places. More over, if the user customized a template, it would probably not be available in the compiled file / views folder. I would come either from the user directory / DB / BLOB.

Let me know in case you need more info. Would be happy to help in anyway.

gr8tushar avatar Jul 21 '21 05:07 gr8tushar

I think this should be possible with a custom IFileProvider and Razor runtime compilation.

  • Razor runtime compilation: https://docs.microsoft.com/en-us/aspnet/core/mvc/views/view-compilation?view=aspnetcore-5.0
  • Custom IFileProvider example: https://github.com/mikebrind/RazorEngineViewOptionsFileProviders

I tried configuring services as follows:

var services = new ServiceCollection();
services.AddRazorPages().AddRazorRuntimeCompilation();
services.Configure<Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation.MvcRazorRuntimeCompilationOptions>(opts =>
    opts.FileProviders.Add(new MyCustomFileProvider()));

// Add RazorTemplating after registering all dependencies
// this is important for the razor template engine to find the injected services
services.AddRazorTemplating();

and when calling RazorTemplateEngine.RenderAsync, my custom file provider is correctly used by the razor engine, but I then get a CompilationFailedException.

I have found similar errors on the web:

  • https://stackoverflow.com/questions/61526883/compilationfailedexception-during-runtime-compilation-of-razor-from-aspnetcore-t
  • https://github.com/aspnet/Razor/issues/1212

But I couldn't get it to compile without errors.

ins0mniaque avatar Sep 12 '21 02:09 ins0mniaque

Looks promising :) will give it a try. Thanks!

soundaranbu avatar Sep 13 '21 13:09 soundaranbu

Hi @gr8tushar, that sounds like an interesting use case. However, as far as I know, I doubt this can be achieved as this library is just an abstraction of ASP.NET Core Razor engine which expects the razor view file path as input. Anyhow, I'll still check the possibilities & let you know. Thanks :)

Like @gr8tushar mentioned, our use case is that we have several user/customer-specific templates and as such they are stored either on the cloud or in the database for editing purposes. This library makes the email generation process so easy especially since we can use dependency injection to ensure we're only retrieving the data and services needed for any given template. Based on your comment here, I wondered if saving the file at runtime after pulling it from the db would help but it appears there's no runtime support at all. I found older libraries which support this capability and was wondering if this is something worth pursuing in the future or if it was ruled out due to relying on older/unsupported technologies.

sjd2021 avatar Dec 01 '21 16:12 sjd2021

@ins0mniaque I tried this approach. This is working in v1.7.0. I'll be adding samples for this in the coming days. Also, I'm adding some helper classes. so that, it's easier to implement.

cc: @sjd2021 @gr8tushar

soundaranbu avatar Mar 02 '22 13:03 soundaranbu

This is a great new feature. Thanks!

ins0mniaque avatar Mar 02 '22 18:03 ins0mniaque

Hello, any idea when this will be available to use?

NichlasNielsen avatar Jun 10 '22 08:06 NichlasNielsen

Hi @NichlasNielsen, this feature is already possible. Please refer to the above commit for sample implementation. I'll need to add documentation for this. I'll do that once I get some free time. Thanks.

soundaranbu avatar Jun 14 '22 08:06 soundaranbu

Does this work for azure functions, I can't get it to work?

Joffreyvl avatar Jun 14 '22 13:06 Joffreyvl

Hi @Joffreyvl, technically it should work. But I need to try once to confirm. In the meantime, please upload a sample project if possible. I'll take a look. Thanks.

soundaranbu avatar Jun 14 '22 15:06 soundaranbu

@soundaranbu I create a sample project, but both the in-process and the isolated function don't work for me. When I run the in-process function I get a DI issue and when I run the isolated function the custom file provider isn't used.

Joffreyvl avatar Jun 15 '22 08:06 Joffreyvl

Ok, I can the see the problem already. Make sure, you've followed the steps here for creating the razor class library https://soundaranbu.medium.com/render-razor-view-cshtml-to-string-in-net-core-7d125f32c79

soundaranbu avatar Jun 15 '22 08:06 soundaranbu

@soundaranbu do you need a razor class library when you are doing runtime compilation? I am trying to read cshtml files from a blob storage account.

Joffreyvl avatar Jun 15 '22 08:06 Joffreyvl

That's a valid point. It's not necessary. However, you'll need to make these changes to make it to work.

soundaranbu avatar Jun 15 '22 09:06 soundaranbu

@soundaranbu thanks for the information! I got it to work, I can now dynamically render string templates! For others wanting to solve this problem, you can find a code sample in this repo.

Joffreyvl avatar Jun 15 '22 12:06 Joffreyvl

~With my recent PR, the function public static void AddRazorTemplating(this IServiceCollection services) could be extended to allow customizing the MvcRazorRuntimeCompilationOptions, such as public static void AddRazorTemplating(this IServiceCollection services, Action<MvcRazorRuntimeCompilationOptions>? setupOptions) and if setupOptions is not null, then services.Configure<MvcRazorRuntimeCompilationOptions>(setupOptions); can be called. This approach will work for the manually registered instance instead of the static RazorTemplateEngine methods.~

My previous comment is not required. Callers can customize MvcRazorRuntimeCompilationOptions themselves during service collection setup.

pbolduc avatar Sep 09 '22 19:09 pbolduc

Hi, I have a pull request #48 that should hopefully address your concerns. Can you take a look and provide any feedback to changes for your use case. In this case, you will need to use the following approach to initialize

var services = new ServiceCollection();
services.Configure<Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation.MvcRazorRuntimeCompilationOptions>(opts =>
    opts.FileProviders.Add(new MyCustomFileProvider()));

services.AddRazorTemplating();

and either use the static method RazorTemplateEngine.RenderAsync(...) or you can inject in an instance of IRazorTemplateEngine into your defendant service. Hopefully we can have a prerelease with these changes deployed to nuget soon.

pbolduc avatar Sep 13 '22 20:09 pbolduc

any solution on this, rendering razor string as input instead of path ?

vaskolekoski avatar Mar 19 '24 16:03 vaskolekoski

any solution on this, rendering razor string as input instead of path ?

The PR mention here was merged. You should be able save your template as an embedded resource and add a ManifestEmbeddedFileProvider.

Say you want to store your template in a database, you would need to create a file provider that could query the database for the given template.

https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.fileproviders.ifileprovider?view=dotnet-plat-ext-8.0

pbolduc avatar Mar 19 '24 16:03 pbolduc