在 MVC 中将视图渲染为字符串,然后重定向——解决方法?

发布于 2024-08-02 09:03:13 字数 3492 浏览 4 评论 0原文

尽管这个答案来自二月<,但我无法将视图渲染为字符串然后重定向/a> (我认为在 1.0 版之后)声称这是可能的。我以为我做错了什么,然后我读了这个 Haack 在 7 月份的回答声称这是不可能的。

如果有人让它工作并且可以帮助我让它工作,那就太好了(我将发布代码,错误)。但是,我现在需要解决方法。有一些,但没有什么是理想的。有人解决了这个问题,或者对我的想法有什么意见吗?

  1. 这是为了呈现电子邮件。虽然我肯定可以在 Web 请求之外发送电子邮件(将信息存储在数据库中并稍后获取),但电子邮件的类型有很多种,我不想存储模板数据(用户对象、一些其他 LINQ 对象) )在数据库中以便稍后渲染。我可以创建一个更简单、可序列化的 POCO 并将其保存在数据库中,但为什么呢? ...我只想要渲染的文本!
  2. 我可以创建一个新的 RedirectToAction 对象来检查标头是否已发送(无法弄清楚如何执行此操作 - try/catch?),如果是这样,则构建一个带有元重定向、javascript 重定向的简单页面,还有一个“单击此处”链接。
  3. 在我的控制器中,我可以记住是否已呈现电子邮件,如果是,则通过显示视图手动执行#2。
  4. 我可以在任何潜在的电子邮件呈现之前手动发送重定向标头。然后,我不使用 MVC 基础结构来重定向到操作,而是直接调用 result.end。这看起来很简单,但确实很混乱。
  5. 还要别的吗?

编辑:我已经尝试过 Dan 的代码(与我已经尝试过的一月/二月的代码非常相似),但我仍然遇到相同的错误。我能看到的唯一实质性区别是他的示例使用视图,而我使用部分视图。稍后我会尝试用视图来测试这一点。

这是我得到的:

Controller

public ActionResult Certifications(string email_intro)
        {
            //a lot of stuff

            ViewData["users"] = users;

            if (isPost())
            {
                //create the viewmodel
                var view_model = new ViewModels.Emails.Certifications.Open(userContext)
                {
                    emailIntro = email_intro
                };

                //i've tried stopping this after just one iteration, in case the problem is due to calling it multiple times
                foreach (var user in users)
                {
                    if (user.Email_Address.IsValidEmailAddress())
                    {
                        //add more stuff to the view model specific to this user
                        view_model.user = user;
                        view_model.certification302Summary.subProcessesOwner = new SubProcess_Certifications(RecordUpdating.Role.Owner, null, null, user.User_ID, repository);
                        //more here....

                        //if i comment out the next line, everything works ok
                        SendEmail(view_model, this.ControllerContext);
                    }
                }

                return RedirectToAction("Certifications");
            }

            return View();
        }

SendEmail()

   public static void SendEmail(ViewModels.Emails.Certifications.Open model, ControllerContext context)
        {
            var vd = context.Controller.ViewData;
            vd["model"] = model;
            var renderer = new CustomRenderers();
            //i fixed an error in your code here
            var text = renderer.RenderViewToString3(context, "~/Views/Emails/Certifications/Open.ascx", "", vd, null);
            var a = text;
        }

CustomRenderers

public class CustomRenderers
    {
        public virtual string RenderViewToString3(ControllerContext controllerContext, string viewPath, string masterPath, ViewDataDictionary viewData, TempDataDictionary tempData)
        {
            //copy/paste of dan's code
        }
    }

Error

[HttpException (0x80004005): Cannot redirect after HTTP headers have been sent.]
   System.Web.HttpResponse.Redirect(String url, Boolean endResponse) +8707691

谢谢, 詹姆斯

I can't render a view to a string and then redirect, despite this answer from Feb (after version 1.0, I think) that claims it's possible. I thought I was doing something wrong, and then I read this answer from Haack in July that claims it's not possible.

