【问题标题】:An error has occurred.", ExceptionMessage: "Invalid 'HttpContent' instance provided发生错误。", ExceptionMessage: "提供了无效的 'HttpContent' 实例
【发布时间】:2018-12-24 10:10:16
【问题描述】:

尝试向 http rest 调用添加文件时出现此错误:

responseJson = {Message:“发生错误。”,ExceptionMessage: “提供的 'HttpContent' 实例无效。它确实... '多部分/'。 ↵参数名称:内容”,异常类型: “System.ArgumentException”,堆栈跟踪:“在 System.Net.Http.Formatting.Parsers.MimeMulti…tpControllerDispatcher.d__1.MoveNext()"}

我的反应组件:

import React, { Component } from 'react';
import { Row, Col } from 'antd';
import PageHeader from '../../components/utility/pageHeader';
import Box from '../../components/utility/box';
import LayoutWrapper from '../../components/utility/layoutWrapper.js';
import ContentHolder from '../../components/utility/contentHolder';
import basicStyle from '../../settings/basicStyle';
import IntlMessages from '../../components/utility/intlMessages';
import { adalApiFetch } from '../../adalConfig';

export default class extends Component {
  constructor(props) {
    super(props);
    this.state = {TenantId: '', TenantUrl: '', TenantPassword: '' };
    this.handleChangeTenantUrl = this.handleChangeTenantUrl.bind(this);
    this.handleChangeTenantPassword = this.handleChangeTenantPassword.bind(this);
    this.handleChangeTenantId= this.handleChangeTenantId.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  };


  handleChangeTenantUrl(event){
    this.setState({TenantUrl: event.target.value});
  }

  handleChangeTenantPassword(event){
    this.setState({TenantPassword: event.target.value});
  }

  handleChangeTenantId(event){
    this.setState({TenantId: event.target.value});
  }

  handleSubmit(event){
    event.preventDefault();

    const formData = new FormData();
    formData.append("TenantId", this.state.TenantId);
    formData.append("TenantUrl", this.state.TenantUrl);
    formData.append("TenantPassword", this.state.TenantPassword);

    const options = {
      method: 'put',
      data: formData,
      config: {
        headers: {
          'Content-Type': 'multipart/form-data'
        }
      }
    };

    adalApiFetch(fetch, "/Tenant", options)
      .then(response => response.json())
      .then(responseJson => {
        if (!this.isCancelled) {
          this.setState({ data: responseJson });
        }
      })
      .catch(error => {
        console.error(error);
      });
  }


  upload(e){
      let data = new FormData();
      //Append files to form data
      let files = e.target.files;
      for (let i = 0; i < files.length; i++) {
        data.append('files', files[i], files[i].name);
      }      
  }

  render(){
    const { data } = this.state;
    const { rowStyle, colStyle, gutter } = basicStyle;

    return (
      <div>
        <LayoutWrapper>
        <PageHeader>{<IntlMessages id="pageTitles.TenantAdministration" />}</PageHeader>
        <Row style={rowStyle} gutter={gutter} justify="start">
          <Col md={12} sm={12} xs={24} style={colStyle}>
            <Box
              title={<IntlMessages id="pageTitles.TenantAdministration" />}
              subtitle={<IntlMessages id="pageTitles.TenantAdministration" />}
            >
              <ContentHolder>
              <form onSubmit={this.handleSubmit}>
                <label>
                  TenantId:
                  <input type="text" value={this.state.TenantId} onChange={this.handleChangeTenantId} />
                </label>
                <label>
                  TenantUrl:
                  <input type="text" value={this.state.TenantUrl} onChange={this.handleChangeTenantUrl} />
                </label>
                <label>
                  TenantPassword:
                  <input type="text" value={this.state.TenantPassword} onChange={this.handleChangeTenantPassword} />
                </label>
                <label>
                  Certificate:
                  <input onChange = { e => this.upload(e) } type = "file" id = "files" ref = { file => this.fileUpload } />
                </label>             
              <input type="submit" value="Submit" />
              </form>
              </ContentHolder>
            </Box>
          </Col>
        </Row>
      </LayoutWrapper>
      </div>
    );
  }
}

我的租户控制器Put方法

[HttpPut]
        public async Task<IHttpActionResult> PutTenant([FromBody]Tenant tenant)
        {
            var provider = new MultipartMemoryStreamProvider();
            var contentType = "";
            var content = new byte[0];
            await base.Request.Content.ReadAsMultipartAsync(provider);
            if (provider.Contents.Count > 0)
            {
                contentType = provider.Contents[0].Headers.ContentType.MediaType;
                content = await provider.Contents[0].ReadAsByteArrayAsync();
            }


            CloudStorageAccount storageAccount = CloudStorageAccount.Parse(ConfigurationManager.AppSettings["AzureStorageKey"].ToString());
            // Create the blob client.
            CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();

            // Retrieve reference to a previously created container.
            CloudBlobContainer container = blobClient.GetContainerReference(ConfigurationManager.AppSettings["certificatesContainer"].ToString());

            // Retrieve reference to a blob named "myblob".
            CloudBlockBlob blockBlob = container.GetBlockBlobReference("myblob");

            // Create or overwrite the "myblob" blob with contents from a local file.
            blockBlob.Properties.ContentType = contentType;
            MemoryStream stream = new MemoryStream(content);
            blockBlob.UploadFromStream(stream);

            var tenantStore = CosmosStoreFactory.CreateForEntity<Tenant>();
            tenant.content = content;
            tenant.CertificatePath = blockBlob.Uri;

            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            var added = await tenantStore.AddAsync(tenant);
            return StatusCode(HttpStatusCode.NoContent); 
        }

和租户 POCO

 public class Tenant
    {
        public string TenantId { get; set; }
        public string TenantUrl { get; set; }
        public Uri CertificatePath { get; set; }
        public string CertificatePassword { get; set; }

        public byte[] content { get; set; }

        public override string ToString()
        {
            return JsonConvert.SerializeObject(this);
        }
    }

堆栈跟踪:

"   at System.Net.Http.Formatting.Parsers.MimeMultipartBodyPartParser.ValidateArguments(HttpContent content, Int64 maxMessageSize, Boolean throwOnError)
   at System.Net.Http.Formatting.Parsers.MimeMultipartBodyPartParser..ctor(HttpContent content, MultipartStreamProvider streamProvider, Int64 maxMessageSize, Int32 maxBodyPartHeaderSize)
   at System.Net.Http.HttpContentMultipartExtensions.<ReadAsMultipartAsync>d__0`1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at TenantManagementWebApi.Controllers.TenantController.<PutTenant>d__2.MoveNext() in C:\Users\levm3\source\repos\TenantManagementWebApi\Controllers\TenantController.cs:line 48
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Threading.Tasks.TaskHelpersExtensions.<CastToObject>d__3`1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Web.Http.Controllers.ApiControllerActionInvoker.<InvokeActionAsyncCore>d__0.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Web.Http.Controllers.ActionFilterResult.<ExecuteAsync>d__2.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Web.Http.Filters.AuthorizationFilterAttribute.<ExecuteAuthorizationFilterAsyncCore>d__2.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Web.Http.Dispatcher.HttpControllerDispatcher.<SendAsync>d__1.MoveNext()"

更新 1:

当我远程附加调试器时,租户参数为空,所以在客户端组件上设置参数时一定有问题。

【问题讨论】:

  • 您能否发布整个堆栈跟踪,这将有助于了解谁在抱怨内容类型。
  • 我发布了堆栈跟踪,我还附加了调试器,我可以看到租户为空,没有收到任何参数。
  • 尝试从 react 组件中的 put 方法调用中删除 content-type 标头。
  • 附加调试器时控制器中的租户参数为空
  • 使用 chrome 找出你从 JS 发送的确切信息,并尝试从邮递员发送相同的信息,看看 asp.net 是否可以读取租户。另外,我看到 js 中的上传方法存储了未保留的 FormData 数据。

标签: c# .net reactjs asp.net-web-api asp.net-web-api2


【解决方案1】:

我猜你的[FromBody]Tenant tenant 是空的,因为你使用了multipart/form-data

在这种情况下,默认模型绑定器无法解析您的模型。

请查看来自 Microsoft documentation 的示例(Reading Form Control Data 部分)。

您需要使用MultipartFormDataStreamProvider 读取您的模型数据。如我所见,您仅将提供程序用于文件内容读取。但您也可以从 FormData 属性中读取您的模型数据(如 TenantId、TenantUrl 等)。

作为第二种(更通用的)方法,您可以创建自己的 IModelBinder 实现。

您可以在这里查看我的 IModelBinder 实现示例(它是来自真实项目的代码):

public class NativeTranslateModelBinder : IModelBinder
{
    public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
    {
        if (bindingContext.ModelType != typeof(NativeTranslateViewModel))
        {
            return false;
        }

        var task = Task.Run(async () =>
        {
            var model = new NativeTranslateViewModel();

            if (!actionContext.Request.Content.IsMimeMultipartContent())
            {
                bindingContext.ModelState.AddModelError(bindingContext.ModelName, "WebRequeest content 'multipart/form-data' is valid");
            }
            else
            {
                var provider = await actionContext.Request.Content.ReadAsMultipartAsync();

                var fileContent = provider.Contents.FirstOrDefault(n => n.Headers.ContentDisposition.Name.Equals("file"));
                if (fileContent == null)
                {
                    bindingContext.ModelState.AddModelError(bindingContext.ModelName, "Section 'file' is missed");
                }

                var modelContent = provider.Contents.FirstOrDefault(n => n.Headers.ContentDisposition.Name.Equals("model"));
                if (modelContent == null)
                {
                    bindingContext.ModelState.AddModelError(bindingContext.ModelName, "Section 'model' is missed");
                }

                if (fileContent != null && modelContent != null)
                {
                    model = JsonConvert.DeserializeObject<NativeTranslateViewModel>(await modelContent.ReadAsStringAsync());
                    model.Text = "<NativeTranslation>";
                    model.FileData = await fileContent.ReadAsByteArrayAsync();
                    model.FileName = fileContent.Headers.ContentDisposition.FileName;
                }
            }

            return model;
        });

        task.Wait();

        bindingContext.Model = task.Result;
        return true;
    }
}

我们可以很容易地使用新的模型绑定器:

[HttpPost]
public HttpResponseMessage UploadNativeDocument([ModelBinder(typeof(NativeTranslateModelBinder))] NativeTranslateViewModel model)

现在我们可以在控制器动作中使用我们的模型和模型属性。

【讨论】:

  • 所以我不需要更改我的反应代码,我的问题只是在 web api 中,我以为我发送的参数错误
  • 我认为您在使用 web api 时遇到了问题。从我的回答中查看文档链接。只需尝试使用MultipartFormDataStreamProvider 读取所有模型属性。
  • 那么每次我有一个带有文件的表单时我都需要一个自定义 ModelBinder 吗?对于标准表格,我不需要模型活页夹吗?
  • 您只能为multipart/form-data 请求实现模型绑定器。如果您只发送表单,您可以发送 JSON 或表单。在这种情况下,您不需要 ModelBinder。发送表单的示例是here
  • @Alexamder,我完全按照你说的做了,但是 actionContext.Request.Content.IsMimeMultipartContent() 是假的,
【解决方案2】:

错误消息告诉您它不喜欢您格式化数据的方式。根据经验,C# MVC 和 API 不喜欢表单数据。我会将您的内容类型更改为“application/json”,然后发送如下数据:

data:{"TenantId": this.state.TenantId, "TenantUrl": this.state.TenantUrl, "TenantPassword": this.state.TenantPassword }

此外,请仔细检查您插入的表是否包含任何非空字段,当您执行插入时,这些字段不为空。

【讨论】:

  • 你确定这会起作用吗?应用程序/json?我也在发送文件上传。
  • 这个答案无效,它不起作用,我尝试更改内容类型,然后错误说它的expectint multiform/data,因为我期待文件上传。正如你所说,我确实改变了数据。
  • 你需要改变你的 React 端来构造一个包含文件上传数据的 json 消息,可能需要进行 base64 编码,这会增加负载大小
【解决方案3】:

WebApi 对 FormMultipart 数据的支持不是很好,基本上需要手动解析:https://stackoverflow.com/a/28462699/356218

我想我记得必须为 multipart/form-data 注册处理程序,以便您的服务器甚至知道它可以尝试来处理该数据类型,但上述以及 https://docs.microsoft.com/en-us/aspnet/web-api/overview/advanced/sending-html-form-data-part-2(非常类似于上面的链接)似乎没有提到这一点

【讨论】:

    猜你喜欢
    • 2021-06-24
    • 1970-01-01
    • 1970-01-01
    • 2016-12-16
    • 2014-08-21
    • 2020-02-06
    • 1970-01-01
    • 2019-09-19
    • 2018-09-22
    相关资源
    最近更新 更多