如何在C#中为所有类/方法注入变量?API控制器代码优化问询
避免ASP.NET Core API控制器重复代码的优雅方案
针对你提到的在多个API控制器中重复编写相同初始化逻辑的问题,这里有两种无需重复复制粘贴的优雅方案,既保持代码整洁,又能通过依赖注入获取所需变量:
方案1:自定义上下文服务 + 依赖注入
这种方式将重复逻辑封装成一个可注入的服务,符合SOLID原则,还能在其他组件中复用,也方便单元测试。
步骤1:定义上下文模型和服务接口
首先创建一个承载所有所需变量的模型,以及对应的服务接口:
// 承载租户相关上下文的模型 public class TenantContext { public Tenant Tenant { get; set; } public ICosmosStore<SiteCollection> SiteCollectionStore { get; set; } public string TenantAdminUrl { get; set; } public string KeyVaultSecret { get; set; } } // 服务接口,定义获取上下文的方法 public interface ITenantContextService { Task<TenantContext> GetTenantContextAsync(); }
步骤2:实现服务逻辑
把你重复的代码放到服务实现里:
public class TenantContextService : ITenantContextService { private readonly ICosmosStoreFactory _cosmosStoreFactory; // 通过构造函数注入CosmosStoreFactory(确保它已经注册到DI容器) public TenantContextService(ICosmosStoreFactory cosmosStoreFactory) { _cosmosStoreFactory = cosmosStoreFactory; } public async Task<TenantContext> GetTenantContextAsync() { // 原重复代码逻辑 var tenant = await TenantHelper.GetActiveTenant(); var siteCollectionStore = _cosmosStoreFactory.CreateForEntity<SiteCollection>(); await siteCollectionStore.RemoveAsync(x => x.Title != string.Empty); string domainUrl = tenant.TestSiteCollectionUrl; string tenantName = domainUrl.Split('.')[0]; string tenantAdminUrl = $"{tenantName}-admin.sharepoint.com"; var keyVaultHelper = new KeyVaultHelper(); await keyVaultHelper.OnGetAsync(tenant.SecretIdentifier); // 返回封装好的上下文 return new TenantContext { Tenant = tenant, SiteCollectionStore = siteCollectionStore, TenantAdminUrl = tenantAdminUrl, KeyVaultSecret = keyVaultHelper.SecretValue }; } }
步骤3:注册服务到DI容器
在Program.cs(.NET 6+)或Startup.cs里注册这个服务:
// .NET 6+ Program.cs builder.Services.AddScoped<ITenantContextService, TenantContextService>();
步骤4:在控制器中使用
现在控制器只需注入服务,就能获取所有所需变量:
[ApiController] [Route("api/[controller]")] public class SiteCollectionsController : ControllerBase { private readonly ITenantContextService _tenantContextService; public SiteCollectionsController(ITenantContextService tenantContextService) { _tenantContextService = tenantContextService; } [HttpGet] public async Task<List<SiteCollection>> Get() { // 获取上下文 var tenantContext = await _tenantContextService.GetTenantContextAsync(); // 后续逻辑直接使用上下文里的变量 using (var context = new AuthenticationManager().GetSharePointOnlineAuthenticatedContextTenant( tenantContext.TenantAdminUrl, tenantContext.Tenant.Email, tenantContext.KeyVaultSecret)) { Tenant tenantOnline = new Tenant(context); SPOSitePropertiesEnumerable siteProps = tenantOnline.GetSitePropertiesFromSharePoint("0", true); context.Load(siteProps); context.ExecuteQuery(); List<SiteCollection> sites = new List<SiteCollection>(); foreach (var site in siteProps) { if(site.Template.Contains("SITEPAGEPUBLISHING#0") || site.Template.Contains("GROUP#0")) { string strTemplate = site.Template.Contains("SITEPAGEPUBLISHING#0") ? "CommunicationSite" : "Modern Team Site"; try { Guid id = Guid.NewGuid(); var sc = new SiteCollection() { Id = id.ToString(), Owner = site.Owner, Template = strTemplate, Title = site.Title, Active = false, Url = site.Url }; var added = await tenantContext.SiteCollectionStore.AddAsync(sc); sites.Add(sc); } catch (Exception ex) { throw ex; } } } return sites; } } }
方案2:使用控制器基类
如果你的API控制器大多都需要这些变量,可以创建一个基类控制器,在请求执行前完成初始化,让其他控制器继承它即可。
步骤1:创建基类控制器
public class BaseTenantController : ControllerBase { // 定义受保护的属性,供子类使用 protected Tenant Tenant { get; private set; } protected ICosmosStore<SiteCollection> SiteCollectionStore { get; private set; } protected string TenantAdminUrl { get; private set; } protected string KeyVaultSecret { get; private set; } private readonly ICosmosStoreFactory _cosmosStoreFactory; public BaseTenantController(ICosmosStoreFactory cosmosStoreFactory) { _cosmosStoreFactory = cosmosStoreFactory; } // 重写Action执行前的方法,完成初始化 public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { // 原重复代码逻辑 Tenant = await TenantHelper.GetActiveTenant(); SiteCollectionStore = _cosmosStoreFactory.CreateForEntity<SiteCollection>(); await SiteCollectionStore.RemoveAsync(x => x.Title != string.Empty); string domainUrl = Tenant.TestSiteCollectionUrl; string tenantName = domainUrl.Split('.')[0]; TenantAdminUrl = $"{tenantName}-admin.sharepoint.com"; var keyVaultHelper = new KeyVaultHelper(); await keyVaultHelper.OnGetAsync(Tenant.SecretIdentifier); KeyVaultSecret = keyVaultHelper.SecretValue; // 继续执行后续Action逻辑 await next(); } }
步骤2:控制器继承基类
[ApiController] [Route("api/[controller]")] public class SiteCollectionsController : BaseTenantController { // 构造函数传递依赖给基类 public SiteCollectionsController(ICosmosStoreFactory cosmosStoreFactory) : base(cosmosStoreFactory) { } [HttpGet] public async Task<List<SiteCollection>> Get() { // 直接使用基类的属性即可 using (var context = new AuthenticationManager().GetSharePointOnlineAuthenticatedContextTenant( TenantAdminUrl, Tenant.Email, KeyVaultSecret)) { // 后续逻辑和之前一致,无需重复初始化 Tenant tenantOnline = new Tenant(context); SPOSitePropertiesEnumerable siteProps = tenantOnline.GetSitePropertiesFromSharePoint("0", true); context.Load(siteProps); context.ExecuteQuery(); List<SiteCollection> sites = new List<SiteCollection>(); foreach (var site in siteProps) { if(site.Template.Contains("SITEPAGEPUBLISHING#0") || site.Template.Contains("GROUP#0")) { string strTemplate = site.Template.Contains("SITEPAGEPUBLISHING#0") ? "CommunicationSite" : "Modern Team Site"; try { Guid id = Guid.NewGuid(); var sc = new SiteCollection() { Id = id.ToString(), Owner = site.Owner, Template = strTemplate, Title = site.Title, Active = false, Url = site.Url }; var added = await SiteCollectionStore.AddAsync(sc); sites.Add(sc); } catch (Exception ex) { throw ex; } } } return sites; } } }
方案对比
- 自定义服务:更灵活,低耦合,可复用性强,方便单元测试(可Mock服务),推荐用于复杂场景或需要在非控制器组件中复用逻辑的情况。
- 控制器基类:实现简单,代码更简洁,适合所有相关控制器都需要相同上下文的场景,但耦合度稍高,测试时需要处理基类的逻辑。
另外,建议把KeyVaultHelper也注册到DI容器中,而不是每次手动new,这样更符合依赖注入的设计思想,也方便后续扩展和测试。
内容的提问来源于stack exchange,提问作者Luis Valencia
相关产品推荐
相关产品推荐

