Как визуализировать представление ASP.NET MVC в виде строки?

485

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

Возможно ли это в ASP.NET MVC beta?

Я пробовал несколько примеров:

1. RenderPartial к String в ASP.NET MVC Beta

Если я использую этот пример, я получаю сообщение «Невозможно перенаправить после отправки заголовков HTTP».

2. MVC Framework: захват вывода представления

Если я использую это, мне кажется, что я не могу выполнить redirectToAction, так как он пытается отобразить представление, которое может не существовать. Если я верну вид, он полностью испорчен и выглядит не совсем правильно.

У кого-нибудь есть какие-либо идеи / решения этих проблем, которые у меня есть, или есть предложения для лучших?

Большое спасибо!

Ниже приведен пример. Я пытаюсь создать метод GetViewForEmail :

public ActionResult OrderResult(string ref)
{
    //Get the order
    Order order = OrderService.GetOrder(ref);

    //The email helper would do the meat and veg by getting the view as a string
    //Pass the control name (OrderResultEmail) and the model (order)
    string emailView = GetViewForEmail("OrderResultEmail", order);

    //Email the order out
    EmailHelper(order, emailView);
    return View("OrderResult", order);
}

Принял ответ от Тима Скотта (немного измененный и отформатированный мной):

public virtual string RenderViewToString(
    ControllerContext controllerContext,
    string viewPath,
    string masterPath,
    ViewDataDictionary viewData,
    TempDataDictionary tempData)
{
    Stream filter = null;
    ViewPage viewPage = new ViewPage();

    //Right, create our view
    viewPage.ViewContext = new ViewContext(controllerContext, new WebFormView(viewPath, masterPath), viewData, tempData);

    //Get the response context, flush it and get the response filter.
    var response = viewPage.ViewContext.HttpContext.Response;
    response.Flush();
    var oldFilter = response.Filter;

    try
    {
        //Put a new filter into the response
        filter = new MemoryStream();
        response.Filter = filter;

        //Now render the view into the memorystream and flush the response
        viewPage.ViewContext.View.Render(viewPage.ViewContext, viewPage.ViewContext.HttpContext.Response.Output);
        response.Flush();

        //Now read the rendered view.
        filter.Position = 0;
        var reader = new StreamReader(filter, response.ContentEncoding);
        return reader.ReadToEnd();
    }
    finally
    {
        //Clean up.
        if (filter != null)
        {
            filter.Dispose();
        }

        //Now replace the response filter
        response.Filter = oldFilter;
    }
}

Пример использования

Принимая вызов от контроллера, чтобы получить подтверждение заказа по электронной почте, передав местоположение Site.Master.

string myString = RenderViewToString(this.ControllerContext, "~/Views/Order/OrderResultEmail.aspx", "~/Views/Shared/Site.Master", this.ViewData, this.TempData);
Dan Atkinson
источник
2
Как вы можете использовать это с видом, который строго типизирован? То есть. как я могу подать модель на страницу?
Кьенсен
Невозможно использовать это и создать JsonResult впоследствии, потому что тип контента не может быть установлен после отправки заголовков (потому что Flush отправляет их).
Арнис Лапса
Потому что нет единого правильного ответа, я полагаю. :) Я создал вопрос, который был специфичен для меня, но я знал, что он также будет широко задаваться.
Дэн Аткинсон
2
Предлагаемое решение не работает в MVC 3.
Kasper Holdum
1
@Qua: предлагаемому решению более двух лет. Я не ожидал бы, что это будет работать и для MVC 3! Кроме того, есть лучшие способы сделать это сейчас.
Дэн Аткинсон

Ответы:

572

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

Стиль MVC2 .ascx

protected string RenderViewToString<T>(string viewPath, T model) {
  ViewData.Model = model;
  using (var writer = new StringWriter()) {
    var view = new WebFormView(ControllerContext, viewPath);
    var vdd = new ViewDataDictionary<T>(model);
    var viewCxt = new ViewContext(ControllerContext, view, vdd,
                                new TempDataDictionary(), writer);
    viewCxt.View.Render(viewCxt, writer);
    return writer.ToString();
  }
}

Бритва .cshtml стиль

public string RenderRazorViewToString(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();
  }
}

Изменить: добавлен код бритвы.

