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?

6
  • why don't you just return a string from your ActionResult? Commented Sep 5, 2016 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. Commented Sep 5, 2016 at 14:18
  • Take a look at this SO question Commented Sep 5, 2016 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... Commented Sep 5, 2016 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? Commented Sep 5, 2016 at 14:22

1 Answer 1

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();
    }
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.