简体   繁体   中英

How to render an MVC partial view (containing a Kendo Grid) as an html string

I'm trying to render an MVC partial view (containing a Kendo Grid) as an html string using a "Fake Controller Context" ..

I want to know if there is a way to create the proper Fake Controller for this.. so I can have access to an MVC partial view (containing a Kendo Grid) from a static method .

Any help with this is appreciated. Thanks in advance!!

I kept getting this error:

Value cannot be null. Parameter name: controllerContext Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

 Exception Details: System.ArgumentNullException: Value cannot be null. Parameter name: controllerContext Source Error: Line 8: @(Html.Kendo().Grid<SimpleKendoModelData>() 

Stack Trace:

[ArgumentNullException: Value cannot be null.
Parameter name: controllerContext]
   System.Web.Mvc.ChildActionValueProviderFactory.GetValueProvider(ControllerContext controllerContext) +137
   System.Web.Mvc.ValueProviderFactoryCollection.GetValueProvider(ControllerContext controllerContext) +80
   System.Web.Mvc.ControllerBase.get_ValueProvider() +39
   Kendo.Mvc.UI.Grid`1.ProcessDataSource() +254
   Kendo.Mvc.UI.Grid`1.WriteHtml(HtmlTextWriter writer) +818
   Kendo.Mvc.UI.WidgetBase.ToHtmlString() +102
   Kendo.Mvc.UI.Fluent.WidgetBuilderBase`2.ToHtmlString() +15
   System.Web.WebPages.WebPageBase.Write(Object value) +103
   ASP._Page_Views_Reporting_ReportElement_SimpleKendoGrid_cshtml.Execute() in c:\Users\xxx\Documents\Visual Studio 2015\Projects\xxx\Views\Reporting\ReportElement_SimpleKendoGrid.cshtml:8
   System.Web.WebPages.WebPageBase.ExecutePageHierarchy() +253
   System.Web.Mvc.WebViewPage.ExecutePageHierarchy() +148
   System.Web.WebPages.WebPageBase.ExecutePageHierarchy(WebPageContext pageContext, TextWriter writer, WebPageRenderingBase startPage) +122
   xxx.Api.Ui.Helpers.ViewRenderer.RenderViewToStringInternal(String viewPath, Object model, Boolean partial) in C:\Users\xxx\Documents\Visual Studio 2015\Projects\xxx\Helpers\ViewRenderer.cs:218
   xxx.Api.Ui.Helpers.ViewRenderer.RenderPartialView(String viewPath, Object model) in C:\Users\xxx\Documents\Visual Studio 2015\Projects\xxx\Helpers\ViewRenderer.cs:81
   xxx.Api.Ui.Helpers.ViewRenderer.RenderPartialView(String viewPath, Object model, ControllerContext controllerContext) in C:\Users\xxx\Documents\Visual Studio 2015\Projects\xxx\Helpers\ViewRenderer.cs:149
   xxx.Api.Ui.Controllers.ReportingController.Test() in C:\Users\xxx\Documents\Visual Studio 2015\Projects\xxx\Controllers\ReportingController.cs:86
   lambda_method(Closure , ControllerBase , Object[] ) +87
   System.Web.Mvc.ReflectedActionDescriptor.Execute(ControllerContext controllerContext, IDictionary`2 parameters) +229
   System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary`2 parameters) +35
   System.Web.Mvc.Async.AsyncControllerActionInvoker.<BeginInvokeSynchronousActionMethod>b__39(IAsyncResult asyncResult, ActionInvocation innerInvokeState) +39
   System.Web.Mvc.Async.WrappedAsyncResult`2.CallEndDelegate(IAsyncResult asyncResult) +67
   System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeActionMethod(IAsyncResult asyncResult) +42
   System.Web.Mvc.Async.AsyncInvocationWithFilters.<InvokeActionMethodFilterAsynchronouslyRecursive>b__3d() +72
   System.Web.Mvc.Async.<>c__DisplayClass46.<InvokeActionMethodFilterAsynchronouslyRecursive>b__3f() +386
   System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeActionMethodWithFilters(IAsyncResult asyncResult) +42
   System.Web.Mvc.Async.<>c__DisplayClass2b.<BeginInvokeAction>b__1c() +38
   System.Web.Mvc.Async.<>c__DisplayClass21.<BeginInvokeAction>b__1e(IAsyncResult asyncResult) +186
   System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeAction(IAsyncResult asyncResult) +38
   System.Web.Mvc.Controller.<BeginExecuteCore>b__1d(IAsyncResult asyncResult, ExecuteCoreState innerState) +29
   System.Web.Mvc.Async.WrappedAsyncVoid`1.CallEndDelegate(IAsyncResult asyncResult) +65
   System.Web.Mvc.Controller.EndExecuteCore(IAsyncResult asyncResult) +53
   System.Web.Mvc.Async.WrappedAsyncVoid`1.CallEndDelegate(IAsyncResult asyncResult) +36
   System.Web.Mvc.Controller.EndExecute(IAsyncResult asyncResult) +38
   System.Web.Mvc.MvcHandler.<BeginProcessRequest>b__5(IAsyncResult asyncResult, ProcessRequestState innerState) +44
   System.Web.Mvc.Async.WrappedAsyncVoid`1.CallEndDelegate(IAsyncResult asyncResult) +65
   System.Web.Mvc.MvcHandler.EndProcessRequest(IAsyncResult asyncResult) +38
   System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +399
   System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +157

This is my testing method in a controller:

[AllowAnonymous]
public ActionResult Test()
{
    var model = new SimpleKendoModel();
    for (int x = 0; x < 10; x++)
    {
        var result = new SimpleKendoModelData();
        result.Data = x;
        model.Results.Add(result);
    }

    var currentContext = ControllerExtensions.GetFakeControllerContext();

    //This commented out code works
    //var html =  ViewRenderer.RenderPartialView("~/Views/Reporting/ReportElement_SimpleKendoGrid.cshtml", model, ControllerContext);
    var html = ViewRenderer.RenderPartialView("~/Views/Reporting/ReportElement_SimpleKendoGrid.cshtml", model, currentContext);

    return View();
}

ControllerExtensions Class:

public static class ControllerExtensions
{  
    public static ControllerContext GetFakeControllerContext()
    {
        var currentContext = new HttpContext(new HttpRequest(null, "http://tempuri.org", null), new HttpResponse(null));

        var st = new StringWriter();
        var context = new HttpContextWrapper(currentContext);
        var routeData = new RouteData();
        routeData.Values.Add("controller", "someValue");

        var controllerContext = new ControllerContext(new RequestContext(context, routeData), new FakeController());

        return controllerContext;
    }
}

View Renderer Class:

// Read: https://weblog.west-wind.com/posts/2012/may/30/rendering-aspnet-mvc-views-to-string

/// <summary>
/// Class that renders MVC views to a string using the
/// standard MVC View Engine to render the view. 
/// </summary>
public class ViewRenderer
{
    /// <summary>
    /// Required Controller Context
    /// </summary>
    protected ControllerContext Context { get; set; }

    /// <summary>
    /// Initializes the ViewRenderer with a Context.
    /// </summary>
    /// <param name="controllerContext">
    /// If you are running within the context of an ASP.NET MVC request pass in
    /// the controller's context. 
    /// Only leave out the context if no context is otherwise available.
    /// </param>
    public ViewRenderer(ControllerContext controllerContext = null)
    {
        // Create a known controller from HttpContext if no context is passed
        if (controllerContext == null)
        {
            if (HttpContext.Current != null)
            {
                var currentContext = HttpContext.Current;
                var context = new HttpContextWrapper(currentContext);
                var routeData = new RouteData();
                controllerContext = new ControllerContext(new RequestContext(context, routeData), new FakeController());
            }
            else
                throw new InvalidOperationException(
                    "ViewRenderer must run in the context of an ASP.NET " +
                    "Application and requires HttpContext.Current to be present.");
        }
        Context = controllerContext;
    }

    /// <summary>
    /// Renders a full MVC view to a string. Will render with the full MVC
    /// View engine including running _ViewStart and merging into _Layout        
    /// </summary>
    /// <param name="viewPath">
    /// The path to the view to render. Either in same controller, shared by 
    /// name or as fully qualified ~/ path including extension
    /// </param>
    /// <param name="model">The model to render the view with</param>
    /// <returns>String of the rendered view or null on error</returns>
    public string RenderView(string viewPath, object model)
    {
        return RenderViewToStringInternal(viewPath, model, false);
    }


    /// <summary>
    /// Renders a partial MVC view to string. Use this method to render
    /// a partial view that doesn't merge with _Layout and doesn't fire
    /// _ViewStart.
    /// </summary>
    /// <param name="viewPath">
    /// The path to the view to render. Either in same controller, shared by 
    /// name or as fully qualified ~/ path including extension
    /// </param>
    /// <param name="model">The model to pass to the viewRenderer</param>
    /// <returns>String of the rendered view or null on error</returns>
    public string RenderPartialView(string viewPath, object model)
    {
        return RenderViewToStringInternal(viewPath, model, true);
    }

    /// <summary>
    /// Renders a partial MVC view to string. Use this method to render
    /// a partial view that doesn't merge with _Layout and doesn't fire
    /// _ViewStart.
    /// </summary>
    /// <param name="viewPath">
    /// The path to the view to render. Either in same controller, shared by 
    /// name or as fully qualified ~/ path including extension
    /// </param>
    /// <param name="model">The model to pass to the viewRenderer</param>
    /// <param name="controllerContext">Active Controller context</param>
    /// <returns>String of the rendered view or null on error</returns>
    public static string RenderView(string viewPath, object model,
                                    ControllerContext controllerContext)
    {
        ViewRenderer renderer = new ViewRenderer(controllerContext);
        return renderer.RenderView(viewPath, model);
    }

    /// <summary>
    /// Renders a partial MVC view to string. Use this method to render
    /// a partial view that doesn't merge with _Layout and doesn't fire
    /// _ViewStart.
    /// </summary>
    /// <param name="viewPath">
    /// The path to the view to render. Either in same controller, shared by 
    /// name or as fully qualified ~/ path including extension
    /// </param>
    /// <param name="model">The model to pass to the viewRenderer</param>
    /// <param name="controllerContext">Active Controller context</param>
    /// <param name="errorMessage">optional out parameter that captures an error message instead of throwing</param>
    /// <returns>String of the rendered view or null on error</returns>
    public static string RenderView(string viewPath, object model,
                                    ControllerContext controllerContext,
                                    out string errorMessage)
    {
        errorMessage = null;
        try
        {
            ViewRenderer renderer = new ViewRenderer(controllerContext);
            return renderer.RenderView(viewPath, model);
        }
        catch (Exception ex)
        {
            errorMessage = ex.GetBaseException().Message;
        }
        return null;
    }

    /// <summary>
    /// Renders a partial MVC view to string. Use this method to render
    /// a partial view that doesn't merge with _Layout and doesn't fire
    /// _ViewStart.
    /// </summary>
    /// <param name="viewPath">
    /// The path to the view to render. Either in same controller, shared by 
    /// name or as fully qualified ~/ path including extension
    /// </param>
    /// <param name="model">The model to pass to the viewRenderer</param>
    /// <param name="controllerContext">Active controller context</param>
    /// <returns>String of the rendered view or null on error</returns>
    public static string RenderPartialView(string viewPath, object model,
                                            ControllerContext controllerContext)
    {
        ViewRenderer renderer = new ViewRenderer(controllerContext);
        return renderer.RenderPartialView(viewPath, model);
    }

    /// <summary>
    /// Renders a partial MVC view to string. Use this method to render
    /// a partial view that doesn't merge with _Layout and doesn't fire
    /// _ViewStart.
    /// </summary>
    /// <param name="viewPath">
    /// The path to the view to render. Either in same controller, shared by 
    /// name or as fully qualified ~/ path including extension
    /// </param>
    /// <param name="model">The model to pass to the viewRenderer</param>
    /// <param name="controllerContext">Active controller context</param>
    /// <param name="errorMessage">optional output parameter to receive an error message on failure</param>
    /// <returns>String of the rendered view or null on error</returns>
    public static string RenderPartialView(string viewPath, object model,
                                            ControllerContext controllerContext,
                                            out string errorMessage)
    {
        errorMessage = null;
        try
        {
            ViewRenderer renderer = new ViewRenderer(controllerContext);
            return renderer.RenderPartialView(viewPath, model);
        }
        catch (Exception ex)
        {
            errorMessage = ex.GetBaseException().Message;
        }
        return null;
    }

    /// <summary>
    /// Internal method that handles rendering of either partial or 
    /// or full views.
    /// </summary>
    /// <param name="viewPath">
    /// The path to the view to render. Either in same controller, shared by 
    /// name or as fully qualified ~/ path including extension
    /// </param>
    /// <param name="model">Model to render the view with</param>
    /// <param name="partial">Determines whether to render a full or partial view</param>
    /// <returns>String of the rendered view</returns>
    protected string RenderViewToStringInternal(string viewPath, object model,
                                                bool partial = false)
    {
        // first find the ViewEngine for this view
        ViewEngineResult viewEngineResult = null;
        if (partial)
            viewEngineResult = ViewEngines.Engines.FindPartialView(Context, viewPath);
        else
            viewEngineResult = ViewEngines.Engines.FindView(Context, viewPath, null);

        if (viewEngineResult == null)
            throw new FileNotFoundException("ViewCouldNotBeFound");

        // get the view and attach the model to view data
        var view = viewEngineResult.View;
        Context.Controller.ViewData.Model = model;

        string result = null;

        using (var sw = new StringWriter())
        {
            var ctx = new ViewContext(Context, view,
                                        Context.Controller.ViewData,
                                        Context.Controller.TempData,
                                        sw);
            view.Render(ctx, sw);
            result = sw.ToString();
        }

        return result;
    }


    /// <summary>
    /// Creates an instance of an MVC controller from scratch 
    /// when no existing ControllerContext is present       
    /// </summary>
    /// <typeparam name="T">Type of the controller to create</typeparam>
    /// <returns></returns>
    public static T CreateController<T>(RouteData routeData = null)
                where T : Controller, new()
    {
        T controller = new T();

        // Create an MVC Controller Context
        HttpContextBase wrapper = null;
        if (HttpContext.Current != null)
            wrapper = new HttpContextWrapper(System.Web.HttpContext.Current);
        //else
        //    wrapper = CreateHttpContextBase(writer);


        if (routeData == null)
            routeData = new RouteData();

        if (!routeData.Values.ContainsKey("controller") && !routeData.Values.ContainsKey("Controller"))
            routeData.Values.Add("controller", controller.GetType().Name
                                                        .ToLower()
                                                        .Replace("controller", ""));

        controller.ControllerContext = new ControllerContext(wrapper, routeData, controller);
        return controller;
    }

}

~/Views/Reporting/ReportElement_SimpleKendoGrid.cshtml Partial View:

@{
    ViewBag.Title = "Index";
    Layout = "~/Views/Shared/_ReportTemplateLayout.cshtml";
}
@model SimpleKendoModel

@(Html.Kendo().Grid<SimpleKendoModelData>()
        .Name("Results")
        .BindTo(Model.Results)
        .Columns(columns =>
        {
            columns.Bound(x => x.Data).Title("Data").Width(250);
        })
        .DataSource(datasource => datasource
            .Ajax()
            .PageSize(5000)
            .ServerOperation(false)
        )
)

~/Views/Shared/_ReportTemplateLayout.cshtml:

@RenderBody()

MVC Partial View Model:

public class SimpleKendoModel
{
    public SimpleKendoModel()
    {
        Results = new List<SimpleKendoModelData>();
    }

    public List<SimpleKendoModelData> Results { get; set;}
}
public class SimpleKendoModelData
{
    public SimpleKendoModelData()
    {
        Data = 0;
    }

    public int Data { get; set; }
}

This is the partial view result

Thanks to this post:

Setting HttpContext.Current.Session in a unit test

I've actually solved this problem..

Using the class below, I was able to successfully create a template HTML using an MVC Razor partial view (containing Kendo Grids) for email templates purposes.. and allows me to call it from a static method.

I created a class called ControllerExtensions

public static class ControllerExtensions
{
    public static ControllerContext GetFakeControllerContext()
    {
        HttpContext.Current = HttpContext.Current ?? GetFakeHttpContext();

        var currentContext = HttpContext.Current;

        var controller = CreateController<FakeController>();

        var st = new StringWriter();
        var context = new HttpContextWrapper(currentContext);
        var routeData = GetFakeRouteData(controller);

        var controllerContext = new ControllerContext(new RequestContext(context, routeData), controller);

        return controllerContext;
    }

    public static HttpContext GetFakeHttpContext()
    {
        var httpRequest = new HttpRequest("", "http://stackoverflow/", "");
        var stringWriter = new StringWriter();
        var httpResponse = new HttpResponse(stringWriter);
        var httpContext = new HttpContext(httpRequest, httpResponse);

        var sessionContainer = new HttpSessionStateContainer("id", new SessionStateItemCollection(),
                                                new HttpStaticObjectsCollection(), 10, true,
                                                HttpCookieMode.AutoDetect,
                                                SessionStateMode.InProc, false);

        httpContext.Items["AspSession"] = typeof(HttpSessionState).GetConstructor(
                                    BindingFlags.NonPublic | BindingFlags.Instance,
                                    null, CallingConventions.Standard,
                                    new[] { typeof(HttpSessionStateContainer) },
                                    null)
                            .Invoke(new object[] { sessionContainer });

        return httpContext;
    }

    public static RouteData GetFakeRouteData(Controller controller)
    {
        var routeData = new RouteData();

        if (!routeData.Values.ContainsKey("controller") && !routeData.Values.ContainsKey("Controller"))
            routeData.Values.Add("controller", controller.GetType().Name
                                                        .ToLower()
                                                        .Replace("controller", ""));

        return routeData;
    }

    public static T CreateController<T>(RouteData routeData = null)
        where T : Controller, new()
    {
        T controller = new T();

        // Create an MVC Controller Context
        var wrapper = new HttpContextWrapper(System.Web.HttpContext.Current);

        if (routeData == null)
            routeData = new RouteData();

        if (!routeData.Values.ContainsKey("controller") && !routeData.Values.ContainsKey("Controller"))
            routeData.Values.Add("controller", controller.GetType().Name
                                                        .ToLower()
                                                        .Replace("controller", ""));

        controller.ControllerContext = new ControllerContext(wrapper, routeData, controller);
        return controller;
    }
}

public class FakeController : Controller
{

}

And use it the following way:

public static string GetHtml()
{
    var model = new SimpleKendoModel();
    for (int x = 0; x < 10; x++)
    {
        var result = new SimpleKendoModelData();
        result.Data = x;
        model.Results.Add(result);
    }

    var currentContext = ControllerExtensions.GetFakeControllerContext();

    var html = ViewRenderer.RenderPartialView("~/Views/Reporting/ReportElement_SimpleKendoGrid.cshtml", model, currentContext);

    return html;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM