POST 405 (Method Not Allowed) on custom web api

I have the following react code to call a post endpoint, the webapi does have a post and still I dont know why I get this error

import React, { Component } from 'react';
import { Row, Col, Tabs, Menu, Dropdown, Button, Icon, message, Input } from 'antd';

import Form from '../../components/uielements/form';
import PageHeader from '../../components/utility/pageHeader';
import Box from '../../components/utility/box';
import LayoutWrapper from '../../components/utility/layoutWrapper';
import ContentHolder from '../../components/utility/contentHolder';
import basicStyle from '../../settings/basicStyle';
import IntlMessages from '../../components/utility/intlMessages';
import { Cascader } from 'antd';
import { adalApiFetch } from '../../adalConfig';

import Notification from '../../components/notification';

const FormItem = Form.Item;
 class ExtractPageTemplate extends Component {
    constructor(props) {
        super(props);
        this.state = {options:[], loading:false, selectedOptions:[], description:''};
        this.loadData = this.loadData.bind(this);
        this.enterLoading = this.enterLoading.bind(this);
        this.onChange = this.onChange.bind(this);
        this.handleChangeDescription= this.handleChangeDescription.bind(this);

    }

    handleChangeDescription(event){
        this.setState({description : event.target.value});
    }

    enterLoading (){
        this.setState({ loading: true });

        const options = {
            method: 'post',
            body: JSON.stringify(
              {
                  "SiteCollectionUrl": this.state.selectedOptions[0].value,
                  "PageName": this.state.selectedOptions[1].label, 
                  "Description": this.state.Description
              }),
              headers: {
                  'Content-Type': 'application/json; charset=utf-8'
              } 
          };

          adalApiFetch(fetch, "/Page/CreatePageTemplate", options)
            .then(response =>{
              if(response.status === 204){
                  Notification(
                      'success',
                      'Page tempate created',
                      ''
                      );
               }else{
                  throw "error";
               }
            })
            .catch(error => {
              Notification(
                  'error',
                  'Page template not created',
                  error
                  );
              console.error(error);
          });

    }

    componentDidMount() {
        adalApiFetch(fetch, "/SiteCollection", {})
          .then(response => response.json())
          .then(json => {
            console.log(json);
            const firstLevelOptions = json.map(post => ({
                value: post.Url,
                label: post.Title,
                isLeaf: false    
            }));

            this.setState({
                options: firstLevelOptions
            });
          });
    }

    onChange(value, selectedOptions) {
        console.log("value:", value, "selectedOptions", selectedOptions);
        this.setState({
            selectedOptions: selectedOptions
        });
    }

    loadData(selectedOptions){
        console.log("loaddata", selectedOptions);

        const targetOption = selectedOptions[selectedOptions.length - 1];
        targetOption.loading = true;

        const options = {
            method: 'get',
              headers: {
                      'Content-Type': 'application/json; charset=utf-8'
              }                    
          };

        adalApiFetch(fetch, "/Page/"+encodeURIComponent(targetOption.value.replace("https://","")), options)
          .then(response => response.json())
          .then(json => {
            targetOption.loading = false;
            console.log(json);
            const secondLevelOptions = json.map(page => ({
                value: page.Id,
                label: page.Name,
                isLeaf: true    
            }));
            targetOption.children = secondLevelOptions;
            this.setState({
                options: [...this.state.options],
            });
            }
        );

    }

    render(){

        console.log("uepa" , this.props);
        const { rowStyle, colStyle, gutter } = basicStyle;
        const TabPane = Tabs.TabPane;
        const { getFieldDecorator } = this.props.form;
        const formItemLayout = {
        labelCol: {
            xs: { span: 24 },
            sm: { span: 6 },
        },
        wrapperCol: {
            xs: { span: 24 },
            sm: { span: 14 },
        },
        };
        const tailFormItemLayout = {
        wrapperCol: {
            xs: {
            span: 24,
            offset: 0,
            },
            sm: {
            span: 14,
            offset: 6,
            },
        },
        };

        return (
        <div>
            <LayoutWrapper>
            <PageHeader>{<IntlMessages id="pageTitles.PageAdministration" />}</PageHeader>
            <Row style={rowStyle} gutter={gutter} justify="start">
            <Col md={12} sm={12} xs={24} style={colStyle}>
                <Box
                title={<IntlMessages id="pageTitles.siteCollectionsTitle" />}
                subtitle={<IntlMessages id="pageTitles.siteCollectionsTitle" />}
                >
                <ContentHolder>
                    <Cascader
                                options={this.state.options}
                                loadData={this.loadData}
                                onChange={this.onChange}
                                changeOnSelect
                    />

                <FormItem {...formItemLayout} label="Description " hasFeedback>
                {getFieldDecorator('Description', {
                    rules: [
                        {
                            required: true,
                            message: 'Please input the page template description',
                        }
                    ]
                })(<Input name="Description" id="Description" onChange={this.handleChangeDescription} />)}
                </FormItem>


                    <Button type="primary" loading={this.state.loading} onClick={this.enterLoading}>
                            Click me!
                            </Button>


                </ContentHolder>
                </Box>
            </Col>
            </Row>
        </LayoutWrapper>
        </div>
        );
  }
}

