Jak renderować widok ASP.NET MVC jako ciąg?

485

Chcę wygenerować dwa różne widoki (jeden jako ciąg, który zostanie wysłany jako wiadomość e-mail), a drugi stronę wyświetlaną użytkownikowi.

Czy jest to możliwe w programie ASP.NET MVC w wersji beta?

Próbowałem wielu przykładów:

1. Renderuj na łańcuch w ASP.NET MVC Beta

Jeśli użyję tego przykładu, otrzymuję komunikat „Nie można przekierować po wysłaniu nagłówków HTTP”.

2. MVC Framework: Przechwytywanie danych wyjściowych widoku

Jeśli go użyję, wydaje mi się, że nie mogę wykonać operacji redirectToAction, ponieważ próbuje ona renderować widok, który może nie istnieć. Jeśli zwrócę widok, jest on całkowicie popsuty i wcale nie wygląda dobrze.

Czy ktoś ma jakieś pomysły / rozwiązania tych problemów, czy mam jakieś sugestie dotyczące lepszych?

Wielkie dzięki!

Poniżej znajduje się przykład. Próbuję utworzyć metodę 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);
}

Zaakceptowana odpowiedź od Tima Scotta (zmieniona i sformatowana przeze mnie trochę):

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

Przykładowe użycie

Przyjęcie połączenia z kontrolera w celu otrzymania wiadomości e-mail z potwierdzeniem zamówienia, przekazanie lokalizacji Site.Master.

string myString = RenderViewToString(this.ControllerContext, "~/Views/Order/OrderResultEmail.aspx", "~/Views/Shared/Site.Master", this.ViewData, this.TempData);
Dan Atkinson
źródło
2
Jak możesz tego użyć z silnie wpisanym widokiem? To znaczy. jak mogę nakarmić model na stronie?
Kjensen
Nie można tego użyć i później utworzyć JsonResult, ponieważ nie można ustawić typu treści po wysłaniu nagłówków (ponieważ Flush je wysyła).
Arnis Lapsa
Ponieważ chyba nie ma jednej właściwej odpowiedzi. :) Stworzyłem pytanie, które było dla mnie specyficzne, ale wiedziałem, że będzie ono również zadawane często.
Dan Atkinson
2
Sugerowane rozwiązanie nie działa w MVC 3.
Kasper Holdum
1
@Qua: Sugerowane rozwiązanie ma ponad dwa lata. Nie spodziewałbym się też, że zadziała w MVC 3! Poza tym istnieją teraz lepsze sposoby na zrobienie tego.
Dan Atkinson

Odpowiedzi:

572

Oto, co wymyśliłem i działa dla mnie. Dodałem następujące metody do mojej podstawowej klasy kontrolera. (Zawsze możesz wykonać te metody statyczne gdzie indziej, które akceptują kontroler jako parametr, jak sądzę)

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

Styl Razor .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();
  }
}

Edycja: dodano kod Razor.

Ben Lesh
źródło
31
Renderowanie widoku do łańcucha jest zawsze „niezgodne z całą koncepcją routingu”, ponieważ nie ma nic wspólnego z routingiem. Nie jestem pewien, dlaczego odpowiedź, która działa, została odrzucona.
Ben Lesh
4
Myślę, że może być konieczne usunięcie „static” z deklaracji metody wersji Razor, w przeciwnym razie nie można znaleźć ControllerContext i in.
Mike
3
Będziesz musiał wdrożyć własną metodę usuwania tych zbędnych białych znaków. Najlepszym sposobem, jaki mogę wymyślić z góry głowy, jest załadowanie ciągu do XmlDocument, a następnie zapisanie go z powrotem do ciągu za pomocą XmlWriter, zgodnie z linkiem, który pozostawiłem w ostatnim komentarzu. Naprawdę mam nadzieję, że to pomoże.
Ben Lesh
3
Hmm, jak powinienem to zrobić za pomocą kontrolera WebApi, wszelkie sugestie byłyby mile widziane
Alexander
3
Cześć wszystkim, aby użyć go ze słowem kluczowym „Static” dla wszystkich kontrolerów, aby uczynić go powszechnym, musisz stworzyć klasę statyczną, a wewnątrz niego musisz umieścić tę metodę z „this” jako parametrem „ControllerContext”. Możesz zobaczyć tutaj stackoverflow.com/a/18978036/2318354 it.
Dilip0165
68

Ta odpowiedź nie jest w drodze. Pochodzi z https://stackoverflow.com/a/2759898/2318354, ale tutaj pokazałem sposób użycia go ze słowem kluczowym „statycznym”, aby był wspólny dla wszystkich kontrolerów.

W tym celu musisz utworzyć staticklasę w pliku klasy. (Załóżmy, że Twoja nazwa pliku klasy to Utils.cs)

Ten przykład dotyczy For 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();
        }
    }
}

