2

I have a ASP.NET MVC controller with a bunch of action methods returning ViewResult. Now, I need to be able to change the result of the action based on a certain URL parameter in the following way:

  • If the parameter is not present, just return the ViewResult as it is
  • If the parameter is present, take the ViewResult from the action that was just executed, render the view into a string, and return FileStreamResult containing this string (raw HTML) + some additional info (not relevant for the question)

I've tried to do this by overriding OnActionExecuted in my controller:

protected override void OnActionExecuted(ActionExecutedContext filterContext)
{
    base.OnActionExecuted(filterContext);
    var viewResult = filterContext.Result as ViewResult;
    if (viewResult != null /* && certain URL param present*/)
    {
        string rawHtml = RenderViewIntoString(viewResult);
        filterContext.Result = new FileStreamResult(new MemoryStream(Encoding.UTF8.GetBytes(rawHtml)), "application/octet-stream");
    }
}

But I can't find a way to implement RenderViewIntoString, because for some reason viewResult.View is null here.

How can I render the view into a string here?

Andre Borges
  • 1,360
  • 14
  • 37
  • why don't you just return a string from your ActionResult? – Jordy van Eijk Sep 05 '16 at 14:15
  • @JordyvanEijk, because I have a lot of actions in my controller for which I need to implement the same logic. I want to have it all in one method. – Andre Borges Sep 05 '16 at 14:18
  • Take a look at [this SO question](http://stackoverflow.com/questions/553936/in-mvc-how-do-i-return-a-string-result) – Jordy van Eijk Sep 05 '16 at 14:18
  • just make a method that does it all and call that method from every action. And have that method return something like a string or a ContentResult... – Jordy van Eijk Sep 05 '16 at 14:19
  • @JordyvanEijk, by "a lot" I mean 50+ methods. What you're suggesting is not very convenient. Is there no way to do it using action filters? – Andre Borges Sep 05 '16 at 14:22
  • Well for starters you need to give some more information. Some code sample of your Controller and how you want to call your controller. If you can tell u that we maybe can find a way. Otherwise calling a method 50 times is better then something else because you have 50 Actions. But i doubt that 50 actions on 1 controller is normal. I never had so many action on one controller :) – Jordy van Eijk Sep 05 '16 at 14:28

1 Answers1

2

viewResult.View is filled only when the view result is executed in the context of a controller (see ExecuteResult method in MVC source code). The method OnActionExecuted is called earlier in the pipeline, that's why viewResult.View is null in your case.

What you need to do is manually find the view using ViewEngineCollection and then render it:

private static string RenderViewIntoString(ViewResult viewResult, ActionExecutedContext filterContext)
{
    string viewName = !string.IsNullOrEmpty(viewResult.ViewName) ? viewResult.ViewName : filterContext.ActionDescriptor.ActionName;

    IView view = viewResult.ViewEngineCollection.FindView(filterContext.Controller.ControllerContext, viewName, viewResult.MasterName).View;

    if (view == null)
    {
        throw new InvalidOperationException($"The view '{viewName}' or its master was not found");
    }

    using (var stringWriter = new StringWriter())
    {
        var viewContext = new ViewContext(filterContext.Controller.ControllerContext, view, filterContext.Controller.ViewData, filterContext.Controller.TempData, stringWriter);
        view.Render(viewContext, stringWriter);
        return stringWriter.ToString();
    }
}
holdenmcgrohen
  • 1,031
  • 2
  • 9
  • 30