Ben Lesh
источник
31
Визуализация представления строки всегда «несовместима со всей концепцией маршрутизации», поскольку она не имеет ничего общего с маршрутизацией. Я не уверен, почему ответ, который работает, получил отрицательный голос.
Бен Леш
4
Я думаю, что вам может потребоваться удалить «статический» из объявления метода версии Razor, в противном случае он не может найти ControllerContext и др.
Майк
3
Вам нужно будет реализовать свой собственный метод удаления для этих лишних пробелов. Лучший способ, который я могу придумать, - загрузить строку в XmlDocument, а затем записать ее обратно в строку с помощью XmlWriter по ссылке, которую я оставил в своем последнем комментарии. Я действительно надеюсь, что это поможет.
Бен Леш
3
Хм, как я должен сделать это с помощью контроллера WebApi, любые предложения будут оценены
Александр
3
Всем привет, используйте его с ключевым словом «Static» для всех контроллеров, чтобы сделать его общим, вы должны сделать статический класс, и внутри него вы должны поместить этот метод с «this» в качестве параметра в «ControllerContext». Вы можете увидеть здесь stackoverflow.com/a/18978036/2318354 это.
Dilip0165
68

Этот ответ не на моем пути. Это родом из https://stackoverflow.com/a/2759898/2318354, но здесь я покажу, как использовать его со «статическим» ключевым словом, чтобы сделать его общим для всех контроллеров.

Для этого вы должны сделать staticкласс в файле класса. (Предположим, ваше имя файла класса Utils.cs)

Этот пример для бритвы.

Utils.cs

public static class RazorViewToString
{
    public static string RenderRazorViewToString(this Controller controller, string viewName, object model)
    {
        controller.ViewData.Model = model;
        using (var sw = new StringWriter())
        {
            var viewResult = ViewEngines.Engines.FindPartialView(controller.ControllerContext, viewName);
            var viewContext = new ViewContext(controller.ControllerContext, viewResult.View, controller.ViewData, controller.TempData, sw);
            viewResult.View.Render(viewContext, sw);
            viewResult.ViewEngine.ReleaseView(controller.ControllerContext, viewResult.View);
            return sw.GetStringBuilder().ToString();
        }
    }
}

Теперь вы можете вызвать этот класс из вашего контроллера, добавив NameSpace в ваш файл контроллера следующим образом, передав «this» в качестве параметра в Controller.

string result = RazorViewToString.RenderRazorViewToString(this ,"ViewName", model);

По предложению @Sergey этот метод расширения может также вызываться из cotroller, как указано ниже

string result = this.RenderRazorViewToString("ViewName", model);

Надеюсь, это поможет вам сделать код чистым и аккуратным.

Dilip0165
источник
1
Отличное решение! Во-первых, RenderRazorViewToString фактически является методом расширения (поскольку вы передаете параметр контроллера с этим ключевым словом), поэтому этот метод расширения можно назвать следующим образом: this.RenderRazorViewToString ("ViewName", model);
Сергей
@ Сергей Хммм ... Позвольте мне проверить таким образом, если это хорошо, тогда я обновлю свой ответ. В любом случае, спасибо за ваше предложение.
Dilip0165
Dilip0165, я получил ошибку нулевой ссылки на var viewResult = ViewEngines.Engines.FindPartialView (controller.ControllerContext, viewName) ;. Есть ли у вас какие-либо идеи?
CB4
@ CB4 Я думаю, это может быть проблема "viewName", которую вы передаете в функцию. Вы должны передать «viewName» с полным путем согласно структуре вашей папки. Так что проверь это.
Dilip0165
1
@ Сергей Спасибо за ваше предложение, я обновил свой ответ в соответствии с вашим предложением, который является абсолютно правильным
Dilip0165
32

Это работает для меня:

