Metaprogramming in C# with LamarCompiler

I gave a talk last week at NDC London called “Dynamic Runtime Code with Roslyn” that featured an example of using LamarCompiler and Lamar to create a minimal equivalent of the Refit library. If you’re not familiar with Refit, it allows you to express the intended service proxy to a web service with a .Net interface or abstract class, apply a few attributes to explain Url patterns or other HTTP request items, and then allow Refit to dynamically generate a class that implements your interface that deals with all the repetitive gunk of building Urls, serializing or deserializing JSON, and calling HttpClient to actually make the requests.

Cool, right? I’d argue that Refit is an example of metaprogramming inside of C# where a tool is able to generate and eventually execute code gleaned from data (an interface and a handful of attributes in this case) within the application. I tend to think of metaprogramming as something specific to the Ruby and other dynamic languages arena, but now that we have the ability to compile C# code on the fly with Roslyn, I think it’s going to become something much more commonly done in .Net systems.

Today in the Lamar codebase, there’s a little project called LamarRest that contains all the code I’m going to show here if you’re curious to see more about how it all fits together. I should say that I intended this code strictly as a demonstration of what LamarCompiler can do and do not have any plans right now to make this a production capable library.

The Problem and Conceptual Solution

Okay, let’s get concrete and write a very small web service that manages users. The whole API surface in this case is implemented by the UserController class shown below (the full source code for this web service is here):

    public class UserController : Controller
    {
        private readonly UserRepository _repository;
        private readonly ILogger _logger;

        public UserController(UserRepository repository, ILogger logger)
        {
            _repository = repository;
            _logger = logger;
        }

        [HttpPost("/user/create")]
        public IActionResult Create([FromBody] User user)
        {
            _logger.LogInformation($"I created a new user with the name '{user.Name}'");
            _repository.Users.Fill(user);
            return Ok();
        }
        
        [HttpGet("/user/{name}")]
        public User GetUser(string name)
        {
            return _repository.Users.FirstOrDefault(x => x.Name == name);
        }

        [HttpGet("/users")]
        public User[] GetUsers()
        {
            return _repository.Users.ToArray();
        }
    }

Now, back in the codebase for an application that needs to communicate with the user service, we ideally want to just write an interface to act as a proxy to the User web service that looks like this:

    public interface IUserService
    {
        [Get("/user/{name}")]
        Task GetUser(string name);

        [Post("/user/create")]
        Task Create(User user);
    }

In our application, we just want to interact with the IUserService and not really have to know where it comes from. To that end, I’m going to go a step farther than Refit does and say that our solution should automatically resolve the generated type for IUserService from our application’s IoC container on demand.

As you’ll notice, I did have to add a couple attributes ([Get] and [Post]) that just specify the Url pattern for the two proxy methods. In this case as well though, the presence of the route marker attributes tells the code we’re about to write that these methods should be generated to call HttpClient.

Implementing Our Refit Clone

To generate the concrete HTTP proxy classes, I’m going to leverage LamarCompiler’s Frame model. If you write out implementation of IUserService above, you’ll notice discrete set of logical steps, each of which becomes a LamarCompile Frame in this model:

  1. Resolving an HttpClient from the HttpClientFactory for our application.  That became BuildClientFrame.
  2. Fill the real Url by substituting any route arguments with method parameters from our interface method signatures. That’s implemented (crudely and incompletely) in LamarRest by the FillUrlFrame.
  3. If necessary, serialize the input boy argument to Json using Newtonsoft.Json’s default settings (Refit being a real library allows you to configure the Json serialization, LamarRest being a demo worked up the day before a conference talk, does not). For that purpose, I created SerializeJsonFrame.
  4. To actually send an Http request, we need to create an HttpRequestMessage object with the right Url, Http method, and the serialized Json if there is any. This one is a little easier, as I just subclass Lamar’s built in support for calling a class constructor with any mix of arguments and setters in the BuildRequestFrame class.
  5. To actually invoke HttpClient.SendAsync() , there’s just a built in MethodCall Frame inside of LamarCompiler.
  6. If there is a response body expected, as in the IUserService.GetUser() method, we need a Frame that can deserialize the contents of the HTTP response body into the desired model. I implemented that in DeserializeObjectFrame.

Okay, so that’s a lot of steps, but once these Frame classes were built, it was easy to assemble them to match an interface method. In LamarRest, there’s a fair sized class called GeneratedServiceType that handles all the code generation and compilation for these proxy classes. Most applicable to this discussion is the method that builds out the concrete proxy methods by assembling the necessary list of Frame objects as shown below:

