Wednesday, April 27, 2011

ASP.NET MVC View Engine with FreeMarker

FreeMarker is a quite success template engine in Java world. It is used with Apache Struts, an MVC framework, as a view engine. ASP.NET MVC is using Web Form as the default view engine. The problem is the view become a spaghetti of HTML and C# snippets fairly quickly, just like classic ASP and PHP. FreeMarker.Net is a project that ports the FreeMaker as an ASP.NET MVC view engine

The idea is compiling FreeMarker to .Net assembly with IKVM and create a wrapper to wrap .Net objects so that FreeMarker can understand.

Compiling FreeMarker
It is a strength forward process. It can be done with one command:
ikvmc freemarker.jar -target:library

Separating Necessary IKVM Libraries
Only the following libraries is required for development:
  • IKVM.OpenJDK.Beans.dll 
  • IKVM.OpenJDK.Charsets.dll
  • IKVM.OpenJDK.Core.dll
  • IKVM.OpenJDK.SwingAWT.dll
  • IKVM.OpenJDK.Text.dll
  • IKVM.OpenJDK.Util.dll
  • IKVM.Runtime.dll

Wrapping .Net Object
FreeMarker does not directly deal with the objects. Instead, it deals with the TemplateModel objects. There are a few template models to be implemented:
  • TemplateBooleanModel
  • TemplateDateModel
  • TemplateHashModel
  • TemplateMethodModelEx
  • TemplateNumberModel
  • TemplateScalarModel
  • TemplateSequenceModel
FreeMarker provides an ObjectWrapper interface to wrap the raw objects into TemplateModel.

The NetObjectModel is actually is a TemplateHashModel
public class NetObjectModel : StringModel, 
                              TemplateModel, 
                              TemplateHashModel {
        Dictionary<string, PropertyInfo> props = 
            new Dictionary<string, PropertyInfo>();
        Dictionary<string, MethodModel> methods = 
            new Dictionary<string, MethodModel>();
        Dictionary<string, ExtensionMethodModel> extensionMethods = 
            new Dictionary<string, ExtensionMethodModel>();

        public NetObjectModel(object data, 
                              NetObjectWrapper wrapper)
            : base(data, wrapper){
            var type = data.GetType();
            foreach (var p in type.GetProperties()) {
                props.Add(p.Name, p);
            }
            foreach (var m in type.GetMethods()) {
                if (!methods.ContainsKey(m.Name)) {
                    methods.Add(m.Name, 
                                new MethodModel(data,  
                                                wrapper, 
                                                m.Name));
                }
            }
        }

        public virtual TemplateModel get(string key) {
            if (props.ContainsKey(key)) {
                return wrapper.wrap(props[key].GetGetMethod()
                                              .Invoke(data, null));
            }
            else if (methods.ContainsKey(key)) {
                return methods[key];
            }
            else if (wrapper.ExtensionTypes.Count > 0) {
                if (!extensionMethods.ContainsKey(key)) {
                    extensionMethods[key] = 
                        new ExtensionMethodModel(data, 
                                                 wrapper, 
                                                 key);
                }
                return extensionMethods[key];
            }
            else {
                return TemplateModel.__Fields.NOTHING;
            }            
        }

        public virtual bool isEmpty() {
            return props.Count == 0;
        }
    }

To adapt the ASP.NET objects, three more template model are created:
  • HttpApplicationStateModel
  • HttpRequestModel
  • HttpSessionStateModel
They are similar but not in the same interface, all of them are like this:
public class HttpApplicationStateModel : NetObjectModel, 
                                         TemplateModel, 
                                         TemplateHashModel {

        public HttpApplicationStateModel(
             object data, 
             NetObjectWrapper wrapper)
            : base(data, wrapper) {
            
        }

        public override TemplateModel get(string key) {
            HttpApplicationStateBase dic = 
                data as HttpApplicationStateBase;
            if (dic.Keys.Cast<string>().Contains(key)) {
                return wrapper.wrap(dic[key]);
            }
            else {
                return base.get(key);
            }
        }

        public override bool isEmpty() {
            IDictionary<string, object> dic = 
                data as IDictionary<string, object>;
            return dic.Count == 0 && base.isEmpty();
        }
    }
View Engine
The view engine is fairly simple, it simply initialize the FreeMarker configuration.
public class FreemarkerViewEngine : VirtualPathProviderViewEngine {
        Configuration config;
        public FreemarkerViewEngine(string rootPath, 
                                    string encoding = "utf-8") {
            config = new Configuration();
            config.setDirectoryForTemplateLoading(
                new java.io.File(rootPath));
            AspNetObjectWrapper wrapper = 
                new AspNetObjectWrapper();

            config.setObjectWrapper(wrapper); 
            base.ViewLocationFormats = 
                new string[] { "~/Views/{1}/{0}.ftl" };
            config.setDefaultEncoding(encoding);
            base.PartialViewLocationFormats = 
                base.ViewLocationFormats;
        }

