WPF unit testing trouble with Pack URIs

|

I have been busily coding WPF and Silverlight 2 for the last couple of months, and I will be for at least a few more weeks to come; however, during this development frenzy I ran into an issue with unit testing some new code. I also found an interesting solution to the problem, which required more than a simple query by way of Google, so I thought I’d share the problem and the solution here, just help the boys and girls at Google out a little.

The code under test relies on certain embedded resources. Said resources are to be consumed by a WPF bootstrapped assembly; therefore each resource is embedded in the assembly by using the build type of Resource, as opposed to Embedded Resource, which in turn means each resource has to be referenced by using a Pack URI. Therein lies the rub: whenever the unit test attempted to call the code that pulled out any of resources I would get the following  UriFormatException:

System.UriFormatException was unhandled
  Message="Invalid URI: A port was expected because of there is a colon (':') present but the port could not be parsed."
  Source="System"
  StackTrace:
       at System.Uri.CreateThis(String uri, Boolean dontEscape, UriKind uriKind)
       at System.Uri..ctor(String uriString, UriKind uriKind)
… <snip/>

Bugger! You can easily reproduce this error by running the following code snippet in anything other than a WPF application:

var packUri = new Uri(
    "pack://application:,,,/ReferencedAssembly;component/Subfolder/ResourceFile.xaml",
    UriKind.Absolute);

As the test harness is not bootstrapped by a WPF application it knows absolutely nothing about Pack URIs; and in my case the System.Uri parsing code ended up trying to parse the Pack URI as if it were a Web URI, which is clearly not what I wanted and creates a situation where all roads lead to Exception City.

However, all is not lost, here is the simple solution I used for my tests:

if (!UriParser.IsKnownScheme("pack"))
{
    UriParser.Register(new GenericUriParser
        (GenericUriParserOptions.GenericAuthority), "pack", -1);
}

