asp.net - 在 ASP.NET MVC5 中执行长时间运行任务的最佳方法

标签 asp.net asp.net-mvc iis asp.net-mvc-5 quartz.net

我正在开发一个部署在 IIS 7.0 内的 asp.net mvc5 Web 应用程序 + Entity Framework 6.0。目前我有一个 NetworkScanning 服务器,我将其作为正常操作方法实现,可以通过两种方式启动:-

1.基于global.asax中定义的时间表:-

static void ScheduleTaskTrigger()
        {
            HttpRuntime.Cache.Add("ScheduledTaskTrigger",
                                  string.Empty,
                                  null,
                                  Cache.NoAbsoluteExpiration,
                                  TimeSpan.FromMinutes(Double.Parse(System.Web.Configuration.WebConfigurationManager.AppSettings["ScanInMinutes"])), // as defined inside the web.config
                                  CacheItemPriority.NotRemovable,
                                  new CacheItemRemovedCallback(PerformScheduledTasks));
        }

        static void PerformScheduledTasks(string key, Object value, CacheItemRemovedReason reason)
        {
            //Your TODO
            HomeController h = new HomeController();
            var c = h.ScanNetwork("***", "allscan");
            ScheduleTaskTrigger();
        }

2.或从用户浏览器手动调用操作方法。

现在操作方法如下所示(我删除了很多代码,因为这个想法是为了提示我在操作方法中正在做什么):-

