Showing posts with label clean code. Show all posts
Showing posts with label clean code. Show all posts

Wednesday, 27 April 2011

AOP - caching with postsharp

Aspect oriented programming is a nice way of keeping your code clean.  Essentially what it allows us to do,  is place various attributes on a method of class declaration which can perform various actions for us without cluttering up our logic.

The example below has a cache aspect declared meaning that the data returned can be cached.

  [Cache(CacheType.Absolute, 120)]  
     public Dictionary<string, string> GetData(string rawUrl)  
     {  

Postsharp extends msbuild and as a result it does extend a compile time a little bit, but it is not obviously noticeable. Using postsharp, we can  hook into the events that happen before and after a method is called. In this case we are intercepting before a method is called.

  [Serializable]  
   public sealed class CacheAttribute : MethodInterceptionAspect  
   {  
     private readonly CacheType m_cacheType;  
     private readonly int m_expiry;  
     public CacheAttribute(CacheType cacheType, int expiry)  
     {  
       m_cacheType = cacheType;  
       m_expiry = expiry;  
     }  
     public override void OnInvoke(MethodInterceptionArgs context)  
     {  
       object value;  
       var key = GenerateKey(context);  
       if (!CacheHelper.Get(key, out value))  
       {  
         // Do lookup based on caller's logic.   
         context.Proceed();  
         value = context.ReturnValue;  
         CacheHelper.Add(value, key, m_cacheType, m_expiry);  
       }  
       context.ReturnValue = value;  
     }  
     private static string GenerateKey(MethodInterceptionArgs context)  
     {  
       var keyBuilder = new StringBuilder();  
       keyBuilder.Append(context.Method.GetHashCode());  
       foreach (var arg in context.Arguments.ToArray())  
       {  
         keyBuilder.Append(arg.GetHashCode());  
       }  
       return keyBuilder.ToString();  
     }  
   }  

Here is the CacheHelper class referenced above:

 public enum CacheType  
   {  
     Absolute,  
     Sliding  
   }  
   public static class CacheHelper  
   {  
     private static CacheItemPolicy GetCachePolicy(CacheType type, int expiry)  
     {  
       var policy = new CacheItemPolicy();  
       switch (type)  
       {  
         case (CacheType.Absolute):  
           policy.AbsoluteExpiration = DateTimeOffset.Now.AddSeconds(expiry);  
           break;  
         case (CacheType.Sliding):  
           policy.SlidingExpiration = new TimeSpan(0, 0, 0, expiry);  
           break;  
       }  
       return policy;  
     }  
     /// <summary>  
     /// Insert value into the cache using  
     /// appropriate name/value pairs  
     /// </summary>  
     /// <typeparam name="T">Type of cached item</typeparam>  
     /// <param name="o">Item to be cached</param>  
     /// <param name="key">Name of item</param>  
     /// <param name="cacheType">Cache Expiration type</param>  
     /// <param name="expiry">Expiry time in seconds</param>  
     public static void Add<T>(T o, string key, CacheType cacheType, int expiry)  
     {  
       var cacheItem = new CacheItem(key, o);  
       MemoryCache.Default.Add(cacheItem, GetCachePolicy(cacheType, expiry));  
     }  
     /// <summary>  
     /// Remove item from cache  
     /// </summary>  
     /// <param name="key">Name of cached item</param>  
     public static void Clear(string key)  
     {  
       MemoryCache.Default.Remove(key);  
     }  
     /// <summary>  
     /// Check for item in cache  
     /// </summary>  
     /// <param name="key">Name of cached item</param>  
     /// <returns></returns>  
     public static bool Exists(string key)  
     {  
       return MemoryCache.Default.Contains(key);  
     }  
     /// <summary>  
     /// Retrieve cached item  
     /// </summary>  
     /// <typeparam name="T">Type of cached item</typeparam>  
     /// <param name="key">Name of cached item</param>  
     /// <param name="value">Cached value. Default(T) if item doesn't exist.</param>  
     /// <returns>Cached item as type</returns>  
     public static bool Get<T>(string key, out T value)  
     {  
       try  
       {  
         if (!Exists(key))  
         {  
           value = default(T);  
           return false;  
         }  
         value = (T)MemoryCache.Default.Get(key);  
       }  
       catch  
       {  
         value = default(T);  
         return false;  
       }  
       return true;  
     }  
   }  

So anytime I want to add caching to a repository now, I just add this aspect (attribute) to my method. The same can be applied for concepts just as easily for logging and transaction handling.

(Neil Duncan wrote this code originally btw!)

Domain Events

We recently trialled using domain events in one of our .net applications. Domain events are basically a simple way to delegate functionality to allow you keep you code clean.

The most common use is for you to raise an event which will send an email after completing some action, but there are many more uses.

First we have a class that will handle all ensure any events for a particular action (TEvent) are processed.

 public class EventNotifier : IEventNotifier  
   {  
     public void Notify<TEvent>(TEvent @event) where TEvent : class, IDomainEvent  
     {  
       var handlers = DependencyInjector.Instance.GetAllInstances<IHandle<TEvent>>();  
       foreach (var handler in handlers)  
       {  
         handler.Handle(@event);  
       }  
     }  
   }  

All of the events for a given action will implement a simple marker interface called IHandle and by using generics we can inject the events at runtime.

 public interface IHandle<in TEvent> where TEvent : class, IDomainEvent  
   {  
     void Handle(TEvent @event);  
   }   

An event is effectively just a model which we create and pass into our event notifier.

 public class FeedbackSubmitted : IDomainEvent  
   {  
     public Feedback Feedback { get; private set; }  
     public FeedbackSubmitted(Feedback feedback)  
     {  
       Feedback = feedback;  
     }  
   }  

The class that performs the action for a given event is pretty simple. It just needs to implement the Handle method for the given type (event - which is just a class!).


 public class OnFeedbackSubmittedSendEmail : IHandle<FeedbackSubmitted>  
   {  
     private readonly IEmailTemplateRepository m_emailTemplateRepository;  
     private readonly IFeedbackEmailSettings m_mailSettings;  
     private readonly IMailService m_mailService;  
     public OnFeedbackSubmittedSendEmail(IEmailTemplateRepository emailTemplateRepository, IMailService mailService, IFeedbackEmailSettings mailSettings)  
     {  
       m_emailTemplateRepository = emailTemplateRepository;  
       m_mailService = mailService;  
       m_mailSettings = mailSettings;  
     }  
     public void Handle(FeedbackSubmitted @event)  
     {  
       var template = m_emailTemplateRepository.GetFeedbackTemplate();  
       if (template != null)  
       {  
         var feedback = @event.Feedback;  
         var message = template.CreateMessage(new MailAddress(m_mailSettings.FeedbackEmailTo),  
                                 new Hashtable  
                                   {  
                                     {"dateSubmitted", feedback.Posted},  
                                     {"firstName", feedback.FirstName},  
                                                    {"lastName", feedback.LastName},  
                                     {"email", feedback.EmailAddress},  
                                     {"phone", feedback.PhoneNumber},  
                                                       {"comment", feedback.Comment}  
                                   }  
           );  
         m_mailService.SendMail(message);  
       }  
     }  
   }  

So once we have set all this up, we can continue to add actions for the event without modifying our core business logic. It allows us to avoid having our services becoming very cluttered.

Tuesday, 26 April 2011

Model View Presenter (MVP) and Sitecore

There are various ways to try and abstract our interaction with sitecore. The two most common that we have used are the Model View Presenter (MVP)  and MVC ( Model View Controller) patterns. In this post I will talk about how to use MVP with sitecore.

FIrst of: Don't use XSLT. It is untestable.

We start of with a base user control that all our controls will extend. All we are doing here is injecting the Presenter using structure map ( abstracted away inside DependencyInjector) and then calling a common method that will exist on all presenters, Init(). It also provides some common properties that we will be using later on.

 public class MVPUserControl<TPresenter> : BaseUserControl  
     where TPresenter : MVPPresenter  
   {  
     protected override void OnInit(EventArgs e)  
     {  
       var presenter = DependencyInjector.Instance.GetDefaultInstance<TPresenter>();  
       presenter.View = this;  
       IntegrateWithPresenter(presenter);  
       presenter.Init();  
       base.OnInit(e);  
     }  
     public bool IsFirstViewing  
     {  
       get { return !IsPostBack; }  
     }  
     public Guid CurrentItemId  
     {  
       get { return Sitecore.Context.Item.ID.ToGuid(); }  
     }  
     public string CurrentItemName  
     {  
       get { return Sitecore.Context.Item.Name; }  
     }  
     public string LanguageCode { get { return "UK"; } }  
     public string RegionCode { get { return "2"; } }  
     public virtual void IntegrateWithPresenter(TPresenter presenter) { }  
   }  


In the user control ( the view) we keep it very simple and it should just be used to pass information from and to. This way we can make it conform to an interface and as a result we can mock it using different frameworks.

The interface:

 public interface IView : IMVPView  
   {  
     string SectionTitle { set; }  
     IList<FaqItem> Questions { set; }  
   }  


The Implementation:

 public partial class FaqSection : MVPUserControl<Presenter>, IView  
   {  
     public string SectionTitle { protected get; set; }  
     public IList<FaqItem> Questions  
     {  
       set  
       {  
         rptFaqs.DataSource = value;  
         rptFaqs.DataBind();  
       }  
     }  
   }  

As you probably saw from our base user control, our presenter get magically wired up and it calls the Init() method when loaded.

The Presenter base class:

 public class MVPPresenter<TView> : MVPPresenter where TView : IMVPView  
   {  
     public new TView View  
     {  
       get { return (TView)base.View; }  
       set { base.View = value; }  
     }  
     protected internal override void CheckViewType(object view)  
     {  
       Type viewType = view.GetType();  
       Type allowedType = typeof(TView);  
       if (viewType != allowedType && allowedType.IsAssignableFrom(viewType) == false)  
         throw new InvalidOperationException(Resources.Constants.ObjectType + " " + viewType.Name + Resources.Constants.NotAllowed + " " + GetType().Name);  
     }  
   }  
   public abstract class MVPPresenter  
   {  
     private object m_view;  
     public object View  
     {  
       get { return m_view; }  
       set  
       {  
         CheckViewType(value);  
         m_view = value;  
       }  
     }  
     public virtual void Init() { }  
     protected internal virtual void CheckViewType(object view) { }  
   }  



The Presenter:


  public class Presenter :MVPPresenter<IView>  
   {  
     private readonly IFaqRepository m_repository;  
     public Presenter(IFaqRepository repository)  
     {  
       m_repository = repository;  
     }  
     public override void Init()  
     {  
       base.Init();  
       var section = m_repository.GetFaqSection(View.CurrentItemId);  
       View.Questions = section.Questions;  
       View.SectionTitle = section.SectionTitle;  
     }  
   }  
As you can see, all the asp.net and sitecore nastiness (Sitecore.Context.Item) is hidden in the view. The presenter just deals with initiating the repositories and doing some simple logic and passing it down to the view.

I can test this code. Happy days!