ASP.NET MVC在参数为Model时的posted file模型绑定

24

是否有办法使 ASP.NET MVC 中的模型绑定可以自动处理提交的文件 (<input type="file" />),而无需手动查看请求上下文中的自定义模型绑定器,并且不需要创建仅接受已提交文件作为输入的单独操作方法?

我原本以为这会起作用:

class MyModel {
  public HttpPostedFileBase MyFile { get; set; }
  public int? OtherProperty { get; set; }
}

<form enctype="multipart/form-data">
  <input type="file" name="MyFile" />
  <input type="text" name="OtherProperty" />
</form>

public ActionResult Create(MyModel myModel) { ... } 

但是,在上述情况下,MyFile甚至不是绑定上下文中值提供者值的一部分。(当然,OtherProperty是。)但是如果我这样做,它就可以工作:

public ActionResult Create(HttpPostedFileBase postedFile, ...) { ... } 
那么,当参数是模型时为什么不会发生绑定,如何使其起作用?我使用自定义模型绑定器没有问题,但如何在不查看Request.Files["MyFile"]的情况下在自定义模型绑定器中实现此功能?
为了一致性、清晰度和可测试性,我希望我的代码可以自动绑定模型上的所有属性,包括那些绑定到已发布文件的属性,而不需要手动检查请求上下文。我目前正在使用Scott Hanselman所写的方法测试模型绑定。
或者,我走错了吗?你会如何解决这个问题?或者由于Request.Form和Request.Files之间的分离历史,这是不可能的设计?
4个回答

29

原来的问题是这样的: ValueProviderDictionary 只会查找 Request.FormRouteDataRequest.QueryString 来填充模型绑定上下文中的值提供程序字典。所以,如果没有直接检查请求上下文中的文件集合,自定义模型绑定程序就无法让已发布的文件参与模型绑定。以下是我找到的最接近实现相同功能的方法:

public ActionResult Create(MyModel myModel, HttpPostedFileBase myModelFile) { }
只要myModelFile实际上是文件输入表单字段的名称,就不需要任何自定义内容。

5
注意,不要忽略表单中的 enctype 属性。它必须指定为 "multipart/form-data"。否则,在 POST 请求中与输入标签上的 name 属性匹配的 HttpPostedFileBase 参数将保持为空值。 - Bart Verkoeijen
我尝试使用相同的代码,但出现了错误:“无法绑定多个参数”,在我的$.ajax中我设置了:type: 'POST',dataType: 'json',contentType: 'multipart/form-data',data: formData。 - ujjaval
这个解决方案不起作用。它会导致异常:无法将多个参数绑定到请求的内容。请参见:https://dev59.com/EqLia4cB1Zd3GeqPos78 - OronDF343
@OronDF343 这个解决方案适用于ASP.NET MVC 1。您正在使用的是该版本吗?如果您需要ASP.NET MVC 2的解决方案,请参考例如https://dev59.com/U3NA5IYBdhLWcg3wcNbF#967101?noredirect=1#comment2144431_1646735 - bzlm

14

另一种方法是添加一个与输入框名称相同的隐藏字段:

<input type="hidden" name="MyFile" id="MyFileSubmitPlaceHolder" />

DefaultModelBinder会检测到一个字段并创建正确的绑定器。


2
似乎ASP.NET MVC 2 RC通过不使用隐藏字段来处理this。 - Brian Chance
非常正确,我正在使用ASP.NET MVC 2,并且可以成功地将我的文件输入绑定到我的模型上,而不需要任何额外的工作。太棒了! - Pandincus

7

您看过他从您链接的那篇文章(通过另一篇文章...)中链接到的这篇文章吗?

如果没有,它看起来非常简单。这是他使用的模型绑定器:

public class HttpPostedFileBaseModelBinder : IModelBinder {
    public ModelBinderResult BindModel(ModelBindingContext bindingContext) {
        HttpPostedFileBase theFile =
            bindingContext.HttpContext.Request.Files[bindingContext.ModelName];
        return new ModelBinderResult(theFile);
    }
}

他在 Global.asax.cs 中注册它,如下所示:
ModelBinders.Binders[typeof(HttpPostedFileBase)] = 
    new HttpPostedFileBaseModelBinder();

并带有一个看起来像这样的表单的帖子:
<form action="/File/UploadAFile" enctype="multipart/form-data" method="post">
    Choose file: <input type="file" name="theFile" />
    <input type="submit" />
</form>

所有的代码都是直接从博客文章中复制过来的...

实际上,这是我目前正在使用的方法。但是这种方法有两个问题:1;它使用请求上下文(通过bindingContext.HttpContext.Request),而我真的不想要这个;2;它只处理了一个仅包含上传文件的场景(当然可以轻松更改)。 - bzlm
此外,BindModel(ModelBindingContext bindingContext)看起来像是预发布代码。其中还有一个ControllerContext。 - bzlm
1
你有没有看过MVC框架的源代码?我不确定“正常”的模型绑定器是如何工作的,但我真的看不出来如果不在某个地方使用HttpContext.Current.Request.Form[]集合,你怎么能够获取表单值...... http://weblogs.asp.net/scottgu/archive/2008/03/21/asp-net-mvc-source-code-now-available.aspx - Tomas Aschan
实际上,我刚刚查看了ASP.NET MVC 1.0的源代码,发现HttpPostedFileBaseModelBinder实际上已经内置在框架中了。你应该能够通过使用像ActionResult(HttpPostedFileBase theFile, string theFileName, string theDescription)这样的签名来绑定其他几个参数。 - Tomas Aschan
是的。我的问题是:没有办法使用一个带有模型作为参数的操作来完成这个任务吗?而不仅仅是一个已发布的文件?必须将一个操作方法限制为一个参数才能使其自动工作,这似乎有点受限制。 - bzlm

-17

您不需要注册自定义绑定器,HttpPostedFileBase 在框架中默认已注册:

public ActionResult Create(HttpPostedFileBase myFile)
{
    ...
}

偶尔阅读一本书(原文链接)是很有帮助的,而不是仅仅依赖于博客和网络论坛。


我已经编辑了问题,更清楚地表明我不想要一个以发布的文件作为唯一参数的操作方法。如果发布的文件只是模型中许多属性之一,是否有任何方法可以实现相同的功能?(顺便说一下,我还有另一本ASP.NET MVC书籍。 :)) - bzlm
这是正确的答案;我之前遇到过同样的问题。HttpPostedFileBase 可以绑定但 HttpPostedFile 不行。 - Nathan Ridley
不,HttpPostedFileBase将不会在默认模型绑定器中使用标准值提供程序绑定进行绑定。但是它将在此答案中的方法中进行绑定。 - bzlm
24
在你变得自以为是之前,最好先完整、仔细地阅读问题;这也有助于你更好地理解问题。 - James Allen

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