I’ve been struggling with building a custom ResourceManager and hooking it into ASP.NET over the last few days as part of a localization engine that is data driven rather than through Resources. I’ve had a separate engine for this for sometime, but it hasn’t been hooked into the .NET or ASP.NET infrastructure so went back and tried to figure out how to build a custom resource manager and resource provider to hook it into ASP.NET.
This has been a pretty painful experience for me. The ResourceManager class is not well setup for extensibility – no interfaces, you have to basically inherit and selectively override methods in various classes (ResourceManager, ResourceSet, ResourceReader, ResourceWriter). With the help of some code I found I was able to make that work. The ASP.NET interfaces ResourceProvider and ResourceProviderFactory are a little better, but not much – also no interfaces but at least these interfaces are relatively simple and it’s easy to see what needs to be overridden. So after a painful few days I managed to get my code hooked up and running and serving resources.
It works in the development project and I can run the page just fine if I use in place compilation that VS.NET uses in development. However, when I compile the project I get an error like this:
:
The resource object with key 'LinkButton1Resource1.Text' was not found.
error ASPPARSE: /LocalizationTest.aspx(Line 27)
LinkButton1 looks something like this:
<asp:LinkButton ID="LinkButton1" runat="server" Text="<%$ Resources:LinkButton1Resource1.Text %>" meta:resourcekey="LinkButton1Resource2"></asp:LinkButton>
Which is an explicit expression and as I mentioned this works fine in dev mode. At pre-compile time however this fails outright.
ASP.NET compiles pages and does something like this for the parse tree:
private LinkButton __BuildControlLinkButton1()
{
LinkButton button1 = new LinkButton();
base.LinkButton1 = button1;
button1.ApplyStyleSheetSkin(this);
button1.ID = "LinkButton1";
button1.Text = Convert.ToString(base.GetLocalResourceObject("LinkButton1Resource1.Text"), CultureInfo.CurrentCulture);
return button1;
}
Notice that it assigns the resource directly by calling the base.GetLocalResourceObject, which in turn calls the local resource manager. That’s reasonable, but it doesn’t explain WHY the compiler would need to have access to this resource.
So I took out my custom provider and compiled the Resource based version. Now it too will fail if there’s a missing explicit or implicit resource in the base (Invariant) resource file. Apparently ASP.NET tries to be overly helpful by checking and making sure the resource exists. This must be some sort of explicit check though because nothing that gets compiled into the page really changes in this respect. This is a compiler issue.
So now that I sort of understand how this works I can see that the ResourceManager needs to be accessible and working at Compile time. How freaking LAME is that? That pretty much defeats any dynamic mechanism for loading resources at runtime.
Ok, fine, to see if I could get this to work I hardcoded my connection string into the ResourceManager (not realistic I know for a real application but I’ll worry about that later) and then hooked it back up and re-ran the compile. I also hooked up SQL Profiler to see if it’s hitting the database and what it’s asking for. Sure enough the database gets hit and the compiler is asking for the en-us and en ResourceSets which should work fine. Both of these include the resource that the precompiler is failing on. In theory at least my provider should be feeding the appropriate resource set now and the compiler should be finding the correct resource string.
But it’s still failing…
This is looking more and more like I wasted 2 days working on this because of a bad compiler check implementation. The code runs just fine, but the damn compiler isn’t happy because it find resources at compile time.
I guess one solution to this debacle is to use Web Application Projects which doesn’t pre-compile the ASPX pages and defers this process until runtime where it appears to work correctly. But then using Web Deployment Projects on the project won’t work…
Has anybody been down this path and figured out a way around this mess?
Other Posts you might also like