A bit about the stuff I've done


Thursday, 28 November 2013

Viewing print styles with firebug

After much searching I couldn't find a way to reliably view the css print styles in the browser and debug them using firebug. So I decided to make my own solution. Save the following link as a bookmark called "Toggle Print Styles" and behold the magic. This link has only been tested in firefox but it ought to be cross-browser. javascript:%20(function(){%20%20%20function%20ShowPrintStyles(Style)%20%20%20{%20%20%20%20%20var%20$Style=$(Style);%20%20%20%20%20$Style.text($Style.text().replace('@media%20screen',%20'@media%20xxscreen').replace('@media%20print',%20'@media%20screen'));%20%20%20%20%20$Style.attr('ShowingPrintStyles',%20true);%20%20%20%20%20console.log(Style);%20%20%20}%20%20%20ShowHide%20=%20window.ShowPrintStyles;%20%20%20if%20(ShowHide)%20%20%20{%20%20%20%20%20window.ShowPrintStyles=false;%20%20%20%20%20$('style[OrigSrc]').each(function(id,%20item){%20%20%20%20%20%20%20var%20$Link%20=%20$('<link>').attr({rel:'stylesheet',%20href:$(item).attr('OrigSrc')});%20%20%20%20%20%20%20$(item).replaceWith($Link);%20%20%20%20%20});%20%20%20}%20else%20{%20%20%20%20%20window.ShowPrintStyles=true;%20%20%20%20%20$('link').each(function(id,%20item){%20%20%20%20%20%20%20var%20$Style%20=%20$('<style>').attr({OrigSrc:$(item).attr('href')}).load($(item).attr('href'),%20null,%20function(){%20%20%20%20%20%20%20%20%20ShowPrintStyles($Style[0]);%20%20%20%20%20%20%20%20%20$(item).replaceWith($Style);%20%20%20%20%20%20%20});%20%20%20%20%20});%20%20%20}%20})(); Let's take a look at that link in detail. It is actually a javascript link. I had some issues with a multi-statement javascript: url in firefox so I wrapped the whole thing in a closure so the link is actually a single statement. javascript: (function(){ function ShowPrintStyles(Style) { var $Style=$(Style); $Style.text($Style.text().replace('@media screen', '@media xxscreen').replace('@media print', '@media screen')); $Style.attr('ShowingPrintStyles', true); console.log(Style); } ShowHide = window.ShowPrintStyles; if (ShowHide) { window.ShowPrintStyles=false; $('style[OrigSrc]').each(function(id, item){ var $Link = $('<link>').attr({rel:'stylesheet', href:$(item).attr('OrigSrc')}); $(item).replaceWith($Link); }); } else { window.ShowPrintStyles=true; $('link').each(function(id, item){ var $Style = $('<style>').attr({OrigSrc:$(item).attr('href')}) .load($(item).attr('href'), null, function(){ ShowPrintStyles($Style[0]); $(item).replaceWith($Style); }); }); } })(); The ShowPrintStyles function takes an inline style element, replaces any "@media screen" sections with "@media xxscreen", then does likewise for the print sections to screen. This obviously has the effect of disabling any screen-only styles, while enabling the print-only ones (Assuming you are viewing on a screen that is - if not then adjust the javascript to suit your device) A property is set on the global window object to indicate whether we are showing or hiding the print styles. If we are showing then any link elements are replaced with an inline style so that we can modify the css content. If we are hiding then the inline styles are replaced with their original link elements.

Wednesday, 24 July 2013

Private members in javascript classes

Whoever told you this couldn't be done was lying.

By utilising closures we can create a class that holds its own private members which cannot be accessed outside of that class - even by adding a new function onto the class

Consider the following javascript function

function Outer() { var PrivateVar='PrivateValue'; function Inner() { PrivateVar='Some Value'; } }

This is known as a closure.
You can read more about them here: http://stackoverflow.com/questions/111102/how-do-javascript-closures-work

Put simply the inner function has access to the variables in the outer function.
These variables are "live" - that is they are not fixed when the inner function was created.

Now consider that the Outer and Inner functions are, in fact, constructors

function MyClassWithPrivateVars(params) { var PrivateVar; function MyClass(params) { PrivateVar='Initial Value'; } return new MyClass(params); } var Instance = new MyClassWithPrivateVars('whatever');

When you call the outer constructor you are actually calling the inner constructor.
The inner constructor has access to the vars within the outer constructor as before.

Let's add a method to the inner constructor.
This new method of course has access to the variables in the outer constructor.

In this instance the method is a "property".

function MyClassWithPrivateVars(params) { var PrivateVar; function MyClass(params) { PrivateVar='Initial Value'; this.PublicProperty = function(value) { if (typeof(value)!=="undefined") { PrivateVar=value; } return PrivateVar; } } return new MyClass(); }

Now if we create an instance of MyClassWithPrivateVars and invoke the property we'll see that it gets the value as expected.

var MyInstance=new MyClassWithPrivateVars(); console.log(MyInstance.PublicProperty());

