5

I am trying to render ViewComponent to a string in a service in ASP.NET Core 3.0 (Pages).

I found two partial solutions to my problem, but both are for older pre 3.0 version and have some issues that I do not know how to resolve.

  • First one is taken from GitHub Gist, but designed with controllers in mind and NullView was removed 3.0 (and is really hard to find any reference on it).
  • The second option is here, but fails one line 42 (no view is found). I believe that this is due to the changes made in .net core, where the views are now precompiled and the razor view engine therefore cannot find them.

I did some of my own experimenting, but could not get it working (I believe I am facing the same issue as the second solution I posted). I would appreciate any help regarding this matter on how to get it working. I've added my best attempt and a minimum project to GitHub, where Render is in the Service folder and in the Index.cshtml.cs is the call to the service.

Thanks!

Jaka Konda
  • 1,385
  • 3
  • 13
  • 35

1 Answers1

6

The Reasons

  1. NullView is an internal class now. But that's a very simple class that does nothing. In other words, simply copy & paste it from the src into your source code.
  2. The tutorials you linked above is used to render a plain view. However, since you're trying to render a view component instead of a view, you should avoid passing a view path directly. You should pass a ViewComponent Class Name (or Type) instead.

    Model = await _viewRender.RenderToStringAsync(
        "/Components/Test/Default.cshtml", // don't pass a view path since we're not rendering a view file but a view component
        "Test",                            // because the `ViewComponent` Name is Test
        new TestModel { StrToPrint = "Print From Service" });
    
  3. According to the official docs, The runtime searches for the view in the following paths:

    1. /Views/{Controller Name}/Components/{View Component Name}/{View Name}
    2. /Views/Shared/Components/{View Component Name}/{View Name}
    3. /Pages/Shared/Components/{View Component Name}/{View Name}

    However, your Test ViewComponent resides in Pages/Components/Test/Default.cshtml, which can not be found by Razor by default. Either configure a custom View Location, or move it to the standard location such that Razor can find the view files.

  4. Finally, rendering a ViewComponent as a page seems a bit of overkill. I would suggest you should use IViewComponentHelper to render the ViewComponent as an IHtmlContent such that I can write to a StringWriter:

    public class MyViewComponentContext 
    {
        public HttpContext HttpContext { get; set; }
        public ActionContext ActionContext { get; set; }
        public ViewDataDictionary ViewData { get; set; }
        public ITempDataDictionary TempData { get; set; }
    }
    private async Task<string> Render( MyViewComponentContext myViewComponentContext,string viewComponentName,object args) {
        using (var writer = new StringWriter())
        { 
            var helper = this.GetViewComponentHelper(myViewComponentContext, writer);
            var result = await helper.InvokeAsync(viewComponentName, args);  // get an IHtmlContent
            result.WriteTo(writer, HtmlEncoder.Default);
            await writer.FlushAsync();
            return writer.ToString();
        }
    }
    

Demo

If I fix the issues as I described above, when running your project I'll get the correct result:

enter image description here

itminus
  • 23,772
  • 2
  • 53
  • 88