Skip to content

Commit

Permalink
Add resource attribute
Browse files Browse the repository at this point in the history
    Make ConfigureApi static
    Add ApiBase,ApiContext as DI service
    Make container does not require ApiFactory
  • Loading branch information
chinadragon0515 committed Aug 24, 2016
1 parent 673d520 commit 32cf19d
Show file tree
Hide file tree
Showing 34 changed files with 397 additions and 390 deletions.
1 change: 1 addition & 0 deletions src/GlobalSuppressions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Diagnostics.CodeAnalysis;

#region Permanent Exclusions
[assembly: SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes", Scope = "member", Target = "Microsoft.Restier.Providers.EntityFramework.EntityFrameworkApi`1.#ConfigureApi(System.Type,Microsoft.Extensions.DependencyInjection.IServiceCollection)")]
[assembly: SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Scope = "member", Target = "Microsoft.Restier.Core.Query.QueryRequest.#Create`2(System.Linq.IQueryable`1<!!0>,System.Linq.Expressions.Expression`1<System.Func`2<System.Linq.IQueryable`1<!!0>,!!1>>,System.Nullable`1<System.Boolean>)")]
[assembly: SuppressMessage("Microsoft.Design", "CA1032:ImplementStandardExceptionConstructors", Scope = "type", Target = "Microsoft.Restier.Core.Submit.ChangeSetValidationException", Justification = "We do not intend to support serialization of this exception yet")]
[assembly: SuppressMessage("Microsoft.Design", "CA1061:DoNotHideBaseClassMethods", Scope = "member", Target = "Microsoft.Restier.Publishers.OData.Batch.RestierBatchChangeSetRequestItem.#DisposeResponses(System.Collections.Generic.IEnumerable`1<System.Net.Http.HttpResponseMessage>)")]
Expand Down
104 changes: 43 additions & 61 deletions src/Microsoft.Restier.Core/ApiBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,24 @@ public abstract class ApiBase : IDisposable

private ApiConfiguration apiConfiguration;
private ApiContext apiContext;
private IServiceProvider serviceProvider;

/// <summary>
/// Gets the <see cref="IServiceProvider"/> which contains all services of this <see cref="ApiConfiguration"/>.
/// </summary>
public IServiceProvider ServiceProvider
{
get
{
return serviceProvider;
}

set
{
// TODO use set but not in constructor as need to update lots of test cases
this.serviceProvider = value;
}
}

/// <summary>
/// Gets the API context for this API.
Expand All @@ -41,13 +59,7 @@ public ApiContext Context

if (this.apiContext == null)
{
this.apiContext = this.CreateApiContext(
this.Configuration);
var apiScope = this.apiContext.GetApiService<ApiHolder>();
if (apiScope != null)
{
apiScope.Api = this;
}
this.apiContext = serviceProvider.GetService<ApiContext>();
}

return this.apiContext;
Expand All @@ -66,60 +78,35 @@ public ApiConfiguration Configuration
{
get
{
if (this.apiConfiguration != null)
if (this.apiConfiguration == null)
{
return this.apiConfiguration;
this.apiConfiguration = serviceProvider.GetService<ApiConfiguration>();
}

Configurations.TryGetValue(this.GetType(), out this.apiConfiguration);
return this.apiConfiguration;
}

set
{
// TODO keep now as lots of test cases need to update
this.apiConfiguration = value;
bool isSuccess = Configurations.TryAdd(GetType(), apiConfiguration);
if (isSuccess)
{
UpdateApiConfiguration(this.apiConfiguration);
}
}
}

/// <summary>
/// Performs application-defined tasks associated with
/// freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
if (this.IsDisposed)
{
return;
}

this.IsDisposed = true;

if (this.apiContext != null)
{
this.apiContext.DisposeScope();
this.apiContext = null;
Configurations.TryAdd(GetType(), apiConfiguration);
}

GC.SuppressFinalize(this);
}

