Question Background:
I have a basic WebApi project hosted as a WebApp in Azure.
The Issue:
The problem I have is If I access any method other than a 'GET' type then I'm receiving the following error in my JSON response:
The requested resource does not support http method 'GET'
The Code:
The following code is how the project currently is.
RouteConfig.cs class:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Home",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
The ValuesController controller class:
public class ValuesController : ApiController
{
private List<CompanyData> _company;
public ValuesController()
{
_company = new List<CompanyData>
{
new CompanyData
{
CompanyName = "SmallTech.Ltd",
CompanyOwner = "John Smith",
CompanyIndustry = "Electronic Components",
EmployeeNo = "3"
}
};
}
public List<CompanyData> GetCompanyData()
{
return _company;
}
//GET api/values
public IEnumerable<string> Get()
{
return new string[] { "Test GET Method"};
}
// GET api/values/5
public string Get(int id)
{
return "value";
}
// POST api/values
public void Post(string value)
{
string test = value;
}
// PUT api/values/5
public void Put(int id, [FromBody]string value)
{
}
// DELETE api/values/5
[HttpDelete]
public void Delete(int id)
{
}
An example of calling the above Delete method when the error occurs is:
http://testwebapisite.azurewebsites.net/api/values/Delete/5
I have read other people having the same issue and using the HTTP attributes from the System.Net.MVC. I can confirm I'm not using this and am using `System.Net.Http.HttpPostAttribute.
Any help working out why I'm receiving the GET error message would be great.
You are trying to access an action which clearly specifies delete as its verb via a GET request.
By default the browser will do a GET request if you paste a url so thats pretty much easy to test but for the other verbs you'll have to use an actual rest/http client to specify the verb. You can use Postman or Rest Console if you use chrome to dev/test
In addition to those tools, you might want to have fiddler installed .. it will help you track all http activity (both sent/received) you'll know exactly what you are sending and receiving from the wire
You could also do this from code if you want using HttpClient.
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://testwebapisite.azurewebsites.net/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = await client.DeleteAsync("api/values/5");
}
You haven't shown the code that you are using to invoke the API, but I suspect you are not using the DELETE HTTP verb. The resource you are accessing has URI or http://testwebapisite.azurewebsites.net/api/values/5 - note the action name is not specified. Rather, as the comment of your method suggests, you should be using the DELETE HTTP verb. Example:
using (var client = new HttpClient())
await client.DeleteAsync("http://testwebapisite.azurewebsites.net/api/values/5");
Related
I'm trying to create a API broker program so that a frontend can call a api with any endpoint given, in the broker I decide which URL to be executed. I have the following problem:
In my front-end I am calling my API broker with the following url:
http://localhost:54857/getClients
When my API Broker receives this request it instantly returns a error, before this error occurs I want to redirect this to a controller action which does not match the name getClients.
Front-end code which requests the call to my API Broker:
[HttpGet]
public async Task<ActionResult> getCall()
{
string url = "http://localhost:54857/";
string operation = "getClients";
using (var client = new HttpClient())
{
//get logged in userID
HttpContext context = System.Web.HttpContext.Current;
string sessionID = context.Session["userID"].ToString();
//Create request and add headers
client.BaseAddress = new Uri(url);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
//Custom header
client.DefaultRequestHeaders.Add("loggedInUser", sessionID);
//Response
HttpResponseMessage response = await client.GetAsync(operation);
if (response.IsSuccessStatusCode)
{
string jsondata = await response.Content.ReadAsStringAsync();
return Content(jsondata, "application/json");
}
return Json(1, JsonRequestBehavior.AllowGet);
}
}
My routing in my API Broker:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller=Redirect}/{action=RedirectApi}/{id}");
}
API Broker controller:
[System.Web.Http.AcceptVerbs("GET")]
[System.Web.Http.HttpGet]
public Customer RedirectApi()
{
Customer t = dbProducts.Customers
.Where(h => h.customerID == 1)
.FirstOrDefault();
return t;
}
I also have a filter that works when the api method getClients is present:
public override void OnActionExecuting(HttpActionContext actionContext)
{
//Checks if header is method Get and has attribute
if ((actionContext.Request.Method.Method == "GET") && (actionContext.Request.Headers.GetValues("loggedinUser").First() != null))
{
}
base.OnActionExecuting(actionContext);
}
In the current situation(code above) I'm receiving a error and the code in my API broker is not even getting executed. What I'm trying to achieve is that I redirect any given request received in my API Broker to my API Broker Controller which executed the method RedirectApi().
Hope somebody can help!
Thanks in advance!
You could add this code after the other mapping code:
routes.MapRoute(
"404-PageNotFound",
"{*url}",
new { controller = "Redirect", action = "RedirectApi" }
);
I have a .Net mobile service API that accepts an Id and a bool. If I call the API from Postman with the correct parameters and headers, it works fine. When I call it from my AngularJs app, I get a 404 error and I cannot figure out why.
My backend API method looks like this:
[HttpPost]
public async Task<IHttpActionResult> SetHoldStatus(string id, bool isHeld)
=> Ok(new
{
Transfer = await _workItemRepository.SetTransferHoldStatus(id, isHeld)
});
My AngularJs controller looks like this:
(function () {
angular
.module("pinnacleWarehouser")
.controller("TransferListCtrl",
["transfers",
"transferWorkItemHoldResource",
TransferListCtrl]);
function TransferListCtrl(transfers, transferWorkItemHoldResource) {
var vm = this;
vm.transfers = transfers;
vm.length = transfers.length;
if (vm.length > 0) {
vm.branch = transfers[0].branchId;
for (var i = 0; i < vm.transfers.length; i++) {
vm.transfers[i].transferId = vm.transfers[i].transferId.trim();
};
}
vm.changeHold = function (transferId, isHeld) {
transferWorkItemHoldResource.save({ Id: transferId, IsHeld: isHeld });
};
}}());
My resource looks like this:
(function () {
"use strict";
angular
.module("common.services")
.factory("transferWorkItemHoldResource",
["$resource", transferWorkItemHoldResource]);
function transferWorkItemHoldResource($resource) {
return $resource("https://my-test-url.net/api/TransferWorkItem/SetHoldStatus", {}, {
save: {
method: 'POST',
headers: { 'ZUMO-API-VERSION': '2.0.0' }
}
});
}}());
So, when I just call the API from Postman, it updates the record with the bool that I send as a parameter. It works.
When I run the app, and call the API, this is what I get:
Looking at the request header in dev tools, I can see that my parameters are in a request payload:
I've never tried using $resource to POST with a custom header and parameters, so I may be missing something. I'm hoping someone can point out what I'm doing wrong.
You should define the action parameter as complex object.
Change it
public async Task<IHttpActionResult> SetHoldStatus(string id, bool isHeld)
to
public async Task<IHttpActionResult> SetHoldStatus(SetHoldStatusInput input)
public class SetHoldStatusInput
{
public string Id { get; set; }
public bool IsHeld { get; set; }
}
I have an ASP.Net WebApi2 project hosting odata both ApiController and ODataController.
And I want to add a custom action in an ODataController.
I saw this seems to be achievable by either adding [HttpPost] attribute on the desired action, or by configuring the ODataConventionModelBuilder with a specific FunctionConfiguration when using the MapODataServiceRoute.
To distinguish between odata routes and webapi routes we use the following scheme :
odata : http://localhost:9292/myProject/odata
webapi : http://localhost:9292/myProject/api
I tried both these solution without success which all led to get an HTTP 404 result.
My custom action is defined as following:
public class SomeModelsController : ODataController
{
//...
[EnableQuery]
public IHttpActionResult Get()
{
//...
return Ok(data);
}
public IHttpActionResult MyCustomAction(int parameterA, int parameterB)
{
//...
return Json(data);
}
//...
}
So as you guessed it, the Get call on the controller perfectly work with odata. However the MyCustomAction is a bit more difficult to setup properly.
Here is what I have tried :
Setting an [HttpPost] attribute on MyCustomAction
[HttpPost]
public IHttpActionResult MyCustomAction(int parameterA, int parameterB)
{
//...
return Json(data);
}
I also tried decorating MyCustomAction with the [EnableQuery] attribute.
Also, I tried adding the [AcceptVerbs("GET", "POST")] attribute on the method without changes.
Configuring the ODataConventionModelBuilder
private static IEdmModel GetEdmModel()
{
var builder = new ODataConventionModelBuilder
{
Namespace = "MyApp",
ContainerName = "DefaultContainer"
};
// List of entities exposed and their controller name
// ...
FunctionConfiguration function = builder.Function("MyCustomAction ").ReturnsFromEntitySet<MyModel>("SomeModels");
function.Parameter<int>("parameterA");
function.Parameter<int>("parameterB");
function.Returns<MyModel>();
return builder.GetEdmModel();
}
Also tried decoration of MyCustomAction with [EnableQuery], HttpPost and [AcceptVerbs("GET", "POST")] attributes.
I still get HTTP 404 result.
My query url is as follow:
http://localhost:9292/myProject/odata/SomeModels/MyCustomAction?parameterA=123¶meterB=123
I also tried to POST parameters on
http://localhost:9292/myProject/odata/SomeModels/MyCustomAction with the same result. Actually with or without parameters I get HTTP 404 status.
I've created a working example from scratch with Visual Studio 2017.
If you want more info you can read this tutorial:
https://learn.microsoft.com/en-us/aspnet/web-api/overview/odata-support-in-aspnet-web-api/odata-v4/odata-actions-and-functions
Create a new ASP.Net Web Application (no .Net Core)
Choose WebApi Template
Install from NuGet the package Microsoft.AspNet.OData (I have used v. 6.0.0)
Create a simple model class into Models folder
TestModel.cs
namespace DemoOdataFunction.Models
{
public class TestModel
{
public int Id { get; set; }
public int MyProperty { get; set; }
public string MyString { get; set; }
}
}
Configure WebApiConfig
WebApiConfig.cs
using DemoOdataFunction.Models;
using System.Web.Http;
using System.Web.OData.Builder;
using System.Web.OData.Extensions;
namespace DemoOdataFunction
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
ODataModelBuilder builder = new ODataConventionModelBuilder();
builder.Namespace = "MyNamespace";
builder.EntitySet<TestModel>("TestModels");
ActionConfiguration myAction = builder.EntityType<TestModel>().Action("MyAction");
myAction.Parameter<string>("stringPar");
FunctionConfiguration myFunction = builder.EntityType<TestModel>().Collection.Function("MyFunction");
myFunction.Parameter<int>("parA");
myFunction.Parameter<int>("parB");
myFunction.ReturnsFromEntitySet<TestModel>("TestModels");
config.MapODataServiceRoute(
routeName: "ODataRoute",
routePrefix: "odata",
model: builder.GetEdmModel()
);
}
}
}
Create the controller TestModelsController into Controllers folder
TestModelsController.cs
using DemoOdataFunction.Models;
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;
using System.Web.OData;
using System.Web.OData.Query;
namespace DemoOdataFunction.Controllers
{
public class TestModelsController : ODataController
{
IQueryable<TestModel> testModelList = new List<TestModel>()
{
new TestModel{
MyProperty = 1,
MyString = "Hello"
}
}.AsQueryable();
[EnableQuery]
public IQueryable<TestModel> Get()
{
return testModelList;
}
[EnableQuery]
public SingleResult<TestModel> Get([FromODataUri] int key)
{
IQueryable<TestModel> result = testModelList.Where(t => t.MyProperty == 1);
return SingleResult.Create(result);
}
[HttpPost]
public IHttpActionResult MyAction([FromODataUri] int key, ODataActionParameters parameters)
{
string stringPar = parameters["stringPar"] as string;
return Ok();
}
[HttpGet]
[EnableQuery(AllowedQueryOptions = AllowedQueryOptions.All, MaxExpansionDepth = 2)]
public IHttpActionResult MyFunction(int parA, int parB)
{
return Ok(testModelList);
}
}
}
Edit Web.config changing the handlers section in system.webServer
web.config
<system.webServer>
<handlers>
<clear/>
<add name="ExtensionlessUrlHandler-Integrated-4.0" path="/*"
verb="*" type="System.Web.Handlers.TransferRequestHandler"
preCondition="integratedMode,runtimeVersionv4.0" />
</handlers>
[...]
</system.webServer>
That's all.
This is the request for MyAction:
POST
http://localhost:xxxx/odata/TestModels(1)/MyNamespace.MyAction
{
"stringPar":"hello"
}
This is the request for MyFunction:
GET
http://localhost:xxxx/odata/TestModels/MyNamespace.MyFunction(parA=1,parB=2)
I am using HTTP POST with route on the controller functions like below:
[HttpPost]
[Route("{application}/{envName}/date/{offset}")]
[ResponseType(typeof(DateInfo))]
public async Task<IHttpActionResult> SetDateOffsetForEnvironmentName(string application, string envName, string offset)
{
}
can you try setting the route on the function and then call the post method on it like this:
POST /status/environments/ATOOnline/PTH/date/0
Also try and capture a request through Fiddler and see what is being passed.
I have two methods on a web service that I am trying to invoke with WebClient:
[Route("TestDownload")]
[HttpGet]
public string TestDownload()
{
return "downloaded";
}
[Route("TestUpload")]
[HttpPost]
public string TestUpload(string uploaded)
{
return uploaded;
}
This code works:
using (var wc = new WebClient())
{
var sResult = wc.DownloadString("http://localhost/Website/TestDownload");
Console.WriteLine(sResult);
}
This code throws a System.Net.WebException: (404) Not Found
using (var wc = new WebClient())
{
var sResult = wc.UploadString("http://localhost/Website/TestUpload", "test");
Console.WriteLine(sResult);
}
What am I doing wrong? Thanks
try adding a routing for that controller/method, something like this:
routes.MapRoute(
"yourRouteName", // Route name
"{controller}/{action}",
new { controller = "yourController", action = "TestUpload", uploaded="" } // Parameter defaults
);
I think I figured it out. The UploadString on WebClient is using the string parameter as the http request body. By default, WebApi supplies controller method parameters for simple types, including string, from the query string (see https://learn.microsoft.com/en-us/aspnet/web-api/overview/formats-and-model-binding/parameter-binding-in-aspnet-web-api). To override this behavior and indicate that a string parameter is to be found in the request body, you use the [FromBody] attribute.
[Route("TestUpload")]
[HttpPost]
public string TestUpload([FromBody] string uploaded)
{
return uploaded;
}
I have the following code, but the request ends (Foo() / Bar()) always in No action was found on the controller 'Device' that matches the request.
I've a custom route in my WebApiConfig:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new {id = RouteParameter.Optional}
);
My ASP.NET WebAPI controller:
[HttpPost]
public void UpdateToken(string newToken)
{
_deviceHandler.UpdateToken(newToken);
}
To query my ASP.NET WebAPI I'm using RestSharp.
private static void Send(string resource, Method method, object payload)
{
var client = new RestClient(baseUrl);
var request = new RestRequest(resource, method);
request.XmlSerializer = new JsonSerializer();
request.RequestFormat = DataFormat.Json;
request.AddBody(payload);
var response = client.Execute(request);
// ... handling response (exceptions, errors, ...)
}
public void Foo()
{
var newToken = "1234567890";
Send("/api/device/updatetoken", RestSharp.Method.POST, newToken );
}
public void Bar()
{
var newToken = new { newToken = "1234567890" };
Send("/api/device/updatetoken", RestSharp.Method.POST, newToken );
}
The only way to avoid this error is creating a wrapper class with a property (get;set;) in it which has the name of the controller argument (newToken).
I have a lot of requests which sends one or two custom strings (undefined length) as post (get is limited in length). But to create for each scenario a wrapper implementation is real overhead! I'm looking for another way to go.
PS: I hope I haven't made any mistakes by simplifying the scenario =)
Primitives are by default bound from the URI. If you want a primitive to come from the body, you should use the [FromBody] attribute like this:
[HttpPost]
public void UpdateToken([FromBody] string newToken)
{
_deviceHandler.UpdateToken(newToken);
}
The string will then be deserialized using the appropriate formatter. If it's JSON, the request body should look like this:
"1234567890"