An error has occurred.“, ExceptionMessage: ”Invalid 'HttpContent' instance provided

喜夏-厌秋 提交于 2019-12-24 03:53:16

问题


Trying to add a file to an http rest call I get this error:

responseJson = {Message: "An error has occurred.", ExceptionMessage: "Invalid 'HttpContent' instance provided. It does n…rting with 'multipart/'. ↵Parameter name: content", ExceptionType: "System.ArgumentException", StackTrace: " at System.Net.Http.Formatting.Parsers.MimeMulti…tpControllerDispatcher.d__1.MoveNext()"}

my react component:

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>
    );
  }
}

My tenant controller Put method

[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); 
        }

and the tenant 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);
        }
    }

stacktrace:

"   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()"

Update 1:

When I attach the debugger remotely the tenant parameter is null, so something must be wrong when setting the parameters on the client component.


回答1:


I guess your [FromBody]Tenant tenant is null because you use multipart/form-data.

The default model binder can not resolve your model in this case.

Please review example from Microsoft documentation (section Reading Form Control Data).

You need to read your model data using MultipartFormDataStreamProvider. As I can see you use the provider only for file content reading. But also you can read your model data (like TenantId, TenantUrl, etc) from FormData property.

As second (and more general) approach you can create your own IModelBinder implementation.

Here you can review example of my implementation of IModelBinder (It is code from real project):

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;
    }
}

We can use new model binder very easy:

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

Now we can use our model and model properties inside controller action.




回答2:


The error message is telling you it doesn't like the way you formatted the data. From experience, C# MVC and API does not like form data. I would change your content type to 'application/json', then send the data like below:

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

Also, double check that if the table you're inserting into contains any not null fields, those fields are not null when you perform the insert.




回答3:


WebApi doesn't support FormMultipart data very well, you need to basically parse it manually: https://stackoverflow.com/a/28462699/356218

I thought I recalled having to register handlers for multipart/form-data so that your server even knows it can try to handle that datatype, but the above as well as https://docs.microsoft.com/en-us/aspnet/web-api/overview/advanced/sending-html-form-data-part-2 (pretty similar to the above link) doesn't seem to mention that



来源:https://stackoverflow.com/questions/51368982/an-error-has-occurred-exceptionmessage-invalid-httpcontent-instance-provi

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!