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

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

SimonAsked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

David FavorLinux/LXD/WordPress/Hosting SavantCommented:
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 FavorLinux/LXD/WordPress/Hosting SavantCommented:
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.
SimonAuthor Commented:
ref. Setup a proxy for API calls for your Angular CLI appAngular CLI proxy configuration

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
webapi

From novice to tech pro — start learning today.