.NET Core Tutorial: Using the ServiceCollection Extension Pattern II: Pass options using the Options Pattern

In a previous post I demonstrated how to use to ServiceCollection Extension Pattern and how this prevents a bloated ConfigureServices() pipeline. In this follow up you will learn how to pass additional information to your implementation.

Consider the following extension method that adds your project containing some services:

public static class DataProjectServiceCollectionExtensions
{
    public static IServiceCollection AddServicesProject(this IServiceCollection services)
    {
        services.AddScoped<ICustomerService, CustomerService>();

        services.AddScoped<IArticleService, ArticleService>();

        return services;
    }
}

As seen in the previous post, we add these services to the IServiceCollection of the project by simply calling services.AddServicesProject(); in the ConfigureServices() pipeline in the startup.cs class.
Example service that gets data from a repository:

namespace Project.ServicesProject.Services
{
    internal sealed class ArticleService: ArticleService
    {
        private readonly IArticleRepository _repository;

        public ArticleRepository(IArticleRepository repository)
        {
            _repository = repository;
        }

        public async Task<Article[]> GetArticles()
            => await _repository.GetArticles();
    }

    internal interface ArticleService
    {
        Task<Article[]> GetArticles();
    }
}

In this example we want to pass a value from the hosting project’s appsettings.json to the services project. Let’s use an fixed StoreId in our project’s settings and we want to filter out the articles only for this specific store whenever we call GetArticles().

appsettings.json:

{
  "HttpsRedirection": {
    "Enabled": true
  },
  "StoreSettings": {
    "StoreId": 15
  },
}

We can pass this value through an options object that is used as a parameter in out extension method. We create the options preferably in the same folder as your extensions method. I add this to the root of the project where ServicesProjectServiceCollectionsExtension is located.

namespace Project.ServicesProject
{
    public sealed class ServicesProjectOptions
    {
        public int StoreId { get; set; }
    }
}

Now we have an value to get from the appsettings.json in the hosting project, we have an object to store this value, we have a different project that requires this value and all that remains is to pass this value to the new project, using the ServiceCollection Extension Pattern we implemented.

We adjust the extension method so it requires an Action<> delegate holding the options object. You can validate any additional attributes added to the properties of the options object if these are required.

public static class ServicesProjecterviceCollectionExtensions
{
    public static IServiceCollection AddServicesProject(this IServiceCollection services, , Action<ServicesProjectOptions> configureOptions)
    {
        services.AddOptions<ServicesProjectOptions>()
            .Configure(configureOptions)
            .ValidateDataAnnotations();

        services.AddScoped<ICustomerService, CustomerService>();

        services.AddScoped<IArticleService, ArticleService>();

        return services;
    }
}

Next step is to update the call to AddServicesProject() in your startup.cs file so that we read the storeId from the appsettings.json and pass it using the options object we just created for this.

public void ConfigureServices(IServiceCollection services)
        {
            services.AddServicesProject(options =>
            {
                options.StoreId = configuration.GetValue<int>("StoreSettings:StoreId");;
            });
}

And that’s it. Now we can access the storeId in our ServicesProject and use it wherever needed. To get the storeId value in our we inject the IOptions<DataProjectOptions> (via Dependency Injection) in the service class and read the value from it. What we just implemented is another pattern called the Options Pattern. We can update the ArticleService as followed:

namespace Project.ServicesProject.Services
{
    internal sealed class ArticleService: ArticleService
    {
        private readonly IArticleRepository _repository;

        private readonly ServiceProjectOptions _options;

        public ArticleRepository(IArticleRepository repository, IOptions<ServicesProjectOptions> options)
        {
            _repository = repository;
            _options = options.Value;
        }

        public async Task<Article[]> GetArticles()
            => await _repository.GetArticles().Where(x => x.StoreId == _options.StoreId);
    }

    internal interface ArticleService
    {
        Task<Article[]> GetArticles();
    }
}

More information about the Option Pattern and additional know-how on validating options on the DOCS.

.NET Core Tutorial: Using the ServiceCollection Extension Pattern

If you have worked on applications (in .NET Core) you should know how Dependency Injection (DI) is supported and is applied for your services. This is straight forward and not so hard at all. Just add them to the IServiceCollection in the ConfigureServices() pipeline in the startup.cs file. Considering a N-Tier architrecture, where our Startup.cs is in the Project.Host project, a small example:

// This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddTransient<IService, Service>();
            services.AddScoped<IProcessor, Processor>();
            services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
        }

