将Razor视图渲染为string =>从循环任务调用时,ControllerContext为null

时间:2014-12-04 15:34:22

标签: .net asp.net-mvc asp.net-mvc-3 task controllercontext

我使用的是ASP.NET MVC3 我有一个.cshtml视图,我希望 stringify 将其合并到一个电子邮件正文中。
这是我使用的方法:

//Renders a view to a string
private string RenderRazorViewToString(string viewName, object model)
{
    ViewData.Model = model;

    using (var sw = new System.IO.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();
    }
}

当我从一个从Ajax调用中调用的ActionResult方法调用此方法时,完美地工作

但是,我遇到了一个不寻常的情况:

在我的Global.asax文件中,我有一个每10分钟调用一次的方法,其目的是验证在过去10分钟内是否在数据库中创建了一些特殊记录,如果是,则发送电子邮件。当然,电子邮件正文是 stringified 视图。

以下是我的一段代码:此方法的灵感来自于this post

/* File : Gloabal.asax.cs */

private static CacheItemRemovedCallback OnMatchingCacheRemove = null;

protected void Application_Start()
{
    // ...
    AddMatchingTask("SendEmail", 600);
}

private void AddMatchingTask(string name, int seconds)
{
    OnMatchingCacheRemove = new CacheItemRemovedCallback(CacheItemMatchingRemoved);
    HttpRuntime.Cache.Insert(name, seconds, null, DateTime.UtcNow.AddSeconds(seconds), Cache.NoSlidingExpiration, CacheItemPriority.NotRemovable, OnMatchingCacheRemove);
}


//This method is called every 600 seconds
public void CacheItemMatchingRemoved(string k, object v, CacheItemRemovedReason r)
{
    using (MyEntities context = new MyEntities())
    {
        var qMatching = from m in context.MY_TABLE
                        where m.IsNew == true
                        select m;

        if (qMatching.Any())
        {
            MatchingController matchingController = new MatchingController();
            matchingController.SendEmail();
        }
    }

    // re-add our task so it recurs
    AddMatchingTask(k, Convert.ToInt32(v));
 }

SendEmail()方法应该创建电子邮件正文,获取视图并将其放入HTML字符串中发送

public void SendEmail()
{
     /* [...] Construct a model myModel */

     /* Then create the body of the mail */
     string htmlContent = RenderRazorViewToString("~/Views/Mailing/MatchingMail.cshtml", myModel);  
}

此处,RenderRazorViewToString()(此帖子顶部给出的方法正文)在此行失败:

var viewResult = ViewEngines.Engines.FindPartialView(ControllerContext, viewName);
  

ControllerContext不能为空

为什么,在这种情况下只有ControllerContextnull? 我已阅读this post,但如果我理解正确,这是因为我手动实例化了我的控制器编写:

MatchingController matchingController = new MatchingController();

但是,我不知道如何继续......

任何帮助都将非常赞赏 谢谢

3 个答案:

答案 0 :(得分:2)

B2K有正确的想法 - 您需要通过从外部调用应用程序来初始化Web请求,因此它将启动一个新的HttpContext来生成HTML。

您可以使用建议herehere来创建后台电子邮件。

一种方法是使用初始请求对您的电子邮件进行字符串化并将其保存在数据库中以供以后邮寄。

或者,您可以安装MvcMailerPostal并使用他们的解决方案。

答案 1 :(得分:1)

收到MSDN

  

此类型的任何公共静态(在Visual Basic中为Shared)成员都是   线程安全。任何实例成员都不能保证是线程   安全

该方法失败,因为ControllerContext未在本地枚举,MatchingController matchingController = new MatchingController();不生效!那么,它的真正价值是什么?考虑到您在单独的方法(有时是线程)中调用它,随后,您不能使用诸如[ViewEngineCollection.FindPartialView()][2]之类的上下文相关方法,因为它不能使用其controllerContext } member(是null)。

<强>解决方案:

如果Application_Start,您可能需要use a constructor并使用相同的方法。像这样的东西:

var viewResult = ViewEngines.Engines.FindPartialView(new ControllerContext(), viewName);

或使用ViewContext.View代替FindPartialView并重写一些方法:(

答案 2 :(得分:1)

您可以实际触发网络点击,而不是尝试模拟网络点击,以允许您使用适当的上下文呈现视图。获取结果并将其存储到电子邮件正文中。我必须为保险报价做类似的事情。这是我的代码,然后根据您的需要进行调整。

    public ActionResult StartInsuranceQuote()
    {

        using (var client = new WebClient())
        {
            var values = new NameValueCollection
            {
                { "sid", DataSession.Id.ExtractSid() }
            };
            client.UploadValuesAsync(new Uri(Url.AbsoluteAction("QuoteCallback", "Quote")), values);
        }
        return PartialView();                
    }

这样做的关键是从模型中填充值集合。由于你没有提供,我会假设一些属性用于说明:

    public void SendEmail(YourViewModel model)
    {
        using (var client = new WebClient())
        {
            var values = new NameValueCollection
            {
                { "Name",  model.Name },
                { "Product", model.Product },
                { "Color", model.Color },
                { "Comment", model.Comment }
            };
            string body = client.UploadValues(new Uri(Url.AbsoluteAction("GenerateBody", "RenderEmail")), values);

            // send email here
        }
    }

RenderEmailController:

    public ActionResult GenerateBody()
    {
        return View();
    }

GenerateBody.cshtml:

@foreach (string key in Request.Form.AllKeys)
{
    Response.Write(key + "=" + Request[key] + "<br />");
}

更新:AbsoluteAction是一种扩展方法,包含在

下面
public static string AbsoluteAction(this UrlHelper url, string actionName, string controllerName, object routeValues = null)
{
    if (url.RequestContext.HttpContext.Request.Url != null)
    {
        string scheme = url.RequestContext.HttpContext.Request.Url.Scheme;
        return url.Action(actionName, controllerName, routeValues, scheme);
    }
    throw new Exception("Absolute Action: Url is null");
}