Як візуалізувати представлення 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.