Now, in the code snippet above, we just add a service, a processor and the IHttpContextAccessor to the DI container. As the application grows and grows, more and more services will be added here and on a long term, this will just bloat up the startup.cs file.

For example, there is a new project added to the solution Project.Reporting which hold services like IPdfReportGeneratorService and a IPdfReportConverterService, and maybe a few more for demonstration purposes. What you would normally do is just add a reference to the reporting project and just add those services to the container. You can see already how this is adding up to the pipeline.

using Solution.Project.Reporting.Services;

// This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddTransient<IPdfReportGeneratorService , PdfReportGeneratorService >();
            services.AddTransient<IPdfReportConverterService, PdfReportConverterService>();
            services.AddTransient<IServiceThis, ServiceThis>();
            services.AddTransient<IServiceThat, ServiceThat>();
            services.AddTransient<IServiceOther, ServiceOther>();

            services.AddTransient<IService, Service>();
            services.AddScoped<IProcessor, Processor>();
            services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
        }

We can clean this up a bit. The Service Collection Extension Pattern is exactly what it says. We add our services in a extension method in its own project. The goal is to keep this pipeline as clear as possible. Let’s do this for the Project.Reporting library in the aplication.

Inside the reporting project, we add a new static class called ReportingServiceCollectionExtensions at the root of the project. Inside we create a extension method where we extend the IServiceCollection and add the services. We also return the services – because the service registration implements the Chain Pattern.

using Microsoft.Extensions.DependencyInjection;

public static class ReportingServiceCollectionExtensions
{
    public static IServiceCollection AddReporting(this IServiceCollection services)
    {
            services.AddTransient<IPdfReportGeneratorService , PdfReportGeneratorService >();
            services.AddTransient<IPdfReportConverterService, PdfReportConverterService>();
            services.AddTransient<IServiceThis, ServiceThis>();
            services.AddTransient<IServiceThat, ServiceThat>();
            services.AddTransient<IServiceOther, ServiceOther>();

        return services;
    }
}

Now we can easily call this extension in our RegisterServices pipeline in the startup.cs file in the host project by calling services.AddReporting();. If you do this for each project/assembly/layer in your solution, this keeps the startup file nice and clean.

using Solution.Project.Reporting.Services;

// This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddReporting();

            services.AddTransient<IService, Service>();
            services.AddScoped<IProcessor, Processor>();
            services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
        }

I hope you understand what the Service Collection Extension Pattern is now and how to implement it. If you have any feedback or questions, do not hesitate to comment.

Tutorial: Generate seperate files from a T4 Template

When you run the custom tool of a T4 Template, it generates code in a child node of that template. On StackOverflow I have seen a a lot of devs asking how to write to separate files, or even to a different folder. This is actually easy to achieve, and there are multiple possible solutions. In this article, I will address how I achieved this in my TexTran projects.

Generation environment

In order to have control of the output, we must know how to fetch it. You can access the currently output by calling this.GenerationEnvironment inside <# Control blocks #> . But what is GenerationEnvironment ?

TextTemplate.GenerationEnvironment is the string builder that generation-time code is using to assemble generated output

MSDN

Write to separate file

So, this.GenerationEnvironment is basically a stringBuilder holding the generated string that is our output. We have to write this a new file. For this, just add this simple method to your T4 <#+ Class Feature Block #> .

public void SaveFile(string folder, string fileName, string content)
{
	using (FileStream fs = new FileStream(Path.Combine(folder, fileName.Trim() + ".cs"), FileMode.Create))
    {
        using (StreamWriter str = new StreamWriter(fs))
        {
            str.WriteLine(content);
            str.Flush();
        }
    }
}

This is a simple method that takes the desired path, file name and the filename and uses a StreamWriter object to write your files to a specific location.

Inside your T4 Template, at the bottom (under the last output block) you can add the following lines

<#
	SaveFile(path, fileName, this.GenerationEnvironment.ToString());
	this.GenerationEnvironment.Remove(0, this.GenerationEnvironment.Length);
#>

What this does, is it writes the string content of the GenerationEnvironment StringBuilder to the path specified. Then we remove the full string value from the GenerationEnvironment so that there is no output left for the transformer to write to the child node.

Generate multiple files

One template can generate multiple files. In my TexTran example, I generate multiple Entities from a definition file.

Entities.tt:

... //imports 
<#
//Get current directory
var directory = Path.GetDirectoryName(this.Host.TemplateFile);
var entitiesPath = @"\ProjectName\FolderName";
var regExFilter = "(?<=src).+$";
var entitiesFolder = Regex.Replace(directory, regExFilter, entitiesPath);

