You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 09:17:46