【问题标题】:How to automatically set default value of API model's property with value from appSettings.json?如何使用 appSettings.json 中的值自动设置 API 模型属性的默认值?
【发布时间】:2021-01-04 15:17:45
【问题描述】:

我正在 Core 3.1 中创建一个 Web API,我有 PagingParams 模型,我将在许多控制器操作中使用它。该模型有PageNumberPageSize参数,这两个参数都有一个默认值:

public class PagingParams
{
    [Range(1, Int32.MaxValue, ErrorMessage = "The field {0} must be greater than 0.")]
    public int? PageNumber { get; set; } = 1;

    [Range(1, 10000)] //TODO: Get 10000 from appsettings
    public int? PageSize { get; set; } = 10000; //TODO: Get 10000 from appsettings
}

PageSize 参数值需要设置为 10000,并且与 Range 属性中的最大值相同。这可以防止用户每页获得超过 10k 个项目。

我希望能够从appSettings.json 中检索该值,这样我就可以修改它而无需每次都重新发布应用程序。在整个代码中,我一直使用IOptionsSnapshotappSettings.json 中读取值,但这里似乎无法完成,因为模型需要具有无参数构造函数,导致以下错误:

System.InvalidOperationException:无法创建“WebAPI.Models.PagingParams”类型的实例。模型绑定的复杂类型不能是抽象类型或值类型,并且必须具有无参数构造函数。或者,给“pagingParams”参数一个非空的默认值。

如何设置模型,以便默认情况下 int 取自 appSettings 并在两个位置进行设置?

这就是类的使用方式:

[Route("api/[controller]")]
    [ApiController]
    public class UsersController : ControllerBase
    {
        private readonly IMapper _mapper;
        private readonly IUserRepo _userRepo;

        public MissingNamesController(IMapper mapper, IUserRepo userRepo)
        {
            _mapper = mapper;
            _userRepo = userRepo;
        }

        [HttpGet]
        public IEnumerable<UserDto> Get([FromQuery] UsersQueryParams queryParams, [FromQuery] PagingParams pagingParams)
        {
            IEnumerable<User> models = _userRepo.Get(queryParams, pagingParams);
            IEnumerable<UserDto> dtoModels = _mapper.Map<IEnumerable<UserDto>>(models);
            return dtoModels;
        }
    }
}

注意:我的问题与this one 类似,只是我希望能够进行设置以使其自动化。我想避免每次在操作上使用 PagingParams 类时调用方法,然后必须手动调用 ModelState.IsValid 并返回模型错误。在我目前的设置下,Range 属性会自动处理模型错误的返回,我不需要做任何进一步的事情。

【问题讨论】:

  • 这取决于您何时创建/获取PagingParams 的实例,真的。这对于实现你想要的非常重要。为属性预设默认值并不是什么神奇的事情,我们只是实例化对象并正常设置属性,尤其是当它只是一个包含数据/值的对象时(不是通过接口公开的服务或使用 IoC 容器注入的东西)。
  • 感谢您的评论。我已经编辑了我的问题以显示PagingParams 的使用位置。它只是动作的参数之一。我不确定具体细节,但我猜想在接收到数据并将其绑定到该对象时会创建一个对象实例。可能只需要编写一些逻辑并手动检查PagingParams 对象的属性值。
  • 好吧,如果很简单,我只是告诉你机制,你可以搜索更多细节。这个想法是使用 global IActionFilter(对于 Razor 页面,我们有 IAsyncPageFilter)。它将有机会获取(调用的操作/处理程序的)输入参数,因此您可以在在一个地方修改PagingParams。有点像cross-cutting concern。这里的原则是找到一个点(在执行/请求流中)您可以修改您的对象,这样您只需要一个地方来编写您的(修改)代码。在这种情况下,IActionFilter 就是其中之一。
  • 是的,我曾想过使用动作过滤器,但问题是我必须硬编码参数对象的名称。因此,查看我提供的Get 操作,我需要在过滤器中使用filterContext.Controller.ValueProvider.GetValue("pagingParams").AttemptedValue 之类的东西来获取对象。但是"pagingParams" 是硬编码的,我需要始终小心确保PagingParams 类型的每个动作的参数都被准确地调用pagingParams。如果有的话,也许有办法按类型获取它,但我还没有找到。
  • 不,您过滤的是 参数类型,而不是参数名称。完全可行,实施起来也不难。

标签: c# asp.net asp.net-core asp.net-web-api


【解决方案1】:

您想要的是方便地使用一些默认值初始化一些属性。核心机制很简单,就像您在将对象用于下一个管道之前实例化一个对象并设置其属性一样。有一些方法可以实现:

  • 在类声明中正确设置属性:这不是您想要的,因为您想从某些设置中填充默认值(在运行时)。
  • 在您拥有对象的任何地方设置属性:这意味着在当前上下文中,您需要从设置/配置中加载默认值,以便您可以初始化属性。

通常你用第二种方法。但是为了尽可能避免重复代码,我们需要在管道(代码执行 - 请求处理流程)中找到一个点,您可以在其中获取对象并在一个地方初始化其属性(从配置中加载)。

在您的情况下,PagingParams 并没有在代码中的任何地方实例化,这很容易,它只是来自控制器操作(通过模型绑定实例化)。所以在这种情况下,我们可以使用动作过滤器(IActionFilterIAsyncActionFilter 都可以)作为我们可以获得参数(PagingParams 类型)并以我们想要的任何方式修改它(初始化加载默认值的属性)配置中的值)。

这很简单:

public class InitPagingParamsAttribute : IActionFilter
{
    public void OnActionExecuted(ActionExecutedContext context)
    {
        
    }

    public void OnActionExecuting(ActionExecutingContext context)
    {
        var pp = context.ActionArguments.Values.OfType<PagingParams>().FirstOrDefault();
        //this means there is at least 1 PagingParams in the controller action
        //NOTE that you can get all args of PagingParams, but looks like usually 
        //there should be just 1 arg of such type (in one same controller action)
        if (pp != null)
        {
            //modify the pp here
            //...
        }
    }
}

Startup.cs文件中,可以配置MVC中间件注册全局动作过滤器。如果不这样做,您需要在您想要的控制器或操作方法上装饰属性InitPagingParamsAttribute

public void ConfigureServices(IServiceCollection services){
    //...
    services.AddMvc(o => {
        o.Filters.Add<InitPagingParamsAttribute>();
    });
    //...
}

注意:您可以在InitPagingParamsAttribute 构造函数中注入您的IOptionsSnapshot。所以你可以从那里的配置中加载默认值。

【讨论】:

  • 非常感谢。我只是不知道如何按类型获取动作参数,感谢您的解决方案和解释!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-03-21
  • 1970-01-01
  • 2013-09-15
  • 1970-01-01
  • 2017-02-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多