Как сделать рендеринг ASP.NET MVC вид в виде строки?
Я хочу вывести два разных представления (одно в виде строки, которая будет отправлена по электронной почте), а другая страница отображается пользователю.
возможно ли это в ASP.NET MVC beta?
Я пробовал несколько примеров:
1.RenderPartial в строку ASP.NET MVC Beta
Если я использую этот пример, я получаю "не удается перенаправить после HTTP заголовки были отправленный.".
2.MVC Framework: захват вывода представления
Если я использую это, я, кажется, не в состоянии сделать redirectToAction, как это пытается отобразить представление, которое может не существовать. Если я верну представление, это полностью испорчен и не выглядит правильно вообще.
есть ли у кого-нибудь идеи/решения этих проблем у меня есть, или есть какие-либо предложения для лучшего одни?
большое спасибо!
Ниже приведен пример. Что я пытаюсь сделать, это создать GetViewForEmail метод:
public ActionResult OrderResult(string ref)
{
//Get the order
Order order = OrderService.GetOrder(ref);
//The email helper would do the meat and veg by getting the view as a string
//Pass the control name (OrderResultEmail) and the model (order)
string emailView = GetViewForEmail("OrderResultEmail", order);
//Email the order out
EmailHelper(order, emailView);
return View("OrderResult", order);
}
принятый ответ от Тима Скотта (немного изменен и отформатирован мной):
public virtual string RenderViewToString(
ControllerContext controllerContext,
string viewPath,
string masterPath,
ViewDataDictionary viewData,
TempDataDictionary tempData)
{
Stream filter = null;
ViewPage viewPage = new ViewPage();
//Right, create our view
viewPage.ViewContext = new ViewContext(controllerContext, new WebFormView(viewPath, masterPath), viewData, tempData);
//Get the response context, flush it and get the response filter.
var response = viewPage.ViewContext.HttpContext.Response;
response.Flush();
var oldFilter = response.Filter;
try
{
//Put a new filter into the response
filter = new MemoryStream();
response.Filter = filter;
//Now render the view into the memorystream and flush the response
viewPage.ViewContext.View.Render(viewPage.ViewContext, viewPage.ViewContext.HttpContext.Response.Output);
response.Flush();
//Now read the rendered view.
filter.Position = 0;
var reader = new StreamReader(filter, response.ContentEncoding);
return reader.ReadToEnd();
}
finally
{
//Clean up.
if (filter != null)
{
filter.Dispose();
}
//Now replace the response filter
response.Filter = oldFilter;
}
}
пример использования
предполагая вызов от контроллера, чтобы получить подтверждение заказа по электронной почте, передавая сайт.Мастер-расположение.
string myString = RenderViewToString(this.ControllerContext, "~/Views/Order/OrderResultEmail.aspx", "~/Views/Shared/Site.Master", this.ViewData, this.TempData);
14 ответов:
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(); } }
бритвы .стиль 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(); } }
изменить: добавлен код бритвы.
этот ответ не на моем пути . Это первоначально от https://stackoverflow.com/a/2759898/2318354 но здесь я должен показать способ использовать его с ключевым словом "Static", чтобы сделать его общим для всех контроллеров .
для этого вы должны сделать
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(); } } }
теперь вы можете вызвать этот класс из вашего контроллер, добавив пространство имен в файл контроллера следующим образом, передав "this" в качестве параметра контроллеру.
string result = RazorViewToString.RenderRazorViewToString(this ,"ViewName", model);
как предложение, данное @Sergey этот метод расширения также может вызывать из cotroller, как указано ниже
string result = this.RenderRazorViewToString("ViewName", model);
Я надеюсь, это будет полезно для вас сделать код чистым и аккуратным.
это работает для меня:
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; } }
Я нашел новое решение, которое отображает представление строки без необходимости возиться с потоком ответов текущего 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" исключение).
обновление: in 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); //...
в этой статье описывает, как отобразить представление строки в различных сценариях:
- контроллер MVC вызывает другой из своих собственных ActionMethods
- контроллер MVC вызывает ActionMethod другого контроллера MVC
- WebAPI контроллер вызывает ActionMethod контроллера MVC
решение / код предоставляется в виде класса с именем ViewRenderer. Она является частью Рик Шталя WestwindToolkit на GitHub.
использование (3. - Пример WebAPI):
string html = ViewRenderer.RenderView("~/Areas/ReportDetail/Views/ReportDetail/Index.cshtml", ReportVM.Create(id));
Если вы хотите полностью отказаться от 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);
здесь используется удивительный двигатель бритвы с открытым исходным кодом: https://github.com/Antaris/RazorEngine
вы получаете представление в строке, используя этот способ
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)
Я использую 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
Я видел реализацию для MVC 3 и Razor с другого сайта, это сработало для меня:
public static string RazorRender(Controller context, string DefaultAction) { string Cache = string.Empty; System.Text.StringBuilder sb = new System.Text.StringBuilder(); System.IO.TextWriter tw = new System.IO.StringWriter(sb); RazorView view_ = new RazorView(context.ControllerContext, DefaultAction, null, false, null); view_.Render(new ViewContext(context.ControllerContext, view_, new ViewDataDictionary(), new TempDataDictionary(), tw), tw); Cache = sb.ToString(); return Cache; } public static string RenderRazorViewToString(string viewName, object model) { ViewData.Model = model; using (var sw = new StringWriter()) { var viewResult = ViewEngines.Engines.FindPartialView(ControllerContext, viewName); var viewContext = new ViewContext(ControllerContext, viewResult.View, ViewData, TempData, sw); viewResult.View.Render(viewContext, sw); return sw.GetStringBuilder().ToString(); } } public static class HtmlHelperExtensions { public static string RenderPartialToString(ControllerContext context, string partialViewName, ViewDataDictionary viewData, TempDataDictionary tempData) { ViewEngineResult result = ViewEngines.Engines.FindPartialView(context, partialViewName); if (result.View != null) { StringBuilder sb = new StringBuilder(); using (StringWriter sw = new StringWriter(sb)) { using (HtmlTextWriter output = new HtmlTextWriter(sw)) { ViewContext viewContext = new ViewContext(context, result.View, viewData, tempData, output); result.View.Render(viewContext, output); } } return sb.ToString(); } return String.Empty; } }
подробнее о Razor render-MVC3 View Render to String
совет
для строго типизированной модели просто добавьте его в ViewData.Свойство модели перед передачей в RenderViewToString. е.г
this.ViewData.Model = new OrderResultEmailViewModel(order); string myString = RenderViewToString(this.ControllerContext, "~/Views/Order/OrderResultEmail.aspx", "~/Views/Shared/Site.Master", this.ViewData, this.TempData);
чтобы отобразить представление строки в слое сервиса без необходимости передавать 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);
чтобы повторить из более неизвестного вопроса, взгляните на 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")); }); }
вот класс, который я написал, чтобы сделать это для ASP.NETCore RC2. Я использую его, чтобы я мог генерировать html-адрес электронной почты с помощью бритвы.
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(); } } } }
Я нашел лучший способ визуализации страницы просмотра razor, когда я получил ошибку с описанными выше методами, это решение для среды веб-формы и среды mvc. Контроллер не требуется.
вот пример кода, в этом примере я смоделировал действие mvc с асинхронным обработчиком http:
/// <summary> /// Enables processing of HTTP Web requests asynchronously by a custom HttpHandler that implements the IHttpHandler interface. /// </summary> /// <param name="context">An HttpContext object that provides references to the intrinsic server objects.</param> /// <returns>The task to complete the http request.</returns> protected override async Task ProcessRequestAsync(HttpContext context) { if (this._view == null) { this.OnError(context, new FileNotFoundException("Can not find the mvc view file.".Localize())); return; } object model = await this.LoadModelAsync(context); WebPageBase page = WebPageBase.CreateInstanceFromVirtualPath(this._view.VirtualPath); using (StringWriter sw = new StringWriter()) { page.ExecutePageHierarchy(new WebPageContext(new HttpContextWrapper(context), page, model), sw); await context.Response.Output.WriteAsync(sw.GetStringBuilder().ToString()); } }