Outputs "Initial Value" because the private variable was set to that value in the constructor.

MyInstance.PublicProperty(10); console.log(MyInstance.PublicProperty());

This time we get 10

So is the variable really private?

Well - let's try and access it directly:

console.log(MyInstance.PrivateVar);

This should output undefined as PrivateVar is not a member of the inner class.

Likewise

console.log(MyClassWithPrivateVars.PrivateVar);

is also undefined because PrivateVar is a member of the instance of the outer class - not of the constructor itself.

Friday, 31 May 2013

Type 'System.Collections.Generic.IDictionary`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Object, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]' is not supported for deserialization of an array.

So I was getting this error when calling a page method from jquery. Google turned up quite a few variations but not the exact string, and the suggestions associated with those variations were not relevant. It turned out that in my case the data packet I was sending in the jquery ajax call was wrong. I had code like this:

MyParam = []; ... $.ajax({ type: 'POST', url: document.location.pathname + '/DoSomething', data: JSON.stringify(MyParam), contentType: "application/json; charset=utf-8", dataType: "json", complete: function (a, b, c, d) { console.log(a, b, c, d); } });

and my page method looked like this:

[WebMethod()] public static void DoSomething(List<s> B) { B.ToString(); }

fairly simple but there are 2 things wrong with this. The first problem was the data - I hadn't named the paramater

so the fixed javascript code looks like this:

MyParam = []; ... $.ajax({ type: 'POST', url: document.location.pathname + '/DoSomething', data: JSON.stringify({B:MyParam}), contentType: "application/json; charset=utf-8", dataType: "json", complete: function (a, b, c, d) { console.log(a, b, c, d); } });

The second problem was it turns out the .NET javascript seraliser doesn't like List<t> so a quick change to the C# fixed that.

[WebMethod()] public static void DoSomething(S[] B) { B.ToString(); }

So in summary - make sure you pass an object with named parameters as the data argument - not simple the content of the only parameter!

Friday, 4 January 2013

Staging long loops in Javascript

So I've encounted this issue a number of times where you need to do something, probably in a loop, which is going to take some time and the browser will "hang" will the loop takes place.
Sometimes you may even get a message pop up asking if you want to terminate a stuck script.

Not only does this make for a bad user experience but it can cause scripts to terminate without warning.

 The solution?
