In questi giorni di calma apparente mi sto guardando un po' di RubyOnRails. In questo framework ho trovato cose interessanti. Una tra le tante è la possibilità di definire nel Model dei metodi in modo tale che vengano invocati prima e dopo una determinata azione. Es. è possibile indicare un metodo come azione before_save in modo che venga eseguito prima del metodo save.
Sono presenti parecchie possibilità tra cui:
Maggiori info a riguardo le trovate qui.
Ora mi sono chiesto come è possibile avere una funzionalità simile in C#. Ho aperto VisualStudio è sono giusto a questa soluzione. E' solo una prima bozza, sicuramente si può fare di meglio!
Partiamo per esempio da un piccolo pezzo di codice che salva un oggetto Task così:
1: Task task = new Task(1, DateTime.Today, "Demo task");
2: IModel model = new TaskModel();
3: model.Save(task);
ora quello che vorrei ottenere è la possibilità di eseguire un metodo prima ed uno metodo dopo il Save. La prima soluzione sfrutta una mia classe Hook in cui ho definito un metodo che accetta come parametri il metodo da invocare, il parametro per questo metodo e successivamente 2 delegate ai metodi che voglio eseguire come before_save e after_save:
1: public class Hook
2: {
3: public delegate void Action<A>(A a);
4:
5: public static void Do<A>(Action<A> action, A a, Action beforeAction, Action afterAction)
6: {
7: beforeAction();
8: action(a);
9: afterAction();
10: }
11: }
Poi viene usata così:
1: Task task = new Task(1, DateTime.Today, "Demo task");
2: IModel model = new TaskModel();
3: Hook.Do(model.Save, task, model.ActionsBeforeSave, model.ActionsAfterSave);
Partendo da questa prima soluzione ho cercato di sfruttare l'uso deglil attributi in modo tale da rendere il mio codice più semplice e leggibile. La seconda soluzione sfrutta quindi oltre ai delegate anche gli attributi da me creati Before e After con cui viene decorato il metodo Save
1: public class TaskModel
2: {
3: readonly Storage _storage;
4: public TaskModel()
5: {
6: _storage = new Storage();
7: }
8: [Before("ActionsBeforeSave"), After("ActionsAfterSave")]
9: public void Save(Task task)
10: {
11: _storage.SaveOrUpdate(task);
12: }
13: public void ActionsBeforeSave()
14: {
15: //_storage.Connect();
16: Console.WriteLine("Action before save....");
17: }
18: public void ActionsAfterSave()
19: {
20: //_storage.Disconnect();
21: Console.WriteLine("Action after save....");
22: }
23: }
Poi sulla classe Hook ho aggiunto un overload di Do che sfrutta l'uso dei gli attributi
1: public class Hook
2: {
3: public delegate void Action<A>(A a);
4:
5: public static void Do<A>(Action<A> action, A a)
6: {
7: Invoke(action, typeof(BeforeAttribute));
8: action(a);
9: Invoke(action, typeof(AfterAttribute));
10: }
11:
12: private static void Invoke(Delegate action, Type type)
13: {
14: object[] attributes = action.Method.GetCustomAttributes(type, true);
15: ActionAttribute attribute = (ActionAttribute)attributes[0];
16: MethodInfo method = action.Target.GetType().GetMethod(attribute.Action);
17: method.Invoke(action.Target, null);
18: }
19: }
che poi nel viene usata così:
1: Task task = new Task(1, DateTime.Today, "Demo task");
2: IModel model = new TaskModel();
3: Hook.Do(model.Save, task);
Per concludere l'utilizzo del metodo Do della classe Hook è un pò primitivo, andrebbe migliorato in modo che si possa invocare direttamente il metodo Save del model (come l'esempio all'iniziale) senza dove esplicitamente usare Hook.Do(...) . Non dovrebbe essere difficile, magari usando un Decorator, potrer migliorare anche questo aspetto, ma questo è un'altro post...