left-icon

T4 Succinctly®
by Nick Harrison

Previous
Chapter

of
A
A
A

CHAPTER 7

Working with Reflection

Working with Reflection


Introduction

Reflection is how we access metadata about code at run time. This opens up some wonderful dynamic runtime possibilities, but it can also be leveraged to create some exciting code generation opportunities.

In this chapter, we will look through the various types of metadata that we have available, showing how the pieces fit together. Once we have finished this survey of reflection techniques, we will tie it all together with a code generator that will load an assembly and find all of the types that are derived from ApiController. For each of these classes, we will find the actions that are exposed and create a method to automate calling that action using HttpClient.

Throughout this chapter we will add functionality to a new class called ReflectionHelper that we can use to simplify accessing eeflection data. Go ahead and a new class called ReflectionHelper in the T4Utilities project that we created in Chapter 4.

Load an Assembly

Loading an assembly can be a bit tricky. Without proper care, you can wind up locking the assembly that you are trying to load. If you are reflecting on an assembly being built by your current solution, you could be stuck having to shut down Visual Studio every time you run your template.

We have a couple of options to avoid this. The most straightforward approach is to read the contents of the assembly into a Byte array, and then load the array. With this approach there is no file to lock. We can see a simple implementation in Code Listing 54. Add the following method to the ReflectionHelper class that we added to the T4Utilities project.

  public void LoadAssembly(AssemblyName assembly)

  {

     var url = new Uri(assembly.CodeBase);

     var bytes = File.ReadAllBytes(url.AbsolutePath);

     TargetAssembly = Assembly.Load(bytes);

  }

  public Assembly TargetAssembly {get;set;}

Load an Assembly Without Locking

AssemblyName is a handy object in the Reflection namespace that simplifies the process of referring to an assembly and ensuring that you are referring to the correct one. It can be initialized like this:

var path = this.Host.ResolvePath (@"..\WebApi\bin\WebApi.dll");

var name = System.Reflection.AssemblyName.GetAssemblyName(assemblyPath);

Initializing an AssemblyName

This approach to loading an assembly will ensure that you can continue compiling the code that generated the assembly being loaded and rerunning the templates using that assembly without clashing with each other.

Note: This is very important, because nothing is more frustrating than having to exit and restart Visual Studio just to unlock an assembly so you can recompile. This is an area that has been greatly enhanced in the more recent version of Visual Studio, which used to lock assemblies behind the scenes in many places. But as of Visual Studio 2013, this should be a thing of the past. Any locked assemblies should be self-inflicted, and avoidable by following this simple trick.

Get a List of Types

While gathering the metadata to support code generation, we will usually be interested in multiple types in an assembly. This means that we will call GetTypes to get all of the types in the assembly. Depending on your metadata requirements, you may know which type you are interested in. If this is the case, you can call the GetType method, which will return a single Type object.

Tip: In general, you will be better off making a single call to GetTypes rather than several calls to GetType. Unless you are explicitly interesting in a single type, GetTypes is probably the better choice.

Getting a list of types is pretty straightforward:

        private IList<Type> _cachedTypes;

        public IList<Type> GetTypes()

        {

            if (_cachedTypes == null)

                _cachedTypes = TargetAssembly.GetTypes().ToList();

            if (_cachedTypes.First().Assembly != TargetAssembly)

                _cachedTypes = TargetAssembly.GetTypes().ToList();

            return _cachedTypes;

        }

Getting a List of Types

Note: The list of types is cached to improve performance. Getting the list of types is a fairly expensive operation. If the TargetAssembly has not changed, there is no reason to repeatedly to through the cost of building this list.

We can build on this to simplify some common filters by running LINQ queries against the results, using any of the methods or properties exposed by Type.

For example:

  public IList<Type> GetTypesInNamespace (string nameSpace)

  {

     return GetTypes()

        .Where(t=>t.Namespace == nameSpace).ToList();

  }

Filtering Types By NameSpace

Or

  public IList<Type> GetInterfaces()

  {

      return GetTypes()

            .Where(t => t.IsInterface ).ToList();

  }

Getting all Interfaces

We can add a couple of helper functions to give us even more options:

  public bool HasBaseType (Type currentType, Type baseType)

  {

     return GetBaseClasses(currentType).Any(b => b == baseType);

  } 

  public List<Type> GetBaseClasses(Type type)

  {

     var allBases = new List<Type>();

     var derived = type;

     do

     {

        derived = derived.BaseType;

        if (derived != null)

           allBases.Add(derived);

     } while (derived != null);

     return allBases;

  }

Determing if a Class has a Particular Base Class

