Отобразить представление Razor в string => ControllerContext имеет значение null при вызове из повторяющейся задачи
Я использую ASP.NET MVC3
у меня есть .cshtml
просмотреть, и я хочу, чтобы это было включено в текст сообщения электронной почты.
Вот метод, который я использую:
//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();
}
}
Когда я вызываю этот метод из ActionResult
метод, который вызывается из вызова Ajax, это прекрасно работает.
Однако я сталкиваюсь с необычной ситуацией:
В моем Global.asax
файл, у меня есть метод, который вызывается каждые 10 минут, цель которого - проверить, были ли сделаны какие-то специальные записи в базе данных за последние 10 минут, и если да, отправляет электронное письмо. Конечно, тело письма - это строковое представление.
Вот часть моего кода: Этот метод очень вдохновлен этим постом
/* 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 не может быть нулевым
Почему только в этом случае ControllerContext
является null
? Я прочитал этот пост, но если я правильно его понял, это потому, что я вручную создал запись моего контроллера:
MatchingController matchingController = new MatchingController();
Однако я не знаю, как поступить иначе...
Любая помощь будет очень ценится.
Спасибо
3 ответа
Вместо того, чтобы пытаться симулировать веб-попадание, вы на самом деле можете инициировать веб-попадание, чтобы позволить вам визуализировать представление в правильном контексте. Возьмите результат и сохраните его в теле письма. Мне пришлось сделать что-то подобное для страховых котировок. Вот мой код с последующей адаптацией под ваши нужды.
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");
}
У B2K правильная идея - вам нужно инициализировать веб-запрос, вызвав приложение извне, чтобы оно генерировало новый HttpContext для генерации HTML.
Вы можете использовать совет здесь или здесь, чтобы создать фоновый e-mail.
Один из подходов состоит в том, чтобы использовать первоначальный запрос, чтобы структурировать вашу электронную почту и сохранить ее в базе данных для последующей рассылки.
Кроме того, вы можете просто установить MvcMailer или Postal и использовать их решение.
По MSDN:
Любые открытые статические (Shared в Visual Basic) члены этого типа являются потокобезопасными. Любые члены экземпляра не гарантированно являются потокобезопасными.
Метод не работает, потому что ControllerContext
не перечисляется локально и MatchingController matchingController = new MatchingController();
не вступает в силу! Итак, какова реальная ценность для этого? Учитывая, что вы вызываете это в отдельных методах (а иногда и в потоках), впоследствии вы не можете использовать такие контекстно-релевантные методы, как, например, тот, который используется, скажем, [ViewEngineCollection.FindPartialView()][2]
так как он не может использовать его controllerContext
член (есть null
).
Решение:
Вы можете использовать конструктор в случае Application_Start
и использовать тот же метод. что-то вроде этого:
var viewResult = ViewEngines.Engines.FindPartialView(new ControllerContext(), viewName);
или используя ViewContext.View
вместо FindPartialView
и переписать некоторые методы:(