Friday 7 July 2023

Mocking Http Client used for Blazor apps using bUnit

This article will look at running http client calls used by Blazor apps using bUnit. First off, bUnit is a library to perform unit tests for Blazor apps. We will look at mocking http client calls in this article. I have added a Github repo with the sample code in this article here :

https://github.com/toreaurstadboss/BlazorHttpClientMocking
Setting up the project Nuget package references of the test project - BlazorHttpClientMocking.Test
 

    <PackageReference Include="bunit" Version="1.21.9" />
    <PackageReference Include="FluentAssertions" Version="6.11.0" />
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
    <PackageReference Include="Moq" Version="4.18.4" />
    <PackageReference Include="RichardSzalay.MockHttp" Version="6.0.0" />
    <PackageReference Include="xunit" Version="2.4.2" />
    <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
      <PrivateAssets>all</PrivateAssets>
    </PackageReference>
    <PackageReference Include="coverlet.collector" Version="3.2.0">
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
      <PrivateAssets>all</PrivateAssets>
    </PackageReference>
 
 
We will use the Nuget package RichardSzalay.MockHttp to do much of the mocking of http client. The following helper extension methods allow us to easier add mocking of http client calls.
 
 
Helper extension methods for http client using bUnit - MockHttpClientBunitHelpers.cs
 
using Bunit;
using Microsoft.Extensions.DependencyInjection;
using RichardSzalay.MockHttp;
using System.Net;
using System.Net.Http.Headers;
using System.Text.Json;

namespace BlazorHttpClientMocking.Test.Helpers
{
    public static class MockHttpClientBunitHelpers
    {

        public static MockHttpMessageHandler AddMockHttpClient(this TestServiceProvider services, string baseAddress = @"http://localhost")
        {
            var mockHttpHandler = new MockHttpMessageHandler();
            var httpClient = mockHttpHandler.ToHttpClient();
            httpClient.BaseAddress = new Uri(baseAddress);
            services.AddSingleton<HttpClient>(httpClient);
            return mockHttpHandler;
        }

        public static T? FromResponse<T>(this HttpResponseMessage? response, JsonSerializerOptions? options = null)
        {
            if (response == null)
            {
                return default(T);
            }
            if (options == null)
            {
                options = new JsonSerializerOptions
                {
                    PropertyNameCaseInsensitive = true
                };
            }
            string responseString = response.Content.ReadAsStringAsync().Result;
            var result = JsonSerializer.Deserialize<T>(responseString, options);
            return result;
        }

        public static async Task<T?> FromResponseAsync<T>(this HttpResponseMessage? response, JsonSerializerOptions? options = null)
        {
            if (response == null)
            {
                return await Task.FromResult(default(T));
            }
            if (options == null)
            {
                options = new JsonSerializerOptions
                {
                    PropertyNameCaseInsensitive = true
                };
            }
            string responseString = await response.Content.ReadAsStringAsync();
            var result = JsonSerializer.Deserialize<T>(responseString, options);
            return result;
        }

        public static MockedRequest RespondJson<T>(this MockedRequest request, T content)
        {
            request.Respond(req =>
            {
                var response = new HttpResponseMessage(HttpStatusCode.OK);
                response.Content = new StringContent(JsonSerializer.Serialize(content));
                response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
                return response;
            });
            return request;
        }

        public static MockedRequest RespondJson<T>(this MockedRequest request, Func<T> contentProvider)
        {
            request.Respond(req =>
            {
                var response = new HttpResponseMessage(HttpStatusCode.OK);
                response.Content = new StringContent(JsonSerializer.Serialize(contentProvider()));
                response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
                return response;
            });
            return request;
        }


    }
}

 
 
The method AddMockHttpClient, which is an extension method on TestServiceProvider adds the mocked client. In the code above we read the response into a string and deserialize with System.Text.Json, defaulting to case insensitive property naming, since this is default System.Text.Json on web, but not elsewhere, such as in test projects.


Helper methods for serialization - SerializationHelpers.cs
  
using System.Text.Json;

namespace BlazorHttpClientMocking.Test.Helpers
{
    public static class SerializationHelpers
    {

        public static async Task<T?> DeserializeJsonAsync<T>(string path, JsonSerializerOptions? options = null)
        {
            if (options == null)
            {
                options = new JsonSerializerOptions
                {
                    WriteIndented = true,
                    IncludeFields = true,
                    PropertyNameCaseInsensitive = true
                };
            }

            using (Stream stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
            {
                if (File.Exists(path) && stream.Length > 0)
                {
                    T? obj = await JsonSerializer.DeserializeAsync<T>(stream, options);
                    return obj;
                }
                return default(T);
            }

        }

    }
}

  

Let's look at a unit test which then sets up a mocked http client response that is used in the Blazor sample app on the FetchData page.


using BlazorHttpClientMocking.Test.Helpers;
using Bunit;
using FluentAssertions;
using Microsoft.Extensions.DependencyInjection;
using RichardSzalay.MockHttp;
using static BlazorHttpClientMocking.Pages.FetchData;

namespace BlazorHttpClientMocking.Test
{
    public class FetchDataTests
    {
        [Fact]
        public async Task FetchData_HttpClient_Request_SuccessResponse()
        {
            //Arrange 
            using var ctx = new TestContext();
            var httpMock = ctx.Services.AddMockHttpClient();
            string knownUrl = @"/sample-data/weather.json";
            var sampleData = await SerializationHelpers.DeserializeJsonAsync<WeatherForecast[]>(knownUrl.TrimStart('/')); //trimming start of url since we need a physical path
            httpMock.When(knownUrl).RespondJson(sampleData);

            //Act
            var httpClient = ctx.Services.BuildServiceProvider().GetService<HttpClient>();
            var httpClientResponse = await httpClient!.GetAsync(knownUrl);
            httpClientResponse.EnsureSuccessStatusCode();
            var forecasts = await httpClientResponse.FromResponseAsync<WeatherForecast[]>();

            //Assert 
            forecasts.Should().NotBeNull();
            forecasts.Should().HaveCount(5);
        }

    }
}


In the arrange part of the unit test above, we create a TestContext and add a mocked http client using the extension method shown earlier. We read out the sample json data and set up using the When method and remember to add "/" to the path as this is expected since we have a baseAddress specified on the http client, set to @"http://localhost" default.

We retrieve http client via the Services collection on the TestContext and call BuildServiceProvider and GetService method to get the http client with the mocking. The mocking must be done via the When method and then we get the client. The mocked http client is a singleton service here.

We can also do parameters in the mocking of http client calls.

Using parameters in http client calls

Lets first add parameter support for the Fetchdata razor page. Fetchdata.razor

 @page "/fetchdata/"
 @page "/fetchdata/{id:int}"
 
  
 
 @code {
    internal WeatherForecast[]? forecasts;

    [Parameter]
    public int? Id { get; set; }

    protected override async Task OnInitializedAsync()
    {
        forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>($"sample-data/weather.json");
        if (forecasts != null && Id >= 0 && Id < 5)
        {
            forecasts = forecasts.Skip(Id.Value).Take(1).ToArray();
        }
    }

    public class WeatherForecast
    {
        public DateOnly Date { get; set; }

        public int TemperatureC { get; set; }

        public string? Summary { get; set; }

        public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
    }
}
 



Let's now look at using parameters in mocked http client calls in another unit test.
 
 
         [Fact]
        public async Task FetchData_HttpClient_With_Parameter_Request_SuccessResponse()
        {
            //Arrange 
            using var ctx = new TestContext();
            var httpMock = ctx.Services.AddMockHttpClient();
            string knownUrl = @"/sample-data/weather.json/0";
            string fileUrl = @"sample-data/weather.json";

            var sampleData = await SerializationHelpers.DeserializeJsonAsync<WeatherForecast[]>(fileUrl); //trimming start of url since we need a physical path
            httpMock.When(knownUrl).RespondJson(sampleData);

            //Act
            var renderComponent = ctx.RenderComponent<FetchData>(p => p
                .Add(fd => fd.Id, 0));

            //Assert 
            renderComponent.Instance.forecasts.Should().NotBeNull();

            renderComponent.Instance.forecasts.Should().HaveCount(1);    
        }
    
 
 
Here we use bUnit's capabilities in rendering Blazor components using the RenderComponent method and we also set the Id parameter here to the value 0 which now will prepare our component with the right forecasts, here only one forecast will be shown. We use the Instance property to look at the forecasts field of the component. internal WeatherForecast[]? forecasts; So bUnit can be used both the mock http client calls and also render Blazor components and also support parametrized calls of mocked http client calls.


Finally a tip concerning letting your internal fields to be available for test project. In the csproj file of the application we can set it up like in this example :