The GetBaseClasses method will walk the entire inheritance hierarchy back to object and return this as a list. HasBaseType will determine if any of the items returned matches the item that we are looking for.

With these helper functions, we can now include a filter like this:

  public IList<Type > GetTypesDerivedFrom (Type baseType)

  {

     return GetTypes()

         .Where(t=> HasBaseType(t, baseType)).ToList();

  }

Filtering Types Based on Base Class

Once we have an Assembly loaded, we can easily get all of the types, a single type by name, or list of types filtered any number of ways.

Get the Properties of a Type

Once we have identified the type or types that we are interested in, we can start learning more about the members in that type.

Again, getting the properties is fairly straightforward:

  public IList<PropertyInfo> GetProperties (Type name)

  {

     return name.GetProperties().ToList();

  }

Getting the Properties of a Type

There are some built-in filters that .NET provides directly through the BindingFlags that can be passed to the GetProperties method. As will see shortly, these BindingFlags can be passed to a number of places.

Depending on your metadata requirements, you may want different filters, but a common practice is to return only the public properties defined at this level of inheritance.

  public IList<PropertyInfo> GetProperties (Type name)

  {

     return name.GetProperties(

             BindingFlags.DeclaredOnly |

             BindingFlags.Public |

             BindingFlags.Instance).ToList();

  }

Adding BindingFlags to GetProperties

This will filter the properties and only return the public ones defined at this level of inheritance that are not static.

Getting the Methods of a Type

Getting the methods defined in a type is very similar to getting the properties.

  public IList<MethodInfo> GetMethods(Type name)

  {

     return name.GetMethods(

             BindingFlags.DeclaredOnly |

             BindingFlags.Public |

             BindingFlags.Instance)

          .Where(m => !m.Name.StartsWith("get_")

                    && !m.Name.StartsWith("set_")).ToList();

  }

 

Getting a List of Methods Including the Binding Flags Filtering Out Properties

We are using the binding flags that we saw earlier, but I add an extra filter. We want to remove the methods that start with get_ and set_. These are the methods that implement the getters and setters for the properties in the type. As a general rule, when we are dealing with methods, we are not interested in the methods that implement a property. We will deal with those methods as we deal with the property.

Methods also have a couple of interesting properties worth exploring: ReturnType and Parameters.

We may want to filter based on the ReturnType like this:

  public IList<MethodInfo> GetMethodsReturningVoid(Type name)

  {

     return GetMethods(name)name)

          .Where(m=>m.ReturnType == typeof(void)).ToList();

  }

  }

Filtering Methods Based on Return Type

Alternately, we may want to filter based on some attribute of the parameters.

  public IList<MethodInfo> GetMethodsWithNoParameters(Type name)

  {

  return GetMethods( name ))

   .

   .Where(m => m.GetParameters().Count() == 0).ToList();

  }

Ficltering Based on the Parameters

Get the Attributes of a Type, Method, or Property

The GetCustomAttributes method is defined as part of the MemberInfo class. Fortunately for us, this just so happens to be the base class for types, methods, and properties. We can easily write a method to get the custom attributes for any of these types like this:

  public IEnumerable<Attribute> GetAttributes(MemberInfo member)

  {

     var list = member.GetCustomAttributes(true).ToList();

     var returnValue = new List<Attribute>();

     foreach (var item in list)

     {

        returnValue.Add(item as Attribute);

     }

     return returnValue;

  }

Getting Attributes

Once we have this list of attributes, we can define a handy function to easily determine if the member that we are interested in has a particular attribute.

  public bool HasAttribute(MemberInfo member, string attr)

  {

     return GetAttributes(member)

      .Any(a => a.GetType().Name == attr);

  }

Checking to See If a Member Has a Particular Attribute

Now that we have the handy HasAttribute method, we could write new filters against properties or methods, taking into account their custom attributes.

  public IList<PropertyInfo> GetRequiredProperties (Type member)

  {

     return GetProperties(member)

      .Where(p => HasAttribute(p, "RequiredAttribute")).ToList();

  }

Filtering Properties by Attribute

One key point to remember here is that even though we think of the “Required” attribute, the full name for this attribute is “RequiredAttribute”.

Defining a Custom Attribute

Now that we know how to access the types in an assembly and the various members of a type, as well as the custom attributes for each of these, the next question is: “How do I create my own custom attribute?”

It’s pretty straightforward; all we need to do is define a new class and derive it from System.Attribute.

Let’s go ahead and add a new project to our solution and name it book.Model. Let’s add a new class to this project called HideMethodAttribute.

Add the following code to HideMethodAttribute:

using System;

namespace book.Models

