0

I am trying to call a webapi from react and I get this error

responseJson = {Message: "An error has occurred.", ExceptionMessage: "Can't bind multiple parameters ('tenant' and 'certificateFile') to the request's content.", ExceptionType: "System.InvalidOperationException", StackTrace: " at System.Web.Http.Controllers.HttpActionBindin…tpControllerDispatcher.d__1.MoveNext()"}

my react code is like this:

    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("TenanrUrl", 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 web api is this:


public class TenantController : ApiController
    {
        public async Task<List<Tenant>> GetTenants()
        {
            var tenantStore = CosmosStoreFactory.CreateForEntity<Tenant>();
            return await tenantStore.Query().Where(x => x.TenantId != null ).ToListAsync();

        }

        public async Task<IHttpActionResult> GetTenant(string tenantId)
        {
            var tenantStore = CosmosStoreFactory.CreateForEntity<Tenant>();
            var tenant = await tenantStore.Query().FirstOrDefaultAsync(x => x.TenantId == tenantId);
            if (tenant == null)
            {
                return NotFound();
            }
            return Ok(tenant);
        }

        public async Task<IHttpActionResult>  PutTenant([FromBody]Tenant tenant, HttpPostedFile certificateFile)
        {
            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 = certificateFile.ContentType;
            blockBlob.UploadFromStream(certificateFile.InputStream);

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

            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }
            //if (id != tenant.TenantId)
            //{
            //    return BadRequest();
            //}

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

        public async Task<IHttpActionResult> PostTenant(string id, Tenant tenant)
        {
            var tenantStore = CosmosStoreFactory.CreateForEntity<Tenant>();

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

            var result = await tenantStore.UpdateAsync(tenant);
            return Ok(result);
        }

        public async Task<IHttpActionResult> DeleteTenant(string tenantId)
        {
            var tenantStore = CosmosStoreFactory.CreateForEntity<Tenant>();
            await tenantStore.RemoveByIdAsync(tenantId);// Removes an entity with the specified ID
            return Ok(tenantId);
        }
    }

My tenant object is this:

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

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

and the controller

public class TenantController : ApiController
    {
        public async Task<List<Tenant>> GetTenants()
        {
            var tenantStore = CosmosStoreFactory.CreateForEntity<Tenant>();
            return await tenantStore.Query().Where(x => x.TenantId != null ).ToListAsync();

        }

        public async Task<IHttpActionResult> GetTenant(string tenantId)
        {
            var tenantStore = CosmosStoreFactory.CreateForEntity<Tenant>();
            var tenant = await tenantStore.Query().FirstOrDefaultAsync(x => x.TenantId == tenantId);
            if (tenant == null)
            {
                return NotFound();
            }
            return Ok(tenant);
        }

        public async Task<IHttpActionResult>  PutTenant([FromBody]Tenant tenant, HttpPostedFile certificateFile)
        {
            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 = certificateFile.ContentType;
            blockBlob.UploadFromStream(certificateFile.InputStream);

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

            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }
            //if (id != tenant.TenantId)
            //{
            //    return BadRequest();
            //}

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

        public async Task<IHttpActionResult> PostTenant(string id, Tenant tenant)
        {
            var tenantStore = CosmosStoreFactory.CreateForEntity<Tenant>();

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

            var result = await tenantStore.UpdateAsync(tenant);
            return Ok(result);
        }

        public async Task<IHttpActionResult> DeleteTenant(string tenantId)
        {
            var tenantStore = CosmosStoreFactory.CreateForEntity<Tenant>();
            await tenantStore.RemoveByIdAsync(tenantId);// Removes an entity with the specified ID
            return Ok(tenantId);
        }
    }
sideshowbarker
  • 81,827
  • 26
  • 193
  • 197
Luis Valencia
  • 32,619
  • 93
  • 286
  • 506

1 Answers1

2

Problem is exactly what is specified in the exception message. You are binding multiple parameters to action body, you cannot do that with Web API. Please read Parameter binding in Web API.

Use following code to read uploaded file.

    using System.Net.Http;
    using System.IO;
    public async Task<IHttpActionResult>  PutTenant([FromBody]Tenant tenant)
    {
            var provider = new MultipartMemoryStreamProvider();
            var contentType = "";
            Stream content = new MemoryStream();
            await base.Request.Content.ReadAsMultipartAsync(provider);
            if(provider.Contents.Count > 0)
            {
                contentType = provider.Contents[0].Headers.ContentType.MediaType;
                content = await provider.Contents[0].ReadAsStreamAsync();
            }

        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;
        blockBlob.UploadFromStream(content);

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

        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }
        //if (id != tenant.TenantId)
        //{
        //    return BadRequest();
        //}

        var added = await tenantStore.AddAsync(tenant);
        return StatusCode(HttpStatusCode.NoContent); 
    }
Dipen Shah
  • 25,562
  • 1
  • 32
  • 58
  • HttpPostedFIle is a file upload. – Luis Valencia Jul 16 '18 at 17:35
  • @LuisValencia Then you can use second approach mentioned. – Dipen Shah Jul 16 '18 at 17:48
  • I think this solution is not complete: 1. If I change the signature as you said, how do I reference the file upload in these lines: blockBlob.Properties.ContentType = certificateFile.ContentType; blockBlob.UploadFromStream(certificateFile.InputStream); – Luis Valencia Jul 16 '18 at 18:16
  • 2nd. Do I need to change the Tenant POCO to have a fileupload property or something like that? – Luis Valencia Jul 16 '18 at 18:17
  • @LuisValencia I changed code so that it'll use new variables to set data. For 2nd question, it depends on your requirements. If you want to access file properties all the time, then to me it makes sense to include them as a part of Tenant POCO. – Dipen Shah Jul 16 '18 at 18:22
  • cannot convert from 'byte[]' to 'System.IO.Stream – Luis Valencia Jul 16 '18 at 18:57
  • @LuisValencia Not sure about that, compiles fine for me. – Dipen Shah Jul 16 '18 at 19:00
  • I created a MemoryStream with the content, then assigned that stream to that property and it worked, if you have time please check this one: https://stackoverflow.com/questions/51368982/an-error-has-occurred-exceptionmessage-invalid-httpcontent-instance-provi – Luis Valencia Jul 16 '18 at 19:36