  <ItemGroup>
		<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
			<_Parameter1>BlazorHttpClientMocking.Test</_Parameter1>
		</AssemblyAttribute>
  </ItemGroup>

Here we set up that the test project can see the internals of the blazor app project. This allows the test project to see internal methods and internal fields, internal classes and so on. This allows you to avoid changing parameters or fields in your components from private to public for example and instead change access modifier to internal so the tests can access those members.

Tuesday 4 July 2023

Writing cookies with Blazor WASM

This article presents some source code of how to write and read cookies from Blazor WebAssembly - WASM. For Blazor WASM, we are going to use Javascript to write these cookies. Blazor WASM has not an easy way to write these cookies programatically, as the use of HttpContext accessor is discouraged and not available, i.e. you cannot just add cookies without round trips to backend services.

But via Js, the client can write cookies. I looked into a helper lib to write such cookies and do so using different attribute values for the cookies.
The following Mozilla Developer Network (MDN) page is helpful in detailing cookies, which attribute values can be set on them. Cookies are used to give user experience since they track user's on a web site and give tailored user experience - and advertising - and also can track the users accross servers / web sites as third-party cookies. They are small string values that are either stored on clients inside cookie storage in the browser's folder on the user's hard drive or in memory or other place such as partitioned cookies.

MND page - document.cookies

https://developer.mozilla.org/en-US/docs/Web/API/Document/cookie First off, the source code is here:

Forked Repo with adaptions in cookie handling for Blazor WASM

https://github.com/toreaurstadboss/AltairCA.Blazor.WebAssembly.Cookie This is a forked repo from this repo:

Original repo with cookie handling for Blazor WASM

https://github.com/AltairCA/AltairCA.Blazor.WebAssembly.Cookie The most recent branch of mine is this one: https://github.com/toreaurstadboss/AltairCA.Blazor.WebAssembly.Cookie/blob/feature/more-cookie-keys/README.md
Let's first look at the interface for the util class that will write cookies :

Interface for cookie handling for Blazor.wasm, IAltairCABlazorCookieUtil.cs

 
 
using AltairCA.Blazor.WebAssembly.Cookie.Models;

namespace AltairCA.Blazor.WebAssembly.Cookie
{
    public interface IAltairCABlazorCookieUtil
    {
        /// <summary>
        /// Set a object in the cookie
        /// </summary>
        /// <param name="key">The key for the cookie (name)</param>
        /// <param name="value">Cookie value</param>
        /// <param name="span">TimeSpan that will be set to the 'expires' attribute value</param>
        /// <param name="path">Path in the request url which must exist for the cookie to be sent in requests </param>
        /// <param name="domain">The host to which the cookie will be sent</param>
        /// <param name="secure">Specifies that the cookie will be sent only over secure protocols</param>
        /// <param name="isSession">Flags the cookie as a session cookie (temporal) by setting the 'expires' attribute value to ''</param>
        /// <param name="partitioned">Requires that the browser has activated partitioned cookies</param>
        /// <param name="maxAgeInSeconds">Maximum age in seconds</param> 
        /// <returns></returns>
        Task SetValueAsync(string key, object value, TimeSpan? span = null, string? path = null,
            string? domain = null, bool? secure = null, SameSite? sameSite = null, bool? partitioned = null, 
            bool? isSession = null, int? maxAgeInSeconds = null);

        /// <summary>
        /// Set a string in the cookie
        /// </summary>
        /// <param name="key">The key for the cookie (name)</param>
        /// <param name="value">Cookie value</param>
        /// <param name="span">TimeSpan that will be set to the 'expires' attribute value</param>
        /// <param name="path">Path in the request url which must exist for the cookie to be sent in requests </param>
        /// <param name="domain">The host to which the cookie will be sent</param>
        /// <param name="secure">Specifies that the cookie will be sent only over secure protocols</param>
        /// <param name="isSession">Flags the cookie as a session cookie (temporal) by setting the 'expires' attribute value to ''</param>
        /// <param name="partitioned">Requires that the browser has activated partitioned cookies</param>
        /// <param name="maxAgeInSeconds">Maximum age in seconds</param> 
        /// <returns></returns>
        Task SetValueAsync(string key, string value, TimeSpan? span = null, string? path=null, string? domain=null, 
            bool? secure = null, SameSite? sameSite = null, bool? partitioned = null, bool? isSession = null,
            int? maxAgeInSeconds = null);
       
