Generic Dependency Injection on .Net 7.0

Bora Kaşmer
8 min readJun 27, 2023

Hi,
Today we will talk about, using Generic Dependency Injection while implementing Strategy Design Pattern on .Net 7.0.

Firstly why do we need Strategy Pattern? Because in this scenario, we have to use different Analyze Tools. But all services are working for the same purpose and have the same methods. They are all investigating malicious emails. We have to avoid repeated and unreadable codes.

In the beginning, we will create a base Service Request class.

BaseServiceRequest: Every “Analyze Service Request” inherit from this class.

namespace InterestingDependencyInjection.Analyze
{
public class BaseServiceRequest
{

}
}

ServiceResult: This is the resulting model of every “Analyze Service Return Model”.

namespace InterestingDependencyInjection.Analyze
{
using System.Collections.Generic;

public class ServiceResult
{
public ResultStatus Status { get; set; }
public string Message { get; set; }
public List<string> ValidationMessages { get; set; } = new List<string>();
public bool IsSuccess { get; set; };
}

public class ServiceResult<T> : ServiceResult where T : class
{
public T Data { get; set; }
}

public enum ResultStatus
{
Successful,
Created,
Failed,
BadRequest
}

public class IdAnalize
{
public string AnalizeId { get; set; } = string.Empty;
}
}

IAnalyze: Every Analyze service inherits from the IAnalyze interface. So we know every common service’s methods.

using System.Dynamic;

namespace InterestingDependencyInjection.Analyze
{
public interface IAnalyze
{

ServiceResult<ExpandoObject> Create<T>(T request) where T : BaseServiceRequest;
ServiceResult<ExpandoObject> Update<T1>(T1 request) where T1 : BaseServiceRequest;
ServiceResult<IdAnalize> Delete(string analizeId);
ServiceResult<IdAnalize> Get(string analizeId);
}
}

“Champions keep playing until they get it right.” – Billie Jean King

Now let’s create first dummy AnyRunAnalize class:

AnyRunAnalyze: We will inherit AnyRun class from the IAnalyze interface. And we will implement all crud dummy methods. We get the BaseServiceRequest parameter and return the ServiceResult model.

This is AnyRunAnalyze services so we know the type of request. It must be the AnyRunRequest model and we will cast the request to the AnyRunRequest. But we will declare the “Create<T>() and Update<T1>” methods’ paramere as a “BaseServiceRequest”. Because every request is Inherited from this request model. We will declare these methods’ parameters as a “BaseServiceRequest” in AnalyzeAdaptor. So we can give any type request parameter which is inherited from “BaseServiceRequest” for every Analyze Service.

using System.Dynamic;

namespace InterestingDependencyInjection.Analyze
{
public class AnyRunAnalyze : IAnalyze
{
public ServiceResult<ExpandoObject> Create<T>(T request) where T : BaseServiceRequest
{
//Create Analize Operation
var serviceRequest = request as AnyRunRequest;
Console.WriteLine($"Create AnyRun Analyze {serviceRequest.AnyRunId}");
return new ServiceResult<ExpandoObject>() { IsSuccess = true };
}

public ServiceResult<IdAnalize> Delete(string analizeId)
{
//Delete Analize Operation
Console.WriteLine("Delete AnyRun Analyze");
return new ServiceResult<IdAnalize>();
}

public ServiceResult<IdAnalize> Get(string analizeId)
{
//Get Analize Operation
Console.WriteLine("Get AnyRun Analyze");
return new ServiceResult<IdAnalize>();
}

public ServiceResult<ExpandoObject> Update<T1>(T1 request) where T1 : BaseServiceRequest
{
//Update Analize Operation
var serviceRequest = request as AnyRunRequest;
Console.WriteLine($"Update AnyRun Analyze: {serviceRequest.AnyRunId}");
return new ServiceResult<ExpandoObject>() { IsSuccess = true };
}
}
}

VirusTotalAnalyze: This is a similar analyze class of “AnyRun”. I wrote these two dummy classes for giving an example of the Strategy Design pattern.

This is VirusTotalAnalyze services so we know the type of request. It must be the VirusTotelRequest model and we will cast the request to the VirusTotelRequest.

using System.Dynamic;

