Receiving Error 405 when POST or PUT from Angular 7 to ASP.NET WebAPI

Simon
Simon used Ask the Experts™
on
Hi,

I am studying Angular 7 with ASP.NET WebAPI.
I host ASP.NET Web API REST Service in IIS 10.
In Angular 7, I can successfully GET from ASP.NET WebAPI, but I receive Error 405 when POST or PUT to ASP.NET WebAPI.
(I can successfully GET / POST / PUT to the same ASP.NET WebAPI using Postman.)

I cannot figure it out, thanks for your help.

While I click "save" button, it display error 405 (Method Not Allowed)
Client code:
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
 
import { Observable, of } from 'rxjs';
import { catchError, map, tap } from 'rxjs/operators';

import { MDLSyst } from '../_Model/MDLSyst';
import { MessageService } from '../_Service/message.service';

const httpOptions = {
  headers: new HttpHeaders({ 'Content-Type': 'application/json' })
};

@Injectable({
  providedIn: 'root'
})
export class SystService {

  private systUrl = 'http://localhost/fars-api/api/syst';  // URL to web api

  constructor(private http: HttpClient,
    private messageService: MessageService) { }
 
  getSystList(): Observable<MDLSyst[]> {
    return this.http.get<MDLSyst[]>(this.systUrl)
      .pipe(
        tap(_ => this.log('fetched SystList')),
        catchError(this.handleError('getSystList', []))
      );
    }

  /** GET syst by id. Will 404 if id not found */
  getSyst(id: number): Observable<MDLSyst> {
    const url = `${this.systUrl}/${id}`;
    return this.http.get<MDLSyst>(url).pipe(
      tap(_ => this.log(`fetched syst id=${id}`)),
      catchError(this.handleError<MDLSyst>(`getSyst id=${id}`))
    );
  }

  /** POST: add a new syst to the server */
  addSyst(syst: MDLSyst): Observable<MDLSyst> {
    return this.http.post<MDLSyst>(this.systUrl, syst, httpOptions).pipe(
      tap((syst: MDLSyst) => this.log(`added syst w/ id=${syst.Id}`)),
      catchError(this.handleError<MDLSyst>('addSyst'))
    );
  }

  /** PUT: update the syst on the server */
  updateSyst(syst: MDLSyst): Observable<any> {
    const id = syst.Id;
    const url = `${this.systUrl}/${id}`;

    return this.http.put(url, syst, httpOptions).pipe(
      tap(_ => this.log(`updated syst id=${syst.Id}`)),
      catchError(this.handleError<any>('updateSyst'))
    );
  }

  /**
   * Handle Http operation that failed.
   * Let the app continue.
   * @param operation - name of the operation that failed
   * @param result - optional value to return as the observable result
   */
  private handleError<T> (operation = 'operation', result?: T) {
    return (error: any): Observable<T> => {
 
      // TODO: send the error to remote logging infrastructure
      //console.error(error); // log to console instead
 
      // TODO: better job of transforming error for user consumption
      this.log(`${operation} failed: ${error.message}`);
 
      // Let the app keep running by returning an empty result.
      return of(result as T);
    };
  }
 
  /** Log a SystService message with the MessageService */
  private log(message: string) {
    this.messageService.add(`SystService: ${message}`);
  }
}

Open in new window


WebAPI code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;

using System.Data;
using System.Data.SqlClient;

using FARS.MDL;

namespace FarsApi.Controller
{
    public class SystController : ApiController
    {
        // GET: api/Syst
        public IHttpActionResult Get()
        {
            ICollection<MDLSyst> SystList = new List<MDLSyst>();

            // get Syst from database
            using (SqlDataReader rdr = SQLHelper.ExecuteReader(SQLHelper.CONN_STRING, CommandType.StoredProcedure, "SS_SYST_L", null))
            {
                while (rdr.Read())
                {
                    MDLSyst itmDept = new MDLSyst { Id = rdr.GetInt16(0), Status = rdr.GetString(1), Type = rdr.GetString(2), Brief = rdr.GetString(3), Name = rdr.GetString(4), Frequency = rdr.GetString(5), ContactName = rdr.GetString(6), ContactEmail = rdr.GetString(7), ContactTel = rdr.GetString(8), DisplayOrder = rdr.GetInt16(9), UpdateTime = rdr.GetDateTime(10), UpdateUser = rdr.GetString(11) };
                    SystList.Add(itmDept);
                }
            }

            //
            if (SystList.Count == 0)
            {
                return NotFound();
            }

            return Ok(SystList);
        }

