如何将MVC局部视图(包含Kendo Grid)渲染为html字符串

时间:2017-02-15 12:20:36

标签: c# asp.net-mvc razor kendo-grid kendo-asp.net-mvc

我正在尝试使用“伪控制器上下文”将MVC局部视图(包含Kendo Grid)渲染为html字符串..

我想知道是否有办法为此创建合适的假控制器..所以我可以从静态方法 访问MVC局部视图(包含Kendo网格)。

对此有任何帮助表示赞赏。在此先感谢!!

我一直收到此错误:

  

值不能为空。       参数名称:controllerContext       描述:执行当前Web请求期间发生未处理的异常。请查看堆栈跟踪了解更多信息   有关错误的信息以及它在代码中的起源。

Exception Details: System.ArgumentNullException: Value cannot be null.
Parameter name: controllerContext

Source Error:

Line 8:  @(Html.Kendo().Grid<SimpleKendoModelData>()

堆栈追踪:

[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

这是我在控制器中的测试方法:

[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类:

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;
    }
}

查看渲染器类:

// 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局部视图:

@{
    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)
        )
)

〜/查看/共享/ _ReportTemplateLayout.cshtml:

@RenderBody()

MVC部分视图模型:

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

1 个答案:

答案 0 :(得分:0)

感谢此帖:

Setting HttpContext.Current.Session in a unit test

我实际上已经解决了这个问题..

使用下面的类,我能够使用MVC Razor局部视图(包含Kendo网格)成功创建模板HTML以用于电子邮件模板..并允许我从静态方法调用它。

我创建了一个名为 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
{

}

并按以下方式使用:

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;
}