namespace InterestingDependencyInjection.Analyze
{
public class VirusTotalAnalyze : IAnalyze
{
public ServiceResult<ExpandoObject> Create<T>(T request) where T : BaseServiceRequest
{
//Create Analize Operation
var serviceRequest = request as VirusTotalRequest;
Console.WriteLine($"Create VirusTotalAnalyze Analyze {serviceRequest.VirusTotalId}");
return new ServiceResult<ExpandoObject>() { IsSuccess = true };
}

public ServiceResult<IdAnalize> Delete(string analizeId)
{
//Delete Analize Operation
Console.WriteLine("Delete VirusTotalAnalyze Analyze");
return new ServiceResult<IdAnalize>();
}

public ServiceResult<IdAnalize> Get(string analizeId)
{
//Get Analize Operation
Console.WriteLine("Get VirusTotalAnalyze Analyze");
return new ServiceResult<IdAnalize>();
}

public ServiceResult<ExpandoObject> Update<T1>(T1 request) where T1 : BaseServiceRequest
{
//Update Analize Operation
var serviceRequest = request as VirusTotalRequest;
Console.WriteLine($"Update VirusTotalAnalyze Analyze {serviceRequest.VirusTotalId}");
return new ServiceResult<ExpandoObject>() { IsSuccess = true };
}
}
}

What is the Main Problem Of Dependency Inject While Implementing The Strategy Design Pattern?

Strategy Desing Pattern is not the main topic of this article. But if you want to more details about design patterns you can check my this article: https://medium.com/swlh/refactoring-for-clean-code-with-design-patterns-2d3d754c3bfe

https://www.robertblack.com.au

It is a behavioral software design pattern that enables selecting an algorithm at runtime. But the main problem is, how can we rise service or any class on runtime?

Firstly we need an Adaptor for implementing one of the Analyze services by the business case.

IAnalyzeAdaptor.cs: We will use Dependency Injection to automatically injects AnalyzeAdaptor into the service’s constructor too. So we have to inherit AnalyzAdaptor class from an Interface.

using System.Dynamic;

namespace InterestingDependencyInjection.Analyze
{
public interface IAnalyzeAdaptor<T> where T : IAnalyze
{
ServiceResult<ExpandoObject> Create<T1>(T1 request) where T1 : BaseServiceRequest;
ServiceResult<ExpandoObject> Update<T1>(T1 request) where T1 : BaseServiceRequest;
ServiceResult<IdAnalize> Delete(string analizeId);
ServiceResult<IdAnalize> Get(string analizeId);
}
}

AnalyzeAdaptor.cs: We will use this adaptor for all kinds of Analyze Services. So we can manage all of them in one place. We have only one rule, all Analyze Service must inherit from the “IAnalze” interface. <T> type is our Analyze service. We will call the common “IAnalyze” Service method on every AnalyzeAdaptor’s method.

If you notice, the method names of AnalyzeAdaptor and the method names of the Analyze class are used the same to increase code readability. One more thing, “Create<T1>() and Update<T1>() methods are waiting “BaseServiceRequest” parameter.

using System.Dynamic;

namespace InterestingDependencyInjection.Analyze
{
public class AnalyzeAdaptor<T> : IAnalyzeAdaptor<T> where T : IAnalyze
{
readonly T _analyze;

public AnalyzeAdaptor(T analyze)
{
_analyze = analyze;
}
public ServiceResult<ExpandoObject> Create<T1>(T1 request) where T1 : BaseServiceRequest
{
return _analyze.Create(request);
}

public ServiceResult<IdAnalize> Delete(string analizeId)
{
return _analyze.Delete(analizeId);
}

public ServiceResult<IdAnalize> Get(string analizeId)
{
return _analyze.Get(analizeId);
}

public ServiceResult<ExpandoObject> Update<T1>(T1 request) where T1 : BaseServiceRequest
{
return _analyze.Update(request);
}
}
}

Your service Request could be different for every service. But if all requests are inherited from a base class, you can use these request parameters generally in AnalyzeAdaptor.
We will make request classes for AnyRun and VirusTotal services. Both of them are inherited from BaseServiceRequest class.

AnyRunRequest.cs:

namespace InterestingDependencyInjection.Analyze
{
public class AnyRunRequest: BaseServiceRequest
{
public int AnyRunId { get; set; }
public int ScheduleTypeId { get; set; }
public DateTime? ScheduledDate { get; set; }
public int Duration { get; set; }
public byte[] Data { get; set; }
}
}

VirusTotalRequest:

namespace InterestingDependencyInjection.Analyze
{
public class VirusTotalRequest : BaseServiceRequest
{
public int VirusTotalId { get; set; }
public int RepatCount { get; set; }
public bool isAttachmentInclude { get; set; }
public byte[] Data { get; set; }
}
}

Program.cs (DependencyInjection): We will use Scoped service lifetime, which will create and share an instance of the service per request to the application for AnalyzeAdaptor. But for AnyRun and VirusTotalAnalyze, we will use Transient service lifetime, which will create and share an instance of the service every time the application when we ask for it.

If you notice, AnyRun and VirusTotal services are not derived from any interface. Because we will use these class directly.

