Як візуалізувати представлення ASP.NET MVC у вигляді рядка?


485

Я хочу вивести два різних представлення (один як рядок, який буде надісланий як електронний лист), а інший - сторінка, що відображається користувачеві.

Чи можливо це в бета-версії ASP.NET MVC?

Я спробував кілька прикладів:

1. RenderPartial для String в бета-версії ASP.NET MVC

Якщо я використовую цей приклад, я отримую "Не вдалося переспрямувати після відправлення заголовків HTTP."

2. MVC Framework: Захоплення результатів подання

Якщо я використовую це, я, здається, не можу зробити перенаправленняToAction, оскільки воно намагається надати погляд, який може не існувати. Якщо я поверну вигляд, він повністю переплутався і зовсім не виглядає правильно.

Хтось має якісь ідеї / рішення цих питань у мене є, чи є якісь пропозиції щодо кращих?

Дуже дякую!

Нижче наведено приклад. Що я намагаюся зробити, це створити метод 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);

2
Як ви можете використовувати це для перегляду, який сильно набраний? Тобто як я можу подати модель на сторінку?
— Kjensen

Неможливо використати це та створити 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();
  }
}

Редагувати: доданий код бритви


31
Надання перегляду рядку завжди "не відповідає всій концепції маршрутизації", оскільки це не має нічого спільного з маршрутизацією. Я не впевнений, чому відповідь, яка працює, призвела до відмови.
— Бен Леш

4
Я думаю, що вам може знадобитися видалити "статичний" з декларації методу версії Razor, інакше він не може знайти ControllerContext та ін.
— Майк

3
Вам потрібно буде застосувати власний метод видалення для цих зайвих пробілів. Найкращий спосіб мені придумати верхній частині голови - це завантажити рядок у XmlDocument, а потім записати її назад у рядок за допомогою XmlWriter, згідно з посиланням, яке я залишив у своєму останньому коментарі. Я дуже сподіваюся, що це допомагає.
— Бен Леш

3
Гм, як мені це зробити за допомогою контролера WebApi, будь-які пропозиції будуть вдячні
— Олександр

3
Привіт усім, щоб використовувати його зі ключовим словом "Статичний" для всіх контролерів, щоб це було звичайним, вам потрібно зробити статичний клас, і всередині нього ви повинні поставити цей метод з "цим" як параметр "ControllerContext". Ви можете побачити тут stackoverflow.com/a/18978036/2318354 це.
— Dilip0165

68

Ця відповідь не на моєму шляху. Це спочатку з https://stackoverflow.com/a/2759898/2318354, але тут я показав спосіб використовувати його за допомогою "Статичного" ключового слова, щоб зробити його загальним для всіх контролерів.

Для цього вам потрібно скласти staticклас у файлі класу. (Припустимо, ваше ім'я файлу класу - Utils.cs)

Цей приклад - для Razor.

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 цей метод розширення може також викликати котроллер, як зазначено нижче

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

Сподіваюся, це стане в нагоді для того, щоб ви зробили код чистим та акуратним.


1
Приємне рішення! Одне, RenderRazorViewToString - це власне метод розширення (тому що ви передаєте параметр контролера за допомогою цього ключового слова), тому цей метод розширення можна назвати так: this.RenderRazorViewToString ("ViewName", модель);
— Сергій

@Sergey Hmmm ... Дозвольте мені перевірити, чи добре, ніж я оновлю свою відповідь. У будь-якому випадку дякую за вашу пропозицію.
— Dilip0165

Dilip0165, я отримав нульову помилку посилання на var viewResult = ViewEngines.Engines.FindPartialView (controller.ControllerContext, viewName) ;. У вас є ідея?
— CB4

@ CB4 Я думаю, що це може бути проблема "viewName", яку ви передаєте у функцію. Ви повинні пройти "viewName" з повним шляхом відповідно до структури папки. Тож перевірте цю річ.
— Dilip0165

1
@Sergey Дякую за вашу пропозицію, я оновив свою відповідь за вашою пропозицією, що є абсолютно правильним
— 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

Я знайшов нове рішення, яке надає перегляд рядка без необхідності возитися з потоком відповідей поточного 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);

//...

На об’єкті HtmlHelper немає методу RenderPartial. Це неможливо - html.RenderPartial (viewName, viewData);
— MartinF

1
У випуску 1.0 ASP.NET MVC є пара методів розширення RenderPartial. Я використовую, зокрема, System.Web.Mvc.Html.RenderPartialExtensions.RenderPartial (це HtmlHelper, рядок, об’єкт). Мені невідомо, чи був доданий метод в останніх редакціях MVC та чи не був він присутній у попередніх версіях.
— LorenzCK

Дякую. Просто потрібно додати простір імен System.Web.Mvc.Html до використовуючої декларації (ще html.RenderPartial (..) звичайно не буде доступною :))
— MartinF

Хтось має це працює з RC MVC2? Вони додали додатковий параметр Textwriter до ViewContext. Я спробував лише додати новий StringWriter (), але це не вийшло.
— бекельвель

1
@beckelmw: я оновив відповідь. Ви повинні передати оригінал, який StringWriterви використовуєте для запису в StringBuilder, а не новий екземпляр, інакше результат перегляду буде втрачено.
— LorenzCK

11

У цій статті описано, як надати перегляд рядка в різних сценаріях:

  1. Контролер MVC викликає інший власний метод ActionMethods
  2. Контролер MVC викликає ActionMethod іншого контролера MVC
  3. Контролер WebAPI, що викликає ActionMethod 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 Engine з відкритим кодом: https://github.com/Antaris/RazorEngine


Приємно! Чи знаєте ви, чи існує аналогічний механізм розбору синтаксису WebForms? У мене все ще є старі перегляди WebForms, які ще не можна перенести в Razor.
— Ден Аткінсон

Привіт, у мене було багато проблем з розренгліном, і повідомлення про помилки не дуже приємне. Я не думаю, що помічник Url підтримується
— Layinka

@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)

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

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- MVC3 Перегляд візуалізації до рядка


Так, це фактично більш-менш копія прийнятої відповіді. :)
— Ден Аткінсон

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

Потім для надання представлення в Класі обслуговування:

var stringView = RenderViewToString(CreateController<GenericController>().ControllerContext, "~/Path/To/View/Location/_viewName.cshtml", theViewModel, true);

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);

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-дію з обробником async 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());
        }
    }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.