On my last post I wrote about a solution for the problem that arises when we try the use path infos and ASP.NET Themes and Skins together.
Raj Kaimal suggested rewriting all LINK HTML elements URLs to the correct URL as seen from the client. Something like this:
void HttpApplicationPreRequestHandlerExecute(object sender, System.EventArgs e)
{
var httpApplication = sender as System.Web.HttpApplication;
var httpContext = httpApplication.Context;
var page = httpContext.CurrentHandler as System.Web.UI.Page;
if ((page != null) && !string.IsNullOrEmpty(httpContext.Request.PathInfo))
{
page.PreRenderComplete += delegate(object source, System.EventArgs args)
{
var p = source as System.Web.UI.Page;
foreach (System.Web.UI.Control headerControl in p.Header.Controls)
{
var link = headerControl as System.Web.UI.HtmlControls.HtmlLink;
if (link != null)
{
link.Href = p.ResolveUrl(link.Href);
}
}
};
}
}
With this approach you still have a problem (which mine didn’t solve) with post backs because the rendering of the ACTION of the HTML FORM is also broken.
Israel Aéce suggested the use of the BASE HTML element to re-base relative URLs. Something like this:
void HttpApplicationPreRequestHandlerExecute(object sender, System.EventArgs e)
{
var httpApplication = sender as System.Web.HttpApplication;
var httpContext = httpApplication.Context;
var page = httpContext.CurrentHandler as System.Web.UI.Page;
if ((page != null) && !string.IsNullOrEmpty(httpContext.Request.PathInfo))
{
page.PreRenderComplete += delegate(object source, System.EventArgs args)
{
page.Init += delegate(object source, System.EventArgs args)
{
var p = source as System.Web.UI.Page;
var htmlBase = new System.Web.UI.WebControls.WebControl(System.Web.UI.HtmlTextWriterTag.Base);
htmlBase.Attributes.Add("href", p.Request.Url.GetLeftPart(System.UriPartial.Authority) + p.Request.CurrentExecutionFilePath);
p.Header.Controls.Add(htmlBase);
};
};
}
}
This seems like the better solution except if your site sits behind several security perimeters and it is not possible to be sure what the domain is as seem from the client side, which was my problem to begin with.
But if you are thinking of calling Server.Execute, Server.TransferRequest or Server.TransferRequest, neither of these two solutions will work.
On my last post I wrote about the problem that arises when we try the use path infos and ASP.NET Themes and Skins together.
But most of the times you don’t care about the why you can’t. You just want to know how you can.
The way I see it, the right solution would be to render the URLs for the stylesheets rooted.
But since I can’t do that, the next best thing is the serve the wrongly addressed request properly.
But how can we do that?
The only way I could come up with, was an HTTP Module:
public class AppThemesModule : global::System.Web.IHttpModule
{
private static int searchStartIndex = System.Web.HttpRuntime.AppDomainAppVirtualPath.Length;
#region IHttpModule Members
public void Dispose()
{
}
public void Init(System.Web.HttpApplication context)
{
context.BeginRequest += HttpApplicationBeginRequest;
}
#endregion
void HttpApplicationBeginRequest(object sender, System.EventArgs e)
{
System.Web.HttpApplication httpApplication = sender as System.Web.HttpApplication;
string path = httpApplication.Request.Path;
if (path.Length > searchStartIndex)
{
int appThemesStartIndex = path.IndexOf("/App_Themes/", searchStartIndex);
if (appThemesStartIndex > 0)
{
httpApplication.Context.RewritePath("~" + path.Substring(appThemesStartIndex));
}
}
}
}
Besides registering the module, you’ll have to configure your virtual directory so that all the files to be served out of the themes are handled by a StaticFileHandler.
If you ever worked with ASP.NET Themes and Skins, you know that stylesheet links are added to the head section of the HTML document.
The rendered URL to these stylesheets is always relative to location of the page being requested.
So, for a request to:
http://MySite/Section/Default.aspx
you'll get:
<link href="../App_Themes/Default/Styles.css" type="text/css" rel="stylesheet" />
which will make the web browser request for:
http://MySite/App_Themes/Default/Styles.css
and it all works fine.
Well, it works fine until you need to navigate to:
http://MySite/Section/Default.aspx/PathInfo
You'll get the same stylesheet reference and the browser will request for:
http://MySite/Section/Default.aspx/App_Themes/Default/Styles.css
This happens because the web browser has no knowledge of what PathInfos are. It only accounts for the number of forward slashes (/).
I've filed a bug on Microsoft Connect about this.
I find the System.Web.HttpValueCollection class very useful in a wide number of situations that involve composing HTTP requests or any other need to represent name/value collection as a string (in an XML attribute, for example).
As of now (.NET Framework 3.5 SP1 Beta), the only way to create an instance of the System.Web.HttpValueCollection class is using the System.Web.HttpUtility.ParseQueryString method.
I’d like to see it public and available in a more generic assembly like System.DLL to be available on every type of .NET application (Windows Client applications, Windows Service applications, Silverlight applications, Windows Mobile applications, etc.).
If you agree with me, vote on my suggestion on Microsoft Connect.
If you run this code:
System.Collections.Specialized.NameValueCollection queryString = System.Web.HttpUtility.ParseQueryString("noKey&=emptyKey&A=Akey");
queryString will actually have the running type of System.Web.HttpValueCollection.
What's great about this class is that its ToString method output is the collection's content in a nice URL encoded format.
As with its base class (NameValueCollection), there’s a difference between a null string and an empty string key and the parsing treats query string parameters with no parameter specification as having a null string key and parameters with an empty string key having an empty string parameter key.
So, when call ToString on the instance returned by System.Web.HttpUtility.ParseQueryString method you would expect to get the parsed string (or, at least, one that would be parsed into the equivalent collection), right? But what you’ll get instead is this: noKey&emptyKey&A=Akey.
I’ve filed a bug into connect. If you think this is important and must be corrected, please vote.
Today Typemock released version 4.3 of Typemock Isolator. Download it from here.
What’s new?
- Support for Ivonna. For those of you who develop ASP.Net applications, Ivonna is a great tool, built on top of Isolator’s platform, to simplify writing tests for ASP.NET.
- Typemock.Integration.Packs namespace APIs added to support license management through Isolator, the way Ivonna does.
- As announced when it was released, version 4.2 was the last version of Isolator to support .NET 1.1. Version 4.3 only supports the 2.0 runtime and its Visual Studio counterparts: VS2005 and VS2008.
- For 64 bit machines, now there’s a single installer. (don’t forget to uninstall both previous 32 and 64 installers prior to installing 4.3.)
- RecorderManager.GetMockOf(instanceRef) and MockManager.GetMockOf(instanceRef). To retrieve the mock controller object based on a reference to the instance. (more...).
Bug fixes:
- Fixes to DLINQ support. LINQ Queries with data tables now work better, for example with GroupBy.
- Static constructors in Natural Mocks are now invoked correctly.
- A bug that caused an exception to be thrown when mocking interfaces ("Method XX in type IMyInterface has no matching overload that returns TypeMock.Mock+a) was fixed.
- A bug that caused an exception to be thrown when the mocked object was overriding Equals was fixed.
- A bug that caused failure in mocking explicit interface with the same name was fixed.
- A bug occurring im multithreading scenarios in VerifyWithWait was fixed.
- A bug that causes NullReferenceException to be thrown when using Auto Deploy was fixed.
See also:
On a previous post I introduced a library for generating random values for purposes of unit testing.
I received a few comments and questions on my blogs [^][^][^][^].
Simon says that he’d “generally want every instance of the test I run to be repeatable on *every* run” and he’d “inclined to use the data source functionality in VSTS Unit Tests”. I couldn’t agree more.
Hugo pointed me to Pex. Looks nice. I’ll have to take a deeper look at it.
Other commenters were worried that in the case of a test failing to pass they were unable to reproduce the conditions of the test and thus would be unable to understand why the test had failed. As I see it, all data used in a failed test should be in the test output (at least, the data that made the test fail). Being it predictable data or random data.
I guess that most of the comments “against” my idea were due to the fact that I didn’t explain it well. I’ll try to do a better job now.
I use these random values when the value itself is not important, what’s important is its uniqueness.
Imagine you have Costumer class where it’s enough to tell that the costumer is the same in any two given instances if the costumer ID is the same. In order to test the comparison code, I don’t need any particular reproducible set of values. I just need them to be different or the same depending on the test.
Imagine I have some code that needs to access the database with the connection string in a specific connection string settings element. I don’t care what the connection string is because I won’t be really accessing the database (this is really easy to accomplish with Isolator).
I hope I’ve explained myself better this time.
Recently I’ve been asked if it would be possible to replace the assemblies loaded by a .NET Windows Service application while it was running like with ASP.NET. Like with ASP.NET, an application start and end events where needed.
The solution is quite simple. The Windows Service application is just a loader that has no references to the loaded assemblies that might change have an assembly with an entry point that acts as the start event. This assembly must be loaded in a new AppDomain with ShadowCopyFiles set. The end event is handled by handling the DomainUnload event of the AppDomian where the running assembly is loaded.
If you want to have the running assembly and its referenced assemblies unloaded and reloaded whenever a change occurs in the assembly files, a FileSystemWatcher could be used, although I would prefer to override such behavior in ASP.NET, not copy it.
An assembly loader can be as simple as this:
class Program
{
private static Thread thread = null;
private static AppDomain appDomain = null;
static void Main(string[] args)
{
while (true)
{
Console.WriteLine();
Console.WriteLine("Options:");
if (appDomain == null)
{
Console.WriteLine(" L - Load");
}
else
{
Console.WriteLine(" U - Unload");
}
Console.WriteLine(" X - Exit");
switch (Console.ReadKey().KeyChar)
{
case 'l':
case 'L':
thread = new Thread(Load);
thread.Name = "Runner";
thread.Start(args);
while (appDomain == null) ;
break;
case 'u':
case 'U':
Unload();
break;
case 'x':
case 'X':
Unload();
return;
}
}
}
private static void Load(object obj)
{
string[] args = obj as string[];
AppDomainSetup appDomainSetup = new AppDomainSetup();
appDomainSetup.ApplicationBase = args[0];
appDomainSetup.PrivateBinPath = args[0];
appDomainSetup.ShadowCopyFiles = "true";
appDomain = AppDomain.CreateDomain("Runner", AppDomain.CurrentDomain.Evidence, appDomainSetup);
appDomain.ExecuteAssemblyByName("ConsoleApplication", AppDomain.CurrentDomain.Evidence, new string[0]);
}
private static void Unload()
{
AppDomain.Unload(appDomain);
appDomain = null;
thread = null;
}
}
The loader receives the path for the assembly to run and its name and loads it.
And the assembly to run as simple as this:
class Program
{
static readonly DateTime dateTime = DateTime.Now;
static void Main(string[] args)
{
AppDomain.CurrentDomain.DomainUnload += delegate
{
Console.WriteLine();
Console.WriteLine("Unloading: {0}", dateTime);
};
System.Reflection.Assembly assembly = System.Reflection.Assembly.GetExecutingAssembly();
Console.WriteLine("Loading: {0}", dateTime);
Console.WriteLine("Assembly: {0}", assembly.FullName);
Console.WriteLine("Location: {0}", assembly.Location);
FileInfo fileInfo = new FileInfo(assembly.Location);
Console.WriteLine("Location Dates: CreationTime={0}, LastWriteTime={1}, LastAccessTime={2}",
fileInfo.CreationTime, fileInfo.LastWriteTime, fileInfo.LastAccessTime);
while (true)
{
Console.Write('.');
Thread.Sleep(100);
}
}
}
With this “system” you can replace the running assembly and load or unload it whenever you want to.
When writing my unit tests I don’t like to use hard coded fixed values because I either end up using the same values or, because of that, tests may succeed by coincidence.
Over time, I have developed an helper class to generate random values for testing.
namespace PauloMorgado.VisualStudio.TestTools.UnitTesting
{
public static class RandomGenerator
{
public static bool Boolean();
public static string String();
public static string String(string prefix);
public static short Int8();
public static short Int8(short maxValue);
public static short Int8(short minValue, short maxValue);
public static short Int16();
public static short Int16(short maxValue);
public static short Int16(short minValue, short maxValue);
public static int Int32();
public static int Int32(int maxValue);
public static int Int32(int minValue, int maxValue);
public static TEnum Enum<TEnum>();
public static TEnum EnumFlagsWith<TEnum>(TEnum flagsToAdd);
public static TEnum EnumFlagsWithout<TEnum>(TEnum flagsToRemove);
public static TEnum Enum<TEnum>(int maxValue);
public static TEnum Enum<TEnum>(int minValue, int maxValue);
public static System.Guid Guid();
}
}
This is something that I would like to find on mock frameworks (like Typemock Isolator, Rhino.Mocks or MoQ).
It’s still a work in progress, but if you want to try it, it’s on my MSDN Code Gallery: Random Generator For Unit Testing
More Posts
Next page »