{

   [System.AttributeUsage(System.AttributeTargets.Method)]

   public class HideMethodAttribute : Attribute

   {

   }

}

Defining a Simple Attribute

The AttributeUsageAttribute might seem a little strange at first. It simply instructs the compiler that this attribute is valid only on methods. Thanks to this attribute, you will get a compile error if you try to use it anywhere else.

Properties for the attribute are simple properties added to this class, defined like any other property, set when it is applied, and accessed during code generation. That is it for a simple attribute. The interpretation and meaning of the attribute comes from what we do when we detect that the attribute has been applied to a method.

Pulling it All Together

Go back to the solution that we have been working in, and add a new project. This time, add a new MVC Web API Project.

Adding a Web API Project

Add a reference to the book.Model project that we created when we discussed creating a custom attribute. In this project, let’s create a new class called EmployeeModel.

Add the following code to EmployeeModel.cs:

namespace book.Model

{

  public class EmployeeModel

  {

    public int Id { get; set; }

    public string FirstName { get; set; }

    public string LastName { get; set; }

    public DateTime DateOfBirth { get; set; }

    public DateTime HireDate { get; set; }

    public DateTime LastReview { get; set; }

  }

}

Some Common Properties for an Employee Model

Now let’s go back to the Web API project, and create a controller based on this model.

Add Controller Specifying Scaffolding Options

In the Scaffolding options section, we can specify the Template; this is the MVC template that will be used to generate the controller. We will select MVC controller read/write actions and views. We also specify the Model class and the Data context class for Entity Framework. In this case, we selected Create a new one, and were prompted for the name to give it.

With this minimal information, the scaffolding system will generate the full controller and the Entity Framework context.

Let’s look at the code generated for the controller.

using System;

using System.Collections.Generic;

using System.Data;

using System.Data.Entity;

using System.Data.Entity.Infrastructure;

using System.Linq;

using System.Net;

using System.Net.Http;

using System.Web.Http;

using System.Web.Http.Description;

using book.MVC.API1.Models;

using book.Model;

namespace book.MVC.API1.Controllers

{

 public class EmployeeController : ApiController

 {

  private ApplicationDbContext db = new ApplicationDbContext();

  // GET: api/EmployeeModels

  public IQueryable<EmployeeModel> GetEmployeeModels()

  {

  return db.EmployeeModels;

  }

  // GET: api/EmployeeModels/5

  [ResponseType(typeof(EmployeeModel))]

  public IHttpActionResult GetEmployeeModel(int id)

  {

  EmployeeModel employeeModel = db.EmployeeModels.Find(id);

  if (employeeModel == null)

  {

   return NotFound();

  }

  return Ok(employeeModel);

  }

  // PUT: api/EmployeeModels/5

  [ResponseType(typeof(void))]

  public IHttpActionResult PutEmployeeModel(int id, EmployeeModel employeeModel)

  {

  if (!ModelState.IsValid)

  {

   return BadRequest(ModelState);

  }

  if (id != employeeModel.Id)

  {

   return BadRequest();

  }

  db.Entry(employeeModel).State = EntityState.Modified;

  try

  {

   db.SaveChanges();

  }

  catch (DbUpdateConcurrencyException)

  {

   if (!EmployeeModelExists(id))

   {

    return NotFound();

   }

   else

   {

    throw;

   }

  }

  return StatusCode(HttpStatusCode.NoContent);

  }

  // POST: api/EmployeeModels

  [ResponseType(typeof(EmployeeModel))]

  public IHttpActionResult PostEmployeeModel(EmployeeModel employeeModel)

  {

  if (!ModelState.IsValid)

  {

   return BadRequest(ModelState);

  }

  db.EmployeeModels.Add(employeeModel);

  db.SaveChanges();

  return CreatedAtRoute("DefaultApi", new { id = employeeModel.Id }, employeeModel);

  }

  // DELETE: api/EmployeeModels/5

  [ResponseType(typeof(EmployeeModel))]

  public IHttpActionResult DeleteEmployeeModel(int id)

  {

  EmployeeModel employeeModel = db.EmployeeModels.Find(id);

  if (employeeModel == null)

  {

   return NotFound();

  }

  db.EmployeeModels.Remove(employeeModel);

  db.SaveChanges();

  return Ok(employeeModel);

  }

  protected override void Dispose(bool disposing)

  {

  if (disposing)

  {

   db.Dispose();

  }

  base.Dispose(disposing);

  }

  private bool EmployeeModelExists(int id)

  {

  return db.EmployeeModels.Count(e => e.Id == id) > 0;

  }

 }

}

Generated Controller Code

