Tuesday, 18 December 2012

Updating Enterprise Logging Block config dynamically

We have a ClickOnce application that is using the Microsoft Enterprise Logging Block, its fairly easy to set up the blog from XML in the app.config, however we wanted out log path to be dynamic so that we could change it at runtime.

Here is our sample app.config.

  <!-- =================================== -->  
  <!-- logging configuration        -->  
  <!-- =================================== -->  
  <exceptionHandling>  
   <exceptionPolicies>  
    <add name="LoggingPolicy">  
     <exceptionTypes>  
      <add name="All Exceptions" type="System.Exception" postHandlingAction="ThrowNewException">  
       <exceptionHandlers>  
        <add name="Logging Exception Handler" type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Logging.LoggingExceptionHandler, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Logging"  
         logCategory="General" eventId="100" severity="Error" title="Enterprise Library Exception Handling"  
         formatterType="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.TextExceptionFormatter, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling"  
         priority="0" />  
       </exceptionHandlers>  
      </add>  
     </exceptionTypes>  
    </add>  
   </exceptionPolicies>  
  </exceptionHandling>  
  <loggingConfiguration name="" tracingEnabled="true" defaultCategory="General"  
   logWarningsWhenNoCategoriesMatch="false">  
   <listeners>  
 <!--  
    <add name="Rolling Flat File Trace Listener" type="Microsoft.Practices.EnterpriseLibrary.Logging.TraceListeners.RollingFlatFileTraceListener, Microsoft.Practices.EnterpriseLibrary.Logging"  
     listenerDataType="Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.RollingFlatFileTraceListenerData, Microsoft.Practices.EnterpriseLibrary.Logging"  
     fileName="./Logs/Client.log" footer="" formatter="Text Formatter"  
     header="" rollSizeKB="20000" />  
 -->      
    <add name="XML Trace Listener" type="Microsoft.Practices.EnterpriseLibrary.Logging.TraceListeners.XmlTraceListener, Microsoft.Practices.EnterpriseLibrary.Logging"  
     listenerDataType="Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.XmlTraceListenerData, Microsoft.Practices.EnterpriseLibrary.Logging"  
     fileName="trace-xml.log" traceOutputOptions="None" filter="Verbose" />  
    <add name="Event Log Trace Listener" type="Microsoft.Practices.EnterpriseLibrary.Logging.TraceListeners.FormattedEventLogTraceListener, Microsoft.Practices.EnterpriseLibrary.Logging"  
     listenerDataType="Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.FormattedEventLogTraceListenerData, Microsoft.Practices.EnterpriseLibrary.Logging"  
     source="Application Error" formatter="Text Formatter" log="Application" filter="Information" />  
   </listeners>  
   <formatters>  
    <add type="Microsoft.Practices.EnterpriseLibrary.Logging.Formatters.TextFormatter, Microsoft.Practices.EnterpriseLibrary.Logging"  
     template="{timestamp(local)} : {severity} : {keyvalue(namespace)} :{message}&#xA;"  
     name="Text Formatter" />  
   </formatters>  
   <categorySources>  
    <add switchValue="information" name="General">  
     <listeners>  
 <!--     <add name="Rolling Flat File Trace Listener" />-->  
      <add name="Event Log Trace Listener" />  
     </listeners>  
    </add>  
   </categorySources>  
   <specialSources>  
    <allEvents switchValue="All" name="All Events" />  
    <notProcessed switchValue=
"All" name="Unprocessed Category">  
     <listeners>  
      <add name="Rolling Flat File Trace Listener" />  
     </listeners>  
    </notProcessed>  
    <errors switchValue="Verbose" name="Logging Errors &amp; Warnings">  
     <listeners>  
      <add name="Rolling Flat File Trace Listener" />  
      <add name="Event Log Trace Listener" />  
     </listeners>  
    </errors>  
   </specialSources>  
  </loggingConfiguration>  

We can then dynamically update the config to add out new path :-

     private static DictionaryConfigurationSource ConfigureLoggingLocationForClickOnce()  
     {  
       string logFileName = @".\Logs\Client.log";  
       if (ApplicationDeployment.IsNetworkDeployed)  
       {  
         logFileName = ApplicationDeployment.CurrentDeployment.DataDirectory + @"\Logs\Client.log";  
       }  
   
       //Create the config builder for the Fluent APIvar   
       var source = new FileConfigurationSource(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile);  
       ConfigurationSourceBuilder configBuilder = new ConfigurationSourceBuilder();  
   
       //Get the existing logging config section           
       var logginConfigurationSection = (LoggingSettings)source.GetSection("loggingConfiguration");  
   
       logginConfigurationSection.RevertImpersonation = false;  
       var _rollingFileListener = new RollingFlatFileTraceListenerData("Rolling Flat File Trace Listener", logFileName, "", "",  
                20000, "MM/dd/yyyy", RollFileExistsBehavior.Increment,  
                RollInterval.Day, TraceOptions.Callstack | TraceOptions.None,  
                "Text Formatter", SourceLevels.Information);  
   
   
       _rollingFileListener.MaxArchivedFiles = 1;  
   
       //Add trace listener to current config  
       logginConfigurationSection.TraceListeners.Add(_rollingFileListener);  
   
       //Configure the category source section of config for rolling file  
       var _rollingFileCategorySource = logginConfigurationSection.TraceSources.Get("General");  
   
       //Must be named exactly the same as the flat file trace listener above.  
       _rollingFileCategorySource.TraceListeners.Add(new TraceListenerReferenceData("Rolling Flat File Trace Listener"));  
   
       //Add category source information to current config  
       logginConfigurationSection.TraceSources.Add(_rollingFileCategorySource);  
   
       //Add the loggingConfiguration section to the config.  
       configBuilder.AddSection("loggingConfiguration", logginConfigurationSection);  
   
       //Required code to update the EntLib Configuration with settings set above.  
       var configSource = new DictionaryConfigurationSource();  
       configBuilder.UpdateConfigurationWithReplace(configSource);  
   
       //Set the Enterprise Library Container for the inner workings of EntLib to use when logging  
       EnterpriseLibraryContainer.Current = EnterpriseLibraryContainer.CreateDefaultContainer(configSource);  
   
       return configSource;  
     }  
   

