WebAPI request throws 404 error ... on same request - asp.net-web-api

I have done some looking around and not stumbled onto anything resembling the issue I am experiencing so I thought I would throw it up here and see what, if anything sticks.
I have a controller and method set up.
public class BoothAPIController : ITApiControllerBase
{
[HttpGet]
public HttpResponseMessage GetActiveAssetNumbersLike([FromUri] String id)
{
HttpResponseMessage ret;
// ... do some processing
return ret;
}
}
The routes are set up in Global.asax
protected void Application_Start(object sender, EventArgs e)
{
GlobalConfiguration.Configuration.Routes.MapHttpRoute(
name: "CustomApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional });
GlobalConfiguration.Configuration.Routes.MapHttpRoute(
name: "BoothWithDateAPI",
routeTemplate: "api/{controller}/{boothID}/{year}/{month}/{day}");
GlobalConfiguration.Configuration.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional });
}
And these two requests execute flawlessly ...
http://localhost:52211/api/BoothAPI/GetActiveAssetNumbersLike/PR
http://localhost:52211/api/BoothAPI/GetActiveAssetNumbersLike/PRN0
This one however ... returns a 404 error ...
http://localhost:52211/api/BoothAPI/GetActiveAssetNumbersLike/PRN
The Header for the failed request looks like ...
Cache-Control →private
Content-Length →2879
Content-Type →text/html; charset=utf-8
Date →Mon, 29 Aug 2016 12:53:08 GMT
Server →Microsoft-IIS/8.0
X-AspNet-Version →4.0.30319
X-Powered-By →ASP.NET
X-SourceFiles →= [string]
While the successful requests look like
Cache-Control →no-cache
Content-Length →7731
Content-Type →application/json; charset=utf-8
Date →Mon, 29 Aug 2016 13:13:43 GMT
Expires →-1
Pragma →no-cache
Server →Microsoft-IIS/8.0
X-AspNet-Version →4.0.30319
X-Powered-By →ASP.NET
X-SourceFiles → [string]
(Shrugs) I dunno ... I am at a complete loss why the one change in the parameter makes a difference.

I did a little digging on this and was surprised myself. Turn out that the parameter you are trying to send PRN is a reserved word in MS-DOS Device Driver
Below is a list of default device driver names.
PRN System list device, usually a parallel port
This question has an answer to the problem:
IIS gives 404 when MS-DOS reserved names are used in parameters values
But you should be aware of the potentials pitfalls in setting RelaxedUrlToFileSystemMapping to true. See this article by Scott Hanselman: Experiments in Wackiness: Allowing percents, angle-brackets, and other naughty things in the ASP.NET/IIS Request URL

Related

"The length of the string exceeds the value set on the maxJsonLength property" isn't respecting the web.config value?

