Skip to content

CreateRequestContainer fails when called multiple times #1616

@brjohnstmsft

Description

@brjohnstmsft

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:

https://github.com/brjohnstmsft/odata-repros/blob/55437e152aa5eacffd822de6d9817d2ccc106d13/ODataAndCorsClassic/ODataAndCorsClassic/CustomODataPathRouteConstraint.cs#L208

https://github.com/brjohnstmsft/odata-repros/blob/55437e152aa5eacffd822de6d9817d2ccc106d13/ODataAndCorsClassic/ODataAndCorsClassic/CustomODataPathRouteConstraint.cs#L263

Then you need to manually create an ODataRoute and replace the one that MapODataServiceRoute builds for you:

https://github.com/brjohnstmsft/odata-repros/blob/55437e152aa5eacffd822de6d9817d2ccc106d13/ODataAndCorsClassic/ODataAndCorsClassic/Repro.cs#L109

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.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions