【问题标题】:415 (Unsupported Media Type) with REST Post request带有 REST Post 请求的 415(不支持的媒体类型)
【发布时间】:2019-01-16 05:03:55
【问题描述】:

我有一个反应组件,当一个复选框被按下时,它会调用一个rest api,使用单个参数发布请求。

我在 webapi 中设置了一个断点,但它从未命中,但我仍然在组件上得到一个 415 不支持的媒体类型

react js component (see onchange event)

import React, { Component } from 'react';
import {  Table, Radio} from 'antd';
import { adalApiFetch } from '../../adalConfig';
import Notification from '../../components/notification';

class ListTenants extends Component {

    constructor(props) {
        super(props);
        this.state = {
            data: []
        };
    }



    fetchData = () => {
        adalApiFetch(fetch, "/Tenant", {})
          .then(response => response.json())
          .then(responseJson => {
            if (!this.isCancelled) {
                const results= responseJson.map(row => ({
                    key: row.ClientId,
                    ClientId: row.ClientId,
                    ClientSecret: row.ClientSecret,
                    Id: row.Id,
                    SiteCollectionTestUrl: row.SiteCollectionTestUrl,
                    TenantDomainUrl: row.TenantDomainUrl
                  }))
              this.setState({ data: results });
            }
          })
          .catch(error => {
            console.error(error);
          });
      };


    componentDidMount(){
        this.fetchData();
    }

    render() {
        const columns = [
                {
                    title: 'Client Id',
                    dataIndex: 'ClientId',
                    key: 'ClientId'
                }, 
                {
                    title: 'Site Collection TestUrl',
                    dataIndex: 'SiteCollectionTestUrl',
                    key: 'SiteCollectionTestUrl',
                },
                {
                    title: 'Tenant DomainUrl',
                    dataIndex: 'TenantDomainUrl',
                    key: 'TenantDomainUrl',
                }
        ];

        // rowSelection object indicates the need for row selection
        const rowSelection = {
            onChange: (selectedRowKeys, selectedRows) => {
                if(selectedRows[0].key != undefined){
                    console.log(selectedRows[0].key);


                    const options = { 
                        method: 'post', 
                        body: JSON.stringify({ clientid : selectedRows[0].key.toString() }) ,
                        config: {
                            headers: {
                              'Content-Type': 'application/json'
                            }
                          }
                    };

                    adalApiFetch(fetch, "/Tenant/SetTenantActive", options)
                        .then(response =>{
                        if(response.status === 200){
                            Notification(
                                'success',
                                'Tenant set to active',
                                ''
                                );
                        }else{
                            throw "error";
                        }
                        })
                        .catch(error => {
                        Notification(
                            'error',
                            'Tenant not activated',
                            error
                            );
                        console.error(error);
                    });
                }
            },
            getCheckboxProps: record => ({
                type: Radio
            }),
        };

        return (
            <Table rowSelection={rowSelection} columns={columns} dataSource={this.state.data} />
        );
    }
}

export default ListTenants;

和 webapi 方法

[HttpPost]
        [Route("api/Tenant/SetTenantActive")]
        public async Task<IHttpActionResult> SetTenantActive([FromBody]string clientid)
        {
            var tenantStore = CosmosStoreFactory.CreateForEntity<Tenant>();
            var allTenants = await tenantStore.Query().Where(x => x.TenantDomainUrl != null).ToListAsync();
            foreach(Tenant ten  in allTenants)
            {
                ten.Active = false;
                await tenantStore.UpdateAsync(ten);
            }

            var tenant = await tenantStore.Query().FirstOrDefaultAsync(x => x.clientid == clientid);
            if (tenant == null)
            {
                return NotFound();
            }

            tenant.Active = true;
            var result = await tenantStore.UpdateAsync(tenant);

            return Ok(result);
        }

【问题讨论】:

  • 按F12,进入网络日志,做请求,检查打开请求在请求中查找Content-Type参数,然后在底部查看payload是什么
  • 打开请求标头,这是指定媒体类型的地方
  • 您的 Content-Type 是 text/plain; charset=utf-8,这在技术上是正确的,但服务器可能期待 application/json;字符集=utf-8。我看到 adalFetch 没有用config.contentType 设置内容类型

标签: javascript jquery reactjs asp.net-web-api asp.net-web-api2


【解决方案1】:

我注意到的几件事。

  1. 您正在尝试使用 JSON 正文执行 POST 请求。在客户端,您的请求看起来不错。

据我了解,POST 正文是

{ clientid: 'some-client-id' }
  1. 有趣的是在您收到它的 Web API 中

public async Task&lt;IHttpActionResult&gt; SetTenantActive([FromBody]string clientid)