// GenerateEntities method is in TransforManagers.ttinclude
List<EntityDefinition> entities = GenerateEntities(entityLines); 

if(entities != null)
{
	foreach (var entity in entities)
	{
#>
// This file is auto generated. Changes to this file will be lost!
using System;
using System.Collections.Generic;
using TextTran.Transformations.Enums;
	
namespace TexTran.Data.Abstractions.Entities
{
<#
	if (!string.IsNullOrEmpty(entity.Summary))
	{
#>
	/// <summary>
	/// <#= Regex.Replace(entity.Summary, "// ", "") #>
	/// </summary>
<#
	}
#>
	public class <#= entity.Name.Trim() #> : BaseEntity
	{
	<#
	foreach (var property in entity.Properties)
	{
		if (property.Nullable)
		{
#>		
		public <#= property.Type #>? <#= property.Name #> { get; set; }
<#
	}
		else
		{
#>		
		public <#= property.Type #> <#= property.Name #> { get; set; }
<#
		}
	}
#>
	}
}
<#
	SaveFile(entitiesFolder, entity.Name, this.GenerationEnvironment.ToString());
	this.GenerationEnvironment.Remove(0, this.GenerationEnvironment.Length);
	}
}
#>

The template above will write a file for each Entity iterated, using the entity.Name property for the filename. Call the SaveFile method at the end of the foreach loop. I added some lines on top that show how I create the path where I want to write to. In my TexTran project, this is located in the TransformSetup.ttinclude file.

Appendix

Just a small note. What would happen if one if your files name changes? It would keep the old file, and write a new one next to it. Add following method to your Class Feature Block, it removes all content from the destination folder:

public void RemoveFilesFromFolder(string path)
{
	Array.ForEach(Directory.GetFiles(path), File.Delete);
}

Simply call this method first in your template, so that the folder is cleared everytime you start re-generating your files.

I hope this tutorial helps! Please leave any feedback or questions in the comments below.

All templates used referenced in this article can be found here.

T4 Tutorial: Execute templates at build time

This is not so hard to do actually. It ensures your transformations are executed when you build your solution, when in some cases you did not click ‘Build > Transform all T4 Templates’ when changes are made to your .tt files.

If one of your projects uses T4 to generate code, and you want it to execute at build time, consider the next steps:

Unload your project

Righ-click on the project in your solution explorer and click ‘unload’.

Open the .csproj of your project

Right-click again and select ‘edit projectname.csproj’

Edit the .csproj file

Add following PropertyGroup at the beginning of your csproj file (for VS2017):

<PropertyGroup>
    <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">15.0</VisualStudioVersion>
    <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
    <TransformOnBuild>true</TransformOnBuild>
   <OverwriteReadOnlyOutputFiles>true</OverwriteReadOnlyOutputFiles>
    <TransformOutOfDateOnly>false</TransformOutOfDateOnly>
</PropertyGroup>

The TransformOnBuild property set tot true ensures the templates to be transformed when you build your project (false by default).

OverwriteReadOnlyOutputFiles forces overwriting of readonly output files.

Set TransformOutOfDateOnly to false to transform files even if the output is up to date.

At the end of your csproj file, add following import:

<Import Project="$(VSToolsPath)\TextTemplating\Microsoft.TextTemplating.targets" />

Now you can reload your project and you are done. All T4 Templates within this project will be executed on each build.

T4 Beginner Tutorial: Generate C# classes based on text definitions

Introduction

T4 stands for Text Template Transformation Toolkit. When you scaffold a controller or view in ASP.NET, in the background T4 Templates that contain those structures are executed and files are generated for you.

In this article I will explain how you can generate classes yourself, based on simple definitions in a text file. This is a easy way to understand how T4 templating works and afterwards you should be able to create your own templates.
Here you can find more information about T4 templates.

Create your first template

In the solution explorer, right click on your project and choose ‘add > new item’ and pick the ‘Text Template’

Name it ‘Models.tt’ and add it to your project.

<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".txt" #>

In the template code above we can see that ‘<#@  #>’ tags are used to enclose the ‘directives’, by default the System.Linq, System.Text and System.Collections.Generic namespaces are imported. You can import any namespace like this. More information about template directives here.

Note that the ‘output extension’ is set to ‘.txt’. Since we are generating C# classes, we have to change this to ‘.cs’.

<#@ output extension=".cs" #>

To execute a template, choose ‘Build’ in the menu and select Transform all T4 templates’. You can also let Visual Studio transform your templates whenever you build your solution or project. See my other post here.

Control blocks

<# Statement blocks #>

With statement blocks we can embed C# code in our template, everything that is not inside these block is written to the generated file.

