Validating that XmlSchemas and their Imports are Valid and All Good with an XmlResolver.
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:
class XmlCustomResolver : XmlUrlResolverpublic
{
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 backwards to figure out how many ../..'s were removed for me, and put them back. Not worth it.
Oleg has a great article up on his blog on how to Create your Own XmlResolver. His actually retrieves the schema (stored) in a SQL Server.
About Scott
Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.
About Newsletter
Comments are closed.
could you post the xsd's too
i have an xsd sample and this does not seem to work.
it can never for some reason find my xmlurlResolver
-siddharth