tisdag 31 mars 2009

Removing a subdirectory from your application will cost you your session state

Encountered a curious error, with users being logged out from an admin application, when they removed a folder. Turns out the behavior is by design: Removing a folder anywhere in your application, programatically or manually, will cause a full AppDomain recycle, in just the same manner as uploading a DLL file to bin or making a change to web.config does, and that's all there's to it.

Managed to come up with only one work-around, that'll still allow the file change notifier to monitor web.config and bin directory, and it's not very pretty, but it seems to work:
private void SafelyDeleteFolder(string folder, bool recursive)
{
   //FIX disable AppDomain restart when deleting subdirectory
   //This code will turn off monitoring from the root website directory.
   //Monitoring of Bin, App_Themes and other folders will still be operational, so updated DLLs will still auto deploy.

   System.Reflection.PropertyInfo p = typeof(HttpRuntime).GetProperty("FileChangesMonitor", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);

   object o = p.GetValue(null, null);
   System.Reflection.FieldInfo f = o.GetType().GetField("_dirMonSubdirs", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.IgnoreCase);

   object monitor = f.GetValue(o);
   System.Reflection.MethodInfo m = monitor.GetType().GetMethod("StopMonitoring", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
   m.Invoke(monitor, new object[] { });

   System.IO.Directory.Delete(folder, recursive);
}

tisdag 24 mars 2009

Default AJAX Object for calling WebMethods from jQuery

Calling WebMethods from jQuery is a neat way of dealing with AJAX requests, both on the client- and serverside, but the AJAX object expected by jQuery requires a lot of parameters to be set, in order to work properly with .NET WebMethods. Properties that usually remain constant, yet always have to be previded (i.e. a lot of unnecessary duplication of code). To resolve the issue, I have written the following snippet:
Object.prototype.toAjaxObject = function()
{
   if(!this.type) this.type = 'POST';
   if(!this.contentType) this.contentType = 'application/json; charset=utf-8';
   if(!this.dataType) this.dataType = 'json';

   if(!this.url && typeof this.method == 'string')
   {
      this.url = location.href.substring(0, location.href.indexOf('?')) + '/' + this.method.toString();
      this.method = undefined;
   }

   if(typeof this.data != 'string' && typeof JSON =='object')
      if(typeof JSON.stringify == 'function')
         this.data = JSON.stringify(this.data);

   return this;
}
The above code adds an extension method to all JavaScript objects, which populates some properties with default values provided that these values have not already been set. Hence, you can still always override the use of any default value, at will.

As added value, there is a check for whether the data parameter is an object, and the function JSON.stringify is defined. If they are, data will be stringified. That is, if you know you're working in a project where JSON serialization functions will always be at hand, you can pass the data object as a custom JSON object, rather than a valid JSON-parseable string.

Another feature is that the url parameter is not required. Instead, it is possible to pass a method parameter, which will set url to a WebMethod call to that method, assuming the current url is used, and then remove the method parameter from the object. For this to work, it is required that url is not set.

Including the above snippet, it is therefore possible to reduce this piece of code code:
$.ajax({
   type: 'POST',
   url: 'MyPage.aspx/GetUser',
   contentType: 'application/json; charset=utf-8',
   dataType: 'json',
   data: '{"id":'+id+'"}',
   success: myCallback
});
To the following:
$.ajax({
   method: 'GetUser',
   data: {id:id},
   success: myCallback
}.toAjaxObject());
Again, provided that a serializer function called JSON.stringify is present. Otherwise, the data property must still be passed as a string in the manner of the first example.

Update


Looks like jQuery wasn't quite ready for object extension like this. The code works fine, but what Object.prototype does is to extend all Objects with some new functionality. Ran in to trouble as dialog wasn't prepared that the object passed might be extended with a function like this one. Eventually retorted to what feels pretty much like a hack. I would rather see that the jQuery UI team fixed up their components to allow the use of regular JavaScript like this, but for now, the following will do the trick:
$.ajaxCore = $.ajax;
$.ajax = function(opts)
{
   var addDefaults = arguments[0] ? arguments[0] : false;
   if(addDefaults)
   {
      if(!opts.type) opts.type = 'POST';
      if(!opts.contentType) opts.contentType = 'application/json; charset=utf-8';
      if(!opts.dataType) opts.dataType = 'json';

      if(!opts.url && typeof opts.method == 'string')
      {
         opts.url = location.href.substring(0, location.href.indexOf('?')) + '/' + opts.method.toString();
         opts.method = undefined;
      }

      if(typeof opts.data != 'string' && typeof JSON =='object')
         if(typeof JSON.stringify == 'function')
            opts.data = JSON.stringify(opts.data);
   }

   $.ajaxCore(opts);
}
The method call should, then, be modified accordingly:
$.ajax({
   method: 'GetUser',
   data: {id:id},
   success: myCallback
}, true);