<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".cs" #>

// This is output
<#  
/* Code goes inside this statement block */
#>
// More output

In VS, if the T4 template is executed, the resultant .cs file is created as a child node off of the template’s node (in the solution explorer).

<#+ Class Feature Blocks #>

With class feature blocks you can write code that is reusable in your template. Here you can write code to use in your statement blocks.

<#= GenerateComment(“This is a comment”)  #>

<#+
public string GenerateComment(string comment)
{
 return $“/* {comment} */”;
}
#>

<#= Expression Blocks #>

With expression blocks you can pass data to the generated output.

<#
string[] namespaces = {"System" ,"System.Linq"};

for(int i = 0; i < namespaces.Length; i++)
{
#>
using <#= namespaces[i] #>;
<#
}
#>

More information about control blocks click here.

Define your classes

Time to generate some models. In this tutorial, I choose to read model and property definitions from a plain text file. By doing so, we can easily add more models to generate. Create a text file in the same folder named ModelDefinitions.txt‘ and add following text:

Product
Id : Guid
Name : string
Price : int

Order
Id : Guid
Products : List<Product>

Mind the indentations.

Inside our template we will have to locate the definitions so we can read them. Inside statements blocks we can write code to do this. To achieve this we must acces ‘this.Host’ to use the ‘ResolvePath’ method by setting ‘hostpecific’ to true. We must also import the ‘System.IO’ namespace.

At the bottom of our template we can also add a class feature block to create the models we need in order to generate our classes.

<#@ import namespace="System.IO" #>
<#@ template debug="false" hostspecific="true" language="C#" #>
<# 
string path = this.Host.ResolvePath(@"ModelDefinitions.txt");
var modelDefinitions = File.ReadLines(path).ToArray();
#>

<#+
public class ModelDefinition
{
	public string Name { get; set; }
	public PropertyDefinition[] Properties { get; set; }
}

public class PropertyDefinition
{
	public PropertyDefinition(string name, string type)
	{
		Name = name;
		Type = type;
	}

	public string Name { get; set; }
	public string Type { get; set; }
}

Create classes

We read the lines of the definition file and fill up a list of ModelDefinition objects. Let’s create a method that iterates over the lines and detects when a word starts at the beginning of a string. This way we can find what line is a model definition, and we can achieve this by using a regular expression.

Add the following code to the class feature block:

<#@ import namespace="System.Text.RegularExpressions" #>

public List<ModelDefinition> GenerateModels(string[] lines)
{
	List<ModelDefinition> result = new List<ModelDefinition>();
	var lineNumber = -1;

	foreach(var line in lines)
	{
		lineNumber++;
		if (String.IsNullOrEmpty(line)) continue;
		
		var match= Regex.Match(line, @"^[^\s].+");

		if(match.Success)
		{
			var model = new ModelDefinition
			{
				Name = line
			};

			result.Add(model);
		}
	}
	return result;
}

Generate code

Now we are can already generate empty Product and Order classes. Add following code to the template so it looks like this:

<#@ template hostspecific="true" language="C#" #>
<#@ output extension=".cs" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Text.RegularExpressions" #>
<#@ import namespace="System.Collections.Generic" #>
<#
string path = this.Host.ResolvePath(@"ModelDefinitions.txt");
var definitions= File.ReadLines(path).ToArray();
#>
//This code is auto-generated. Changes to this file will be lost! 
using System;

namespace Solution.Project.Models
{
<#
List<ModelDefinition> models = GenerateModels(definitions); 

foreach (var model in models)
{
#>
	public class <#= model.Name #>
	{
	}
<#
}
#>
}
<#+
// ….

Output in Models.cs:

//This code is auto-generated. Changes to this file will be lost! 
using System;

namespace Solution.Project.Models
{
	public class Product
	{
	}
	public class Order
	{
	}
}

We can also use a regular expression to find out what lines are property definitions and distinguish their names and types. Let’s add GenerateProperties method that will iterate over the lines, starting from the line number of the model definition. We know that all the next property definitions belong to this model, until we have a empty line.

Add the GenerateProperties method to the class feature block, and change the GenerateModels method as following:

public List<ModelDefinition> GenerateModels(string[] lines)
{
	List<ModelDefinition> result = new List<ModelDefinition>();
	var lineNumber = -1;

	foreach(var line in lines)
	{
		lineNumber++;
		if (String.IsNullOrEmpty(line)) continue;
		
		var match= Regex.Match(line, @"^[^\s].+");

		if(match.Success)
		{
			var model = new ModelDefinition
			{
				Name = line,
				Properties = GenerateProperties(lineNumber, lines)
			};

			result.Add(model);
		}
	}
	return result;
}

