Thursday, May 10, 2012

Silverlight Client Logging

The major pin in the ass for client side applications is logging. Silverlight doubles it due to its "sandbox" which blocks from saving a local copy on the client for later sending. As usual, I needed a log to trap a bug. And as usual too, the bug didn't reproduce itself on the development machine. So I came up with idea of logging the actions.
Building a log class is a simple matter:
public static class Logger
    {
        private static List<LogEntry> Entries = new List
<LogEntry>();
        private static bool _isActive = false;

        public static bool IsActive
        {
            get
            {
                return _isActive;
            }
            set
            {
                if (_isActive != value)
                {
                    if (value)
                    {
                        _isActive = true;
                    }
                    else
                    {
                        _isActive = false;
                        lock (Entries)
                        {
                            Entries.Clear();
                        }
                    }
                }
            }
        }
        public static int MinLogLevel { get; set; }
       
        public static List
<LogEntry> GetLogEntries()
        {
            List
<LogEntry> tmp;
            lock (Entries)
            {
                tmp = Entries;
                Entries = new List();
            }
            return tmp;
        }
        public static void AddEntry(int level, string message)
        {
            if ((IsActive) && (level >= MinLogLevel))
            {
                LogEntry entry = new LogEntry(level, message);
                lock (Entries)
                {
                    Entries.Add(entry);
                }
            }
        }
    }


The first thing to note is "GetLogEntries" clears the list. I did it in order to send the entries to the server only once, even if the server did not receive them. You might need to implement it in a different way.
The second thing is I relay on a helper class LogEntry. I provide my implementation of it, feel free to change it, the logic wouldn't change. Maybe a minor change to the constructor is needed. Or using the Server's LogEntry class and setting the properties inside {}.
public class LogEntry
        {
            public DateTime Stamp { get; private set; }
            public int Level { get; private set; }
            public string Message { get; private set; }
            public Guid UserID { get; private set; }
            public Guid TenantID { get; private set; }
            public string Operation { get; private set; }


            public LogEntry(int p_Level, string p_Message)
            {
                Stamp = DateTime.Now;
                Level = p_Level;
                Message = p_Message;
                StringBuilder sb = new StringBuilder();
                StackTrace stackTrace = new StackTrace();
                StackFrame[] stackFrames = stackTrace.GetFrames();
                foreach (StackFrame stackFrame in stackFrames)
                {
                    sb.AppendLine(stackFrame.GetMethod().Name);
                }
                Operation = sb.ToString();
                if (WebContext.Current.WFUser == null)
                {
                    TenantID = Guid.Empty;
                    UserID = Guid.Empty;
                }
                else
                {
                    TenantID = WebContext.Current.WFUser.TenantID;
                    UserID = WebContext.Current.WFUser.UserID;
                }
            }


            public override string ToString()
            {
                return Stamp.ToString() + " " + Message + " - " + Level.ToString() + ": " + Message;
            }
        }

Now we'll have to add a background sending utility. This implementation is the most naive one, but it works great, so we didn't bother to change it.
private bool IsActive = true;
        private void SendLog()
        {
            while (IsActive)
            {
                int CountDown = 300;
                while (IsActive && CountDown>0)
                {
                    System.Threading.Thread.Sleep(1000);
                    CountDown--;
                }
                SendToServer(Logger.GetLogEntries());
            }
        }
        public App()
        {
            InitializeComponent();
            Application.Current.Exit += Application_Exit;
            System.Threading.Thread th = new System.Threading.Thread(SendLog);
            th.Start();
        }
        private void Application_Exit(object sender, EventArgs e)
        {
            IsActive = false;
            System.Threading.Thread.Sleep(5000);
        }

This code goes to the application class. SendToServer is supposed to be a server side function (using WCF or RIA or whatever is at hand).

 That's all, now add the debug lines as Logger.AddEntry(debugLevel, message) whenever you feel like it. Just remember to set the logging level and to activate/deactivate when you need it.

Silverlight style converter

Recently we had a problem which required the ListItems inside a Listbox to use different styles. The reason was a need for image, but without the hassle of loading a PNG or JPG (GIF is not supported by Silverlight, and it was tough to teach the designers...)

In the "old world" of .NET it would look like:
foreach (ListItem item in Listbox.Items){
item.Style = new Style(item.SomeProperty);
}

Fortunately, the WPF's binding has a mechanism which does the same: the Converter.

In order to create a converter one has to declare the class as IValueConverter. This interface defines 2 methods:
Convert - the real converter.
ConvertBack - for the case of TwoWay mode.

I didn't implement it the ConvertBack, no reason, though it's quite easy.

I'll start with the conversion method itself, though it's based on a static dictionary of Styles.
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{

if(value is string)
{
Style res = known_styles[value] as Style;
return res;
} else {
return value;
}
}

A short explanation: value is an object, nothing forces it to be a string, except for my requirements from the client XAML.
known_styles is a static ResourceDictionary loaded from somewhere by someone. I.e. our little fairy was busy prior to calling the converter.

Loading the dictionary is achieved by this line:
private static ResourceDictionary known_styles = new ResourceDictionary { Source = new Uri("/MY.DLL;component/Assets/Styles.xaml", UriKind.RelativeOrAbsolute) };

Note: nothing throughout the code forces the Style to be valid to the ListItem. Remember that when trying to debug an unexplained behavior on behalf of the client.