I have developed an API for an application and i have all routes set up, but when the users request the wrong action, the server returns 404 by default and i tried to find some way of changing that but i didnt find anything...
So lets say i have an api with the following route:
/api/school/1/classes
if the client requests
/api/school/1/classez
i want to return response code 501 or 405 instead of the default 404.
how can this be done?
Use Route attributes.
[HttpGet]
[Route("api/{id}/foo")]
public HttpResponseMessage foo(int id)
{
var response = new ApiResponse();
response.StatusCode = HttpStatusCode.OK;
return response;
}
[HttpGet]
[Route("api/{id}/{route}")]
public HttpResponseMessage bar(int id)
{
var response = new ApiResponse();
response.StatusCode = HttpStatusCode.BadRequest;
return response;
}
If they type it correctly then it will enter foo. Otherwise they will enter bar and you can handle that how ever you like.
Related
I'm building an webapi in c# to be called by an outside server.
let's say my API address is www.server.com/webapi/service1
when I set the address above in the app that will use it, it sends a simple POST with an empty body to service1 and waits for a specific KEY as response (in body), like an authentication. ok.
the same service1 can be called, using POST too, passing a raw JSON in the body, and I'm using the [FromBody] attribute to get the body and process.
I tried this to manage the empty POST call and the call with body data:
[HttpPost]
[Route("webapi/service1")]
public HttpResponseMessage Post()
{
var resp = new HttpResponseMessage(HttpStatusCode.OK);
resp.Content = new StringContent(TokenKey.ToString(), System.Text.Encoding.UTF8, "text/html");
return resp;
}
[HttpPost]
[Route("webapi/service1")]
public async Task<HttpResponseMessage> Post([FromBody] RetornoChat retornoChat)
{
await closedChat(retornoChat); //process body
return resp;
}
but it was not working.I manage a workaround like the code below, I check if the class in [FromBody] is empty, if this is the case return the special string to validate and finish, if there is a body then get the data validate and process. I'm wondering if there is a better solution.
I really thought that the solution was to double the post method and when there was a body it would call the post with the [frombody] and when there is no body it would go to the empty post.
[HttpPost]
[Route("webapi/service1")]
public async Task<HttpResponseMessage> Post([FromBody] RetornoChat retornoChat)
{
var resp = new HttpResponseMessage(HttpStatusCode.OK);
resp.Content = new StringContent(TokenKey.ToString(), System.Text.Encoding.UTF8, "text/html");
if (retornoChat == null)
{
}
else
{
//get the body data and process
}
return resp;
}
Thanks in advance for your time!
I'm using ASP.NET Core for building REST service
I need to return custom response code if user tries to request an endpoint with unsupported method.
For example endpoint localhost/api/test supports GET method only, but user requests it with POST method. I need to return 404 response and custom body.
How to do this with ASP.NET Core?
UPD:
Possibly I formulated my question incorrectly.
I need ASP Core return 405 response code with custom JSON body in case if a method is not allowed.
This should be a standard behavior, but not implemented yet (according to this issue)
So I'm looking to workaround to return 405 response code nevertheless ASP Core does not support it out of box.
On a controller method level, probably this will guide you. You create a HttpResonseMessage, with your preferred status code and message. Note: if you want status like 302 then you also need to fill location header.
if (Request.Method.Method.Equals("POST", StringComparison.OrdinalIgnoreCase))
{
IHttpActionResult response;
HttpResponseMessage responseMsg = new HttpResponseMessage(HttpStatusCode.NotFound);
responseMsg.Content = new StringContent("Method doesn't support POST or whatever", System.Text.Encoding.UTF8, "text/html");
response = ResponseMessage(responseMsg);
return response;
}
Assuming you add a custom header in your controller method, to differencitate it from framework response.
In webapi.config register a CustomMessageHandler.
config.MessageHandlers.Add(new CustomMessageHandler());
//Define CustomMessageHandler like below and overide SendAsync
public class CustomMessageHandler: DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
var reasonInvalid = String.Empty;
var res= base.SendAsync(request, cancellationToken);
if (res.Result.StatusCode == HttpStatusCode.NotFound || res.Result.StatusCode == HttpStatusCode.MethodNotAllowed)
{
if(!res.Result.Headers.Contains("CustomHeaderforIntentional404"))
{
res.Result.StatusCode = HttpStatusCode.MethodNotAllowed;
res.Result.Content = new StringContent("Method doesn't support this method CUSTOM MESSAGE", System.Text.Encoding.UTF8, "text/html");
return res;
}
}
return res;
}
}
as per the official docs ...
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/error-handling
app.UseStatusCodePages();
// app.UseStatusCodePages(context => context.HttpContext.Response.SendAsync("Handler, status code: " + context.HttpContext.Response.StatusCode, "text/plain"));
// app.UseStatusCodePages("text/plain", "Response, status code: {0}");
// app.UseStatusCodePagesWithRedirects("~/errors/{0}"); // PathBase relative
// app.UseStatusCodePagesWithRedirects("/base/errors/{0}"); // Absolute
// app.UseStatusCodePages(builder => builder.UseWelcomePage());
// app.UseStatusCodePagesWithReExecute("/errors/{0}");
... something like that should work.
Is it possible to send [FromBody] POST data to a controller using client.GetAsync() (or PostAsync/SendAsync?
I had to set up a base controller that all api calls will go through.
My ajax calls all go to this SecureApi controller, and they send the original path as a parameter to that they can be re-routed to the correct controller. Something like:
$.ajax({
url: "./api/SecureApi/?path=/api/OtherApi/SomeRoute",
data: {
param1: 1,
param2: 2
}
});
So my base controller looks something like:
public class SecurityApiController : ApiController
{
[HttpPost]
public HttpResponseMessage SecureApi([FromBody]object data, string path)
{
// do some stuff
// get uri
var applicationPath = Request.RequestUri.Scheme + "://" + Request.GetRequestContext().VirtualPathRoot.Replace("/", String.Empty);
Uri routeUri = new Uri(applicationPath + path);
// then redirect to correct controller
var config = new HttpConfiguration();
var server = new HttpServer(config);
var client = new HttpClient(server);
// how can I send [FromBody]object data here?
var response = client.GetAsync(routeUri).Result; // or PostAsync/SendAsync?
return response;
}
}
The other controller looks like:
public class OtherApiController : ApiController
{
[HttpPost]
public HttpResponseMessage OtherApi([FromBody]OtherData data)
{
// do stuff
}
}
Unfortunately I can't change OtherApi, so I HAVE to send the [FromBody] POST data in the same way (in the POST body).
Is that possible?
EDIT:
Per #Philippe's response below, I'm using PostAsJsonAsync and it seems to want to work, but I'm getting a 401 Unauthorized result. More info:
I went with the correct(?) ASYNC/AWAIT route...
public async Task<HttpResponseMessage> SecureApi([FromBody]Dictionary<string, dynamic> data, string path)
{
...
var client = new HttpClient();
var response = await client.PostAsJsonAsync(routePath, data);
return response;
}
And the Other controller has:
[Authorize(Roles = "Admin")] // I do have the "Admin" role
[Route("Save")]
[HttpPost]
public SaveResultBase Save([FromBody]Dictionary<string, dynamic> data)
{
...
}
But this controller is never hit (no breakpoints are hit there) and it returns a 401 Unauthorized response.
I guess that I have to add my user credentials to the client headers before calling PostAsJsonAsync. Can't find any way to do that though.
The method GetAsync of HttpClient will send a HTTP GET request so it would only be possible to have [FromUri] arguments. Because [FromBody] argument are by definition POST data, you will want to use PostAsJsonAsync/ PostAsXmlAsync/PostAsync. The difference between all of them is how the data is serialized.
var response = client.PostAsJsonAsync(routeUri, data).Result;
That being said, if you have security in mind, it would be rather easy for anyone to call the "right api" directly. Moreover you will increase latency by generating two HTTP requests.
You should take a look at this guide on MSDN. I believe that an authentication filter is probably what you are looking for.
I'm posting some data to an web api controller method from an MVC controller with this method..
private static async Task<HttpResponseMessage> SendDataToApi (List<TogglRow> input)
{
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:****/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var response = await client.PostAsJsonAsync("api/service", input);
//if (response.StatusCode == HttpStatusCode.OK)
//{
// var resultUri = response.Headers.Location;
//}
return response;
}
}
This is the Web Api method i'm posting to..
public HttpResponseMessage Post(HttpRequestMessage request, List<Dagsrapport> value)
{
if (value != null)
{
var rapporter = value.ToList();
//send rapporter to DB
var response = new HttpResponseMessage(HttpStatusCode.OK);
return response;
}
return request.CreateResponse(HttpStatusCode.BadRequest);
}
Now, the post works fine and i'm returning HttpStatusCode.OK. But i'm not beeing redirected back to the method i'm performing the post from (SendDataToApi()). I'm beeing returned back to the page from wich the post was triggered. I can see the page is working (waiting for localhost..) but nothing happens.
I should probably mention that this is two separate projects (MVC & WebApi), but in the same solution.
What am i missing?
EDIT - Solved
The problem I had was due to the method that ran the task "SendDataToApi" was not set to async. Therefore, it did not wait for an results from the post, but instead ran synchronously and the control never returned to the method that ran SendDataToApi, instead it returned to the original caller - the UI.
Here is the method that is runnig the SendDataToApi task..
[HttpPost]
public async Task<ActionResult> Edit(IEnumerable<TogglRow> tr)
{
var listToExport = tr.Where(x => x.Export.Equals(true));
var result = listToExport.ToList();
var response = await SendDataToApi(result);
return RedirectToAction("Index", "Home",
response.StatusCode == HttpStatusCode.OK ? new { message = "Record(s) were successfully stored." } : new { message = "No records selected." });
}
It seems you have some fundamental misunderstandings about how all this works. MVC actions and WebAPI actions work very differently, which is why they're actually in entirely different namespaces, even though they both implement similar components.
If you need to connect to a Web API from an MVC action, you shouldn't be receiving the response as an HttpResponseMessage. That's a return value for a WebAPI action, similar to how a ViewResult is a return value for an MVC action. It has no meaning to anything in MVC. Rather, your actual response from HttpClient, for example, will be a string (technically a byte array) with a content type indicating that it should be interpreted as plain text, JSON, XML, etc.
Based on the content type, you'll process this response accordingly. If it's JSON, for example, then you can use something like JObject from Newtonsoft.Json (default JSON interpreter in MVC). Then, you could use this data object to construct your response for your MVC action. If you have something indicating that a redirect should be made, then the MVC action can return on of the Redirect* family of results. Importantly, you can't just make the redirect the response of the Web API action, because that merely affects the HttpClient response object.
I've got the following code running in a Windows Store application that is supposed to call one of my WebApis:
using (var client = new HttpClient())
{
var parms = new Dictionary<string, string>
{
{"vinNumber", vinNumber},
{"pictureType", pictureType},
{"blobUri", blobUri}
};
HttpResponseMessage response;
using (HttpContent content = new FormUrlEncodedContent(parms))
{
const string url = "http://URL/api/vinbloburis";
response = await client.PostAsync(url, content);
}
return response.StatusCode.ToString();
}
The WebApi code looks like this:
[HttpPost]
public HttpResponseMessage Post(string vinNumber, string pictureType, string blobUri)
{
var vinEntity = new VinEntity
{
PartitionKey = vinNumber,
RowKey = pictureType,
BlobUri = blobUri
};
_vinRepository.InsertOrUpdate(vinEntity);
return new HttpResponseMessage { Content = new StringContent("Success"), StatusCode = HttpStatusCode.OK };
}
Using Fiddler, I've observed the following ... here's what the request looks like:
POST http://URL/api/vinbloburis HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Host: URL
Content-Length: 113
Expect: 100-continue
vinNumber=vinNumber&pictureType=top&blobUri=https%3A%2F%2Fmystorage.blob.core.windows.net%2Fimages%2Fimage.png
But the response is an error with little/no information:
{"Message":"An error has occurred."}
I've even tried locally with the debugger attached to the WebApis and my Post method never catches.
Does anyone see something I've missed here? I feel like I'm looking right at it but not seeing it. I should add that I am able to call an HttpGet method while passing a parameter through the querystring. Right now the problem is only with this HttpPost.
Thanks!
UPDATE: Based on some good comments from folks I'm adding some more details.
I have the default routes configured for WebApis ...
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
Consequently, I think /URL/api/vinbloburis should work. Additionally, as alluded above, I currently have this working with an HttpGet. Here's what's working in the Windows Store app to call the HttpGet WebApi ...
using (var client = new HttpClient())
{
using (var response = await client.GetAsync(uri))
{
if (response.IsSuccessStatusCode)
{
var sasUrl = await response.Content.ReadAsStringAsync();
sasUrl = sasUrl.Trim('"');
return sasUrl;
}
}
}
... and it's calling the following WebApi ...
[HttpGet]
public HttpResponseMessage Get(string blobName)
{
const string containerName = "images";
const string containerPolicyName = "policyname";
_helper.SetContainerPolicy(containerName, containerPolicyName);
string sas = _helper.GenerateSharedAccessSignature(blobName, containerName, containerPolicyName);
return new HttpResponseMessage
{
Content = new StringContent(sas),
StatusCode = HttpStatusCode.OK
};
}
I hope the additional information helps!
I copied your code, as is and I was getting a 404 error.
I changed the signature to
public HttpResponseMessage Post(FormDataCollection data)
and it worked.
You can also just do this,
public HttpResponseMessage Post(VinEntity vinEntity)
and the model binder will do the mapping work for you.
Rick Strahl has a post on the issue here http://www.west-wind.com/weblog/posts/2012/Sep/11/Passing-multiple-simple-POST-Values-to-ASPNET-Web-API
Have you turned on Internet Networking in your manifest?
It may be a Cross Domain Ajax Security issue. See JSONP info here. => http://json-p.org/