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 23, 2016
1 parent 673d520 commit beb0d62
Show file tree
Hide file tree
Showing 33 changed files with 240 additions and 279 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
59 changes: 23 additions & 36 deletions src/Microsoft.Restier.Core/ApiBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,48 +78,23 @@ public ApiConfiguration Configuration
set
{
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,13 +106,25 @@ 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;
}

this.IsDisposed = true;

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

GC.SuppressFinalize(this);
}

/// <summary>
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
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

This file was deleted.

21 changes: 21 additions & 0 deletions src/Microsoft.Restier.Publishers.OData/Model/ResourceAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System;

namespace Microsoft.Restier.Publishers.OData.Model
{
/// <summary>
/// Attribute that indicates a property is an entity set or singleton.
/// The name will be same as property name.
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public sealed class ResourceAttribute : Attribute
{
/// <summary>
/// Gets or sets a value indicating whether it is singleton or entity set.
/// The default value is false means it is an entity set
/// </summary>
public bool IsSingleton { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -176,27 +176,27 @@ private void ScanForDeclaredPublicProperties()
}
}

private void BuildEntitySetsAndSingletons(ModelContext context, EdmModel model)
private void BuildEntitySetsAndSingletons(EdmModel model)
{
var configuration = context.ServiceProvider.GetService<ApiConfiguration>();
foreach (var property in this.publicProperties)
{
if (configuration.IsPropertyIgnored(property.Name))
var resourceAttribute = property.GetCustomAttributes<ResourceAttribute>(true).FirstOrDefault();
if (resourceAttribute == null)
{
continue;
}

var isEntitySet = IsEntitySetProperty(property);
if (!isEntitySet)
bool isSingleton = resourceAttribute.IsSingleton;
if ((!isSingleton && !IsEntitySetProperty(property))
|| (isSingleton && !IsSingletonProperty(property)))
{
if (!IsSingletonProperty(property))
{
continue;
}
// This means property type is not IQueryable<T> when indicating an entityset
// or not non-generic type when indicating a singleton
continue;
}

var propertyType = property.PropertyType;
if (isEntitySet)
if (!isSingleton)
{
propertyType = propertyType.GetGenericArguments()[0];
}
Expand All @@ -209,7 +209,7 @@ private void BuildEntitySetsAndSingletons(ModelContext context, EdmModel model)
}

var container = model.EnsureEntityContainer(this.targetType);
if (isEntitySet)
if (!isSingleton)
{
if (container.FindEntitySet(property.Name) == null)
{
Expand Down Expand Up @@ -341,7 +341,7 @@ public async Task<IEdmModel> GetModelAsync(ModelContext context, CancellationTok
}

ModelCache.ScanForDeclaredPublicProperties();
ModelCache.BuildEntitySetsAndSingletons(context, edmModel);
ModelCache.BuildEntitySetsAndSingletons(edmModel);
ModelCache.AddNavigationPropertyBindings(edmModel);
return edmModel;
}
Expand Down
Loading

0 comments on commit beb0d62

Please sign in to comment.