WCF Data Services Custom Framework – Part 4

I am going to come back to Part 1 in this post and see how we can leverage the business repository classes and the T4 text generator to create automatic service points. This means we would never have to touch the service layer as it will automatically grow as we added repositories.

The first step is to create an attribute for our business repositories so that we know what our service point is called. We obviously can’t have every service point being called ‘FindAll’:

[AttributeUsage(AttributeTargets.Class)]
public class ServicePointAttribute : Attribute
{
    public ServicePointAttribute(string servicePointName)
    {
        ServicePointName = servicePointName;
    }

    public string ServicePointName
    {
        get;
        private set;
    }
}

So now we can define our repository as exposing ‘Customers’ when using it’s ‘FindAll’ method

[ServicePoint("Customers")]
public class ExtendedCustomerRepository : BusinessRepository<ExtendedCustomer>

Let us assume now that our repositories are in a separate project to the location of the service container class. This means we can leverage the power of T4 to find every repository in the repository project and expose it’s ‘FindAll’ method. We want to add new item –> Text Template to start our template, then add in the following imports:

<#@ template hostspecific="true" language="C#" #>
<#@ assembly name="System.Core.dll" #>
<#@ import namespace="System.Reflection" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Linq" #>

These are used by the code within the template system and do not show in the finished product. Now we can start typing text that will appear in the generated code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using TestingOData.Repositories;

namespace TestingOData.Server
{
    public partial class BusinessEntities
    {

Notice I’m making this a partial class that can connect to the partial class shown in the previous post. Combined these partial classes will utilise the business repositories completely with Select/Insert/Update/Delete methods. Next we create a programmatic for-loop that creates the property for each of our repositories.

<#
    List<Type> repositories = GetAllRepositoryClasses();

    foreach(Type repoType in repositories)
    {#>

    public IQueryable<<#= GetEntityName(repoType) #>> <#= GetServicePointName(repoType) #>
    {
        get
        {
            return (new <#= repoType.Name #>()).FindAll();
        }
    }

    <#}
#>

It’s a bit difficult to see but everything between the <# #> tags is run by the T4 engine. In this case we can see three methods are being called by the engine - GetAllRepositoryClasses(), GetEntityName() and GetServicePointName().

  • GetAllRepositoryClasses() – Gets every business repository class type in the repository project. It does this by loading the project’s dll.
internal List<Type> GetAllRepositoryClasses()
{
    Assembly asm = Assembly.LoadFrom("C:\\Projects\\ForFun\\TestingOData\\TestingOData.Repositories\\bin\\Debug\\TestingOData.Repositories.dll");

    return (from type in asm.GetTypes()
            where !type.IsAbstract
                  && type.GetInterface("IBusinessRepository", false) != null
            select type).ToList();
}
  • GetEntityName() – Finds the business entity that this repository actually uses. It does this by finding the Generic Argument of the repositories base type.
internal string GetEntityName(Type repositoryType)
{
    return repositoryType.BaseType.GetGenericArguments()[0].Name;
}

  • GetServicePointName() – This function finds the service name by finding the ServicePointAttribute attached to the business repository
internal string GetServicePointName(Type repositoryType)
{
    var attributes = repositoryType.GetCustomAttributes(false);

    var servicePoint = (from o in attributes
                        let type = o.GetType()
                        where type.Name == "ServicePointAttribute"
                        select new
                        {
                            Obj = o,
                            Type = type
                        }).Single();

    return servicePoint.Type.GetProperty("ServicePointName").GetValue(servicePoint.Obj, null) as string;
}

When this runs it generates code like the following:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using TestingOData.Repositories;

namespace TestingOData.Server
{
    public partial class BusinessEntities
    {
        public IQueryable<ExtendedCustomer> Customers
        {
            get
            {
                return (new ExtendedCustomerRepository()).FindAll();
            }
        }
    }
}

Which is exactly what we want – A public property that returns a Queryable and is named according to the ServicePoint attribute in the business repository and calls the FindAll method.  Now as we add business repositories we don’t have to worry about hooking them up to the service layer!

Just one small thing to look out for. In Part 1 I gave read-only permission to a single method call in the data service class – we should change this to allow read/write to ALL method calls.

config.SetEntitySetAccessRule("*", EntitySetRights.All);

Ok, cool, now we have this jamming WCF Data Service but how do we use it? In my next post I will head back to the client side and go through the queries that can be accomplished and also show how object updates/deletes work.

EDIT: There is a small issue that I should point out with this change. Because we have moved our business entities out of the same assembly as the BusinessEntities class the following line will no longer work (It’s in two places in our IUpdatable implementation):

Type t = Type.GetType(fullTypeName, true);

Instead I add a dictionary constructed by the T4 generator as a mapping from a full type name to an actual type using typeof:

private static Dictionary<string, Type> _typeNamesToTypes = null;

private static Dictionary<string, Type> TypeNamesToTypes
{
    get
    {
        if(_typeNamesToTypes == null)
        {
            _typeNamesToTypes = new Dictionary<string, Type>();

            <#
            foreach(Type repoType in repositories)
            {#>
            _typeNamesToTypes["TestingOData.Repositories.<#= GetEntityName(repoType) #>"] = typeof(<#= GetEntityName(repoType) #>);
            <#}#>
        }

        return _typeNamesToTypes;
    }
}

And the generated code:

private static Dictionary<string, Type> _typeNamesToTypes = null;

private static Dictionary<string, Type> TypeNamesToTypes
{
    get
    {
        if(_typeNamesToTypes == null)
        {
            _typeNamesToTypes = new Dictionary<string, Type>();

            _typeNamesToTypes["TestingOData.Repositories.ExtendedCustomer"] = typeof(ExtendedCustomer);
        }

        return _typeNamesToTypes;
    }
}

Now instead of using Type.GetType – a very expensive call – we do a lookup on a dictionary that uses inline typeof statements – much faster!

Type t = TypeNamesToTypes[fullTypeName];
Print | posted on Friday, 18 June 2010 6:04 PM

Feedback

# re: WCF Data Services Custom Framework – Part 4

left by Jamie at 26/06/2010 9:03 PM Gravatar
Looking forward to the Client Side posts - I found your Business Entities series really informative!

# re: WCF Data Services Custom Framework – Part 4

left by Greg K at 28/05/2011 11:10 AM Gravatar
Nice!
Title  
Name
Email (never displayed)
Url
Comments   
Please add 7 and 6 and type the answer here: