I want to (unit)test the function System.Web.Mvc.ViewEngines.Engines.FindPartialView and check the correct return of the HTML Code.
But every time I start the unit test, it throws an "Object reference not set to an instance of an object" exception.
I've tried to debug through the .net framework source but the debugger gets disoriented and jumps randomly / breaks with no message.
Now I want to know what element I’ve missed in the FakeControllerContext and how to fix it.
Here's my Code:
public static string RenderPartialViewToString(string viewName, object model, ControllerContext controller)
{
if (string.IsNullOrEmpty(viewName))
viewName = controller.RouteData.GetRequiredString("action");
controller.Controller.ViewData.Model = model;
using (var sw = new StringWriter())
{
//"Error: ***.Shop.UnitTests.RenderStuffTest.RenderPartialViewToStringTest-Test method threw an exception: System.NullReferenceException – Object reference not set to an instance of an object"
ViewEngineResult viewResult = ViewEngines.Engines.FindPartialView(controller, viewName);
controller.Controller.ViewData.Model = model;
controller.Controller.ViewBag.Part = true;
var viewContext = new ViewContext(controller, viewResult.View, controller.Controller.ViewData,
controller.Controller.TempData, sw);
viewResult.View.Render(viewContext, sw);
return sw.GetStringBuilder().ToString();
}
}
And here's my test:
[TestMethod]
public void RenderPartialViewToStringTest()
{
const string viewName = "_navi";
var customersController = new ArticleController();
customersController.ControllerContext = new FakeControllerContext(customersController) { RouteData =
{
Route =
new Route(
"{language}/{controller}/{action}/{id}",
new MvcRouteHandler())
,
RouteHandler = new MvcRouteHandler()
},
};
customersController.ControllerContext.RouteData.Values.Add("language", "German");
customersController.ControllerContext.RouteData.Values.Add("controller", "Article");
customersController.ControllerContext.RouteData.Values.Add("action", "Index");
customersController.ControllerContext.RouteData.Values.Add("id", "");
var model = (...);
string actual = RenderStuff.RenderPartialViewToString(viewName, model, customersController.ControllerContext);
(...)
}
For the mocking I’ve used Rhino.Mocks and the MvcFakes from Stephenwalther.com
I think that this thread can help you, you have to mock the ViewEngine then mock the FindPartialView call.
Related
I am trying to render a view to html string using the RenderViewToString method below. When calling the viewResult.View.Render and passing the viewContext, it results in the ViewBag being null in the .cshtml view. Example code of my renderer:
public string RenderViewToString(ControllerContext context, string viewName, object model)
{
if (string.IsNullOrEmpty(viewName))
viewName = context.RouteData.GetRequiredString("action");
ViewDataDictionary viewData = new ViewDataDictionary(model);
using (var sw = new StringWriter())
{
ViewEngineResult viewResult = ViewEngines.Engines.FindView(context, viewName, null);
ViewContext viewContext = new ViewContext(context, viewResult.View, viewData, new TempDataDictionary(), sw);
var thisViewBagHasValues = ViewBag;
var thisViewBagIsNull = viewContext.ViewBag;
viewResult.View.Render(viewContext, sw);
return sw.GetStringBuilder().ToString();
}
}
I think by calling the #ViewContext.Controller.ViewBag in the .cshtml file, we can then access the ViewBag data. However, is there a way to access the ViewBag in the .cshtml file without editing the view file? Such as setting a value to the ViewContext.ViewBag before calling the renderer?
I want to render partial view as string and i searched and found this article:
https://ppolyzos.com/2016/09/09/asp-net-core-render-view-to-string
public async Task<string> RenderToStringAsync(string viewName, object model)
{
var httpContext = new DefaultHttpContext { RequestServices = _serviceProvider };
var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
using (var sw = new StringWriter())
{
var viewResult = _razorViewEngine.FindView(actionContext, viewName, false);
if (viewResult.View == null)
{
throw new ArgumentNullException($"{viewName} does not match any available view");
}
var viewDictionary = new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary())
{
Model = model
};
var viewContext = new ViewContext(
actionContext,
viewResult.View,
viewDictionary,
new TempDataDictionary(actionContext.HttpContext, _tempDataProvider),
sw,
new HtmlHelperOptions()
);
await viewResult.View.RenderAsync(viewContext);
return sw.ToString();
}
}
but inner error happen:
Message: Cannot convert null to 'bool' because it is a non-nullable value type
StackTrace: at CallSite.Target(Closure , CallSite , Object )\r\n at CallSite.Target(Closure , CallSite , Object )\r\n at AspNetCore._Views_Shared_Comments_cshtml.<ExecuteAsync>d__39.MoveNext() in /Views/Shared/Comments.cshtml:line 7\r\n--- End of stack trace fr...
The error with me is in innerException, and I cannot know exactly what is the reason, but i think in that line of code:
var viewContext = new ViewContext(
actionContext,
viewResult.View,
viewDictionary,
new TempDataDictionary(actionContext.HttpContext, _tempDataProvider),
sw,
new HtmlHelperOptions()
);
I check line 7 in the view and found some variable not has value and fixed it, but another error appear:
Message: Index was out of range. Must be non-negative and less than the size of the collection.\r\nParameter name: index
StackTrace: at System.ThrowHelper.ThrowArgumentOutOfRange_IndexException()\r\n at System.Collections.Generic.List``1.get_Item(Int32 index)\r\n at Microsoft.AspNetCore.Mvc.Routing.UrlHelper.GetVirtualPathData(String routeName, RouteValueDictionary values)\r\n a...
I cannot reproduce the exact error but I have 2 suggestions:
1. Check viewResult is NULL or not first
The viewResult might be NULL hence viewResult.View might throw NullReferenceException. So I would do:
if (viewResult == null)
{
throw new ArgumentNullException($"{ viewname } is not found.");
}
2. await on View.RenderAsync()
var viewContext = new ViewContext(actionContext,
viewResult.View,
viewDictionary,
new TempDataDictionary(actionContext.HttpContext, _tempDataProvider),
sw,
new HtmlHelperOptions()
);
await viewResult.View.RenderAsync(viewContext);
return sw.ToString();
I had the same issue. After looking at this closer, I realized, that I had used a ViewData in the View, that had no value set. Hence the error "Cannot convert null to 'bool' because it is a non-nullable value type". Checking the value for Null, solved the issue for me and the view generated correctly.
You need to check Viewbag/Tempdata at /Views/Shared/Comments.cshtml:line 7.
You have not send value from controller to view, in this case it's null so why it's throw the runtime error.
Am trying to render a view as a string to be used as an email template. Am currently trying to implement this example:
https://weblog.west-wind.com/posts/2012/May/30/Rendering-ASPNET-MVC-Views-to-String
However am having difficulty with this section of code:
public ViewRenderer(ControllerContext controllerContext = null)
{
// Create a known controller from HttpContext if no context is passed
if (controllerContext == null)
{
if (HttpContext.Current != null)
controllerContext = CreateController<ErrorController>().ControllerContext;
else
throw new InvalidOperationException(
"ViewRenderer must run in the context of an ASP.NET " +
"Application and requires HttpContext.Current to be present.");
}
Context = controllerContext;
}
Visual Studio is giving me the following error:
"The type or namespace name 'ErrorController' could not be found (are you missing a using directive or an assembly reference?)"
Am probably missing something obvious but can't see what it is. Any ideas?
If you need to Render your view as a string, here is an extension method for the controller I wrote.
NB: I will try and find the exact link I used to help me with this, and update my answer when I find it.
Here is another link, describing this method.
This should do the trick:
public static string RenderViewToString(this Controller controller, string viewName, object model)
{
var context = controller.ControllerContext;
if (string.IsNullOrEmpty(viewName))
viewName = context.RouteData.GetRequiredString("action");
var viewData = new ViewDataDictionary(model);
using (var sw = new StringWriter())
{
var viewResult = ViewEngines.Engines.FindPartialView(context, viewName);
var viewContext = new ViewContext(context, viewResult.View, viewData, new TempDataDictionary(), sw);
viewResult.View.Render(viewContext, sw);
return sw.GetStringBuilder().ToString();
}
}
If you want to call this from a controller, you simply do the following:
var strView = this.RenderViewToString("YourViewName", yourModel);
In previous versions of ASP.NET it was possible, although not very simple, to render Razor views as strings. The methods I've seem are to use a fake controller, or also to use some external engine like RazorEngine.
Now, many things are changed with ASP.NET 5 and I was wondering whether or not this is now simpler than before. So in the new version of the framework is there one straightforward way to render Razor views as strings or we still need to use the methods from the previous versions?
I use the following types injected from the IServiceProvider:
ICompositeViewEngine viewEngine;
ITempDataProvider tempDataProvider;
IHttpContextAccessor httpContextAccessor;
I render the content using the following method:
private async Task<string> RenderView(string path, ViewDataDictionary viewDataDictionary, ActionContext actionContext)
{
using (var sw = new System.IO.StringWriter())
{
var viewResult = viewEngine.FindView(actionContext, path);
var viewContext = new ViewContext(actionContext, viewResult.View, viewDataDictionary, new TempDataDictionary(httpContextAccessor, tempDataProvider), sw);
await viewResult.View.RenderAsync(viewContext);
sw.Flush();
if (viewContext.ViewData != viewDataDictionary)
{
var keys = viewContext.ViewData.Keys.ToArray();
foreach (var key in keys)
{
viewDataDictionary[key] = viewContext.ViewData[key];
}
}
return sw.ToString();
}
}
I call it like this:
var path = "~/Views/Home/Index.cshtml";
var viewDataDictionary = new ViewDataDictionary(new Microsoft.AspNet.Mvc.ModelBinding.EmptyModelMetadataProvider(), new Microsoft.AspNet.Mvc.ModelBinding.ModelStateDictionary());
var actionContext = new ActionContext(httpContextAccessor.HttpContext, new Microsoft.AspNet.Routing.RouteData(), new ActionDescriptor());
viewDataDictionary.Model = null;
var text = await RenderView(path, viewDataDictionary, actionContext);
Of course, my viewDataDictionary and actionContext variables are set by another method for encapsulation. A modification to the new ViewDataDictionary line can result in a typed Model being bound to your View if you choose.
This code uses heavy usings, I think I've listed them below. Otherwise, VS2015 is pretty good about finding them.
using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Mvc.Rendering;
This was written under beta-3; it still builds, but some things may change. I'll try to come back here to update if it does.
There is a solution that I've used an year ago. I'm not sure if there is a new/better way to do, but it solves the problem.
public string RenderViewToString(string viewName, object model)
{
ViewData.Model = model;
using (var sw = new StringWriter())
{
var viewResult = ViewEngines.Engines.FindPartialView(ControllerContext, viewName);
var viewContext = new ViewContext(ControllerContext, viewResult.View, ViewData, TempData, sw);
viewResult.View.Render(viewContext, sw);
viewResult.ViewEngine.ReleaseView(ControllerContext, viewResult.View);
return sw.GetStringBuilder().ToString();
}
}
I'm creating a really simple ViewResult subclass called JavaScriptViewResult that, when executing, calls the base implementation and then sets the Content-Type of the response to text/javascript. In trying to unit test this class, I'm running across a slew of difficulties fulfilling all of the dependencies of the ASP.NET MVC stack.
Here is what my unit test, which uses Rhino, looks like so far:
[TestMethod]
public void TestExecuteAction()
{
var request = MockRepository.GenerateMock<HttpRequestBase>();
request.Expect(m => m.Url).Return(new Uri("/Test/JavaScript", UriKind.Relative));
var httpContext = MockRepository.GenerateMock<HttpContextBase>();
httpContext.Expect(m => m.Request).Return(request);
var controller = MockRepository.GenerateMock<ControllerBase>();
var virtualPathProvider = MockRepository.GenerateMock<VirtualPathProvider>();
var routeCollection = new RouteCollection(virtualPathProvider);
routeCollection.MapRoute("FakeRoute", "Test/JavaScript", new { controller = "Test", action = "JavaScript" });
var routeData = routeCollection.GetRouteData(httpContext);
var context = new ControllerContext(httpContext, routeData, controller);
var viewResult = new JavaScriptViewResult();
viewResult.ExecuteResult(context);
Assert.AreEqual("text/javascript", context.HttpContext.Response.ContentType);
}
The latest exception when running the test is a NullReferenceException deep within the bowels of System.Web.Routing.Route.GetRouteData(HttpContextBase httpContext).
How do I set up all of the dependencies for executing a ViewResult? Are there any techniques for making this simpler? Alternately, is there a different way I can utilize the MVC view engine to generate JavaScript that will set the proper Content-Type for the response?
I figured out how to meet the minimum requirements of ViewResult. One problem I was encountering was mocking the process of finding the view. This was avoidable by ensuring that the View property of my object was populated. Here is my working test:
[TestMethod]
public void TestExecuteAction()
{
var response = MockRepository.GenerateStub<HttpResponseBase>();
response.Output = new StringWriter();
var httpContext = MockRepository.GenerateMock<HttpContextBase>();
httpContext.Expect(m => m.Response).Return(response);
var routeData = new RouteData();
routeData.Values.Add("action", "FakeAction");
var context = new ControllerContext(httpContext, routeData, MockRepository.GenerateMock<ControllerBase>());
var viewResult = new JavaScriptViewResult();
viewResult.View = MockRepository.GenerateMock<IView>();
viewResult.ExecuteResult(context);
Assert.AreEqual("text/javascript", context.HttpContext.Response.ContentType);
}