-
Notifications
You must be signed in to change notification settings - Fork 475
Description
HttpRequestMessageExtensions.CreateRequestContainer
is not idempotent; if you call it more than once, it throws an exception. ODataPathRouteConstraint.Match
attempts to mitigate this by calling it inside a lambda:
path = GetODataPath(oDataPathValue as string, requestLeftPart, queryString, () => request.CreateRequestContainer(RouteName)); |
However, this doesn't help in the case where Match
is called more than once. This can happen if GetRouteData()
is called more than once, which indirectly calls all route constraints. This is necessary to work around this issue.
Assemblies affected
Microsoft.AspNet.OData 7.0.1
Reproduce steps
Call GetRouteData
before CorsMessageHandler
is called. See this project for an example.
Expected result
All tests in the repro would pass.
Actual result
Both tests that call OData endpoints fail with this stack trace:
Test Name: MakeNonCorsODataRequestExpectSuccess
Test Outcome: Failed
Result Message: Assert.AreEqual failed. Expected:<OK>. Actual:<InternalServerError>. Expected matching statuses for request GET http://localhost:9999/odata/$metadata
Result StandardOutput:
Running workaround...
{"Message":"An error has occurred.","ExceptionMessage":"A request container already exists on the request.","ExceptionType":"System.InvalidOperationException","StackTrace":" at Microsoft.AspNet.OData.Extensions.HttpRequestMessageExtensions.CreateRequestContainer(HttpRequestMessage request, String routeName)
at Microsoft.AspNet.OData.Routing.ODataPathRouteConstraint.<>c__DisplayClass0_0.<Match>b__0()
at Microsoft.AspNet.OData.Routing.ODataPathRouteConstraint.GetODataPath(String oDataPathString, String uriPathString, String queryString, Func`1 requestContainerFactory)
at Microsoft.AspNet.OData.Routing.ODataPathRouteConstraint.Match(HttpRequestMessage request, IHttpRoute route, String parameterName, IDictionary`2 values, HttpRouteDirection routeDirection)
at System.Web.Http.Routing.HttpRoute.ProcessConstraint(HttpRequestMessage request, Object constraint, String parameterName, HttpRouteValueDictionary values, HttpRouteDirection routeDirection)
at System.Web.Http.Routing.HttpRoute.ProcessConstraints(HttpRequestMessage request, HttpRouteValueDictionary values, HttpRouteDirection routeDirection)
at System.Web.Http.Routing.HttpRoute.GetRouteData(String virtualPathRoot, HttpRequestMessage request)
at System.Web.Http.HttpRouteCollection.GetRouteData(HttpRequestMessage request)
at System.Web.Http.Dispatcher.HttpRoutingDispatcher.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
at System.Net.Http.DelegatingHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
at System.Web.Http.Cors.CorsMessageHandler.<SendAsync>d__0.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
at ODataAndCorsClassic.WorkaroundHandler.<SendAsync>d__0.MoveNext() in E:\\github\\odata-repros\\ODataAndCorsClassic\\ODataAndCorsClassic\\Repro.cs:line 74
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Web.Http.HttpServer.<SendAsync>d__24.MoveNext()"}
Additional detail
There is a workaround for this issue, detailed here.
You need to replicate ODataPathRouteConstraint
and change the way Match
obtains the request container:
Then you need to manually create an ODataRoute
and replace the one that MapODataServiceRoute
builds for you:
In addition to fixing CreateRequestContainer
so it doesn't throw, it would also be helpful to be able to register a custom ODataPathRouteConstraint
without the hacky ODataRoute
workaround.