Dependency injection (DI) används mycket frekvent i ASP.NET Core och detta inlägg försöker förklara hur denna teknik används. Dependency injection är ett designmönster som gör kod enklare att underhålla.
Dependency injection syftar till att göra klasser oberoende av deras beroenden, en klass är beroende av gränssnitt (interfaces) men klienten bestämmer vilken version av gränssnittet som skickas till klassen. Klienten har kontroll över att instansiera klasserna (Inversion of Control), klassen använder ett gränssnitt och behöver inte instansiera de gränssnitt som klassen är beroende av.
Dependency injection bygger på gränssnitt istället för klasser och det gör det lättare att ändra implementeringen av dessa gränssnitt bara genom att plugga in en annan klass som implementerar gränssnittet. Beroenden registreras normalt i en servicebehållare vid start och ramverket tar hand om att skapa nya instanser av dessa beroenden.
När du skapar en ny klass med DI i ASP.NET så lägger du till de gränssnitt som klassen behöver i klassens konstruktor. Man kan säga att dessa gränssnitt injiceras i klassen. Du kan använda dessa beroenden i din klass utan att behöva skapa nya instanser av dem. Ramverket hanterar skapandet av beroenden.
Skapa en klass med beroenden
Vi vill skapa en klass som hanterar statiska sidor på vår hemsida och den här klassen beror på andra klasser i vårt projekt. Vi börjar med att skapa ett gränssnitt för vår klass.
public interface IStaticPageRepository
{
Int32 Add(StaticPage post);
void Update(StaticPage post);
Int32 GetCountBySearch(string[] keywords);
StaticPage GetOneById(Int32 id);
StaticPage GetOneByConnectionId(byte connectionId);
StaticPage GetOneByPageName(string pageName);
IList<StaticPage> GetAll(string sortField, string sortOrder);
IList<StaticPage> GetAllActive(string sortField, string sortOrder);
IList<StaticPage> GetAllActiveLinks(string sortField, string sortOrder);
IList<StaticPage> GetBySearch(string[] keywords, Int32 pageSize, Int32 pageNumber, string sortField, string sortOrder);
Task<IList<NewsItem>> GetNewsFromRSS(string searchString);
Int32 DeleteOnId(Int32 id);
string GetValidSortField(string sortField);
string GetValidSortOrder(string sortOrder);
} // End of the interface
Detta gränssnitt definierar de metoder som vår klass behöver implementera. I vår klass för statiska sidor kommer vi att injicera beroenden i konstruktorn. Du kan injicera beroenden som kommer från ASP.NET-ramverket och gränssnitt som du har skapat i ditt projekt.
public class StaticPageRepository : IStaticPageRepository
{
#region Variables
private readonly IDatabaseRepository database_repository;
private readonly IHttpClientFactory client_factory;
#endregion
#region Constructors
/// <summary>
/// Create a new repository
/// </summary>
public StaticPageRepository(IDatabaseRepository database_repository, IHttpClientFactory client_factory)
{
// Set values for instance variables
this.database_repository = database_repository;
this.client_factory = client_factory;
} // End of the constructor
#endregion
#region Insert methods
public Int32 Add(StaticPage post)
{
// Create the int to return
Int32 idOfInsert = 0;
// Create the sql statement
string sql = "INSERT INTO dbo.static_pages (connected_to_page, link_name, title, main_content, meta_description, meta_keywords, "
+ "meta_robots, page_name, inactive, news_search_string, sort_value) "
+ "VALUES (@connected_to_page, @link_name, @title, @main_content, @meta_description, @meta_keywords, @meta_robots, "
+ "@page_name, @inactive, @news_search_string, @sort_value);SELECT CAST(SCOPE_IDENTITY() AS INT);";
// Create parameters
IDictionary<string, object> parameters = new Dictionary<string, object>();
parameters.Add("@connected_to_page", post.connected_to_page);
parameters.Add("@link_name", post.link_name);
parameters.Add("@title", post.title);
parameters.Add("@main_content", post.main_content);
parameters.Add("@meta_description", post.meta_description);
parameters.Add("@meta_keywords", post.meta_keywords);
parameters.Add("@meta_robots", post.meta_robots);
parameters.Add("@page_name", post.page_name);
parameters.Add("@inactive", post.inactive);
parameters.Add("@news_search_string", post.news_search_string);
parameters.Add("@sort_value", post.sort_value);
// Insert the post
this.database_repository.Insert<Int32>(sql, parameters, out idOfInsert);
// Return the id of the inserted item
return idOfInsert;
} // End of the Add method
#endregion
} // End of the class
IDatabaseRepository database_repository kan vara en MSSQL-klass eller en MySql-klass som implementerar detta gränssnitt, klienten bestämmer vilken klass som skall användas. Vår MsSqlRepository-klass beror på IOptions och dessa inställningar måste skapas av klienten.
public class MsSqlRepository : IDatabaseRepository
{
#region Variables
private readonly DatabaseOptions options;
#endregion
#region Constructors
public MsSqlRepository(IOptions<DatabaseOptions> options)
{
// Set values for instance variables
this.options = options.Value;
} // End of the constructor
#endregion
} // End of the class
Registrera beroenden
Vi registrerar våra beroenden i en servicebehållare i metoden ConfigureServices i klassen StartUp. Vi behöver inte registrera alla beroenden som vi behöver i vårt projekt, ramverket registrerar vissa beroenden såsom IHostingEnvironment till exempel.
public void ConfigureServices(IServiceCollection services)
{
// Create database options (Injected in MsSqlRepository)
services.Configure<DatabaseOptions>(options =>
{
options.connection_string = configuration.GetSection("AppSettings")["ConnectionString"];
options.sql_retry_count = 1;
});
// Add clients (Registers IHttpClientFactory)
services.AddHttpClient();
// Register dependencies
services.AddSingleton<IDatabaseRepository, MsSqlRepository>();
services.AddTransient<IOperationTransient, Operation>();
services.AddScoped<IOperationScoped, Operation>();
// More code ...
} // End of the ConfigureServices method
Beroenden kan registreras med 3 olika livstider (Singleton, Scoped, Transient). Jag använder singleton-livstiden för de flesta av mina hanteringsklasser, modellklasser skapas när de behövs.
Livslängder för tjänster
- Tjänster med livstiden transient skapas varje gång de begärs.
- Tjänster med livstiden scoped skapas en gång per förfrågan.
- Tjänster med livstiden singleton skapas en gång, första gången de efterfrågas.
Du kan injicera ett singletonberoende i en scoped-tjänst eller en transient-tjänst och du kan injicera en scoped-tjänst i en transient -tjänst. Det är inte bra att injicera ett scoped-beroende eller ett transient-beroende i en singleton-tjänst och det är inte bra att injicera ett transient-beroende i en scoped-tjänst.
En singleton-tjänst bör inte ha tillståndsvariabler som kan ändras mellan förfrågningar, variabler i en singleton-tjänst kommer alltid att vara statiska.