const WrappedExtractPageTemplate = Form.create()(ExtractPageTemplate );
export default WrappedExtractPageTemplate;

and my webapi code

namespace TenantManagementWebApi.Controllers
{
    [Authorize]
    [RoutePrefix("api/Page")]
    public class PageController : ApiController
    {
        [HttpGet]
        [Route("{*sitecollectionUrl}")]
        public async Task<List<TenantManagementWebApi.Entities.Page>> Get(string sitecollectionUrl)
        {
            var tenant = await TenantHelper.GetActiveTenant();
            var siteCollectionStore = CosmosStoreFactory.CreateForEntity<TenantManagementWebApi.Entities.SiteCollection>();
            await siteCollectionStore.RemoveAsync(x => x.Title != string.Empty); // Removes all the entities that match the criteria
            string domainUrl = tenant.TestSiteCollectionUrl;
            string tenantName = domainUrl.Split('.')[0];
            string tenantAdminUrl = tenantName + "-admin.sharepoint.com";
            KeyVaultHelper keyVaultHelper = new KeyVaultHelper();
            await keyVaultHelper.OnGetAsync(tenant.SecretIdentifier);

            using (var context = new OfficeDevPnP.Core.AuthenticationManager().GetSharePointOnlineAuthenticatedContextTenant("https://"+sitecollectionUrl, tenant.Email, keyVaultHelper.SecretValue))
            {

                var pagesLibrary = context.Web.GetListByUrl("SitePages") ?? context.Web.GetListByTitle("SitePages");
                CamlQuery query = CamlQuery.CreateAllItemsQuery(100);
                var pages = pagesLibrary.GetItems(query);
                context.Load(pages);
                context.ExecuteQuery();

                List<TenantManagementWebApi.Entities.Page> listOfPages = new List<TenantManagementWebApi.Entities.Page>();
                foreach(ListItem item in pages)
                {
                    listOfPages.Add(new TenantManagementWebApi.Entities.Page() { Id = item.Id, Name = item.FieldValues["Title"]+".aspx" });
                }
                return listOfPages;

            };
        }


        [HttpPost]
        [Route("api/Page/CreatePageTemplate")]
        public async Task<IHttpActionResult> CreatePageTemplate([FromBody]PageTemplateCreationModel model)
        {
            if (ModelState.IsValid)
            {
                var tenant = await TenantHelper.GetActiveTenant();
                var siteCollectionStore = CosmosStoreFactory.CreateForEntity<TenantManagementWebApi.Entities.SiteCollection>();
                await siteCollectionStore.RemoveAsync(x => x.Title != string.Empty); // Removes all the entities that match the criteria
                string domainUrl = tenant.TestSiteCollectionUrl;
                string tenantName = domainUrl.Split('.')[0];
                string tenantAdminUrl = tenantName + "-admin.sharepoint.com";

                KeyVaultHelper keyVaultHelper = new KeyVaultHelper();
                await keyVaultHelper.OnGetAsync(tenant.SecretIdentifier);
                using (var context = new OfficeDevPnP.Core.AuthenticationManager().GetSharePointOnlineAuthenticatedContextTenant(model.SiteCollectionUrl, tenant.Email, keyVaultHelper.SecretValue))
                {
                    try
                    {
                        var clientsidepageTemplateStore = CosmosStoreFactory.CreateForEntity<OfficeDevPnP.Core.Pages.ClientSidePage>();
                        var page = OfficeDevPnP.Core.Pages.ClientSidePage.Load(context, model.PageName);
                        if (!ModelState.IsValid)
                        {
                            return BadRequest(ModelState);
                        }

                        var added = await clientsidepageTemplateStore.AddAsync(page);
                        return StatusCode(HttpStatusCode.NoContent);


                        //PageTemplate pageTemplate = new PageTemplate();
                        //pageTemplate.Name = model.Name;
                        // var page = OfficeDevPnP.Core.Pages.ClientSidePage.Load(context, "Home.aspx");
                        //int sectionOrder = 0;
                        //foreach (var section in page.Sections)
                        //{
                        //    pageTemplate.Sections.Add(section);

                        //    foreach (var column in section.Columns)
                        //    {
                        //        foreach(var webpart in column.Controls)
                        //        {

                        //        }
                        //    }

                        //    sectionOrder++;
                        //}
                    }
                    catch (System.Exception ex)
                    {
                        throw ex;
                    }
                }
            }
            return BadRequest(ModelState);
        }
    }

}

The get endpoint works perfect

1 answer

  • answered 2019-02-10 15:34 Alexander

    If you want to access CreatePageTemplate action you have to request url of following format: RoutePrefix + Route. In your case resulting url should be api/Page + api/Page/CreatePageTemplate = api/Page/api/Page/CreatePageTemplate. But apparently this code

    adalApiFetch(fetch, "/Page/CreatePageTemplate", options)
    

    requesting api/Page/CreatePageTemplate. So just update it to the following

    adalApiFetch(fetch, "/Page/api/Page/CreatePageTemplate", options)
    

    But if you intention is using original url you should only update your action

    [HttpPost]
    [Route("CreatePageTemplate")] //remove api/Page as it is already in RoutePrefix
    public async Task<IHttpActionResult> CreatePageTemplate([FromBody]PageTemplateCreationModel model)