Writing a custom log4net appender - WcfAppender

18.April.08 – 11:49

log4net is a great logging library that comes with a couple of nice Appenders (these are the parts that actually log the stuff to a specific medium). It even has some appenders that can send the log data over the wire:

  • NetSendAppender
  • UdpAppender
  • RemotingAppender
  • SmtpAppender
  • TelnetAppender

But these appenders just did not fit my needs. E.g. when you have to bypass a firewall/proxy (probably even with authentication). Or when you want your log-data suppliers to authenticate them (in case that you don’t want anyone to log).

That’s why i wrote a custom log4net appender : WcfAppender. This appender just sends the logdata to a remote service where it is saved to a database. If you have a couple of installations out there in the wild and want to get informed about Errors in your Application, this might be very helpful. (you probably won’t send debug output to your service, but later more on that )

To implement your own log4net appender you have to either implement log4net.Appender.IAppender or inherit from log4net.Appender.AppenderSkeleton (recommended).We override the following 3 Methods from AppenderSkeleton:

public void ActivateOptions();

protected void Append(log4net.Core.LoggingEvent loggingEvent);

protected void OnClose();

log4net cares about filling public Properties with the values provided in your log4net config. Therefore the config for our appender will look like this:


 
 <param name=”QueueSize” value=”3″/>
 
 <param name=”FlushLevel” value=”ERROR”/>
 
 <param name=”SecurityMode” value=”None”/>
 <param name=”UseDefaultWebProxy” value=”false”/>
 
 <param name=”Username” value=”myUsername”/>
 
 <param name=”Password” value=”myPassword”/>
 
 <param name=”RemoteAddress” value=”http://localhost:8731/WcfAppenderService/”/>

We have to pass those parameters to the appender via the log4net config instead of the normally used system.servicemodel config stuff because libraries (i.e. DLLs) cannot have their own config files and you probably don’t want your application to include System.ServiceModel stuff. The Wcf client gets configured in code with the parameters supplied in this log4net config.
Because WCF communication involves some overhead, we don’t want the appender to send all loggingevents immediately when they arrive. Therefore we just add the logging event to a local queue and flush it if the queue reaches a given size. (or we get an event with a log level above the treshold we supplied in configuration).
E.g. we get a lot of warnings, but as they are not that important we save them locally till we reach the maximum queue size or till we get an error or fatal message. In that case we flush the queue immediately to our wcf service.
As IAppender.Append() is called in a synchronous way we don’t want any lengthy operations in there. Thats why i called the FlushQueue() call of the wcf appender asynchronously with a System.Action delegate.

We also have to flush the queue in the overridden OnClose() method so that we won’t loose logging events when our application closes.On the Service-Side we create a little database that we access via LinqToSql:

The Application table is there to support different Applications (for test purposes this is just set to the root-namespace of the app using the wcf appender).
The service offers the following methods:

  [OperationContract(Name="AppendLoggingEvent",IsOneWay=true)]
  void Append(LoggingEventContainer loggingEvent);

  [OperationContract(Name = "AppendLoggingEvents",IsOneWay=true)]
  void Append(List<LoggingEventContainer> loggingEvents);

We have to define a unique operation name because you cannot use overloaded methods with webservice-operations the normal way. And as we don’t need the service to return information we can mark the operations as OneWay calls.

To use the appender in your project, just add a reference to WcfAppender.dll, add the appender to your log4net configuration and add it to your log4net root:

 
  
   <root>
    <level value=”WARN”/>
    <appender-ref ref=”wcfappender”/>
   </root>
  
 

The sample solution is attached below, but be careful. This appender is maybe not suitable for production use, so use it at your own risk.

Download: WcfAppender.zip

Writing a custom SettingsProvider - SqlCeSettingsProvider

18.April.08 – 9:52

Visual Studio offers a nice designer for creating/editing settings for your Application. Accessing those settings via the static Settings class is a breeze and the configuration method of choice for small applications (i wouldn’t use it in larger LOB applications though).
But i was never happy how Microsoft solved the way they separated User and Application settings. It’s actually a nice idea to store the settings a user can change in a seperate (user owned) directory. But sometimes this isn’t just what you want. Why not storing the settings in a local database while still using the Visual Studio Designer for creating your settings?