I asked this question the other day but was not able to come up with a solution:
What happens between clicking a button and the Javascript method actually executing?
Posting a new question with better information including a screenshot of the ajax request, the details from the request / response, as well as my own understanding of what was going on yesterday more clearly so I could debug it better.
My jQuery sends the following request to my server:
The RtfErrorList in data is north of 4 million characters and includes RTF encoding.
Despite adding a variety of XML nodes to my web.config to increase the the maxJsonLength, I'm still getting this exception.
If I look at the network tab in Chrome's dev tools, I get the following information (I stripped some unimportant bits out like origin: locahost and other things like that)
Request Method:POST
Status Code:500 Internal Server Error
Response Headers:
HTTP/1.1 500 Internal Server Error
Content-Type: text/html; charset=utf-8
Server: Microsoft-IIS/7.5
X-AspNet-Version: 4.0.30319
X-UA-Compatible: IE=Edge
Request Headers:
Content-Length: 3799356
Content-Type: application/json;
Accept: text/html, /; q=0.01
X-Requested-With: XMLHttpRequest
Request Payload:
FileName : "someFileName"
RtfErrorList : "some4MillionCharacterStringIncludingRtfEncoding"
The last few entries from the stack trace:
[ArgumentException: Error during serialization or deserialization using the JSON JavaScriptSerializer. The length of the string exceeds the value set on the maxJsonLength property.
Parameter name: input]
System.Web.Script.Serialization.JavaScriptSerializer.Deserialize(JavaScriptSerializer serializer, String input, Type type, Int32 depthLimit)
System.Web.Mvc.JsonValueProviderFactory.GetValueProvider(ControllerContext controllerContext)
System.Web.Mvc.ValueProviderFactoryCollection.GetValueProvider(ControllerContext controllerContext)
I've tried adding the following keys to my web.config:
<add key="JSONMAXJSONLENGTH" value="2147483644" />
<add key="aspnet:MaxJsonDeserializerMembers" value="2147483644" />
I'm actually not sure what that first node is from; it was here before me.
As well as the snippet found here.
Per this SO answer it is my understanding these web.config values are used only by the internal JavaScriptSerializer class, not any "custom" code I might write in a controller myself. In that case, this should be fine as it is System.web.SCript.Serialization.javaScriptSerializer.Deserialize that is throwing the exception. This should be using the web.config value, right?
I cannot figure out what could possibly be causing this...
Are you expecting to receive html format as you set dataType:'html' in Ajax ?
Did you try this in your Ajax:
data: JSON.stringify(data),
contentType: 'application/json; charset=utf-8',
dataType: 'json',
Moreover, according to the documentation, the value of the MaxJsonLength property applies only to the internal JavaScriptSerializer instance that is used by the asynchronous communication layer to invoke Web services methods.
In this case, you may need to serialize manually in the controller. For instance:
public ActionResult FOO()
{
var serializer = new JavaScriptSerializer { MaxJsonLength = Int32.MaxValue };
// You could also read MaxJsonLength from your config
//serializer.MaxJsonLength = Int32.MaxValue;
var myLargeData = new {
ID = "5",
Foo = "Bar",
Value = "foo"
};
var result = new ContentResult
{
Content = serializer.Serialize(myLargeData),
ContentType = "application/json"
};
return result;
}

CORS issue on chrome & firefox

The web API from IIS 7.5 are not responding for Chrome & Firefox.
I am getting the following error in chrome
No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://10.xx.xx.xx:81' is therefore not allowed access
Firefox throw 401 unauthorized error.
Works perfectly on IE 11
Are there any additional setting required for these browsers?
First install WebApi Cors NuGet Package:
Install-Package Microsoft.AspNet.WebApi.Cors
Then in your WebApiConfig.cs file:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
var cors = new EnableCorsAttribute("*", "*", "*");
config.EnableCors(cors);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
//other code
}
}
This way you enable CORS header globally allowing cross-site requests from any domain. If you want to allow cross-site requests from only a single (or a list of) domain, then change the constructor parameters of EnableCorsAttribute:
new EnableCorsAttribute("http://alloweddomain.com", "*", "*");
You may also apply the EnableCorsAttribute on a Controller or Action basis.
More information on the official documentation: http://www.asp.net/web-api/overview/security/enabling-cross-origin-requests-in-web-api
"A resource makes a cross-origin HTTP request when it requests a resource from a different domain than the one which the first resource itself serves"
You can use a chrome addon to allow CORS:
https://chrome.google.com/webstore/detail/allow-control-allow-origi/nlfbmbojpeacfghkpbjhddihlkkiljbi

How can I test my asp.net web API method?