public virtual string RenderView(ViewContext viewContext)
{
    var response = viewContext.HttpContext.Response;
    response.Flush();
    var oldFilter = response.Filter;
    Stream filter = null;
    try
    {
        filter = new MemoryStream();
        response.Filter = filter;
        viewContext.View.Render(viewContext, viewContext.HttpContext.Response.Output);
        response.Flush();
        filter.Position = 0;
        var reader = new StreamReader(filter, response.ContentEncoding);
        return reader.ReadToEnd();
    }
    finally
    {
        if (filter != null)
        {
            filter.Dispose();
        }
        response.Filter = oldFilter;
    }
}
Тим Скотт
источник
Спасибо за ваш комментарий, но разве это не используется для рендеринга внутри представления? Как я мог использовать это в контексте, с которым я обновил вопрос?
Дэн Аткинсон
Извините, я все еще думаю о Silverlight в прошлом году, у которого первый rc был 0. :) Сегодня я попробую. (Как только я
определю
Это все еще ломает перенаправления в RC1
побежденный
победил: нет, это не так. Если это так, то вы делаете что-то не так.
Дэн Аткинсон
Объединил это с stackoverflow.com/questions/520863/… , добавил осведомленность о ViewEnginesCollection, попытался открыть частичное представление и получил этот stackoverflow.com/questions/520863/… . : E
Арнис Лапса
31

Я нашел новое решение, которое отображает строку без необходимости связываться с потоком Response текущего HttpContext (который не позволяет вам изменять ContentType ответа или другие заголовки).

По сути, все, что вам нужно сделать, это создать поддельный HttpContext для представления, которое будет отображаться само по себе:

/// <summary>Renders a view to string.</summary>
public static string RenderViewToString(this Controller controller,
                                        string viewName, object viewData) {
    //Create memory writer
    var sb = new StringBuilder();
    var memWriter = new StringWriter(sb);

    //Create fake http context to render the view
    var fakeResponse = new HttpResponse(memWriter);
    var fakeContext = new HttpContext(HttpContext.Current.Request, fakeResponse);
    var fakeControllerContext = new ControllerContext(
        new HttpContextWrapper(fakeContext),
        controller.ControllerContext.RouteData,
        controller.ControllerContext.Controller);

    var oldContext = HttpContext.Current;
    HttpContext.Current = fakeContext;

    //Use HtmlHelper to render partial view to fake context
    var html = new HtmlHelper(new ViewContext(fakeControllerContext,
        new FakeView(), new ViewDataDictionary(), new TempDataDictionary()),
        new ViewPage());
    html.RenderPartial(viewName, viewData);

    //Restore context
    HttpContext.Current = oldContext;    

    //Flush memory and return output
    memWriter.Flush();
    return sb.ToString();
}

/// <summary>Fake IView implementation used to instantiate an HtmlHelper.</summary>
public class FakeView : IView {
    #region IView Members

    public void Render(ViewContext viewContext, System.IO.TextWriter writer) {
        throw new NotImplementedException();
    }

    #endregion
}

Это работает в ASP.NET MVC 1.0 вместе с ContentResult, JsonResult и т. Д. (Изменение заголовков исходного HttpResponse не приводит к исключению « Сервер не может установить тип содержимого после отправки заголовков HTTP »).

Обновление: в ASP.NET MVC 2.0 RC код немного меняется, потому что мы должны передать StringWriterиспользуемый для записи представления в ViewContext:

//...

//Use HtmlHelper to render partial view to fake context
var html = new HtmlHelper(
    new ViewContext(fakeControllerContext, new FakeView(),
        new ViewDataDictionary(), new TempDataDictionary(), memWriter),
    new ViewPage());
html.RenderPartial(viewName, viewData);

//...
оборота LorenzCK
источник
В объекте HtmlHelper нет метода RenderPartial. Это невозможно - html.RenderPartial (viewName, viewData);
MartinF
1
В версии 1.0 ASP.NET MVC есть несколько методов расширения RenderPartial. В частности, я использую System.Web.Mvc.Html.RenderPartialExtensions.RenderPartial (это HtmlHelper, string, object). Я не знаю, был ли метод добавлен в последние версии MVC и не присутствовал в более ранних.
LorenzCK
Спасибо. Просто нужно добавить пространство имен System.Web.Mvc.Html в объявление использования (иначе html.RenderPartial (..), конечно, будет недоступен :))
MartinF
У кого-нибудь есть такая работа с RC от MVC2? Они добавили дополнительный параметр Textwriter в ViewContext. Я попытался просто добавить новый StringWriter (), но это не сработало.
beckelmw
1
@beckelmw: я обновил ответ. Вы должны передать оригинал, который StringWriterвы используете для записи StringBuilder, а не новый экземпляр, иначе выходные данные представления будут потеряны.
LorenzCK
11