.
.
builder.Services.AddScoped(typeof(IAnalyzeAdaptor<>), typeof(AnalyzeAdaptor<>));
builder.Services.AddTransient<AnyRunAnalyze>();
builder.Services.AddTransient<VirusTotalAnalyze>();
.
.

Now it is time to use AnalyzeAdaptor for VirusTotal and AnyRun Controllers.

AnyRunController: We will create “Create()” and “Update()” endpoints for AnyRun analysis.

Generic DecencyInjection for AnyRun

1-) As seen above, we will create AnalyzeAdaptor with the “<AnyRunAnalyze>” type in AnyRunController. We will Inject this class into the AnyrunController with the Generic AnyRunAnalyze service, which is inherited from “IAnalyze”. So we don’t have to create a new instance of the “AnyRunAnalyze” class anymore.

2-) As seen above, when we call Create() method of analyzeAdaptor, actually we will trigger the “AnyRunAnalyze” service’s Create() method.

using InterestingDependencyInjection.Analyze;
using Microsoft.AspNetCore.Mvc;

namespace InterestingDependencyInjection.Controllers
{
[ApiController]
[Route("[controller]")]
public class AnyRunController : ControllerBase
{
readonly IAnalyzeAdaptor<AnyRunAnalyze> _analyzeAdaptor;
public AnyRunController(IAnalyzeAdaptor<AnyRunAnalyze> analyzeAdaptor)
{
_analyzeAdaptor = analyzeAdaptor;
}

[HttpPost]
[Route("create")]
public bool CreateAnalyze(AnyRunRequest request)
{
var result = _analyzeAdaptor.Create(request);
return result.IsSuccess;
}
[HttpPost]
[Route("delete")]
public bool UpdateAnalyze(AnyRunRequest updateRequest)
{
var result = _analyzeAdaptor.Update(updateRequest);
return result.IsSuccess;
}
}
}

AnyRun Create() Result:

“The way to get started is to quit talking and begin doing.”

– Walt Disney

VirusTotalController: We will create the “Create()” and “Update()” service for VirusTotal analys.

Generic DecencyInjection for VirusTotal

1-) As seen above, we will create the same AnalyzeAdaptor as AnyRunController with the “<VirusTotalAnalyze>” type in VirusTotalController. We will Inject this class into the VirusTotalController with the Generic VirusTotalAnalyze service, which is inherited from “IAnalyze”. So we don’t have to create a new instance of the “VirusTotalAnalyze” class anymore.

using InterestingDependencyInjection.Analyze;
using Microsoft.AspNetCore.Mvc;

namespace InterestingDependencyInjection.Controllers
{
[ApiController]
[Route("[controller]")]
public class VirusTotalController : ControllerBase
{
readonly IAnalyzeAdaptor<VirusTotalAnalyze> _analyzeAdaptor;
public VirusTotalController(IAnalyzeAdaptor<VirusTotalAnalyze> analyzeAdaptor)
{
_analyzeAdaptor = analyzeAdaptor;
}

[HttpPost]
[Route("create")]
public bool CreateAnalyze(VirusTotalRequest request)
{
var result = _analyzeAdaptor.Create(request);
return result.IsSuccess;
}
[HttpPost]
[Route("delete")]
public bool UpdateAnalyze(VirusTotalRequest updateRequest)
{
var result = _analyzeAdaptor.Update(updateRequest);
return result.IsSuccess;
}
}
}

VirusTotal Create() Result:

Conclusion:

In this article, we tried to implement a Strategy design pattern with Generic Dependency Injection in .Net 7.0. Our first goal was, to create Generic Adaptor Class without creating an instance of <T> type class => “public class AnalyzeAdaptor<T>”

In the beginning, we will create every service in “program.cs” with dependency injection. Later we created a generic Adaptor service. Our goal is to manage all Analyze services in one place. Later if we need any validation or maybe logging, we can manage the methods in one place for every analysis service. In every Analyze controller, we injected AnalyzeAdaptor with its own analyze service class. This was the main topic of this article.

Every AnalyzeService class has its own Create and Update methods. They were waiting for a specific request for a Parameter class. We inherited every Request class from the BaseServiceRequest class and cast it when we need it in any service method.

By using Strategy Design Pattern, we can implement new any type analyze service without changing the main codes. And also by using dependency injection, we don’t have to create new instances for every analyze class.

“Life is ten percent what happens to you and ninety percent how you respond to it.” -Lou Holtz

See you until the next article.

“If you have read so far, first of all, thank you for your patience and support. I welcome all of you to my blog for more!”

Github Source: https://github.com/borakasmer/GenericDependencyInjection

--

--

Bora Kaşmer

I have been coding since 1993. I am computer and civil engineer. Microsoft MVP. Software Architect(Cyber Security). https://www.linkedin.com/in/borakasmer/