From 673d520ff193bf4ef57fc0399d9ad644977d2ab7 Mon Sep 17 00:00:00 2001 From: Vincent He Date: Mon, 22 Aug 2016 14:19:48 +0800 Subject: [PATCH] More changes for test cases --- src/Microsoft.Restier.Core/ApiBase.cs | 45 +++--- .../ApiConfiguratorAttribute.cs | 87 ----------- .../ApiConfiguratorAttributes.cs | 137 ----------------- .../ApiContextExtensions.cs | 5 +- .../ConventionBasedOperationAuthorizer.cs | 2 +- .../ConventionBasedOperationFilter.cs | 2 +- .../InvocationContext.cs | 12 ++ .../InvocationContextExtensions.cs | 23 ++- .../Microsoft.Restier.Core.csproj | 3 +- .../Model/ModelContext.cs | 11 -- .../Operation/OperationContext.cs | 31 +++- .../Properties/Resources.Designer.cs | 9 ++ .../Properties/Resources.resx | 3 + .../RestierContainerBuilder.cs | 142 ++++++++++++++++++ .../ServiceCollectionExtensions.cs | 52 +------ .../Submit/SubmitContext.cs | 5 - .../EntityFrameworkApi.cs | 1 - .../Model/ModelProducer.cs | 3 +- .../Batch/RestierBatchHandler.cs | 22 ++- .../Deserialization/DeserializationHelpers.cs | 54 +++---- .../DefaultRestierSerializerProvider.cs | 52 +++---- .../Model/RestierModelExtender.cs | 30 ++-- .../Operation/OperationExecutor.cs | 15 +- .../Query/RestierQueryBuilder.cs | 130 ++++++---------- .../RestierController.cs | 50 +++--- .../Routing/HttpConfigurationExtensions.cs | 66 +++----- .../ServiceCollectionExtensions.cs | 9 +- .../Microsoft.Restier.Core.Tests/Api.Tests.cs | 23 +-- .../ApiBase.Tests.cs | 70 +-------- .../ApiConfiguration.Tests.cs | 15 +- .../ApiContext.Tests.cs | 2 +- .../InvocationContext.Tests.cs | 4 +- .../Model/DefaultModelHandler.Tests.cs | 8 +- .../PropertyBag.Tests.cs | 6 +- .../ServiceConfiguration.Tests.cs | 20 +-- .../ChangeSetPreparerTests.cs | 2 +- .../Model/RestierModelBuilderTests.cs | 4 +- .../PublicApi.bsl | 92 +++++------- .../TestOrdersEntitySetAutoExpandQuery.txt | 2 +- .../ODataFeedTests.cs | 12 +- .../SaveTests.cs | 3 +- .../App_Start/WebApiConfig.cs | 2 + .../App_Start/WebApiConfig.cs | 5 + 43 files changed, 524 insertions(+), 747 deletions(-) delete mode 100644 src/Microsoft.Restier.Core/ApiConfiguratorAttribute.cs delete mode 100644 src/Microsoft.Restier.Core/ApiConfiguratorAttributes.cs create mode 100644 src/Microsoft.Restier.Core/RestierContainerBuilder.cs diff --git a/src/Microsoft.Restier.Core/ApiBase.cs b/src/Microsoft.Restier.Core/ApiBase.cs index 7bc5bd76..b92a446f 100644 --- a/src/Microsoft.Restier.Core/ApiBase.cs +++ b/src/Microsoft.Restier.Core/ApiBase.cs @@ -48,9 +48,6 @@ public ApiContext Context { apiScope.Api = this; } - - ApiConfiguratorAttributes.ApplyInitialization( - this.GetType(), this, this.apiContext); } return this.apiContext; @@ -63,9 +60,9 @@ public ApiContext Context public bool IsDisposed { get; private set; } /// - /// Gets the API configuration for this API. + /// Gets or sets the API configuration for this API. /// - protected ApiConfiguration Configuration + public ApiConfiguration Configuration { get { @@ -74,17 +71,18 @@ protected ApiConfiguration Configuration return this.apiConfiguration; } - return this.apiConfiguration = Configurations.GetOrAdd( - this.GetType(), - apiType => - { - IServiceCollection services = new ServiceCollection(); - services = this.ConfigureApi(services); + Configurations.TryGetValue(this.GetType(), out this.apiConfiguration); + return this.apiConfiguration; + } - var configuration = this.CreateApiConfiguration(services); - ApiConfiguratorAttributes.ApplyConfiguration(apiType, configuration); - return configuration; - }); + set + { + this.apiConfiguration = value; + bool isSuccess = Configurations.TryAdd(GetType(), apiConfiguration); + if (isSuccess) + { + UpdateApiConfiguration(this.apiConfiguration); + } } } @@ -103,9 +101,6 @@ public void Dispose() if (this.apiContext != null) { - ApiConfiguratorAttributes.ApplyDisposal( - this.GetType(), this, this.apiContext); - this.apiContext.DisposeScope(); this.apiContext = null; } @@ -121,13 +116,12 @@ public void Dispose() /// /// The . [CLSCompliant(false)] - protected virtual IServiceCollection ConfigureApi(IServiceCollection services) + public virtual IServiceCollection ConfigureApi(IServiceCollection services) { Type apiType = this.GetType(); // Add core and convention's services services = services.AddCoreServices(apiType) - .AddAttributeServices(apiType) .AddConventionBasedServices(apiType); // This is used to add the publisher's services @@ -137,18 +131,13 @@ protected virtual IServiceCollection ConfigureApi(IServiceCollection services) } /// - /// Creates the API configuration for this API. - /// Descendants may override to use a customized DI container, or further configure the built + /// Allow user to update the ApiConfiguration /// . /// - /// The containing API service registrations. - /// - /// An with which to create the API configuration for this API. - /// + /// The for the Api instance. [CLSCompliant(false)] - protected virtual ApiConfiguration CreateApiConfiguration(IServiceCollection services) + protected virtual void UpdateApiConfiguration(ApiConfiguration configuration) { - return services.BuildApiConfiguration(); } /// diff --git a/src/Microsoft.Restier.Core/ApiConfiguratorAttribute.cs b/src/Microsoft.Restier.Core/ApiConfiguratorAttribute.cs deleted file mode 100644 index 075fbfad..00000000 --- a/src/Microsoft.Restier.Core/ApiConfiguratorAttribute.cs +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -using System; -using System.Linq; -using Microsoft.Extensions.DependencyInjection; - -namespace Microsoft.Restier.Core -{ - /// - /// Specifies a set of methods that can participate in the - /// configuration, initialization and disposal of an API. - /// - [Serializable] - [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] - public abstract class ApiConfiguratorAttribute : Attribute - { - /// - /// Add Api services into the DI container. - /// - /// - /// The Api services registration. - /// - /// - /// The Api type on which this attribute was placed. - /// - [CLSCompliant(false)] - public virtual void AddApiServices( - IServiceCollection services, - Type type) - { - } - - /// - /// Update an Api configuration after ApiConfiguration is created. - /// - /// - /// An Api configuration. - /// - /// - /// The Api type on which this attribute was placed. - /// - public virtual void UpdateApiConfiguration( - ApiConfiguration configuration, - Type type) - { - } - - /// - /// Update an Api context after ApiContext is created. - /// - /// - /// An Api context. - /// - /// - /// The Api type on which this attribute was placed. - /// - /// - /// An Api instance, if applicable. - /// - public virtual void UpdateApiContext( - ApiContext context, - Type type, - object instance) - { - } - - /// - /// Disposes an Api context. - /// - /// - /// An Api context. - /// - /// - /// The Api type on which this attribute was placed. - /// - /// - /// An Api instance, if applicable. - /// - public virtual void Dispose( - ApiContext context, - Type type, - object instance) - { - } - } -} diff --git a/src/Microsoft.Restier.Core/ApiConfiguratorAttributes.cs b/src/Microsoft.Restier.Core/ApiConfiguratorAttributes.cs deleted file mode 100644 index 87a2e702..00000000 --- a/src/Microsoft.Restier.Core/ApiConfiguratorAttributes.cs +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -using System; -using System.Linq; -using Microsoft.Extensions.DependencyInjection; - -namespace Microsoft.Restier.Core -{ - /// - /// Specifies a set of methods that can initialize, configure, add service, dispose for all attributes. - /// - internal static class ApiConfiguratorAttributes - { - /// - /// Applies configuration from any API configurator attributes - /// specified on an API type to an API services. - /// - /// - /// An API type. - /// - /// - /// The API services registration. - /// - public static void AddApiServices( - Type type, IServiceCollection services) - { - Ensure.NotNull(type, "type"); - Ensure.NotNull(services, "services"); - if (type.BaseType != null) - { - AddApiServices( - type.BaseType, services); - } - - var attributes = type.GetCustomAttributes( - typeof(ApiConfiguratorAttribute), false); - foreach (ApiConfiguratorAttribute attribute in attributes) - { - attribute.AddApiServices(services, type); - } - } - - /// - /// Applies configuration from any API configurator attributes - /// specified on an API type to an API configuration. - /// - /// - /// An API type. - /// - /// - /// An API configuration. - /// - public static void ApplyConfiguration( - Type type, ApiConfiguration configuration) - { - Ensure.NotNull(type, "type"); - Ensure.NotNull(configuration, "configuration"); - if (type.BaseType != null) - { - ApplyConfiguration( - type.BaseType, configuration); - } - - var attributes = type.GetCustomAttributes( - typeof(ApiConfiguratorAttribute), false); - foreach (ApiConfiguratorAttribute attribute in attributes) - { - attribute.UpdateApiConfiguration(configuration, type); - } - } - - /// - /// Applies initialization routines from any API configurator - /// attributes specified on an API type to an API context. - /// - /// - /// An API type. - /// - /// - /// An API instance, if applicable. - /// - /// - /// An API context. - /// - public static void ApplyInitialization( - Type type, object instance, ApiContext context) - { - Ensure.NotNull(type, "type"); - Ensure.NotNull(context, "context"); - if (type.BaseType != null) - { - ApplyInitialization( - type.BaseType, instance, context); - } - - var attributes = type.GetCustomAttributes( - typeof(ApiConfiguratorAttribute), false); - foreach (ApiConfiguratorAttribute attribute in attributes) - { - attribute.UpdateApiContext(context, type, instance); - } - } - - /// - /// Applies disposal routines from any API configurator - /// attributes specified on an API type to an API context. - /// - /// - /// An API type. - /// - /// - /// An API instance, if applicable. - /// - /// - /// An API context. - /// - public static void ApplyDisposal( - Type type, object instance, ApiContext context) - { - Ensure.NotNull(type, "type"); - Ensure.NotNull(context, "context"); - var attributes = type.GetCustomAttributes( - typeof(ApiConfiguratorAttribute), false); - foreach (ApiConfiguratorAttribute attribute in attributes.Reverse()) - { - attribute.Dispose(context, type, instance); - } - - if (type.BaseType != null) - { - ApplyDisposal( - type.BaseType, instance, context); - } - } - } -} diff --git a/src/Microsoft.Restier.Core/ApiContextExtensions.cs b/src/Microsoft.Restier.Core/ApiContextExtensions.cs index 24383438..89d1762a 100644 --- a/src/Microsoft.Restier.Core/ApiContextExtensions.cs +++ b/src/Microsoft.Restier.Core/ApiContextExtensions.cs @@ -200,7 +200,8 @@ public static async Task GetModelAsync( try { - var buildContext = new ModelContext(context); + var buildContext = new ModelContext(); + buildContext.ServiceProvider = context.ServiceProvider; var model = await builder.GetModelAsync(buildContext, cancellationToken); source.SetResult(model); return model; @@ -461,8 +462,6 @@ public static async Task SubmitAsync( Ensure.NotNull(context, "context"); var submitContext = new SubmitContext(context, changeSet); - var model = await context.GetModelAsync(cancellationToken); - submitContext.Model = model; return await DefaultSubmitHandler.SubmitAsync(submitContext, cancellationToken); } diff --git a/src/Microsoft.Restier.Core/Conventions/ConventionBasedOperationAuthorizer.cs b/src/Microsoft.Restier.Core/Conventions/ConventionBasedOperationAuthorizer.cs index 644374e7..15bb8352 100644 --- a/src/Microsoft.Restier.Core/Conventions/ConventionBasedOperationAuthorizer.cs +++ b/src/Microsoft.Restier.Core/Conventions/ConventionBasedOperationAuthorizer.cs @@ -51,7 +51,7 @@ public Task AuthorizeAsync( object target = null; if (!method.IsStatic) { - target = context.GetApiService(); + target = context.ImplementInstance; if (target == null || !this.targetType.IsInstanceOfType(target)) { diff --git a/src/Microsoft.Restier.Core/Conventions/ConventionBasedOperationFilter.cs b/src/Microsoft.Restier.Core/Conventions/ConventionBasedOperationFilter.cs index 1ee33662..290e6b0b 100644 --- a/src/Microsoft.Restier.Core/Conventions/ConventionBasedOperationFilter.cs +++ b/src/Microsoft.Restier.Core/Conventions/ConventionBasedOperationFilter.cs @@ -80,7 +80,7 @@ private Task InvokeProcessorMethodAsync( object target = null; if (!method.IsStatic) { - target = context.GetApiService(); + target = context.ImplementInstance; if (target == null || !this.targetType.IsInstanceOfType(target)) { diff --git a/src/Microsoft.Restier.Core/InvocationContext.cs b/src/Microsoft.Restier.Core/InvocationContext.cs index 7d2d068e..753caadd 100644 --- a/src/Microsoft.Restier.Core/InvocationContext.cs +++ b/src/Microsoft.Restier.Core/InvocationContext.cs @@ -15,6 +15,13 @@ namespace Microsoft.Restier.Core /// public class InvocationContext { + /// + /// Initializes a new instance of the class. + /// + public InvocationContext() + { + } + /// /// Initializes a new instance of the class. /// @@ -32,5 +39,10 @@ public InvocationContext(ApiContext apiContext) /// Gets the API context. /// public ApiContext ApiContext { get; private set; } + + /// + /// Gets or sets the which contains all services of this scope. + /// + public IServiceProvider ServiceProvider { get; set; } } } diff --git a/src/Microsoft.Restier.Core/InvocationContextExtensions.cs b/src/Microsoft.Restier.Core/InvocationContextExtensions.cs index 41c30eb3..9083697e 100644 --- a/src/Microsoft.Restier.Core/InvocationContextExtensions.cs +++ b/src/Microsoft.Restier.Core/InvocationContextExtensions.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using Microsoft.Extensions.DependencyInjection; namespace Microsoft.Restier.Core { @@ -26,7 +27,16 @@ public static class InvocationContextExtensions public static T GetApiService(this InvocationContext context) where T : class { Ensure.NotNull(context, "context"); - return context.ApiContext.GetApiService(); + if (context.ServiceProvider != null) + { + return context.ServiceProvider.GetService(); + } + else if (context.ApiContext != null) + { + return context.ApiContext.GetApiService(); + } + + return null; } /// @@ -40,7 +50,16 @@ public static T GetApiService(this InvocationContext context) where T : class public static IEnumerable GetApiServices(this InvocationContext context) where T : class { Ensure.NotNull(context, "context"); - return context.ApiContext.GetApiServices(); + if (context.ServiceProvider != null) + { + return context.ServiceProvider.GetServices(); + } + else if (context.ApiContext != null) + { + return context.ApiContext.GetApiServices(); + } + + return null; } #endregion diff --git a/src/Microsoft.Restier.Core/Microsoft.Restier.Core.csproj b/src/Microsoft.Restier.Core/Microsoft.Restier.Core.csproj index 1632e1c5..99868f3d 100644 --- a/src/Microsoft.Restier.Core/Microsoft.Restier.Core.csproj +++ b/src/Microsoft.Restier.Core/Microsoft.Restier.Core.csproj @@ -33,6 +33,7 @@ + @@ -62,7 +63,6 @@ True SharedResources.resx - @@ -77,7 +77,6 @@ - diff --git a/src/Microsoft.Restier.Core/Model/ModelContext.cs b/src/Microsoft.Restier.Core/Model/ModelContext.cs index e065d600..6a9d3c06 100644 --- a/src/Microsoft.Restier.Core/Model/ModelContext.cs +++ b/src/Microsoft.Restier.Core/Model/ModelContext.cs @@ -12,17 +12,6 @@ namespace Microsoft.Restier.Core.Model /// public class ModelContext : InvocationContext { - /// - /// Initializes a new instance of the class. - /// - /// - /// An API context. - /// - public ModelContext(ApiContext apiContext) - : base(apiContext) - { - } - /// /// Gets or sets resource set and resource type map dictionary, it will be used by publisher for model build. /// diff --git a/src/Microsoft.Restier.Core/Operation/OperationContext.cs b/src/Microsoft.Restier.Core/Operation/OperationContext.cs index 841a3fff..31495c1e 100644 --- a/src/Microsoft.Restier.Core/Operation/OperationContext.cs +++ b/src/Microsoft.Restier.Core/Operation/OperationContext.cs @@ -4,7 +4,7 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Linq; +using System.Net.Http; namespace Microsoft.Restier.Core.Operation { @@ -15,6 +15,7 @@ namespace Microsoft.Restier.Core.Operation public class OperationContext : InvocationContext { private readonly string operationName; + private readonly object implementInstance; private readonly Func getParameterValueFunc; private readonly bool isFunction; private readonly IEnumerable bindingParameterValue; @@ -23,15 +24,15 @@ public class OperationContext : InvocationContext /// /// Initializes a new instance of the class. /// - /// - /// An API context. - /// /// /// The function that used to retrieve the parameter value name. /// /// /// The operation name. /// + /// + /// The instance which has the implementation of the operation and used for reflection call + /// /// /// A flag indicates this is a function call or action call. /// @@ -39,15 +40,16 @@ public class OperationContext : InvocationContext /// A queryable for binding parameter value and if it is function/action import, the value will be null. /// public OperationContext( - ApiContext apiContext, Func getParameterValueFunc, string operationName, + object implementInstance, bool isFunction, IEnumerable bindingParameterValue) - : base(apiContext) + : base() { this.getParameterValueFunc = getParameterValueFunc; this.operationName = operationName; + this.implementInstance = implementInstance; this.isFunction = isFunction; this.bindingParameterValue = bindingParameterValue; } @@ -63,6 +65,17 @@ public string OperationName } } + /// + /// Gets the instance have implemented the operation and used for reflection call. + /// + public object ImplementInstance + { + get + { + return this.implementInstance; + } + } + /// /// Gets the function that used to retrieve the parameter value name. /// @@ -113,5 +126,11 @@ public ICollection ParameterValues this.parameterValues = value; } } + + /// + /// Gets or sets the http request for this operation call + /// TODO consider moving to base class after more investigation + /// + public HttpRequestMessage Request { get; set; } } } diff --git a/src/Microsoft.Restier.Core/Properties/Resources.Designer.cs b/src/Microsoft.Restier.Core/Properties/Resources.Designer.cs index 1b0074f2..21639102 100644 --- a/src/Microsoft.Restier.Core/Properties/Resources.Designer.cs +++ b/src/Microsoft.Restier.Core/Properties/Resources.Designer.cs @@ -60,6 +60,15 @@ internal Resources() { } } + /// + /// Looks up a localized string similar to The argument with name {0} can not be null.. + /// + internal static string ArgumentCanNotBeNull { + get { + return ResourceManager.GetString("ArgumentCanNotBeNull", resourceCulture); + } + } + /// /// Looks up a localized string similar to Calling the methods in 'QueryableSource' or 'QueryableSource<T>' is not supported.. /// diff --git a/src/Microsoft.Restier.Core/Properties/Resources.resx b/src/Microsoft.Restier.Core/Properties/Resources.resx index 62490383..297246fa 100644 --- a/src/Microsoft.Restier.Core/Properties/Resources.resx +++ b/src/Microsoft.Restier.Core/Properties/Resources.resx @@ -219,4 +219,7 @@ The precondition check for request {0} on resource {1} is failed. + + The argument with name {0} can not be null. + \ No newline at end of file diff --git a/src/Microsoft.Restier.Core/RestierContainerBuilder.cs b/src/Microsoft.Restier.Core/RestierContainerBuilder.cs new file mode 100644 index 00000000..f913606d --- /dev/null +++ b/src/Microsoft.Restier.Core/RestierContainerBuilder.cs @@ -0,0 +1,142 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Globalization; +using System.Threading; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData; +using Microsoft.OData.Edm; +using ServiceLifetime = Microsoft.OData.ServiceLifetime; + +namespace Microsoft.Restier.Core +{ + /// + /// The default container builder implementation based on the Microsoft dependency injection framework. + /// + public class RestierContainerBuilder : IContainerBuilder + { + private readonly IServiceCollection services = new ServiceCollection(); + private Func apiFactory; + + /// + /// Initializes a new instance of the class. + /// + /// The Api factory to create the Api instance + public RestierContainerBuilder(Func apiFactory) + { + this.apiFactory = apiFactory; + } + + /// + /// Adds a service of with an . + /// + /// The lifetime of the service to register. + /// The type of the service to register. + /// The implementation type of the service. + /// The instance itself. + public virtual IContainerBuilder AddService( + ServiceLifetime lifetime, + Type serviceType, + Type implementationType) + { + if (serviceType == null) + { + throw new ArgumentException(string.Format( + CultureInfo.InvariantCulture, Resources.ArgumentCanNotBeNull, "serviceType")); + } + + if (implementationType == null) + { + throw new ArgumentException(string.Format( + CultureInfo.InvariantCulture, Resources.ArgumentCanNotBeNull, "implementationType")); + } + + services.Add(new ServiceDescriptor( + serviceType, implementationType, TranslateServiceLifetime(lifetime))); + + return this; + } + + /// + /// Adds a service of with an . + /// + /// The lifetime of the service to register. + /// The type of the service to register. + /// The factory that creates the service. + /// The instance itself. + public IContainerBuilder AddService( + ServiceLifetime lifetime, + Type serviceType, + Func implementationFactory) + { + if (serviceType == null) + { + throw new ArgumentException(string.Format( + CultureInfo.InvariantCulture, Resources.ArgumentCanNotBeNull, "serviceType")); + } + + if (implementationFactory == null) + { + throw new ArgumentException(string.Format( + CultureInfo.InvariantCulture, Resources.ArgumentCanNotBeNull, "implementationFactory")); + } + + services.Add(new ServiceDescriptor( + serviceType, implementationFactory, TranslateServiceLifetime(lifetime))); + + return this; + } + + /// + /// Builds a container which implements and contains + /// all the services registered. + /// + /// The container built by this builder. + public virtual IServiceProvider BuildContainer() + { + AddRestierService(); + return services.BuildServiceProvider(); + } + + internal IContainerBuilder AddRestierService() + { + Func modelFactory = sp => + { + using (var api = apiFactory()) + { + var configuation = sp.GetService(); + if (api.Configuration == null) + { + api.Configuration = configuation; + } + + var model = api.Context.GetModelAsync(default(CancellationToken)).Result; + return model; + } + }; + + using (var api = apiFactory()) + { + api.ConfigureApi(services); + } + + services.AddSingleton(modelFactory); + return this; + } + + private static Microsoft.Extensions.DependencyInjection.ServiceLifetime TranslateServiceLifetime( + ServiceLifetime lifetime) + { + switch (lifetime) + { + case ServiceLifetime.Scoped: + return Microsoft.Extensions.DependencyInjection.ServiceLifetime.Scoped; + case ServiceLifetime.Singleton: + return Microsoft.Extensions.DependencyInjection.ServiceLifetime.Singleton; + default: + return Microsoft.Extensions.DependencyInjection.ServiceLifetime.Transient; + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.Restier.Core/ServiceCollectionExtensions.cs b/src/Microsoft.Restier.Core/ServiceCollectionExtensions.cs index f32bf30a..45269913 100644 --- a/src/Microsoft.Restier.Core/ServiceCollectionExtensions.cs +++ b/src/Microsoft.Restier.Core/ServiceCollectionExtensions.cs @@ -219,28 +219,12 @@ public static IServiceCollection AddCoreServices(this IServiceCollection service .AddScoped(sp => sp.GetService().Api.Context); } + services.TryAddSingleton(); + return services.AddService() .AddScoped(); } - /// - /// Add services of enabled attributes. - /// - /// - /// The containing API service registrations. - /// - /// - /// The type of a class on which code-based conventions are used. - /// - /// Current - public static IServiceCollection AddAttributeServices(this IServiceCollection services, Type apiType) - { - Ensure.NotNull(apiType, "apiType"); - - ApiConfiguratorAttributes.AddApiServices(apiType, services); - return services; - } - /// /// Enables code-based conventions for an API. /// @@ -264,38 +248,6 @@ public static IServiceCollection AddConventionBasedServices(this IServiceCollect return services; } - /// - /// Build the - /// - /// The . - /// The built - internal static ApiConfiguration BuildApiConfiguration(this IServiceCollection services) - { - return services.BuildApiConfiguration(null); - } - - /// - /// Build the - /// - /// The . - /// - /// An optional factory to create an . - /// Use this to inject your favorite DI container. - /// - /// The built - internal static ApiConfiguration BuildApiConfiguration( - this IServiceCollection services, - Func serviceProviderFactory) - { - Ensure.NotNull(services, "services"); - - services.TryAddSingleton(); - - var serviceProvider = serviceProviderFactory != null ? - serviceProviderFactory(services) : services.BuildServiceProvider(); - return serviceProvider.GetService(); - } - private static IServiceCollection AddContributorNoCheck( this IServiceCollection services, ApiServiceContributor contributor) diff --git a/src/Microsoft.Restier.Core/Submit/SubmitContext.cs b/src/Microsoft.Restier.Core/Submit/SubmitContext.cs index 92805ad8..fc2219d1 100644 --- a/src/Microsoft.Restier.Core/Submit/SubmitContext.cs +++ b/src/Microsoft.Restier.Core/Submit/SubmitContext.cs @@ -29,11 +29,6 @@ public SubmitContext(ApiContext apiContext, ChangeSet changeSet) this.changeSet = changeSet; } - /// - /// Gets the model that informs this submit context. - /// - public IEdmModel Model { get; internal set; } - /// /// Gets or sets the change set. /// diff --git a/src/Microsoft.Restier.Providers.EntityFramework/EntityFrameworkApi.cs b/src/Microsoft.Restier.Providers.EntityFramework/EntityFrameworkApi.cs index b8a3ff2e..48294fc2 100644 --- a/src/Microsoft.Restier.Providers.EntityFramework/EntityFrameworkApi.cs +++ b/src/Microsoft.Restier.Providers.EntityFramework/EntityFrameworkApi.cs @@ -58,7 +58,6 @@ public override IServiceCollection ConfigureApi(IServiceCollection services) // Add core and convention's services services = services.AddCoreServices(apiType) - .AddAttributeServices(apiType) .AddConventionBasedServices(apiType); // Add EF related services diff --git a/src/Microsoft.Restier.Providers.EntityFramework/Model/ModelProducer.cs b/src/Microsoft.Restier.Providers.EntityFramework/Model/ModelProducer.cs index a94963d8..75d0e908 100644 --- a/src/Microsoft.Restier.Providers.EntityFramework/Model/ModelProducer.cs +++ b/src/Microsoft.Restier.Providers.EntityFramework/Model/ModelProducer.cs @@ -12,6 +12,7 @@ using System.Reflection; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; #if EF7 using Microsoft.EntityFrameworkCore; #endif @@ -58,7 +59,7 @@ public Task GetModelAsync(ModelContext context, CancellationToken can #else var resourceSetTypeMap = new Dictionary(); var resourceTypeKeyPropertiesMap = new Dictionary>(); - var dbContext = context.ApiContext.GetApiService(); + var dbContext = context.ServiceProvider.GetService(); var efModel = (dbContext as IObjectContextAdapter).ObjectContext.MetadataWorkspace; var efEntityContainer = efModel.GetItems(DataSpace.CSpace).Single(); diff --git a/src/Microsoft.Restier.Publishers.OData/Batch/RestierBatchHandler.cs b/src/Microsoft.Restier.Publishers.OData/Batch/RestierBatchHandler.cs index d0542aee..a6e4d992 100644 --- a/src/Microsoft.Restier.Publishers.OData/Batch/RestierBatchHandler.cs +++ b/src/Microsoft.Restier.Publishers.OData/Batch/RestierBatchHandler.cs @@ -9,7 +9,9 @@ using System.Web.Http; using System.Web.Http.Batch; using System.Web.OData.Batch; -using Microsoft.OData.Core; +using System.Web.OData.Extensions; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData; using Microsoft.Restier.Core; namespace Microsoft.Restier.Publishers.OData.Batch @@ -52,15 +54,11 @@ public override async Task> ParseBatchRequestsAsync Ensure.NotNull(request, "request"); - ODataMessageReaderSettings readerSettings = new ODataMessageReaderSettings - { - DisableMessageStreamDisposal = true, - MessageQuotas = MessageQuotas, - BaseUri = GetBaseUri(request) - }; + IServiceProvider requestContainer = request.CreateRequestContainer(ODataRouteName); + requestContainer.GetRequiredService().BaseUri = GetBaseUri(request); - ODataMessageReader reader = - await request.Content.GetODataMessageReaderAsync(readerSettings, cancellationToken); + ODataMessageReader reader + = await request.Content.GetODataMessageReaderAsync(requestContainer, cancellationToken); request.RegisterForDispose(reader); List requests = new List(); @@ -75,6 +73,7 @@ public override async Task> ParseBatchRequestsAsync foreach (HttpRequestMessage changeSetRequest in changeSetRequests) { changeSetRequest.CopyBatchRequestProperties(request); + changeSetRequest.DeleteRequestContainer(false); } requests.Add(this.CreateRestierBatchChangeSetRequestItem(changeSetRequests)); @@ -82,10 +81,9 @@ public override async Task> ParseBatchRequestsAsync else if (batchReader.State == ODataBatchReaderState.Operation) { HttpRequestMessage operationRequest = await batchReader.ReadOperationRequestAsync( - batchId, - bufferContentStream: true, - cancellationToken: cancellationToken); + batchId, true, cancellationToken); operationRequest.CopyBatchRequestProperties(request); + operationRequest.DeleteRequestContainer(false); requests.Add(new OperationRequestItem(operationRequest)); } } diff --git a/src/Microsoft.Restier.Publishers.OData/Formatter/Deserialization/DeserializationHelpers.cs b/src/Microsoft.Restier.Publishers.OData/Formatter/Deserialization/DeserializationHelpers.cs index d2d1918f..2d912dbc 100644 --- a/src/Microsoft.Restier.Publishers.OData/Formatter/Deserialization/DeserializationHelpers.cs +++ b/src/Microsoft.Restier.Publishers.OData/Formatter/Deserialization/DeserializationHelpers.cs @@ -4,10 +4,12 @@ using System; using System.Collections.Generic; using System.Linq.Expressions; +using System.Net.Http; +using System.Web.OData.Formatter; using System.Web.OData.Formatter.Deserialization; -using Microsoft.OData.Core; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData; using Microsoft.OData.Edm; -using Microsoft.Restier.Core; namespace Microsoft.Restier.Publishers.OData.Formatter { @@ -18,38 +20,22 @@ internal static class DeserializationHelpers { internal static object ConvertValue( object odataValue, + string parameterName, Type expectedReturnType, IEdmTypeReference propertyType, IEdmModel model, - ApiContext apiContext) + HttpRequestMessage request, + IServiceProvider serviceProvider) { - ODataDeserializerContext readContext = new ODataDeserializerContext + var readContext = new ODataDeserializerContext { - Model = model + Model = model, + Request = request }; - ODataDeserializerProvider deserializerProvider = apiContext.GetApiService(); - - if (odataValue == null) - { - return null; - } - - ODataNullValue nullValue = odataValue as ODataNullValue; - if (nullValue != null) - { - return null; - } - - ODataComplexValue complexValue = odataValue as ODataComplexValue; - if (complexValue != null) - { - ODataEdmTypeDeserializer deserializer - = deserializerProvider.GetEdmTypeDeserializer(propertyType.AsComplex()); - return deserializer.ReadInline(complexValue, propertyType, readContext); - } - - ODataEnumValue enumValue = odataValue as ODataEnumValue; + // Enum logic can be removed after RestierEnumDeserializer extends ODataEnumDeserializer + var deserializerProvider = serviceProvider.GetService(); + var enumValue = odataValue as ODataEnumValue; if (enumValue != null) { ODataEdmTypeDeserializer deserializer @@ -57,17 +43,15 @@ ODataEdmTypeDeserializer deserializer return deserializer.ReadInline(enumValue, propertyType, readContext); } - ODataCollectionValue collection = odataValue as ODataCollectionValue; - if (collection != null) - { - ODataEdmTypeDeserializer deserializer - = deserializerProvider.GetEdmTypeDeserializer(propertyType as IEdmCollectionTypeReference); - var collectionResult = deserializer.ReadInline(collection, propertyType, readContext); + var returnValue = ODataModelBinderConverter.Convert( + odataValue, propertyType, expectedReturnType, parameterName, readContext, serviceProvider); - return ConvertCollectionType(collectionResult, expectedReturnType); + if (!propertyType.IsCollection()) + { + return returnValue; } - return odataValue; + return ConvertCollectionType(returnValue, expectedReturnType); } internal static object ConvertCollectionType(object collectionResult, Type expectedReturnType) diff --git a/src/Microsoft.Restier.Publishers.OData/Formatter/Serialization/DefaultRestierSerializerProvider.cs b/src/Microsoft.Restier.Publishers.OData/Formatter/Serialization/DefaultRestierSerializerProvider.cs index b1fc3ff8..5f6801cc 100644 --- a/src/Microsoft.Restier.Publishers.OData/Formatter/Serialization/DefaultRestierSerializerProvider.cs +++ b/src/Microsoft.Restier.Publishers.OData/Formatter/Serialization/DefaultRestierSerializerProvider.cs @@ -3,7 +3,9 @@ using System; using System.Net.Http; +using System.Web.OData; using System.Web.OData.Formatter.Serialization; +using Microsoft.Extensions.DependencyInjection; using Microsoft.OData.Edm; namespace Microsoft.Restier.Publishers.OData.Formatter @@ -13,56 +15,41 @@ namespace Microsoft.Restier.Publishers.OData.Formatter /// public class DefaultRestierSerializerProvider : DefaultODataSerializerProvider { - private static readonly DefaultRestierSerializerProvider SingletonInstanceField - = new DefaultRestierSerializerProvider(); - - private RestierFeedSerializer feedSerializer; + private RestierResourceSetSerializer resourceSetSerializer; private RestierPrimitiveSerializer primitiveSerializer; private RestierRawSerializer rawSerializer; - private RestierComplexTypeSerializer complexTypeSerializer; + private RestierResourceSerializer resourceSerializer; private RestierCollectionSerializer collectionSerializer; private RestierEnumSerializer enumSerializer; /// /// Initializes a new instance of the class. /// - public DefaultRestierSerializerProvider() + /// The container to get the service + public DefaultRestierSerializerProvider(IServiceProvider rootContainer) : base(rootContainer) { - this.feedSerializer = new RestierFeedSerializer(this); + this.resourceSetSerializer = new RestierResourceSetSerializer(this); this.primitiveSerializer = new RestierPrimitiveSerializer(); this.rawSerializer = new RestierRawSerializer(); - this.complexTypeSerializer = new RestierComplexTypeSerializer(this); + this.resourceSerializer = new RestierResourceSerializer(this); this.collectionSerializer = new RestierCollectionSerializer(this); - this.enumSerializer = new RestierEnumSerializer(); - } - - /// - /// Gets the default instance of the . - /// - internal static DefaultRestierSerializerProvider SingletonInstance - { - get - { - return SingletonInstanceField; - } + this.enumSerializer = new RestierEnumSerializer(this); } /// /// Gets the serializer for the given result type. /// - /// The EDM model. /// The type of result to serialize. /// The HTTP request. /// The serializer instance. public override ODataSerializer GetODataPayloadSerializer( - IEdmModel model, Type type, HttpRequestMessage request) { ODataSerializer serializer = null; - if (type == typeof(EntityCollectionResult)) + if (type == typeof(ResourceSetResult)) { - serializer = this.feedSerializer; + serializer = this.resourceSetSerializer; } else if (type == typeof(PrimitiveResult)) { @@ -74,9 +61,9 @@ public override ODataSerializer GetODataPayloadSerializer( } else if (type == typeof(ComplexResult)) { - serializer = this.complexTypeSerializer; + serializer = this.resourceSerializer; } - else if (type == typeof(NonEntityCollectionResult)) + else if (type == typeof(NonResourceCollectionResult)) { serializer = this.collectionSerializer; } @@ -86,7 +73,7 @@ public override ODataSerializer GetODataPayloadSerializer( } else { - serializer = base.GetODataPayloadSerializer(model, type, request); + serializer = base.GetODataPayloadSerializer(type, request); } return serializer; @@ -101,7 +88,7 @@ public override ODataEdmTypeSerializer GetEdmTypeSerializer(IEdmTypeReference ed { if (edmType.IsComplex()) { - return this.complexTypeSerializer; + return this.resourceSerializer; } if (edmType.IsPrimitive()) @@ -116,9 +103,14 @@ public override ODataEdmTypeSerializer GetEdmTypeSerializer(IEdmTypeReference ed if (edmType.IsCollection()) { - if (edmType.AsCollection().ElementType().IsEntity()) + var collectionType = edmType.AsCollection(); + if (collectionType.Definition.IsDeltaFeed()) + { + return base.GetEdmTypeSerializer(edmType); + } + else if (collectionType.ElementType().IsEntity() || collectionType.ElementType().IsComplex()) { - return this.feedSerializer; + return this.resourceSetSerializer; } return this.collectionSerializer; diff --git a/src/Microsoft.Restier.Publishers.OData/Model/RestierModelExtender.cs b/src/Microsoft.Restier.Publishers.OData/Model/RestierModelExtender.cs index 58ea66c1..df0d4365 100644 --- a/src/Microsoft.Restier.Publishers.OData/Model/RestierModelExtender.cs +++ b/src/Microsoft.Restier.Publishers.OData/Model/RestierModelExtender.cs @@ -178,7 +178,7 @@ private void ScanForDeclaredPublicProperties() private void BuildEntitySetsAndSingletons(ModelContext context, EdmModel model) { - var configuration = context.ApiContext.Configuration; + var configuration = context.ServiceProvider.GetService(); foreach (var property in this.publicProperties) { if (configuration.IsPropertyIgnored(property.Name)) @@ -213,29 +213,29 @@ private void BuildEntitySetsAndSingletons(ModelContext context, EdmModel model) { if (container.FindEntitySet(property.Name) == null) { - this.entitySetProperties.Add(property); - var addedEntitySet = container.AddEntitySet(property.Name, entityType); - this.addedNavigationSources.Add(addedEntitySet); + container.AddEntitySet(property.Name, entityType); } - else + + // If ODataConventionModelBuilder is used to build the model, and a entity set is added, + // i.e. the entity set is already in the container, + // we should add it into entitySetProperties and addedNavigationSources + if (!this.entitySetProperties.Contains(property)) { - // If ODataConventionModelBuilder is used to build the model, and a entity set is added, - // i.e. the entity set is already in the container, - // we should add it into entitySetProperties and addedNavigationSources - if (!this.entitySetProperties.Contains(property)) - { - this.entitySetProperties.Add(property); - this.addedNavigationSources.Add(container.FindEntitySet(property.Name) as EdmEntitySet); - } + this.entitySetProperties.Add(property); + this.addedNavigationSources.Add(container.FindEntitySet(property.Name) as EdmEntitySet); } } else { if (container.FindSingleton(property.Name) == null) + { + container.AddSingleton(property.Name, entityType); + } + + if (!this.singletonProperties.Contains(property)) { this.singletonProperties.Add(property); - var addedSingleton = container.AddSingleton(property.Name, entityType); - this.addedNavigationSources.Add(addedSingleton); + this.addedNavigationSources.Add(container.FindSingleton(property.Name) as EdmSingleton); } } } diff --git a/src/Microsoft.Restier.Publishers.OData/Operation/OperationExecutor.cs b/src/Microsoft.Restier.Publishers.OData/Operation/OperationExecutor.cs index 19d4035b..a16feac2 100644 --- a/src/Microsoft.Restier.Publishers.OData/Operation/OperationExecutor.cs +++ b/src/Microsoft.Restier.Publishers.OData/Operation/OperationExecutor.cs @@ -11,6 +11,7 @@ using System.Security; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; using Microsoft.OData.Edm; using Microsoft.Restier.Core; using Microsoft.Restier.Core.Operation; @@ -38,7 +39,7 @@ public async Task ExecuteOperationAsync( var parameterArray = method.GetParameters(); - var model = await context.ApiContext.GetModelAsync(cancellationToken); + var model = context.ServiceProvider.GetService(); // Parameters of method and model is exactly mapped or there is parsing error var parameters = new object[parameterArray.Length]; @@ -63,7 +64,13 @@ public async Task ExecuteOperationAsync( // Change to right CLR class for collection/Enum/Complex/Entity convertedValue = DeserializationHelpers.ConvertValue( - currentParameterValue, parameter.ParameterType, parameterTypeRef, model, context.ApiContext); + currentParameterValue, + parameter.Name, + parameter.ParameterType, + parameterTypeRef, + model, + context.Request, + context.ServiceProvider); } else { @@ -188,7 +195,7 @@ private static async Task InvokeAuthorizers( private static void PerformPreEvent(OperationContext context, CancellationToken cancellationToken) { - var processor = context.GetApiService(); + var processor = context.ServiceProvider.GetService(); if (processor != null) { processor.OnOperationExecutingAsync(context, cancellationToken); @@ -197,7 +204,7 @@ private static void PerformPreEvent(OperationContext context, CancellationToken private static void PerformPostEvent(OperationContext context, CancellationToken cancellationToken) { - var processor = context.GetApiService(); + var processor = context.ServiceProvider.GetService(); if (processor != null) { processor.OnOperationExecutedAsync(context, cancellationToken); diff --git a/src/Microsoft.Restier.Publishers.OData/Query/RestierQueryBuilder.cs b/src/Microsoft.Restier.Publishers.OData/Query/RestierQueryBuilder.cs index cb64c27f..4d2f80a5 100644 --- a/src/Microsoft.Restier.Publishers.OData/Query/RestierQueryBuilder.cs +++ b/src/Microsoft.Restier.Publishers.OData/Query/RestierQueryBuilder.cs @@ -6,27 +6,23 @@ using System.Globalization; using System.Linq; using System.Linq.Expressions; -using System.Web.OData.Routing; -using Microsoft.OData.Core; -using Microsoft.OData.Core.UriParser; using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; using Microsoft.Restier.Core; +using ODataPath = System.Web.OData.Routing.ODataPath; namespace Microsoft.Restier.Publishers.OData.Query { internal class RestierQueryBuilder { private const string DefaultNameOfParameterExpression = "currentValue"; - private const char EntityKeySeparator = ','; - private const char EntityKeyNameValueSeparator = '='; private readonly ApiBase api; private readonly ODataPath path; - private readonly IDictionary> handlers = - new Dictionary>(); + private readonly IDictionary> handlers = + new Dictionary>(); private IQueryable queryable; - private IEdmEntityType currentEntityType; private Type currentType; public RestierQueryBuilder(ApiBase api, ODataPath path) @@ -36,18 +32,16 @@ public RestierQueryBuilder(ApiBase api, ODataPath path) this.api = api; this.path = path; - this.handlers[ODataSegmentKinds.EntitySet] = this.HandleEntitySetPathSegment; - this.handlers[ODataSegmentKinds.Singleton] = this.HandleSingletonPathSegment; - this.handlers[ODataSegmentKinds.UnboundFunction] = this.EmptyHandler; - this.handlers[ODataSegmentKinds.Function] = this.EmptyHandler; - this.handlers[ODataSegmentKinds.Action] = this.EmptyHandler; - this.handlers[ODataSegmentKinds.UnboundAction] = this.EmptyHandler; - this.handlers[ODataSegmentKinds.Count] = this.HandleCountPathSegment; - this.handlers[ODataSegmentKinds.Value] = this.HandleValuePathSegment; - this.handlers[ODataSegmentKinds.Key] = this.HandleKeyValuePathSegment; - this.handlers[ODataSegmentKinds.Navigation] = this.HandleNavigationPathSegment; - this.handlers[ODataSegmentKinds.Property] = this.HandlePropertyAccessPathSegment; - this.handlers[ODataSegmentKinds.Cast] = this.HandleCastPathSegment; + this.handlers[typeof(EntitySetSegment)] = this.HandleEntitySetPathSegment; + this.handlers[typeof(SingletonSegment)] = this.HandleSingletonPathSegment; + this.handlers[typeof(OperationSegment)] = this.EmptyHandler; + this.handlers[typeof(OperationImportSegment)] = this.EmptyHandler; + this.handlers[typeof(CountSegment)] = this.HandleCountPathSegment; + this.handlers[typeof(ValueSegment)] = this.HandleValuePathSegment; + this.handlers[typeof(KeySegment)] = this.HandleKeyValuePathSegment; + this.handlers[typeof(NavigationPropertySegment)] = this.HandleNavigationPathSegment; + this.handlers[typeof(PropertySegment)] = this.HandlePropertyAccessPathSegment; + this.handlers[typeof(TypeSegment)] = this.HandleEntityTypeSegment; // Complex cast is not supported by EF, and is not supported here // this.handlers[ODataSegmentKinds.ComplexCast] = null; @@ -64,7 +58,7 @@ public IQueryable BuildQuery() foreach (var segment in this.path.Segments) { Action handler; - if (!this.handlers.TryGetValue(segment.SegmentKind, out handler)) + if (!this.handlers.TryGetValue(segment.GetType(), out handler)) { throw new NotImplementedException( string.Format(CultureInfo.InvariantCulture, Resources.PathSegmentNotSupported, segment)); @@ -82,13 +76,13 @@ internal static IReadOnlyDictionary GetPathKeyValues(ODataPath p if (path.PathTemplate == "~/entityset/key" || path.PathTemplate == "~/entityset/key/cast") { - KeyValuePathSegment keySegment = (KeyValuePathSegment)path.Segments[1]; - return GetPathKeyValues(keySegment, (IEdmEntityType)path.EdmType); + var keySegment = (KeySegment)path.Segments[1]; + return GetPathKeyValues(keySegment); } else if (path.PathTemplate == "~/entityset/cast/key") { - KeyValuePathSegment keySegment = (KeyValuePathSegment)path.Segments[2]; - return GetPathKeyValues(keySegment, (IEdmEntityType)path.EdmType); + var keySegment = (KeySegment)path.Segments[2]; + return GetPathKeyValues(keySegment); } else { @@ -100,53 +94,19 @@ internal static IReadOnlyDictionary GetPathKeyValues(ODataPath p } private static IReadOnlyDictionary GetPathKeyValues( - KeyValuePathSegment keySegment, - IEdmEntityType entityType) + KeySegment keySegment) { var result = new Dictionary(); - var keys = entityType.Key(); // TODO GitHubIssue#42 : Improve key parsing logic // this parsing implementation does not allow key values to contain commas // Depending on the WebAPI to make KeyValuePathSegment.Values collection public // (or have the parsing logic public). - string[] values = keySegment.Value.Split(EntityKeySeparator); - if (values.Length > 1) - { - foreach (string value in values) - { - // Split key name and key value - var keyValues = value.Split(EntityKeyNameValueSeparator); - if (keyValues.Length != 2) - { - throw new InvalidOperationException(Resources.IncorrectKeyFormat); - } - - // Validate the key name - if (!keys.Select(k => k.Name).Contains(keyValues[0])) - { - throw new InvalidOperationException( - string.Format( - CultureInfo.InvariantCulture, - Resources.KeyNotValidForEntityType, - keyValues[0], - entityType.Name)); - } - - result.Add(keyValues[0], ODataUriUtils.ConvertFromUriLiteral(keyValues[1], ODataVersion.V4)); - } - } - else - { - // We just have the single key value - // Validate it has exactly one key - if (keys.Count() > 1) - { - throw new InvalidOperationException(Resources.MultiKeyValuesExpected); - } + var keyValuePairs = keySegment.Keys; - var keyName = keys.First().Name; - result.Add(keyName, ODataUriUtils.ConvertFromUriLiteral(keySegment.Value, ODataVersion.V4)); + foreach (var keyValuePair in keyValuePairs) + { + result.Add(keyValuePair.Key, keyValuePair.Value); } return result; @@ -177,18 +137,16 @@ private static LambdaExpression CreateNotEqualsNullExpression( #region Handler Methods private void HandleEntitySetPathSegment(ODataPathSegment segment) { - var entitySetPathSegment = (EntitySetPathSegment)segment; - var entitySet = entitySetPathSegment.EntitySetBase; - this.currentEntityType = entitySet.EntityType(); + var entitySetPathSegment = (EntitySetSegment)segment; + var entitySet = entitySetPathSegment.EntitySet; this.queryable = this.api.GetQueryableSource(entitySet.Name, (object[])null); this.currentType = this.queryable.ElementType; } private void HandleSingletonPathSegment(ODataPathSegment segment) { - var singletonPathSegment = (SingletonPathSegment)segment; + var singletonPathSegment = (SingletonSegment)segment; var singleton = singletonPathSegment.Singleton; - this.currentEntityType = singleton.EntityType(); this.queryable = this.api.GetQueryableSource(singleton.Name, (object[])null); this.currentType = this.queryable.ElementType; } @@ -210,10 +168,10 @@ private void HandleValuePathSegment(ODataPathSegment segment) private void HandleKeyValuePathSegment(ODataPathSegment segment) { - var keySegment = (KeyValuePathSegment)segment; + var keySegment = (KeySegment)segment; var parameterExpression = Expression.Parameter(this.currentType, DefaultNameOfParameterExpression); - var keyValues = GetPathKeyValues(keySegment, this.currentEntityType); + var keyValues = GetPathKeyValues(keySegment); BinaryExpression keyFilter = null; foreach (KeyValuePair keyValuePair in keyValues) @@ -229,12 +187,10 @@ private void HandleKeyValuePathSegment(ODataPathSegment segment) private void HandleNavigationPathSegment(ODataPathSegment segment) { - var navigationSegment = (NavigationPathSegment)segment; + var navigationSegment = (NavigationPropertySegment)segment; var entityParameterExpression = Expression.Parameter(this.currentType); var navigationPropertyExpression = - Expression.Property(entityParameterExpression, navigationSegment.NavigationPropertyName); - - this.currentEntityType = navigationSegment.NavigationProperty.ToEntityType(); + Expression.Property(entityParameterExpression, navigationSegment.NavigationProperty.Name); // Check whether property is null or not before further selection var whereExpression = @@ -267,10 +223,10 @@ private void HandleNavigationPathSegment(ODataPathSegment segment) private void HandlePropertyAccessPathSegment(ODataPathSegment segment) { - var propertySegment = (PropertyAccessPathSegment)segment; + var propertySegment = (PropertySegment)segment; var entityParameterExpression = Expression.Parameter(this.currentType); var structuralPropertyExpression = - Expression.Property(entityParameterExpression, propertySegment.PropertyName); + Expression.Property(entityParameterExpression, propertySegment.Property.Name); // Check whether property is null or not before further selection if (propertySegment.Property.Type.IsNullable && !propertySegment.Property.Type.IsPrimitive()) @@ -306,13 +262,21 @@ private void HandlePropertyAccessPathSegment(ODataPathSegment segment) // This only covers entity type cast // complex type cast uses ComplexCastPathSegment and is not supported by EF now // CLR type is got from model annotation, which means model must include that annotation. - private void HandleCastPathSegment(ODataPathSegment segment) + private void HandleEntityTypeSegment(ODataPathSegment segment) { - var castSegment = (CastPathSegment)segment; - var elementType = castSegment.CastType.GetClrType(api); - this.currentEntityType = castSegment.CastType; - this.currentType = elementType; - this.queryable = ExpressionHelpers.OfType(this.queryable, elementType); + var typeSegment = (TypeSegment)segment; + var edmType = typeSegment.EdmType; + + if (typeSegment.EdmType.TypeKind == EdmTypeKind.Collection) + { + edmType = ((IEdmCollectionType)typeSegment.EdmType).ElementType.Definition; + } + + if (edmType.TypeKind == EdmTypeKind.Entity) + { + this.currentType = edmType.GetClrType(api); + this.queryable = ExpressionHelpers.OfType(this.queryable, this.currentType); + } } #endregion } diff --git a/src/Microsoft.Restier.Publishers.OData/RestierController.cs b/src/Microsoft.Restier.Publishers.OData/RestierController.cs index c6b17334..dfea734d 100644 --- a/src/Microsoft.Restier.Publishers.OData/RestierController.cs +++ b/src/Microsoft.Restier.Publishers.OData/RestierController.cs @@ -19,24 +19,24 @@ using System.Web.OData.Results; using System.Web.OData.Routing; using Microsoft.OData.Edm; -using Microsoft.OData.Edm.Library; +using Microsoft.OData.UriParser; using Microsoft.Restier.Core; using Microsoft.Restier.Core.Operation; using Microsoft.Restier.Core.Query; using Microsoft.Restier.Core.Submit; using Microsoft.Restier.Publishers.OData.Batch; -using Microsoft.Restier.Publishers.OData.Formatter; using Microsoft.Restier.Publishers.OData.Query; // This is a must for creating response with correct extension method using Net::System.Net.Http; +using ODataPath = System.Web.OData.Routing.ODataPath; namespace Microsoft.Restier.Publishers.OData { /// /// The all-in-one controller class to handle API requests. /// - [RestierFormatting] + [ODataFormatting] [RestierExceptionFilter] public class RestierController : ODataController { @@ -88,12 +88,13 @@ public async Task Get( bool isIfNoneMatch; // TODO #365 Do not support additional path segment after function call now - if (lastSegment.SegmentKind == ODataSegmentKinds.UnboundFunction) + if (lastSegment is OperationImportSegment) { - var unboundSegment = (UnboundFunctionPathSegment)lastSegment; + var unboundSegment = (OperationImportSegment)lastSegment; + var operation = unboundSegment.OperationImports.FirstOrDefault(); Func getParaValueFunc = p => unboundSegment.GetParameterValue(p); result = await ExecuteOperationAsync( - getParaValueFunc, unboundSegment.FunctionName, true, null, cancellationToken); + getParaValueFunc, operation.Name, true, null, cancellationToken); result = ApplyQueryOptions(result, path, true, out isIfNoneMatch, out etag); } else @@ -106,14 +107,15 @@ public async Task Get( Resources.ResourceNotFound)); } - if (lastSegment.SegmentKind == ODataSegmentKinds.Function) + if (lastSegment is OperationSegment) { result = await ExecuteQuery(queryable, cancellationToken); - var boundSeg = (BoundFunctionPathSegment)lastSegment; + var boundSeg = (OperationSegment)lastSegment; + var operation = boundSeg.Operations.FirstOrDefault(); Func getParaValueFunc = p => boundSeg.GetParameterValue(p); result = await ExecuteOperationAsync( - getParaValueFunc, boundSeg.Function.Name, true, result, cancellationToken); + getParaValueFunc, operation.Name, true, result, cancellationToken); result = ApplyQueryOptions(result, path, true, out isIfNoneMatch, out etag); } @@ -290,11 +292,13 @@ public async Task PostAction( return parameters[p]; }; - if (lastSegment.SegmentKind == ODataSegmentKinds.UnboundAction) + var segment = lastSegment as OperationImportSegment; + if (segment != null) { - var unboundSegment = (UnboundActionPathSegment)lastSegment; + var unboundSegment = segment; + var operation = unboundSegment.OperationImports.FirstOrDefault(); result = await ExecuteOperationAsync( - getParaValueFunc, unboundSegment.ActionName, false, null, cancellationToken); + getParaValueFunc, operation.Name, false, null, cancellationToken); } else { @@ -308,14 +312,13 @@ public async Task PostAction( Resources.ResourceNotFound)); } - if (lastSegment.SegmentKind == ODataSegmentKinds.Action) + if (lastSegment is OperationSegment) { + var operationSegment = lastSegment as OperationSegment; + var operation = operationSegment.Operations.FirstOrDefault(); var queryResult = await ExecuteQuery(queryable, cancellationToken); - - // TODO GitHubIssue#114, need etag check here for single bound entity - var boundSeg = (BoundActionPathSegment)lastSegment; result = await ExecuteOperationAsync( - getParaValueFunc, boundSeg.Action.Name, false, queryResult, cancellationToken); + getParaValueFunc, operation.Name, false, queryResult, cancellationToken); } } @@ -490,14 +493,14 @@ private HttpResponseMessage CreateQueryResponse( if (typeReference.IsCollection()) { var elementType = typeReference.AsCollection().ElementType(); - if (elementType.IsPrimitive() || elementType.IsComplex() || elementType.IsEnum()) + if (elementType.IsPrimitive() || elementType.IsEnum()) { return this.Request.CreateResponse( - HttpStatusCode.OK, new NonEntityCollectionResult(query, typeReference, this.Api.Context)); + HttpStatusCode.OK, new NonResourceCollectionResult(query, typeReference, this.Api.Context)); } return this.Request.CreateResponse( - HttpStatusCode.OK, new EntityCollectionResult(query, typeReference, this.Api.Context)); + HttpStatusCode.OK, new ResourceSetResult(query, typeReference, this.Api.Context)); } var entityResult = query.SingleOrDefault(); @@ -562,8 +565,9 @@ private IQueryable ApplyQueryOptions( } HttpRequestMessageProperties properties = this.Request.ODataProperties(); + var model = Api.GetModelAsync().Result; ODataQueryContext queryContext = - new ODataQueryContext(properties.Model, queryable.ElementType, path); + new ODataQueryContext(model, queryable.ElementType, path); ODataQueryOptions queryOptions = new ODataQueryOptions(queryContext, this.Request); // Get etag for query request @@ -652,7 +656,9 @@ private Task ExecuteOperationAsync( { var executor = Api.Context.GetApiService(); var context = new OperationContext( - Api.Context, getParaValueFunc, operationName, isFunction, bindingParameterValue); + getParaValueFunc, operationName, Api, isFunction, bindingParameterValue); + context.ServiceProvider = Request.GetRequestContainer(); + context.Request = Request; var result = executor.ExecuteOperationAsync(Api, context, cancellationToken); return result; } diff --git a/src/Microsoft.Restier.Publishers.OData/Routing/HttpConfigurationExtensions.cs b/src/Microsoft.Restier.Publishers.OData/Routing/HttpConfigurationExtensions.cs index d980124d..48b72f1b 100644 --- a/src/Microsoft.Restier.Publishers.OData/Routing/HttpConfigurationExtensions.cs +++ b/src/Microsoft.Restier.Publishers.OData/Routing/HttpConfigurationExtensions.cs @@ -2,18 +2,22 @@ // Licensed under the MIT License. See License.txt in the project root for license information. using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.ComponentModel; using System.Globalization; using System.Threading.Tasks; using System.Web.Http; +using System.Web.OData.Batch; using System.Web.OData.Extensions; using System.Web.OData.Routing; using System.Web.OData.Routing.Conventions; -using Microsoft.OData.Core; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData; using Microsoft.OData.Edm; using Microsoft.Restier.Core; using Microsoft.Restier.Publishers.OData.Batch; +using ServiceLifetime = Microsoft.OData.ServiceLifetime; namespace Microsoft.Restier.Publishers.OData { @@ -24,6 +28,7 @@ namespace Microsoft.Restier.Publishers.OData public static class HttpConfigurationExtensions { private const string UseVerboseErrorsFlagKey = "Microsoft.Restier.UseVerboseErrorsFlag"; + private const string RootContainerKey = "System.Web.OData.RootContainerMappingsKey"; /// TODO GitHubIssue#51 : Support model lazy loading /// @@ -52,37 +57,34 @@ public static Task MapRestierRoute( { services.AddODataServices(); }); + using (var api = apiFactory()) { - var model = GetModel(api); - - var conventions = CreateRestierRoutingConventions(config, model, apiFactory); + Func func = () => new RestierContainerBuilder(apiFactory); + config.UseCustomContainerBuilder(func); + var conventions = CreateRestierRoutingConventions(config, routeName, apiFactory); if (batchHandler != null && batchHandler.ApiFactory == null) { batchHandler.ApiFactory = apiFactory; + batchHandler.ODataRouteName = routeName; } - // Customized path handler should be added in ConfigureApi as service - // Allow to handle URL encoded slash (%2F), and backslash(%5C) with customized handler - var handler = api.Context.GetApiService(); - if (handler == null) - { - handler = new DefaultODataPathHandler(); - } + Action configureAction = builder => builder + .AddService>(ServiceLifetime.Singleton, sp => conventions) + .AddService(ServiceLifetime.Singleton, sp => batchHandler); - var route = config.MapODataServiceRoute( - routeName, routePrefix, model, handler, conventions, batchHandler); + var route = config.MapODataServiceRoute(routeName, routePrefix, configureAction); - // Customized converter should be added in ConfigureApi as service - var converter = api.Context.GetApiService(); - if (converter == null) + // Set ApiConfiguration instance for further usage + if (config != null) { - converter = new RestierPayloadValueConverter(); + var mapping = (ConcurrentDictionary)config.Properties[RootContainerKey]; + IServiceProvider rootContainer; + mapping.TryGetValue(routeName, out rootContainer); + api.Configuration = rootContainer.GetService(); } - model.SetPayloadValueConverter(converter); - return Task.FromResult(route); } } @@ -152,13 +154,13 @@ public static void SetUseVerboseErrors(this HttpConfiguration configuration, boo /// Creates the default routing conventions. /// /// The instance. - /// The EDM model. + /// The name of the route. /// The API factory. /// The routing conventions created. private static IList CreateRestierRoutingConventions( - this HttpConfiguration config, IEdmModel model, Func apiFactory) + this HttpConfiguration config, string routeName, Func apiFactory) { - var conventions = ODataRoutingConventions.CreateDefaultWithAttributeRouting(config, model); + var conventions = ODataRoutingConventions.CreateDefaultWithAttributeRouting(routeName, config); var index = 0; for (; index < conventions.Count; index++) { @@ -172,25 +174,5 @@ private static IList CreateRestierRoutingConventions( conventions.Insert(index + 1, new RestierRoutingConvention(apiFactory)); return conventions; } - - private static IEdmModel GetModel(ApiBase api) - { - // Here await is not used because if method MapRestierRoute is mapped async, - // Then during application starts, the http service initialization may complete first - // before this method call is complete. - // Then all request will fail, and this happen for some test cases before when get model takes long time. - IEdmModel model; - try - { - model = api.GetModelAsync().Result; - } - catch (AggregateException e) - { - // Without await, the exception is wrapped and inner exception has more meaningful message. - throw e.InnerException; - } - - return model; - } } } diff --git a/src/Microsoft.Restier.Publishers.OData/ServiceCollectionExtensions.cs b/src/Microsoft.Restier.Publishers.OData/ServiceCollectionExtensions.cs index 802694a1..d16c2ea6 100644 --- a/src/Microsoft.Restier.Publishers.OData/ServiceCollectionExtensions.cs +++ b/src/Microsoft.Restier.Publishers.OData/ServiceCollectionExtensions.cs @@ -7,6 +7,7 @@ using System.Web.OData.Query; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.OData; using Microsoft.Restier.Core; using Microsoft.Restier.Core.Model; using Microsoft.Restier.Core.Operation; @@ -50,8 +51,8 @@ public static IServiceCollection AddODataServices(this IServiceCollection ser PageSize = null, // no support for server enforced PageSize, yet }; - services.TryAddSingleton(typeof(ODataQuerySettings), querySettingFactory); - services.TryAddSingleton(); + services.AddSingleton(typeof(ODataQuerySettings), querySettingFactory); + services.AddSingleton(); // Make serializer and deserializer provider as DI services // WebApi OData service provider will be added first, need to overwrite. @@ -59,11 +60,11 @@ public static IServiceCollection AddODataServices(this IServiceCollection ser services.AddSingleton(); services.TryAddSingleton(); + services.AddSingleton(); services.AddService(); - return - services.AddScoped() + return services.AddScoped() .AddService(); } } diff --git a/test/Microsoft.Restier.Core.Tests/Api.Tests.cs b/test/Microsoft.Restier.Core.Tests/Api.Tests.cs index 735e513b..ed0d375d 100644 --- a/test/Microsoft.Restier.Core.Tests/Api.Tests.cs +++ b/test/Microsoft.Restier.Core.Tests/Api.Tests.cs @@ -106,7 +106,7 @@ private class TestApiEmpty : ApiBase public void ApiSourceOfEntityContainerElementIsCorrect() { var api = new TestApi(); - var container = new RestierContainerBuilder(api); + var container = new RestierContainerBuilder(() => new TestApi()); api.Configuration = new ApiConfiguration(container.BuildContainer()); var arguments = new object[0]; @@ -131,7 +131,7 @@ public void ApiSourceOfEntityContainerElementIsCorrect() public void SourceOfEntityContainerElementThrowsIfNotMapped() { var api = new TestApiEmpty(); - var container = new RestierContainerBuilder(api); + var container = new RestierContainerBuilder(() => new TestApiEmpty()); api.Configuration = new ApiConfiguration(container.BuildContainer()); var context = api.Context; var arguments = new object[0]; @@ -166,7 +166,7 @@ public void SourceOfEntityContainerElementIsCorrect() public void ApiSourceOfComposableFunctionIsCorrect() { var api = new TestApi(); - var container = new RestierContainerBuilder(api); + var container = new RestierContainerBuilder(() => new TestApi()); api.Configuration = new ApiConfiguration(container.BuildContainer()); var arguments = new object[0]; @@ -193,8 +193,9 @@ public void ApiSourceOfComposableFunctionIsCorrect() public void SourceOfComposableFunctionThrowsIfNotMapped() { var api = new TestApiEmpty(); - var container = new RestierContainerBuilder(api); - api.Configuration = new ApiConfiguration(container.BuildContainer()); + var container = new RestierContainerBuilder(() => new TestApiEmpty()); + var serviceProvider = container.BuildContainer(); + api.Configuration = serviceProvider.GetService(); var context = api.Context; var arguments = new object[0]; @@ -205,7 +206,7 @@ public void SourceOfComposableFunctionThrowsIfNotMapped() public void SourceOfComposableFunctionIsCorrect() { var api = new TestApi(); - var container = new RestierContainerBuilder(api); + var container = new RestierContainerBuilder(() => new TestApi()); api.Configuration = new ApiConfiguration(container.BuildContainer()); var context = api.Context; @@ -265,7 +266,7 @@ public void GenericSourceOfEntityContainerElementThrowsIfWrongType() public void GenericSourceOfEntityContainerElementIsCorrect() { var api = new TestApi(); - var container = new RestierContainerBuilder(api); + var container = new RestierContainerBuilder(() => new TestApi()); api.Configuration = new ApiConfiguration(container.BuildContainer()); var context = api.Context; @@ -316,7 +317,7 @@ public void GenericApiSourceOfComposableFunctionIsCorrect() public void GenericSourceOfComposableFunctionThrowsIfWrongType() { var api = new TestApi(); - var container = new RestierContainerBuilder(api); + var container = new RestierContainerBuilder(() => new TestApi()); api.Configuration = new ApiConfiguration(container.BuildContainer()); var context = api.Context; @@ -384,7 +385,7 @@ public void SourceQueryProviderCannotGenericExecute() public void SourceQueryProviderCannotExecute() { var api = new TestApi(); - var container = new RestierContainerBuilder(api); + var container = new RestierContainerBuilder(() => new TestApi()); api.Configuration = new ApiConfiguration(container.BuildContainer()); var context = api.Context; @@ -396,7 +397,7 @@ public void SourceQueryProviderCannotExecute() public async Task ApiQueryAsyncWithQueryReturnsResults() { var api = new TestApi(); - var container = new RestierContainerBuilder(api); + var container = new RestierContainerBuilder(() => new TestApi()); api.Configuration = new ApiConfiguration(container.BuildContainer()); var request = new QueryRequest(api.GetQueryableSource("Test")); @@ -410,7 +411,7 @@ public async Task ApiQueryAsyncWithQueryReturnsResults() public async Task ApiQueryAsyncCorrectlyForwardsCall() { var api = new TestApi(); - var container = new RestierContainerBuilder(api); + var container = new RestierContainerBuilder(() => new TestApi()); api.Configuration = new ApiConfiguration(container.BuildContainer()); var queryRequest = new QueryRequest( diff --git a/test/Microsoft.Restier.Core.Tests/ApiBase.Tests.cs b/test/Microsoft.Restier.Core.Tests/ApiBase.Tests.cs index b5a725af..0827a48c 100644 --- a/test/Microsoft.Restier.Core.Tests/ApiBase.Tests.cs +++ b/test/Microsoft.Restier.Core.Tests/ApiBase.Tests.cs @@ -53,7 +53,7 @@ public void ApiAndApiContextCanBeInjectedByDI() { using (var api = new TestApi()) { - var container = new RestierContainerBuilder(api); + var container = new RestierContainerBuilder(() => new TestApi()); api.Configuration = new ApiConfiguration(container.BuildContainer()); var context = api.Context; @@ -66,73 +66,5 @@ public void ApiAndApiContextCanBeInjectedByDI() Assert.Throws(() => api.Context); } } - - [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] - private class TestApiConfiguratorAttribute : - ApiConfiguratorAttribute - { - public TestApiConfiguratorAttribute(string value) - { - this.Value = value; - } - - public string Value { get; private set; } - - public override void UpdateApiConfiguration( - ApiConfiguration configuration, - Type type) - { - base.UpdateApiConfiguration(configuration, type); - Assert.Same(typeof(TestApiWithParticipants), type); - configuration.SetProperty(this.Value, true); - } - - public override void UpdateApiContext( - ApiContext context, - Type type, object instance) - { - base.UpdateApiContext(context, type, instance); - Assert.Same(typeof(TestApiWithParticipants), type); - context.SetProperty(this.Value + ".Self", instance); - context.SetProperty(this.Value, true); - } - - public override void Dispose( - ApiContext context, - Type type, object instance) - { - Assert.Same(typeof(TestApiWithParticipants), type); - context.SetProperty(this.Value, false); - base.Dispose(context, type, instance); - } - } - - [TestApiConfigurator("Test1")] - [TestApiConfigurator("Test2")] - private class TestApiWithParticipants : ApiBase - { - } - - [Fact] - public void TestApiAppliesApiParticipantsCorrectly() - { - ApiBase api = new TestApiWithParticipants(); - var container = new RestierContainerBuilder(api); - api.Configuration = new ApiConfiguration(container.BuildContainer()); - - var configuration = api.Context.Configuration; - Assert.True(configuration.GetProperty("Test1")); - Assert.True(configuration.GetProperty("Test2")); - - var context = api.Context; - Assert.True(context.GetProperty("Test1")); - Assert.Same(api, context.GetProperty("Test1.Self")); - Assert.True(context.GetProperty("Test2")); - Assert.Same(api, context.GetProperty("Test2.Self")); - - (api as IDisposable).Dispose(); - Assert.False(context.GetProperty("Test2")); - Assert.False(context.GetProperty("Test1")); - } } } diff --git a/test/Microsoft.Restier.Core.Tests/ApiConfiguration.Tests.cs b/test/Microsoft.Restier.Core.Tests/ApiConfiguration.Tests.cs index a6ca5408..57d7a7a1 100644 --- a/test/Microsoft.Restier.Core.Tests/ApiConfiguration.Tests.cs +++ b/test/Microsoft.Restier.Core.Tests/ApiConfiguration.Tests.cs @@ -18,7 +18,7 @@ public class ApiConfigurationTests public void CachedConfigurationIsCachedCorrectly() { ApiBase api = new TestApiA(); - var container = new RestierContainerBuilder(api); + var container = new RestierContainerBuilder(() => new TestApiA()); api.Configuration = new ApiConfiguration(container.BuildContainer()); var configuration = api.Context.Configuration; @@ -32,17 +32,18 @@ public void CachedConfigurationIsCachedCorrectly() public void ConfigurationRegistersApiServicesCorrectly() { var api = new TestApiA(); - var container = new RestierContainerBuilder(api); + var container = new RestierContainerBuilder(() => new TestApiA()); api.Configuration = new ApiConfiguration(container.BuildContainer()); Assert.Null(api.Context.GetApiService()); Assert.Null(api.Context.GetApiService()); var apiB = new TestApiB(); - container = new RestierContainerBuilder(apiB); + container = new RestierContainerBuilder(() => new TestApiB()); apiB.Configuration = new ApiConfiguration(container.BuildContainer()); - Assert.Same(apiB.serviceA, apiB.Context.GetApiService()); + // This is not same as during configure Api, a new APi is created which has new Service A registered. + Assert.NotSame(apiB.serviceA, apiB.Context.GetApiService()); var serviceBInstance = apiB.Context.GetApiService(); var serviceBInterface = apiB.Context.GetApiService(); @@ -53,14 +54,16 @@ public void ConfigurationRegistersApiServicesCorrectly() var serviceBFirst = serviceBInterface as ServiceB; Assert.NotNull(serviceBFirst); - Assert.Same(apiB.serviceB, serviceBFirst.InnerHandler); + + // This is not same as during configure Api, a new APi is created which has new Service B registered. + Assert.NotSame(apiB.serviceB, serviceBFirst.InnerHandler); } [Fact] public void ServiceChainTest() { var api = new TestApiC(); - var container = new RestierContainerBuilder(api); + var container = new RestierContainerBuilder(() => new TestApiC()); api.Configuration = new ApiConfiguration(container.BuildContainer()); var handler = api.Context.GetApiService(); diff --git a/test/Microsoft.Restier.Core.Tests/ApiContext.Tests.cs b/test/Microsoft.Restier.Core.Tests/ApiContext.Tests.cs index 68165f13..ab8412b8 100644 --- a/test/Microsoft.Restier.Core.Tests/ApiContext.Tests.cs +++ b/test/Microsoft.Restier.Core.Tests/ApiContext.Tests.cs @@ -17,7 +17,7 @@ private class TestApi : ApiBase public void NewApiContextIsConfiguredCorrectly() { var api = new TestApi(); - var container = new RestierContainerBuilder(api); + var container = new RestierContainerBuilder(() => new TestApi()); api.Configuration = new ApiConfiguration(container.BuildContainer()); var context = api.Context; diff --git a/test/Microsoft.Restier.Core.Tests/InvocationContext.Tests.cs b/test/Microsoft.Restier.Core.Tests/InvocationContext.Tests.cs index fa7b4071..9562c251 100644 --- a/test/Microsoft.Restier.Core.Tests/InvocationContext.Tests.cs +++ b/test/Microsoft.Restier.Core.Tests/InvocationContext.Tests.cs @@ -35,7 +35,7 @@ public override IServiceCollection ConfigureApi(IServiceCollection services) public void NewInvocationContextIsConfiguredCorrectly() { var api = new TestApi(); - var container = new RestierContainerBuilder(api); + var container = new RestierContainerBuilder(() => new TestApi()); api.Configuration = new ApiConfiguration(container.BuildContainer()); var apiContext = api.Context; var context = new InvocationContext(apiContext); @@ -46,7 +46,7 @@ public void NewInvocationContextIsConfiguredCorrectly() public void InvocationContextGetsApiServicesCorrectly() { var api = new TestApi(); - var container = new RestierContainerBuilder(api); + var container = new RestierContainerBuilder(() => new TestApi()); api.Configuration = new ApiConfiguration(container.BuildContainer()); var apiContext = api.Context; var context = new InvocationContext(apiContext); diff --git a/test/Microsoft.Restier.Core.Tests/Model/DefaultModelHandler.Tests.cs b/test/Microsoft.Restier.Core.Tests/Model/DefaultModelHandler.Tests.cs index 0cab9091..3d64b337 100644 --- a/test/Microsoft.Restier.Core.Tests/Model/DefaultModelHandler.Tests.cs +++ b/test/Microsoft.Restier.Core.Tests/Model/DefaultModelHandler.Tests.cs @@ -105,7 +105,7 @@ public async Task GetModelAsync(ModelContext context, CancellationTok public async Task GetModelUsingDefaultModelHandler() { var api = new TestApiA(); - var container = new RestierContainerBuilder(api); + var container = new RestierContainerBuilder(() => new TestApiA()); api.Configuration = new ApiConfiguration(container.BuildContainer()); var context = api.Context; @@ -141,6 +141,10 @@ public async Task GetModelAsync(ModelContext context, CancellationTok private static Task[] PrepareThreads(int count, Type apiType, ManualResetEventSlim wait) { + var api2 = (ApiBase)Activator.CreateInstance(apiType); + var container = new RestierContainerBuilder(() => (ApiBase)Activator.CreateInstance(apiType)); + api2.Configuration = new ApiConfiguration(container.BuildContainer()); + var tasks = new Task[count]; var result = Parallel.For(0, count, (inx, state) => { @@ -151,8 +155,6 @@ private static Task[] PrepareThreads(int count, Type apiType, ManualR wait.Wait(); var api = (ApiBase)Activator.CreateInstance(apiType); - var container = new RestierContainerBuilder(api); - api.Configuration = new ApiConfiguration(container.BuildContainer()); var context = api.Context; try diff --git a/test/Microsoft.Restier.Core.Tests/PropertyBag.Tests.cs b/test/Microsoft.Restier.Core.Tests/PropertyBag.Tests.cs index 59cea8e6..b5c4cfe9 100644 --- a/test/Microsoft.Restier.Core.Tests/PropertyBag.Tests.cs +++ b/test/Microsoft.Restier.Core.Tests/PropertyBag.Tests.cs @@ -13,7 +13,7 @@ public class PropertyBagTests public void PropertyBagManipulatesPropertiesCorrectly() { var api = new TestApi(); - var container = new RestierContainerBuilder(api); + var container = new RestierContainerBuilder(() => new TestApi()); api.Configuration = new ApiConfiguration(container.BuildContainer()); var context =api.Context; @@ -38,7 +38,7 @@ public void PropertyBagManipulatesPropertiesCorrectly() public void DifferentPropertyBagsDoNotConflict() { var api = new TestApi(); - var container = new RestierContainerBuilder(api); + var container = new RestierContainerBuilder(() => new TestApi()); api.Configuration = new ApiConfiguration(container.BuildContainer()); var context = api.Context; @@ -55,7 +55,7 @@ public void DifferentPropertyBagsDoNotConflict() public void PropertyBagsAreDisposedCorrectly() { var api = new TestApi(); - var container = new RestierContainerBuilder(api); + var container = new RestierContainerBuilder(() => new TestApi()); api.Configuration = new ApiConfiguration(container.BuildContainer()); var context = api.Context; var configuration = context.Configuration; diff --git a/test/Microsoft.Restier.Core.Tests/ServiceConfiguration.Tests.cs b/test/Microsoft.Restier.Core.Tests/ServiceConfiguration.Tests.cs index 9cbb1594..ea7cbbb8 100644 --- a/test/Microsoft.Restier.Core.Tests/ServiceConfiguration.Tests.cs +++ b/test/Microsoft.Restier.Core.Tests/ServiceConfiguration.Tests.cs @@ -297,7 +297,7 @@ public string Call() public void ContributorsAreCalledCorrectly() { var api = new TestApiA(); - var container = new RestierContainerBuilder(api); + var container = new RestierContainerBuilder(() => new TestApiA()); api.Configuration = new ApiConfiguration(container.BuildContainer()); var value = api.Context.GetApiService().Call(); Assert.Equal("03210", value); @@ -307,7 +307,7 @@ public void ContributorsAreCalledCorrectly() public void NextInjectedViaProperty() { var api = new TestApiB(); - var container = new RestierContainerBuilder(api); + var container = new RestierContainerBuilder(() => new TestApiB()); api.Configuration = new ApiConfiguration(container.BuildContainer()); var value = api.Context.GetApiService().Call(); Assert.Equal("01", value); @@ -321,13 +321,13 @@ public void NextInjectedViaProperty() public void ContextApiScopeWorksCorrectly() { var api = new TestApiC(); - var container = new RestierContainerBuilder(api); + var container = new RestierContainerBuilder(() => new TestApiC()); api.Configuration = new ApiConfiguration(container.BuildContainer()); var service1 = api.Context.GetApiService(); var api2 = new TestApiC(); - container = new RestierContainerBuilder(api2); + container = new RestierContainerBuilder(() => new TestApiC()); api2.Configuration = new ApiConfiguration(container.BuildContainer()); var service2 = api2.Context.GetApiService(); @@ -345,7 +345,7 @@ public void NothingInjectedStillWorks() { // Outmost service does not call inner service var api = new TestApiD(); - var container = new RestierContainerBuilder(api); + var container = new RestierContainerBuilder(() => new TestApiD()); api.Configuration = new ApiConfiguration(container.BuildContainer()); var value = api.Context.GetApiService().Call(); @@ -362,7 +362,7 @@ public void NothingInjectedStillWorks() public void ServiceInjectedViaProperty() { var api = new TestApiE(); - var container = new RestierContainerBuilder(api); + var container = new RestierContainerBuilder(() => new TestApiE()); api.Configuration = new ApiConfiguration(container.BuildContainer()); var expected = "Text42"; @@ -384,7 +384,7 @@ public void ServiceInjectedViaProperty() public void DefaultValueInConstructorUsedIfNoService() { var api = new TestApiF(); - var container = new RestierContainerBuilder(api); + var container = new RestierContainerBuilder(() => new TestApiF()); api.Configuration = new ApiConfiguration(container.BuildContainer()); var value = api.Context.GetApiService().Call(); @@ -401,7 +401,7 @@ public void DefaultValueInConstructorUsedIfNoService() public void MultiInjectionViaConstructor() { var api = new TestApiG(); - var container = new RestierContainerBuilder(api); + var container = new RestierContainerBuilder(() => new TestApiG()); api.Configuration = new ApiConfiguration(container.BuildContainer()); var value = api.Context.GetApiService().Call(); @@ -418,7 +418,7 @@ public void MultiInjectionViaConstructor() public void ThrowOnNoServiceFound() { var api = new TestApiH(); - var container = new RestierContainerBuilder(api); + var container = new RestierContainerBuilder(() => new TestApiH()); api.Configuration = new ApiConfiguration(container.BuildContainer()); Assert.Throws(() => { api.Context.GetApiService(); }); @@ -428,7 +428,7 @@ public void ThrowOnNoServiceFound() public void NextInjectedWithInheritedField() { var api = new TestApiI(); - var container = new RestierContainerBuilder(api); + var container = new RestierContainerBuilder(() => new TestApiI()); api.Configuration = new ApiConfiguration(container.BuildContainer()); var value = api.Context.GetApiService().Call(); diff --git a/test/Microsoft.Restier.Providers.EntityFramework.Tests/ChangeSetPreparerTests.cs b/test/Microsoft.Restier.Providers.EntityFramework.Tests/ChangeSetPreparerTests.cs index d25f8dcd..958cba16 100644 --- a/test/Microsoft.Restier.Providers.EntityFramework.Tests/ChangeSetPreparerTests.cs +++ b/test/Microsoft.Restier.Providers.EntityFramework.Tests/ChangeSetPreparerTests.cs @@ -19,7 +19,7 @@ public async Task ComplexTypeUpdate() { // Arrange var libraryApi = new LibraryApi(); - var container = new RestierContainerBuilder(libraryApi); + var container = new RestierContainerBuilder(() => new LibraryApi()); libraryApi.Configuration = new ApiConfiguration(container.BuildContainer()); var item = new DataModificationItem( diff --git a/test/Microsoft.Restier.Publishers.OData.Test/Model/RestierModelBuilderTests.cs b/test/Microsoft.Restier.Publishers.OData.Test/Model/RestierModelBuilderTests.cs index 0dca64de..36a833f9 100644 --- a/test/Microsoft.Restier.Publishers.OData.Test/Model/RestierModelBuilderTests.cs +++ b/test/Microsoft.Restier.Publishers.OData.Test/Model/RestierModelBuilderTests.cs @@ -16,7 +16,7 @@ public class RestierModelBuilderTests public void ComplexTypeShoudWork() { var api = new LibraryApi(); - var container = new RestierContainerBuilder(api); + var container = new RestierContainerBuilder(() => new LibraryApi()); api.Configuration = new ApiConfiguration(container.BuildContainer()); var model = api.Context.GetModelAsync().Result; @@ -34,7 +34,7 @@ public void ComplexTypeShoudWork() public void PrimitiveTypesShouldWork() { var api = new LibraryApi(); - var container = new RestierContainerBuilder(api); + var container = new RestierContainerBuilder(() => new LibraryApi()); api.Configuration = new ApiConfiguration(container.BuildContainer()); var model = api.Context.GetModelAsync().Result; diff --git a/test/Microsoft.Restier.TestCommon/PublicApi.bsl b/test/Microsoft.Restier.TestCommon/PublicApi.bsl index 807dae94..314d87df 100644 --- a/test/Microsoft.Restier.TestCommon/PublicApi.bsl +++ b/test/Microsoft.Restier.TestCommon/PublicApi.bsl @@ -1,39 +1,21 @@ public abstract class Microsoft.Restier.Core.ApiBase : IDisposable { protected ApiBase () - Microsoft.Restier.Core.ApiConfiguration Configuration { protected get; } + Microsoft.Restier.Core.ApiConfiguration Configuration { public get; public set; } Microsoft.Restier.Core.ApiContext Context { public get; } bool IsDisposed { [CompilerGeneratedAttribute(),]public get; } [ CLSCompliantAttribute(), ] - protected virtual Microsoft.Extensions.DependencyInjection.IServiceCollection ConfigureApi (Microsoft.Extensions.DependencyInjection.IServiceCollection services) - - [ - CLSCompliantAttribute(), - ] - protected virtual Microsoft.Restier.Core.ApiConfiguration CreateApiConfiguration (Microsoft.Extensions.DependencyInjection.IServiceCollection services) + public virtual Microsoft.Extensions.DependencyInjection.IServiceCollection ConfigureApi (Microsoft.Extensions.DependencyInjection.IServiceCollection services) protected virtual Microsoft.Restier.Core.ApiContext CreateApiContext (Microsoft.Restier.Core.ApiConfiguration configuration) public virtual void Dispose () -} - -[ -AttributeUsageAttribute(), -SerializableAttribute(), -] -public abstract class Microsoft.Restier.Core.ApiConfiguratorAttribute : System.Attribute, _Attribute { - protected ApiConfiguratorAttribute () - [ CLSCompliantAttribute(), ] - public virtual void AddApiServices (Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Type type) - - public virtual void Dispose (Microsoft.Restier.Core.ApiContext context, System.Type type, object instance) - public virtual void UpdateApiConfiguration (Microsoft.Restier.Core.ApiConfiguration configuration, System.Type type) - public virtual void UpdateApiContext (Microsoft.Restier.Core.ApiContext context, System.Type type, object instance) + protected virtual void UpdateApiConfiguration (Microsoft.Restier.Core.ApiConfiguration configuration) } [ @@ -215,11 +197,6 @@ CLSCompliantAttribute(), ExtensionAttribute(), ] public sealed class Microsoft.Restier.Core.ServiceCollectionExtensions { - [ - ExtensionAttribute(), - ] - public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddAttributeServices (Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Type apiType) - [ ExtensionAttribute(), ] @@ -282,9 +259,11 @@ public class Microsoft.Restier.Core.ApiContext { } public class Microsoft.Restier.Core.InvocationContext { + public InvocationContext () public InvocationContext (Microsoft.Restier.Core.ApiContext apiContext) Microsoft.Restier.Core.ApiContext ApiContext { [CompilerGeneratedAttribute(),]public get; } + System.IServiceProvider ServiceProvider { [CompilerGeneratedAttribute(),]public get; [CompilerGeneratedAttribute(),]public set; } } [ @@ -314,6 +293,14 @@ public class Microsoft.Restier.Core.ResourceNotFoundException : System.Exception public ResourceNotFoundException (string message, System.Exception innerException) } +public class Microsoft.Restier.Core.RestierContainerBuilder : IContainerBuilder { + public RestierContainerBuilder (System.Func`1[[Microsoft.Restier.Core.ApiBase]] apiFactory) + + public virtual Microsoft.OData.IContainerBuilder AddService (Microsoft.OData.ServiceLifetime lifetime, System.Type serviceType, System.Func`2[[System.IServiceProvider],[System.Object]] implementationFactory) + public virtual Microsoft.OData.IContainerBuilder AddService (Microsoft.OData.ServiceLifetime lifetime, System.Type serviceType, System.Type implementationType) + public virtual System.IServiceProvider BuildContainer () +} + public interface Microsoft.Restier.Core.Model.IModelBuilder { System.Threading.Tasks.Task`1[[Microsoft.OData.Edm.IEdmModel]] GetModelAsync (Microsoft.Restier.Core.Model.ModelContext context, System.Threading.CancellationToken cancellationToken) } @@ -324,7 +311,7 @@ public interface Microsoft.Restier.Core.Model.IModelMapper { } public class Microsoft.Restier.Core.Model.ModelContext : Microsoft.Restier.Core.InvocationContext { - public ModelContext (Microsoft.Restier.Core.ApiContext apiContext) + public ModelContext () System.Collections.Generic.IDictionary`2[[System.String],[System.Type]] ResourceSetTypeMap { [CompilerGeneratedAttribute(),]public get; [CompilerGeneratedAttribute(),]public set; } System.Collections.Generic.IDictionary`2[[System.Type],[System.Collections.Generic.ICollection`1[[System.Reflection.PropertyInfo]]]] ResourceTypeKeyPropertiesMap { [CompilerGeneratedAttribute(),]public get; [CompilerGeneratedAttribute(),]public set; } @@ -344,13 +331,15 @@ public interface Microsoft.Restier.Core.Operation.IOperationFilter { } public class Microsoft.Restier.Core.Operation.OperationContext : Microsoft.Restier.Core.InvocationContext { - public OperationContext (Microsoft.Restier.Core.ApiContext apiContext, System.Func`2[[System.String],[System.Object]] getParameterValueFunc, string operationName, bool isFunction, System.Collections.IEnumerable bindingParameterValue) + public OperationContext (System.Func`2[[System.String],[System.Object]] getParameterValueFunc, string operationName, object implementInstance, bool isFunction, System.Collections.IEnumerable bindingParameterValue) System.Collections.IEnumerable BindingParameterValue { public get; } System.Func`2[[System.String],[System.Object]] GetParameterValueFunc { public get; } + object ImplementInstance { public get; } bool IsFunction { public get; } string OperationName { public get; } System.Collections.Generic.ICollection`1[[System.Object]] ParameterValues { public get; public set; } + System.Net.Http.HttpRequestMessage Request { [CompilerGeneratedAttribute(),]public get; [CompilerGeneratedAttribute(),]public set; } } public interface Microsoft.Restier.Core.Query.IQueryExecutor { @@ -518,7 +507,6 @@ public class Microsoft.Restier.Core.Submit.SubmitContext : Microsoft.Restier.Cor public SubmitContext (Microsoft.Restier.Core.ApiContext apiContext, Microsoft.Restier.Core.Submit.ChangeSet changeSet) Microsoft.Restier.Core.Submit.ChangeSet ChangeSet { public get; public set; } - Microsoft.OData.Edm.IEdmModel Model { [CompilerGeneratedAttribute(),]public get; } Microsoft.Restier.Core.Submit.SubmitResult Result { public get; public set; } } @@ -584,7 +572,7 @@ public class Microsoft.Restier.Providers.EntityFramework.EntityFrameworkApi`1 : [ CLSCompliantAttribute(), ] - protected virtual Microsoft.Extensions.DependencyInjection.IServiceCollection ConfigureApi (Microsoft.Extensions.DependencyInjection.IServiceCollection services) + public virtual Microsoft.Extensions.DependencyInjection.IServiceCollection ConfigureApi (Microsoft.Extensions.DependencyInjection.IServiceCollection services) } [ @@ -625,8 +613,8 @@ public sealed class Microsoft.Restier.Publishers.OData.ServiceCollectionExtensio } [ +ODataFormattingAttribute(), RestierExceptionFilterAttribute(), -RestierFormattingAttribute(), ] public class Microsoft.Restier.Publishers.OData.RestierController : System.Web.OData.ODataController, IDisposable, IHttpController { public RestierController () @@ -663,7 +651,7 @@ public class Microsoft.Restier.Publishers.OData.RestierController : System.Web.O public System.Threading.Tasks.Task`1[[System.Web.Http.IHttpActionResult]] Put (System.Web.OData.EdmEntityObject edmEntityObject, System.Threading.CancellationToken cancellationToken) } -public class Microsoft.Restier.Publishers.OData.RestierPayloadValueConverter : Microsoft.OData.Core.ODataPayloadValueConverter { +public class Microsoft.Restier.Publishers.OData.RestierPayloadValueConverter : Microsoft.OData.ODataPayloadValueConverter { public RestierPayloadValueConverter () public virtual object ConvertToPayloadValue (object value, Microsoft.OData.Edm.IEdmTypeReference edmTypeReference) @@ -691,53 +679,53 @@ public class Microsoft.Restier.Publishers.OData.Batch.RestierBatchHandler : Syst } public class Microsoft.Restier.Publishers.OData.Formatter.DefaultRestierDeserializerProvider : System.Web.OData.Formatter.Deserialization.DefaultODataDeserializerProvider { - public DefaultRestierDeserializerProvider () + public DefaultRestierDeserializerProvider (System.IServiceProvider rootContainer) public virtual System.Web.OData.Formatter.Deserialization.ODataEdmTypeDeserializer GetEdmTypeDeserializer (Microsoft.OData.Edm.IEdmTypeReference edmType) } public class Microsoft.Restier.Publishers.OData.Formatter.DefaultRestierSerializerProvider : System.Web.OData.Formatter.Serialization.DefaultODataSerializerProvider { - public DefaultRestierSerializerProvider () + public DefaultRestierSerializerProvider (System.IServiceProvider rootContainer) public virtual System.Web.OData.Formatter.Serialization.ODataEdmTypeSerializer GetEdmTypeSerializer (Microsoft.OData.Edm.IEdmTypeReference edmType) - public virtual System.Web.OData.Formatter.Serialization.ODataSerializer GetODataPayloadSerializer (Microsoft.OData.Edm.IEdmModel model, System.Type type, System.Net.Http.HttpRequestMessage request) + public virtual System.Web.OData.Formatter.Serialization.ODataSerializer GetODataPayloadSerializer (System.Type type, System.Net.Http.HttpRequestMessage request) } public class Microsoft.Restier.Publishers.OData.Formatter.RestierCollectionSerializer : System.Web.OData.Formatter.Serialization.ODataCollectionSerializer { public RestierCollectionSerializer (System.Web.OData.Formatter.Serialization.ODataSerializerProvider provider) - public virtual void WriteObject (object graph, System.Type type, Microsoft.OData.Core.ODataMessageWriter messageWriter, System.Web.OData.Formatter.Serialization.ODataSerializerContext writeContext) + public virtual void WriteObject (object graph, System.Type type, Microsoft.OData.ODataMessageWriter messageWriter, System.Web.OData.Formatter.Serialization.ODataSerializerContext writeContext) } -public class Microsoft.Restier.Publishers.OData.Formatter.RestierComplexTypeSerializer : System.Web.OData.Formatter.Serialization.ODataComplexTypeSerializer { - public RestierComplexTypeSerializer (System.Web.OData.Formatter.Serialization.ODataSerializerProvider provider) +public class Microsoft.Restier.Publishers.OData.Formatter.RestierEnumSerializer : System.Web.OData.Formatter.Serialization.ODataEnumSerializer { + public RestierEnumSerializer (System.Web.OData.Formatter.Serialization.ODataSerializerProvider provider) - public virtual void WriteObject (object graph, System.Type type, Microsoft.OData.Core.ODataMessageWriter messageWriter, System.Web.OData.Formatter.Serialization.ODataSerializerContext writeContext) + public virtual void WriteObject (object graph, System.Type type, Microsoft.OData.ODataMessageWriter messageWriter, System.Web.OData.Formatter.Serialization.ODataSerializerContext writeContext) } -public class Microsoft.Restier.Publishers.OData.Formatter.RestierEnumSerializer : System.Web.OData.Formatter.Serialization.ODataEnumSerializer { - public RestierEnumSerializer () +public class Microsoft.Restier.Publishers.OData.Formatter.RestierPrimitiveSerializer : System.Web.OData.Formatter.Serialization.ODataPrimitiveSerializer { + public RestierPrimitiveSerializer () - public virtual void WriteObject (object graph, System.Type type, Microsoft.OData.Core.ODataMessageWriter messageWriter, System.Web.OData.Formatter.Serialization.ODataSerializerContext writeContext) + public virtual Microsoft.OData.ODataPrimitiveValue CreateODataPrimitiveValue (object graph, Microsoft.OData.Edm.IEdmPrimitiveTypeReference primitiveType, System.Web.OData.Formatter.Serialization.ODataSerializerContext writeContext) + public virtual void WriteObject (object graph, System.Type type, Microsoft.OData.ODataMessageWriter messageWriter, System.Web.OData.Formatter.Serialization.ODataSerializerContext writeContext) } -public class Microsoft.Restier.Publishers.OData.Formatter.RestierFeedSerializer : System.Web.OData.Formatter.Serialization.ODataFeedSerializer { - public RestierFeedSerializer (System.Web.OData.Formatter.Serialization.ODataSerializerProvider provider) +public class Microsoft.Restier.Publishers.OData.Formatter.RestierRawSerializer : System.Web.OData.Formatter.Serialization.ODataRawValueSerializer { + public RestierRawSerializer () - public virtual void WriteObject (object graph, System.Type type, Microsoft.OData.Core.ODataMessageWriter messageWriter, System.Web.OData.Formatter.Serialization.ODataSerializerContext writeContext) + public virtual void WriteObject (object graph, System.Type type, Microsoft.OData.ODataMessageWriter messageWriter, System.Web.OData.Formatter.Serialization.ODataSerializerContext writeContext) } -public class Microsoft.Restier.Publishers.OData.Formatter.RestierPrimitiveSerializer : System.Web.OData.Formatter.Serialization.ODataPrimitiveSerializer { - public RestierPrimitiveSerializer () +public class Microsoft.Restier.Publishers.OData.Formatter.RestierResourceSerializer : System.Web.OData.Formatter.Serialization.ODataResourceSerializer { + public RestierResourceSerializer (System.Web.OData.Formatter.Serialization.ODataSerializerProvider provider) - public virtual Microsoft.OData.Core.ODataPrimitiveValue CreateODataPrimitiveValue (object graph, Microsoft.OData.Edm.IEdmPrimitiveTypeReference primitiveType, System.Web.OData.Formatter.Serialization.ODataSerializerContext writeContext) - public virtual void WriteObject (object graph, System.Type type, Microsoft.OData.Core.ODataMessageWriter messageWriter, System.Web.OData.Formatter.Serialization.ODataSerializerContext writeContext) + public virtual void WriteObject (object graph, System.Type type, Microsoft.OData.ODataMessageWriter messageWriter, System.Web.OData.Formatter.Serialization.ODataSerializerContext writeContext) } -public class Microsoft.Restier.Publishers.OData.Formatter.RestierRawSerializer : System.Web.OData.Formatter.Serialization.ODataRawValueSerializer { - public RestierRawSerializer () +public class Microsoft.Restier.Publishers.OData.Formatter.RestierResourceSetSerializer : System.Web.OData.Formatter.Serialization.ODataResourceSetSerializer { + public RestierResourceSetSerializer (System.Web.OData.Formatter.Serialization.ODataSerializerProvider provider) - public virtual void WriteObject (object graph, System.Type type, Microsoft.OData.Core.ODataMessageWriter messageWriter, System.Web.OData.Formatter.Serialization.ODataSerializerContext writeContext) + public virtual void WriteObject (object graph, System.Type type, Microsoft.OData.ODataMessageWriter messageWriter, System.Web.OData.Formatter.Serialization.ODataSerializerContext writeContext) } [ diff --git a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Northwind.Tests/Baselines/TestOrdersEntitySetAutoExpandQuery.txt b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Northwind.Tests/Baselines/TestOrdersEntitySetAutoExpandQuery.txt index 9da0046d..89c2de02 100644 --- a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Northwind.Tests/Baselines/TestOrdersEntitySetAutoExpandQuery.txt +++ b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Northwind.Tests/Baselines/TestOrdersEntitySetAutoExpandQuery.txt @@ -1 +1 @@ -{"@odata.context":"http://localhost/api/Northwind/$metadata#Orders","value":[{"OrderID":10248,"CustomerID":"VINET","EmployeeID":5,"OrderDate":"1996-07-04","RequiredDate":"1996-08-01","ShippedDate":"1996-07-16","ShipVia":3,"Freight":32.3800,"ShipName":"Vins et alcools Chevalier","ShipAddress":"59 rue de l'Abbaye","ShipCity":"Reims","ShipRegion":null,"ShipPostalCode":"51100","ShipCountry":"France"},{"OrderID":10249,"CustomerID":"TOMSP","EmployeeID":6,"OrderDate":"1996-07-05","RequiredDate":"1996-08-16","ShippedDate":"1996-07-10","ShipVia":1,"Freight":11.6100,"ShipName":"Toms Spezialit\u00e4ten","ShipAddress":"Luisenstr. 48","ShipCity":"M\u00fcnster","ShipRegion":null,"ShipPostalCode":"44087","ShipCountry":"Germany"},{"OrderID":10250,"CustomerID":"HANAR","EmployeeID":4,"OrderDate":"1996-07-08","RequiredDate":"1996-08-05","ShippedDate":"1996-07-12","ShipVia":2,"Freight":65.8300,"ShipName":"Hanari Carnes","ShipAddress":"Rua do Pa\u00e7o, 67","ShipCity":"Rio de Janeiro","ShipRegion":"RJ","ShipPostalCode":"05454-876","ShipCountry":"Brazil"},{"OrderID":10251,"CustomerID":"VICTE","EmployeeID":3,"OrderDate":"1996-07-08","RequiredDate":"1996-08-05","ShippedDate":"1996-07-15","ShipVia":1,"Freight":41.3400,"ShipName":"Victuailles en stock","ShipAddress":"2, rue du Commerce","ShipCity":"Lyon","ShipRegion":null,"ShipPostalCode":"69004","ShipCountry":"France"},{"OrderID":10252,"CustomerID":"SUPRD","EmployeeID":4,"OrderDate":"1996-07-09","RequiredDate":"1996-08-06","ShippedDate":"1996-07-11","ShipVia":2,"Freight":51.3000,"ShipName":"Supr\u00eames d\u00e9lices","ShipAddress":"Boulevard Tirou, 255","ShipCity":"Charleroi","ShipRegion":null,"ShipPostalCode":"B-6000","ShipCountry":"Belgium"}]} \ No newline at end of file +{"@odata.context":"http://localhost/api/Northwind/$metadata#Orders","value":[{"OrderID":10248,"CustomerID":"VINET","EmployeeID":5,"OrderDate":"1996-07-04","RequiredDate":"1996-08-01","ShippedDate":"1996-07-16","ShipVia":3,"Freight":32.3800,"ShipName":"Vins et alcools Chevalier","ShipAddress":"59 rue de l'Abbaye","ShipCity":"Reims","ShipRegion":null,"ShipPostalCode":"51100","ShipCountry":"France","Order_Details":[{"OrderID":10248,"ProductID":11,"UnitPrice":14.0000,"Quantity":12,"Discount":0},{"OrderID":10248,"ProductID":42,"UnitPrice":9.8000,"Quantity":10,"Discount":0},{"OrderID":10248,"ProductID":72,"UnitPrice":34.8000,"Quantity":5,"Discount":0}]},{"OrderID":10249,"CustomerID":"TOMSP","EmployeeID":6,"OrderDate":"1996-07-05","RequiredDate":"1996-08-16","ShippedDate":"1996-07-10","ShipVia":1,"Freight":11.6100,"ShipName":"Toms Spezialit\u00e4ten","ShipAddress":"Luisenstr. 48","ShipCity":"M\u00fcnster","ShipRegion":null,"ShipPostalCode":"44087","ShipCountry":"Germany","Order_Details":[{"OrderID":10249,"ProductID":14,"UnitPrice":18.6000,"Quantity":9,"Discount":0},{"OrderID":10249,"ProductID":51,"UnitPrice":42.4000,"Quantity":40,"Discount":0}]},{"OrderID":10250,"CustomerID":"HANAR","EmployeeID":4,"OrderDate":"1996-07-08","RequiredDate":"1996-08-05","ShippedDate":"1996-07-12","ShipVia":2,"Freight":65.8300,"ShipName":"Hanari Carnes","ShipAddress":"Rua do Pa\u00e7o, 67","ShipCity":"Rio de Janeiro","ShipRegion":"RJ","ShipPostalCode":"05454-876","ShipCountry":"Brazil","Order_Details":[{"OrderID":10250,"ProductID":41,"UnitPrice":7.7000,"Quantity":10,"Discount":0},{"OrderID":10250,"ProductID":51,"UnitPrice":42.4000,"Quantity":35,"Discount":0.15},{"OrderID":10250,"ProductID":65,"UnitPrice":16.8000,"Quantity":15,"Discount":0.15}]},{"OrderID":10251,"CustomerID":"VICTE","EmployeeID":3,"OrderDate":"1996-07-08","RequiredDate":"1996-08-05","ShippedDate":"1996-07-15","ShipVia":1,"Freight":41.3400,"ShipName":"Victuailles en stock","ShipAddress":"2, rue du Commerce","ShipCity":"Lyon","ShipRegion":null,"ShipPostalCode":"69004","ShipCountry":"France","Order_Details":[{"OrderID":10251,"ProductID":22,"UnitPrice":16.8000,"Quantity":6,"Discount":0.05},{"OrderID":10251,"ProductID":57,"UnitPrice":15.6000,"Quantity":15,"Discount":0.05},{"OrderID":10251,"ProductID":65,"UnitPrice":16.8000,"Quantity":20,"Discount":0}]},{"OrderID":10252,"CustomerID":"SUPRD","EmployeeID":4,"OrderDate":"1996-07-09","RequiredDate":"1996-08-06","ShippedDate":"1996-07-11","ShipVia":2,"Freight":51.3000,"ShipName":"Supr\u00eames d\u00e9lices","ShipAddress":"Boulevard Tirou, 255","ShipCity":"Charleroi","ShipRegion":null,"ShipPostalCode":"B-6000","ShipCountry":"Belgium","Order_Details":[{"OrderID":10252,"ProductID":20,"UnitPrice":64.8000,"Quantity":40,"Discount":0.05},{"OrderID":10252,"ProductID":33,"UnitPrice":2.0000,"Quantity":25,"Discount":0.05},{"OrderID":10252,"ProductID":60,"UnitPrice":27.2000,"Quantity":40,"Discount":0}]}]} \ No newline at end of file diff --git a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Northwind.Tests/ODataFeedTests.cs b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Northwind.Tests/ODataFeedTests.cs index a3a260e7..be75c8ea 100644 --- a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Northwind.Tests/ODataFeedTests.cs +++ b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Northwind.Tests/ODataFeedTests.cs @@ -77,6 +77,14 @@ public async Task TestBatch() { int id = ODataFeedTests.InsertTestProduct(); + NorthwindContext ctx2 = GetDbContext(); + Product[] insertedProducts2 = ctx2.Products + .Where(b => b.ProductName == "Horizon" || b.ProductName == "Commons") + .OrderBy(b => b.ProductName) + .ToArray(); + ctx2.Products.RemoveRange(insertedProducts2); + ctx2.SaveChanges(); + string batchContentString = @"--batch_35114042-958d-48fd-8189-bd93264b31de Content-Type: multipart/mixed; boundary=changeset_3ffaecfa-069f-4ad7-bb41-bcc2481ea0dd @@ -94,7 +102,7 @@ public async Task TestBatch() Accept-Charset: UTF-8 User-Agent: Microsoft ADO.NET Data Services -{""@odata.type"":""#Microsoft.OData.Service.Sample.Northwind.Models.Product"",""CategoryID"":null,""Discontinued"":false,""ProductID"":0,""ProductName"":""Horizon"",""QuantityPerUnit"":""4"",""ReorderLevel"":10,""SupplierID"":null,""UnitPrice"":2.5,""UnitsInStock"":100,""UnitsOnOrder"":0} +{'@odata.type':'#Microsoft.OData.Service.Sample.Northwind.Models.Product','CategoryID':null,'Discontinued':false,'ProductID':0,'ProductName':'Horizon','QuantityPerUnit':'4','ReorderLevel':10,'SupplierID':null,'UnitPrice':2.5,'UnitsInStock':100,'UnitsOnOrder':0} --changeset_3ffaecfa-069f-4ad7-bb41-bcc2481ea0dd Content-Type: application/http Content-Transfer-Encoding: binary @@ -108,7 +116,7 @@ public async Task TestBatch() Accept-Charset: UTF-8 User-Agent: Microsoft ADO.NET Data Services -{""@odata.type"":""#Microsoft.OData.Service.Sample.Northwind.Models.Product"",""CategoryID"":null,""Discontinued"":true,""ProductID"":0,""ProductName"":""Commons"",""QuantityPerUnit"":""5"",""ReorderLevel"":11,""SupplierID"":null,""UnitPrice"":15.99,""UnitsInStock"":200,""UnitsOnOrder"":10} +{'@odata.type':'#Microsoft.OData.Service.Sample.Northwind.Models.Product','CategoryID':null,'Discontinued':true,'ProductID':0,'ProductName':'Commons','QuantityPerUnit':'5','ReorderLevel':11,'SupplierID':null,'UnitPrice':15.99,'UnitsInStock':200,'UnitsOnOrder':10} --changeset_3ffaecfa-069f-4ad7-bb41-bcc2481ea0dd Content-Type: application/http Content-Transfer-Encoding: binary diff --git a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Northwind.Tests/SaveTests.cs b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Northwind.Tests/SaveTests.cs index 5203a8a5..a6170dda 100644 --- a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Northwind.Tests/SaveTests.cs +++ b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Northwind.Tests/SaveTests.cs @@ -32,7 +32,6 @@ public override IServiceCollection ConfigureApi(IServiceCollection services) Type apiType = this.GetType(); // Add core and convention's services services = services.AddCoreServices(apiType) - .AddAttributeServices(apiType) .AddConventionBasedServices(apiType); // Add EF related services services.AddEfProviderServices(); @@ -56,7 +55,7 @@ protected async Task OnInsertingCustomers(Customer customer) public async Task TestEntityFilterReturnsTask() { TestEntityFilterReturnsTaskApi api = new TestEntityFilterReturnsTaskApi(); - var container = new RestierContainerBuilder(api); + var container = new RestierContainerBuilder(() => new TestEntityFilterReturnsTaskApi()); api.Configuration = new ApiConfiguration(container.BuildContainer()); DataModificationItem createCustomer = new DataModificationItem( "Customers", diff --git a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Northwind/App_Start/WebApiConfig.cs b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Northwind/App_Start/WebApiConfig.cs index 6d3e8adb..775683b0 100644 --- a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Northwind/App_Start/WebApiConfig.cs +++ b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Northwind/App_Start/WebApiConfig.cs @@ -32,6 +32,8 @@ public static void Register(HttpConfiguration config) public static void RegisterNorthwind( HttpConfiguration config, HttpServer server) { + config.Filter().Expand().Select().OrderBy().MaxTop(null).Count(); + config.SetUseVerboseErrors(true); config.MapRestierRoute( "NorthwindApi", "api/Northwind", new RestierBatchHandler(server)); diff --git a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Trippin/App_Start/WebApiConfig.cs b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Trippin/App_Start/WebApiConfig.cs index 19e85702..cc57183a 100644 --- a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Trippin/App_Start/WebApiConfig.cs +++ b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Trippin/App_Start/WebApiConfig.cs @@ -1,8 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. +using System; using System.Web.Http; using System.Web.OData; +using System.Web.OData.Extensions; using Microsoft.OData.Service.Sample.Trippin.Api; using Microsoft.Restier.Publishers.OData; using Microsoft.Restier.Publishers.OData.Batch; @@ -20,6 +22,9 @@ public static void Register(HttpConfiguration config) public static async void RegisterTrippin( HttpConfiguration config, HttpServer server) { + // enable query options for all properties + config.Filter().Expand().Select().OrderBy().MaxTop(null).Count(); + await config.MapRestierRoute( "TrippinApi", "api/Trippin", new RestierBatchHandler(server));