public PropertyDefinition[] GenerateProperties(int lineNumber, string[] lines)
{
	List<PropertyDefinition> properties = new List<PropertyDefinition>();

	for (var i = lineNumber + 1; i < lines.Length; i++)
	{
		var match = Regex.Match(lines[i], @"^\s+(?<name>[^:]+)(?<center>[\s]?:[\s]?)(?<type>[^\?\s\?]+)");

		if (match.Success)
		{
				properties.Add(new PropertyDefinition(match.Groups["name"].Value, match.Groups["type"].Value ));
		}
		else
		{
			break;
		}
	}
	return properties.ToArray();
}

That’s it! All that’s is left to do is adjust our template so that can add out properties to the output. Adjust the template as shown below:

<#@ template hostspecific="true" language="C#" #>
<#@ output extension=".cs" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Text.RegularExpressions" #>
<#@ import namespace="System.Collections.Generic" #>
<#
string path = this.Host.ResolvePath(@"..\Definitions\Models.txt");
var definitions= File.ReadLines(path).ToArray();
#>
//This code is auto-generated. Changes to this file will be lost! 
using System;

namespace TextTran.Transformations.Models
{
<#
List<ModelDefinition> models = GenerateModels(definitions); 
foreach (var model in models)
{
#>
	public class <#= model.Name #>
	{
<#
	foreach (var property in model.Properties)
	{
#>
		public <#= property.Type #> <#= property.Name #> { get; set; }

<# 
	}
#>
	}

<#
}
#>
}

If all goes well, the Models.cs file should now look like this:

//This code is auto-generated. Changes to this file will be lost! 
using System;
using System.Collections.Generic;

namespace TextTran.Transformations.Models
{
	public class Product
	{
		public Guid Id { get; set; }

		public string Name { get; set; }

		public int Price { get; set; }

	}

	public class Order
	{
		public Guid Id { get; set; }

		public List<Product> Products { get; set; }

	}
}

Just keep adding definitions, execute the template and voila, your classes are generated for you! You can use this template and adjust it to your needs if you like. There’s more to add to the template: nullable type support, comments and summaries. I have already done this on my experimental project found on my GitHub repository. Feel free to use my code to practice.

Appendix

We can tidy up the template by moving the class feature block and directives to a ‘TemplateManager.tt” file. By adding a include directive in our template we can use all imports and logic defined in the TemplateManager. Full example below:

TemplateManager.tt

<#@ template hostspecific="true" language="C#" #>
<#@ output extension=".cs" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Text.RegularExpressions" #>
<#@ import namespace="System.Collections.Generic" #>
<#+
public List<ModelDefinition> GenerateModels(string[] lines)
{
	List<ModelDefinition> result = new List<ModelDefinition>();
	var lineNumber = -1;

	foreach(var line in lines)
	{
		lineNumber++;
		if (String.IsNullOrEmpty(line)) continue;
		
		var match= Regex.Match(line, @"^[^\s].+");

		if(match.Success)
		{
			var model = new ModelDefinition
			{
				Name = line,
				Properties = GenerateProperties(lineNumber, lines)
			};

			result.Add(model);
		}
	}
	return result;
}

public PropertyDefinition[] GenerateProperties(int lineNumber, string[] lines)
{
	List<PropertyDefinition> properties = new List<PropertyDefinition>();

	for (var i = lineNumber + 1; i < lines.Length; i++)
	{
		var match = Regex.Match(lines[i], @"^\s+(?<name>[^:]+)(?<center>[\s]?:[\s]?)(?<type>[^\?\s\?]+)");

		if (match.Success)
		{
				properties.Add(new PropertyDefinition(match.Groups["name"].Value, match.Groups["type"].Value ));
		}
		else
		{
			break;
		}
	}
	return properties.ToArray();
}
#>

Models.tt

<#@ include file="TemplateManager.tt" #>
<#
string path = this.Host.ResolvePath(@"..\Definitions\Models.txt");
var definitions= File.ReadLines(path).ToArray();
#>
//This code is auto-generated. Changes to this file will be lost! 
using System;

namespace TextTran.Transformations.Models
{
<#
List<ModelDefinition> models = GenerateModels(definitions); 
foreach (var model in models)
{
#>
	public class <#= model.Name #>
	{
<#
	foreach (var property in model.Properties)
	{
#>
		public <#= property.Type #> <#= property.Name #> { get; set; }

<# 
	}
#>
	}

<#
}
#>
}

Have fun experimenting!