如何为声明创建自定义参数绑定?

时间:2019-06-25 04:27:05

标签: c# asp.net-core .net-core jwt asp.net-core-webapi

我希望能够直接在控制器的参数中向当前用户提出索赔。这样我就可以编写单元测试,而无需触碰ClaimPrincipal魔术。

像[FromUri]或[FromBody],也许是[FromClaim]?

我尝试实现Microsoft的本文档中指定的CustomModelProvider:https://docs.microsoft.com/en-us/aspnet/core/mvc/advanced/custom-model-binding?view=aspnetcore-2.2

但是我不知道如何提供ClaimsPrincipal或List。 另外,ValueProvider返回一个字符串,因此我不确定这是否切实可行。

这是我对ClaimModelBinder的尝试

public class ClaimModelBinder : IModelBinder
{
    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        if (bindingContext == null) throw new ArgumentNullException(nameof(bindingContext));

        var modelName = bindingContext.ModelName;

        // Try to fetch the value of the argument by name
        var valueProviderResult = bindingContext.ValueProvider.GetValue(modelName);

        if (valueProviderResult == ValueProviderResult.None) return Task.CompletedTask;

        bindingContext.ModelState.SetModelValue(modelName, valueProviderResult);

        var value = valueProviderResult.FirstValue;

        // TODO: Unsure, how to continue after this.

        // Check if the argument value is null or empty
        if (string.IsNullOrEmpty(value)) return Task.CompletedTask;

        int id = 0;
        if (!int.TryParse(value, out id))
        {
            // Non-integer arguments result in model state errors
            bindingContext.ModelState.TryAddModelError(
                                    modelName,
                                    "Author Id must be an integer.");
            return Task.CompletedTask;
        }

        // Model will be null if not found, including for 
        // out of range id values (0, -3, etc.)
        bindingContext.Result = ModelBindingResult.Success(null);
        return Task.CompletedTask;
    }
}

1 个答案:

答案 0 :(得分:2)

  

您能提供“构建一个ClaimsPrincipal进行测试比您尝试做的要容易得多且正确得多”的来源吗?

消息来源是我。至于我为什么这么说,它是基于对how the ASP NET Core framework is written的理解,如下文所示。

要回答您的问题,Controller拥有一个User属性来访问声明,那么当一个User属性已经存在时,无需编写Model Binder来访问声明,除非您由于以下原因而无法从该User属性访问声明:您的主张逻辑不同。但是您还没有提及。

  

“我希望能够直接在控制器的参数中向当前用户提供索赔。这样我就可以编写单元测试,而无需触动ClaimPrincipal魔术。”

我解释为,

  

“我想为我的控制器编写单元测试,该控制器具有涉及Claims Principal的逻辑,但是我不知道如何提供伪造的Claims Principal,所以我将避免这种情况并改为传递方法参数”

ClaimsPrincipal如下所述。

  • Controller有一个User属性,但仅是Get。 魔术
  • HttpContext具有一个User属性,该属性为“获取并设置”(很好),而Controller.HttpContext仅是Get属性(不太好 >)
  • Controller的{​​{1}}属性是Get and Set,ControllerContext的{​​{1}}属性是Get and Set。 头奖!

这是ControllerContextHttpContext衍生自ControllerBase的{​​{3}}

Controller

正如您在此处看到的那样,您访问的用户是一个便捷的Getter,最终可以访问ApiController。了解了这些信息后,您可以对使用ClaimsPrincipal的控制器进行单元测试,如下所示。

public abstract class ControllerBase
{
    /* simplified below */
    public ControllerContext ControllerContext
    {
            get => _controllerContext;
            set => _controllerContext = value;
    }
    /* ... */
    public HttpContext HttpContext => ControllerContext.HttpContext;
    /* ... */
    public ClaimsPrincipal User => HttpContext?.User;
}

这是每次收到实际的Web请求时ASP NET Core的工作方式。从字面上看,它可以使控制器正常工作并可以使用。

以上所有内容都是 public ASP NET Core API的一部分,在没有主要版本错误的情况下不会受到重大更改,因此可以安全使用。实际上,这是使ASP Net Core与旧的ASP NET MVC脱颖而出的原因之一,这是一个噩梦,因为它没有公开暴露任何上述内容。

由于种种原因,我已经忽略了所有这些内容,如果您确实需要编写模型绑定器来提供声明,请插入source code。但这需要您检查方法参数的类型和分支执行。一个分支将从值提供程序绑定属性,而另一个分支从HttpContext绑定。但是,当您可以使用0重构来完成上述操作时,为什么还要麻烦呢?