Thread.Timer 无法使用 HttpContext

Thread.Timer With HttpContext is not working

你好,我正在尝试在特定时间发送电子邮件,我找到了代码。 在 global.asax.cs 文件上

protected void Application_Start()
{
    ETHOS.Controllers.HomeController mail = new Controllers.HomeController();
    mail.ScheduleService();
    private void SchedularCallback(object e)
    {
        this.WriteToFile("Simple Service Log: {0}");
        getData();//Email function
        this.ScheduleService();
    }
    public void ScheduleService()
    {
        try
        {
            Schedular = new Timer(new TimerCallback(SchedularCallback));
            string mode = "DAILY";
            this.WriteToFile("Simple Service Mode: " + mode + " {0}");
            //Set the Default Time.
            //DateTime d = DateTime.Today;
            //TimeSpan t = new TimeSpan(12, 40, 00);
            //DateTime scheduledTime = d.Date + t;
            DateTime scheduledTime = DateTime.Now.AddSeconds(30);
            if (DateTime.Now > scheduledTime)
            {
                //If Scheduled Time is passed set Schedule for the next day.
                // scheduledTime = scheduledTime.AddDays(1);
                scheduledTime = scheduledTime.AddDays(1);
            }
            TimeSpan timeSpan = scheduledTime.Subtract(DateTime.Now);
            string schedule = string.Format("{0} day(s) {1} hour(s) {2} minute(s) {3} seconds(s)", timeSpan.Days, timeSpan.Hours, timeSpan.Minutes, timeSpan.Seconds);
            this.WriteToFile("Simple Service scheduled to run after: " + schedule + " {0}");
            //Get the difference in Minutes between the Scheduled and Current Time.
            int dueTime = Convert.ToInt32(timeSpan.TotalMilliseconds);

            //Change the Timer's Due Time.
            Schedular.Change(dueTime, Timeout.Infinite);
        }
        catch (Exception ex)
        {
            WriteToFile("Simple Service Error on: {0} " + ex.Message + ex.StackTrace);
        }
    }

在电子邮件功能中,我通过 gmail 发送电子邮件,在正文中,我将部分视图呈现为字符串 class,将我的剃刀视图呈现为字符串

 string body = ViewRenderer.RenderPartialView("~/Views/Shared/Email.cshtml", LEM);

问题是它没有得到 HttpContext.Current.It show HttpContext.Current = null。我认为两者都在创建线程,所以这就是为什么它没有获得 HttpContext 那么我如何将两者与相同的 httpcontext 一起使用。 这是下面的 RendererView Class

   public class ViewRenderer:ETHOS.Controllers.HomeController
      {/// <summary>
    /// Required Controller Context
    /// </summary>
    protected ControllerContext Context { get; set; }

    /// <summary>
    /// Initializes the ViewRenderer with a Context.
    /// </summary>
    /// <param name="controllerContext">
    /// If you are running within the context of an ASP.NET MVC request pass in
    /// the controller's context. 
    /// Only leave out the context if no context is otherwise available.
    /// </param>
    public ViewRenderer(ControllerContext controllerContext = null)
    {
        System.Web.HttpContext ctx =  (System.Web.HttpContext) Session["ctx"];
        // Create a known controller from HttpContext if no context is passed
        if (controllerContext == null)
        {
            if (System.Web.HttpContext.Current != null)
                controllerContext = CreateController<EmptyController>().ControllerContext;
            else
                throw new InvalidOperationException(
                    "ViewRenderer must run in the context of an ASP.NET " +
                    "Application and requires HttpContext.Current to be present.");
        }
        Context = controllerContext;
    }

    /// <summary>
    /// Renders a full MVC view to a string. Will render with the full MVC
    /// View engine including running _ViewStart and merging into _Layout        
    /// </summary>
    /// <param name="viewPath">
    /// The path to the view to render. Either in same controller, shared by 
    /// name or as fully qualified ~/ path including extension
    /// </param>
    /// <param name="model">The model to render the view with</param>
    /// <returns>String of the rendered view or null on error</returns>
    public string RenderViewToString(string viewPath, object model = null)
    {
        return RenderViewToStringInternal(viewPath, model, false);
    }

    /// <summary>
    /// Renders a full MVC view to a writer. Will render with the full MVC
    /// View engine including running _ViewStart and merging into _Layout        
    /// </summary>
    /// <param name="viewPath">
    /// The path to the view to render. Either in same controller, shared by 
    /// name or as fully qualified ~/ path including extension
    /// </param>
    /// <param name="model">The model to render the view with</param>
    /// <returns>String of the rendered view or null on error</returns>
    public void RenderView(string viewPath, object model, TextWriter writer)
    {
        RenderViewToWriterInternal(viewPath, writer, model, false);
    }


    /// <summary>
    /// Renders a partial MVC view to string. Use this method to render
    /// a partial view that doesn't merge with _Layout and doesn't fire
    /// _ViewStart.
    /// </summary>
    /// <param name="viewPath">
    /// The path to the view to render. Either in same controller, shared by 
    /// name or as fully qualified ~/ path including extension
    /// </param>
    /// <param name="model">The model to pass to the viewRenderer</param>
    /// <returns>String of the rendered view or null on error</returns>
    public string RenderPartialViewToString(string viewPath, object model = null)
    {
        return RenderViewToStringInternal(viewPath, model, true);
    }

    /// <summary>
    /// Renders a partial MVC view to given Writer. Use this method to render
    /// a partial view that doesn't merge with _Layout and doesn't fire
    /// _ViewStart.
    /// </summary>
    /// <param name="viewPath">
    /// The path to the view to render. Either in same controller, shared by 
    /// name or as fully qualified ~/ path including extension
    /// </param>
    /// <param name="model">The model to pass to the viewRenderer</param>
    /// <param name="writer">Writer to render the view to</param>
    public void RenderPartialView(string viewPath, object model, TextWriter writer)
    {
        RenderViewToWriterInternal(viewPath, writer, model, true);
    }

    /// <summary>
    /// Renders a partial MVC view to string. Use this method to render
    /// a partial view that doesn't merge with _Layout and doesn't fire
    /// _ViewStart.
    /// </summary>
    /// <param name="viewPath">
    /// The path to the view to render. Either in same controller, shared by 
    /// name or as fully qualified ~/ path including extension
    /// </param>
    /// <param name="model">The model to pass to the viewRenderer</param>
    /// <param name="controllerContext">Active Controller context</param>
    /// <returns>String of the rendered view or null on error</returns>
    public static string RenderView(string viewPath, object model = null,
                                    ControllerContext controllerContext = null)
    {
        ViewRenderer renderer = new ViewRenderer(controllerContext);
        return renderer.RenderViewToString(viewPath, model);
    }

    /// <summary>
    /// Renders a partial MVC view to the given writer. Use this method to render
    /// a partial view that doesn't merge with _Layout and doesn't fire
    /// _ViewStart.
    /// </summary>
    /// <param name="viewPath">
    /// The path to the view to render. Either in same controller, shared by 
    /// name or as fully qualified ~/ path including extension
    /// </param>
    /// <param name="model">The model to pass to the viewRenderer</param>
    /// <param name="writer">Writer to render the view to</param>
    /// <param name="controllerContext">Active Controller context</param>
    /// <returns>String of the rendered view or null on error</returns>
    public static void RenderView(string viewPath, TextWriter writer, object model,
                                    ControllerContext controllerContext)
    {
        ViewRenderer renderer = new ViewRenderer(controllerContext);
        renderer.RenderView(viewPath, model, writer);
    }

    /// <summary>
    /// Renders a partial MVC view to string. Use this method to render
    /// a partial view that doesn't merge with _Layout and doesn't fire
    /// _ViewStart.
    /// </summary>
    /// <param name="viewPath">
    /// The path to the view to render. Either in same controller, shared by 
    /// name or as fully qualified ~/ path including extension
    /// </param>
    /// <param name="model">The model to pass to the viewRenderer</param>
    /// <param name="controllerContext">Active Controller context</param>
    /// <param name="errorMessage">optional out parameter that captures an error message instead of throwing</param>
    /// <returns>String of the rendered view or null on error</returns>
    public static string RenderView(string viewPath, object model,
                                    ControllerContext controllerContext,
                                    out string errorMessage)
    {
        errorMessage = null;
        try
        {
            ViewRenderer renderer = new ViewRenderer(controllerContext);
            return renderer.RenderViewToString(viewPath, model);
        }
        catch (Exception ex)
        {
            errorMessage = ex.GetBaseException().Message;
        }
        return null;
    }

    /// <summary>
    /// Renders a partial MVC view to the given writer. Use this method to render
    /// a partial view that doesn't merge with _Layout and doesn't fire
    /// _ViewStart.
    /// </summary>
    /// <param name="viewPath">
    /// The path to the view to render. Either in same controller, shared by 
    /// name or as fully qualified ~/ path including extension
    /// </param>
    /// <param name="model">The model to pass to the viewRenderer</param>
    /// <param name="controllerContext">Active Controller context</param>
    /// <param name="writer">Writer to render the view to</param>
    /// <param name="errorMessage">optional out parameter that captures an error message instead of throwing</param>
    /// <returns>String of the rendered view or null on error</returns>
    public static void RenderView(string viewPath, object model, TextWriter writer,
                                    ControllerContext controllerContext,
                                    out string errorMessage)
    {
        errorMessage = null;
        try
        {
            ViewRenderer renderer = new ViewRenderer(controllerContext);
            renderer.RenderView(viewPath, model, writer);
        }
        catch (Exception ex)
        {
            errorMessage = ex.GetBaseException().Message;
        }
    }


    /// <summary>
    /// Renders a partial MVC view to string. Use this method to render
    /// a partial view that doesn't merge with _Layout and doesn't fire
    /// _ViewStart.
    /// </summary>
    /// <param name="viewPath">
    /// The path to the view to render. Either in same controller, shared by 
    /// name or as fully qualified ~/ path including extension
    /// </param>
    /// <param name="model">The model to pass to the viewRenderer</param>
    /// <param name="controllerContext">Active controller context</param>
    /// <returns>String of the rendered view or null on error</returns>
    public static string RenderPartialView(string viewPath, object model = null,
                                            ControllerContext controllerContext = null)
    {
        ViewRenderer renderer = new ViewRenderer(controllerContext);
        return renderer.RenderPartialViewToString(viewPath, model);
    }

    /// <summary>
    /// Renders a partial MVC view to string. Use this method to render
    /// a partial view that doesn't merge with _Layout and doesn't fire
    /// _ViewStart.
    /// </summary>
    /// <param name="viewPath">
    /// The path to the view to render. Either in same controller, shared by 
    /// name or as fully qualified ~/ path including extension
    /// </param>
    /// <param name="model">The model to pass to the viewRenderer</param>
    /// <param name="controllerContext">Active controller context</param>
    /// <param name="writer">Text writer to render view to</param>
    /// <param name="errorMessage">optional output parameter to receive an error message on failure</param>
    public static void RenderPartialView(string viewPath, TextWriter writer, object model = null,
                                            ControllerContext controllerContext = null)
    {
        ViewRenderer renderer = new ViewRenderer(controllerContext);
        renderer.RenderPartialView(viewPath, model, writer);
    }


    /// <summary>
    /// Internal method that handles rendering of either partial or 
    /// or full views.
    /// </summary>
    /// <param name="viewPath">
    /// The path to the view to render. Either in same controller, shared by 
    /// name or as fully qualified ~/ path including extension
    /// </param>
    /// <param name="model">Model to render the view with</param>
    /// <param name="partial">Determines whether to render a full or partial view</param>
    /// <param name="writer">Text writer to render view to</param>
    protected void RenderViewToWriterInternal(string viewPath, TextWriter writer, 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();

        // get the view and attach the model to view data
        var view = viewEngineResult.View;
        Context.Controller.ViewData.Model = model;

        var ctx = new ViewContext(Context, view,
                                    Context.Controller.ViewData,
                                    Context.Controller.TempData,
                                    writer);
        view.Render(ctx, writer);
    }

    /// <summary>
    /// Internal method that handles rendering of either partial or 
    /// or full views.
    /// </summary>
    /// <param name="viewPath">
    /// The path to the view to render. Either in same controller, shared by 
    /// name or as fully qualified ~/ path including extension
    /// </param>
    /// <param name="model">Model to render the view with</param>
    /// <param name="partial">Determines whether to render a full or partial view</param>
    /// <returns>String of the rendered view</returns>
    private string RenderViewToStringInternal(string viewPath, object model,
                                                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 || viewEngineResult.View == null)
            throw new FileNotFoundException();//Resources.ViewCouldNotBeFound);

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


    /// <summary>
    /// Creates an instance of an MVC controller from scratch 
    /// when no existing ControllerContext is present       
    /// </summary>
    /// <typeparam name="T">Type of the controller to create</typeparam>
    /// <returns>Controller for T</returns>
    /// <exception cref="InvalidOperationException">thrown if HttpContext not available</exception>
    public static T CreateController<T>(RouteData routeData = null, params object[] parameters)
                where T : Controller, new()
    {
        // create a disconnected controller instance
        T controller = (T)Activator.CreateInstance(typeof(T), parameters);

        // get context wrapper from HttpContext if available
        HttpContextBase wrapper = null;
        if (System.Web.HttpContext.Current != null)
            wrapper = new HttpContextWrapper(System.Web.HttpContext.Current);
        else
            throw new InvalidOperationException(
                "Can't 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;
    }

}

/// <summary>
/// Empty MVC Controller instance used to 
/// instantiate and provide a new ControllerContext
/// for the ViewRenderer
/// </summary>
public class EmptyController : Controller
{
}

HttpContext 仅在 HTTP 请求的生命周期内可用。在此类后台线程中无法在外部使用它。 implement recurring background tasks in an ASP.NET applications 也被认为是不好的做法。推荐的方法是从您的 Web 应用程序 off-load 此任务并将其放在 Windows 服务或控制台应用程序中,这些服务或控制台应用程序将由 Windows 调度程序定期执行。

也就是说,如果您决定违背推荐的方法并仍然坚持这样做,您可能会考虑 using the Razor engine outside of an ASP.NET application 这样您就不会依赖 HttpContext

那是因为您无法从除 HTTP 请求线程之外的其他线程访问 HttpContext.Current。除了传入上下文之外,您无能为力,这可能很危险。

我会认真地建议您 运行 进程中的代码与 ASP.NET 分开(例如 Windows 服务)。