从包含冒号 (:) 的查询参数名称中读取值

Posted

技术标签:

【中文标题】从包含冒号 (:) 的查询参数名称中读取值【英文标题】:Reading a value from a Query parameter name containing a colon (:) 【发布时间】:2021-12-27 16:51:08 【问题描述】:

我收到了在 .NET 应用程序中创建新 REST API 的请求,但我不知道如何实现其中一个参数。

我得到了一个Swagger定义,参数定义如下:

如果只是eventCreatedDateTime=2021-04-01T14:12:56+01:00 没问题,但它得到了冒号和等号之间的部分,我不知道如何得到。

基本上,我可以将eventCreatedDateTime:gte=2021-04-01T14:12:56+01:00 作为查询字符串参数,我必须阅读gte 部分,并且还能够验证它是否是允许的后缀之一。后缀不是强制性的,所以eventCreatedDateTime=2021-04-01T14:12:56+01:00 也应该是有效的。

为了澄清,这是一个查询字符串参数,因此是 URL 的一部分。 例如https://example.com/api/mycontroller?param1=value&param2=value&eventCreatedDateTime:gte=2021-04-01T14:12:56+01:00&param4=value

知道如何在 .NET 中执行此操作吗?

【问题讨论】:

除了你所说的之外别无他法——接受string,将其拆分为=,看看第一部分是否包含: 如果没有参数名称,我怎么能得到它? 这听起来不符合 OAS... 也许你可以通过操作员做一个参数。 只是好奇,让你的控制器方法像MyWhatever(string param1, string param2, [FromQuery(Name = "eventCreatedDateTime:gte")] DateTime? greaterThanEqualDate, [FromQuery(Name = "eventCreatedDateTime:gt")] DateTime? greaterThanDate, ...) 工作(即只有一个指定的日期时间会有一个值)-(不确定你使用的是什么风格的网络;如果不是核心,它可能是 [FromUri] 而不是 [FromQuery]) 我可以确认您的建议有效。但是,我将尝试看看 vernou 的答案是否有效,因为它看起来比拥有 6 个参数更清晰。仍然是一个很好的备份,谢谢。 【参考方案1】:

查看 vernou 对 .NET Core 方法的回复。 我的环境仍然是框架,所以这是解决方案。

我的自定义类型有点不同,有一个 DateTime 和一个枚举器属性,这当然也可以在 Core 中使用:

public enum Operator

    Equals,
    GreaterThenEquals,
    GreaterThen,
    LesserThenEquals,
    LesserThen

public class DateTimeFilter

    public DateTime? Date  get; set; 
    public Operator Operator  get; set; 

自定义模型绑定器在Framework中有点不同:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.Http.ModelBinding;
using System.Web.Http.ModelBinding.Binders;

