Link to home
Avatar of Simon
Simon

asked on

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.

User generated image
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

Avatar of David Favor
David Favor
Flag of United States of America image

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.
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.
ASKER CERTIFIED SOLUTION
Avatar of Simon
Simon

Blurred text
THIS SOLUTION IS ONLY AVAILABLE TO MEMBERS.
View this solution by signing up for a free trial.
Members can start a 7-Day free trial and enjoy unlimited access to the platform.
See Pricing Options
Start Free Trial