public static void BuildOut(
    // The contract type
    Type interfaceType, 
    
    // The MethodInfo from Reflection that describes
    // the Url structure through attributes, the input type
    // if any, and the .Net type of the response (if any)
    MethodInfo definition, 
    
    // This models the method being generated by LamarCompiler
    GeneratedMethod generated)
{
    var path = definition.GetCustomAttribute();

    // Get the right HttpClient from IHttpClientFactory
    generated.Frames.Add(new BuildClientFrame(interfaceType));

    // Build out the Url from the method arguments and route
    // pattern
    var urlFrame = new FillUrlFrame(definition);
    generated.Frames.Add(urlFrame);

    
    // See if there is an input type that should be serialized
    // to the HTTP request body
    var inputType = DetermineRequestType(definition);
    if (inputType == null)
    {
        // Just build the HttpRequestMessage
        generated.Frames.Add(new BuildRequestFrame(path.Method, urlFrame, null));
    }
    else
    {
        // Add a step to serialize the input model to Json
        generated.Frames.Add(new SerializeJsonFrame(inputType));
        
        // Just build the HttpRequestMessage
        generated.Frames.Add(new BuildRequestFrame(path.Method, urlFrame, new SerializeJsonFrame(inputType)));
    }

    // Actually call the HttpClient to send the request
    generated.Frames.Call(x => x.SendAsync(null));

    // Is there a response type that should be serialized out of the HTTP
    // response body?
    var returnType = DetermineResponseType(definition);
    if (returnType != null)
    {
        // Deserialize the response JSON into a new variable
        var deserialize = new DeserializeObjectFrame(returnType);
        generated.Frames.Add(deserialize);
        
        // Return that deserialized object from the method
        generated.Frames.Return(deserialize.ReturnValue);
    }
}

I should note that LamarCompiler does some work of connecting variables, method arguments, and class fields by type and sometimes by name — which is why you see no explicit mention of how IHttpClientFactory comes into play. LamarCompiler is smart enough to see that a Frame object needs an IHttpClientFactory object, know that that service is a singleton-coped object in the application’s IoC container, and therefore generate a field for that service.

For the IUserService interface, LamarRest will generate this code for the concrete class, but I warn you now, it’s generated code and hence will be as ugly as sin:

    public class UserService : LamarRest.Testing.IUserService
    {
        private readonly System.Net.Http.IHttpClientFactory _httpClientFactory;

        public UserService(System.Net.Http.IHttpClientFactory httpClientFactory)
        {
            _httpClientFactory = httpClientFactory;
        }



        public async System.Threading.Tasks.Task GetUser(string name)
        {

            // From BuildClientFrame
            var httpClient = _httpClientFactory.CreateClient("IUserService");

            // From FillUrlFrame
            var url = $"/user/{name}";
            var httpRequestMessage = new System.Net.Http.HttpRequestMessage(new System.Net.Http.HttpMethod("GET"), url);
            using (var httpResponseMessage = await httpClient.SendAsync(httpRequestMessage))
            {

                // From DeserializeObjectFrame
                var user = Newtonsoft.Json.JsonConvert.DeserializeObject(await httpResponseMessage.Content.ReadAsStringAsync());
                return user;
            }

        }


        public Task Create(UserApp.Controllers.User user)
        {

            // From BuildClientFrame
            var httpClient = _httpClientFactory.CreateClient("IUserService");

            // From FillUrlFrame
            var url = $"/user/create";

            // From SerializeJsonFrame
            var json = Newtonsoft.Json.JsonConvert.SerializeObject(user);
            var httpRequestMessage = new System.Net.Http.HttpRequestMessage(new System.Net.Http.HttpMethod("POST"), url){Content = new System.Net.Http.StringContent(json, System.Text.Encoding.UTF8, "application/json")};
            return httpClient.SendAsync(httpRequestMessage);
        }

Hooking it up to the Application

Lastly, we need to hook up our new LamarRest library to our application’s IoC container and application configuration. Assuming that you’re using Lamar as your IoC container, the configuration might be something like this:

            var container = new Container(x =>
            {
                x.AddHttpClient(
                    typeof(IUserService).Name, 
                    // You'd pull this from actual configuration in real life
                    c => c.BaseAddress = new Uri("http://localhost:5000"));
                
                // Adding a Lamar policy here that will auto-resolve LamarRest
                // proxies on demand
                x.Policies.Add<LamarRestPolicy>();
            });

In the code above, I’m heavily leveraging the standard ASP.Net Core AddHttpClient() registration to deal with base Urls and default HTTP request attributes like authentication credentials. I’d probably switch it to being strong typed later, but for now the convention is to name the HttpClient based on the interface name.

The second registration is Lamar specific to add a policy to allow Lamar to auto-resolve a concrete class for any interface that is otherwise unknown to the container and has methods marked with any kind of LamarRest route attribute.

Just for completeness sake, that’s shown below:

    public class LamarRestPolicy : Lamar.IFamilyPolicy
    {
        public ServiceFamily Build(Type type, ServiceGraph serviceGraph)
        {
            if (type.GetMethods().Any(x => x.HasAttribute()))
            {
                var rules = new GenerationRules("LamarRest");
                var generatedAssembly = new GeneratedAssembly(rules);
                var generatedType = new GeneratedServiceType(generatedAssembly, type);

                var container = (IContainer)serviceGraph.RootScope;
                
                container.CompileWithInlineServices(generatedAssembly);

                return new ServiceFamily(
                    type, 
                    new IDecoratorPolicy[0], 
                    new ConstructorInstance(type, 
                    generatedType.CompiledType, 
                    ServiceLifetime.Singleton
                ));
            }

            return null;
        }

The final result of all this setup is to be able to do this:

            
            // Build an implementation of this service as needed
            // I'm using service location here, but you'd be able
            // to inject IUserService as a constructor argument as well
            var userService = container.GetInstance<IUserService>();
            

 

Advertisements

2 thoughts on “Metaprogramming in C# with LamarCompiler

    1. jeremydmiller Post author

      Thank you for the heads up. Fixed the ones I could spot, but in the end, that’s why the real code is linked ’cause it never looks all that great in this template.

      Reply

Leave a Reply to Thomas Levesque Cancel reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s