public async Task<ActionResult> ScanNetwork(string token, string FQDN) 
        {
            string TToken = System.Web.Configuration.WebConfigurationManager.AppSettings["TToken"];//get the T token from the web.config, this should be encrypted
            var cccc = Request == null ? System.Web.Configuration.WebConfigurationManager.AppSettings["TIP"] : Request.UserHostAddress;
            if (token != TToken || cccc != System.Web.Configuration.WebConfigurationManager.AppSettings["TIP"]) 
            {


                if (FQDN != "allscan")
                { return Json(new { status = "fail", message = "Authintication failed." }, JsonRequestBehavior.AllowGet); }
                return new HttpStatusCodeResult(403, "request failed");
            }

            try
            {

              scaninfo = await repository.populateScanInfo(false); // get the info for all the servers from the database



                using (WebClient wc = new WebClient()) 
                {
                    string url = currentURL + "resources?AUTHTOKEN=" + pmtoken;
                    var json = await wc.DownloadStringTaskAsync(url);
                    resourcesinfo = JsonConvert.DeserializeObject<ResourcesInfo>(json);
                }


                foreach (var c in scaninfo) //loop through all the hypervisot server/s
                {


                    if (passwordmanagerResource.Count() == 0) // if there is not any record for the resource on the password manager
                    {


                       await repository.Save();
                        continue;

                    }
                    else if (passwordmanagerResource.Count() > 1) // if more than on record is defined for the same resource on PM
                    {


                        await repository.Save();
                        continue;

                    }
                    else
                    {



                        using (WebClient wc = new WebClient()) // call the PM API to get the account id 
                        {
                            string url = currentURL + "resources/" + passwordmanagerResourceID + "/accounts?AUTHTOKEN=" + pmtoken;
                            var json = await wc.DownloadStringTaskAsync(url);

                            resourceAccountListInfo = JsonConvert.DeserializeObject<ResourceAccountListInfo>(json);
                        }

                        using (WebClient wc = new WebClient()) // call the PM API to get the password for the account id under the related resource
                        {
                            string url = currentURL + "resources/" + passwordmanagerResourceID + "/accounts/" + passwordmanagerAccountID + "/password?AUTHTOKEN=" + pmtoken;
                            var json = await wc.DownloadStringTaskAsync(url);
                            resourceAccountPasswordListInfo = JsonConvert.DeserializeObject<ResourceAccountPasswordInfo>(json);
                        }

                        var shell = PowerShell.Create();
                        var shell2 = PowerShell.Create();
                        var shell3 = PowerShell.Create();

                        //Powercli script to get the hypervisot info
                        string PsCmd = "add-pssnapin VMware.VimAutomation.Core; $vCenterServer = '" + vCenterName + "';$vCenterAdmin = '" + vCenterUsername + "' ;$vCenterPassword = '" + vCenterPassword + "';" + System.Environment.NewLine;



                        PsCmd = PsCmd + "$VIServer = Connect-VIServer -Server $vCenterServer -User $vCenterAdmin -Password $vCenterPassword;" + System.Environment.NewLine;



                        PsCmd = PsCmd + "Get-VMHost " + System.Environment.NewLine;

                        string PsCmd2 = "add-pssnapin VMware.VimAutomation.Core; $vCenterServer = '" + vCenterName + "';$vCenterAdmin = '" + vCenterUsername + "' ;$vCenterPassword = '" + vCenterPassword + "';" + System.Environment.NewLine;



                        PsCmd2 = PsCmd2 + "$VIServer = Connect-VIServer -Server $vCenterServer -User $vCenterAdmin -Password $vCenterPassword;" + System.Environment.NewLine;



                        PsCmd2 = PsCmd2 + " Get-VMHost " + vCenterName + "| Get-VMHostNetworkAdapter -VMKernel" + System.Environment.NewLine;



                        shell.Commands.AddScript(PsCmd);
                        shell2.Commands.AddScript(PsCmd2);
                        dynamic results = shell.Invoke(); // execute the first powercli script
                        dynamic results2 = shell2.Invoke();//execute the second powercli script


                        if (results != null && results.Count > 0 && results[0].BaseObject != null) // the powercli executed successfully
                        {
                            // call the service desk API to update the hypervisor info
                            var builder = new StringBuilder();

                            XmlDocument doc = new XmlDocument();
                            using (var client = new WebClient())
                            {

                                var query = HttpUtility.ParseQueryString(string.Empty);


                              //code goes here

                                string xml = await client.DownloadStringTaskAsync(url.ToString());

                                doc.LoadXml(xml);
                                status = doc.SelectSingleNode("/operation/operationstatus").InnerText;
                                message = doc.SelectSingleNode("/operation/message").InnerText;


                            }

                        else//if the powershell script return zero result..
                        {

                            c.TServer.ScanResult = "Scan return zero result";
                            scan.Description = scan.Description + "<span style='color:red'>" + c.TServer.ScanResult + "</span><br/>";
                            await repository.Save();
                            continue;
                        }
                        if (FQDN == "allscan")
                        {

                           //code goes here
            }
            catch (WebException ex)
            {
                errormessage = "Password manager or manage engine can not be accessed";
                errorstatus = "fail";

            }
            catch (Exception e)
            {
                errormessage = "scan can not be completed. Message" + e.InnerException;
                errorstatus = "fail";

            }
            scan.EndDate = System.DateTime.Now;


                using (MailMessage mail = new MailMessage(from, "*****"))
                {
                    mail.Subject = "scan report generated";
                    //  mail.Body = emailbody;

                    mail.IsBodyHtml = true;
                    System.Text.StringBuilder mailBody = new System.Text.StringBuilder();
                    mailBody.AppendLine("<span style='font-family:Segoe UI'>Hi, <br/>");
                    mailBody.AppendLine(scan.Description);
                    mailBody.AppendLine("<br/><br/><div style='color:#f99406;font-weight:bold'>T scanning Management </div> <br/> <div style='color:#f99406;font-weight:bold'>Best Regards</div></span>");

                    SmtpClient smtp = new SmtpClient();
                    smtp.Host = System.Web.Configuration.WebConfigurationManager.AppSettings["smtpIP"];
                    smtp.EnableSsl = true;
                    mail.Body = mailBody.ToString();

                    smtp.UseDefaultCredentials = false;

                    smtp.Port = Int32.Parse(System.Web.Configuration.WebConfigurationManager.AppSettings["smtpPort"]);
                    S
                    smtp.Send(mail);
                }
            }
            return Json(new { status = errorstatus, message = errormessage }, JsonRequestBehavior.AllowGet);



        }

现在如上面的操作方法所示,它包含许多操作,例如;从数据库检索对象、保存数据库更改、在第三方应用程序上调用 Web 客户端、运行 powercli 脚本等。现在我在 IIS 上部署了该应用程序,并且无论是由用户手动运行还是根据计划运行,它似乎都工作正常时间。但根据我自己的阅读,运行长时间运行的任务(例如上述操作方法)被认为是有风险的,我需要使用不同的方法。那么任何人都可以建议为什么上述方法被认为是有风险的以及我如何改进它? 第二个问题。现在我读到 Quartz.NET 是一种可以遵循的方法,但不确定我是否仍然可以从 Web 浏览器调用 Quartz.NET 方法,并且不确定我是否可以在 Quartz.NET 内调用 webclient、执行 powercli 脚本等?

谢谢

最佳答案

正如有人建议的,Quartz.Net 的替代方案可以是 Hangfire .
最后一个比 Quartz.Net 更容易实现,并且它有一个漂亮的管理仪表板。

Hangfire 为您提供与 Quartz.Net 几乎相同的功能。

您可以在 Owin Startup 中引导它:

var options = new SqlServerStorageOptions
{
    PrepareSchemaIfNecessary = true,
    QueuePollInterval = TimeSpan.FromSeconds(15)
};

GlobalConfiguration.Configuration
    .UseSqlServerStorage("<your connection string here>", options)
    .UseNLogLogProvider()
    .UseActivator(new StructureMapHangfireJobActivator(My.Application.Container));

app.UseHangfireServer();

app.UseHangfireDashboard("/hangfire", new DashboardOptions
{
    AuthorizationFilters = new[] { new BpNetworkSales.Web.Infrastructure.ActionFilters.HangfireDashboardAuthorizationFilter() }
});

您可以使用您喜欢的记录器:

GlobalConfiguration.Configuration.UseNLogLogProvider();

您可以使用您最喜欢的 DI 容器:

GlobalConfiguration.Configuration.UseActivator(new StructureMapHangfireJobActivator(My.Application.Container));

您可以轻松运行重复任务:

RecurringJob.AddOrUpdate("RunSyncDocumentsForStatus", () => My.Application.SyncDocumentsForStatus(My.Application.CompanyCode), "0/3 * * * *");

安排延迟作业:

Hangfire.BackgroundJob.Schedule(() => My.Application.SyncSubmittedDocuments(), TimeSpan.FromSeconds(60));

并使用属性自动重试操作:

[Hangfire.AutomaticRetry(Attempts = 5)]
public static void SyncSubmittedDocuments()
{
    ...
}

或禁用并发执行:

[Hangfire.DisableConcurrentExecution(timeoutInSeconds: 120)]
public static void SyncDocumentsForStatus(string companyCode)
{

}

这只是一个设计精美的软件;而 Quartz.Net 则稍微复杂一些,尤其是当您开始做“花哨”的事情时。

您始终必须记住,您是在 IIS 内运行这些任务,因此当 IIS 挂起或回收时,您的作业将会关闭。

在您上一个问题中Jay Vilalta (对 Quartz.Net 了解很多)告诉你,替代方案是使用 Quartz.Net 作为 Windows 服务。

我想,如果您计划安排在特定时间运行的长期重复性作业,并且希望 100% 确定它们将被执行,那么 Windows 服务是最佳选择你有。

另一个优点是您的 ASP.NET MVC 应用程序将更加流畅,因为您不会在用户执行日常工作时运行其他后台任务。

如果您不熟悉 Windows 服务,我建议您使用 Topshelf其中有 integration对于 Quartz.Net。

关于asp.net - 在 ASP.NET MVC5 中执行长时间运行任务的最佳方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32456064/

相关文章:

asp.net - Visual Studio 2015 Web 应用程序 .NET Core 与 .NET Framework

html - 永久删除带有 web 配置的 HTML 扩展

ASP.NET 应用程序针对 NHibernate 抛出 "unable to find assembly"错误

c# - 如何使用 C# 在 GridView 中启用或禁用文本框

c# - 重定向会避免重复发布吗?

.net - ASP.NET 主题中每页一个 CSS

.net - 什么是 session 和 session 变量?

asp.net - 为什么 EF 无法翻译 Int32.Pars(...?

生成服务器上的 ASP.NET v5

c# - 需要重启IIS才能获取sql连接