【问题标题】:.Net MVC How to submit multipart/form-data to Web Api.Net MVC 如何将 multipart/form-data 提交到 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    
        }
}

【问题讨论】:

    标签: c# .net ajax asp.net-mvc asp.net-web-api


    【解决方案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 来“解决”的:)
    猜你喜欢
    • 1970-01-01
    • 2019-06-22
    • 2017-09-20
    • 2022-06-11
    • 1970-01-01
    • 1970-01-01
    • 2018-12-01
    • 2015-04-06
    • 2015-09-19
    相关资源
    最近更新 更多