在asp.net mvc5中执行长时间运行任务的最佳方法

时间:2015-09-08 11:01:29

标签: 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方法&amp;不确定我是否可以在Quartz.NET中调用webclient,执行powercli脚本等?

由于

1 个答案:

答案 0 :(得分:4)

有人建议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获得Quartz.Net的integration