I decided to write a small custom SettingsProvider that uses a Sql CE 3.5 Database to store all your settings. (that way it was nearly no work to synchronize the users settings (using the new Sync Framework) with a server. (This may look similar to Asp.Net client application services)

Let’s start. As you want to store your settings (at least some of them) strong typed, our database table contains fields for Name, Value and Type (this is actually a MyType.GetType().ToString() representation of a type).

To write your own SettingsProvider you have to inherit from System.Configuration.SettingsProvider. Therefore you have to implement the following Methods:

public SettingsPropertyValueCollection GetPropertyValues(SettingsContext context,SettingsPropertyCollection collection); 

public void SetPropertyValues(SettingsContext context, SettingsPropertyValueCollection collection);

public string ApplicationName { get; set; } 

In GetPropertyValues() we have to return a collection of SettingsPropertyValues (which we read from the local database via Linq). As we don’t save the values strong typed in the database, we have to convert them back to the corresponding type. The snippet below shows how you can read the values from database, convert them to the type you want and save them in a temporary IDictionary:

using (var db = new SqlConfigDemoDb(connStr))
{
  var q2 = from c in db.Configuration select c;
  foreach (Configuration conf in q2)
  {
    object v = Convert.ChangeType(conf.Value, Type.GetType(conf.Type));
    sqldata.Add(conf.Name, v);
  }
}

In SetPropertyValues() you have to write all the SettingsPropertyValue’s supplied in the collection back to the database. I.e. that you just check for a record with the corresponding name. If you find one, just change its value. If there is no record with the name, we just create a new one. (That is what is actually done when you add new Settings in the Visual Studio Designer).
When you have processed alls SettingsPropertyValues in the collection, submit your changes to the database and you are done. We are nearly finished. The last thing we have to do is adding the SettingsProviderAttribute to our Settings class. Open your Settings.settings file in Visual Studio and click the “View Code” button in the settings designer and add the following attribute:

//...
[System.Configuration.SettingsProvider(typeof(SqlConfigDemo.Config.SqlCeSettingsProvider))]
internal sealed partial class Settings {
//...

Now you can work with your Settings as usual. Access them via the static Default instance, call the Save() method to persist changes and add new Settings with the Visual Studio Settings designer. But don’t forget that you cannot change Application scope settings. At runtime you can only change User scoped settings.
The designer still edits the app.config file, but you don’t need it anymore as the settings are read from the database.
I made a small demo app showing the current Settings in a PropertyGrid. You can change the user scope settings, close the application and see the changes when you start the next time (as i call Save() when the application closes. Code is attached below.


Download SqlCeConfigDemo.zip

Lighttpd and ClickOnce

10.March.08 – 21:35

Although it might seem that you can only use ClickOnce installers with IIS, it’s not true, as all you need to do is set some mime-types in your lighttpd configuration: 

mimetype.assign = (
".application" => "application/x-ms-application",
".manifest" => "application/x-ms-manifest",
".deploy" => "application/octet-stream"
)

This should actually work with any other webserver, as long as you’re able to configure mime-types.

VMWare Fusion WPF issues

10.March.08 – 20:53

I wrote about a WPF problem i had in an earlier post.When i installed a new Virtual Machine with XP SP2 and VS2008, i encountered the same problem. So i realized that this just couldn’t be a Microsoft related issue. I realized that this behavior started when i enabled DirectX support in VMWare Fusion (Yes, i admit. I’m a .NET developer working on a Macbook Pro with OS X 10.5)

I know that VMWare Fusion didn’t support Aero in their first version, but as Vista works perfectly in my VM (with DirectX support enabled) i didn’t thought of this being an issue.

When i turned off DirectX support for my Virtual Machines, WPF worked as expected….Ticket closed.

Update: This post was originally targeted at VMWare Fusion 1.1. I recently tested their 2.0 release and it seems that VMWare has fixed some the problems when displaying WPF Applications. However, (at least on XP SP2) when using normal wpf applications (no excessive 3D stuff) it feels somewhat slower and has more delays compared to software-only rendering in Vmware fusion 1.1. And some of my applications just show empty windows when 3D acceleration is turned off. Conclusion: VMWare Fusion is still a mess for WPF developers.

The only solution that works for me so far is disabling hardware rendering for all WPF apps (as it is slow when 3d support is enabled and NOT USABLE when 3d support is disabled withing vmware fusion.).
To disable hardware rendering create (or modify) the following registry Dword:

HKEY_CURRENT_USER\SOFTWARE\Microsoft\Avalon.Graphics\DisableHWAcceleration

and set it to 1 to disable.

A lot of data…

5.March.08 – 10:04

Yesterday i received a mail from my Hoster telling me that i exceeded my disk quota:

Sehr geehrte(r) Joachim Kerschbaumer,

eine routinemäßige Überprüfung hat ergeben, dass Sie ihren Speicherplatz bei uns seit über einer Woche überschritten haben.

Domain: joachim.at
Erlaubter Speicherplatz: 5000 MB
Verwendeter Speicherplatz: 17592186044432.65 MB 

Eine nähere Aufschlüsselung des Speicherplatzverbrauches finden Sie im Kundenlogin …

I didn’t knew that the few files i host there exceed 17 Exabyte.;)

Strange WPF runtime behavior

3.March.08 – 10:10

Recently i noticed a really strange behavior in one of my development VM’s. WPF is completely corrupted. There are no error messages or complaints at all. I cannot really describe what’s going on, but the screenshots may be self-explaining. Even the VS2008 WPF designer doesn’t work correctly anymore. Reinstalling .NET (2.0, 3.0, 3.5) doesn’t solve this issue. Even third-party WPF apps (e.g. kaxaml) look really…. strange.

Visual Studio 2008 WPF Property Grid
Visual Studio 2008 WPF Property Grid

Visual Studio 2008 WPF Designer Zoom Control
Visual Studio 2008 WPF Designer Zoom Control

Kaxaml User Interface
Kaxaml User Interface

I hope to find a solution because i don’t wanna install the machine over again.

CurrentCulture = CultureInfo.CreateSpecificCulture(”en”);

3.March.08 – 9:47

Due to the fact that some of my post may be of interest to some people that do not speak german, i decided to switch my blog to english. I hope that everyone appreciates this ;)

Guidance Automation Extensions Update!!

17.February.08 – 18:03

Da ich durch die side-by-side installation von VS2008 und VS2005 doch einige Probleme mit der SCSF und WSSF hatte (funktionierte nur auf VS2005) gibts nun endlich support für die Guidance Automation Extensions für VS2008 UND VS2005 auf einmal. Zusätzlich fällt nun endlich die notwendige Deinstallation der Vorgängerversion weg. Danke!

http://blogs.msdn.com/agile/archive/2008/02/15/gax-gat-february-2008-final-release.aspx

update: Die Feb CTP der WSSF funktioniert anscheinend imemr noch nicht mit GAX 1.4 und einer side-by-side installatino von VS2005 und VS2008 :( 

Workflow Activities und ActivityDesigner

15.February.08 – 15:58

Dass man den Workflow Desiger den man aus Visual Studio her kennt auch in eigenen Projekten verwenden kann ist an sich schon eine tolle Sache. Wenn man jetzt aber eine toll Applikation an Kuden verkaufen will, dann sollte alles einen einheitlichen Optischen Eindruck machen.Wer Anwendern die Möglichkeit geben will selbst Workflows zu definieren, sich nicht die Arbeit antun will einen eigenen Designer dafür zu schreiben, aber nicht mit der Optik der Activities zufrieden  ist, dem sei geholfen.Dank System.Workflow.ComponentModel.Design.ActivityDesigner kann man das optische Erscheinungsbild von Activities im Designer auf sehr angenehme Art und Weise anpassen. Leider muss man dafür auf GDI+ zurückgreifen um die Activities zu “zeichnen”. Es wäre wünschenswert wenns schon einen WPF Workflow Designer geben würde… dann würde die Sache mit der optischen Anpassung von Activities schon gleich anders aussehn.Nichts desto trotz kann man sich trotzdem ganz ahnsehnliche Activities zaubern.  

 Werde in kürze mehr berichten (code muss nur noch etwas refactored werden ;)  

WPF Object Intersection and Animation

2.February.08 – 18:58

Um ein bisschen mit WPF und XAML zu spielen hab ich mich ein bisschen mit Intersection und Combination von WPF Elementen beschäftigt. Da System.Windows.Media.CombinedGeometry leider immer nur 2 Objekte auf einmal miteinander verknüpfen kann(egal ob via Exclude, Xor, Union oder Intersect). Um eine CSG-ähnliche Konstruktion von Custom Path-Objekten zu erzeuge muss man die CombinedGeometry-Operationen also schachteln.Intersect.xamlIn diesem Beispiel habe ich einfach ein paar Ringsegmente erzeugt die sich (um der Sache ein bisschen Pepp zu verleihen) sich alle um denselben Ursprung drehen und so ein bisschen Leben in das ganze bringen.Sieht dann ungefähr so aus:

Hier der XAML Code für das Beispiel:RotWindow.xamlDann nur noch die Storyboards für die RotateTransform´s im Code Behind File via:
((Storyboard)FindResource("T1Storyboard")).Begin(this); ((Storyboard)FindResource("T2Storyboard")).Begin(this); ((Storyboard)FindResource("T3Storyboard")).Begin(this);

starten und gut.