This post originated from an RSS feed registered with .NET Buzz
by Scott Hanselman.
Original Post: Validating that XmlSchemas and their Imports are Valid and All Good with an XmlResolver.
Feed Title: Scott Hanselman's ComputerZen.com
Feed URL: http://radio-weblogs.com/0106747/rss.xml
Feed Description: Scott Hanselman's ComputerZen.com is a .NET/WebServices/XML Weblog. I offer details of obscurities (internals of ASP.NET, WebServices, XML, etc) and best practices from real world scenarios.
The very awesome Oleg Tkachenko commented
in a recent
post of mine (as did Patrick Cauldwell, in person) that what I was doing could
have been accomplished with a custom XmlResolver. Both are absolutely right.
I did my little hack because it was quick but Oleg's right, it would have been "more
correct" to do something like this:
public
class XmlCustomResolver
: XmlUrlResolver
{
override public object GetEntity(Uri
absoluteUri, string role, Type ofObjectToReturn)
{
//Here, mess with absoluteUri.AbsolutePath
and return an XPathNavigator or Stream or whatever
}
}>
>
So what happens is that you pass in the Resolver into the call to .Compile like:
foreach
(XmlSchema x in w.Schemas)
{
x.Compile(new ValidationEventHandler(OnValidationEvent), new XmlBaseDirectoryResolver());
}>
However, my problem was a smidge more subtle than it initially appeared.
The problem was, for me, that I want to resolve the schemaLocations (which look like
"banking/someDomainObject.xsd") relative to the path that
the WSDL is in, like "C:\dev\whatever\wsdl\". However,
by the time we get into the Resolver (when .Compile calls back to GetEntity()) the
propery absoluteUrl.AbsolutePath already contains "C:\dev\MyTestConsole\bin\debug\someDomain\banking\someDomainObject.xsd."
See? It's already "pre-resolved" the path relative to AppDomain.CurrentDomain.CurrentDirectory.
At this point, I have no way (that I can see) to know what was the original relative
path. I want the schemaLocation to be "C:\dev\whatever\wsdl\banking\someDomainObject.xsd."
I could have passed the directory of the WSDL file into the constructor call to the
Resolver. So we add:
foreach(XmlSchema x in w.Schemas)
{
x.Compile(new ValidationEventHandler(OnValidationEvent), new XmlBaseDirectoryResolver(wsdlFile.DirectoryName));
}
Note that the AbsolutePath has the Directory Separators as "/", so I have to fix those
as well. Here's the final "XmlBaseDirectoryResolver." It's more lines
of code, but it's also reusable.
public
class XmlBaseDirectoryResolver
: XmlUrlResolver
{
private string baseDir
= String.Empty;
public XmlBaseDirectoryResolver(string baseDirectory)
: base()
{
baseDir = baseDirectory;
}
override public object GetEntity(Uri
absoluteUri, string role, Type
ofObjectToReturn)
{
if (absoluteUri.IsFile
== true)
{
//Change
the directory characters to the same ones that AppDomain.CurrentDomain.BaseDirectory
uses
string newFileName
= absoluteUri.AbsolutePath.Replace('/',Path.DirectorySeparatorChar);
//Now,
yank the automatically added portion...
newFileName
= newFileName.Replace(AppDomain.CurrentDomain.BaseDirectory,String.Empty);
//Add
our Base Directory...
newFileName
= Path.Combine(baseDir, newFileName);
//Return
the file...
return new FileStream(newFileName,
FileMode.Open, FileAccess.Read, FileShare.Read);
}
return base.GetEntity(absoluteUri,
role, ofObjectToReturn);
}
}>
The big problem with this particular result? It doesn't work with relative paths
that use the dotdotslash "../../whatever/this.xsd." At this point - the point
of resolution - too much has been already resolved for me. :) The only way I
could fix that would be to work backwardsto figure out
how many ../..'s were removed for me, and put them back. Not worth it.