If you run this code before you try to parse any Pack URIs all will be well. It is worth mentioning that you need to only call this code once per process. This code registers pack as a valid scheme (in the form of pack://) and then puts all URI parsing into a generic mode, only parsing against registered schemes (which pack is now one), and that is it .. the job, as they say, is a good ‘un.

I’ve written a small test application that tests the URI parsing across threads to prove that the Register call is truly process wide, which you can download from here: PaulJ.PackUriParsing.zip

Above is a screenshot of the app .. the first call shows the parse failing, before the call to Register, then the second two calls show the exact same Pack URI being parsed successfully on two different threads (the main UI thread, and a newly created thread for the test).

I hope this helps to make for a better Google experience for someone; as usual if you have any comments, questions, flames, enhancements I would love to hear from you. In the meantime think deeply and enjoy.

22 comments:

jo said...

Another way of going about this is using an isolation framework like Typemock Isolator or Rhinomock.

Paolo B said...

Your snippet solve the UriFormatException, but after I find this exception: URI prefix not recognized..

Any suggestion?

Thanks
Paolo

Paul said...

Hey Paolo, the prefix error sounds like there might be a typo in the registration of the scheme, but I'm only guessing that it's the scheme prefix that the exception is going on about.

If you would like to post some sample code that produces the error I'd be happy to take a look at it for you.

Thanks for reading.

-PJ

Paolo B said...

The error happens in this line, only in unit testing:

... = New BitmapImage(New Uri("pack://application:,,,/TruckSelf.ViewModel;component/Resources/options.png")

And, of course, the resource exists and in normal code works.

Thank you for your answer.
Paolo

Paul said...

So is that line of code actually in the test method? The reason I ask is that ultimately if you want to use Pack URIs to get at resources you're going to need to be bootstrapped by WPF .. or as Jo suggests above use an isolation framework of some description (http://www.mockframeworks.com/) .. it's hard for me to tell from the little amount of code you have shown me.

Maybe you could write a small sample that demonstrates the issue and upload somewhere (box.net) or mail me (see links at the top of the page)?

-PJ

Paolo B said...

In my code (no test code) I create some buttons with an image, using the line code I post.
When I run the test the buttons should be created, because the creation is in the New function, but it raise the error.
The solution I find is create an instance of the application in the ClassInitialize test method.
I don't know if is the right solution, but it works.

Thanks
Paolo

Balaji Gunasekaran said...

Yup It helped me (i Googled for this)

Paul said...

Hey Balaji Gunasekaran, thanks for your comment; I'm glad you found it useful.

-PJ

Oskar said...

Hi Paul, thanks for this post. I have the same problem, but just like for Paolo B, I now get NotSupportedException instead of UriFormatException.

I will try to use a mock framework to see if that helps...

Paul said...

Hey Oskar - thanks for reading.

I think in that in your situation, as with PaoloB, what you might want to do is make a call to the Application class in some way (Application.Current for example). The reason you want to do this is that the Application static constructor will do a bunch of work for you when using Pack URI's, including the correct registration of "pack://".

Unfortunately I have not had a chance to test this out in any Unit Tests yet, but I strongly suspect that it will solve the problem (as something similar has for PaoloB).

If you give it a try then please let me know how it works out for you.

-PJ

Oskar said...

Hi again Paul, I ended up just calling new Application() in a class initializer for my test class like this:

[ClassInitialize()]
public static void InitializeWPFToBeAbleToUsePackURIs(TestContext testContext)
{
System.Windows.Application app = new System.Windows.Application();
}

This works perfectly and allows me to use pack URIs without any exceptions (and without manually registering the pack scheme, just like you wrote).

Is there any reason for me to use Application.Current instead of creating a new instance?

Thanks,
Oskar

Paul said...

Hey Oskar, glad that's worked out for you.

To answer your question though: no, there's no reason. However, the reason that I think it's working for you is because by creating an instance of the Application class the static constructor is being called; which is really the goal here, as that's what's doing all the heavy lifting to make the Pack URI system work.

So I suspect you could just call any static method/property to get that initialization done, but I also see no reason why you can't also just stick with creating an instance, it works :)

Thanks again for reading. -PJ

Andrew Jackson said...

Thank you, that was really helpful.

Paul said...

Hey Andrew, glad you found it useful; thanks for reading.
-PJ

Reticeo said...

Thanks for the tip, it worked for me!

Paul said...

Glad it helped it you out. Thanks for reading.

-PJ

Anonymous said...

Also found this when googling for the error message, found it very helpful.
I also had the problem with a different exception after registering the "pack" scheme, but since I only used it to load images which are not important for testing, I just put that code into a try/catch. This works for me

Prabhu said...

Thanks a lot. Glad I found your article after a lot of head scratching.

Prabhu said...

Thanks a lot. Glad I found your article after a lot of head scratching.

Craig said...

Hello,
After spending many hours searching I found a very simple method, I found no example and so I share mine here
which works with images. (mine was a .gif)

Summary:

It returns a BitmapFrame which ImageSource "destinations" seem to like.

Use:

doGetImageSourceFromResource ("[YourAssemblyNameHere]", "[YourResourceNameHere]");

Method:

static internal ImageSource doGetImageSourceFromResource(string psAssemblyName, string psResourceName){
Uri oUri = new Uri("pack://application:,,,/" +psAssemblyName +";component/" +psResourceName, UriKind.RelativeOrAbsolute);
return BitmapFrame.Create(oUri);
}

Learnings:

From my experiences the pack string is not the issue, check your streams and especially if reading it the first time has set the pointer
to the end of the file and you need to re-set it to zero before reading again.

I hope this saves you the many hours I wish this piece had for me!

Thanks,
Craig
Platinum Salon Software
http://www.SalonSoftwareSystem.com

Thomas Levesque said...

The pack URI scheme is registered in the type initializer of the PackUriHelper class. So all you need to do is access a static member of PackUriHelper (e.g. the UriSchemePack field) to ensure the type initializer has run.

Brent said...

I had the same issue as Paolo and Oskar. I wanted to note that in order to reference System.Windows.Application() I had to add a reference in my UnitTest project to Presetnationframework. You will look and look for "System.Windows" and never find it :)