如何在asp.net core web api中绑定Json查询字符串

10
在ASP.NET Web API中,以下代码运行良好,但在ASP.NET Core中不起作用。
端点api/devices?query={"deviceName":"example"}
[HttpGet]
public Device ([FromUri] string deviceName)
{        
        var device = context.Computers.Where(x => x.deviceName == deviceName);
        return device;
}
在ASP.NET Core Web API中,缺少[FromUri]属性,我尝试使用以下代码,但没有成功。
[HttpGet]
public Device  Get([FromQuery] string  deviceName)
{
    return repo.GetDeviceByName(deviceName);
}

1
看起来像是非功能性的代码。参数被称为query而不是deviceNamedeviceName是一些类似JSON的查询参数的属性。对于GET请求,你应该只使用查询参数,而对于POST请求,则将数据传输到请求体中。如果你真的想要这种非标准的方式(与ASP.NET Core没有任何关联)仍然工作,你需要编写自己的模型绑定器。 - Tseng
在自己苦苦思索一段时间后,我最终意识到,尽管我想要一个值回来,但我需要执行POST而不是GET。发送的数据虽然没有被持久化,但仍会影响API的状态。你需要考虑你的RESTful接口的设计 - 这是关键。 - Neo
我认为Tseng的意思是将其更改为**Get([FromQuery] string query)**。 - Phil Huhn
2个回答

12

很遗憾,无法像您所描述的那样在GET查询中绑定JSON。您要寻找的是使用自定义模型绑定程序,告诉ASP.net Core如何进行绑定。

首先,您需要构建JSON对象的模型。

public class MyCustomModel
{
    public string DeviceName { get; set; }
}

接下来,您需要构建模型绑定器。以下是一个简单的示例,但您显然希望在其周围进行其他检查,例如是否可以进行转换、Try/Catch块等。实质上,模型绑定器告诉ASP.net Core如何绑定模型。您可能还会遇到TypeConverters,它们给出了类型,在模型绑定期间如何将其更改为另一种类型。现在,让我们只使用模型绑定器。

public class MyViewModelBinder : IModelBinder
{
    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        var jsonString = bindingContext.ActionContext.HttpContext.Request.Query["query"];
        MyCustomModel result = JsonConvert.DeserializeObject<MyCustomModel>(jsonString);

        bindingContext.Result = ModelBindingResult.Success(result);
        return Task.CompletedTask;
    }
}

所以我们正在做的就是将查询字符串反序列化到我们的模型中。

接下来,我们构建一个提供程序。提供程序告诉 ASP.net core 使用哪个 modelbinder。在我们的情况下很简单,如果模型类型是我们的自定义类型,则使用我们的自定义绑定器。

public class MyViewModelBinderProvider : IModelBinderProvider
{
    public IModelBinder GetBinder(ModelBinderProviderContext context)
    {
        if (context.Metadata.ModelType == typeof(MyCustomModel))
            return new MyViewModelBinder();

        return null;
    }
}

而拼图的最后一块是在我们的startup.cs文件中,找到我们添加MVC服务的位置,并将我们的模型绑定器插入列表的最前面。这很重要。如果我们只是将模型绑定器添加到列表中,另一个模型绑定器可能认为它应该被使用(先到先得),所以我们可能永远无法使用我们的模型绑定器。因此,请确保将其插入开头。

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc(config => config.ModelBinderProviders.Insert(0, new MyViewModelBinderProvider()));
}

现在我们只需要创建一个操作来读取数据,不需要任何属性。

[HttpGet]
public void Get(MyCustomModel model)
{

}

更多阅读:


谢谢。这非常有帮助。如果我在API控制器中有两个Get方法,一个获取所有设备,另一个Get方法需要设备名称,则在具有相同Get方法时会出现错误。 - Rohit
如果它们都接受相同的有效载荷(整个JSON对象),那么您只需要一个,对吧? - MindingData
这样做的缺点是,现在每次创建自定义模型绑定器时,您都必须确保您的JSON也可以反序列化它。使用JSON值提供程序可能是一个更好的想法,然后再让默认的模型绑定器接管。这样,您还可以获得有关各个属性的错误信息。 - johnny 5
哇,这需要很多工作才能避免直接在控制器中调用JSON反序列化器。 - Grault

3

我遇到了这个问题,因为我有一个类似的问题。在我的情况下,更方便实现ModelBinderAttribute:

public class FromUriJsonAttribute : ModelBinderAttribute
{
    public FromUriJsonAttribute()
    {
        BinderType = typeof(JsonBinder);
    }

    public FromUriJsonAttribute(string paramName)
        : this()
    {
        Name = paramName;
    }
}

正如 @MindingData 的回答所述,需要使用 binder:
public class JsonBinder : IModelBinder
{
    public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
    {
        try
        {
            var name = bindingContext.ModelName;
            var json = actionContext.Request.GetQueryNameValuePairs().FirstOrDefault(x => x.Key == name).Value;
            var targetType = bindingContext.ModelType;
            var model = Newtonsoft.Json.JsonConvert.DeserializeObject(json, targetType);
            bindingContext.Model = model;
            return true;
        }
        catch
        {
        }
        return false;
    }
}

而且用法:

[Route("{id}/items")]
public Item GetItem(string id, [FromUriJson] Filter filter)
{
    //logic
}  

或者:

[Route("{id}/items")]
public Item GetItem(string id, [FromUriJson("queryStringKey")] Filter filter)
{
    //logic
} 

如果您只想在某些端点中使用Json格式的查询字符串参数,或者不想通过插入新的IModelBinderProvider实现来干扰默认的IServiceCollection配置,则此方法可能很有用。


你需要在任何地方(比如提供者)注册ModelBinder或Attribute吗?因为在我的情况下,ModelBinder没有被激活。 - Bouke Versteegh
@BoukeVersteegh 不,我只在控制器方法中需要时使用属性。ModelBinderAttribute 的 BinderType 设置为 JsonBinder。在我的情况下,BindModel() 在控制器操作之前被调用。 - Maciej Medycki
值得注意的是,这种方法会触发GitHub的安全警报“反序列化不受信任的数据”。 参考:https://owasp.org/www-community/vulnerabilities/Deserialization_of_untrusted_data - Dandry

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接