Teraz możesz wywołać tę klasę ze swojego kontrolera, dodając NameSpace do pliku kontrolera w następujący sposób, przekazując „this” jako parametr do kontrolera.

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

Zgodnie z sugestią @Sergey ta metoda rozszerzenia może również wywoływać z cotroller, jak podano poniżej

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

Mam nadzieję, że przyda ci się, aby kod był czysty i uporządkowany.

Dilip0165
źródło
1
Fajne rozwiązanie! Jedno, RenderRazorViewToString jest w rzeczywistości metodą rozszerzenia (ponieważ przekazujesz parametr kontrolera tym słowem kluczowym), więc tę metodę rozszerzenia można nazwać w ten sposób: this.RenderRazorViewToString („ViewName”, model);
Siergiej
@Sergey Hmmm ... Pozwól mi sprawdzić w ten sposób, jeśli jest w porządku, zaktualizuję odpowiedź. W każdym razie dzięki za sugestię.
Dilip0165
Dilip0165, dostałem błąd odniesienia null na var viewResult = ViewEngines.Engines.FindPartialView (controller.ControllerContext, viewName) ;. Masz jakiś pomysł?
CB4,
@ CB4 Myślę, że może to być kwestia „viewName”, którą przekazujesz do funkcji. Musisz przekazać „viewName” z pełną ścieżką zgodnie ze strukturą folderów. Więc sprawdź to.
Dilip0165
1
@ Sergey Dzięki za sugestię, zaktualizowałem odpowiedź zgodnie z sugestią, która jest całkowicie poprawna
Dilip0165
32

To działa dla mnie:

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;
    }
}
Tim Scott
źródło
Dziękuję za komentarz, ale czy nie jest on używany do renderowania w widoku? Jak mogę go użyć w kontekście, w którym zaktualizowałem pytanie?
Dan Atkinson
Przepraszam, wciąż myślę o Silverlight w zeszłym roku, którego pierwszy rc wynosił 0. :) Dziś daję temu szansę. (Jak tylko
opracuję
To wciąż przerywa przekierowania w RC1
pokonany
pokonany: Nie, nie ma. Jeśli tak, to robisz coś złego.
Dan Atkinson
Połączyła to z stackoverflow.com/questions/520863/... dodał świadomości ViewEnginesCollection, próbował pop PartialView i dostał ten stackoverflow.com/questions/520863/... . : E
Arnis Lapsa
31

Znalazłem nowe rozwiązanie, które renderuje widok na ciąg bez konieczności bałagania w strumieniu odpowiedzi bieżącego HttpContext (co nie pozwala na zmianę ContentType odpowiedzi lub innych nagłówków).

Zasadniczo wszystko, co robisz, to stworzyć fałszywy HttpContext, aby widok sam się renderował:

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

Działa to na ASP.NET MVC 1.0, razem z ContentResult, JsonResult itp. (Zmiana Nagłówków na oryginalnej HttpResponse nie generuje wyjątku „ Serwer nie może ustawić typu zawartości po wysłaniu nagłówków HTTP ”).

Aktualizacja: w ASP.NET MVC 2.0 RC kod nieco się zmienia, ponieważ musimy przekazać dane StringWriterużywane do zapisu widoku w 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
źródło
Nie ma metody RenderPartial na obiekcie HtmlHelper. Nie jest to możliwe - html.RenderPartial (viewName, viewData);
MartinF
1
W ASP.NET MVC wersja 1.0 istnieje kilka metod rozszerzenia RenderPartial. Ten, którego szczególnie używam, to System.Web.Mvc.Html.RenderPartialExtensions.RenderPartial (ten HtmlHelper, łańcuch, obiekt). Nie wiem, czy metoda została dodana w najnowszych wersjach MVC i nie była obecna we wcześniejszych wersjach.
LorenzCK,
Dzięki. Wystarczyło dodać przestrzeń nazw System.Web.Mvc.Html do deklaracji using (w innym przypadku html.RenderPartial (..) oczywiście nie będzie dostępny :))
MartinF
Czy ktoś ma to do pracy z RC MVC2? Dodali dodatkowy parametr Textwriter do ViewContext. Próbowałem właśnie dodać nową StringWriter (), ale to nie działało.
beckelmw
1
@beckelmw: Zaktualizowałem odpowiedź. Musisz przekazać oryginał, StringWriterktórego używasz do zapisu StringBuilder, a nie do nowej instancji, w przeciwnym razie wynik widoku zostanie utracony.
LorenzCK,
11

W tym artykule opisano, jak renderować widok do łańcucha w różnych scenariuszach:

  1. Kontroler MVC wywołuje inną własną ActionMethods
  2. Kontroler MVC wywołuje ActionMethod innego kontrolera MVC
  3. Kontroler WebAPI wywołujący ActionMethod kontrolera MVC

Rozwiązanie / kod jest dostarczane jako klasa o nazwie ViewRenderer . Jest częścią zestawu WestwindToolkit Ricka Stahla w GitHub .

