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
17. January 2012 20:22
Who would even thought this post would come from me, but today I saw a tweet that made me very, very unhappy… It was something like this (don’t want to quote people by name, everyone has a right to an opinion):
“How nice, my view models are completely auto generated…”
Let me explain (or at least try to) why bothers me so much about this. MVVM is a good pattern, so good that I decided to write a framework for it (called Catel). I like it and use it in all my applications. Below are the reasons why I like MVVM:
- Testable view logic (because you do not use any UI elements in your VM)
- Separation of concerns
- Protection of your models
- It’s it the pattern of the century!
The last reason is nonsense, but it seems that everyone is just using MVVM because it is MVVM. The reason why you *should* be using MVVM is because you are writing logic of a view or to protect a model.
As soon as you see words as “auto-generated”, “automatic”, etc in a view model, it is WRONG. And why? Because you can bind directly to the model as well (because that is all an “auto-generated” VM can do), saves you a lot of performance. One on one bindings which create an additional layer called view model is not fancy and should be avoided at all cost.
So, think again why you have started using MVVM and keep asking yourself the following questions:
- Do I want to be able to unit test my view logic and am I actually doing that?
- Do I want a clean separation of concerns (and is it separated, or am I using DependencyObject or any other UI elements in my view model)?
- Do I want to protect my models by exposing only a set of the properties to a view?
If the answer to all those questions is no, stop using MVVM. Seriously, just code everything in your code behind, add dependency properties to your “view model” and do something like this:
1: public MyView()
2: {
3: InitializeComponent();
4:
5: DataContext = this;
6: }
f875c9c4-09a4-4980-a83b-f1b16834b682|3|5.0
Tags: mvvm
MVVM
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.