        // GET: api/Syst/5
        public IHttpActionResult Get(Int16 Id)
        {
            // Set up a return value
            MDLSyst Syst = null;

            // Create a parameter
            SqlParameter parm = new SqlParameter("@ID", SqlDbType.SmallInt);

            // Bind the parameter
            parm.Value = Id;

            // Execute the query
            using (SqlDataReader rdr = SQLHelper.ExecuteReader(SQLHelper.CONN_STRING, CommandType.StoredProcedure, "SS_SYST_S", parm))
            {
                while (rdr.Read())
                {
                    Syst = new MDLSyst { Id = rdr.GetInt16(0), Status = rdr.GetString(1), Type = rdr.GetString(2), Brief = rdr.GetString(3), Name = rdr.GetString(4), Frequency = rdr.GetString(5), ContactName = rdr.GetString(6), ContactEmail = rdr.GetString(7), ContactTel = rdr.GetString(8), DisplayOrder = rdr.GetInt16(9), UpdateTime = rdr.GetDateTime(10), UpdateUser = rdr.GetString(11) };
                }
            }

            if (Syst == null)
            {
                return NotFound();
            }

            return Ok(Syst);
        }

        // POST: api/Syst
        public IHttpActionResult Post(MDLSyst Syst)
        {
            if (!ModelState.IsValid)
                return BadRequest("Not a valid model");

            // Create the parameters
            SqlParameter[] parms = new SqlParameter[] {
                //new SqlParameter("@ID", SqlDbType.SmallInt),
                new SqlParameter("@STATUS", SqlDbType.Char, 1),
                new SqlParameter("@TYPE", SqlDbType.VarChar, 10),
                new SqlParameter("@BRIEF", SqlDbType.NVarChar, 20),
                new SqlParameter("@NAME", SqlDbType.NVarChar, 50),
                new SqlParameter("@FREQUENCY", SqlDbType.VarChar, 20),
                new SqlParameter("@CONTACT_NAME", SqlDbType.NVarChar, 50),
                new SqlParameter("@CONTACT_EMAIL", SqlDbType.VarChar, 255),
                new SqlParameter("@CONTACT_TEL", SqlDbType.VarChar, 20),
                new SqlParameter("@DISPLAY_ORDER", SqlDbType.SmallInt),
                new SqlParameter("@UPDATE_USER", SqlDbType.VarChar, 20)
            };

            // Bind the parameters
            //parms[0].Value = Syst.Id
            parms[0].Value = Syst.Status;
            parms[1].Value = Syst.Type;
            parms[2].Value = Syst.Brief;
            parms[3].Value = Syst.Name;
            parms[4].Value = Syst.Frequency;
            parms[5].Value = Syst.ContactName;
            parms[6].Value = Syst.ContactEmail;
            parms[7].Value = Syst.ContactTel;
            parms[8].Value = Syst.DisplayOrder;
            parms[9].Value = Syst.UpdateUser;

            // Execute the query
            SQLHelper.ExecuteNonQuery(SQLHelper.CONN_STRING, CommandType.StoredProcedure, "SS_SYST_I", parms);

            return Ok();
        }