В этой статье описывается, как визуализировать представление в строку в различных сценариях:

  1. MVC Controller вызывает другой собственный ActionMethods
  2. MVC Controller вызывает ActionMethod другого MVC Controller
  3. Контроллер WebAPI вызывает метод ActionMet для контроллера MVC

Решение / код предоставляется в виде класса с именем ViewRenderer . Это часть WestwindToolkit Рика Штала в GitHub .

Использование (3. - пример WebAPI):

string html = ViewRenderer.RenderView("~/Areas/ReportDetail/Views/ReportDetail/Index.cshtml", ReportVM.Create(id));
Дженни О'Рейли
источник
3
Также как пакет NuGet West Wind Web MVC Utilities ( nuget.org/packages/Westwind.Web.Mvc ). В качестве бонуса средство визуализации представлений может отображать не только частичные представления, но и весь вид, включая макет. Статья в блоге с кодом: weblog.west-wind.com/posts/2012/May/30/…
Jeroen K
Было бы здорово, если бы это было разбито на более мелкие пакеты. Пакет Nuget вносит кучу изменений в ваш web.config и добавляет в ваш проект js-файлы, которые затем не удаляются при его удалении: /
Josh Noe
8

Если вы хотите полностью отказаться от MVC, избегая тем самым всего беспорядка HttpContext ...

using RazorEngine;
using RazorEngine.Templating; // For extension methods.

string razorText = System.IO.File.ReadAllText(razorTemplateFileLocation);
string emailBody = Engine.Razor.RunCompile(razorText, "templateKey", typeof(Model), model);

Здесь используется потрясающий движок Razor с открытым исходным кодом: https://github.com/Antaris/RazorEngine

Josh Noe
источник
Ницца! Знаете ли вы, есть ли подобный механизм синтаксического анализа для синтаксиса WebForms? У меня все еще есть старые представления WebForms, которые пока нельзя перенести в Razor.
Дэн Аткинсон
Привет, у меня было много проблем с razorengine, и сообщение об ошибке не очень приятно. Я не думаю, что Url
Helper
@Layinka Не уверен, поможет ли это, но большая часть информации об ошибках находится в CompilerErrorsсвойстве исключения.
Джош Ное
5

Вы получаете представление в строке, используя этот способ

protected string RenderPartialViewToString(string viewName, object model)
{
    if (string.IsNullOrEmpty(viewName))
        viewName = ControllerContext.RouteData.GetRequiredString("action");

    if (model != null)
        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();
    }
}

Мы называем этот метод двумя способами

string strView = RenderPartialViewToString("~/Views/Shared/_Header.cshtml", null)

ИЛИ

var model = new Person()
string strView = RenderPartialViewToString("~/Views/Shared/_Header.cshtml", model)
Jayesh Patel
источник
4

Дополнительный совет для ASP NET CORE:

Интерфейс:

public interface IViewRenderer
{
  Task<string> RenderAsync<TModel>(Controller controller, string name, TModel model);
}

Реализация:

public class ViewRenderer : IViewRenderer
{
  private readonly IRazorViewEngine viewEngine;

  public ViewRenderer(IRazorViewEngine viewEngine) => this.viewEngine = viewEngine;

  public async Task<string> RenderAsync<TModel>(Controller controller, string name, TModel model)
  {
    ViewEngineResult viewEngineResult = this.viewEngine.FindView(controller.ControllerContext, name, false);

    if (!viewEngineResult.Success)
    {
      throw new InvalidOperationException(string.Format("Could not find view: {0}", name));
    }

    IView view = viewEngineResult.View;
    controller.ViewData.Model = model;

    await using var writer = new StringWriter();
    var viewContext = new ViewContext(
       controller.ControllerContext,
       view,
       controller.ViewData,
       controller.TempData,
       writer,
       new HtmlHelperOptions());

       await view.RenderAsync(viewContext);

       return writer.ToString();
  }
}

Регистрация в Startup.cs

...
 services.AddSingleton<IViewRenderer, ViewRenderer>();
...

И использование в контроллере:

public MyController: Controller
{
  private readonly IViewRenderer renderer;
  public MyController(IViewRendere renderer) => this.renderer = renderer;
  public async Task<IActionResult> MyViewTest
  {
    var view = await this.renderer.RenderAsync(this, "MyView", model);
    return new OkObjectResult(view);
  }
}
Marcin
источник
3

