I'm using a traditional WebAPI controller:
[Route("api/results/{query}")]
[AcceptVerbs("GET")]
public HttpResponseMessage GetQueryResults(string query)
{
var userAgent = Request.Headers.UserAgent;
var result = _fooService.GetResults(GetUsername(), query);
var response = Request.CreateResponse(HttpStatusCode.OK, result);
return response;
}
GetResults
returns an array of elements that looks like this:
[{
"resultId":2039016,
"text":null,
"dateCreated":"2020-09-10T02:24:36.003",
"targetPlatform":"FooBar"
}]
On most browsers, this works fine. My user agent header looks like this:
{Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36}
However when I debug from Chrome using the device toolbar, or when I visit my site from Safari on my iPhone, my user agent changes. From Chrome's device toolbar (the mobile simulator), it looks something like this:
{Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Mobile Safari/537.36}
And in this case, CheckInvalidPathChars
is invoked and fails against the JSON:
"type": "System.ArgumentException",
"message": "Illegal characters in path.",
"stackTrace": " at System.IO.Path.CheckInvalidPathChars(String path, Boolean checkAdditional)
at System.IO.Path.GetExtension(String path)
at System.Web.WebPages.DefaultDisplayMode.TransformPath(String virtualPath, String suffix)
at System.Web.WebPages.DefaultDisplayMode.GetDisplayInfo(HttpContextBase httpContext, String virtualPath, Func`2 virtualPathExists)
at System.Web.WebPages.DisplayModeProvider.GetDisplayInfoForVirtualPath(String virtualPath, HttpContextBase httpContext, Func`2 virtualPathExists, IDisplayMode currentDisplayMode, Boolean requireConsistentDisplayMode)
at System.Web.WebPages.WebPageRoute.GetRouteLevelMatch(String pathValue, String[] supportedExtensions, Func`2 virtualPathExists, HttpContextBase context, DisplayModeProvider displayModeProvider)
at System.Web.WebPages.WebPageRoute.MatchRequest(String pathValue, String[] supportedExtensions, Func`2 virtualPathExists, HttpContextBase context, DisplayModeProvider displayModes)
at System.Web.WebPages.WebPageRoute.DoPostResolveRequestCache(HttpContextBase context)
at System.Web.WebPages.WebPageHttpModule.OnApplicationPostResolveRequestCache(Object sender, EventArgs e)
at System.Web.HttpApplication.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
at System.Web.HttpApplication.ExecuteStepImpl(IExecutionStep step)
at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)"
I can reproduce this manually by attempting to parse the serialized JSON as a file path:
try
{
var isValid = System.IO.Path.GetExtension(jsonString);
}
catch (Exception e)
{
throw e;
}
But of course, attempting to parse a serialized JSON object as a file will throw errors. Why is ASP.NET modifying parsing behavior based on user agent headers?
Can I somehow override the inbound header from the Request
to coerce the framework towards functional behavior?
To be clear - I can invoke the controller from Chrome (standard) with no issues. When I invoke the controller with the same exact request from Chrome (with devtools open and the mobile simulator active), the exception is thrown. Likewise, when I invoke it with the same exact request from Safari on iPhone, the exception is thrown. The User Agent header is the independent variable in these cases - so it must follow that this header is somehow causing a different execution path to be invoked. Right?
The exception is thrown after all Controller logic has executed - the response
is returned by the controller when it's thrown.