ASP.Net Core中通过Body进行自定义模型绑定。

12
我想通过HTTP Post请求体在控制器中绑定对象。 它的工作方式如下。
public class MyModelBinder : IModelBinder
    {
        public Task BindModelAsync(ModelBindingContext bindingContext)
        {
            if (bindingContext == null)
                throw new ArgumentNullException("No context found");

            string modelName = bindingContext.ModelName;
            if (String.IsNullOrEmpty(modelName)) {
                bindingContext.Result = ModelBindingResult.Failed();
                return Task.CompletedTask;
            }

            string value = bindingContext.ValueProvider.GetValue(modelName).FirstValue;
...

modelNameviewModel(说实话,我不知道为什么,但它可行...)

我的控制器看起来像这样

    [HttpPost]
    [Route("my/route")]
    public IActionResult CalcAc([ModelBinder(BinderType = typeof(MyModelBinder))]IViewModel viewModel)
    {
   ....

即,当我进行此 HTTP-Post 请求时,它会起作用。
url/my/route?viewModel=URLparsedJSON

我希望可以将其通过请求主体进行传递,即:
public IActionResult Calc([FromBody][ModelBinder(BinderType = typeof(MyModelBinder))]IViewModel viewModel)

在我的Modelbinder中,modelName是"",ValueProvider返回null...我做错了什么?
更新:
例如,假设您有一个接口IGeometry和许多不同2D形状的实现,如Circle: IGeometry或Rectangle: IGeometry或Polygon: IGeometry。 IGeometry本身具有方法decimal getArea()。现在,我的URL将为实现IGeometry的任何形状计算面积,看起来像这样。
    [HttpPost]
    [Route("geometry/calcArea")]
    public IActionResult CalcArea([FromBody]IGeometry geometricObject)
    {
         return Ok(geometricObject.getArea());
         // or for sake of completness
         // return Ok(service.getArea(geometricObject));
    }

问题在于,您无法绑定到一个接口,这会导致错误,您需要一个类!这就是使用自定义模型绑定器的地方。假设您的IGeometry还有以下属性string Type {get; set;} ,那么在自定义模型绑定中,您只需在传递的JSON中搜索该Type并将其绑定到正确的实现即可。类似于:
if (bodyContent is Rectangle) // that doesn't work ofc, but you get the point
var boundObject = Newtonsoft.Json.JsonConvert.DeserializeObject<Rectangle>(jsonString);

ASP.Net EF

在 ASP.Net EF 中,自定义模型绑定的代码如下:

 public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)

这里可以像这样获取HTTPPost请求的主体内容:
 string json = actionContext.Request.Content.ReadAsStringAsync().Result;

在ASP.Net Core中,您没有actionContext,只有bindingContext,在那里我找不到HTTP Post的正文。

更新2

好的,我找到了正文,请参见接受的答案。现在,在控制器方法内部,我确实有一个类型为IGeometry(一个接口)的对象,该对象是在自定义模型绑定器中实例化的!我的控制器方法如下:

    [HttpPost]
    [Route("geometry/calcArea")]
    public IActionResult CalcArea([FromBody]IGeometry geometricObject)
    {
         return Ok(service.getArea(geometricObject));
    }

我的注入服务如下所示

    public decimal getArea(IGeometry viewModel)
    {
        return viewModel.calcArea();
    }

IGeometry 另外一方面看起来像这样

public interface IGeometry
{
    string Type { get; set; } // I use this to correctly bind to each implementation
    decimal calcArea();

每个类根据需要计算相应的面积,因此
public class Rectangle : IGeometry
{
    public string Type {get; set; }
    public decimal b0 { get; set; }
    public decimal h0 { get; set; }

    public decimal calcArea()
    {
        return b0 * h0;
    }

或者
public class Circle : IGeometry
{
    public string Type {get; set; }
    public decimal radius { get; set; }

    public decimal calcArea()
    {
        return radius*radius*Math.Pi;
    }

我有点困惑。你为什么要尝试使用ModelBinder呢?为什么不直接将你要绑定的模型作为参数放入方法中呢? - Robert Perry
3
老实说,我绝不会像那样将接口用作通过网络公开的方法的参数。a) 它会引起比解决更多的问题 b) 你会一直试图追踪奇怪的错误。在我看来,坚持使用简单的 POCO(Plain Old CLR Object)进行跨网络传输,你将根本不需要模型绑定.. 这已经内置了。 - Robert Perry
1
这是什么罕见的情况?我的意思是,你只收到数据。没有任何东西阻止你从这个接口继承到你的ViewModel中(例如class ViewModel : IViewModel)。接口允许你拥有不同的实现。我无法想象传输的数据如何可以有不同的实现。它们只是数据而已。也许你需要第二种方法,可以接收另一个数据模型。然后将其传递给您的服务层,并在那里使用接口(如果必要)。 - Christian Gollhardt
1
换句话说,如果你需要在数据传输模型上使用方法,那么你做错了什么。数据传输模型只应负责数据传输。如果你需要对这些数据进行操作,那么你应该在服务层中进行操作。如果你的数据传输对象不使用方法,那么它为什么是一个接口(除非直接被你的模型继承)? - Christian Gollhardt
模型绑定器的工作是绑定模型。您所描述的是业务逻辑。如果您需要接收一个 Circle : IGeometry,则需要支持该方法。如果您接收到一个 Rectangle : IGeometry,则需要另一个接收该方法。然后,您可以将其传递给您的服务 service.DoSomething(IGeometry)。我在字里行间读出,您想滥用模型绑定器来执行业务逻辑(服务层)。 - Christian Gollhardt
显示剩余8条评论
1个回答

9

我找到了解决方案。在 ASP.NET Core 中,可以使用以下代码在自定义模型绑定器中获取 HTTP Post 请求的正文。

string json;
using (var reader = new StreamReader(bindingContext.ActionContext.HttpContext.Request.Body, Encoding.UTF8))
   json = reader.ReadToEnd();

在查看旧的 EF 项目后,我找到了解决方案。在那里,主体位于 ActionContext 内,该对象作为单独的参数传递给 BindModel 方法。我发现在 ASP.Net Core 中,同样的 ActionContextModelBindingContext 的一部分,你可以得到一个 IO.Stream 而不是字符串(很容易转换 :-))。


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