views:

25

answers:

2

I need to generate html (for the body of an email message) specific to a Customer object
I thought of making a View that gets a Customer object and renders the appropriate text.

Is there a way to call a view and get the rendered output without associating it to a controller action?

Ideally in pseydocode I would do something like this

customer = new Customer();
view = new GetCustomerEmailBodyView(customer);
string htmlBody = view.SomeFunctionToRenderViewAndGetOutput()

I have found a solution to get the HTML of a view here that has an action returning a StringResult (inherits from ViewResult) instead of ActionResult which exposes an Html property.
However I still have to make a custom action to call it, and I don't like the fact that it depends on the ControllerContext making it hard to test it.

Is what I am requesting against the MVC principals? How should my code be structured for this scenario?

+1  A: 

Are you saying that you want some program to be able to leverage a View without being in a controller context at all, or are you saying that you want to be able to render a view into a string from within a controller, without calling some other controller?

For the former, I can't be of much assistance, but for the latter, we have this method in the base controller type that we inherit with all our other controllers:

    /// <summary>
    /// Generates a string based on the given PartialViewResult.
    /// </summary>
    /// <param name="partialViewResult"></param>
    /// <returns></returns>
    protected internal string RenderPartialViewToString(ViewResultBase partialViewResult)
    {
        Require.ThatArgument(partialViewResult != null);
        var context = ControllerContext;
        Require.That(context != null);
        using (var sw = new StringWriter())
        {
            if (string.IsNullOrEmpty(partialViewResult.ViewName))
            {
                partialViewResult.ViewName = context.RouteData.GetRequiredString("action");
            }
            ViewEngineResult result;
            if (partialViewResult.View == null)
            {
                result = partialViewResult.ViewEngineCollection.FindPartialView(context, partialViewResult.ViewName);
                Require.That(result.View != null,
                             () => new InvalidOperationException(
                                       "Unable to find view. Searched in: " +
                                       string.Join(",", result.SearchedLocations)));
                partialViewResult.View = result.View;
            }

            var view = partialViewResult.View;
            var viewContext = new ViewContext(context, view, partialViewResult.ViewData,
                                              partialViewResult.TempData, sw);
            view.Render(viewContext, sw);
            return sw.ToString();
        }
    }

Usage:

public ActionResult MyAction(...) 
{
    var myModel = GetMyModel(...);
    string viewString = RenderPartialViewToString(PartialView("MyView", myModel));
    // do something with the string
    return someAction;
}

We actually use this in an event-based AJAX model, where most of our actions actually just return an AJAX-encoded list of client-side events, and some of those client-side events may be to update a particular DOM element with the string produces by rendering this partial view.

StriplingWarrior
I like the Require.That() syntax - is that something you wrote yourself, or is it part of a (publicly available) library?
Tomas Lycken
We wrote the "Require" class ourselves. I wanted to use Contract.Requires, but it adds a lot of overhead. I copied most of this code out of RenderPartial using the .NET Reflector, and then tweaked it to fit our needs.
StriplingWarrior
Require.That() rocks!
Dimitris Baltas
Wow, I never thought I'd get so much response from Require.That. Should I see if I can convince my boss to make an open-source library out of it?
StriplingWarrior
+1  A: 

Original code from here

protected string RenderPartialViewToString(string viewName, object model) {
    if (string.IsNullOrEmpty(viewName))
        viewName = ControllerContext.RouteData.GetRequiredString("action");
    ViewData.Model = model;
    using (StringWriter sw = new StringWriter()) {
        ViewEngineResult viewResult = ViewEngines.Engines.FindPartialView(ControllerContext, viewName);
        ViewContext viewContext = new ViewContext(ControllerContext, viewResult.View, ViewData, TempData, sw);
        viewResult.View.Render(viewContext, sw);

        return sw.GetStringBuilder().ToString();
    }
}
Lorenzo
thank you, pretty straight forward.
Dimitris Baltas
you are welcome :)
Lorenzo