        Task<string> GetValueAsync(string key);
       
        Task<T> GetValueAsync<T>(string key) where T : class;
        
        Task RemoveAsync(string key, string? path = null);

    }
}
 
 
And here is the implementation.

Implementation for cookie handling for Blazor.wasm, AltairCABlazorCookieUtil.cs

 
 
using System.ComponentModel;
using AltairCA.Blazor.WebAssembly.Cookie.Models;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.JSInterop;
using Newtonsoft.Json;

namespace AltairCA.Blazor.WebAssembly.Cookie
{

    internal class AltairCABlazorCookieUtil : IAltairCABlazorCookieUtil
    {
        readonly IJSRuntime JSRuntime;
        private readonly AltairCABlazorCookieConfigOptions _settings;
        public AltairCABlazorCookieUtil(IJSRuntime jsRuntime,IOptions<AltairCABlazorCookieConfigOptions> options)
        {
            JSRuntime = jsRuntime;
            _settings = options.Value;
        }

        public Task SetValueAsync(string key, object value, TimeSpan? span = null, string? path = null,
            string? domain = null, bool? secure = null, SameSite? sameSite = null, bool? partitioned = null, bool? isSession = null,
            int? maxAgeInSeconds = null)
        {
            return SetValueAsync(key, JsonConvert.SerializeObject(value), span, path, domain, secure, sameSite, partitioned,
                isSession);
        }
        public async Task SetValueAsync(string key, string value, TimeSpan? span = null, string? path=null, string? domain=null, 
            bool? secure = null, SameSite? sameSite = null, bool? partitioned = null, bool? isSession = null,
             int? maxAgeInSeconds = null)
        {
            if (string.IsNullOrWhiteSpace(path))
                path = _settings.Path;
            if (!span.HasValue)
                span = _settings.DefaultExpire;
            if (string.IsNullOrWhiteSpace(domain))
                domain = _settings.Domain;
            if (!secure.HasValue)
                secure = _settings.IsSecure;
            
            var curExp = span.HasValue && span.Value.Ticks > 0 && isSession != true && !maxAgeInSeconds.HasValue ?  DateToUTC(span.Value) : "";            
            
            List<string> keyvals = new List<string>();
            keyvals.Add($"{key}={value}");
            keyvals.Add($"expires={curExp}");
            keyvals.Add($"path={path}");
            if(!string.IsNullOrWhiteSpace(domain))
                keyvals.Add($"domain={domain}");
            if(secure.HasValue && secure.Value)
                keyvals.Add("secure");
            if (maxAgeInSeconds.HasValue && isSession != true)
            {
                keyvals.Add($"max-age={maxAgeInSeconds.Value}");
            }
            if (sameSite.HasValue){
                DescriptionAttribute desc = (DescriptionAttribute) typeof(SameSite).GetMember(sameSite.Value.ToString()).First().GetCustomAttributes(typeof(DescriptionAttribute), false).First();
                keyvals.Add($"samesite={desc.Description}");
            } 
            string cookieToSet = string.Join(";", keyvals);
            if (partitioned == true){
                cookieToSet += ";partitioned";
            }
            
            await SetCookie(cookieToSet);
        }

        public async Task RemoveAsync(string key,string path = null)
        {
            if (string.IsNullOrWhiteSpace(path))
                path = _settings.Path;
            List<string> keyvals = new List<string>();
            keyvals.Add($"{key}=");
            keyvals.Add($"Path={path}");
            keyvals.Add($"expires=Thu, 01 Jan 1970 00:00:01 GMT;");
            await SetCookie(string.Join(";", keyvals));
        }

        public async Task<T> GetValueAsync<T>(string key) where T : class
        {
            var res = await GetValueAsync(key);
            if (res == null)
                return default(T);
            return JsonConvert.DeserializeObject<T>(res);
        }
        public async Task<string> GetValueAsync(string key)
        {
            var cValue = await GetCookie();
            if (string.IsNullOrEmpty(cValue)) return null;                

            var vals = cValue.Split(';');
            foreach (var val in vals)
                if(!string.IsNullOrEmpty(val) && val.IndexOf('=') > 0)
                    if(val.Substring(0, val.IndexOf('=')).Trim().Equals(key, StringComparison.OrdinalIgnoreCase))
                        return val.Substring(val.IndexOf('=') + 1);
            return null;
        }

