.Net MVC如何将多部分/表单数据提交到Web Api

时间:2019-07-05 04:15:31

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

我想通过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    
        }
}

2 个答案:

答案 0 :(得分:0)

ApiController期望您的控制器隐式接收JSON,而Controller期望对Form Data执行相同的操作。要告诉apicontroller中的方法期望表单数据,您需要[FromForm]

[HttpPost]
[ResponseType(typeof(News))]
public IHttpActionResult PostNewsModel([FromForm] News newsModel)
{
          //some code    
}

答案 1 :(得分:0)

前一段时间,我正在处理几乎相同的问题。之所以出现此行为,是因为ASP.Net中没有multipart/form-data媒体类型的“开箱即用” formatter > WepAPI (奇怪的是,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());

        // ...
    }