Search code examples
c#asp.netasp.net-corerazorsignalr

How do i convert a partial view to String, so i can use it in signalr hub?


I can't find a way to render a partial view inside a signalr hub, the real problem is that i don't know how i would convert the partial to a string.

I made a table partial that renders some data from my database, i want that data to be refreshed each 2 minutes, i using signalr hub for this purpose. But i need to pass my partial as a string to the hub, otherwise it won't work. Any ideas?

Hub Code (I was trying to use a function but it didnt work):

    public async Task<string> Teste()
    {
        var body = await _renderer.RenderPartialToStringAsync("../Areas/Admin/Pages/Partial/_Filas.cshtml", new principalModel());
        return body.ToString();
    }

Solution

  • You can use this class to render view to string :

    using Microsoft.AspNetCore.Mvc;
    using Microsoft.AspNetCore.Mvc.Abstractions;
    using Microsoft.AspNetCore.Mvc.ModelBinding;
    using Microsoft.AspNetCore.Mvc.Razor;
    using Microsoft.AspNetCore.Mvc.Rendering;
    using Microsoft.AspNetCore.Mvc.ViewFeatures;
    
    namespace RenderView.Models
    {
        public class ViewRenderer
        {
            private readonly IRazorViewEngine _viewEngine;
            private readonly ITempDataProvider _tempDataProvider;
            private readonly IServiceProvider _serviceProvider;
            private readonly IHttpContextAccessor _httpContextAccessor;
    
            public ViewRenderer(
                IRazorViewEngine viewEngine,
                ITempDataProvider tempDataProvider,
                IServiceProvider serviceProvider,
                IHttpContextAccessor httpContextAccessor)
            {
                _viewEngine = viewEngine;
                _tempDataProvider = tempDataProvider;
                _serviceProvider = serviceProvider;
                _httpContextAccessor = httpContextAccessor;
            }
    
    
            public async Task<string> RenderViewToStringAsync<TModel>(string viewNameOrPath, TModel model)
            {
                var actionContext = GetActionContext();
                var view1 = _viewEngine.FindView(actionContext, viewNameOrPath, false);
                if (!view1.Success)
                {
                    view1 = _viewEngine.GetView("~/", viewNameOrPath, false);
                    if (!view1.Success)
                        throw new FileNotFoundException($"Couldn't find '{(object)viewNameOrPath}'");
                }
    
                var view2 = view1.View;
                await using var output = new StringWriter();
                var viewData = new ViewDataDictionary<TModel>((IModelMetadataProvider)new EmptyModelMetadataProvider(),
                    new ModelStateDictionary())
                {
                    Model = model
                };
                var context = new ViewContext(actionContext, view2, (ViewDataDictionary)viewData,
                    (ITempDataDictionary)new TempDataDictionary(actionContext.HttpContext, this._tempDataProvider),
                    (TextWriter)output, new HtmlHelperOptions());
                await view2.RenderAsync(context);
                var stringAsync = output.ToString();
                return stringAsync;
            }
    
            private ActionContext GetActionContext()
            {
                var httpContext = _httpContextAccessor?.HttpContext;
                if (httpContext != null)
                    return new ActionContext(httpContext, httpContext.GetRouteData(), new ActionDescriptor());
                var defaultHttpContext = new DefaultHttpContext
                {
                    RequestServices = this._serviceProvider
                };
                return new ActionContext((HttpContext)defaultHttpContext, new RouteData(), new ActionDescriptor());
            }
        }
    }
    

    And you should add this lines to startup:

    builder.Services.AddMvcCore().AddRazorViewEngine();
    builder.Services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
    builder.Services.AddScoped<ViewRenderer>();
    

    Use ViewRenderer class as follows:

       public class HomeController : Controller
        {
            private readonly ILogger<HomeController> _logger;
            private readonly ViewRenderer _viewRenderer;
    
            public HomeController(ILogger<HomeController> logger, ViewRenderer viewRenderer)
            {
                _logger = logger;
                _viewRenderer = viewRenderer;
            }
    
            public async Task<IActionResult> Index()
            {
                var renderedView =await 
                    _viewRenderer.RenderViewToStringAsync<MyViewModel>("_MyView", new MyViewModel() { Id = 1 });
                ViewData["RenderedView"]= renderedView;
                return View();
    
            }
            public class MyViewModel
            {
                public int Id { get; set; }
            }
        }