        private async Task SetCookie(string value)
        {
            await JSRuntime.InvokeVoidAsync("eval", $"document.cookie = \'{value}\'");
        }

        private async Task<string> GetCookie()
        {
            return await JSRuntime.InvokeAsync<string>("eval", $"document.cookie");
        }
        private static string DateToUTC(TimeSpan span) => DateTime.Now.Add(span).ToUniversalTime().ToString("R");
    }
}


//we also have this enum used in cookie handler class above

using System.ComponentModel;

namespace AltairCA.Blazor.WebAssembly.Cookie.Models;

public enum SameSite {
    
    [Description("lax")]
    Lax = 0,

    [Description("strict")]
    Strict = 1,

    [Description("none")]
    None = 2
    
}
 
 


The MDN article details a lot around cookies and there are a lot of way of controlling these cookies via attributes. Most browsers limit Cookie sizes to be 4 kilobytes maximum length (4096 bytes) of all cookies on a server. And a maxiumum of 50 cookies, still must be below the 4 kB limit. https://stackoverflow.com/a/4604212 We set up the cookie handling via Program.cs of a Blazor WASM like this :

Implementation for cookie handling for Blazor.wasm, AltairCABlazorCookieUtil.cs

 

builder.Services.AddAltairCACookieService(options =>
{
    options.DefaultExpire = TimeSpan.FromMinutes(15);
});




This extension method on IServiceCollection adds the cookie handling as a singleton service, AltairCABlazorCookieUtil.cs

 

using AltairCA.Blazor.WebAssembly.Cookie.Models;
using Microsoft.Extensions.DependencyInjection;

namespace AltairCA.Blazor.WebAssembly.Cookie.Framework;

public static class PipelineExtension
{
    public static IServiceCollection AddAltairCACookieService(this IServiceCollection services,Action<AltairCABlazorCookieConfigOptions> configure)
    {
        services.Configure(configure);
        services.AddSingleton<IAltairCABlazorCookieUtil, AltairCABlazorCookieUtil>();
        return services;
    } 
}

//note - the extension method above also uses this model class to set up default options for cookies 

namespace AltairCA.Blazor.WebAssembly.Cookie.Models;

public class AltairCABlazorCookieConfigOptions
{
    public TimeSpan DefaultExpire { get; set; } = TimeSpan.Zero;
    public string Path { get; set; } = "/";
    public string Domain { get; set; } = string.Empty;
    public bool IsSecure { get; set; } = false;
}



As can be seen in the implementation, these default config options are injected in the constructor via :

AltairCABlazorCookieUtil.cs - constructor parameter injecting the options which was set via services.Configure in the extension method of the pipeline

 
public AltairCABlazorCookieUtil(IJSRuntime jsRuntime,IOptions<AltairCABlazorCookieConfigOptions> options)
{
	JSRuntime = jsRuntime;
    _settings = options.Value;
}

As we can see, we remove the cookie by setting it to expire at Unix Epoch zero (1970, 1st of January) in the cookie handling. A good util to inspect Cookies and even edit them are available in this Google plugin: Edit this Cookie




Counter.razor - using the Cookie util in Blazor WASM sample app

 
 
 @inject IAltairCABlazorCookieUtil _cookieUtil;
 
  
 
 
private async Task IncrementCount()
    {
        currentCount++;
        Content = await _cookieUtil.GetValueAsync("c");
        ContentObj = await _cookieUtil.GetValueAsync<object>("d");

        await _cookieUtil.SetValueAsync("c", "this is cookie with key c");
        await _cookieUtil.SetValueAsync("d", new
        {
            hello = "hello world. i am a cookie with key d"
        });
        await _cookieUtil.SetValueAsync("cookieWithSameSiteSet", "Cookie which specified cross site request inclusion of the cookie : SameSite value (lax | strict | none)", sameSite: SameSite.Lax);

        await _cookieUtil.SetValueAsync("cookiePartitioned", "Partitioned cookie", partitioned: true);
     
        await _cookieUtil.SetValueAsync("cookieWithMaxAge", "Max age cookie", maxAgeInSeconds: 600);

        await _cookieUtil.SetValueAsync("cookieWhichIsToBeSetToSessionCookie", "Session cookie = temporal cookie", isSession: true);

    }
  
One important note about the cookie util here, observe the usage of the 'eval' method to set the cookie via Js in the util class and also retrieve cookies :

Implementation for cookie handling for Blazor.wasm, AltairCABlazorCookieUtil.cs

 
 
