Render MVC PartialView into SignalR response

2019-01-21 13:03发布

问题:

I would like to render a PartialView to an HTML string so I can return it to a SignalR ajax request.

Something like:

SignalR Hub (mySignalHub.cs)

public class mySignalRHub: Hub
{
    public string getTableHTML()
    {
        return PartialView("_MyTablePartialView", GetDataItems()) // *How is it possible to do this*
    }
}

Razor PartialView (_MyTablePartialView.cshtml)

@model IEnumerable<DataItem>

<table>
    <tbody>
        @foreach (var dataItem in Model)
        {
        <tr>
            <td>@dataItem.Value1</td>
            <td>@dataItem.Value2</td>
        </tr>
        }
    </tbody>
</table>

HTML (MySignalRWebPage.html)

<Script>
    ...      
    //Get HTML from SignalR function call
    var tableHtml = $.connection.mySignalRHub.getTableHTML();

    //Inject into div
    $('#tableContainer).html(tableHtml);
</Script>

<div id="tableContainer"></div>

My problem is that I can't seem to render a PartialView outside of a Controller. Is it even possible to render a PartialView outside of a Controller? It would be very nice to still be able to leverage the awesome HTML generating abilities that come with Razor.

Am I going about this all wrong? Is there another way?

回答1:

Here, this is what I use in Controllers for ajax, I modified it a bit so it can be called from method instead of controller, method returnView renders your view and returns HTML string so you can insert it with JS/jQuery into your page when you recive it on client side:

  public static string RenderPartialToString(string view, object model, ControllerContext Context)
        {
            if (string.IsNullOrEmpty(view))
            {
                view = Context.RouteData.GetRequiredString("action");
            }

            ViewDataDictionary ViewData = new ViewDataDictionary();

            TempDataDictionary TempData = new TempDataDictionary();

            ViewData.Model = model;

            using (StringWriter sw = new StringWriter())
            {
                ViewEngineResult viewResult = ViewEngines.Engines.FindPartialView(Context, view);

                ViewContext viewContext = new ViewContext(Context, viewResult.View, ViewData, TempData, sw);

                viewResult.View.Render(viewContext, sw);

                return sw.GetStringBuilder().ToString();
            }
        }

        //"Error" should be name of the partial view, I was just testing with partial error view
        //You can put whichever controller you want instead of HomeController it will be the same
        //You can pass model instead of null
        private string returnView()
        {
            var controller = new HomeController();
            controller.ControllerContext = new ControllerContext(HttpContext,new System.Web.Routing.RouteData(), controller);
            return RenderPartialToString("Error", null, new ControllerContext(controller.Request.RequestContext, controller));
        }

I didn't test it on a Hub but it should work.



回答2:

Probably the best choice is to use RazorEngine, as Wim is suggesting.

public class mySignalRHub: Hub
{
    public string getTableHTML()
    {
        var viewModel = new[] { new DataItem { Value1 = "v1", Value2 = "v2" } };

        var template = File.ReadAllText(Path.Combine(
            AppDomain.CurrentDomain.BaseDirectory,
            @"Views\PathToTablePartialView\_MyTablePartialView.cshtml"));

        return Engine.Razor.RunCompile(template, "templateKey", null, viewModel);
    }
}


回答3:

Further to the answer provided by @user1010609 above, I struggled through this as well and have ended up with a function that returns the rendered PartialView given a controller name, path to the view and model.

Takes account of the fact you don't have a controller and hence none of the usual state as coming from a SignalR event.

public static string RenderPartialView(string controllerName, string partialView, object model)
{
    var context = new HttpContextWrapper(System.Web.HttpContext.Current) as HttpContextBase;

    var routes = new System.Web.Routing.RouteData();
    routes.Values.Add("controller", controllerName);

    var requestContext = new RequestContext(context, routes);

    string requiredString = requestContext.RouteData.GetRequiredString("controller");
    var controllerFactory = ControllerBuilder.Current.GetControllerFactory();
    var controller = controllerFactory.CreateController(requestContext, requiredString) as ControllerBase;

    controller.ControllerContext = new ControllerContext(context, routes, controller);      

    var ViewData = new ViewDataDictionary();

    var TempData = new TempDataDictionary();

    ViewData.Model = model;

    using (var sw = new StringWriter())
    {
        var viewResult = ViewEngines.Engines.FindPartialView(controller.ControllerContext, partialView);
        var viewContext = new ViewContext(controller.ControllerContext, viewResult.View, ViewData, TempData, sw);

        viewResult.View.Render(viewContext, sw);
        return sw.GetStringBuilder().ToString();
    }
}

You would call it with something similar to:

RenderPartialView("MyController", "~/Views/MyController/_partialView.cshtml", model);


回答4:

Have you thought about using a razor template engine like http://razorengine.codeplex.com/ ? You can't use it to parse partial views but you can use it to parse razor templates, which are almost similar to partial views.



回答5:

How about using the RazorEngineHost and RazorTemplateEngine. I found this nice article that might be what you're looking for. It's about hosting Razor outside of ASP.NET (MVC).



回答6:

Based on the answers supplied to asimilar question below, I would suggest using

Html.Partial(partialViewName)

It returns an MvcHtmlString, which you should able to use as the content of your SignalR reponse. I have not tested this, however.

Stack Overflow Question: Is it possible to render a view outside a controller?