Я использую MVC 1.0 RTM, и ни одно из вышеперечисленных решений не помогло мне. Но этот сделал:

Public Function RenderView(ByVal viewContext As ViewContext) As String

    Dim html As String = ""

    Dim response As HttpResponse = HttpContext.Current.Response

    Using tempWriter As New System.IO.StringWriter()

        Dim privateMethod As MethodInfo = response.GetType().GetMethod("SwitchWriter", BindingFlags.NonPublic Or BindingFlags.Instance)

        Dim currentWriter As Object = privateMethod.Invoke(response, BindingFlags.NonPublic Or BindingFlags.Instance Or BindingFlags.InvokeMethod, Nothing, New Object() {tempWriter}, Nothing)

        Try
            viewContext.View.Render(viewContext, Nothing)
            html = tempWriter.ToString()
        Finally
            privateMethod.Invoke(response, BindingFlags.NonPublic Or BindingFlags.Instance Or BindingFlags.InvokeMethod, Nothing, New Object() {currentWriter}, Nothing)
        End Try

    End Using

    Return html

End Function
Джереми Белл
источник
2

Я видел реализацию MVC 3 и Razor с другого сайта, у меня это сработало:

    public static string RazorRender(Controller context, string DefaultAction)
    {
        string Cache = string.Empty;
        System.Text.StringBuilder sb = new System.Text.StringBuilder();
        System.IO.TextWriter tw = new System.IO.StringWriter(sb); 

        RazorView view_ = new RazorView(context.ControllerContext, DefaultAction, null, false, null);
        view_.Render(new ViewContext(context.ControllerContext, view_, new ViewDataDictionary(), new TempDataDictionary(), tw), tw);

        Cache = sb.ToString(); 

        return Cache;

    } 

    public static string RenderRazorViewToString(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);
            return sw.GetStringBuilder().ToString();
        }
    } 

    public static class HtmlHelperExtensions
    {
        public static string RenderPartialToString(ControllerContext context, string partialViewName, ViewDataDictionary viewData, TempDataDictionary tempData)
        {
            ViewEngineResult result = ViewEngines.Engines.FindPartialView(context, partialViewName);

            if (result.View != null)
            {
                StringBuilder sb = new StringBuilder();
                using (StringWriter sw = new StringWriter(sb))
                {
                    using (HtmlTextWriter output = new HtmlTextWriter(sw))
                    {
                        ViewContext viewContext = new ViewContext(context, result.View, viewData, tempData, output);
                        result.View.Render(viewContext, output);
                    }
                }
                return sb.ToString();
            } 

            return String.Empty;

        }

    }

Подробнее о Razor render - MVC3 View Render to String

Adamy
источник
Да, это на самом деле более или менее копия принятого ответа. :)
Дэн Аткинсон
2

Чтобы визуализировать представление строки в слое обслуживания без необходимости передавать ControllerContext, здесь есть хорошая статья Рика Строла http://www.codemag.com/Article/1312081, которая создает универсальный контроллер. Краткое содержание кода ниже:

