Greater Heights Ltd

Azure & .net Software Consultants

Remove magic strings from Breeze.js queries with T4 and typescript.

If you are familiar with Breeze.js you will be familiar with the following style of statement.
var manager = new breeze.EntityManager('api/northwind');

var query = new breeze.EntityQuery()
    .from("Employees");
manager.executeQuery(query).then(function(data){
    ko.applyBindings(data);
}).fail(function(e) {
    alert(e);  
});

There are a number of problems with this statement.

  1. How do we know that we have an entity set called “Employees”.
  2. What if the developer spells it wrong?
  3. How do we keep this in line with our EntityFramework entities?
To solve both these problems I have written a T4 template to generate the typescript module that exports a class for each entity in our EntityFramework context. I cannot claim the contents of the template are entirely my work the code for the FindClasses function comes from this stackoverflow post.
<#@ template language="C#" debug="false" hostspecific="true" #>
<#@ output extension=".ts" #>
<#@ assembly name="System" #>
<#@ assembly name="System.Core" #>
<#@ assembly name="System.Data.Entity" #>
<#@ assembly name="EnvDTE" #>
<#@ import namespace="EnvDTE" #>
<#@ import namespace="System" #>
<#@ import namespace="System.Collections" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Reflection" #>

<#
var targetNamespace = "GreaterHeights.Test.Domain";

    IServiceProvider serviceProvider = (IServiceProvider)this.Host;
    DTE dte = serviceProvider.GetService(typeof(DTE)) as DTE;  
    var project = dte.ActiveDocument.ProjectItem.ContainingProject;
#>
module greaterheights.constants{

<#


 foreach (CodeClass c in FindClasses(project, targetNamespace, "")) { #>
    export class <#= c.Name #>Constants {

       public static <#= c.Name #>Entity : string = '<#= c.Name #>'; 
<#     var properties = c.Members.OfType<EnvDTE.CodeProperty>()
           .Where(p => p.Access.HasFlag(vsCMAccess.vsCMAccessPublic))
           .OrderBy(p => p.Name);
       foreach (var prop in properties) { 
#>
       public static <#= prop.Name #> : string = '<#= prop.Name #>'; 
<#     } #>

   }

<# } #>

}

<#+ List<CodeClass> FindClasses(Project project, string ns, string className) {
        List<CodeClass> result = new List<CodeClass>();
        FindClasses(project.CodeModel.CodeElements, className, ns, result, false);
        return result;
    }

    void FindClasses(CodeElements elements, string className, string searchNamespace, List<CodeClass> result, bool isNamespaceOk) {
        if (elements == null) return;
        foreach (CodeElement element in elements) {
            if (element is CodeNamespace) {
                CodeNamespace ns = element as CodeNamespace;
                if (ns != null) {
                    if (ns.FullName == searchNamespace)
                        FindClasses(ns.Members, className, searchNamespace, result, true);
                    else
                        FindClasses(ns.Members, className, searchNamespace, result, false);
                }
            } else if (element is CodeClass && isNamespaceOk) {
                CodeClass c = element as CodeClass;
                if (c != null) {
                    if (c.FullName.Contains(className))
                        result.Add(c);

                    FindClasses(c.Members, className, searchNamespace, result, true);
                }
            }
        }
    }

    #>
Simply add this template to the scripts directory of your web project, replace the targetNamespace with the namespace of your EntityFramework project and select the “Run Custom Tool” context menu for the template. The generated typescript file should look something similar to this.
module greaterheights.constants{

    export class Employee {

       public static EmployeeEntity : string = 'Employees'; 
       public static EmailAddress : string = 'EmailAddress'; 
       public static Forename : string = 'Forename'; 
       public static Id : string = 'Id'; 
       public static Surname : string = 'Surname'; 

   }
}
Now simply reference the file in your Typescript breeze data service and you can create the same query without magic strings.
var manager = new breeze.EntityManager('api/northwind');

var query = new breeze.EntityQuery()
    .from(greaterheights.constants.EmployeeEntity);
manager.executeQuery(query).then(function(data){
    ko.applyBindings(data);
}).fail(function(e) {
    alert(e);  
});
Loading