Użycie (3. - przykład WebAPI):

string html = ViewRenderer.RenderView("~/Areas/ReportDetail/Views/ReportDetail/Index.cshtml", ReportVM.Create(id));
Jenny O'Reilly
źródło
3
Również jako pakiet NuGet West Wind Web MVC Utilities ( nuget.org/packages/Westwind.Web.Mvc ). Jako bonus, moduł renderujący widok może renderować nie tylko widoki częściowe, ale także cały widok łącznie z układem. Artykuł w blogu z kodem: weblog.west-wind.com/posts/2012/May/30/…
Jeroen K
Byłoby wspaniale, gdyby zostało to podzielone na mniejsze pakiety. Pakiet Nuget wprowadza kilka zmian w pliku web.config i dodaje do projektu pliki js, które nie są czyszczone po odinstalowaniu: /
Josh Noe
8

Jeśli chcesz całkowicie zrezygnować z MVC, unikając w ten sposób całego bałaganu 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);

Wykorzystuje to niesamowity silnik Razor Open Source tutaj: https://github.com/Antaris/RazorEngine

Josh Noe
źródło
Miły! Czy wiesz, czy istnieje podobny silnik analizujący składnię WebForms? Nadal mam kilka starych widoków formularzy internetowych, których nie można jeszcze przenieść do Razor.
Dan Atkinson,
Cześć, miałem wiele problemów z Razorengine, a zgłaszanie błędów nie jest zbyt miłe. Nie sądzę, że pomocnik Url jest obsługiwany
Layinka
@Layinka Nie jestem pewien, czy to pomaga, ale większość informacji o błędach jest CompilerErrorswłasnością wyjątku.
Josh Noe,
5

w ten sposób otrzymujesz widok w postaci ciągu

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

Nazywamy tę metodę na dwa sposoby

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

LUB

var model = new Person()
string strView = RenderPartialViewToString("~/Views/Shared/_Header.cshtml", model)
Jayesh Patel
źródło
4

Dodatkowa wskazówka dla ASP NET CORE:

Berło:

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

Realizacja:

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

Rejestracja w Startup.cs

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

I użycie w kontrolerze:

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
źródło
3

Używam MVC 1.0 RTM i żadne z powyższych rozwiązań nie działało dla mnie. Ale ten zrobił:

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
Jeremy Bell
źródło
2

Widziałem implementację dla MVC 3 i Razor z innej strony, działało to dla mnie:

    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;

        }

    }

Więcej informacji o renderowaniu Razor - MVC3 Wyświetl renderowanie do łańcucha

Adamy
źródło
Tak, w rzeczywistości jest to mniej więcej kopia zaakceptowanej odpowiedzi. :)
Dan Atkinson
2

Aby wyświetlić widok na ciąg w warstwie serwisowej bez konieczności przekazywania kontroleraContext, istnieje dobry artykuł Ricka Strahla tutaj http://www.codemag.com/Article/1312081, który tworzy ogólny kontroler. Podsumowanie kodu poniżej:

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

Następnie, aby wyrenderować widok w klasie usługi:

var stringView = RenderViewToString(CreateController<GenericController>().ControllerContext, "~/Path/To/View/Location/_viewName.cshtml", theViewModel, true);
RickL
źródło
1

Szybka wskazówka

W przypadku silnie typowanego modelu wystarczy dodać go do właściwości ViewData.Model przed przekazaniem do RenderViewToString. na przykład

this.ViewData.Model = new OrderResultEmailViewModel(order);
string myString = RenderViewToString(this.ControllerContext, "~/Views/Order/OrderResultEmail.aspx", "~/Views/Shared/Site.Master", this.ViewData, this.TempData);
longhairedsi
źródło
0

Aby powtórzyć z bardziej nieznanego pytania, spójrz na MvcIntegrationTestFramework .

To oszczędza ci pisania własnych pomocników w celu strumieniowego przesyłania wyników i udowodniono, że działa wystarczająco dobrze. Zakładam, że byłoby to w projekcie testowym, a jako bonus miałbyś inne możliwości testowania, gdy tylko dostaniesz tę konfigurację. Głównym problemem byłoby prawdopodobnie uporządkowanie łańcucha zależności.

 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"));
        });
}
gołąb
źródło
0

Oto klasa, którą napisałem, aby to zrobić dla ASP.NETCore RC2. Używam go, aby generować e-maile HTML za pomocą 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();
            }
        }
    }
}
Joe Audette
źródło
0

Znalazłem lepszy sposób na renderowanie strony widoku maszynki do golenia, gdy otrzymałem błąd w powyższych metodach, to rozwiązanie zarówno dla środowiska formularzy internetowych, jak i środowiska mvc. Kontroler nie jest potrzebny.

Oto przykład kodu, w tym przykładzie symulowałem akcję mvc za pomocą asynchronicznego modułu obsługi 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
źródło