/// <summary>
/// Configure services for this API.
/// </summary>
/// <param name="apiType">
/// The Api type.
/// </param>
/// <param name="services">
/// The <see cref="IServiceCollection"/> with which to create an <see cref="ApiConfiguration"/>.
/// The <see cref="IServiceCollection"/> with which is used to store all services.
/// </param>
/// <returns>The <see cref="IServiceCollection"/>.</returns>
[CLSCompliant(false)]
public virtual IServiceCollection ConfigureApi(IServiceCollection services)
public static IServiceCollection ConfigureApi(Type apiType, IServiceCollection services)
{
Type apiType = this.GetType();

// Add core and convention's services
services = services.AddCoreServices(apiType)
.AddConventionBasedServices(apiType);
Expand All @@ -131,29 +118,24 @@ public virtual IServiceCollection ConfigureApi(IServiceCollection services)
}

/// <summary>
/// Allow user to update the ApiConfiguration
/// <see cref="ApiConfiguration"/>.
/// Performs application-defined tasks associated with
/// freeing, releasing, or resetting unmanaged resources.
/// </summary>
/// <param name="configuration">The <see cref="ApiConfiguration"/> for the Api instance.</param>
[CLSCompliant(false)]
protected virtual void UpdateApiConfiguration(ApiConfiguration configuration)
public void Dispose()
{
}
if (this.IsDisposed)
{
return;
}

/// <summary>
/// Creates the API context for this API.
/// Descendants may further configure the built <see cref="ApiContext"/>.
/// </summary>
/// <param name="configuration">
/// The API configuration to use.
/// </param>
/// <returns>
/// The API context for this API.
/// </returns>
protected virtual ApiContext CreateApiContext(
ApiConfiguration configuration)
{
return new ApiContext(configuration);
this.IsDisposed = true;

if (this.apiContext != null)
{
this.apiContext = null;
}

GC.SuppressFinalize(this);
}

// Registered as a scoped service so that IApi and ApiContext could be exposed as scoped service.
Expand Down
13 changes: 7 additions & 6 deletions src/Microsoft.Restier.Core/ApiConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,19 +56,20 @@ internal IServiceProvider ServiceProvider
internal IEdmModel Model { get; private set; }

/// <summary>
/// Adds a configuration procedure for API type <typeparamref name="TApi"/>.
/// Adds a configuration procedure for apiType.
/// This is expected to be called by publisher like WebApi to add services.
/// </summary>
/// <typeparam name="TApi">The API type.</typeparam>
/// <param name="apiType">
/// The Api Type.
/// </param>
/// <param name="configurationCallback">
/// An action that will be called during the configuration of <typeparamref name="TApi"/>.
/// An action that will be called during the configuration of apiType.
/// </param>
[CLSCompliant(false)]
public static void AddPublisherServices<TApi>(Action<IServiceCollection> configurationCallback)
where TApi : ApiBase
public static void AddPublisherServices(Type apiType, Action<IServiceCollection> configurationCallback)
{
publisherServicesCallback.AddOrUpdate(
typeof(TApi),
apiType,
configurationCallback,
(type, existing) => existing + configurationCallback);
}
Expand Down
17 changes: 7 additions & 10 deletions src/Microsoft.Restier.Core/ApiContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,23 @@ namespace Microsoft.Restier.Core
/// </remarks>
public class ApiContext
{
private readonly IServiceScope scope;
private IServiceProvider serviceProvider;

/// <summary>
/// Initializes a new instance of the <see cref="ApiContext" /> class.
/// </summary>
/// <param name="configuration">
/// An API configuration.
/// </param>
public ApiContext(ApiConfiguration configuration)
/// <param name="provider">
/// The service provider.
/// </param>
public ApiContext(IServiceProvider provider, ApiConfiguration configuration)
{
Ensure.NotNull(configuration, "configuration");

this.Configuration = configuration;
this.scope = configuration.ServiceProvider
.GetRequiredService<IServiceScopeFactory>().CreateScope();
this.serviceProvider = provider;
}

/// <summary>
Expand All @@ -41,12 +43,7 @@ public ApiContext(ApiConfiguration configuration)
/// </summary>
internal IServiceProvider ServiceProvider
{
get { return this.scope.ServiceProvider; }
}

internal void DisposeScope()
{
this.scope.Dispose();
get { return this.serviceProvider; }
}
}
}
40 changes: 23 additions & 17 deletions src/Microsoft.Restier.Core/RestierContainerBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Globalization;
using System.Reflection;
using System.Threading;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.OData;
Expand All @@ -17,15 +18,15 @@ namespace Microsoft.Restier.Core
public class RestierContainerBuilder : IContainerBuilder
{
private readonly IServiceCollection services = new ServiceCollection();
private Func<ApiBase> apiFactory;
private Type apiType;

/// <summary>
/// Initializes a new instance of the <see cref="RestierContainerBuilder" /> class.
/// </summary>
/// <param name="apiFactory">The Api factory to create the Api instance</param>
public RestierContainerBuilder(Func<ApiBase> apiFactory)
/// <param name="apiType">The Api Type</param>
public RestierContainerBuilder(Type apiType)
{
this.apiFactory = apiFactory;
this.apiType = apiType;
}

/// <summary>
Expand Down Expand Up @@ -103,24 +104,29 @@ internal IContainerBuilder AddRestierService()
{
Func<IServiceProvider, IEdmModel> modelFactory = sp =>
{
using (var api = apiFactory())
{
var configuation = sp.GetService<ApiConfiguration>();
if (api.Configuration == null)
{
api.Configuration = configuation;
}

var model = api.Context.GetModelAsync(default(CancellationToken)).Result;
return model;
}
var context = sp.GetService<ApiContext>();
var model = context.GetModelAsync(default(CancellationToken)).Result;
return model;
};

using (var api = apiFactory())
// Configure the API via reflection call
var methodDeclaredType = apiType;

MethodInfo method = null;
while (method == null && methodDeclaredType != null)
{
api.ConfigureApi(services);
// In case the subclass does not override the method, call super class method
method = methodDeclaredType.GetMethod("ConfigureApi");
methodDeclaredType = methodDeclaredType.BaseType;
}

var parameters = new object[]
{
apiType, services
};

method.Invoke(null, parameters);

services.AddSingleton(modelFactory);
return this;
}
Expand Down
12 changes: 5 additions & 7 deletions src/Microsoft.Restier.Core/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -211,13 +211,11 @@ public static IServiceCollection MakeTransient<TService>(this IServiceCollection
/// <returns>Current <see cref="IServiceCollection"/></returns>
public static IServiceCollection AddCoreServices(this IServiceCollection services, Type apiType)
{
if (!services.HasService<ApiBase>())
{
services.AddScoped<ApiBase.ApiHolder>()
.AddScoped(apiType, sp => sp.GetService<ApiBase.ApiHolder>().Api)
.AddScoped(sp => sp.GetService<ApiBase.ApiHolder>().Api)
.AddScoped(sp => sp.GetService<ApiBase.ApiHolder>().Api.Context);
}
Ensure.NotNull(apiType, "apiType");

services.AddScoped(apiType, apiType)
.AddScoped(typeof(ApiBase), apiType)
.AddScoped<ApiContext>();

services.TryAddSingleton<ApiConfiguration>();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,17 +45,18 @@ protected T DbContext
/// Configures the API services for this API. Descendants may override this method to register
/// <typeparamref name="T"/> as a scoped service.
/// </summary>
/// <param name="apiType">
/// The Api type.
/// </param>
/// <param name="services">
/// The <see cref="IServiceCollection"/> with which to create an <see cref="ApiConfiguration"/>.
/// </param>
/// <returns>
/// The <see cref="IServiceCollection"/>.
/// </returns>
[CLSCompliant(false)]
public override IServiceCollection ConfigureApi(IServiceCollection services)
public static new IServiceCollection ConfigureApi(Type apiType, IServiceCollection services)
{
Type apiType = this.GetType();

// Add core and convention's services
services = services.AddCoreServices(apiType)
.AddConventionBasedServices(apiType);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,8 @@
<Compile Include="Batch\RestierBatchChangeSetRequestItem.cs" />
<Compile Include="Formatter\Deserialization\DeserializationHelpers.cs" />
<Compile Include="Formatter\Serialization\RestierResourceSerializer.cs" />
<Compile Include="Model\ApiConfigurationExtensions.cs" />
<Compile Include="Model\ModelMapper.cs" />
<Compile Include="Model\ResourceAttribute.cs" />
<Compile Include="Model\OperationAttribute.cs" />
<Compile Include="Model\PropertyAttributes.cs" />
<Compile Include="Model\RestierModelBuilder.cs" />
Expand Down
Loading

0 comments on commit 32cf19d

Please sign in to comment.