I have a web api method:
[HttpPost, ActionName("GET")]
public string Get(string apiKey, DateTime start, DateTime end)
{
var user = db.Users.SingleOrDefault(u => u.Id == apiKey);
if (user == null)
{
return string.Empty;
}
var records = db.Histories.Where(h => h.Date >= start && h.Date <= end);
return JsonConvert.SerializeObject(records);
}
And here is the url I tried to call the method, but it doesn't reach to the method.
http://localhost:11847/api/History/Get?apiKey=398cfa9b-8c5c-4cf4-b4f3-8904a827ff22&start=2014-01-01&end=2014-12-01
I also have changed the WebApiConfig.cs
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}",
defaults: new { id = RouteParameter.Optional }
);
from "api/{controller}/{id} to "api/{controller}/{action}
UPDATE 2022:
It's 2022 now. A lot has changed. There are plenty of clients out there. I will list my favourites.
Postman - Postman has improved since I answered this in 2014. Apart from being a client, it has other features like collaboration, scripting, importing endpoints from various sources like Open API etc. Pretty simple to use.
Thunder Client - A Visual Studio extension that has a similar feel as Postman but a pure API client.
For testing the api, you can use fiddler(http://www.telerik.com/fiddler) or a chrome app called postman.
You should also try the POSTMAN by http://www.getpostman.com/ which can be added to chrome as an app. It really good and lets you organize your apis.
I found out how to write the url in Fiddler:
In the Composer panel:
Parsed:
GET : http://localhost:11847/api/History/GetRecords?apiKey=398cfa9b-8c5c-4cf4-b4f3-8904a827ff22&start=2014-01-01&end=2014-12-01
Request Headers:
User-Agent: Fiddler
Content-Type: application/json
Host: localhost:11847
Content-Length: 0

Asp.Net WebApi OData with DataJs throws an error

I just created a really simple ASP.Net WebApi project. I used NuGet to download the latest OData in WebAPI – RC release. I also download DataJs and Knockout via NuGet. All my dependencies are up to date. I created a simple "Books" class and wired everything together using HttpConfiguration.EnableOData(IEdmModel). I also added the [Queryable] attribute to my Get action in the controller. There is not database involved, I hard-coded the data I want returned. Basically, I did the minimum amount of changes to run my project with WebApi and OData.
When I try to query the OData service using DataJs, I get a 500 Internal Server Error in the response, but if I browse to the URL directly I can see the XML data. I've included the request, response, my C# class, the Javascript code, and the Global.asax code. What am I missing to get this to work?
REQUEST
Response Headers
Cache-Control private
Content-Length 966
Content-Type application/json; odata=fullmetadata; charset=utf-8
DataServiceVersion 3.0;
Date Fri, 21 Dec 2012 22:13:27 GMT
Server Microsoft-IIS/8.0
X-AspNet-Version 4.0.30319
X-Powered-By ASP.NET
X-SourceFiles =?UTF-8?B?YzpcdXNlcnNcanVzdGluXGRvY3VtZW50c1x2aXN1YWwgc3R1ZGlvIDIwMTJcUHJvamVjdHNcRGF0YUpzU3Bpa2VcRGF0YUpzU3Bpa2VcYXBpXEJvb2tz?=
Request Headers
Accept application/atomsvc+xml;q=0.8, application/json;odata=fullmetadata;q=0.7, application/json;q=0.5, */*;q=0.1
Accept-Encoding gzip, deflate
Accept-Language en-US,en;q=0.5
Connection keep-alive
Cookie glimpseState=null; glimpseLatestVersion=0.87; glimpseOptions=null; glimpseClientName=null
Host localhost:31652
MaxDataServiceVersion 3.0
Referer http://{localhost}/
User-Agent Mozilla/5.0 (Windows NT 6.2; WOW64; rv:17.0) Gecko/20100101 Firefox/17.0
RESPONSE
{
"odata.error":{
"code":"","message":{
"lang":"en-US","value":"An error has occurred."
},"innererror":{
"message":"The 'ObjectContent`1' type failed to serialize the response body for content type 'application/json; odata=fullmetadata; charset=utf-8'.","type":"System.InvalidOperationException","stacktrace":"","internalexception":{
"message":"The related entity set could not be found. The related entity set is required to serialize the payload.","type":"System.Runtime.Serialization.SerializationException","stacktrace":" at System.Web.Http.OData.Formatter.Serialization.ODataFeedSerializer.WriteObject(Object graph, ODataMessageWriter messageWriter, ODataSerializerContext writeContext)\r\n at System.Web.Http.OData.Formatter.ODataMediaTypeFormatter.<>c__DisplayClass8.<WriteToStreamAsync>b__7()\r\n at System.Threading.Tasks.TaskHelpers.RunSynchronously(Action action, CancellationToken token)"
}
}
}
}
C# Class
namespace DataJsSpike.Models
{
public class Book
{
public string ISBN { get; set; }
public string Title { get; set; }
public string Author { get; set; }
public string Publisher { get; set; }
}
}
Javascript Code
// the URL of the first page to retrieve
var startPage = "api/Books";
var viewModel = new Object();
viewModel.books = ko.observable();
// On initialization, make a request for the first page
$(document).ready(function () {
LoadDataJs();
function LoadDataJs() {
OData.read(startPage, function (data) {
viewModel.books(data.results);
ko.applyBindings(viewModel);
});
}
});
Global.asax
public class WebApiApplication : HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
var modelBuilder = new ODataConventionModelBuilder();
EntityTypeConfiguration<Book> bookConfiguration = modelBuilder.Entity<Book>();
bookConfiguration.HasKey(x => x.ISBN);
modelBuilder.EntitySet<Book>("Books");
IEdmModel model = modelBuilder.GetEdmModel();
GlobalConfiguration.Configuration.EnableOData(model, "api");
}
}
EnableOData actually registers a route for you, but since you registered routes before it ran, those routes take precedence. If you remove this line:
RouteConfig.RegisterRoutes(RouteTable.Routes);
I think it should work out. The request needs to come in on an OData route for the OData formatting to work because the route parses the OData path and gives the formatter information about things like the Entity Set that's being accessed.

Is it fine if first response is private with AppCache (Symfony2)?

I'm trying to use http caching. In my controller I'm setting a response as follows:
$response->setPublic();
$response->setMaxAge(120);
$response->setSharedMaxAge(120);
$response->setLastModified($lastModifiedAt);
dev mode
In dev environment first response is a 200 with following headers:
cache-control:max-age=120, public, s-maxage=120
last-modified:Wed, 29 Feb 2012 19:00:00 GMT
For next 2 minutes every response is a 304 with following headers:
cache-control:max-age=120, public, s-maxage=120
This is basically what I expect it to be.
prod mode
In prod mode response headers are different. Note that in app.php I wrap the kernel in AppCache.
First response is a 200 with following headers:
cache-control:must-revalidate, no-cache, private
last-modified:Thu, 01 Mar 2012 11:17:35 GMT
So it's a private no-cache response.
Every next request is pretty much what I'd expect it to be; a 304 with following headers:
cache-control:max-age=120, public, s-maxage=120
Should I worry about it? Is it an expected behaviour?
What will happen if I put Varnish or Akamai server in front of it?
I did a bit of debugging and I figured that response is private because of last-modified header. HttpCache kernel uses EsiResponseCacheStrategy to update the cached response (HttpCache::handle() method).
if (HttpKernelInterface::MASTER_REQUEST === $type) {
$this->esiCacheStrategy->update($response);
}
EsiResponseCacheStrategy turns a response into non cacheable if it uses either Last-Response or ETag (EsiResponseCacheStrategy::add() method):
if ($response->isValidateable()) {
$this->cacheable = false;
} else {
// ...
}
Response::isValidateable() returns true if Last-Response or ETag header is present.
It results in overwriting the Cache-Control header (EsiResponseCacheStrategy::update() method):
if (!$this->cacheable) {
$response->headers->set('Cache-Control', 'no-cache, must-revalidate');
return;
}
I asked this question on Symfony2 user group but I didn't get an answer so far: https://groups.google.com/d/topic/symfony2/6lpln11POq8/discussion
Update.
Since I no longer have access to the original code I tried to reproduce the scenario with the latest Symfony standard edition.
Response headers are more consistent now, but still seem to be wrong.
As soon as I set a Last-Modified header on the response, the first response made by a browser has a:
Cache-Control:must-revalidate, no-cache, private
Second response has an expected:
Cache-Control:max-age=120, public, s-maxage=120
If I avoid sending If-Modified-Since header, every request returns must-revalidate, no-cache, private.
It doesn't matter if the request was made in prod or dev environment anymore.
I have faced same problem. I had to supply 'public' headers my cdn. By default when gateway caching is enabled in prod mode, it returns 200 OK with private, nocache must validate headers.
I solved problem this way.
In app.php, before I send response to user ($respond->send), I have overwritten the cache control header to blank and set cache headers to public and max age(some value).
//code snippet from app.php
$response = $kernel->handle($request);
$response->headers->set('Cache-Control', '');
$response->setPublic();
$response->setMaxAge(86400);
$response->send();
The behavior you experience is intended. Symfony2 Docs explicitly describe the situations when private and public are used, default being private.

Resources