Reputation: 1
I am working on an asp.net mvc5 web application + Entity framework 6.0 deployed inside IIS 7.0. currently i have a NetworkScanning server which I implemented as a normal action method , that can be started in 2 ways :-
1.based on a schedule defined inside the 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.or manually calling the action method from the user browser.
now the action method looks as follow (i removed a lot of code as the idea is to give a hint on what i am doing inside the action method):-
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);
}
now as shown on the above action method it contain many operations such as ; retrieve objects from DB , save DB changes, call web clients on third party applications, run powercli scripts , etc.. now i deployed the application on IIS and seems it is working fine, either when it run manually by users or based on the schedule time. but from my own reading is that running long running tasks such as the above action method is considered risky, and i need to use different approach . so can anyone adivce why the above approach is considered risky and how i can improve it ? second question. now i read that Quartz.NET is an approach to follow, but not sure if i can still call Quartz.NET method from a web browser & not sure if i can call webclient, execute powercli scripts, etc inside Quartz.NET ?
Thanks
Upvotes: 4
Views: 5089
Reputation: 35597
As someone suggested the alternative to Quartz.Net can be Hangfire.
This last one is much simpler to implement than Quartz.Net and it's got a beautiful admin dashboard.
Hangfire offers you pretty much the same functionalities of Quartz.Net.
You can bootstrap it in the 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() }
});
You can use your preferred logger:
GlobalConfiguration.Configuration.UseNLogLogProvider();
You can use your favourite DI container:
GlobalConfiguration.Configuration.UseActivator(new StructureMapHangfireJobActivator(My.Application.Container));
and you can easily run recurring tasks:
RecurringJob.AddOrUpdate("RunSyncDocumentsForStatus", () => My.Application.SyncDocumentsForStatus(My.Application.CompanyCode), "0/3 * * * *");
schedule delayed job:
Hangfire.BackgroundJob.Schedule(() => My.Application.SyncSubmittedDocuments(), TimeSpan.FromSeconds(60));
and use attributes to automatic retry the operation:
[Hangfire.AutomaticRetry(Attempts = 5)]
public static void SyncSubmittedDocuments()
{
...
}
or disable concurrent execution:
[Hangfire.DisableConcurrentExecution(timeoutInSeconds: 120)]
public static void SyncDocumentsForStatus(string companyCode)
{
}
It's just a beautifully designed piece of software; whereas Quartz.Net is a little bit more complicated, especially if you're starting to do "fancy" stuff.
You always have to remember that you're running these tasks inside IIS so when IIS is suspended or recycled your jobs will shut down.
In your previous question Jay Vilalta (who knows quite a lot about Quartz.Net) told you that the alternative is to use Quartz.Net as a Windows Service.
I guess if you're planning to schedule long, recurring jobs which you want to run at a specific time and you want to be 100% sure they're going to be executed, well, the Windows Service is the best option you have.
The other advantage is your ASP.NET MVC application will be smoother as you won't have other background tasks which will be running while the user is doing is daily job.
If you are not familiar with Windows Services I would suggest to go for Topshelf which has got an integration for Quartz.Net.
Upvotes: 5