If somebody has it working and can help me get it working, that's great (and I'll post code, errors). However, I'm now at the point of needing workarounds. There are a few, but nothing ideal. Has anybody solved this, or have any comments on my ideas?

  1. This is to render email. While I can surely send the email outside of the web request (store info in a db and get it later), there are many types of emails and I don't want to store the template data (user object, a few other LINQ objects) in a db to let it get rendered later. I could create a simpler, serializable POCO and save that in the db, but why? ... I just want rendered text!
  2. I can create a new RedirectToAction object that checks if the headers have been sent (can't figure out how to do this -- try/catch?) and, if so, builds out a simple page with a meta redirect, a javascript redirect, and also a "click here" link.
  3. Within my controller, I can remember if I've rendered an email and, if so, manually do #2 by displaying a view.
  4. I can manually send the redirect headers before any potential email rendering. Then, rather than using the MVC infrastructure to redirecttoaction, I just call result.end. This seems easiest, but really messy.
  5. Anything else?

EDIT: I've tried Dan's code (very similar to the code from Jan/Feb that I've already tried) and I'm still getting the same error. The only substantial difference I can see is that his example uses a view while I use a partial view. I'll try testing this later with a view.

Here's what I've got:

Controller

public ActionResult Certifications(string email_intro)
        {
            //a lot of stuff

            ViewData["users"] = users;

            if (isPost())
            {
                //create the viewmodel
                var view_model = new ViewModels.Emails.Certifications.Open(userContext)
                {
                    emailIntro = email_intro
                };

                //i've tried stopping this after just one iteration, in case the problem is due to calling it multiple times
                foreach (var user in users)
                {
                    if (user.Email_Address.IsValidEmailAddress())
                    {
                        //add more stuff to the view model specific to this user
                        view_model.user = user;
                        view_model.certification302Summary.subProcessesOwner = new SubProcess_Certifications(RecordUpdating.Role.Owner, null, null, user.User_ID, repository);
                        //more here....

                        //if i comment out the next line, everything works ok
                        SendEmail(view_model, this.ControllerContext);
                    }
                }

                return RedirectToAction("Certifications");
            }

            return View();
        }

SendEmail()

   public static void SendEmail(ViewModels.Emails.Certifications.Open model, ControllerContext context)
        {
            var vd = context.Controller.ViewData;
            vd["model"] = model;
            var renderer = new CustomRenderers();
            //i fixed an error in your code here
            var text = renderer.RenderViewToString3(context, "~/Views/Emails/Certifications/Open.ascx", "", vd, null);
            var a = text;
        }

CustomRenderers

public class CustomRenderers
    {
        public virtual string RenderViewToString3(ControllerContext controllerContext, string viewPath, string masterPath, ViewDataDictionary viewData, TempDataDictionary tempData)
        {
            //copy/paste of dan's code
        }
    }

Error

[HttpException (0x80004005): Cannot redirect after HTTP headers have been sent.]
   System.Web.HttpResponse.Redirect(String url, Boolean endResponse) +8707691

Thanks,
James

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

2024-08-09 09:03:13
public Action SendEmail(int id)
{
  //Let's say that id is the db id of an order that a customer has just placed.

  //Go get that model from the db.
  MyModel model = new Model(id);

  //Now send that email. Don't forget the model and controller context.
  SendEmail(model, this.ControllerContext);

  //Render (or redirect!)
  return RedirectToAction("Wherever");
}

private static void SendEmail(MyModel model, ControllerContext controllerContext)
{
  //Recreate the viewdata
  ViewDataDictionary viewData = controllerContext.Controller.ViewData;
  viewData["Order"] = model;
  string renderedView = "";
  CustomRenderers customRenderers = new CustomRenderers();

  //Now render the view to string
  //ControllerContext, ViewPath, MasterPath, ViewDataDictionary, TempDataDictionary
  //As you can see, we're not passing a master page, and the tempdata is in this instance.
  renderedView = RenderViewToString(controllerContext, "~/Views/Orders/Email.aspx", "", viewData, null);

  //Now send your email with the string as the body.
  //Not writing that, as the purpose is just to show the rendering. :)
}


//Elsewhere...
public class CustomRenderers
{
  public virtual string RenderViewToString(ControllerContext controllerContext, string viewPath, string masterPath, ViewDataDictionary viewData, TempDataDictionary tempData)
  {
    if (tempData == null)
    {
    tempData = new TempDataDictionary();
    }

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

在 Orders/Email.aspx 视图中,确保引用 ViewData 中的所有内容,而不是模型。你可以这样做:

<% MyModel model = (MyModel)ViewData["Order"] %>
public Action SendEmail(int id)
{
  //Let's say that id is the db id of an order that a customer has just placed.

  //Go get that model from the db.
  MyModel model = new Model(id);

  //Now send that email. Don't forget the model and controller context.
  SendEmail(model, this.ControllerContext);

  //Render (or redirect!)
  return RedirectToAction("Wherever");
}

private static void SendEmail(MyModel model, ControllerContext controllerContext)
{
  //Recreate the viewdata
  ViewDataDictionary viewData = controllerContext.Controller.ViewData;
  viewData["Order"] = model;
  string renderedView = "";
  CustomRenderers customRenderers = new CustomRenderers();

  //Now render the view to string
  //ControllerContext, ViewPath, MasterPath, ViewDataDictionary, TempDataDictionary
  //As you can see, we're not passing a master page, and the tempdata is in this instance.
  renderedView = RenderViewToString(controllerContext, "~/Views/Orders/Email.aspx", "", viewData, null);

  //Now send your email with the string as the body.
  //Not writing that, as the purpose is just to show the rendering. :)
}


//Elsewhere...
public class CustomRenderers
{
  public virtual string RenderViewToString(ControllerContext controllerContext, string viewPath, string masterPath, ViewDataDictionary viewData, TempDataDictionary tempData)
  {
    if (tempData == null)
    {
    tempData = new TempDataDictionary();
    }

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

In your Orders/Email.aspx view, make sure you refer to everything from the ViewData, rather than the model. you can do this:

<% MyModel model = (MyModel)ViewData["Order"] %>
给妤﹃绝世温柔 2024-08-09 09:03:13

这是将视图渲染为字符串的另一种方法,该方法永远不会导致数据输出到响应(因此它应该避免您的问题): http://craftycodeblog.com/2010/05/15/asp-net-mvc-render-partial-view- to-string/

要渲染常规视图而不是部分视图,您需要将“ViewEngines.Engines.FindPartialView”更改为“ViewEngines.Engines.FindView”。

Here is an alternative method for rendering a view to a string that never results in data being output to the response (therefore it should avoid your problem): http://craftycodeblog.com/2010/05/15/asp-net-mvc-render-partial-view-to-string/

To render a regular view instead of a partial view, you'll need to change "ViewEngines.Engines.FindPartialView" to "ViewEngines.Engines.FindView".

会发光的星星闪亮亮i 2024-08-09 09:03:13

已更新。

现在我知道您想要使用视图引擎生成 html 格式的实际电子邮件,我建议如下:

在控制器内将操作呈现给文本的代码:
http://mikehadlow.blogspot.com/2008 /06/mvc-framework-capturing-output-of-view_05.html

对代码进行少量编辑:

public ActionResult Certifications(string email_intro)
{
     //a lot of stuff              
     ViewData["users"] = users;              
     if (isPost())             
     {                 
         //create the viewmodel                 
         var view_model = new ViewModels.Emails.Certifications.Open(userContext) { emailIntro = email_intro };                  

         foreach (var user in users)                 
         {                     
             if (user.Email_Address.IsValidEmailAddress())                     
             {                         
                 view_model.user = user;                         
                 view_model.certification302Summary.subProcessesOwner = new SubProcess_Certifications(RecordUpdating.Role.Owner, null, null, user.User_ID, repository);                         

                 SendEmail(view_model);                     
             }                 
         }                  
         return RedirectToAction("Certifications");             
     }              
     return View();         
 } 

 public void SendEmail(ViewModels.Emails.Certifications.Open model)         
 {             
    var vd = context.Controller.ViewData;             
    vd["model"] = model;             
    var renderer = new CustomRenderers();             

    // Implement the actual email rendering as a regular action method on this controller
    var text = this.CaptureActionHtml(c => (ViewResult)c.RenderEmail(model));

    var a = text;         
} 

Updated.

Now that I understand that you want to use the view engine to generate the actual email in html, I propose the following:

Code to render action to text within a controller:
http://mikehadlow.blogspot.com/2008/06/mvc-framework-capturing-output-of-view_05.html

Minor edits to your code:

public ActionResult Certifications(string email_intro)
{
     //a lot of stuff              
     ViewData["users"] = users;              
     if (isPost())             
     {                 
         //create the viewmodel                 
         var view_model = new ViewModels.Emails.Certifications.Open(userContext) { emailIntro = email_intro };                  

         foreach (var user in users)                 
         {                     
             if (user.Email_Address.IsValidEmailAddress())                     
             {                         
                 view_model.user = user;                         
                 view_model.certification302Summary.subProcessesOwner = new SubProcess_Certifications(RecordUpdating.Role.Owner, null, null, user.User_ID, repository);                         

                 SendEmail(view_model);                     
             }                 
         }                  
         return RedirectToAction("Certifications");             
     }              
     return View();         
 } 

 public void SendEmail(ViewModels.Emails.Certifications.Open model)         
 {             
    var vd = context.Controller.ViewData;             
    vd["model"] = model;             
    var renderer = new CustomRenderers();             

    // Implement the actual email rendering as a regular action method on this controller
    var text = this.CaptureActionHtml(c => (ViewResult)c.RenderEmail(model));

    var a = text;         
} 
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文