Scott Hanselman

Validating that XmlSchemas and their Imports are Valid and All Good with an XmlResolver.

July 15, '04 Comments [1] Posted in XML | Bugs
Sponsored By

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 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.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by SherWeb

Using the Server (rather than Workstation) Garbage Collector with the .NET Framework (CLR)

July 15, '04 Comments [2] Posted in ASP.NET | XML | Tools
Sponsored By

We run a big .NET Application Server, often on multi-proc machines, so we care about performance and, consequently, garbage collection.  I've collected a few resources around the two kinds of Garbage Collectors available in .NET, the Workstation GC and the Server GC.

From the MSDN Help:

Two different Garbage Collectors are available for the CLR: a Workstation GC and a Server GC. Console and Windows Forms applications host the Workstation GC, and ASP.NET hosts the Server GC. The Server GC is optimized for throughput and multi-processor scalability. The server GC pauses all threads running managed code for the entire duration of a collection, including both the Mark and Sweep Phases, and GC happens in parallel on all CPU's available to the process on dedicated high-priority CPU-affinitized threads. If threads are running native code during a GC then those threads are paused only when the native call returns. If you are building a server application that is going to run on multiprocessor machines then it is highly recommended that you use the Server GC. If your application in not hosted by ASP.NET, then you are going to have to write a native application that explicitly hosts the CLR.

HINT:   If you are building scalable server applications, host the Server GC. See Implement a Custom Common Language Runtime Host for Your Managed App.
  • Hosting the CLR in a generic service and Hosting the Server GC: "To load the svr GC, we created a generic Windows service that loads the svr GC, creates an AppDomain, and runs our application."
     CComPtr spRuntimeHost;
     HRESULT hr = CorBindToRuntimeEx(NULL, 
       //Retrieve latest version by default
      L"svr",
     
       //Request a Server (svr) or WorkStation (wks) build of the CLR
      STARTUP_LOADER_OPTIMIZATION_SINGLE_DOMAIN, 
      CLSID_CorRuntimeHost,
      IID_ICorRuntimeHost,
      (void**)&spRuntimeHost);
  • Using the Server Garbage Collector:  
    1. Create the system environment variable COMPLUS_BUILDFLAVOR to SVR
    2. SET COMPLUS_BUILDFLAVOR = SVR
    3. Generate this key in the windows registry: HKLM/Software/Microsoft/COMPlus with a single string value BuildFlavor with value "svr"
    SDH Note: This is fully unsupported my Microsoft.
  • Improvements in .NET 1.0 SP3 and .NET 1.1 SP1 and How to enable CLR Server GC?
     <Configuration>
        <runtime>
            <gcServer enabled=“true“ />
        </runtime>
    </Configuration> 

From Chris Kinsman: Here’s a way to do it that’s not a hack – right-click on My Computer in .NET Configuration in Administrative Tools. UPDATE: In fact, this is NOT so, details on this setting at OdeToCode.com

 

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.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by SherWeb

Validating XML schemas that have been xs:imported and have schemaLocation

July 12, '04 Comments [4] Posted in Web Services | XML
Sponsored By

In this example, Wsdl is a class that's one of our internal convenience wrappers around System.Web.Services.Description.ServiceDescription.  The Schemas property is just the XmlSchema[] that from service.Types.Schemas. 

I wasn't sure if my WSDL file was kosher, and I wanted to "validate" the whole setup.  Most of my XML Schemas were pulled in via xsd:import in my wsdl like:

 <wsdl:types>
  <xsd:schema targetNamespace="
http://www.corillian.com/thingie/operations/2199/05" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsd1="http://www.corillian.com/thingie/operations/2199/05"> <!-- bogus namespaces for example -->
   <xsd:import namespace="
http://www.corillian.com/thingie/banking/domain/2199/05" schemaLocation="..\banking\BankingDomain.xsd"/>
  </xsd:schema>  
 </wsdl:types>

...and as you can see, the schemaLocation was relative.  The way I was going to force a Validation of the XML Schemas (other than physically loading each of them into memory myself) was to call .Compile().  However, it appears that the relative paths were being resolved relative to the Current AppDomain's current directory, NOT the directory the WSDL was located in!  So:

