I have a Controller which has the following declaration
[Authorize(Roles = Role.Admin)]
[ApiController]
[Route("meta/[controller]")]
public class ActionParameterController : BaseController<ActionParameterController>
Inside it there is the following method
[HttpPost("insert/{action}/{entity}")]
public IActionResult InsertActionParameter(
[FromBody] MetaActionParameter parameter,
int action,
int entity)
However when I try to do the POST request to this endpoint I get 404.
The url:
http://localhost:5000/meta/actionParameter/insert/2/2
Console output is:
2021-08-19 14:03:35.311 (Microsoft.AspNetCore.Hosting.Diagnostics.POST) [Information] Request starting HTTP/1.1 POST http://192.168.14.104:5000/meta/actionParameter/insert/1/2 - 0
2021-08-19 14:03:38.116 (Microsoft.AspNetCore.HostFiltering.HostFilteringMiddleware.) [Verbose] All hosts are allowed.
2021-08-19 14:03:38.117 (Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware.POST) [Debug] "POST" requests are not supported
2021-08-19 14:03:38.120 (Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware.POST) [Debug] "POST" requests are not supported
2021-08-19 14:03:38.125 (Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware.POST) [Debug] "POST" requests are not supported
2021-08-19 14:03:38.132 (Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler.) [Debug] AuthenticationScheme: "Bearer" was not authenticated.
2021-08-19 14:03:38.180 (Microsoft.AspNetCore.Routing.Matching.DfaMatcher.) [Debug] No candidates found for the request path '"/meta/actionParameter/insert/1/2"'
2021-08-19 14:03:38.182 (Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware.) [Debug] Request did not match any endpoints
2021-08-19 14:03:38.185 (Microsoft.AspNetCore.Server.Kestrel.) [Debug] Connection id ""0HMB303OBKKOL"" completed keep alive response.
2021-08-19 14:03:38.191 (Microsoft.AspNetCore.Hosting.Diagnostics.POST) [Information] Request finished HTTP/1.1 POST http://192.168.14.104:5000/meta/actionParameter/insert/1/2 - 0 - 404 0 - 2880.056
I tried getting all routes from IActionDescriptorCollectionProvider and the route is printed. There are other routes in that controller that do work.
If I change the route to be only "insert" the request goes through. From what I can see, there is no conflict between routes.
What further steps should I take to diagnose this issue?
I think I know what the problem is.
I think the action and/or insert words are probably reserved words.
I tested with the following code and it worked:
[HttpGet("insert/{xaction}/{xentity}")]
public IActionResult InsertActionParameterGet(int xaction, int xentity)
{
Console.WriteLine(xaction + " " + xentity);
return new JsonResult("Ok");
}
Notice I just added an "x" to action and entity, just as a test.
Also, I did a test on a GET action.
Before adding the "x", it was not working, so I do think those are reserved words probably.
You need to write the complete router at Http Method Attribute, like this:
[HttpPost("meta/ActionParameter/insert/{action}/{entity}")]
public IActionResult InsertActionParameter([FromBody] MetaActionParameter parameter, int action, int entity)
There is a similar question here: C# Web Api 2 PUT and POST requests "not supported"
Based on that one, it looks like you should not be using [FromBody].
So your method signature should look something like this:
[HttpPost("insert/{action}/{entity}")]
public IActionResult InsertActionParameter(int action, int entity)
To ready the body of the request, you could do something like this:
var bodyStr = "";
var req = context.HttpContext.Request;
// Allows using several time the stream in ASP.Net Core
req.EnableRewind();
// Arguments: Stream, Encoding, detect encoding, buffer size
// AND, the most important: keep stream opened
using (StreamReader reader
= new StreamReader(req.Body, Encoding.UTF8, true, 1024, true))
{
bodyStr = reader.ReadToEnd();
}
// Rewind, so the core is not lost when it looks the body for the request
req.Body.Position = 0;
// Do whatever work with bodyStr here
Related
We have a .netcore 3.1 ApiController with an endpoint listening for PATCH requests, and defined a Test Server that we're using for the Integration/API tests.
PATCH request sent with Postman works just fine, but requests sent via HttpClient inside the XUnit tests are failing with 415 Unsupported media type.
Postman Patch request:
No specific headers other than Bearer token and Content-Type: "application/json"
In the tests, we use WebApplicationFactory and it's factory.CreateClient() for our HttpClient.
It shouldn't be an issue with Json Serialization since I looked into the content through debugger and it seems to be serialized just fine.
Also, our POST methods work completely out of the box with this exact same code (replacing "PATCH" with "POST" etc)
Looking forward to some advices. Also if you need any more info, please let me know. Thanks a lot.
Controller:
[HttpPatch("{id}")]
public async Task<ActionResult<Unit>> Edit(Edit.Command request)
{
return await Mediator.Send(request);
}
Command:
public class Command : IRequest
{
public string Id { get; set; }
public JsonPatchDocument<ObjectDTO> PatchDocument { get; set; }
}
Test:
[InlineData(/* inline data goes here */)]
public async void TestEdit_Ok(/* required parameters for the test */)
{
var request = new HttpRequestMessage(new HttpMethod("PATCH"), url));
request.Headers.Add("Authorization", "Bearer " + token);
/* create patch document logic goes here */
var command = new Command()
{
Id = target,
PatchDocument = patchDocument,
};
_testHelper.AddJsonContent(request, command);
// Act
var response = await _client.SendAsync(request);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
Where helper method AddJsonContent is defined as:
public void AddJsonContent(HttpRequestMessage request, object content)
{
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
string serializedContent = JsonConvert.SerializeObject(content);
request.Content = new StringContent(serializedContent, Encoding.UTF8, "application/json");
}
just wanted to say that, while I see this is a confirmed bug, I think once we used full url http://localhost:PORT/endpoint in the client instead of just /endpoint, we didn't encounter this issue anymore. This is the same as one of the proposed workarounds on github issue.
I see the ticket Vadim linked is still open so this may fix the issue for some of you.
Thanks for the help.
The problem is confirmed in Asp.Net Core 5.0
(the problem confirmed by me — I have the same problem as the topic starter)
The PATCH method returns the "415 Unsupported Media Type" status, when using the xUnit with WebApplicationFactory and factory.CreateClient() for HttpClient.
All possible attempts in different combinations results the 415 status.
However, other means like Swagger and Postman work well with the PATCH methods.
Only the xUnit (or WebApplicationFactory) PATCH method fails.
Finally, to conduct the testing, we made a workaround with POST methods, which contain /with-partial-update as a route part.
This bug is reported to aspnetcore repository
After migrating to ASP.NET Core 2.1 we have realized that some consumers of our API are sending GET requests with the Content-Type header set to application/json. Sadly, these requests have not been rejected in the past (even though they should have), nevertheless this still is a breaking change..
Since our consumers need to fix this issue on their end, and this will take some time, we would like to temporarily accept these requests so we're not stuck waiting for this.
The framework (correctly) rejects the request with the following error message: "A non-empty request body is required."
The action looks like this:
[Route("api/file/{id:guid}")]
public async Task<IActionResult> Get(Guid id)
{
// Some simple code here
}
The code inside the action isn't being reached as the error is already been thrown before it reaches the action (due to the incorrect request).
#Nkosi's solution resulted in the same response:
[HttpGet("api/file/{id:guid}")]
public async Task<IActionResult> Get([FromRoute]Guid id)
{
// Some simple code here
}
The (PHP) cURL that the consumer uses is this:
$ch = curl_init(self::API_URL."/file/".$id);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_FRESH_CONNECT, 1);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
"Content-Type: application/json",
"Application: APPKey ".$this->AppKey,
"Authorization: APIKey ".$this->ApiKey
));
Removing the "Content-Type: application/json", line turns the request into a valid requests, so we're 99.9% sure that the addition of this header is the evildoer.
Consider removing the header in a middleware early in the pipeline.
public void Configure(IApplicationBuilder app) {
app.Use(async (context, next) => {
var request = context.Request;
var method = request.Method;
IHeaderDictionary headers = request.Headers;
string key = "Content-Type";
if (method == "GET" && request.Headers.ContainsKey(key)) {
headers.Remove(key);
}
// Call the next delegate/middleware in the pipeline
await next();
});
//...
}
I just created this bucket: mybucket231.
Running this C# code to generate Signed-Url:
using (var stream = new FileStream(#"path/to/secret.json", FileMode.Open, FileAccess.Read))
{
var cred = GoogleCredential.FromStream(stream)
.CreateScoped("https://www.googleapis.com/auth/devstorage.full_control")
.UnderlyingCredential as ServiceAccountCredential;
var urlSigner = UrlSigner.FromServiceAccountCredential(cred);
var publicLink = urlSigner.Sign("mybucket231", "file.test", TimeSpan.FromHours(1));
}
This code generate Signed URL with full_control permission. Getting back url looks like this:
https://storage.googleapis.com/mybucket231/file.test?GoogleAccessId=new-storage-service-account#dotnet-core-cluster.iam.gserviceaccount.com&Expires=1548606014&Signature=HbL0ETXucaldz8jpoUDXUzYQu2YyhiMUh4Nfm69Y0sLyG3pvqbVvMMK1N8agywE8gW8s7kkInJCJuVGH%2FAHzd4LfeYo62iFK....FjnImQZq7fftv4TF5SpCPsVFnOGkSD6vOIpKqfJiswqGIERC9D7EJ%2B2DZ9JVMP7cEYjmAB9miemtD2eTVXu3FpBNbnDoxp112eTmu2F4TAckS0toX%2FmYk8GhOc9UnWH1iZ5VJ%2FKslFmRU0NFu4nxkDv7rk%2FRCvsOvvqrOqJT6cezE%2Bz%2FMONh%2FK5KfPs0ZnYslQwYNojhVR4sn5L8tVNst6gclFnA%3D%3D
Now, I'm going to Fiddler and tring to create file with this command:
PUT https://storage.googleapis.com/mybucket231/file.test?GoogleAccessId=new-storage-service-account#secret.iam.gserviceaccount.com&Expires=1548606014&Signature=HbL0ETXucaldz8jpoUDXUzYQu2YyhiMUh4Nfm69Y0sLyG3pvqbVvMMK1N8agywE8gW8s7kkInJCJuVGH%2FAHzd4LfeYo62iFK....FjnImQZq7fftv4TF5SpCPsVFnOGkSD6vOIpKqfJiswqGIERC9D7EJ%2B2DZ9JVMP7cEYjmAB9miemtD2eTVXu3FpBNbnDoxp112eTmu2F4TAckS0toX%2FmYk8GhOc9UnWH1iZ5VJ%2FKslFmRU0NFu4nxkDv7rk%2FRCvsOvvqrOqJT6cezE%2Bz%2FMONh%2FK5KfPs0ZnYslQwYNojhVR4sn5L8tVNst6gclFnA%3D%3D
Headers:
User-Agent: Fiddler
Host: storage.googleapis.com
Content-Length: 3
content-type: text/plain
Body:
Hello world!
This PUT message gives back error 403 (Forbidden) with this message:
<?xml version='1.0' encoding='UTF-8'?>
<Error>
<Code>SignatureDoesNotMatch</Code>
<Message>The request signature we calculated does not match the signature you provided. Check your Google secret key and signing method.</Message><StringToSign>PUT
plain/text
1548606014
/mybucket231/file.test</StringToSign>
</Error>
It says: SignatureDoesNotMatch. But it's strange. I create this file and try the verb GET - same signature does working!
How? How to solve the PUT verb?
Note: My service account (new-storage-service-account#secret.iam.gserviceaccount.com) is StorageAdmin.
Found the problem. Actully, it was two problems:
You must specify content-type header. So add
Then I had another problem. Allowing content-type in lower case - it's not enough! it should be Content-Type instead.
Final code:
string url = urlSigner.Sign(
"mybucket231",
"file.test",
TimeSpan.FromHours(1),
HttpMethod.Put,
contentHeaders: new Dictionary<string, IEnumerable<string>> {
{ "Content-Type", new[] { "text/plain" } }
}
);
Edit 1: Other Controller
public class identityController : ApiController
{
[HttpGet]
public async Task<IHttpActionResult> getfullname(string firstName)
{
string name = firstName;
return Ok(name);
}
}
I have created a controller which uses an API from another solution.
Method that i use in the controller looks like below:
public class GetNameController : ApiController
{
[HttpGet]
public async Task<IHttpActionResult> CalculatePrice(string firstName)
{
string _apiUrl = String.Format("api/identity/getfullname?firstName={0}", firstName);
string _baseAddress = "http://testApp.azurewebsites.net/";
using (var client = new HttpClient())
{
client.BaseAddress = new Uri(_baseAddress);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = await client.GetAsync(_apiUrl);
if (response.IsSuccessStatusCode)
{
return Ok(response);
}
}
return NotFound();
}
The result of response.IsSuccessStatusCode is always false. When i check the response values i see this result:
{
StatusCode: 400, ReasonPhrase: 'Bad Request', Version: 1.1, Content: System.Net.Http.StreamContent, Headers:
{
Connection: close
Date: Thu, 21 Jul 2016 12:28:21 GMT
Server: Microsoft-HTTPAPI/2.0
Content-Length: 334
Content-Type: text/html; charset=us-ascii
}
}
What could i be missing?
string _apiUrl = String.Format("api/identity/{0}", firstName);
This is assuming that your url is correct, and your testapp is up and running. Even though when I hit it azure tells me your app is stopped. You will need to get your app started first, then change the string _apiUrl to the suggestion above.
http://testapp.azurewebsites.net/api/identity/getfullname?firstName=steve
Gives me this message
Error 403 - This web app is stopped.
The web app you have attempted to reach is currently stopped and does
not accept any requests. Please try to reload the page or visit it
again soon.
If you are the web app administrator, please find the common 403 error
scenarios and resolution here. For further troubleshooting tools and
recommendations, please visit Azure Portal.
So there are several things in your identity controller that are going on.
the functions name is getFullName. Since the word get is in the name of the function. Any httpget request will be routed to the function automagically. Thus making the [HttpGet] redundant. This only works if there is 1 and only 1 httpget request in your controller. If there are multiple you will need to fully qualify the url like you have done
Since youa re using the [httpget] method attribute I can assume you are using webapi2. That being the case and you are using a
primitive in your controller argument you can do notneed to fully
qualify the parameter name on your call. ?firstname={0} changes to
/{0}
I made a c# web api at the the moment . It is working fine until today.
I've tried to convert an image to a base64string, and then send the base64string to the c# server through ajax. When I did the said steps, an error occur.
XMLHttpRequest cannot load http://10.0.10.105:50231/api/hello. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://10.0.10.201' is therefore not allowed access.
I dont know where the main problem resides but in my observation, the error only occur when passing a very long base64string to the server because when i try to send short test string the problem wont appear and all is working well.
Do you have any idea what is the better remedy to fix this ? Or any other way to perform my needed objective ? my ajax code is something like this.
$.ajax({
type: 'POST', //GET or POST or PUT or DELETE verb
url: 'http://10.0.10.105:50231/api/hello', // Location of the service
contentType: 'application/x-www-form-urlencoded', // content type sent to server
data: { action: "3",pers_guid:"wew",base64image:"this-is-the-base64-image"},
//dataType: 'jsonp', //Expected data format from server
//processdata: true, //True or False
success: function (data) {//On Successfull service call
$scope.pageView = 2;
console.log("THE Data description for match : " + data[0].description);
},
error: function (msg) {// When Service call fails
alert(msg);
}
});
and my c# server is similar to this (this is not the whole code).
public class theHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
return base.SendAsync(request, cancellationToken)
.ContinueWith((task) =>
{
HttpResponseMessage response = task.Result;
response.Headers.Add("Access-Control-Allow-Origin", "*");
return response;
});
}
}
public class HelloController : ApiController
{
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
//.... more code here
}
This is the result when I tried to pass a very long string...
Request URL:http://10.0.10.105:50231/api/hello
Request Headers CAUTION: Provisional headers are shown.
Accept:*/*
Content-Type:application/x-www-form-urlencoded
Origin:http://10.0.10.201
Referer:http://10.0.10.201/kiosk/
User-Agent:Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.114 Safari/537.36
Form Dataview sourceview URL encoded
action:3
pers_guid:wew
base64image:the_long_base64_string
but when i pass just a sample string this is the result.
Remote Address:10.0.10.105:50231
Request URL:http://10.0.10.105:50231/api/hello
Request Method:POST
Status Code:200 OK
Request Headersview source
Accept:*/*
Accept-Encoding:gzip,deflate,sdch
Accept-Language:en-US,en;q=0.8
Connection:keep-alive
Content-Length:49
Content-Type:application/x-www-form-urlencoded
Host:10.0.10.105:50231
Origin:http://10.0.10.201
Referer:http://10.0.10.201/kiosk/
User-Agent:Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.114 Safari/537.36
Form Dataview sourceview URL encoded
action:3
pers_guid:wew
base64image:sample_string
Response Headersview source
Access-Control-Allow-Origin:*
Content-Length:103
Content-Type:application/json; charset=utf-8
Date:Wed, 04 Jun 2014 01:02:35 GMT
Server:Microsoft-HTTPAPI/2.0
This error shows up because your server listening on '10.0.10.105' does not specify the 'Access-Control-Allow-Origin' header in its HTTP response. This is a common issue with websites that 'talk' to each other, and you may read about it here (or just Google 'CORS').
As a solution, have the server listening on that IP return the following header from the POST response:
Access-Control-Allow-Origin: *
This has some security implications that you might want to read about (in general it's best to not use the "allow-all" star '*' and instead specify the requesting server explicitly).
Update: As explained in that paper under "Preflight requests", non-trivial POST requests require the server to also listen to OPTIONS requests and return these headers in the response:
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: POST, GET, OPTIONS
Can you try having the image server listen to OPTIONS (just like you did for GET, POST, etc.) and return these headers? (The client making the 'POST' request will automatically precede it with an OPTIONS request to the same server, and if the OPTIONS response contains these headers, the subsequent POST will be called.)
Thank you so much for your answers. It gives me lot of ideas about access origin. It seems that my problem is on the configuration. I added this code and all is working fine.
config.MaxReceivedMessageSize = 5000000;
The program will automatically show an access-control-allow-origin when it exceeds the max limit size of the default configuration.
Thank you so much for the ideas.
So, CORS and the Access-Control-Allow-Origin header are awkward. I ran into this one a while back when I had an API that needed to be referenced from JavaScript code running on several different websites. I ended up writing an ActionFilterAttribute to handle it for my API controllers:
[AttributeUsage(AttributeTargets.Method)]
public class AllowReferrerAttribute : System.Web.Http.Filters.ActionFilterAttribute
{
public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext)
{
var ctx = (System.Web.HttpContextWrapper)actionContext.Request.Properties["MS_HttpContext"];
var referrer = ctx.Request.UrlReferrer;
if (referrer != null)
{
string refhost = referrer.Host;
string thishost = ctx.Request.Url.Host;
if (refhost != thishost)
ctx.Response.AddHeader("Access-Control-Allow-Origin", string.Format("{0}://{1}", referrer.Scheme, referrer.Authority));
}
base.OnActionExecuting(actionContext);
}
}
You can decorate your controller's methods with that attribute and it will add the correct Access-Control-Allow-Origin for your caller's website, regardless of what that website is. And assuming that you're using IIS as your host... won't work for OWIN-hosted sites.
Example usage:
public class HelloController : ApiController
{
[AllowReferrer]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
}