这可能是罪魁祸首。您的 API 需要一个字符串作为 POST 正文,其中它是一个 json 对象。您是否尝试将类型更改为dynamicJObject

所以,本质上,

public async Task<IHttpActionResult> SetTenantActive([FromBody]dynamic clientRequest)

public async Task<IHttpActionResult> SetTenantActive([FromBody]JObject clientRequest)

或者,

如果您想继续按原样使用您的 API,那么您只需将您从客户端发出的请求更改为 ’some-client-id’ 而不是 { clientid: 'some-client-id' }

【讨论】:

  • 你是对的,完全错过了。或者,可以将正文设置为'some-client-id'(包括引号并保持application/json 作为内容类型),这将被反序列化为一个简单的字符串,因此@Luis Valencia 可以使用他的post 端点。但这仍然不能解释生成的状态码。
【解决方案2】:

改变

const options = { 
    method: 'post', 
    body: JSON.stringify({ clientid : selectedRows[0].key.toString() }) ,
    config: {
        headers: {
            'Content-Type': 'application/json'
        }
    }
};

const options = { 
    method: 'post', 
    body: JSON.stringify({ clientid : selectedRows[0].key.toString() }) ,
    headers: {
        'Content-Type': 'application/json; charset=utf-8'
    }
};

【讨论】:

    【解决方案3】:

    检查您的服务器设置。默认情况下它应该支持json,但最好验证它。也尝试清除你的api代码中的Accept标头并设置为*,这意味着所有类型。

    另外检查adalApiFetch方法。它发送什么标头? Content-Type的格式是否使用&设置正确?

    【讨论】:

      【解决方案4】:

      对于像这样的简单 RESTFul 调用,您可以遵循建议命名约定以及 HTTP 动词,这样可以更好地阐明意图并简化调用本身。对于这样一个简单的调用,无需过度复杂化 API 模型。

      类似

      [HttpPut] // Or HttpPost. PUT is usually used to update the resourcce
      [Route("api/Tenant/{clientid}/Active")]
      public async Task<IHttpActionResult> SetTenantActive(string clientid) {
          var tenantStore = CosmosStoreFactory.CreateForEntity<Tenant>();
          var allTenants = await tenantStore.Query().Where(x => x.TenantDomainUrl != null).ToListAsync();
          var updates = new List<Task>();
          foreach(Tenant ten  in allTenants) {
              ten.Active = false;
              updates.Add(tenantStore.UpdateAsync(ten));
          }
      
          await Task.WhenAll(updates);
      
          var tenant = await tenantStore.Query().FirstOrDefaultAsync(x => x.clientid == clientid);
          if (tenant == null)
          {
              return NotFound();
          }
      
          tenant.Active = true;
          var result = await tenantStore.UpdateAsync(tenant);
      
          return Ok(result);
      }
      

      在客户端

      const rowSelection = {
          onChange: (selectedRowKeys, selectedRows) => {
              if(selectedRows[0].key != undefined){
                  var clientid = selectedRows[0].key;
                  console.log(clientid);
      
                  var url = "/Tenant/" + clientid + "/Active"
      
                  const options = { 
                      method: 'put'
                  };
      
                  adalApiFetch(fetch, url, options)
                      .then(response => {
                      if(response.status === 200){
                          Notification(
                              'success',
                              'Tenant set to active',
                              ''
                              );
                      }else{
                          throw "error";
                      }
                      })
                      .catch(error => {
                      Notification(
                          'error',
                          'Tenant not activated',
                          error
                          );
                      console.error(error);
                  });
              }
          },
          getCheckboxProps: record => ({
              type: Radio
          }),
      };
      

      【讨论】:

        【解决方案5】:

        你为什么使用post?从 'REST`y 的角度来看,它用于创建实体(在您的情况下为租户)。

        预期的简单请求可以通过GET 解决,clientid 作为路由的一部分:

        [HttpGet]
        [Route("api/Tenant/SetTenantActive/{clientid}")]
        public async Task<IHttpActionResult> SetTenantActive(string clientid)
        {
           // ...
        }
        

        【讨论】:

        • 我以为get只是读操作,settenantactive是对DB的更新操作
        • 您的端点和意图不适合标准的“REST”示例(主要是 CRUD 的东西)。您正在更新现有租户,因此您可以使用 PUTPATCH(其中可能会导致同样的问题)。但是由于您没有要通过主体传递的具有预期新属性值的租户对象,因此您最好使用GET
        猜你喜欢
        • 2015-06-17
        • 2016-11-21
        • 2015-06-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-09-03
        • 2017-03-06
        相关资源
        最近更新 更多