// Some Static Class
public static string RenderViewToString(ControllerContext context, string viewPath, object model = null, 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("View cannot be found.");

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

// In the Service Class
public class GenericController : Controller
{ }

public static T CreateController<T>(RouteData routeData = null) where T : Controller, new()
{
    // create a disconnected controller instance
    T controller = new T();

    // get context wrapper from HttpContext if available
    HttpContextBase wrapper;
    if (System.Web.HttpContext.Current != null)
        wrapper = new HttpContextWrapper(System.Web.HttpContext.Current);
    else
        throw new InvalidOperationException("Cannot create Controller Context if no active HttpContext instance is available.");

    if (routeData == null)
        routeData = new RouteData();

    // add the controller routing if not existing
    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;
}

Затем для рендеринга View в классе Service:

var stringView = RenderViewToString(CreateController<GenericController>().ControllerContext, "~/Path/To/View/Location/_viewName.cshtml", theViewModel, true);
RickL
источник
1

Быстрая подсказка

Для строго типизированной модели просто добавьте ее в свойство ViewData.Model, прежде чем переходить к RenderViewToString. например

this.ViewData.Model = new OrderResultEmailViewModel(order);
string myString = RenderViewToString(this.ControllerContext, "~/Views/Order/OrderResultEmail.aspx", "~/Views/Shared/Site.Master", this.ViewData, this.TempData);
longhairedsi
источник
0

Чтобы повторить более неизвестный вопрос, взгляните на MvcIntegrationTestFramework .

Это избавляет вас от написания собственных помощников для потоковой передачи результатов и доказывает, что работает достаточно хорошо. Я предполагаю, что это будет в тестовом проекте, и в качестве бонуса у вас будут другие возможности тестирования, как только вы получите эту настройку. Главным беспокойством, вероятно, будет сортировка цепочки зависимостей.

 private static readonly string mvcAppPath = 
     Path.GetFullPath(AppDomain.CurrentDomain.BaseDirectory 
     + "\\..\\..\\..\\MyMvcApplication");
 private readonly AppHost appHost = new AppHost(mvcAppPath);

    [Test]
    public void Root_Url_Renders_Index_View()
    {
        appHost.SimulateBrowsingSession(browsingSession => {
            RequestResult result = browsingSession.ProcessRequest("");
            Assert.IsTrue(result.ResponseText.Contains("<!DOCTYPE html"));
        });
}
голубь
источник
0

Вот класс, который я написал, чтобы сделать это для ASP.NETCore RC2. Я использую его, чтобы я мог генерировать html электронную почту, используя Razor.

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewEngines;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Routing;
using System.IO;
using System.Threading.Tasks;

namespace cloudscribe.Web.Common.Razor
{
    /// <summary>
    /// the goal of this class is to provide an easy way to produce an html string using 
    /// Razor templates and models, for use in generating html email.
    /// </summary>
    public class ViewRenderer
    {
        public ViewRenderer(
            ICompositeViewEngine viewEngine,
            ITempDataProvider tempDataProvider,
            IHttpContextAccessor contextAccesor)
        {
            this.viewEngine = viewEngine;
            this.tempDataProvider = tempDataProvider;
            this.contextAccesor = contextAccesor;
        }

        private ICompositeViewEngine viewEngine;
        private ITempDataProvider tempDataProvider;
        private IHttpContextAccessor contextAccesor;

        public async Task<string> RenderViewAsString<TModel>(string viewName, TModel model)
        {

            var viewData = new ViewDataDictionary<TModel>(
                        metadataProvider: new EmptyModelMetadataProvider(),
                        modelState: new ModelStateDictionary())
            {
                Model = model
            };

            var actionContext = new ActionContext(contextAccesor.HttpContext, new RouteData(), new ActionDescriptor());
            var tempData = new TempDataDictionary(contextAccesor.HttpContext, tempDataProvider);

            using (StringWriter output = new StringWriter())
            {

                ViewEngineResult viewResult = viewEngine.FindView(actionContext, viewName, true);

                ViewContext viewContext = new ViewContext(
                    actionContext,
                    viewResult.View,
                    viewData,
                    tempData,
                    output,
                    new HtmlHelperOptions()
                );

                await viewResult.View.RenderAsync(viewContext);

                return output.GetStringBuilder().ToString();
            }
        }
    }
}
Джо Одетт
источник
0

Я нашел лучший способ визуализации страницы просмотра бритвой, когда у меня возникли ошибки с помощью описанных выше методов, это решение как для среды веб-форм, так и для среды MVC. Контроллер не нужен.

Вот пример кода, в этом примере я смоделировал действие mvc с помощью асинхронного http-обработчика:

    /// <summary>
    /// Enables processing of HTTP Web requests asynchronously by a custom HttpHandler that implements the IHttpHandler interface.
    /// </summary>
    /// <param name="context">An HttpContext object that provides references to the intrinsic server objects.</param>
    /// <returns>The task to complete the http request.</returns>
    protected override async Task ProcessRequestAsync(HttpContext context)
    {
        if (this._view == null)
        {
            this.OnError(context, new FileNotFoundException("Can not find the mvc view file.".Localize()));
            return;
        }
        object model = await this.LoadModelAsync(context);
        WebPageBase page = WebPageBase.CreateInstanceFromVirtualPath(this._view.VirtualPath);
        using (StringWriter sw = new StringWriter())
        {
            page.ExecutePageHierarchy(new WebPageContext(new HttpContextWrapper(context), page, model), sw);
            await context.Response.Output.WriteAsync(sw.GetStringBuilder().ToString());
        }
    }
Dexiang
источник