        protected override IView CreatePartialView(
            ControllerContext controllerContext, 
            string partialPath) {
            return new FreemarkerView(config, partialPath);
        }

        protected override IView CreateView(
            ControllerContext controllerContext, 
            string viewPath, 
            string masterPath) {
            return new FreemarkerView(config, viewPath);
        }
    }

View
FreemarkerView implements IView to render the content. It simply create the dictionary as variable bindings and invoke the FreeMarker.
public class FreemarkerView : IView{
    public class ViewDataContainer : IViewDataContainer {
        private ViewContext context;
        public ViewDataContainer(ViewContext context) {
            this.context = context;
        }

        public ViewDataDictionary ViewData {
            get {
                return context.ViewData;
            }
            set {
                context.ViewData = value;
            }
        }
    }

    private freemarker.template.Configuration config;
    private string viewPath;
    public FreemarkerView(freemarker.template.Configuration config, string viewPath) {
        this.config = config;
        this.viewPath = viewPath;
    }

    public void Render(ViewContext viewContext, 
                        System.IO.TextWriter writer) {
        Template temp = config.getTemplate(viewPath.Substring(2));
            
        Dictionary<string, object> data = 
                        new Dictionary<string, object>{
            {"model", viewContext.ViewData.Model},
            {"session", viewContext.HttpContext.Session},
            {"http", viewContext.HttpContext},
            {"request", viewContext.HttpContext.Request},
            {"application", viewContext.HttpContext.Application},
            {"view", viewContext},
            {"controller", viewContext.Controller},
            {"url", new UrlHelper(viewContext.RequestContext)},
            {"html", new HtmlHelper(viewContext, 
                            new ViewDataContainer(viewContext))},
            {"ajax", new AjaxHelper(viewContext, 
                            new ViewDataContainer(viewContext))},
        };

        Writer output = new JavaTextWriter(writer);
        temp.process(data, output);
        output.flush();
    }
}
Configuration
Configuration is as simple as adding the view engine to the view engine collections.
protected void Application_Start() {
    AreaRegistration.RegisterAllAreas();

    // ****** Optionally, you can remove all other view engines ******
    //ViewEngines.Engines.Clear();
    ViewEngines.Engines.Add(new FreemarkerViewEngine(this.Server.MapPath("~/")));

    RegisterGlobalFilters(GlobalFilters.Filters);
    RegisterRoutes(RouteTable.Routes);
}

Extension Methods
ASP.NET MVC relies on extension method quite heavily. In the NetObjectWrapper, the following code is added to find the extension methods:
public virtual void AddExtensionNamespace(string ns) {
    var types = this.ExtensionTypes;
    foreach (var a in AppDomain.CurrentDomain.GetAssemblies()) {
        try {
            foreach (var t in a.GetExportedTypes()) {
                if (!types.Contains(t) && 
                    t.IsClass && 
                    t.Name.EndsWith("Extensions") && 
                    t.Namespace == ns && 
                    t.GetConstructors().Length == 0) {
                    types.Add(t);
                }
            }
        }
        catch { }
    }
}
In the view engine's constructor, it reads the namespaces specified under system.web/pages sections in web.config:
PagesSection section = 
    (PagesSection)WebConfigurationManager
                      .OpenWebConfiguration("~/")
                      .GetSection("system.web/pages");
if (section != null) {
    foreach (NamespaceInfo info in section.Namespaces) {
        wrapper.AddExtensionNamespace(info.Namespace);
    }
}
An ExtensionMethodModel class is created to find the appropriate method to invoke:
foreach (var type in wrapper.ExtensionTypes) {
    MethodInfo method = type.GetMethod(
        methodName, 
        BindingFlags.Public | BindingFlags.Static, 
        Type.DefaultBinder, 
        argTypes, null);
    if (method != null) {
        cache.Add(key, method);
        return wrapper.wrap(method.Invoke(target, args.ToArray()));
    }
}
Localization
ResourceManagerDirectiveModel (a TemplateDirectiveModel) and ResourceManagerModel are created so that we could do something like this:
<@resource type="Freemarker.Net.MvcWeb.App_GlobalResources.PersonResource, Freemarker.Net.MvcWeb"/>

${res.Title}