Finally if using Unity and Prism you can setup both loggers like so:-


       this.Container.AddNewExtension<EnterpriseLibraryCoreExtension>();  
   
       var configurator = new UnityContainerConfigurator(Container);  
       // Read the configuration files and set up the container.  
       var configSource = ConfigureLoggingLocationForClickOnce();  
       EnterpriseLibraryContainer.ConfigureContainer(configurator, configSource);  
   
       // Set up logging with both IOC containers...  
       var oldLogger = EnterpriseLibraryContainer.Current.GetInstance<LogWriter>();  
       if (oldLogger != null)  
         oldLogger.Dispose();  
         
       var newLogger = Container.Resolve<LogWriter>();  
       EnterpriseLibraryContainer.Current = ServiceLocator.Current;  
   
   
       this.Container.RegisterType<ILoggerFacade, LoggingService>(new ContainerControlledLifetimeManager());  
       LoggingService logger = (LoggingService)this.Container.Resolve<ILoggerFacade>();  
       this.Container.RegisterInstance<ILoggingService>(logger, new ContainerControlledLifetimeManager());  
       this.Container.AddNewExtension<EnterpriseLibraryCoreExtension>();  
   

Monday, 17 December 2012

Forcing WCF generated proxies to share POJO's

We had a recent problem at work that seemed quite tough to solve, we have multiple SOAP endpoints exposed from a Java Enterprise Edition (JEE) container. The services were generated from Java code using Axis2. The services shared many POJOs.

We wanted to consume these services in a .NET client, client proxies were generated in the standard manner but this caused an issue. We ended up with the same POJO objects converted into multiple POCO's in different namespaces on the client.

I was resorting to svcutil without any success until I tried the following approach.




Open the Reference.Svc file and add multiple endpoints to one service definition.
  <MetadataSources>  
   <MetadataSource Address="http://mycompany.com/BananaService?wsdl" Protocol="http" SourceId="1" />  
   <MetadataSource Address="http://mycompany.com/OrangeService?wsdl" Protocol="http" SourceId="2" />  
   <MetadataSource Address="http://mycompany.com/AppleService?wsdl" Protocol="http" SourceId="3" />  
   ...  
  </MetadataSources>  
  <Metadata>  
   <MetadataFile FileName="BananaService.xsd" MetadataType="Schema" ...  
   <MetadataFile FileName="BananaService.wsdl" MetadataType="Wsdl" ...  
   <MetadataFile FileName="OrangeService.xsd" MetadataType="Schema" ...  
   <MetadataFile FileName="OrangeService.wsdl" MetadataType="Wsdl" ...  
   <MetadataFile FileName="AppleService.wsdl" MetadataType="Wsdl" ...  
   <MetadataFile FileName="AppleService.xsd" MetadataType="Schema" ...  
   ...  
  </Metadata>  



Now 'Update the service Reference', and bingo one C# source file with all client proxies in in one namespace all sharing the same POCOS!

Making WPF DataGrid Keyboard friendly

The out of the box DataGrid does a great job of making available data, but the standard default config leaves a little to be desired to those stuck with a keyboard.

I aim to show you how to make it a little friendlier with the help of a custom behavior.

First we create a custom binding.
   public class DataGridListBehavior : Behavior<DataGrid>  
   {  
     private bool _endingEdit;  
     protected IList Model { get; private set; }  
     protected override void OnAttached()  
     {  
       AssociatedObject.DataContextChanged += HandleDataContextChanged;  
       AssociatedObject.MouseDoubleClick += HandleMouseDoubleClick;  
       AssociatedObject.CellEditEnding += HandleCellEditEnding;  
       AssociatedObject.PreviewKeyDown += PreviewKeyDown;  
       AssociatedObject.PreviewMouseLeftButtonDown += HandleLeftButtonDown;  
       AssociatedObject.GotFocus += CellGotFocus;  
     }  
     protected override void OnDetaching()  
     {  
       ....  

This allows up to hook important grid events and add in our custom code.

Most of the work is done by CellGotFocus().

     private void CellGotFocus(object sender, RoutedEventArgs args)  
     {  
       DataGridCell gridCell = args.OriginalSource as DataGridCell;  
       if (gridCell != null)  
       {  
         DataGridHelper.EnterEdit(gridCell, AssociatedObject);  
       }  
     }  

Inside here we do the major work of ensuring we enter into the cell then focus the control automatically.

     public static void EnterEdit(DataGridCell gridCell, DataGrid grid, bool bFocus = true)  
     {  
       if (gridCell != null && !gridCell.IsEditing)  
       {  
         // enables editing on single click  
         if (!gridCell.IsFocused)  
           gridCell.Focus();  
         if (!gridCell.IsSelected && grid.SelectionUnit == DataGridSelectionUnit.Cell)  
           gridCell.IsSelected = true;  
         grid.BeginEdit();  
         if (bFocus)  
         {  
           var control = FindChildElement<Control>(gridCell);  
           if (control != null)  
           {  
             control.Focus();  
           }  
         }  
       }  
     }  

You can get the code here.