2017-02-15 27 views
0

Я пытаюсь отобразить частичный вид MVC (содержащий сетку Kendo) в виде строки html с использованием «контекста поддельного контроллера».Как визуализировать частичный вид MVC (содержащий сетку Kendo) в виде строки html

Я хочу знать, есть ли способ создать правильный Fake Controller для этого .. поэтому я могу иметь доступ к частичному представлению MVC (содержащему сетку Kendo) из статического метода.

Любая помощь с этим оценивается. Заранее спасибо!!

Я постоянно получаю эту ошибку:

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>() 

трассировки стека:

[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 Cl жопа:

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

Посмотреть Renderer Класс:

// 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/Отчетность/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) 
     ) 
) 

~/Просмотров/Shared/_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

+0

Проблема может исходить от линии 'общественного ViewRenderer (ControllerContext controllerContext = нуль)'. –

+0

@HizkiSunjaya Где проходит модель на вид в вашем actionMethod? –

+0

@ TânNguyễn Я добавил ** трассировку стека ** для вас, чтобы лучше увидеть проблему, у меня есть –

ответ

0

Благодаря этому сообщению:

Setting HttpContext.Current.Session in a unit test

Я на самом деле решил эту проблему ..

Использование класса ниже, я смог успешно c создать шаблон HTML с использованием частичного представления MVC Razor (содержащего Grid Kendo) для целей шаблонов электронной почты .. и позволяет мне называть его статическим методом.

Я создал класс под названием 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; 
}