Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds dependency injection support in non-model scenario #439

Open
wants to merge 9 commits into
base: release-8.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 14 additions & 12 deletions src/Microsoft.AspNetCore.OData/Extensions/HttpRequestExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -271,12 +271,6 @@ public static IServiceProvider GetRouteServices(this HttpRequest request)
return requestContainer;
}

// if the prefixName == null, it's a non-model scenario
if (request.ODataFeature().RoutePrefix == null)
{
return null;
}

// HTTP routes will not have chance to call CreateRequestContainer. We have to call it.
return request.CreateRouteServices(request.ODataFeature().RoutePrefix);
}
Expand All @@ -300,6 +294,12 @@ public static IServiceProvider CreateRouteServices(this HttpRequest request, str
}

IServiceScope requestScope = request.CreateRequestScope(routePrefix);
if (requestScope == null)
{
// non-model scenario with dependency injection non enabled
return null;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

insert whiteline


IServiceProvider requestContainer = requestScope.ServiceProvider;

request.ODataFeature().RequestScope = requestScope;
Expand Down Expand Up @@ -341,15 +341,17 @@ private static IServiceScope CreateRequestScope(this HttpRequest request, string
{
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))
IServiceProvider rootContainer = options?.GetRouteServices(routePrefix);
if (rootContainer == null)
{
scope.ServiceProvider.GetRequiredService<HttpRequestScope>().HttpRequest = request;
return null;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

insert whiteline after {} block


IServiceScope scope = rootContainer.GetRequiredService<IServiceScopeFactory>().CreateScope();

// Bind scoping request into the OData container.
scope.ServiceProvider.GetRequiredService<HttpRequestScope>().HttpRequest = request;
kakone marked this conversation as resolved.
Show resolved Hide resolved

return scope;
}

Expand Down
17 changes: 16 additions & 1 deletion src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6160,6 +6160,21 @@
</summary>
<remarks>DO NOT modify this dictionary yourself. Instead, use the 'AddRouteComponents()` methods for registering model instances.</remarks>
</member>
<member name="M:Microsoft.AspNetCore.OData.ODataOptions.AddRouteComponents(System.Action{Microsoft.Extensions.DependencyInjection.IServiceCollection})">
<summary>
Configures service collection for non-EDM scenario
</summary>
<param name="configureServices">The sub service configuration action.</param>
<returns>The current <see cref="T:Microsoft.AspNetCore.OData.ODataOptions"/> instance to enable fluent configuration.</returns>
</member>
<member name="M:Microsoft.AspNetCore.OData.ODataOptions.AddRouteComponents(Microsoft.OData.ODataVersion,System.Action{Microsoft.Extensions.DependencyInjection.IServiceCollection})">
<summary>
Configures service collection for non-EDM scenario
</summary>
<param name="version">The OData version to be used.</param>
<param name="configureServices">The sub service configuration action.</param>
<returns>The current <see cref="T:Microsoft.AspNetCore.OData.ODataOptions"/> instance to enable fluent configuration.</returns>
</member>
<member name="M:Microsoft.AspNetCore.OData.ODataOptions.AddRouteComponents(Microsoft.OData.Edm.IEdmModel)">
<summary>
Adds an <see cref="T:Microsoft.OData.Edm.IEdmModel"/> to the default route.
Expand Down Expand Up @@ -6285,8 +6300,8 @@
Build the container.
</summary>
<param name="model">The Edm model.</param>
<param name="setupAction">The setup config.</param>
<param name="version">The OData version config.</param>
<param name="setupAction">The setup config.</param>
<returns>The built service provider.</returns>
</member>
<member name="M:Microsoft.AspNetCore.OData.ODataOptions.SanitizeRoutePrefix(System.String)">
Expand Down
43 changes: 34 additions & 9 deletions src/Microsoft.AspNetCore.OData/ODataOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ namespace Microsoft.AspNetCore.OData
public class ODataOptions
{
#region Settings
private IServiceProvider _serviceProvider;

/// <summary>
/// Gets or sets the <see cref="ODataUrlKeyDelimiter"/> to use while parsing, specifically
/// whether to recognize keys as segments or not.
Expand Down Expand Up @@ -61,7 +63,7 @@ public class ODataOptions
/// <summary>
/// Gets the <see cref="RouteOptions"/> instance responsible for configuring the route template.
/// </summary>
public ODataRouteOptions RouteOptions { get; } = new ODataRouteOptions();
public ODataRouteOptions RouteOptions { get; } = new ODataRouteOptions();

#endregion

Expand All @@ -73,6 +75,28 @@ public class ODataOptions
/// <remarks>DO NOT modify this dictionary yourself. Instead, use the 'AddRouteComponents()` methods for registering model instances.</remarks>
public IDictionary<string, (IEdmModel EdmModel, IServiceProvider ServiceProvider)> RouteComponents { get; } = new Dictionary<string, (IEdmModel, IServiceProvider)>();

/// <summary>
/// Configures service collection for non-EDM scenario
/// </summary>
/// <param name="configureServices">The sub service configuration action.</param>
/// <returns>The current <see cref="ODataOptions"/> instance to enable fluent configuration.</returns>
public ODataOptions AddRouteComponents(Action<IServiceCollection> configureServices)
{
return AddRouteComponents(ODataVersion.V4, configureServices);
}

/// <summary>
/// Configures service collection for non-EDM scenario
/// </summary>
/// <param name="version">The OData version to be used.</param>
/// <param name="configureServices">The sub service configuration action.</param>
/// <returns>The current <see cref="ODataOptions"/> instance to enable fluent configuration.</returns>
public ODataOptions AddRouteComponents(ODataVersion version, Action<IServiceCollection> configureServices)
{
_serviceProvider = BuildRouteContainer(null, version, configureServices);
return this;
}

/// <summary>
/// Adds an <see cref="IEdmModel"/> to the default route.
/// </summary>
Expand Down Expand Up @@ -171,7 +195,7 @@ public IServiceProvider GetRouteServices(string routePrefix)
{
if (routePrefix == null)
{
return null;
return _serviceProvider;
}

string sanitizedRoutePrefix = SanitizeRoutePrefix(routePrefix);
Expand Down Expand Up @@ -298,13 +322,11 @@ public ODataOptions SetMaxTop(int? maxTopValue)
/// Build the container.
/// </summary>
/// <param name="model">The Edm model.</param>
/// <param name="setupAction">The setup config.</param>
/// <param name="version">The OData version config.</param>
/// <param name="setupAction">The setup config.</param>
/// <returns>The built service provider.</returns>
private IServiceProvider BuildRouteContainer(IEdmModel model, ODataVersion version, Action<IServiceCollection> setupAction)
{
Contract.Assert(model != null);

ServiceCollection services = new ServiceCollection();
DefaultContainerBuilder builder = new DefaultContainerBuilder();

Expand All @@ -325,10 +347,13 @@ private IServiceProvider BuildRouteContainer(IEdmModel model, ODataVersion versi
EnableNoDollarQueryOptions = EnableNoDollarQueryOptions // retrieve it from global setting
});

// Inject the Edm model.
// From Current ODL implement, such injection only be used in reader and writer if the input
// model is null.
builder.Services.AddSingleton(sp => model);
if (model != null)
{
// Inject the Edm model.
// From Current ODL implement, such injection only be used in reader and writer if the input
// model is null.
builder.Services.AddSingleton(sp => model);
}

// Inject the customized services.
setupAction?.Invoke(builder.Services);
Expand Down
2 changes: 2 additions & 0 deletions src/Microsoft.AspNetCore.OData/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,8 @@ Microsoft.AspNetCore.OData.ODataMvcOptionsSetup
Microsoft.AspNetCore.OData.ODataMvcOptionsSetup.Configure(Microsoft.AspNetCore.Mvc.MvcOptions options) -> void
Microsoft.AspNetCore.OData.ODataMvcOptionsSetup.ODataMvcOptionsSetup() -> void
Microsoft.AspNetCore.OData.ODataOptions
Microsoft.AspNetCore.OData.ODataOptions.AddRouteComponents(System.Action<Microsoft.Extensions.DependencyInjection.IServiceCollection> configureServices) -> Microsoft.AspNetCore.OData.ODataOptions
Microsoft.AspNetCore.OData.ODataOptions.AddRouteComponents(Microsoft.OData.ODataVersion version, System.Action<Microsoft.Extensions.DependencyInjection.IServiceCollection> configureServices) -> Microsoft.AspNetCore.OData.ODataOptions
Microsoft.AspNetCore.OData.ODataOptions.AddRouteComponents(Microsoft.OData.Edm.IEdmModel model) -> Microsoft.AspNetCore.OData.ODataOptions
Microsoft.AspNetCore.OData.ODataOptions.AddRouteComponents(Microsoft.OData.Edm.IEdmModel model, Microsoft.AspNetCore.OData.Batch.ODataBatchHandler batchHandler) -> Microsoft.AspNetCore.OData.ODataOptions
Microsoft.AspNetCore.OData.ODataOptions.AddRouteComponents(string routePrefix, Microsoft.OData.Edm.IEdmModel model) -> Microsoft.AspNetCore.OData.ODataOptions
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>netcoreapp3.1;net5.0</TargetFrameworks>
<TargetFrameworks Condition="'$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' &gt;= '17.0'">$(TargetFrameworks);net6.0</TargetFrameworks>
<TargetFrameworks>netcoreapp3.1;net6.0</TargetFrameworks>
<AssemblyName>Microsoft.AspNetCore.OData.E2E.Tests</AssemblyName>
<RootNamespace>Microsoft.AspNetCore.OData.E2E.Tests</RootNamespace>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//-----------------------------------------------------------------------------
// <copyright file="CustomersController.cs" company=".NET Foundation">
// Copyright (c) .NET Foundation and Contributors. All rights reserved.
// See License.txt in the project root for license information.
// </copyright>
//------------------------------------------------------------------------------

using System.Linq;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.OData.Query;

namespace Microsoft.AspNetCore.OData.E2E.Tests.NonEdm
{
[ApiController]
[Route("api/[controller]")]
public class CustomersController : ControllerBase
{
[HttpGet]
public IActionResult Get(ODataQueryOptions<Customer> options)
{
return Ok(options.ApplyTo(NonEdmDbContext.GetCustomers().AsQueryable()));
}

[HttpGet("WithEnableQueryAttribute")]
[EnableQuery]
public IActionResult GetWithEnableQueryAttribute()
{
return Ok(NonEdmDbContext.GetCustomers());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//-----------------------------------------------------------------------------
// <copyright file="NonEdmDataModel.cs" company=".NET Foundation">
// Copyright (c) .NET Foundation and Contributors. All rights reserved.
// See License.txt in the project root for license information.
// </copyright>
//------------------------------------------------------------------------------

namespace Microsoft.AspNetCore.OData.E2E.Tests.NonEdm
{
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
public Gender Gender { get; set; }
}

public enum Gender
{
Male = 1,
Female = 2
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//-----------------------------------------------------------------------------
// <copyright file="NonEdmDbContext.cs" company=".NET Foundation">
// Copyright (c) .NET Foundation and Contributors. All rights reserved.
// See License.txt in the project root for license information.
// </copyright>
//------------------------------------------------------------------------------

using System.Collections.Generic;
using System.Linq;

namespace Microsoft.AspNetCore.OData.E2E.Tests.NonEdm
{
public class NonEdmDbContext
{
private static IList<Customer> _customers;

public static IList<Customer> GetCustomers()
{
if (_customers == null)
{
Generate();
}
return _customers;
}

private static void Generate()
{
_customers = Enumerable.Range(1, 10).Select(e =>
new Customer
{
Id = e,
Name = "Customer #" + e,
Gender = e%2 == 0 ? Gender.Female : Gender.Male,
}).ToList();
}
}

}
73 changes: 73 additions & 0 deletions test/Microsoft.AspNetCore.OData.E2E.Tests/NonEdm/NonEdmTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
//-----------------------------------------------------------------------------
// <copyright file="NonEdmTests.cs" company=".NET Foundation">
// Copyright (c) .NET Foundation and Contributors. All rights reserved.
// See License.txt in the project root for license information.
// </copyright>
//------------------------------------------------------------------------------

using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.OData.E2E.Tests.Extensions;
using Microsoft.AspNetCore.OData.TestCommon;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.OData.UriParser;
using Newtonsoft.Json.Linq;
using Xunit;

namespace Microsoft.AspNetCore.OData.E2E.Tests.NonEdm
{
public class NonEdmTests : WebApiTestBase<NonEdmTests>
{
public NonEdmTests(WebApiTestFixture<NonEdmTests> fixture)
: base(fixture)
{
}

protected static void UpdateConfigureServices(IServiceCollection services)
{
services.ConfigureControllers(typeof(CustomersController));
services.AddControllers().AddOData(opt =>
{
opt.EnableQueryFeatures();
opt.AddRouteComponents(services =>
{
services.AddSingleton<ODataUriResolver>(sp => new StringAsEnumResolver() { EnableCaseInsensitive = true });
});
});
}

[Fact]
public async Task NonEdmFilterByEnumString()
{
Assert.Equal(5, (await GetResponse<Customer[]>("$filter=Gender eq 'MaLe'")).Length);
}

[Fact]
public async Task NonEdmFilterByEnumStringWithEnableQueryAttribute()
{
Assert.Equal(5, (await GetResponse<Customer[]>("$filter=Gender eq 'MaLe'", "WithEnableQueryAttribute")).Length);
}

[Fact]
public async Task NonEdmSumFilteredByEnumString()
{
var response = await GetResponse<JArray>("$apply=filter(Gender eq 'female')/aggregate(Id with sum as Sum)");
Assert.Equal(30, response.Single()["Sum"].Value<int>());
}

[Fact]
public async Task NonEdmSelectTopFilteredByEnumString()
{
var response = await GetResponse<Customer[]>("$filter(Gender eq 'female')&$orderby=Id desc&$select=Name&$top=1&$skip=1");
Assert.Equal("Customer #9", response.Single().Name);
}

private async Task<T> GetResponse<T>(string queryOptions, string method = null)
{
using var response = await CreateClient().SendAsync(
new HttpRequestMessage(HttpMethod.Get, $"api/Customers/{method ?? string.Empty}?{queryOptions}"));
return await response.Content.ReadAsObject<T>();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ public class Microsoft.AspNetCore.OData.ODataOptions {
public Microsoft.AspNetCore.OData.ODataOptions AddRouteComponents (string routePrefix, Microsoft.OData.Edm.IEdmModel model)
public Microsoft.AspNetCore.OData.ODataOptions AddRouteComponents (string routePrefix, Microsoft.OData.Edm.IEdmModel model, Microsoft.AspNetCore.OData.Batch.ODataBatchHandler batchHandler)
public Microsoft.AspNetCore.OData.ODataOptions AddRouteComponents (string routePrefix, Microsoft.OData.Edm.IEdmModel model, System.Action`1[[Microsoft.Extensions.DependencyInjection.IServiceCollection]] configureServices)
public Microsoft.AspNetCore.OData.ODataOptions ConfigureServiceCollection (System.Action`1[[Microsoft.Extensions.DependencyInjection.IServiceCollection]] configureServices)
public Microsoft.AspNetCore.OData.ODataOptions Count ()
public Microsoft.AspNetCore.OData.ODataOptions EnableQueryFeatures (params System.Nullable`1[[System.Int32]] maxTopValue)
public Microsoft.AspNetCore.OData.ODataOptions Expand ()
Expand Down