【问题标题】:Base Constructor with DI and arguements带有 DI 和参数的基本构造函数
【发布时间】:2017-01-03 19:33:00
【问题描述】:

我有一个基本 api 控制器,我希望所有控制器都根据请求执行,以充当安全机制。这是那个控制器

public abstract class SharepointAuthController : ApiController
{
    private ClientContext clientContext;
    public SharepointAuthController()
 : base()
    {
        ValidateContext();
    }

    protected void ValidateContext()
    {
        if (ControllerContext.Request != null)
        {
            var spContext = SharePointApiControllerContextProvider.Current.GetSharePointContext(ControllerContext);

            clientContext = spContext.CreateUserClientContextForSPHost();

            if (clientContext == null)
            {
                throw new AuthenticationException();
            }

        }
    }
    protected string GetUserName()
    {
        User spUser = null;
        var spContext = SharePointApiControllerContextProvider.Current.GetSharePointContext(ControllerContext);
        using (clientContext = spContext.CreateUserClientContextForSPHost())
        {
            if (clientContext != null)
            {
                spUser = clientContext.Web.CurrentUser;
                clientContext.Load(spUser, user => user);
                clientContext.ExecuteQuery();
                return spUser.Email;
            }
        }
        throw new AuthenticationException();
    }
}

以及调用它的控制器

    public class CallPointsController : SharepointAuthController
{
    private readonly ICallPointRepository _callPointRepository;

    public CallPointsController(ICallPointRepository callPointRepository)
    {
        _callPointRepository = callPointRepository;
    }

    [SharePointContextFilter]
    [HttpGet]
    [Route("api/callpoints")]
    public List<CallPointDto> Get()
    {
        string user = base.GetUserName();
        if (!string.IsNullOrEmpty(user))
        {
            return _callPointRepository.ListAll();
        }
        return null;
    }
}

我现在想扩展 SharepointAuthController 以获取有关用户的其他信息(存在于 DB 中)。我希望能够将存储库传递给基类的构造函数以获得正确的 DI,就像这样

  private ClientContext clientContext;
  private _repo Repo;
public SharepointAuthController(Repo repo)
: base()
{
    ValidateContext();
    _repo = repo;
}



protected UserDto GetUserName()
{
    User spUser = null;
    var spContext = SharePointApiControllerContextProvider.Current.GetSharePointContext(ControllerContext);
    using (clientContext = spContext.CreateUserClientContextForSPHost())
    {
        if (clientContext != null)
        {
            spUser = clientContext.Web.CurrentUser;
            clientContext.Load(spUser, user => user);
            clientContext.ExecuteQuery();
            return _repo.GetAdditionalUserInfo(spUser.Email);
        }
    }
    throw new AuthenticationException();
}

但是仅仅这样做是行不通的,因为调用这个基类的类没有正确设置

没有给出对应的参数 Repo的必需形式参数'repo'

我这样做的方式是否正确?我可以在没有 DI 的情况下从 Auth 控制器调用 Repo 类

