by Geert
18. January 2012 21:13
Inside Catel, there are several secret gems. One of these gems is the possibility to audit everything that happens in the view models (property changes, events, commands, etc). This is very useful for logging, or in the case of this blog post, real-time tracking with Google Analytics.
Show me first
Below is a movie that demonstrates the example application with real-time tracking:
Cool, now show me the code
The code is pretty simple. First you need to create an implementation of the IAuditor interface. The prevent that you need to implement all methods, there is a convenience implementation in the form of the AuditorBase. For the Google Analytics implementation, we use Web-Analysis.net (also used in the Microsoft Silverlight Analytics Framework).
1: /// <summary>
2: /// Google Analytics auditor.
3: /// </summary>
4: public class GoogleAnalytics : AuditorBase
5: {
6: private readonly ApplicationTracker _appTracker;
7:
8: public GoogleAnalytics(string apiKey, string applicationName)
9: {
10: Argument.IsNotNull("apiKey", apiKey);
11: Argument.IsNotNull("applicationName", applicationName);
12:
13: _appTracker = new ApplicationTracker(apiKey, applicationName);
14: _appTracker.StartSession();
15: }
16:
17: public override void OnCommandExecuted(IViewModel viewModel, string commandName, ICatelCommand command, object commandParameter)
18: {
19: _appTracker.TrackEvent(ApplicationTrackerCategories.Command, string.Format("{0}.{1}", viewModel.GetType().Name, commandName));
20: }
21:
22: public override void OnViewModelCreated(Type viewModelType)
23: {
24: _appTracker.TrackPageView(viewModelType.Name);
25:
26: _appTracker.TrackCustomEvent("ViewModel.Created", viewModelType.Name);
27: }
28:
29: public override void OnViewModelCanceled(IViewModel viewModel)
30: {
31: _appTracker.TrackCustomEvent("ViewModel.Canceled", viewModel.GetType().Name);
32: }
33:
34: public override void OnViewModelSaved(IViewModel viewModel)
35: {
36: _appTracker.TrackCustomEvent("ViewModel.Saved", viewModel.GetType().Name);
37: }
38:
39: public override void OnViewModelClosed(IViewModel viewModel)
40: {
41: _appTracker.TrackCustomEvent("ViewModel.Closed", viewModel.GetType().Name);
42: }
43: }
Last but not least, you need to register the auditor in the AuditorManager.
1: AuditingManager.RegisterAuditor(new GoogleAnalytics(myApiKey, "Catel Analytics Example"));
Easy huh, creating real-time Google Analytics for your app
. The advantages is that you can now see exactly what your users are doing and what features of your application are used most (and thus should have a higher priority for support calls).
by Geert
13. January 2012 12:54
I like unit tests. So much that I wrote 1003 for Catel to make sure we don’t introduce bugs when changing code. We also love automation, so we use build scripts to create a new (beta) release, create a nuget package and upload the beta versions including symbols.
Anyway, enough history, let’s get to the problem. This week, I fixed an issue that as soon as a ReflectionTypeLoadException occurred, the successfully loaded types were not used by Catel either. That is wrong, so we fixed it. We always run the unit tests using Resharper and dotCover and all succeeded. However, as soon as we ran the unit tests in msbuild (which uses mstest), 3 unit tests failed. It was about this particular code:
1: public static Type[] GetAllTypesSafely(Assembly assembly, bool logLoaderExceptions)
2: {
3: Argument.IsNotNull("assembly", assembly);
4:
5: Type[] foundAssemblyTypes;
6:
7: try
8: {
9: foundAssemblyTypes = assembly.GetTypes();
10: }
11: catch (ReflectionTypeLoadException typeLoadException)
12: {
13: foundAssemblyTypes = typeLoadException.Types;
14:
15: Log.Warning("A ReflectionTypeLoadException occured, adding all {0} types that were loaded correctly", foundAssemblyTypes.Length);
16:
17: if (logLoaderExceptions)
18: {
19: Log.Warning("The following loading exceptions occurred:");
20: foreach (var error in typeLoadException.LoaderExceptions)
21: {
22: Log.Warning(" " + error.Message);
23: }
24: }
25: }
26:
27: return foundAssemblyTypes;
28: }
According to the documentation, the ReflectionTypeLoaderException.Types contains all the successfully loaded types. However, when running the unit tests (which loads more assemblies into the current AppDomain), the array contained 3 instances of null.
WTF? A successfully loaded type that is null? /*sarcasme on*/ I bet the Microsoft engineers had a good reason for this /*sarcasm off*/. Anyway, I fixed the issue by filtering out the null values. Below is the fixed code (notice the LINQ expression):
1: public static Type[] GetAllTypesSafely(Assembly assembly, bool logLoaderExceptions)
2: {
3: Argument.IsNotNull("assembly", assembly);
4:
5: Type[] foundAssemblyTypes;
6:
7: try
8: {
9: foundAssemblyTypes = assembly.GetTypes();
10: }
11: catch (ReflectionTypeLoadException typeLoadException)
12: {
13: foundAssemblyTypes = (from type in typeLoadException.Types
14: where type != null
15: select type).ToArray();
16:
17: Log.Warning("A ReflectionTypeLoadException occured, adding all {0} types that were loaded correctly", foundAssemblyTypes.Length);
18:
19: if (logLoaderExceptions)
20: {
21: Log.Warning("The following loading exceptions occurred:");
22: foreach (var error in typeLoadException.LoaderExceptions)
23: {
24: Log.Warning(" " + error.Message);
25: }
26: }
27: }
28:
29: return foundAssemblyTypes;
30: }
So, what have we learned today? Even unit tests cannot be trusted…
by Geert
29. December 2011 16:01
Recently I needed weak actions for the message mediator implementation of Catel. I looked at a few examples and most seem to do something like this:
1: public class WeakAction
2: {
3: private readonly Action _action;
4: private WeakReference _reference;
5:
6: public WeakAction(object target, Action action)
7: {
8: _reference = new WeakReference(target);
9: _action = action;
10: }
11:
12: public bool IsAlive
13: {
14: get { return (_reference != null) ? _reference.IsAlive : false; }
15: }
16:
17: public void Execute()
18: {
19: if (_action != null && IsAlive)
20: {
21: _action();
22: }
23: }
24: }
There are 2 downsides of this approach:
- It doesn’t allow static handlers
- It causes memory leaks
The problem
So if you are using such an implementation of a weak action, all your alarm bells should be ringing right now. The issue is in the storage of the Action object. The action is a method handler. To be able to call the handler, it must store the reference (as Target property on the Action class) of the target.
However, this means that there is now a reference to the target, so the target can never be garbage collected, so you are now dealing with a big issue in your application because you think that you are using weak events, but you aren’t.
The Solution
I already wrote a true weak event listener once, so this one should be easy. What you basically need to do is to write an open instance delegate, create a handler without a target for it and late-bind the handler. Sounds complex, but once you get the grip of it, it’s pretty simple. Below is the code for a non-generic Action. All this code (and the generic implementation) can be found at http://catel.codeplex.com.
1: public class WeakAction
2: {
3: public delegate void OpenInstanceAction<TTarget>(TTarget @this);
4: private Delegate _action;
5: private WeakReference _reference;
6:
7: public WeakAction(object target, Action action)
8: {
9: _reference = new WeakReference(target);
10:
11: var targetType = (target != null) ? target.GetType() : typeof(object);
12: var delegateType = typeof(OpenInstanceAction<>).MakeGenericType(targetType);
13:
14: _action = Delegate.CreateDelegate(delegateType, null, action.Method);
15: }
16:
17: public bool IsAlive
18: {
19: get { return (_reference != null) ? _reference.IsAlive : false; }
20: }
21:
22: public void Execute()
23: {
24: if (_action != null && IsAlive)
25: {
26: _action.DynamicInvoke(_reference.Target);
27: }
28: }
29: }
by Geert
29. December 2011 15:50
Recently I was working on true weak actions and wanted to do something like this:
1: public class WeakAction<TParameter>
2: {
3: public delegate void OpenInstanceGenericAction<TTarget>(TTarget @this, TParameter parameter);
4:
5: public WeakAction(object target, Action<TParameter> action)
6: {
7: var targetType = (target != null) ? target.GetType() : typeof(object);
8: var delegateType = typeof(OpenInstanceGenericAction<>).MakeGenericType(targetType, typeof(TParameter));
9:
10: _action = Delegate.CreateDelegate(delegateType, null, action.Method);
11: }
12: }
I was pretty sure I was doing it all right. In this code, I create a generic open instance handler to make sure I can invoke the method without referencing the instance itself. This allows me to write true weak actions.
Anyway, the error that pops up is:
System.ArgumentException: Cannot bind to the target method because its signature or security transparency is not compatible with that of the delegate type.
Scratching my head over and over again, for about 1 hour, something came into my mind that couldn’t be true: would it be that I have to define the TParameter first, and then the TTarget, even when the delegate uses them in another order?
The answer seemed to be yes!
Apparently, you need to specify the order of arguments. So, because the delegate is part of a class defining the first generic argument (TParameter), you need to specify that first. So, it is not the order in which they are used inside the delegate, but the order of definition.
by Geert
22. December 2011 19:11
Recently, I needed to retrieve binding information on a dependency property in a behavior. In WPF, this is all possible (of course) via the BindingOperations.GetBindingExpression method. However, the developers of Silverlight must have thought that developers are only interested in binding expressions starting from a FrameworkElement. So, this means you cannot get binding expressions from the following types while they can have bindings:
- DependencyObject
- UIElement
- Behavior
- TriggerAction
- TriggerBase
And much more. For a complete list, see this list.
Anyway, I was pretty sad that the Silverlight team decided for me that I would never need to check the binding expression inside a behavior (which I needed in this case). Anyway, below is an extension method that allows you to get the binding expression of any dependency property:
1: /// <summary>
2: /// Gets the binding expression for the specified dependency property.
3: /// </summary>
4: /// <param name="dependencyObject">The dependency object.</param>
5: /// <param name="dependencyProperty">The dependency property.</param>
6: /// <returns>
7: /// The <see cref="BindingExpression"/> or <c>null</c> if the property is not bound.
8: // </returns>
9: /// <exception cref="ArgumentNullException">The <paramref name="dependencyObject"/> is <c>null</c>.</exception>
10: /// <exception cref="ArgumentNullException">The <paramref name="dependencyProperty"/> is <c>null</c>.</exception>
11: public static BindingExpression GetBindingExpression(this DependencyObject dependencyObject, DependencyProperty dependencyProperty)
12: {
13: Argument.IsNotNull("dependencyObject", dependencyObject);
14: Argument.IsNotNull("dependencyProperty", dependencyProperty);
15:
16: return (dependencyObject.ReadLocalValue(dependencyProperty) as BindingExpression);
17: }
Of course, this extension method is included in Catel.