我如何在没有 "new'ing" 一个 class 实例并自己提供参数的情况下进行依赖注入?
How can I do Dependency Injection without "new'ing" a class instance and providing the parameters myself?
我有一个客户端,它是根据使用 NSwagStudio 生成的 OpenApi 定义创建的。生成的代码有一个接口和一个 class。它在构造函数中使用一个 HttpClient。我是这样为 DI 引擎设置的。
services.AddScoped<IConsumerAdapterClient, ConsumerAdapterClient>(sp =>
{
var httpClientFactory = sp.GetRequiredService<IHttpClientFactory>();
var baseUrl = Configuration.GetSection("ConsumerAdapterConfig:EndpointBase").Value;
return new ConsumerAdapterClient(baseUrl,httpClientFactory.CreateClient());
});
但这对我来说似乎不太正确。经过一番搜索,我找到了 this 篇文章。现在相同的代码看起来像这样。
services.AddHttpClient<IConsumerAdapterClient, ConsumerAdapterClient>(client =>
{
var baseUrl = Configuration.GetSection("ConsumerAdapterConfig:EndpointBase").Value;
client.BaseAddress = new Uri(baseUrl);
});
好的,这样更好。但现在我必须增加安全性。为此,我有一个名为 ClientCredentialProvider 的 class,它负责使用客户端凭证流获取访问令牌。我不想修改生成的代码(这只是要求维护问题)所以相反,我可以利用 NSwagStudio 生成 classes 作为部分 class 这一事实。这意味着我可以创建一个新的构造函数并将安全元素注入 class,然后我可以在准备方法挂钩中使用它。
public partial class ConsumerAdapterClient
{
private readonly IClientCredentialProvider _clientCredentialProvider;
public ConsumerAdapterClient(HttpClient httpClient, IClientCredentialProvider clientCredentialProvider)
{
_httpClient = httpClient;
_clientCredentialProvider = clientCredentialProvider;
_settings = new System.Lazy<Newtonsoft.Json.JsonSerializerSettings>(CreateSerializerSettings);
}
protected async Task PrepareRequestAsync(HttpClient client, HttpRequestMessage request, StringBuilder url)
{
await PrepareRequestAsync(client, request, url.ToString());
}
protected async Task PrepareRequestAsync(HttpClient client, HttpRequestMessage request, string url)
{
if (_clientCredentialProvider != null)
{
var accessToken = await _clientCredentialProvider.GetAccessToken();
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
}
}
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
protected async Task ProcessResponseAsync(HttpClient client, HttpResponseMessage response, CancellationToken cancellationToken)
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
{
// nothing to do here.
}
}
这产生了一个新问题,因为它现在抱怨有多个构造函数并且不知道该选择哪个。从生成的代码中删除构造函数后,它按预期工作。
所以这里出现了三个问题。
一个是我必须从生成的代码中删除构造函数。这会造成维护问题,但这并不可怕,尝试 运行 测试会很快发现它。
其次,因为我必须从生成的代码中删除构造函数,所以我不得不移动_settings 的初始化并将其复制到部分class 中的构造函数。这是一次性的事情,所以我可以忍受。
第三个是我使用部分 class 完成的。虽然这本身没什么大不了的,但我有很多这样的客户 classes 并且需要为每个客户一遍又一遍地创建相同的东西。
那么我怎样才能实现这个 class 而不是一遍又一遍地创建样板代码呢?
我把部分 class 变成了基础 class。然后我告诉 NSwagStudio 我的基数 class 所以当代码生成时它会使用基数 class。我现在遇到的唯一问题是我无法重载构造函数。现在,我没有注入 ClientCredentialProvider,而是回到了开始时的方式,只是为 ClientCredentialProvider 设置了 属性。
public class ClientServicesBase
{
private IClientCredentialProvider _clientCredentialProvider;
public IClientCredentialProvider ClientCredentialProvider
{
set => _clientCredentialProvider = value;
}
// The rest of the code is the same as above.
}
所以要设置它,它看起来像这样。
services.AddScoped<IConsumerAdapterClient, ConsumerAdapterClient>(sp =>
{
var httpClientFactory = sp.GetRequiredService<IHttpClientFactory>();
var clientCredentialProvider = sp.GetRequiredService<IClientCredentialProvider>();
var baseUrl = Configuration.GetSection("ConsumerAdapterConfig:EndpointBase").Value;
return new ConsumerAdapterClient(baseUrl,httpClientFactory.CreateClient())
{
ClientCredentialProvider = clientCredentialProvider
};
});
所以这是有效的,它做了我想要它做的事情,但它没有以代表 Microsoft's recommended solution.
的方式完成
我如何:
使用 DI 引擎添加安全元素,而无需从服务容器中提取参数并新建 class?
的新实例
根据 Jeremy 对我的问题的评论,我创建了一个新的 class,它是我从生成的那个派生出来的。它没有添加任何新功能,但确实添加了一个带有强类型参数的构造函数。我仍然使用为我处理安全部分的基础 class,但现在我以 Microsoft 推荐的方式使用 DI 引擎。
public class ConsumerAdapterClientExtension : ConsumerAdapterClient, IConsumerAdapterClient
{
public ConsumerAdapterClientExtension(HttpClient httpClient, IClientCredentialProvider clientCredentialProvider) : base(httpClient)
{
ClientCredentialProvider = clientCredentialProvider;
}
}
然后当我在启动时连接它class,它看起来像这样。
services.AddHttpClient<IConsumerAdapterClient, ConsumerAdapterClientExtension>(client =>
{
var baseUrl = Configuration.GetSection("ConsumerAdapterConfig:EndpointBase").Value;
client.BaseAddress = new Uri(baseUrl);
});
我有一个客户端,它是根据使用 NSwagStudio 生成的 OpenApi 定义创建的。生成的代码有一个接口和一个 class。它在构造函数中使用一个 HttpClient。我是这样为 DI 引擎设置的。
services.AddScoped<IConsumerAdapterClient, ConsumerAdapterClient>(sp =>
{
var httpClientFactory = sp.GetRequiredService<IHttpClientFactory>();
var baseUrl = Configuration.GetSection("ConsumerAdapterConfig:EndpointBase").Value;
return new ConsumerAdapterClient(baseUrl,httpClientFactory.CreateClient());
});
但这对我来说似乎不太正确。经过一番搜索,我找到了 this 篇文章。现在相同的代码看起来像这样。
services.AddHttpClient<IConsumerAdapterClient, ConsumerAdapterClient>(client =>
{
var baseUrl = Configuration.GetSection("ConsumerAdapterConfig:EndpointBase").Value;
client.BaseAddress = new Uri(baseUrl);
});
好的,这样更好。但现在我必须增加安全性。为此,我有一个名为 ClientCredentialProvider 的 class,它负责使用客户端凭证流获取访问令牌。我不想修改生成的代码(这只是要求维护问题)所以相反,我可以利用 NSwagStudio 生成 classes 作为部分 class 这一事实。这意味着我可以创建一个新的构造函数并将安全元素注入 class,然后我可以在准备方法挂钩中使用它。
public partial class ConsumerAdapterClient
{
private readonly IClientCredentialProvider _clientCredentialProvider;
public ConsumerAdapterClient(HttpClient httpClient, IClientCredentialProvider clientCredentialProvider)
{
_httpClient = httpClient;
_clientCredentialProvider = clientCredentialProvider;
_settings = new System.Lazy<Newtonsoft.Json.JsonSerializerSettings>(CreateSerializerSettings);
}
protected async Task PrepareRequestAsync(HttpClient client, HttpRequestMessage request, StringBuilder url)
{
await PrepareRequestAsync(client, request, url.ToString());
}
protected async Task PrepareRequestAsync(HttpClient client, HttpRequestMessage request, string url)
{
if (_clientCredentialProvider != null)
{
var accessToken = await _clientCredentialProvider.GetAccessToken();
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
}
}
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
protected async Task ProcessResponseAsync(HttpClient client, HttpResponseMessage response, CancellationToken cancellationToken)
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
{
// nothing to do here.
}
}
这产生了一个新问题,因为它现在抱怨有多个构造函数并且不知道该选择哪个。从生成的代码中删除构造函数后,它按预期工作。
所以这里出现了三个问题。
一个是我必须从生成的代码中删除构造函数。这会造成维护问题,但这并不可怕,尝试 运行 测试会很快发现它。
其次,因为我必须从生成的代码中删除构造函数,所以我不得不移动_settings 的初始化并将其复制到部分class 中的构造函数。这是一次性的事情,所以我可以忍受。
第三个是我使用部分 class 完成的。虽然这本身没什么大不了的,但我有很多这样的客户 classes 并且需要为每个客户一遍又一遍地创建相同的东西。
那么我怎样才能实现这个 class 而不是一遍又一遍地创建样板代码呢?
我把部分 class 变成了基础 class。然后我告诉 NSwagStudio 我的基数 class 所以当代码生成时它会使用基数 class。我现在遇到的唯一问题是我无法重载构造函数。现在,我没有注入 ClientCredentialProvider,而是回到了开始时的方式,只是为 ClientCredentialProvider 设置了 属性。
public class ClientServicesBase
{
private IClientCredentialProvider _clientCredentialProvider;
public IClientCredentialProvider ClientCredentialProvider
{
set => _clientCredentialProvider = value;
}
// The rest of the code is the same as above.
}
所以要设置它,它看起来像这样。
services.AddScoped<IConsumerAdapterClient, ConsumerAdapterClient>(sp =>
{
var httpClientFactory = sp.GetRequiredService<IHttpClientFactory>();
var clientCredentialProvider = sp.GetRequiredService<IClientCredentialProvider>();
var baseUrl = Configuration.GetSection("ConsumerAdapterConfig:EndpointBase").Value;
return new ConsumerAdapterClient(baseUrl,httpClientFactory.CreateClient())
{
ClientCredentialProvider = clientCredentialProvider
};
});
所以这是有效的,它做了我想要它做的事情,但它没有以代表 Microsoft's recommended solution.
的方式完成
我如何:
使用 DI 引擎添加安全元素,而无需从服务容器中提取参数并新建 class?
根据 Jeremy 对我的问题的评论,我创建了一个新的 class,它是我从生成的那个派生出来的。它没有添加任何新功能,但确实添加了一个带有强类型参数的构造函数。我仍然使用为我处理安全部分的基础 class,但现在我以 Microsoft 推荐的方式使用 DI 引擎。
public class ConsumerAdapterClientExtension : ConsumerAdapterClient, IConsumerAdapterClient
{
public ConsumerAdapterClientExtension(HttpClient httpClient, IClientCredentialProvider clientCredentialProvider) : base(httpClient)
{
ClientCredentialProvider = clientCredentialProvider;
}
}
然后当我在启动时连接它class,它看起来像这样。
services.AddHttpClient<IConsumerAdapterClient, ConsumerAdapterClientExtension>(client =>
{
var baseUrl = Configuration.GetSection("ConsumerAdapterConfig:EndpointBase").Value;
client.BaseAddress = new Uri(baseUrl);
});