ResourceManagerDirectiveModel is getting the ResourceManager from the resource and put it into res template variable:
public void execute(freemarker.core.Environment env, java.util.Map parameters, TemplateModel[] models, TemplateDirectiveBody body) {
    if (parameters.containsKey("type")) {
        TemplateScalarModel scalar = 
            (TemplateScalarModel)parameters.get("type");
        var type = Type.GetType(scalar.getAsString());
        env.setVariable("res", 
            env.getObjectWrapper()
                .wrap(type.GetProperty("ResourceManager", 
                                        BindingFlags.Static | 
                                        BindingFlags.NonPublic | 
                                        BindingFlags.Public)
                        .GetGetMethod(true)
                        .Invoke(null, null)));
    }            
}
Adding More Directives
To all more directives can be added in the future, MEF is used. The directive implementation only needs to export and implement the ImportableDirective interface.
[Export(typeof(ImportableDirective))]
    public class ResourceManagerDirectiveModel : TemplateDirectiveModel, ImportableDirective {
The view engine will import them in the constructor:
public class FreemarkerViewEngine : VirtualPathProviderViewEngine {
        Configuration config;
        [ImportMany]
        IEnumerable<ImportableDirective> directives;
        public FreemarkerViewEngine(string rootPath, 
                                    string encoding = "utf-8") {
            // initialize the config 
            // ...
            // import the extension methods' namespaces
            // ...
            // import the directives
            var dir = new DirectoryInfo(
                Path.Combine(rootPath, "bin"));
            var catalogs = dir.GetFiles("*.dll")
                .Where(o => o.Name != "freemarker.dll" && 
                           !(o.Name.StartsWith("IKVM.") || 
                           o.Name.StartsWith("Freemarker.Net")))
                .Select(o => new AssemblyCatalog(
                             Assembly.LoadFile(o.FullName))
            );
            var container = new CompositionContainer(
                new AggregateCatalog(
                    new AggregateCatalog(catalogs),
                    new AssemblyCatalog(
                      typeof(ImportableDirective).Assembly)
            ));
            container.ComposeParts(this);
        }

The source code is available in CodePlex. Please visit http://freemarkernet.codeplex.com/.

P.S.: The bonus of this project is we have not only a new view engine in ASP.NET MVC, but also a new template engine in .Net Framework. Maybe someday we could use FreeMarker to generate code instead of T4 in Visual Studio SDK.

Thursday, April 21, 2011

Caching in .Net Framework 4

We used to have caching in ASP.NET. For non-web application caching, Cache Application Block from Enterprise Library may be the choice. In .Net Framework 4, caching is baked into the library and no longer limited to ASP.NET.

After adding System.Runtime.Caching assembly as the reference in your project, we can cache the data retrieved by a web services:
using System.Runtime.Caching;
class CrmHelper{
    static ObjectCache cache = MemoryCache.Default;
//...
    string key = string.Format("{0}/{1}", 
                          "account", "address1_addresstypecode");
    MetadataService service = new MetadataService();
    service.Credentials = CredentialCache.DefaultCredentials;

    Option[] options = null;
    if (cache.Contains(key)){
        options = (Option[])cache[key];
    else {
        var picklist = (PicklistAttributeMetadata)
            service.RetrieveAttributeMetadata(
                "account", "address1_addresstypecode");
        options = picklist.Options;
        cache.Set(key, options, new CacheItemPolicy {
            AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(2)
        });
    }
//...
}
if (cache.Contains(key)) ... cache.Set(key, data) is going to be a repeating pattern. We could write an extension method to wrap it up:
public static class Extensions {
    public static T Get<T>(this ObjectCache cache, 
                           string key,
                           Func<T> retrieveFunction) {
        if (cache.Contains(key)) {
            return (T)cache[key];
        }
        else {
            var result = retrieveFunction();
            cache.Set(key, result, new CacheItemPolicy {
                AbsoluteExpiration = 
                    DateTimeOffset.Now.AddMinutes(2)
            });
            return result;
        }
    }
}
The previous code is now cleaned up like this:
using System.Runtime.Caching;
class CrmHelper{
    static ObjectCache cache = MemoryCache.Default;
//...
    string key = string.Format("{0}/{1}", 
                          "account", "address1_addresstypecode");
    MetadataService service = new MetadataService();
    service.Credentials = CredentialCache.DefaultCredentials; 

    Option[] options = cache.Get(key, () => {
        var picklist = (PicklistAttributeMetadata)
            service.RetrieveAttributeMetadata(
                "account", "address1_addresstypecode");
        return picklist.Options;
    });
//...
}
It is worth to note that the Cache Application Block from Enterprise Library is deprecating (http://msdn.microsoft.com/en-us/library/ff664753(v=PandP.50).aspx):
Caching Application Block functionality is built into .NET Framework 4.0; therefore the Enterprise Library Caching Application Block will be deprecated in releases after 5.0. You should consider using the .NET 4.0 System.Runtime.Caching classes instead of the Caching Application Block in future development.
If you only want a quick memory cache, the new caching API might already fit your needs. However, Enterprise Library provides some other implementations of caching (e.g. database, file, etc). If you want something more fancy, Enterprise Library could be an alternative.

Refereces: