OData / AspNetCoreOData

ASP.NET Core OData: A server library built upon ODataLib and ASP.NET Core

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

HttpRequestScope.HttpRequest is not set when RoutePrefix is an empty string.

jusbuc2k opened this issue · comments

Assemblies affected
8.2.3.0

Describe the bug
HttpRequestScope.HttpRequest is not set when RoutePrefix is an empty string.

Reproduce steps
Create an OData route component with an empty string as the route prefix (not null).

Probable Cause

The bug is in this line of code. Assuming we are supposed to be able to use an empty string as a route prefix, which works everywhere else but here, this code is using string.IsNullOrEmpty when I think it should be using != null.

        /// <summary>
        /// Create a scoped request.
        /// </summary>
        /// <param name="request">The <see cref="HttpRequest"/> instance to extend.</param>
        /// <param name="routePrefix">The route prefix for this request. Should match an entry in <see cref="ODataOptions.RouteComponents"/>.</param>
        /// <returns></returns>
        private static IServiceScope CreateRequestScope(this HttpRequest request, string routePrefix)
        {
            ODataOptions options = request.ODataOptions();

            IServiceProvider rootContainer = options.GetRouteServices(routePrefix);
            IServiceScope scope = rootContainer.GetRequiredService<IServiceScopeFactory>().CreateScope();

            // Bind scoping request into the OData container.
            if (!string.IsNullOrEmpty(routePrefix))
            {
                scope.ServiceProvider.GetRequiredService<HttpRequestScope>().HttpRequest = request;
            }

            return scope;
        }

from

While the OData calls seem to work fine with a vanilla configuration, this causes a null reference exception in custom filter binders that I am building that depend on HttpRequestScope.HttpRequest because I need to get services from the HttpContext.RequestServices container. If there's a better way to do this, I can't find it.

Edit: I'm adding a simplified version of my Startup code to show AddRouteComponents

services.AddControllers().AddOData(options => 
{
    options.AddRouteComponents("", ODataModels.GetDefaultModel(100, 100), services =>
    {
        services.AddSingleton<IStreamBasedJsonWriterFactory>(_ => DefaultStreamBasedJsonWriterFactory.Default);
        services.AddSingleton<ODataBatchHandler, DefaultODataBatchHandler>();
        // this is a custom service that my custom filter binders depend on
        services.AddScoped<IOpenSchemaProvider>(sp =>
        {
            var requestScope = sp.GetRequiredService<HttpRequestScope>();
            if (requestScope.HttpRequest == null)
            {
                throw new Exception("HttpRequest is null on OData HttpRequestScope.");
            }

            var schemaProvider = requestScope
                .HttpRequest
                .HttpContext
                .RequestServices
                .GetRequiredService<IOpenSchemaProvider>();

            return schemaProvider;
        });
    });
});

It seems "non-sense" to me also to test "isNullOrEmpty".

By the way, why don't you use 'sp' directly to get the "IOpenSchemaProvider"?

The IOpenSchemaProvider implementation is registered with the ASP.NET service container. Unless I'm mistaken, I thought the service provider from which custom filter binders are created doesn't resolve services out of the ASP.NET one, but only the route services?