Как сделать ASP.NET MVC представление в виде строки?
Я хочу вывести два разных вида (один как строка, которая будет отправлена как электронная почта), а другая страница отображается пользователю.
Возможно ли это в бета-версии ASP.NET MVC?
Я попробовал несколько примеров:
1. RenderPartial для String в бета-версии ASP.NET MVC
Если я использую этот пример, я получаю сообщение "Не удается перенаправить 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);
Ответы
Ответ 1
Вот что я придумал, и это работает на меня. Я добавил следующий метод к моему базовому классу контроллера. (Вы всегда можете сделать эти статические методы в другом месте, которые принимают контроллер как параметр, я полагаю)
MVC2.ascx style
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();
}
}
Стиль 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();
}
}
Изменить: добавлен код Razor.
Ответ 2
Этот ответ не на моем пути. Это изначально из fooobar.com/questions/16451/... но здесь я покажу, как использовать его со "статическим" ключевым словом, чтобы сделать его общим для всех контроллеров.
Для этого вы должны сделать 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);
Надеюсь, это поможет вам сделать код чистым и аккуратным.
Ответ 3
Это работает для меня:
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;
}
}
Ответ 4
Я нашел новое решение, которое отображает представление в строку без необходимости связываться с потоком 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);
//...
Ответ 5
В этой статье описывается, как визуализировать представление в строке в разных сценариях:
- MVC Controller вызывает другой из своих ActionMethods
- MVC-контроллер, вызывающий ActionMethod другого контроллера MVC
- Контроллер WebAPI, вызывающий ActionMethod контроллера MVC
Решение/код предоставляется как класс с именем ViewRenderer. Это часть Rick Stahl WestwindToolkit в GitHub.
Использование (3. - Пример WebAPI):
string html = ViewRenderer.RenderView("~/Areas/ReportDetail/Views/ReportDetail/Index.cshtml", ReportVM.Create(id));
Ответ 6
Если вы хотите полностью отказаться от 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
Ответ 7
вы получаете представление в строке, используя этот способ
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)
Ответ 8
Я использую RTM MVC 1.0, и ни один из вышеперечисленных решений не работал у меня. Но это было сделано:
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
Ответ 9
Я видел реализацию для 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 Просмотр рендеринга в строку
Ответ 10
Чтобы отобразить представление строки на Уровне обслуживания без необходимости пропускать 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;
}
Затем, чтобы отобразить представление в классе Service:
var stringView = RenderViewToString(CreateController<GenericController>().ControllerContext, "~/Path/To/View/Location/_viewName.cshtml", theViewModel, true);
Ответ 11
Я столкнулся с той же проблемой при создании отчета. После выполнения всего вышеописанного решения я встретил некоторое исключение, связанное с HTTP-заголовком. Это не позволяет мне перенаправлять действие или возвращать любой ActionResult позже.
Однако я нашел другой способ решения.
Перейдите в эту статью в своем блоге:
http://trandangkhoa.blogspot.com/2009/05/asp-net-mvc-print-excel-file-using-aspx.html
Идея очень проста: использование HttpClient для получения содержимого HTML из представления:
Ответ 12
Быстрый совет
Для сильно типизированной модели просто добавьте ее в свойство 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);
Ответ 13
Я бы создал новый класс вида, исходящий из исходного вида и переопределяющий метод вывода.
Ответ 14
Чтобы повторить из более неизвестного вопроса, посмотрите 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"));
});
}
Ответ 15
Вот класс, который я написал для этого для 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();
}
}
}
}
Ответ 16
Я нашел лучший способ рендеринга страницы просмотра бритвы, когда у меня возникла ошибка с вышеприведенными методами, это решение как для среды веб-форм, так и для среды 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());
}
}
Ответ 17
Дополнительный совет для 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);
}
}