namespace CustomModelBinders

    public class DateTimeFilterModelBinderProvider : ModelBinderProvider
    
        private CollectionModelBinderProvider originalProvider = null;

        public DateTimeFilterModelBinderProvider(CollectionModelBinderProvider originalProvider)
        
            this.originalProvider = originalProvider;
        

        public override IModelBinder GetBinder(HttpConfiguration configuration, Type modelType)
        
            IModelBinder originalBinder = originalProvider.GetBinder(configuration, modelType);

            if (originalBinder != null && modelType == typeof(DateTimeFilter))
            
                return new DateTimeFilterModelBinder();
            
            return null;
        
    

    public class DateTimeFilterModelBinder : IModelBinder
    
        public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
        
            if (bindingContext.ModelType != typeof(DateTimeFilter))
            
                return false;
            
            //Get's the correct key/value from the querystring based on your receiving paramter name.
            //note: you can't use [FromUri(Name = "customName")] with the custom binder so the have to match (partially)
            var query = actionContext.Request.Properties["MS_QueryNameValuePairs"] as ICollection<KeyValuePair<string, string>>;
            KeyValuePair<string, string> kvp = query.First(q => q.Key.Contains(bindingContext.ModelName));
            if (kvp.Key.Contains(":"))
            
                bindingContext.Model =
                    new DateTimeFilter
                    
                        Operator = ConvertOperator(kvp.Key.Substring(kvp.Key.IndexOf(":")+1)),
                        Date = ConvertDate(kvp.Value)
                    ;
            
            else
            
                bindingContext.Model =
                    new DateTimeFilter
                    
                        Operator = Operator.Equals,
                        Date = ConvertDate(kvp.Value)
                    ;
            
            return true;
        

        private DateTime? ConvertDate(string str)
        
            DateTime result;
            DateTimeOffset resultOffset;
            if (DateTime.TryParse(str, out result))
                return result;
            //Apparently the + gets converted into a space, so we need to revert that to have a valid offset
            else if (DateTimeOffset.TryParse(str.Replace(' ', '+'), out resultOffset))
                return resultOffset.ToLocalTime().DateTime;
            else
                return null;
        

        private Operator ConvertOperator(string str)
        
            switch (str.ToLowerInvariant())
            
                case "gte": return Operator.GreaterThenEquals;
                case "gt": return Operator.GreaterThen;
                case "lte": return Operator.LesserThenEquals;
                case "lt": return Operator.LesserThen;
                case "eq": return Operator.Equals;
                default: throw new ArgumentException("Invalid operator");
            
        
    

转换方法非常适合在核心应用程序中使用

Framework 中没有启动,参数必须通过属性与 binder 耦合:

[HttpGet]
public IHttpActionResult Get(string param1 = null, string param2 = null, [ModelBinder(typeof(DateTimeFilterModelBinder))] DateTimeFilter eventCreatedDateTime = null, string param3 = null)

     //Do Logic

对于 eventCreatedDateTime=2021-04-01T14:12:56+01:00,上述工作正常

例如 eventCreatedDateTime:gte=2021-04-01T14:12:56+01:00

【讨论】:

【参考方案2】:

为此,我将使用自定义类型,例如:

public class EventCreatedDateTime

    public string Operator  get; set; 
    public string Value  get; set; 

接下来我将创建一个自定义模型绑定器:

public class EventCreatedDateTimeModelBinderProvider : IModelBinderProvider

    public IModelBinder GetBinder(ModelBinderProviderContext context)
    
        if(context.Metadata.ModelType == typeof(EventCreatedDateTime))
        
            return new EventCreatedDateTimeModelBinder();
        
        return null;
    


public class EventCreatedDateTimeModelBinder : IModelBinder

    public Task BindModelAsync(ModelBindingContext bindingContext)
    
        foreach(var kvp in bindingContext.HttpContext.Request.Query)
        
            if (kvp.Key.StartsWith("eventCreatedDateTime:"))
            
                bindingContext.Result = ModelBindingResult.Success(
                    new EventCreatedDateTime 
                        Operator = kvp.Key.Substring("eventCreatedDateTime:".Length),
                        Value = kvp.Value.First()
                );
            
        
        return Task.CompletedTask;
    

我在 Startup 中添加的:

public class Startup

    public void ConfigureServices(IServiceCollection services)
    
        services.AddControllers(options => 
            options.ModelBinderProviders.Insert(0, new EventCreatedDateTimeModelBinderProvider())
        );
        ...
    

那么动作是:

[HttpGet]
public IActionResult Get(
    string param1,
    string param2,
    EventCreatedDateTime eventCreatedDateTime)
...

【讨论】:

以上是关于从包含冒号 (:) 的查询参数名称中读取值的主要内容,如果未能解决你的问题,请参考以下文章

需要 bash shell 脚本来从文件中读取名称值对

如何使用 ASP.NET Core 从查询字符串中读取值?

读取具有多个名称空间的子节点

处理用户输入与显示数据------------(读取参数读取程序名称测试参数)

冒号是不是需要在 URI 查询参数中进行编码?

ThinkPHP 读取数据