   private async Task SetCookie(string value)
        {
            await JSRuntime.InvokeVoidAsync("eval", $"document.cookie = \'{value}\'");
        }

        private async Task<string> GetCookie()
        {
            return await JSRuntime.InvokeAsync<string>("eval", $"document.cookie");
        }
 
 


Using 'eval' we let Js run the Js code we pass in here in Blazor WASM app. This also means that we cannot write HttpOnly cookies, since we rely fully on Js here.

As some of you know, third party cookie support are planned by Chrome to be discontinued in support. The following article is interesting reading about this. https://itrust-digital.com/cookieless-future/

Saturday 17 June 2023

Generic factory in C#

I tested out methods for building a simple generic factory in C#. This is using reflection to instantiate objects. It shows how we can combine some central methods in reflection to instantiate objects from types, they either being non-generic types, generic types which are either closed generic types or open generic types :
  • Activator.CreateInstance to instantiate the objects
  • MakeGenericType to close the generic type, which must be an open generic type
  • IsGenericTypeDefinition to check if the type is an open generic type
Consider this code where we attempt to make a generic type from an already closed type, it gives an InvalidOperationException telling us that we must check that IsGenericTypeDefinition is true on the type.

var someconcrete = typeof(Dictionary<string, int>);
var foo = someconcrete.MakeGenericType();

The Generic factory could of course be complex, support a multitude of scenarios, including resolving constructor arguments and their dependencies. This is more just demonstration code how you could instantiate objects via either closed or open generics in .NET. Observe that Activator.CreateInstance got a lot of different possible overloads.

Generic factory pattern in C# using reflection - simple approach






public static class GenericFactory {

	public static object CreateInstance<T>(Type type){		
		if (type.IsGenericTypeDefinition){
			var closedGenericType = type.MakeGenericType(typeof(T));	
			return Activator.CreateInstance(closedGenericType);			
		}		
		return Activator.CreateInstance<T>();	
	}

	public static TReturn CreateInstance<T, TReturn>(Type type)
	{
		if (type.IsGenericTypeDefinition)
		{
			var closedGenericType = type.MakeGenericType(typeof(T));
			return (TReturn) Activator.CreateInstance(closedGenericType);
		}
		return (TReturn) Activator.CreateInstance<TReturn>();
	}

	public static TReturn CreateInstance<T1, T2, TReturn>(Type type, params object[] args)
	{
		if (type.IsGenericTypeDefinition)
		{
			var closedGenericType = type.MakeGenericType(typeof(T1), typeof(T2));
			return (TReturn)Activator.CreateInstance(closedGenericType, args);
		}
		return (TReturn)Activator.CreateInstance(type, args);
	}

	public static object CreateInstance(Type type, Type[]genericTypeArguments, params object[] args)
	{
		if (type.IsGenericTypeDefinition)
		{
			var closedGenericType = type.MakeGenericType(genericTypeArguments);
			return Activator.CreateInstance(closedGenericType, args);
		}
		return Activator.CreateInstance(type, args);
	}

	public static TReturn CreateInstance<TReturn>(Type type, Type[] genericTypeArguments, params object[] args)
	{
		if (type.IsGenericTypeDefinition)
		{
			var closedGenericType = type.MakeGenericType(genericTypeArguments);
			return (TReturn) Activator.CreateInstance(closedGenericType, args);
		}
		return (TReturn) Activator.CreateInstance(type, args);
	}

	public static TReturn CreateInstance<T, TReturn>(Type type, params object[] args)
	{
		if (type.IsGenericTypeDefinition)
		{
			var closedGenericType = type.MakeGenericType(typeof(T));
			return (TReturn)Activator.CreateInstance(closedGenericType, args);
		}
		return (TReturn)Activator.CreateInstance(type, args);
	}

	public static T CreateInstance<T>(){
		return (T) Activator.CreateInstance(typeof(T));
	}
	
