.Net MVC 如何将 multipart/form-data 提交到 Web Api
Posted
技术标签:
【中文标题】.Net MVC 如何将 multipart/form-data 提交到 Web Api【英文标题】:.Net MVC How to submit multipart/form-data to Web Api 【发布时间】:2019-11-15 16:47:56 【问题描述】:我想通过 jquery ajax 和 .net api 控制器向服务器提交一个包含值和图像文件的表单。但是服务端取不到数据,一直显示输入参数为空。
我已将config.Formatters.XmlFormatter.SupportedMediaTypes.Add(new System.Net.Http.Headers.MediaTypeHeaderValue("multipart/form-data"));
添加到WebApiConfig.cs
文件中。但是还是不行。
不过,有趣的是,当我将代码块移动到我的AdminController.cs
时,它可以工作。
在以下特定情况下,如果我将表单提交给/admin/submitnew
,它会完美运行。如果提交到/api/news
,服务器上的newsModel
只会收到空值。
所以我的问题是,为什么在apicontroller
下无法接收/准备好数据,以及如何解决这个问题。
NewsEdit.cshtml
@using (html.BeginForm(null, null, FormMethod.Post, new id = "editform" ))
@Html.AntiForgeryToken()
<div class="form-horizontal">
@Html.ValidationSummary(true, "", new @class = "text-danger" )
<div class="form-group">
@Html.LabelFor(model => model.Title, htmlAttributes: new @class = "control-label col-md-2" )
<div class="col-md-10">
@Html.EditorFor(model => model.Title, new htmlAttributes = new @class = "form-control", @id = "title" )
@Html.ValidationMessageFor(model => model.Title, "", new @class = "text-danger" )
</div>
</div>
<div class="form-group">
<Lable class="control-label col-md-2">Cover Image</Lable>
<div class="col-md-10">
<input type="file" name="ImgFile" class="control-label" accept="image/png, image/jpeg" />
<br /><img src="@Model.ImgPath" style="max-width:300px" />
</div>
</div>
</div>
NewsEdit.js
$("#submit").click(function (e)
if ($("#editform").valid())
e.preventDefault();
$.ajax(
url: "/admin/submitnews",
type: "POST",
data: data,
cache: false,
contentType: false,
processData: false,
async: false,
success: function ()
****
,
error: function (e)
****
,
)
AdminControllers.cs
public class AdminController : Controller
[HttpPost]
[ValidateInput(false)]
public ActionResult SubmitNews(News newsModel)
//some code
NewsController.cs
public class NewsController : ApiController
[HttpPost]
[ResponseType(typeof(News))]
public IHttpActionResult PostNewsModel(News newsModel)
//some code
【问题讨论】:
【参考方案1】:ApiController
期望您的控制器隐式接收 JSON,而 Controller
期望与表单数据相同。要告诉 apicontroller 中的方法期待表单数据,您需要 [FromForm]
[HttpPost]
[ResponseType(typeof(News))]
public IHttpActionResult PostNewsModel([FromForm] News newsModel)
//some code
【讨论】:
FromForm
- 适用于 asp.net core,对吗?但是问题标签是关于经典的 asp.net
@vasily.sib 嗨,我使用的是 .net 4.6。没有“FromForm”。对经典 .net 有什么想法吗?
@Lei.L 前段时间我正在处理这个问题,我会尽快发布答案
我认为这是 .net 核心开发人员的最佳答案,但不是那些寻求经典 .net 解决方案的人。【参考方案2】:
前段时间我正在处理几乎相同的问题。你得到这种行为的原因是在 ASP.Net WepAPI 中没有 "out-of-the-box" formatter for multipart/form-data
media-type strong>(奇怪的是,ASP.Net MVC 中有一个)。
我不记得我看到的 SO 问题、Microsoft 文档、ASP.Net 资源和文章的确切路径,但这是工作结果:
创建一个HttpPostedFileMultipart
类来处理发布的文件:
public class HttpPostedFileMultipart : HttpPostedFileBase
public override string FileName get;
public override string ContentType get;
public override Stream InputStream get;
public override int ContentLength => (int)InputStream.Length;
public HttpPostedFileMultipart(string fileName, string contentType, byte[] fileContents)
FileName = fileName;
ContentType = contentType;
InputStream = new MemoryStream(fileContents);
然后创建您的 MediaTypeFormatter:
public class FormMultipartEncodedMediaTypeFormatter : MediaTypeFormatter
private const string SupportedMediaType = "multipart/form-data";
public FormMultipartEncodedMediaTypeFormatter()
SupportedMediaTypes.Add(new MediaTypeHeaderValue(SupportedMediaType));
// can we deserialize multipart/form-data to specific type
public override bool CanReadType(Type type)
if (type == null) throw new ArgumentNullException(nameof(type));
return true;
// can we serialize specific type to multipart/form-data
public override bool CanWriteType(Type type)
if (type == null) throw new ArgumentNullException(nameof(type));
return false;
// deserialization
public override async Task<object> ReadFromStreamAsync(
Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger)
if (type == null) throw new ArgumentNullException(nameof(type));
if (readStream == null) throw new ArgumentNullException(nameof(readStream));
try
// read content
var multipartProvider = await content.ReadAsMultipartAsync();
// fill out model dictionary
var modelDictionary = await ToModelDictionaryAsync(multipartProvider);
// apply dictionary to model instance
return BindToModel(modelDictionary, type, formatterLogger);
catch (Exception e)
if (formatterLogger == null) throw;
formatterLogger.LogError(string.Empty, e);
return GetDefaultValueForType(type);
// fill out model dictionary
private async Task<IDictionary<string, object>> ToModelDictionaryAsync(MultipartMemoryStreamProvider multipartProvider)
var dictionary = new Dictionary<string, object>();
foreach (var element in multipartProvider.Contents)
// getting element name
var name = element.Headers.ContentDisposition.Name.Trim('"');
// if we have a FileName - this is a file
// if not - pretend this is a string (later binder will transform this strings to objects)
if (!string.IsNullOrEmpty(element.Headers.ContentDisposition.FileName))
// create our HttpPostedFileMultipart instance if we have any data
if (element.Headers.ContentLength.GetValueOrDefault() > 0)
dictionary[name] = new HttpPostedFileMultipart(
element.Headers.ContentDisposition.FileName.Trim('"'),
element.Headers.ContentType.MediaType,
await element.ReadAsByteArrayAsync()
);
else
dictionary[name] = null;
else
dictionary[name] = await element.ReadAsStringAsync();
return dictionary;
// apply dictionary to model instance
private object BindToModel(IDictionary<string, object> data, Type type, IFormatterLogger formatterLogger)
if (data == null) throw new ArgumentNullException(nameof(data));
if (type == null) throw new ArgumentNullException(nameof(type));
using (var config = new HttpConfiguration())
if (RequiredMemberSelector != null && formatterLogger != null)
config.Services.Replace(
typeof(ModelValidatorProvider),
new RequiredMemberModelValidatorProvider(RequiredMemberSelector));
var actionContext = new HttpActionContext
ControllerContext = new HttpControllerContext
Configuration = config,
ControllerDescriptor = new HttpControllerDescriptor Configuration = config
;
// workaround possible locale mismatch
var cultureBugWorkaround = CultureInfo.CurrentCulture.Clone() as CultureInfo;
cultureBugWorkaround.NumberFormat = CultureInfo.InvariantCulture.NumberFormat;
var valueProvider = new NameValuePairsValueProvider(data, cultureBugWorkaround);
var metadataProvider = actionContext.ControllerContext.Configuration.Services.GetModelMetadataProvider();
var metadata = metadataProvider.GetMetadataForType(null, type);
var modelBindingContext = new ModelBindingContext
ModelName = string.Empty,
FallbackToEmptyPrefix = false,
ModelMetadata = metadata,
ModelState = actionContext.ModelState,
ValueProvider = valueProvider
;
// bind our model
var modelBinderProvider = new CompositeModelBinderProvider(config.Services.GetModelBinderProviders());
var binder = modelBinderProvider.GetBinder(config, type);
var haveResult = binder.BindModel(actionContext, modelBindingContext);
// store validation errors
if (formatterLogger != null)
foreach (var modelStatePair in actionContext.ModelState)
foreach (var modelError in modelStatePair.Value.Errors)
if (modelError.Exception != null)
formatterLogger.LogError(modelStatePair.Key, modelError.Exception);
else
formatterLogger.LogError(modelStatePair.Key, modelError.ErrorMessage);
return haveResult ? modelBindingContext.Model : GetDefaultValueForType(type);
最后,在您的 WebApiConfig.Register()
方法中注册此格式化程序:
public static void Register(HttpConfiguration config)
// ...
// add multipart/form-data formatter
config.Formatters.Add(new FormMultipartEncodedMediaTypeFormatter());
// ...
【讨论】:
如果与 .net core 的解决方案相比必须如此复杂,我宁愿创建一个名为apiControllers.cs
的常规控制器,然后在那里做我的 api。 Jusi 我认为....也许更好
@Lei.L 我认为WebAPI
没有这个格式化程序的原因是multipart/form-data
媒体类型更适合MVC
应用程序。例如,本机客户端永远不会以multipart/form-data
格式向 API 服务器发布任何内容。他们只是在 JSON 中发布这个。 JS 客户端也可以。
我同意。显然,MS 知道这个问题并且只在.net core 上解决了:(
明确一点:我认为这不是问题。我认为这只是一个决定(奇怪的一个)。 .net Core 怎么样 - 它是通过将整个 WepAPI 代码库移动到 MVC 来“解决”的:)以上是关于.Net MVC 如何将 multipart/form-data 提交到 Web Api的主要内容,如果未能解决你的问题,请参考以下文章
如何将项目引用添加到 ASP.NET Core 1.0 MVC 项目
如何将 MVC5 RedirectResult () 重定向到 Asp.net 中的整页?
Asp.net Core 如何将 ReflectionIT.Mvc.Paging 与 ViewModel 一起使用?