【问题讨论】:

  • 你从哪里得到这个错误?你需要传递参数。
  • 我在构造函数的 CallPointController 中得到它。
  • 所以传递参数。
  • 具体在哪里? CallPointController中没有对SharepointController的调用,只是继承。
  • 是的;当你继承一个类时,你需要调用它的构造函数(: base()

标签: c# asp.net-web-api dependency-injection


【解决方案1】:

回答你的问题:

您需要将参数注入到您继承的类中并将其传递给父类:

public class SharepointAuthController
{
    public SharepointAuthController(Repo repo)
    {
        ValidateContext();
        _repo = repo;
    }
    // rest of controller ...
}

public class CallPointsController : SharepointAuthController
{
    private readonly ICallPointRepository _callPointRepository;

    public CallPointsController(ICallPointRepository callPointRepository, Repo repo) 
     : base(repo)
    {
        _callPointRepository = callPointRepository;
    }
}

另外说明:要进行身份验证,最好不要使用基本控制器。而是创建一个继承自AuthorizeAttribute 的属性(例如:SharepointAuthAttribute)并在那里进行身份验证。

然后,您可以将该属性应用于需要它的控制器。

【讨论】:

  • 感谢您。如果我想使用过滤器,我可以在过滤器上调用方法(如getuser),还是我需要在每个控制器中都这样做
  • 你不能在过滤器上这样做,但我相信有一种方法可以将该信息传递给 IPrincipal。我不知道究竟是怎么回事,但没有通过文档
  • 知道了。所以 SharepointAuthController 实际上有一个过滤器。我的想法是共享点身份验证由过滤器完成,我需要的额外调用将​​在基础中
【解决方案2】:

您已向基类的构造函数添加了一个参数:

public SharepointAuthController(Repo repo)
 : base()
{
    //...
}

但是您没有在派生类的构造函数中提供该参数:

public CallPointsController(ICallPointRepository callPointRepository)
{
    //...
}

需要提供:

public CallPointsController(ICallPointRepository callPointRepository, Repo repo)
 : base(repo)
{
    //...
}

否则派生类将无法构造基类,因此不能成为基类的实例。

【讨论】:

    【解决方案3】:

    一般不鼓励使用基类。俗话说:

    Composition over inheritance

    基类通常是个坏主意,因为:

    • 它们导致对具体类的额外依赖,从而引入强耦合,而 DI 促进松散耦合。
    • 这种强耦合使您的具体控制器更难测试,这在您的情况下被夸大了,因为您强制在构造函数中调用业务逻辑,而 injection constructors should be simple
    • 当基类用于横切关注点时(如您的情况),它们开始成为吸引越来越多横切关注点的磁石。这会导致基类成为这个不断变化的类,违反了Single Responsibility PrincipleOpen/Closed Principle
    • 这些基类往往需要它们自己的依赖项。这是有问题的,因为它很容易将您拉入Temporal Coupling code smellService Locator anti-pattern。当您通过基类的构造函数应用依赖项时,派生类的构造函数也需要这些依赖项。这意味着每次更改或添加对基类的依赖项时,都会导致整个应用程序发生彻底的更改,因为每个派生类也需要更改。为了缓解这种情况,您的选择是恢复到属性注入(这会导致时间耦合)或恢复到服务定位器反模式。这两种风格都有严重的缺点。

    因此,与使用基类相比,组合是一种更好的系统设计方式,尤其是一种更好的方式来应用横切关注点(例如安全性)。

    应用横切关注点的典型方式是使用装饰器。然而,Web API 使得无法用装饰器包装控制器类型。对于 Web API,designed pattern for applying cross-cutting concerns on the controller levelDelegatingHandlers 的使用。

    【讨论】:

    • 感谢您阅读文章。那么您建议将逻辑用于验证共享点上下文和数据库调用以在他们自己的处理程序中获取更多信息?因此,每当调用 api 时,它都会触发这些处理程序。我什么时候可以从他们那里获得我需要的数据?
    • @IsaacLevin:“我在什么时候从他们那里获得我需要的数据”是什么意思?哪些数据?处理程序获取允许您访问所有请求数据的请求消息。
    • 好吧,我需要进行数据库调用,以及针对 Sharepoint 对用户进行身份验证。我需要将该数据库调用返回给我的控制器。
    • @IsaacLevin:将逻辑提取到在您的处理程序和控制器中都使用的服务中。如果两次查询数据库以获取相同的信息会导致性能问题,请在此新服务周围添加一个缓存装饰器,以便在请求期间缓存该信息。
    猜你喜欢
    • 2018-04-04
    • 2016-07-26
    • 2021-04-05
    • 1970-01-01
    • 2018-06-03
    • 2011-10-08
    • 1970-01-01
    • 2020-03-03
    • 1970-01-01
    相关资源
    最近更新 更多