        // PUT: api/Syst/5
        public IHttpActionResult Put(Int16 Id, MDLSyst Syst)
        {
            if (!ModelState.IsValid)
                return BadRequest("Not a valid model");

            // Create the parameters
            SqlParameter[] parms = new SqlParameter[] {
                new SqlParameter("@ID", SqlDbType.SmallInt),
                new SqlParameter("@STATUS", SqlDbType.Char, 1),
                new SqlParameter("@TYPE", SqlDbType.VarChar, 10),
                new SqlParameter("@BRIEF", SqlDbType.NVarChar, 20),
                new SqlParameter("@NAME", SqlDbType.NVarChar, 50),
                new SqlParameter("@FREQUENCY", SqlDbType.VarChar, 20),
                new SqlParameter("@CONTACT_NAME", SqlDbType.NVarChar, 50),
                new SqlParameter("@CONTACT_EMAIL", SqlDbType.VarChar, 255),
                new SqlParameter("@CONTACT_TEL", SqlDbType.VarChar, 20),
                new SqlParameter("@DISPLAY_ORDER", SqlDbType.SmallInt),
                new SqlParameter("@UPDATE_USER", SqlDbType.VarChar, 20)
            };

            // Bind the parameters
            parms[0].Value = Syst.Id;  // Id
            parms[1].Value = Syst.Status;
            parms[2].Value = Syst.Type;
            parms[3].Value = Syst.Brief;
            parms[4].Value = Syst.Name;
            parms[5].Value = Syst.Frequency;
            parms[6].Value = Syst.ContactName;
            parms[7].Value = Syst.ContactEmail;
            parms[8].Value = Syst.ContactTel;
            parms[9].Value = Syst.DisplayOrder;
            parms[10].Value = Syst.UpdateUser;

            // Execute the query
            SQLHelper.ExecuteNonQuery(SQLHelper.CONN_STRING, CommandType.StoredProcedure, "SS_SYST_U", parms);

            return Ok();
        }
    }
}

Open in new window


WebAPI Web.config:
<?xml version="1.0" encoding="utf-8"?>

<configuration>

  <connectionStrings>
    <add name="FARS_Conn" connectionString="Data Source=localhost; Initial Catalog=FARS; user id=FARS_User; password=xxx;" />
  </connectionStrings>

  <system.web>
    <compilation debug="true" targetFramework="4.6.1" />
    <httpRuntime targetFramework="4.6.1" />
  </system.web>
  <system.codedom>
    <compilers>
      <compiler language="c#;cs;csharp" extension=".cs" type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.CSharpCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" warningLevel="4" compilerOptions="/langversion:default /nowarn:1659;1699;1701" />
      <compiler language="vb;vbs;visualbasic;vbscript" extension=".vb" type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.VBCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" warningLevel="4" compilerOptions="/langversion:default /nowarn:41008 /define:_MYTYPE=\&quot;Web\&quot; /optionInfer+" />
    </compilers>
  </system.codedom>
  
  <system.webServer>

    <httpProtocol>
      <customHeaders>
        <add name="Access-Control-Allow-Origin" value="*" />
      </customHeaders>
    </httpProtocol>

    <handlers>
      <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
      <remove name="OPTIONSVerbHandler" />
      <remove name="TRACEVerbHandler" />
      <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
    </handlers>
  </system.webServer>
</configuration>

Open in new window

Comment
Watch Question

Do more with

Expert Office
EXPERT OFFICE® is a registered trademark of EXPERTS EXCHANGE®
David FavorFractional CTO
Distinguished Expert 2018

Commented:
You're going to have a difficult time getting this to work locally + then you'll have to move it to some public site + start your debugging all over again.

1) Setup all your dev code on the public IPs where your API code will be running.

Tip: Use OVH for cheap + fast hardware.

Tip: Run your API services in an LXD container, so you can easily clone your container to multiple other machines to scale up your API throughput.

2) Go ahead + wrap your API code in HTTPS now.

3) Research CORS + setup your CORS ACLs now.

https://spring.io/understanding/CORS provides a good starting point + you'll likely require a good bit of research + thought + design to create an exact setup which will work for your situation.
David FavorFractional CTO
Distinguished Expert 2018

Commented:
Generally 405s mean...

1) You've requested an existing/valid resource, else you'd have gotten a 404.

2) How you accessed the resource is invalid.

3) Best way to debug this is using curl as your client + tracking exact logged errors on your API side.

Note: This presupposes you have highly granular logging running for your API side, so every minor error/warning is logged.

Tip: Writing APIs in some languages is difficult + time consuming (ASP) while other languages (PHP) you'll be able to write API code in a few minutes.

You might consider starting with PHP to build a prototype + then move to ASP, if you really must.
Commented:
ref. Setup a proxy for API calls for your Angular CLI appAngular CLI proxy configuration

Do more with

Expert Office
Submit tech questions to Ask the Experts™ at any time to receive solutions, advice, and new ideas from leading industry professionals.

Start 7-Day Free Trial