Wsdl w = new Wsdl();
w.Load(file.FullName);
foreach(XmlSchema x in w.Schemas)
{
  foreach(XmlSchemaImport i in x.Includes)
  {
    
i.SchemaLocation = Path.Combine(file.DirectoryName,i.SchemaLocation);
  
}
  x.Compile(new ValidationEventHandler(OnValidationEvent), new XmlUrlResolver());
}

Before I mess with the WSDL and Schemas, I take the directory that the WSDL file was located in and use it as a base directory for the relative schemaLocation.  Thank goodness the SchemaLocation property was not readonly! :)  Using Path.Combine has the added benefit of collapsing ".." relative paths.

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.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by SherWeb

Obscure Off-Topic Tip of the Day - How to Get Windows Explorer File Copy/Move to "Yes to All" or "No to All"

July 10, '04 Comments [2] Posted in Musings
Sponsored By

Thanks to John Elliot, how to get Windows Explorer to "Yes to All" or "No to All":

If you are copying manually in the GUI with a drag and drop or a
CTRL+C/CTRL+V then press SHIFT when you click 'Yes/No' the first time you are prompted and it will remember that as the default answer for all other files in the copy operation.

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.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by SherWeb

Creating your own Custom PatternLayout, PatternParser, and PatternConvertor with log4net

July 9, '04 Comments [4] Posted in ASP.NET
Sponsored By

Log4Net was ported over from the stunning and wonderful Log4J.  Log4Net supports a large series of Appenders that let you, via a config file, setup a series of destinations for your logging calls.  For example, without recompiling your app, you can send Logging information to a Database, or a File, or ASP.NET Tracing.  It's very flexible, and easier to use (IMHO) than the Microsoft Logging Application Block.

Additionally there's a concept called a Layout that affects the logging data and either formats or adds extra information like the thread id, username, etc.  It's very powerful because your Layout will work for and affect any Appender which makes for nice reuse.  So, for example:

    <!-- A1 is set to be a ConsoleAppender -->    
<appender name="A1" type="log4net.Appender.ConsoleAppender">
<!-- A1 uses PatternLayout -->
<layout type="Corillian.Whatever.Log4NetStuff.CustomPatternLayout, Corillian.Whatever.Log4NetStuff">
<conversionPattern value="%-4timestamp [%thread] %-5level %logger %ndc - %message%newline %G" />
</layout>
</appender>

Here's we're outputing the message, as well as timestamp and thread info. 

I wanted to add my OWN CustomPatternLayout and add additionally %whatever tokens.  This is done all the time in Log4J.

HOWEVER, in the log4net source, there's a function "FinalizeConverter" in PatternParser.cs that you need to override when you derive you own CustomPatternParser from PatterParser.  But, it's not marked virtual.  Crap. 

It makes total sense though, as everything in Java (where this code was ported from) is virtual and can be overridden.  They just forgot to set that log4net 1.2-beta8.  That's why it's beta. :)

So, if you make this change in PatternParser line 237:

protected virtual void FinalizeConverter(char c)

...then you'll be all set to do something like this:

public class CustomPatternLayout : log4net.Layout.PatternLayout
{
  
public SecurityPatternLayout() : this(log4net.Layout.PatternLayout.DEFAULT_CONVERSION_PATTERN){ //whatever }
  public SecurityPatternLayout(string pattern) : base(pattern){ //whatever }
  protected override PatternParser CreatePatternParser(string pattern)  //where we override and return US as a PatternParser
  {
    return new SecurityPatternParser(pattern == String.Empty ? log4net.Layout.PatternLayout.DEFAULT_CONVERSION_PATTERN : pattern);
  }
}

public class SecurityPatternParser : log4net.helpers.PatternParser
{
 
public SecurityPatternParser(string pattern) : base(pattern){}
 protected override void FinalizeConverter(char c)
 {
  if (c == 'G') //where 'G' is your custom token like %G in the format string
 
{
   
AddConverter(new SecurityPatternConvertor()); //Where we add ourselves into the mix, since our token (%G) appears in the format string
 
}
  else base.FinalizeConverter(c);
}

public class SecurityPatternConvertor : log4net.helpers.PatternConverter
{
 
public SecurityPatternConvertor() : base(){}
 
public SecurityPatternConvertor(FormattingInfo formattingInfo) : base(formattingInfo) {}
 
 override protected string Convert(LoggingEvent loggingEvent)
 
{
    
return MessWithThisMessage(loggingEvent.RenderedMessage); //Where we mess with the string however we decide %G should affect it
  
}
}

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.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by SherWeb

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.