Plenty of posts on forum sites such as StackOverflow will tell you to use setTimeout.
It can get kind of messy though - so I thought I'd write a wrapper function to take away the pain, and have something that *looks* mostly like the familiar simple loop.
Now obviously there is a performance hit by adding all these extra "unnecessary" delays. (approx. 30% for a tight loop, less for a complex one) BUT if the browser pops up a box and waits for the user to respond, this will add much more delay to the total execution time!
so - heres the code:
function StagedLoop(IterationsBetweenPauses, Delay, AtLeastOneDelay, Test, Run, Complete) { /// <summary>Stages a loop, inserting timer pauses every n Iterations ///Warning - this method is asyncronus and will exit before looping is completed! /// put code to be run only after looping has finished into the Complete inline function parameter ///</summary> ///<param name="Test">A method to run which will perform the "test" part of the loop to see if looping should continue. This test will be run at the start of the loop (i.e. the loop may run 0 or more times)</param> ///<param name="Run">A method which will be run on every iteration of the loop - don't forget to increment any counters!</param> ///<param name="IterationsBetweenPauses">How many loops to run before using a timer pause - suggestion: use a number between 10 and 10000, but if iteration counters will appear anywhere on screen don't use a round number (i.e. 1000)</param> ///<param name="Delay">Milliseconds to set the timer pause for between stages. Recommended value: 1 - this gives the browser ample time to do "stuff" but avoids unnecassary delay. N.B. the actual delay could be substantially longer than the one specified here due to the way browser events are handled</param> ///<param name="Complete">Function to run when the loop has exited</param> if (IterationsBetweenPauses.IterationsBetweenPauses) { Run = IterationsBetweenPauses.Run; Delay = IterationsBetweenPauses.Delay; Complete = IterationsBetweenPauses.Complete; AtLeastOneDelay = IterationsBetweenPauses.AtLeastOneDelay; Test = IterationsBetweenPauses.Test; IterationsBetweenPauses = IterationsBetweenPauses.IterationsBetweenPauses; } if (AtLeastOneDelay) { setTimeout(StagedLoop, Delay, { Test: Test, Run: Run, IterationsBetweenPauses: IterationsBetweenPauses, Delay: Delay, Complete: Complete, AtLeastOneDelay: false }); return; } var RemainingIterations = IterationsBetweenPauses; while (RemainingIterations > 0) { if (Test && !Test()) { Complete(); return; } Run(); RemainingIterations--; } setTimeout(StagedLoop, Delay, { Test: Test, Run: Run, IterationsBetweenPauses: IterationsBetweenPauses, Delay: Delay, AtLeastOneDelay: AtLeastOneDelay, Complete: Complete }); } And here is an example usage: var I = 0; StagedLoop(4567, 1, false,function () { return I < 10000; }, function () { DoSomethingWith(I); I++; //don't forget to increment! },function(){ //run the next bit of code here }); //N.B. code places here will be run *before* the loop exists (possibly before it runs even a single iteration) //Don't put any code here! This would replace a simple for loop such as this one: for (var I=0;I<10000;I++) { DoSomethingWith(I); } //run the next bit of code here
ok - so it's not quite as clean, but it gets the job done with minimal fuss! Complete test harness below showing the difference in times taken <div id="here">Please Wait... - there is a good chance the browser will "hang" at this point</div> <script type="text/javascript"> setTimeout(RunTest, 100); function RunTest() { var Iterations = 100000; var I; var Start = new Date().getTime(); for (I = 0; I < Iterations; I++) { document.getElementById('here').innerHTML = I; } var End = new Date().getTime(); var Normal = (End - Start); I = 0; Start = new Date().getTime(); StagedLoop(4567, 1, false,function () { return I < Iterations; }, function () { document.getElementById('here').innerHTML = I; I++; }, function () { document.getElementById('here').innerHTML = 'Please wait...'; End = new Date().getTime(); var Staged = (End - Start); console.log('Duration for staged loop: ' + Staged); console.log('Duration for "normal" loop: ' + Normal); document.getElementById('here').innerHTML = 'Duration for "normal" loop: ' + Normal + '<br/>' + 'Duration for staged loop: ' + Staged + '<br/>' + '% performance loss: ' + Math.round(((100.0 * (Staged - Normal)) / Normal)) + '%'; console.log(Staged - Normal); console.log(100.0 * (Staged - Normal)); console.log(((100.0 * (Staged - Normal)) / Normal)); //end inline function }); //N.B. code places here will be run *before* the loop exits (possibly before it runs even a single iteration) //Don't put any code here! } function StagedLoop(IterationsBetweenPauses, Delay, AtLeastOneDelay, Test, Run, Complete) {/// <summary>Stages a loop, inserting timer pauses every n Iterations ///Warning - this method is asyncronus and will exit before looping is completed! /// put code to be run only after looping has finished into the Complete inline function parameter ///</summary> ///<param name="Test">A method to run which will perform the "test" part of the loop to see if looping should continue. This test will be run at the start of the loop (i.e. the loop may run 0 or more times)</param> ///<param name="Run">A method which will be run on every iteration of the loop - don't forget to increment any counters!</param> ///<param name="IterationsBetweenPauses">How many loops to run before using a timer pause - suggestion: use a number between 10 and 10000, but if iteration counters will appear anywhere on screen don't use a round number (i.e. 1000)</param> ///<param name="Delay">Milliseconds to set the timer pause for between stages. Recommended value: 1 - this gives the browser ample time to do "stuff" but avoids unnecassary delay. N.B. the actual delay could be substantially longer than the one specified here due to the way browser events are handled</param> ///<param name="Complete">Function to run when the loop has exited</param> if (IterationsBetweenPauses.IterationsBetweenPauses) { Run = IterationsBetweenPauses.Run; Delay = IterationsBetweenPauses.Delay; Complete = IterationsBetweenPauses.Complete; AtLeastOneDelay = IterationsBetweenPauses.AtLeastOneDelay; Test = IterationsBetweenPauses.Test; IterationsBetweenPauses = IterationsBetweenPauses.IterationsBetweenPauses; } if (AtLeastOneDelay) { setTimeout(StagedLoop, Delay, { Test: Test, Run: Run, IterationsBetweenPauses: IterationsBetweenPauses, Delay: Delay, Complete: Complete, AtLeastOneDelay: false }); return; } var RemainingIterations = IterationsBetweenPauses; while (RemainingIterations > 0) { if (Test && !Test()) { Complete(); return; } Run(); RemainingIterations--; } setTimeout(StagedLoop, Delay, { Test: Test, Run: Run, IterationsBetweenPauses: IterationsBetweenPauses, Delay: Delay, AtLeastOneDelay: AtLeastOneDelay, Complete: Complete }); } </script>

Monday, 22 October 2012

Solving long JSON return from a webservice

I was being plagued by this error intermittently:
Error during serialization or deserialization using the JSON JavaScriptSerializer. The length of the string exceeds the value set on the maxJsonLength property

Some Googling led me to realise that there is indeed a limit, and this limit is quite low.
Most of the posts explained how to increase the limit but also explained why this was a bad idea and that you should instead reduce your JSON size.

I agree with this, better to return what you can to the client and tell them to "try again" for whatever is left.
But this poses a problem - how do you know how much data you can return at once?
Google drew a blank on this one.

This little function here will do the trick:

using System.Configuration; using System.Web.Configuration; public int GetMaxJSONLength() { Configuration cConfig = WebConfigurationManager.OpenWebConfiguration(null); return ((ScriptingWebServicesSectionGroup) cConfig. SectionGroups["system.web.extensions"]. SectionGroups["scripting"]. SectionGroups["webServices"] ).JsonSerialization.MaxJsonLength; }
This can be executed from anywhere, it doesn't have to be part of the web project itself.

Something else to be aware of - any strings in your return value will be encoded.
That means characters like < and > will become \u003c and \u003e - 6 times longer than they were previously.
So if you have a string containing html this can make a substantial difference to the length of the string.

To account for this make sure you use the length of the encoded string, not the original.

Tuesday, 9 October 2012

The woes of inline functions

OK; so I like inline fucntions really - especially in javascript. But now I am realising their evil intent after I spent a not insubstantial amount of time tracing a bug.
here is the scenario:
A simple method with tbh not a whole lot in it.
In fact the entire method is taken by a single if/else block. Regardless of the outcome of both if blocks it is necassary to call a method afterwards.
So obviously that call goes outside the if.
Normally that would be fine.
But in my case the else block contained an inline function used as a callback.
A callback that will occur some undetermined (but very small) amount of time after the if block has exited.
Of course I needed to wait until this callback had been executed before calling the final method.
function DoSomething() { if (condition) { DoOneThing(); } else { AsyncronuslyDoAnother(function (result){ DoSomethingWithTheResultWhenTheAsyncCallCompletes(); }); } DoFinalThing(); } Now the simple solution in this instance was to move the call to DoFinalThing() to be inside the if block, and also inside the anonymous callback function.
function DoSomething2() { if (condition) { DoOneThing(); DoFinalThing(); } else { AsyncronuslyDoAnother(function (result){ DoSomethingWithTheResultWhenTheAsyncCallCompletes(); DoFinalThing(); }); } } However in my case the problem only arose in the first place because I simply didn't notice the inline function when I added the call to DoFinalThing() and assumed that it was all just part of the else logic.
So additionally I removed the inline function and henceforth declare all such things to be evil!
(ok well not quite, but at least I am more wary now!)
function AsyncCallback(result) { DoSomethingWithTheResultWhenTheAsyncCallCompletes(); DoFinalThing(); } function DoSomething3() { if (condition) { DoOneThing(); DoFinalThing(); } else { AsyncronuslyDoAnother(AsyncCallback); //N.B. no parens on AsyncCallback - we are passing a reference to the method not invoking it } }

Friday, 14 September 2012

Ajaxable Web Control in a Class Library

So I recently went on a course to get my AJAX knowledge up to scratch.
The course was fine and taught me quite a bit that I didn't know but the most important thing for me was missing - how do you do all this in a class library?

Basically I want the ability to have some control which has both server and client side components, for these 2 parts to talk to eachother with ajax, and I want to be able to wrap the whole thing up into a DLL file that can be dropped into any web application and with no user modifications required.

The instructor gave me this link which was helpful but didn't go the whole way.
I knew there had to be a better, more complete way, and so I set to work.

The result is what you see below.

Add the C# code in the block below to a class library and inherit from it for your server control.
You'll need a WebService and you'll have to provide that as the type parameter to the base class.
And that's about it!
The class does require some tweaking to the web.config file - but it will do that for you the first time you run it.

Here you can find the compiled DLL: http://dev.dj-djl.com/AjaxableServerControlWithWebService.zip
And a Template project for Visual Studio 2012: dev.dj-djl.com/AjaxableServerControlWithWebServiceTemplate.zip
The template control contains a single web-exposed method which is triggered when the button is clicked and the result is given to a simple alert box.

The code below contains some code taken from this Code Project post, which was modified by me.
The rest of the code is written by me. It is all licecned under an LGPL3.0 licence.   /* LGPL3.0 Classes enabling embedding of a fully AJAXable ASPX Server Control into a DLL for reuse or redistribution. Most of the code below is LGPL3.0 - Copyright (C) 2012 Lee Smith http://dev.dj-djl.com http://blog.dj-djl.com Contains some LGPL3.0 code As indicated - (C) 2007 James Ashley http://www.codeproject.com/Articles/22384/ASP-NET-AJAX-Controls-and-Extenders#http_handler LGPL3.0: This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not please see <http://www.gnu.org/licenses/>. */ using System; using System.Collections.Generic; using System.Reflection; using System.Text; using System.Web; using System.Web.Services; using System.Web.SessionState; using System.Web.UI; namespace DJL { internal static class Utils { public static string ToDelimitedstring<T>(this List<T> lst, string Delimiter = ",") { System.Text.StringBuilder sbReturn = new StringBuilder(); Boolean flgFirst = true; foreach (T Item in lst) { if (!flgFirst) { sbReturn.Append(Delimiter); } sbReturn.Append(Item.ToString()); flgFirst = false; } return sbReturn.ToString(); } public static string CompressAndBase64(string Value) { System.IO.MemoryStream mst = new System.IO.MemoryStream(); System.IO.Compression.GZipStream gz = new System.IO.Compression.GZipStream(mst, System.IO.Compression.CompressionLevel.Optimal, true); byte[] bytValue = System.Text.Encoding.UTF8.GetBytes(Value); gz.Write(bytValue,0 , bytValue.Length); gz.Flush(); gz.Close(); return Convert.ToBase64String(mst.ToArray()).Replace("+", "_").Replace("/", "-"); } public static string DeBase64AndDecompress(string Base64Value) { //return System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(Base64Value)); byte[] bytValue = Convert.FromBase64String(Base64Value.Replace("-", "/").Replace("_", "+")); System.IO.MemoryStream mst = new System.IO.MemoryStream(); System.IO.Compression.GZipStream gz =null; try { mst.Write(bytValue, 0, bytValue.Length); mst.Seek(0, System.IO.SeekOrigin.Begin); gz = new System.IO.Compression.GZipStream(mst, System.IO.Compression.CompressionMode.Decompress, false); byte[] bytDecompressedValue = new byte[102400]; //100k - should be enough! return System.Text.Encoding.UTF8.GetString(bytDecompressedValue, 0, gz.Read(bytDecompressedValue, 0, 102400)); } finally { gz.Close(); mst.Close(); } } public static string GetURLWithoutAsmx(Type type) { //System.Security.Cryptography.MD5 md5 = System.Security.Cryptography.MD5.Create(); //byte[] bytTypeName = System.Text.Encoding.UTF8.GetBytes(type.Assembly.FullName + "/" + type.FullName); //return typeof(AjaxableScriptContolWithWebserviceHandlerFactory).Name + "/" + Convert.ToBase64String(md5.ComputeHash(bytTypeName)); return typeof(AjaxableScriptContolWithWebserviceHandlerFactory).Name + "/" + CompressAndBase64(type.Assembly.FullName + "/" + type.FullName); } public static string GetURLWithoutAsmx<T>() { return GetURLWithoutAsmx(typeof(T)); } } /// <summary> /// A serverside control which can be included into a DLL, with full ajax. /// It uses a webservice embedded into the same DLL to perform ajax operations. /// </summary> /// <typeparam name="TService">The type of the Webservice</typeparam> public abstract class AjaxableScriptContolWithWebservice<TService> : ScriptControl where TService : System.Web.Services.WebService { private bool flgConfigChanged = false; /// <summary> /// When set indicates that the web.config file was altered /// </summary> protected bool ConfigChanged { get { return flgConfigChanged; } } public AjaxableScriptContolWithWebservice() { CheckConfig(); this.Load += AjaxableScriptContolWithWebservice_Load; } private void CheckConfig() { //System.Configuration blah = System.Web.Configuration.WebConfigurationManager.OpenWebConfiguration("~"); System.Configuration.Configuration WebConfig = System.Web.Configuration.WebConfigurationManager.OpenWebConfiguration("~"); System.Xml.XmlDocument xmdWebConfig = new System.Xml.XmlDocument(); xmdWebConfig.Load(WebConfig.FilePath); if (System.Web.HttpRuntime.UsingIntegratedPipeline) { SetIntegratedConfig(xmdWebConfig, WebConfig.FilePath); } else { SetClassicConfig(xmdWebConfig, WebConfig.FilePath); } } private void SetIntegratedConfig(System.Xml.XmlDocument WebConfig, string WebConfigPath) { /* <system.webServer> <handlers> <add name="AjaxServerControl1.ServerControl1" verb="*" path="AjaxServerControl1.ServerControl1.asmx" type="AjaxServerControl1.ServerControl1" /> </handlers> </system.webServer> */ System.Xml.XmlElement xmeSystemWeb = (System.Xml.XmlElement)WebConfig.SelectSingleNode("/configuration/system.webServer"); if (xmeSystemWeb == null) { xmeSystemWeb = WebConfig.CreateElement("system.webServer"); WebConfig.DocumentElement.AppendChild(xmeSystemWeb); } System.Xml.XmlElement xmeHandlers = (System.Xml.XmlElement)xmeSystemWeb.SelectSingleNode("handlers"); if (xmeHandlers == null) { xmeHandlers = WebConfig.CreateElement("handlers"); xmeSystemWeb.AppendChild(xmeHandlers); } string strURL = Utils.GetURLWithoutAsmx<TService>(); System.Xml.XmlElement xmeAdd = (System.Xml.XmlElement)xmeHandlers.SelectSingleNode("add[@name='" + strURL + "']"); if (xmeAdd == null) { xmeAdd = WebConfig.CreateElement("add"); xmeAdd.SetAttribute("verb", "*"); xmeAdd.SetAttribute("name", strURL); xmeAdd.SetAttribute("path", strURL + ".asmx"); xmeAdd.SetAttribute("type", typeof(AjaxableScriptContolWithWebserviceHandlerFactory).FullName); xmeHandlers.AppendChild(xmeAdd); WebConfig.Save(WebConfigPath); flgConfigChanged = true; } } private void SetClassicConfig(System.Xml.XmlDocument WebConfig, string WebConfigPath) { throw new Exception(); /* <system.web> <httpHandlers> <add verb="*" path="AjaxServerControl1.ServerControl1.asmx" type="AjaxServerControl1.ServerControl1" validate="true" /> </httpHandlers> </system.web> * */ System.Xml.XmlElement xmeSystemWeb = (System.Xml.XmlElement)WebConfig.SelectSingleNode("/configuration/system.web"); if (xmeSystemWeb == null) { xmeSystemWeb = WebConfig.CreateElement("system.web"); WebConfig.DocumentElement.AppendChild(xmeSystemWeb); } System.Xml.XmlElement xmeHTTPHandlers = (System.Xml.XmlElement)xmeSystemWeb.SelectSingleNode("httpHandlers"); if (xmeHTTPHandlers == null) { xmeHTTPHandlers = WebConfig.CreateElement("httpHandlers"); xmeSystemWeb.AppendChild(xmeHTTPHandlers); } string strURL = Utils.GetURLWithoutAsmx<TService>(); System.Xml.XmlElement xmeAdd = (System.Xml.XmlElement)xmeHTTPHandlers.SelectSingleNode("add[@path='" + strURL + ".asmx']"); if (xmeAdd == null) { xmeAdd = WebConfig.CreateElement("add"); xmeAdd.SetAttribute("verb", "*"); xmeAdd.SetAttribute("path", strURL + ".asmx"); xmeAdd.SetAttribute("type", typeof(AjaxableScriptContolWithWebserviceHandlerFactory).FullName); xmeAdd.SetAttribute("validate", "true"); xmeHTTPHandlers.AppendChild(xmeAdd); WebConfig.Save(WebConfigPath); flgConfigChanged = true; } } protected bool AutoRefreshOnConfigChange { get; set; } protected override sealed void Render(HtmlTextWriter writer) { base.Render(writer); if (ConfigChanged && AutoRefreshOnConfigChange) { writer.WriteLine("<div style='position: fixed; height: 100%; width: 100%; top: 0; left: 0; background-color: rgba(128,128,128,0.85);'>\n" + " <div style='width: 40em; height: 5em; background-color: white; color: black; box-shadow: 2px 2px 0px black; text-align: center;" + "position: absolute; left: 50%; top: 50%; margin-left: -20em; margin-top: -2.5em;'><br/>" + "The web.config was updated. The page will now be reloaded.<br/>\n" + " N.B. You may have to check out the web.config file if it is under source control</div>\n" + "</div>"); writer.WriteLine("<meta http-equiv='refresh' content='1;" + this.Page.Request.Url.ToString() + "'/>"); } else { RenderContent(writer); } } protected virtual void RenderContent(HtmlTextWriter writer) { } void AjaxableScriptContolWithWebservice_Load(object sender, EventArgs e) { string strURL = Utils.GetURLWithoutAsmx<TService>(); ScriptManager.GetCurrent(this.Page).Services.Add(new ServiceReference(strURL + ".asmx")); } } // This class is largely written by James Ashley with some modifications by Lee Smith as indicated // see http://www.codeproject.com/Articles/22384/ASP-NET-AJAX-Controls-and-Extenders#http_handler for the original source code public class AjaxableScriptContolWithWebserviceHandlerFactory : IHttpHandlerFactory { #region IHttpHandlerFactory Members IHttpHandlerFactory factory = null; public IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated) { Assembly ajaxAssembly = typeof(System.Web.Script.Services.GenerateScriptTypeAttribute).Assembly; factory = (IHttpHandlerFactory)System.Activator.CreateInstance(ajaxAssembly.GetType("System.Web.Script.Services.RestHandlerFactory")); IHttpHandler restHandler = (IHttpHandler)System.Activator.CreateInstance(ajaxAssembly.GetType("System.Web.Script.Services.RestHandler")); ConstructorInfo WebServiceDataConstructor = ajaxAssembly.GetType("System.Web.Script.Services.WebServiceData").GetConstructor( BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { typeof(Type), typeof(bool) }, null); MethodInfo CreateHandlerMethod = restHandler.GetType().GetMethod("CreateHandler", BindingFlags.NonPublic | BindingFlags.Static, null, new Type[] { ajaxAssembly.GetType("System.Web.Script.Services.WebServiceData"), typeof(string) }, null); IHttpHandler originalHandler = null; //LS Change the behvaiour for some URLs, original code contained some hard-coded assumptions and also did not provide the js and jsdebug methods. string strAssemblyName = context.Request.Url.Segments[2].Trim('/').Trim(); if (strAssemblyName.ToLower().EndsWith(".asmx")) { strAssemblyName = strAssemblyName.Substring(0, strAssemblyName.Length - 5); } strAssemblyName = Utils.DeBase64AndDecompress(strAssemblyName); string strTypeName = strAssemblyName.Split('/')[1].Trim(); strAssemblyName = strAssemblyName.Split('.')[0].Trim(); string strMethodName = context.Request.Url.Segments[3]; Assembly assAssembly = Assembly.Load(strAssemblyName); Type typWebServiceType = assAssembly.GetType(strTypeName); if (strMethodName == "js" || strMethodName == "jsdebug") { originalHandler = new WebserviceJSGenerator(typWebServiceType); } else { //LS End modifications originalHandler = (IHttpHandler)CreateHandlerMethod.Invoke(restHandler, new Object[]{ WebServiceDataConstructor.Invoke(new object[] { typWebServiceType, false }), strMethodName }); } //<-- LS Type t = ajaxAssembly.GetType("System.Web.Script.Services.ScriptHandlerFactory"); Type wrapperType = null; if (originalHandler is IRequiresSessionState) { wrapperType = t.GetNestedType("HandlerWrapperWithSession", BindingFlags.NonPublic | BindingFlags.Instance); } else { wrapperType = t.GetNestedType("HandlerWrapper", BindingFlags.NonPublic | BindingFlags.Instance); } return (IHttpHandler)System.Activator.CreateInstance(wrapperType, BindingFlags.NonPublic | BindingFlags.Instance, null, new object[] { originalHandler, factory }, null); } public void ReleaseHandler(IHttpHandler handler) { factory.ReleaseHandler(handler); } #endregion } public class WebserviceJSGenerator : IHttpHandler { public bool IsReusable { get { return false; } } public void ProcessRequest(HttpContext context) { if (context == null) { throw new ArgumentNullException("context"); } string clientProxyScript = GetClientProxyScript(type); if (clientProxyScript != null) { context.Response.ContentType = "application/x-javascript"; context.Response.Write(clientProxyScript); } } private string GetClientProxyScript(Type Type) { StringBuilder strScript = new StringBuilder(); strScript.AppendLine("// Generated by " + this.GetType().FullName); strScript.AppendLine("// http://dev.dj-djl.com"); strScript.AppendLine("// http://blog.dj-djl.com"); strScript.AppendLine("// loosely based on the code generated by Microsoft's System.Web.Script.Services.ClientProxyGenerator class"); strScript.AppendLine("// requires other Microsoft javascript libraries to be loaded and assumes that to be the case."); strScript.AppendLine("Type.registerNamespace('" + Type.Namespace + "');"); strScript.AppendLine(Type.FullName.Replace("+", "_") + "=function(){"); strScript.AppendLine(Type.FullName.Replace("+", "_") + ".initializeBase(this);"); strScript.AppendLine(" this._timeout=0,"); strScript.AppendLine(" this._userContext=null,"); strScript.AppendLine(" this._succeeded=null,"); strScript.AppendLine(" this._failed=null}"); strScript.AppendLine(Type.FullName.Replace("+", "_") + ".prototype={"); strScript.AppendLine(" _get_path:function(){"); strScript.AppendLine(" var p = this.get_path();"); strScript.AppendLine(" if (p) return p;"); strScript.AppendLine(" else return " + Type.FullName.Replace("+", "_") + "._staticInstance.get_path();"); strScript.AppendLine(" }"); foreach (System.Reflection.MethodInfo miMethod in Type.GetMethods(BindingFlags.Public | BindingFlags.Instance)) { if (miMethod.GetCustomAttributes(typeof(WebMethodAttribute), true).Length > 0) { strScript.Append(" ," + miMethod.Name + ":function("); List<string> lstParametersObject = new List<string>(); List<string> lstParametersXmlComments = new List<string>(); List<string> lstParametersJSFunction = new List<string>(); foreach (System.Reflection.ParameterInfo piParam in miMethod.GetParameters()) { if (!piParam.IsRetval) { lstParametersObject.Add(piParam.Name + ":" + piParam.Name); lstParametersXmlComments.Add(" /// <param name=\"" + piParam.Name + "\" type=\"" + piParam.ParameterType.Name + "\" optional=\"" + (piParam.IsOptional ? "true" : "false") + "\" mayBeNull=\"" + (piParam.ParameterType.IsValueType ? "true" : "false") + "\"></param>"); lstParametersJSFunction.Add(piParam.Name); } } if (lstParametersJSFunction.Count > 0) { lstParametersJSFunction.Add(""); }; //add a blank parameter so that we do infact get a trailing comma, but only if there are items strScript.AppendLine(lstParametersJSFunction.ToDelimitedstring(", ") + "succeededCallback, failedCallback, userContext) {"); strScript.AppendLine(lstParametersXmlComments.ToDelimitedstring("\n")); strScript.AppendLine(" /// <param name=\"succeededCallback\" type=\"Function\" optional=\"true\" mayBeNull=\"true\"></param>"); strScript.AppendLine(" /// <param name=\"failedCallback\" type=\"Function\" optional=\"true\" mayBeNull=\"true\"></param>"); strScript.AppendLine(" /// <param name=\"userContext\" optional=\"true\" mayBeNull=\"true\"></param>"); strScript.Append(" return this._invoke(this.get_path(), '" + miMethod.Name + "', false, {"); strScript.Append(lstParametersObject.ToDelimitedstring(", ")); strScript.AppendLine("}, succeededCallback,failedCallback,userContext);} "); } } strScript.AppendLine(" }"); string strStatic = @" <<TypeName>>.registerClass('<<TypeName>>',Sys.Net.WebServiceProxy); <<TypeName>>._staticInstance = new <<TypeName>>(); <<TypeName>>.set_path = function(value) { <<TypeName>>._staticInstance.set_path(value); } <<TypeName>>.get_path = function() { /// <value type=""String"" mayBeNull=""true"">The service url.</value> return <<TypeName>>._staticInstance.get_path();} <<TypeName>>.set_timeout = function(value) { <<TypeName>>._staticInstance.set_timeout(value); } <<TypeName>>.get_timeout = function() { /// <value type=""Number"">The service timeout.</value> return <<TypeName>>._staticInstance.get_timeout(); } <<TypeName>>.set_defaultUserContext = function(value) { <<TypeName>>._staticInstance.set_defaultUserContext(value); } <<TypeName>>.get_defaultUserContext = function() { /// <value mayBeNull=""true"">The service default user context.</value> return <<TypeName>>._staticInstance.get_defaultUserContext(); } <<TypeName>>.set_defaultSucceededCallback = function(value) { <<TypeName>>._staticInstance.set_defaultSucceededCallback(value); } <<TypeName>>.get_defaultSucceededCallback = function() { /// <value type=""Function"" mayBeNull=""true"">The service default succeeded callback.</value> return <<TypeName>>._staticInstance.get_defaultSucceededCallback(); } <<TypeName>>.set_defaultFailedCallback = function(value) { <<TypeName>>._staticInstance.set_defaultFailedCallback(value); } <<TypeName>>.get_defaultFailedCallback = function() { /// <value type=""Function"" mayBeNull=""true"">The service default failed callback.</value> return <<TypeName>>._staticInstance.get_defaultFailedCallback(); } <<TypeName>>.set_enableJsonp = function(value) { <<TypeName>>._staticInstance.set_enableJsonp(value); } <<TypeName>>.get_enableJsonp = function() { /// <value type=""Boolean"">Specifies whether the service supports JSONP for cross domain calling.</value> return <<TypeName>>._staticInstance.get_enableJsonp(); } <<TypeName>>.set_jsonpCallbackParameter = function(value) { <<TypeName>>._staticInstance.set_jsonpCallbackParameter(value); } <<TypeName>>.get_jsonpCallbackParameter = function() { /// <value type=""String"">Specifies the parameter name that contains the callback function name for a JSONP request.</value> return <<TypeName>>._staticInstance.get_jsonpCallbackParameter(); } <<TypeName>>.set_path(""/<<URL>>""); "; string strURL = Utils.GetURLWithoutAsmx(Type); strScript.AppendLine(strStatic.Replace("<<NameSpace>>", Type.Namespace).Replace("<<TypeName>>", Type.FullName.Replace("+", "_")).Replace("<<URL>>", strURL + ".asmx")); foreach (System.Reflection.MethodInfo miMethod in Type.GetMethods(BindingFlags.Public | BindingFlags.Instance)) { if (miMethod.GetCustomAttributes(typeof(WebMethodAttribute), true).Length > 0) { List<string> lstParametersJSFunction = new List<string>(); List<string> lstParametersXmlComments = new List<string>(); foreach (System.Reflection.ParameterInfo piParam in miMethod.GetParameters()) { if (!piParam.IsRetval) { lstParametersJSFunction.Add(piParam.Name); lstParametersXmlComments.Add(" /// <param name=\"" + piParam.Name + "\" type=\"" + piParam.ParameterType.Name + "\" optional=\"" + (piParam.IsOptional ? "true" : "false") + "\" mayBeNull=\"" + (piParam.ParameterType.IsValueType ? "true" : "false") + "\"></param>"); } } if (lstParametersJSFunction.Count > 0) { lstParametersJSFunction.Add(""); } string strMethodStatic = @" <<TypeName>>.<<MethodName>>= function(" + lstParametersJSFunction.ToDelimitedstring(", ") + @"onSuccess,onFailed,userContext) { " + lstParametersXmlComments.ToDelimitedstring("\n") + @" /// <param name=""succeededCallback"" type=""Function"" optional=""true"" mayBeNull=""true""></param> /// <param name=""failedCallback"" type=""Function"" optional=""true"" mayBeNull=""true""></param> /// <param name=""userContext"" optional=""true"" mayBeNull=""true""></param> <<TypeName>>._staticInstance.<<MethodName>>(" + lstParametersJSFunction.ToDelimitedstring(", ") + @" onSuccess,onFailed,userContext); }"; strScript.AppendLine(strMethodStatic.Replace("<<NameSpace>>", Type.Namespace).Replace("<<TypeName>>", Type.FullName.Replace("+", "_")).Replace("<<MethodName>>", miMethod.Name)); } } return strScript.ToString(); } Type type; public WebserviceJSGenerator(Type type) { this.type = type; } } }