Programming

ASP.NET MVC 뷰를 문자열로 렌더링하는 방법은 무엇입니까?

procodes 2020. 2. 14. 23:50
반응형

ASP.NET MVC 뷰를 문자열로 렌더링하는 방법은 무엇입니까?


두 가지 다른보기 (하나는 이메일로 전송되는 문자열)와 다른 하나는 사용자에게 표시되는 페이지를 출력하고 싶습니다.

ASP.NET MVC 베타에서 가능합니까?

여러 예제를 시도했습니다.

1. ASP.NET MVC 베타에서 RenderPartial을 문자열로

이 예제를 사용하면 "HTTP 헤더를 보낸 후 리디렉션 할 수 없습니다."라는 메시지가 나타납니다.

2. MVC 프레임 워크 : 뷰의 출력 캡처

이것을 사용하면 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);
}

Tim Scott의 답변 (나에 의해 조금 변경되고 형식화 됨) :

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

여기에 내가 생각해 낸 것이 있으며 나를 위해 일하고 있습니다. 컨트롤러 기본 클래스에 다음 방법을 추가했습니다. (내가 생각하는 매개 변수로 컨트롤러를 허용하는 다른 곳 에서이 정적 메소드를 항상 만들 수 있습니다)

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

편집 : 면도기 코드가 추가되었습니다.


이 대답은 내 길에 없습니다. 이것은 원래 https://stackoverflow.com/a/2759898/2318354 에서 가져온 것이지만 여기서는 "Static"키워드와 함께 사용하여 모든 컨트롤러에 공통으로 사용하는 방법을 보여주었습니다.

이를 위해서는 static클래스 파일에서 클래스 를 만들어야 합니다. (클래스 파일 이름은 Utils.cs라고 입력하십시오)

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

이제 "this"를 매개 변수로 Controller에 전달하여 다음과 같이 Controller File에 NameSpace를 추가하여 컨트롤러에서이 클래스를 호출 할 수 있습니다.

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의 Response 스트림을 엉망으로 만들지 않고 문자열로 뷰를 렌더링하는 새로운 솔루션을 찾았습니다 (응답의 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);

//...

이 문서에서는 다른 시나리오에서 뷰를 문자열로 렌더링하는 방법을 설명합니다.

  1. 다른 자체 ActionMethods를 호출하는 MVC Controller
  2. 다른 MVC 컨트롤러의 ActionMethod를 호출하는 MVC 컨트롤러
  3. MVC 컨트롤러의 ActionMethod를 호출하는 WebAPI 컨트롤러

솔루션 / 코드는 ViewRenderer 라는 클래스로 제공됩니다 . GitHub 의 Rick Stahl WestwindToolkit 의 일부입니다 .

사용법 (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);

이것은 멋진 오픈 소스 Razor Engine을 사용합니다 : 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;

        }

    }

더에 면도기 render- MVC3보기 문자열로 렌더링


ControllerContext를 전달하지 않고 서비스 계층에서 문자열로보기를 렌더링하려면 여기 에 일반 컨트롤러를 작성하는 Rick Rick의 기사 ( 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);

빠른 팁

강력한 형식의 Model의 경우 RenderViewToString에 전달하기 전에 ViewData.Model 속성에 추가하십시오. 예 :

this.ViewData.Model = new OrderResultEmailViewModel(order);
string myString = RenderViewToString(this.ControllerContext, "~/Views/Order/OrderResultEmail.aspx", "~/Views/Shared/Site.Master", this.ViewData, this.TempData);

더 알려지지 않은 질문을 반복하려면 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를 위해 작성한 클래스입니다. Razor를 사용하여 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();
            }
        }
    }
}

위의 방법으로 오류가 발생했을 때 면도기보기 페이지를 렌더링하는 더 좋은 방법을 찾았습니다.이 양식은 웹 양식 환경과 mvc 환경 모두에 적합합니다. 컨트롤러가 필요하지 않습니다.

다음은 코드 예제입니다.이 예제에서는 비동기 http 핸들러를 사용하여 mvc 작업을 시뮬레이션했습니다.

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

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

참고 URL : https://stackoverflow.com/questions/483091/how-to-render-an-asp-net-mvc-view-as-a-string



반응형