As you can see, this controller includes actions for each of the HTTP verbs GET, POST, PUT, and DELETE.

It would be nice to have a proxy class to automate calling these methods using the HttpClient library available by using the Microsoft.Net.Http package available through NuGet.

Let’s go back to our solution, and in the book project, use NuGet to add the Microsoft.Net.Http package.

Install Microsoft.Net.Http

Now, in the book project, we want to create a new text template and name it services.tt.

In order to access the book.WebApi assembly, we will need to add a few references to the template.

<#@ assembly name="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5\System.Data.Entity.dll"#>

<#@ assembly name="$(SolutionDir)book.Model\bin\debug\book.Models.dll" #>

<#@ assembly name="$(SolutionDir)packages\EntityFramework.5.0.0\lib\net45\EntityFramework.dll" #>

<#@ assembly name="$(SolutionDir)packages\Microsoft.AspNet.Mvc.4.0.30506.0\lib\net40\System.Web.Mvc.dll" #>

<#@ assembly name="$(SolutionDir)packages\Microsoft.AspNet.WebApi.Core.4.0.30506.0\lib\net40\System.Web.Http.dll" #>

<#@ assembly name="$(SolutionDir)T4Utilities\bin\Debug\T4Utilities.dll" #>

Assembly References Needed for Services.tt

To make the code a little bit simpler, we will add some basic import directives.

<#@ import namespace = "T4Utilities" #>

<#@ import namespace = "System.Web.Http" #>

<#@ import namespace= "System.Web.Mvc" #>

<#@ import namespace= "System.Reflection" #>

import Directives Needed by Services.tt

Now with the basics for our directives out of the way, let’s think about the type of class that we want to generate.

Let’s generate a single class with a method for each API action that it finds. We will start by initializing our ReflectionHelper, and then outputting some boilerplate code:

<#

 reflection = new ReflectionHelper();

 NamespaceName = "book";

 

#>

using System;

using System.Collections.Generic;

using System.Linq;

using System.Net.Http;

using System.Net.Http.Headers;

using Newtonsoft.Json;

using Newtonsoft.Json.Converters;

namespace <#= NamespaceName #>

{

 public class DataAPIServices

 {

<#

 var path = this.Host.ResolvePath (@"..\WebApi\bin\WebApi.dll");

 OutputActionCalls(path);

#>

 }

}

Initializing the Services template

The bulk of the code generated will be the calls to the various actions that we find in the assembly.

Now let’s turn our attention to the feature block for the template. We need to define a couple of properties for the template that we initialized earlier, as well as the OutputActionCalls method.

<#+

 public ReflectionHelper reflection{get ; set;}

 public string NamespaceName {get; set;}

 public void OutputActionCalls(string assemblyPath)

 {

   var name = AssemblyName.GetAssemblyName(assemblyPath);

   reflection.LoadAssembly (name);

   var targetType= typeof (ApiController);

   var controllers= reflection.GetTypes()

           .Where (t=> reflection.HasBaseType(t, targetType));

   foreach (var controller in controllers)

   {

      PushIndent("  ");

      this.WriteLine( CurrentIndent+"//" + controller.Name);

      var actions= reflection.GetMethods(controller);

      ProcessGetMethods(actions);

      ProcessPostMethods(actions);

      ProcessPutMethods(actions);

      PopIndent();

   }

   PopIndent();

 }

The OutputActionCalls Method

The properties are defined just like any other property, and the OutputActionCalls is rather straightforward. We hand most of the heavy lifting over to the ReflectionHelper. Because we want to handle the various actions differently, we will filter them out by checking their attributes to see which ones handle which verbs.

Note: This does require changing the code generated by the Web API scaffolding. If you don’t want to have to make this change, you could also identify which actions go with which verbs by matching the naming conventions on the methods.

Let’s go back to the controller and some action selector attributes to the generated code. Because this is generated from scaffolding, we are free to change this code as needed—this was always a onetime code generation.

Action Selector Attributes

HttpGetAttribute

Restricts access to an action to HTTP GET requests

HttpPostAttribute

Restricts access to an action to HTTP POST requests

HttpPutAttribute

Restricts access to an action to HTTP PUT requests

HttpDeleteAttribute

Restricts access to an action to HTTP DELETE requests

