I had a site running on a shared hosting server (later moved to a VM) and had the need to run a batch process controlled by C# and processing data to SQL Server. The issue on a shared hosting environment (in most cases) is that you cannot create a scheduled job or upload a console application. I got around this by creating a Batch Job in ASP.NET using Threading.
For long running jobs you can simply PING your site URL every so often to prevent the worker process from shutting down.
There is one gotcha with this approach. You cannot access Httpcontext.current. This should not present an issue. I have many ASP.NET batch jobs running for downloading and processing data.
Here is how this process is setup:
1. Create a web page BatchProcess.aspx. When this page gets called a parameter is passed to control what batch process to start. In my case I call this web page by creating a monitor at
SiteUptime.com. If you only want the job to run at a certain time SiteUptime has blackout periods for the monitor or you can code a time stamp check to only run the job at a certain time.
switch (Job)
{
case "update-blabla1":
UpdateSomeData();
break;
case "update-blabla2":
UpdateSomeData2();
break;
default:
Response.Redirect("~/default.aspx");
break;
}
2. Instantiated an object of the batch process class and start the job
//Create new object and start processing
MySite.Data.ProcessData Batch = new Data.ProcessData();
Batch.StartBatchProcessing();
//The StartBatchProcessing() Creates the new thread
public void StartBatchProcessing()
{
//ProcessMyData is the main method that will process the batch data
Thread newThread = new Thread(this.ProcessMyData);
newThread.Priority = ThreadPriority.Normal;
newThread.Start();
}
3. For long running jobs you will need to PING your site or the worker process may stop if the site is idle for a while. Use the following method to call a URL on the site to keep the working process alive.
//Pass your site URL to the following method to PING it.
//This method can be used to pull HTML for page scraping as welll
public string GetHTML(string url)
{
System.Net.WebClient WC = new System.Net.WebClient();
WC.Credentials = System.Net.CredentialCache.DefaultCredentials;
string HTML;
try
{
System.Text.UTF8Encoding UTF8 = new System.Text.UTF8Encoding();
HTML = UTF8.GetString(WC.DownloadData(url));
}
catch (Exception ex) {throw ex;}
return HTML;
}
The following code will enable you to call a Batch File from ASP.NET / C#.
There are a few steps you must take before this will work.
1. Create a new Login ID on your machine
2. Change the site Application Pool to run under this new ID
3. Give the ID Read/Write Rights to the folder that contains the .bat file
Code Snippet:
using System.IO;
using System.Diagnostics;
HttpContext CTX = HttpContext.Current;
var BatchFile = "YourBatchFile.bat";
var Path = CTX.Server.MapPath("~/YourFolderHere");
ProcessStartInfo processInfo = new ProcessStartInfo();
processInfo.WorkingDirectory = Path;
processInfo.FileName = BatchFile;
processInfo.UseShellExecute = true;
Process batchProcess = new Process();
batchProcess.StartInfo = processInfo; batchProcess.Start();
To prevent Invalid view state or 64Bit invalid viewstate issues, and to improve page performance, I have loaded view state in a flat file at times.
Follow These Steps:
1. Add the Class File to your project
3. Have your page Inherit from the View State class (attached .cs file).
public partial class ViewStateInFlatFile : MyClass.PersistViewStateToFile //Inhert the class that handles view state
4. Setup a scheduled task to delete old view state files. I delete files that are one day old.
using System.IO;
using System.IO.Compression;
namespace MyClass
{
public class PersistViewStateToFile : System.Web.UI.Page
{
public string ViewStatePath = string.Empty;
public PersistViewStateToFile()
{
ViewStatePath = Server.MapPath(@"~\ViewState");
}
protected override void SavePageStateToPersistenceMedium(object state)
{
LosFormatter los = new LosFormatter();
StringWriter sw = new StringWriter();
los.Serialize(sw, state);
StreamWriter w = File.CreateText(ViewStateFilePath);
w.Write(sw.ToString());
w.Close();
sw.Close();
}
protected override object LoadPageStateFromPersistenceMedium()
{
string filepath = ViewStateFilePath;
// determine the file to access
if (!File.Exists(filepath))
return null;
else
{
// open the file
StreamReader sr = File.OpenText(filepath);
string viewStateString = sr.ReadToEnd();
sr.Close();
// deserialize the string
LosFormatter los = new LosFormatter();
return los.Deserialize(viewStateString);
}
}
public string ViewStateFilePath
{
get
{
if (Session["viewstateFilPath"] == null)
{
var fileName = Session.SessionID + "-" + Path.GetFileNameWithoutExtension(Request.Path).Replace("/", "-") + ".ViewState";
var filepath = Path.Combine(ViewStatePath, fileName);
Session["viewstateFilPath"] = filepath;
}
return Session["viewstateFilPath"].ToString();
}
}
public string GetValue(string uniqueId)
{
return System.Web.HttpContext.Current.Request.Form[uniqueId];
}
///
/// Replace this with BAT Files to delete these View State files.
///
private void RemoveFilesfromServer()
{
try
{
//string folderName = Path.Combine(Request.PhysicalApplicationPath, "PersistedViewState");
string folderName = ViewStatePath;
DirectoryInfo _Directory = new DirectoryInfo(folderName);
FileInfo[] files = _Directory.GetFiles();
DateTime threshold = DateTime.Now.AddDays(-3);
foreach (FileInfo file in files)
{
if (file.CreationTime <= threshold)
file.Delete();
}
}
catch (Exception ex)
{
throw new ApplicationException("Removing Files from Server");
}
}
///
/// A GUID is created to store the file names
///
private string GenerateGUID()
{
return System.Guid.NewGuid().ToString("");
}
}
}
C# source for View State save to flat file and save to session:
PersistViewStateToFile.cs (3.45 kb)
PersistViewStateSession.cs (1.23 kb)