Is there scope in the API to add our own events to the backend scheduling, for instance the publish on this date routinue?
I have an admin task that needs performing daily, and don't really want to have to expose a public url to use the scheduler available in the umbracoSettings.config file.
Hi, Mike. As far as I investigated it some time ago there's no such inbuilt functionality. I used a trick that probably could help - creating a document type and binding something to its publishing event then you just create a document of this type and set its 'publish at' property.
did some digging myself, and couldn't find anything, just looking now at basing something on the trick of using the a sliding cache for automated scheduling on an interval http://www.codeproject.com/KB/aspnet/ASPNETService.aspx
or maybe cachedependency on a file to monitor for a change...
By the way, the cache trick like referenced above is actually used in umbraco itself. It's possible to schedule some code by putting a fake cache item with some expiration time and attach code to its CacheItemRemoved event so that it will be executed later after timespan expiration. As far as I know from the source code it's the way that Umbraco schedules log truncation.
Thanks Richard, but I'm trying to avoid any use of a public url (just in case it's exposed)
Rodion, good to know that I'm using a method already utilised in the core... only down side I see to it will be that cache only going to be running if the app pool is up, and as we're in a clustered cloud host app pool uptime is very stringently monitored... might require a separate ping to keep the site alive! although calling a dummy page on cache expiration might also keep the app pool alive.
Unfortunately, you cannot avoid the dependency on application pool running out of the architecture of asp.net web applications. Another possible pattern to use for a custom implementation of a scheduler is the one that e.g. DotNetNuke uses. It has a comon custom HttpModule that catches every BeginRequest events then checks scheduled tasks and executes those that are scheduled for execution to that time. There's another drawback in it since execution depends on existance of an incoming http request, but if a task is not strictly sensitive to the exact time of execution this pattern is also has a chance to be used.
requires custom Global.ascx and deletion of app_global.aspx.dll and the addition of scheduleCacheReset.aspx [add to the ubracoreservedurls] (although this could just as well be real page in umbraco, or a urlredirected url to some content - just needs a unique url for the cache reset)
public class Global : umbraco.Global
{
private const string DummyCacheItemKey = "GagaGuguGigi";
protected static string DummyUrl;
protected override void Application_Start(object sender, EventArgs e)
{
base.Application_Start(sender, e);
// Insert your startup logic here
RegisterCacheEntry();
}
protected override void Application_BeginRequest(Object sender, EventArgs e)
{
HttpApplication app = (HttpApplication)sender;
HttpContext context = app.Context;
if (DummyUrl == null)
{
// Attempt to peform first request initialization
DummyUrl = FirstRequestInitialisation.Initialise(context);
}
// If the dummy page is hit, then it means we want to add another item in cache
if (HttpContext.Current.Request.Url.ToString() == DummyUrl)
{
// Add the item in cache and when succesful, do the work.
RegisterCacheEntry();
}
}
class FirstRequestInitialisation
{
private static string host = null;
private static Object s_lock = new Object();
// Initialise only on the first request
public static string Initialise(HttpContext context)
{
if (string.IsNullOrEmpty(host))
{
lock (s_lock)
{
if (string.IsNullOrEmpty(host))
{
Uri uri = HttpContext.Current.Request.Url;
//host = uri.Scheme + Uri.SchemeDelimiter + uri.Host + ":" + uri.Port;
host = uri.Scheme + Uri.SchemeDelimiter + uri.Host + "/scheduleCacheReset.aspx";
Log.Add(LogTypes.Custom, -1, "FirstInitialise" + host);
}
}
}
return host;
}
}
///
/// Register a cache entry which expires in 1 minute and gives us a callback.
///
///
private void RegisterCacheEntry()
{
// Prevent duplicate key addition
if (null != HttpContext.Current.Cache[DummyCacheItemKey]) return;
Log.Add(LogTypes.Custom, -1, "Scheduler Cache Item reset");
HttpContext.Current.Cache.Add(DummyCacheItemKey, "Test", null, DateTime.MaxValue, TimeSpan.FromMinutes(5), CacheItemPriority.NotRemovable, new CacheItemRemovedCallback(CacheItemRemovedCallback));
}
///
/// Callback method which gets invoked whenever the cache entry expires.
/// We can do our "service" works here.
///
///
///
///
public void CacheItemRemovedCallback(string key,object value, CacheItemRemovedReason reason)
{
// Do the service works
DoWork();
// We need to register another cache item which will expire again in one
// minute. However, as this callback occurs without any HttpContext, we do not
// have access to HttpContext and thus cannot access the Cache object. The
// only way we can access HttpContext is when a request is being processed which
// means a webpage is hit. So, we need to simulate a web page hit and then
// add the cache item.
HitPage();
}
///
/// Hits a local webpage in order to add another expiring item in cache
///
private void HitPage()
{
System.Net.WebClient client = new System.Net.WebClient();
client.DownloadData(DummyUrl);
}
///
/// Asynchronously do the 'service' works
///
private void DoWork()
{
//Log.Add(LogTypes.Custom, -1, "Running as: " + WindowsIdentity.GetCurrent().Name);
//DoSomeFileWritingStuff();
//DoSomeDatabaseOperation();
//DoSomeEmailSendStuff();
//DoSomeMSMQStuff();
//ExecuteQueuedJobs();
}
}
Updated to remove the need for that dummy url.... just now looks for a guid in the querystring. (could make it more secure by resetting the guid on each application recycle)
public class Global : umbraco.Global
{
private const string DummyCacheItemKey = "E4161A68-11D9-11E1-8852-2D874824019B";
protected static string DummyUrl;
protected override void Application_Start(object sender, EventArgs e)
{
base.Application_Start(sender, e);
// Insert your startup logic here
RegisterCacheEntry();
}
protected override void Application_BeginRequest(Object sender, EventArgs e)
{
base.Application_BeginRequest(sender, e);
HttpApplication app = (HttpApplication)sender;
HttpContext context = app.Context;
if (DummyUrl == null)
{
// Attempt to peform first request initialization
DummyUrl = FirstRequestInitialisation.Initialise(context);
}
// If the dummy page (one with a ?guid=XXX) is hit, then it means we want to add another item in cache
try
{
if (HttpContext.Current.Request.QueryString["guid"].ToString() == DummyCacheItemKey)
{
// Add the item in cache and when succesful, do the work.
RegisterCacheEntry();
}
}
catch { }
}
class FirstRequestInitialisation
{
private static string host = null;
private static Object s_lock = new Object();
// Initialise only on the first request
public static string Initialise(HttpContext context)
{
if (string.IsNullOrEmpty(host))
{
lock (s_lock)
{
if (string.IsNullOrEmpty(host))
{
Uri uri = HttpContext.Current.Request.Url;
host = uri.Scheme + Uri.SchemeDelimiter + uri.Host + "/?guid=" + DummyCacheItemKey;
}
}
}
return host;
}
}
///
/// Register a cache entry which expires in 1 minute and gives us a callback.
///
///
private void RegisterCacheEntry()
{
// Prevent duplicate key addition
if (null != HttpContext.Current.Cache[DummyCacheItemKey]) return;
Log.Add(LogTypes.Custom, -1, "Scheduler Cache Item reset");
HttpContext.Current.Cache.Add(DummyCacheItemKey, "Test", null, DateTime.MaxValue, TimeSpan.FromMinutes(5), CacheItemPriority.NotRemovable, new CacheItemRemovedCallback(CacheItemRemovedCallback));
}
///
/// Callback method which gets invoked whenever the cache entry expires.
/// We can do our "service" works here.
///
///
///
///
public void CacheItemRemovedCallback(string key,object value, CacheItemRemovedReason reason)
{
// Do the service works
DoWork();
// We need to register another cache item which will expire again in one
// minute. However, as this callback occurs without any HttpContext, we do not
// have access to HttpContext and thus cannot access the Cache object. The
// only way we can access HttpContext is when a request is being processed which
// means a webpage is hit. So, we need to simulate a web page hit and then
// add the cache item.
HitPage();
}
///
/// Hits a local webpage in order to add another expiring item in cache
///
private void HitPage()
{
System.Net.WebClient client = new System.Net.WebClient();
client.DownloadData(DummyUrl);
}
///
/// Asynchronously do the 'service' works
///
private void DoWork()
{
//Log.Add(LogTypes.Custom, -1, "Running as: " + WindowsIdentity.GetCurrent().Name);
//DoSomeFileWritingStuff();
//DoSomeDatabaseOperation();
//DoSomeEmailSendStuff();
//DoSomeMSMQStuff();
//ExecuteQueuedJobs();
}
}
scheduled admin task..
Is there scope in the API to add our own events to the backend scheduling, for instance the publish on this date routinue?
I have an admin task that needs performing daily, and don't really want to have to expose a public url to use the scheduler available in the umbracoSettings.config file.
Hi, Mike. As far as I investigated it some time ago there's no such inbuilt functionality. I used a trick that probably could help - creating a document type and binding something to its publishing event then you just create a document of this type and set its 'publish at' property.
did some digging myself, and couldn't find anything, just looking now at basing something on the trick of using the a sliding cache for automated scheduling on an interval http://www.codeproject.com/KB/aspnet/ASPNETService.aspx
or maybe cachedependency on a file to monitor for a change...
You can use my taskschedule package http://our.umbraco.org/projects/developer-tools/taskscheduler then the public url will be stored inj the database instead of config file.
Cheers,
Richard
By the way, the cache trick like referenced above is actually used in umbraco itself. It's possible to schedule some code by putting a fake cache item with some expiration time and attach code to its CacheItemRemoved event so that it will be executed later after timespan expiration. As far as I know from the source code it's the way that Umbraco schedules log truncation.
Thanks Richard, but I'm trying to avoid any use of a public url (just in case it's exposed)
Rodion, good to know that I'm using a method already utilised in the core... only down side I see to it will be that cache only going to be running if the app pool is up, and as we're in a clustered cloud host app pool uptime is very stringently monitored... might require a separate ping to keep the site alive! although calling a dummy page on cache expiration might also keep the app pool alive.
Unfortunately, you cannot avoid the dependency on application pool running out of the architecture of asp.net web applications. Another possible pattern to use for a custom implementation of a scheduler is the one that e.g. DotNetNuke uses. It has a comon custom HttpModule that catches every BeginRequest events then checks scheduled tasks and executes those that are scheduled for execution to that time. There's another drawback in it since execution depends on existance of an incoming http request, but if a task is not strictly sensitive to the exact time of execution this pattern is also has a chance to be used.
Here's my take...
requires custom Global.ascx and deletion of app_global.aspx.dll and the addition of scheduleCacheReset.aspx [add to the ubracoreservedurls] (although this could just as well be real page in umbraco, or a urlredirected url to some content - just needs a unique url for the cache reset)
Updated to remove the need for that dummy url.... just now looks for a guid in the querystring. (could make it more secure by resetting the guid on each application recycle)
is working on a reply...