Let’s look at these various process methods.

 private void ProcessGetMethods(IList<MethodInfo> actions)

 {

    this.WriteLine(this.CurrentIndent + "// Get");

    foreach (var action in actions

          .Where (a=>reflection

                 .HasAttribute(a, "HttpGetAttribute")))

    {

        this.Write(CurrentIndent + "public " +

        reflection.FormatType( action.ReturnType)+ " "

                   + action.Name);

        var parameterString = reflection

               .GetParameterString (action);

        if (string.IsNullOrWhiteSpace(parameterString))

              parameterString = " string url";

        else

              parameterString += ", string url";

        this.WriteLine("(" + parameterString + ")");

        this.WriteLine(CurrentIndent + "{");

#>

  HttpClient client = new HttpClient();

  var response = client.GetAsync(url).Result;

  var data = JsonConvert.DeserializeObject<<#=reflection.FormatType(action.ReturnType)#>>(response.Content.ReadAsStringAsync().Result);

  return data;

<#+

        this.WriteLine(CurrentIndent + "}");

    }

 }

Supporting Process Methods

The actual implementation of this method is not necessarily very exciting, but there are a couple of things to note. First, we make a call to get the parameters as a string, and we want to add an extra parameter for the URL. We first need to do a little bit of conditional logic to properly handle the cases where there were originally no parameters.

The other item potentially of interest is the use HttpClient. For each verb, we will call the appropriate method corresponding to the verb that we are working with. Here, we call GetAsync. For POST, we will call PostAsJsonAsync. For PUT, we will call PutAsJsonAsync, and for DELETE, we will call DeleteAsync.

For your reference, here are the remaining methods defined in this template:

 private void ProcessPostMethods(IList<MethodInfo> actions)

 {

    this.WriteLine(this.CurrentIndent + "// Post");

    foreach (var action in actions

              .Where (a=>reflection.HasAttribute(a,

                     "HttpPostAttribute")))

    {

       this.Write(CurrentIndent + "public "

              + reflection.FormatType( action.ReturnType)+ " "

              + action.Name);

       var parameterString = reflection

          .GetParameterString (action);

       if (string.IsNullOrWhiteSpace(parameterString))

           parameterString = " string url";

       else

           parameterString += ", string url";

       this.WriteLine("(" + parameterString + ")");

       this.WriteLine(CurrentIndent + "{");

#>

  var client = new HttpClient();

  client.DefaultRequestHeaders.Accept.Add(

   new MediaTypeWithQualityHeaderValue("application/json"));

  var response = client.PostAsJsonAsync(url, <#=action.GetParameters()[0].Name #>).Result;

  return response;

<#+

       this.WriteLine(CurrentIndent + "}");

    }

 }

 private void ProcessPutMethods(IList<MethodInfo> actions)

 {

    this.WriteLine(this.CurrentIndent + "// Put");

    foreach (var action in actions

             .Where (a=>reflection.HasAttribute(a,

                     "HttpPutAttribute")))

    {

       this.Write(CurrentIndent + "public "

              + reflection.FormatType( action.ReturnType)+ " "

              + action.Name);

       var parameterString= reflection

    .      GetParameterString (action);

       if (string.IsNullOrWhiteSpace(parameterString))

           parameterString = " string url";

       else

           parameterString += ", string url";

       this.WriteLine("(" + parameterString + ")");

       this.WriteLine(CurrentIndent + "{");

#>

  var client = new HttpClient();

  client.DefaultRequestHeaders.Accept.Add(

   new MediaTypeWithQualityHeaderValue("application/json"));

  var response = client.PutAsJsonAsync(url, <#=action.GetParameters()[0].Name #>).Result;

  return response;

<#+

       this.WriteLine(CurrentIndent + "}");

    }

 }

#>

The Supporting Process Methods for the Services template

Note: This is not intended to be a guide to coding Web API calls. For best practices and better understanding the Web API, please refer to the Succinctly Web API book.

An important takeaway from this example should be how easy it would be to modify the template to change the generated code as your best practices improve.

Summary

In this chapter we have covered most topics related to accessing data through reflection. We saw how to load an assembly without locking it, and we explored how to get and filter the types contained in the assembly. We have seen some simple ways to format the name of some complex types so that they look nice in our generated code.

We explored retrieving and filtering the basic components of type that would commonly be used in code generation. There are similar methods available to retrieve the fields and events from a type.

Throughout this chapter we have seen that .NET keeps a wealth of information for us about the code being run. The only drawback is that the code has to be compiled before any of this information is available through reflection.

In the next chapter we will explore ways to retrieve similar information directly from the source code without having to wait for the code to be compiled.

Scroll To Top
Disclaimer
DISCLAIMER: Web reader is currently in beta. Please report any issues through our support system. PDF and Kindle format files are also available for download.

Previous

Next



You are one step away from downloading ebooks from the Succinctly® series premier collection!
A confirmation has been sent to your email address. Please check and confirm your email subscription to complete the download.