	public static T CreateInstance<T>(params object[] args){
		return (T) Activator.CreateInstance(typeof(T), args);
	}
	
	
}


Next up, some sample code to test out these helper methods. The code shows that there really is few methods involved to create instances from open generic types or closed generic types. The three mentioned methods and properties at the top of this article.

Sample code using Generic factory



  
  
void Main()
{
	
	//var someconcrete = typeof(Dictionary<string, int>);
	//var foo = someconcrete.MakeGenericType();
	
	var redCar = GenericFactory.CreateInstance<Car>();
	redCar.Color = Colors.Red;
	redCar.Model = "A5";
	redCar.Make = "Audi";
	redCar.Dump("The red car was created using default constructor and reflection with Activator.CreateInstance");
	
	var blueCar = GenericFactory.CreateInstance<Car>("Tesla", "Model X", Colors.Blue);
	blueCar.Dump("The blue car was created using constructor arguments matching the closest constructor with reflection and Activator.CreateInstance");
	
	var carPool = GenericFactory.CreateInstance<Car>(typeof(VehiclePool<>));
	carPool.Dump("Empty carpool which is of type object was created with reflection passing in an open generic type and specifying the type to close the generic with using MakeGenericType");

	var carPool3 = GenericFactory.CreateInstance<Car, VehiclePool<Car>>(typeof(VehiclePool<>));
    carPool3.AddVechicle(1, redCar);
	carPool3.AddVechicle(2, blueCar);
	carPool3.Dump("CarPool casted to specific type VehiclePool<Car> contains these cars - it was created using open generic type specifying a type to close the generic with with MakeGenericType:");
	
	var dictionaryOfIntAndString = GenericFactory.CreateInstance<int, string, Dictionary<int, string>>(typeof(Dictionary<,>));

	dictionaryOfIntAndString[0] = "Audi A5";
	dictionaryOfIntAndString[1] = "Audi A8";
	dictionaryOfIntAndString[2] = "Audi RS8";
	
	dictionaryOfIntAndString.Dump("Dictionary<int,string> contains these cars. It was constructed using an open generic type Dictionary<,> and passing in the generic type arguments of int and string using Activator.CreateInstance with MakeGenericType");
		
}
  
    
The sample code uses these types :

Sample types using Generic factory



  
  
  

public class VehiclePool<T> : IPool<T>{
	private Dictionary<int, T> _pool = new Dictionary<int, T>();
	
	public Dictionary<int, T> Pool {
		get {
			return _pool;
		}
	}
	public VehiclePool()
	{
	}

	public void AddVechicle(int vehicleId, T vehicle)
	{
		if (!_pool.ContainsKey(vehicleId)){
			_pool.Add(vehicleId, vehicle);
		}
	}

	public T GetVehicle(int vehicleId)
	{
		if (_pool.ContainsKey(vehicleId)){
			return _pool[vehicleId];
		}
		return default(T);
	}

	public void RemoveVehicle(int vehicleId)
	{
		if (_pool.ContainsKey(vehicleId))
		{
			_pool.Remove(vehicleId);
		}
		else
		{
			throw new ArgumentException($"Vehicle with {vehicleId} does not exist");
		}
	}
}

public interface IPool<T> {
	void AddVechicle(int vehicleId, T vehicle);
	void RemoveVehicle(int vehicleId);
	T GetVehicle(int vehicleId);
}

public class Car {
	
	public string Model { get; set; }
	public string Make { get; set; }
	public int WheelCount { get; set; } = 4;
	public Color Color { get; set; }
	
	public Car()
	{		
	}
	
	public Car(string make, string model, Color color)
	{
		Make = make;
		Model = model;
		Color = color;
	}
}

  
  
Here is the output.

Sample code using Generic factory



Note that Activator.CreateInstance returns object, so if you want a strongly typed object, you should specify the return type, TReturn in the code sample above. You could consider using dynamic here, but then you loose the Intellisense if you want to avoid unboxing the object returned to a specific type. Then we would use a very basic method first like this:
 
 

public static T CreateInstance<T>(params object[] args){
		return (T) Activator.CreateInstance(typeof(T), args);
}
 
 
Using dynamic we can ignore casting the object to a specific type and continue using late binding, which we use already with reflection.

    dynamic redCar = GenericFactory.CreateInstance(typeof(Car));
	redCar.Color = Colors.Red;
	redCar.Model = "A5";
	redCar.Make = "Audi";
	redCar.Dump("The red car was created using default constructor and reflection with Activator.CreateInstance");