【问题标题】:Controller parameter does not become null控制器参数不为空
【发布时间】:2013-12-19 18:44:25
【问题描述】:

我遇到了以下问题:

我有一个带有引用类型作为参数的 asp.net mvc 5 控制器:

    [Route("")]
    [HttpGet]
    public ActionResult GetFeeds(Location location)
    {
        if (location == null)
        {
            // init location or smth
        }

        // Do smth with location

        return new EmptyResult();
   }

您会注意到我正在使用 AttributeRouting。没有其他与此操作名称相同的方法。

但是 - 这是我的位置类:

    public class Location : ILocation
    {
       public DbGeography Coordinates { get; set; }

       public double Latitude { get; set; }

       public double Longitude { get; set; }
    }

这里没有什么特别的(接口定义了所有这些属性)。

如果我正在访问控制器操作(实际上是使用 powershell)并传递如下内容:

http://localhost:2000/Feed?latitude=23.1&longitude=37

一切正常,但如果我正在使用

http://localhost:2000/Feed

位置参数不为空(它是具有默认值的新位置),这是我想要的行为:(。

有人知道为什么会这样吗?

提前致谢

【问题讨论】:

  • 活页夹按设计工作 - 它正在实例化一个 Location 对象并使用默认值填充其值,除非您提供覆盖值(您不在第二个示例中)。它不会仅仅因为您的路由没有提供任何值而创建您的 Location 对象的空实例。
  • @ChristopherKellner - 由于“模型绑定器的工作方式”,它似乎不可能。请查看我的答案中的链接以获得更多解释。

标签: c# asp.net-mvc controller


【解决方案1】:

MVC 模型绑定器已接管。我最初发布的内容适用于不通过模型绑定器的实例。但是,based on other answers on SO 和我自己的快速测试,由于活页夹的工作方式以及将属性与表单值联系起来的方式,viewmodel 参数似乎永远不会为空。

在你的例子中,我会检查纬度和经度是否都为空,看看是否没有通过。这意味着您需要使它们在您的 ViewModel 上可以为空

public class Location : ILocation
    {
       public DbGeography Coordinates { get; set; }

       public double? Latitude { get; set; }

       public double? Longitude { get; set; }
    }

更新的控制器代码

if (location.Latitude == null && location.Longitude == null)
    {
        // init location or smth
    }

【讨论】:

  • 我试过了,但这个位置仍然是一个新的位置实例。还有什么建议吗?
  • @ChristopherKellner - 是的,你是对的,为你更新了我的答案
  • location.Latitude.HasValue 会更好看:D
  • 它不会映射到null,这很奇怪。如果将其标记为可空怎么办? (Location? location = null)
  • 将特定于模型的验证放在控制器中是个坏主意(控制器很难测试,而且您很可能会重复自己)。 ASP.Net MVC 附带数据注释验证器是有充分理由的。
【解决方案2】:

ModelBinder 创建一个新的对象实例,所以这里有两种选择:

在“必需属性”上有一个[Required] DataAnnotation 并将它们标记为可为空,然后检查ModelState.IsValid(推荐)

使纬度和经度可以为空double?,您可以检查Latitude.HasValue && Longitude.HasValue

更新:

public class Location : ILocation
    {
       public DbGeography Coordinates { get; set; }

       public double? Latitude { get; set; }

       public double? Longitude { get; set; }
    }

public class LocationGetFeedsViewModel : LocationGetFeedsBinderModel {
       // change coordinates to string because maybe that's easier to handle on the view.
       public string Coordinates { get; set; }
       // added to sum to the example
       public IEnumerable<SelectListItem> Zones { get; set; } 
}

public class LocationGetFeedsBinderModel {
       [Required]
       public double? Latitude { get; set; }
       [Required]
       public double? Longitude { get; set; }
}

控制器:

public ActionResult GetFeeds(LocationGetFeedsBinderModel location) {
    if (!ModelState.IsValid)
        // redirect or display some error 
    return new EmptyResult();
}

【讨论】:

  • 遗憾的是,我无法将这些属性更改为可为空的值,因为它们是由 ILocation 继承的。我看到实现目标的 3 种可能性:创建一个具有可为空属性的新 VM 类并将它们解析到一个新位置,或者将它们都检查为 0,因为这非常罕见。两者都感觉不对:/第三个可以作为参数为空,这看起来更好。还是你有别的想法?
  • 这就是我所说的 BinderModel 而不是 ViewModel。这是非常有用的,因为 viewModel 通常比只接受 2 或 3 的表单帖子有更多的属性。两全其美。实际上,我认为让控制器接受完整的 ViewModel 很少,甚至很糟糕。
  • 那么你会推荐第一个使用“BinderModel”的方法吗?
  • 是的,是的,是的!确实。在真实的场景中,当您只有 1 个表单时,我确实让 ViewModel 继承了 BinderModel,例如:LocationEditViewModel:LocationEditBinderModel。您将可以使用完全有效的数据注释访问视图上的所有属性,并且控制器级别的 Post 签名将收到 LocationEditBinderModel。我对所有 POST 都这样做。对我来说非常重要的是完全控制有多少属性被公开并可供用户使用,以避免在模型级别过度发布并保持控制器签名干净。
  • 在您的示例中,我可以发布 Coordinates 属性,我认为这不是故意的,因此您会暴露过度发布。这可能是安全的,也可能是糟糕的......最好通过设计来防止它。
【解决方案3】:

默认模型绑定器将始终实例化一个复杂对象,因此很遗憾它永远不会为空,并且将跳过任何可选分配。

此行为应用于您的系统的最终结果将是您必须检测默认构造函数的使用。反射或隐式检查无法做到这一点。

必须明确地完成。

这可以通过在默认构造函数中设置类中的标志来实现,如已接受的答案中所建议的可以为空的属性,通过使用 Bart 建议的数据注释,通过对属性使用自定义 get 和 set 方法,或者各种其他方式。

【讨论】:

  • 这是我原来的答案。当您进入 ActionResult 时,该变量仍将是非空的。在VS2013上测试了自己。猜测在 MVC 中的某个时间点对 DefaultModelBinder 进行了更新。
  • @Tommy - 我删除了使用设置为 null 的可选属性的建议。正如您准确指出的那样,它将被实例化,并且永远不会分配可选值。请查看修改。
【解决方案4】:

我一直在用 angularjs 测试发布数据。

我发现,如果您将值强制为 null 而不是 undefined,ModelBinder 不会实例化复杂对象。

if (!location)
    location = null;

$http({ method: "POST", url: "/Location/Create", data: { location } })

【讨论】:

  • 我在 2020 年使用 React,效果很好。
猜你喜欢
  • 1970-01-01
  • 2018-12-24
  • 1970-01-01
  • 2014-02-21
  • 1970-01-01
  • 1970-01-01
  • 2019-04-09
  • 2013-01-18
  • 1970-01-01
相关资源
最近更新 更多