Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Revise routing to avoid route to entity set controller if there is no… #455

Merged
merged 2 commits into from Jun 28, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/GlobalSuppressions.cs
Expand Up @@ -37,7 +37,7 @@
#region CA1006 Nested Generic Type
[assembly: SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Scope = "member", Target = "Microsoft.Restier.Core.ApiBaseExtensions.#QueryAsync`1(Microsoft.Restier.Core.ApiBase,System.Linq.IQueryable`1<!!0>,System.Threading.CancellationToken)")]
[assembly: SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Scope = "member", Target = "Microsoft.Restier.Core.ApiBaseExtensions.#QueryAsync`2(Microsoft.Restier.Core.ApiBase,System.Linq.IQueryable`1<!!0>,System.Linq.Expressions.Expression`1<System.Func`2<System.Linq.IQueryable`1<!!0>,!!1>>,System.Threading.CancellationToken)")]
[assembly: SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Scope = "member", Target = "Microsoft.Restier.Core.Model.ModelContext.#EntityTypeKeyPropertiesMapDictionary")]
[assembly: SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Scope = "member", Target = "Microsoft.Restier.Core.Model.ModelContext.#EntityTypeKeyPropertiesMap")]
[assembly: SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", 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>)")]
#endregion

Expand Down Expand Up @@ -149,8 +149,8 @@
[assembly: SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly", Scope = "member", Target = "Microsoft.Restier.Core.ApiContext.#.ctor(Microsoft.Restier.Core.ApiConfiguration)")]
[assembly: SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly", Scope = "member", Target = "Microsoft.Restier.Core.QueryableSource.#System.Linq.IQueryProvider.CreateQuery`1(System.Linq.Expressions.Expression)")]
[assembly: SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly", Scope = "member", Target = "Microsoft.Restier.Core.QueryableSource.#System.Linq.IQueryProvider.CreateQuery(System.Linq.Expressions.Expression)")]
[assembly: SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Scope = "member", Target = "Microsoft.Restier.Core.Model.ModelContext.#EntitySetTypeMapDictionary")]
[assembly: SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Scope = "member", Target = "Microsoft.Restier.Core.Model.ModelContext.#EntityTypeKeyPropertiesMapDictionary")]
[assembly: SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Scope = "member", Target = "Microsoft.Restier.Core.Model.ModelContext.#EntitySetTypeMap")]
[assembly: SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Scope = "member", Target = "Microsoft.Restier.Core.Model.ModelContext.#EntityTypeKeyPropertiesMap")]
#endregion

#region CA1801 Unused Parameters
Expand Down
4 changes: 2 additions & 2 deletions src/Microsoft.Restier.Core/Model/ModelContext.cs
Expand Up @@ -26,14 +26,14 @@ public ModelContext(ApiContext apiContext)
/// <summary>
/// Gets or sets Entity set and entity type map dictionary, it will be used by publisher for model build.
/// </summary>
public IDictionary<string, Type> EntitySetTypeMapDictionary { get; set; }
public IDictionary<string, Type> EntitySetTypeMap { get; set; }

/// <summary>
/// Gets or sets entity type and its key properties map dictionary, and used by publisher for model build.
/// This is useful when key properties does not have key attribute
/// or follow Web Api OData key property naming convention.
/// Otherwise, this collection is not needed.
/// </summary>
public IDictionary<Type, ICollection<PropertyInfo>> EntityTypeKeyPropertiesMapDictionary { get; set; }
public IDictionary<Type, ICollection<PropertyInfo>> EntityTypeKeyPropertiesMap { get; set; }
}
}
Expand Up @@ -45,8 +45,8 @@ public Task<IEdmModel> GetModelAsync(ModelContext context, CancellationToken can
{
Ensure.NotNull(context, "context");

var entitySetTypeMapDictionary = new Dictionary<string, Type>();
var entityTypeKeyPropertiesMapDictionary = new Dictionary<Type, ICollection<PropertyInfo>>();
var entitySetTypeMap = new Dictionary<string, Type>();
var entityTypeKeyPropertiesMap = new Dictionary<Type, ICollection<PropertyInfo>>();
var dbContext = context.ApiContext.GetApiService<DbContext>();

var efModel = (dbContext as IObjectContextAdapter).ObjectContext.MetadataWorkspace;
Expand All @@ -60,19 +60,19 @@ public Task<IEdmModel> GetModelAsync(ModelContext context, CancellationToken can
Type clrType = itemCollection.GetClrType(objectSpaceType);

// As entity set name and type map
entitySetTypeMapDictionary.Add(efEntitySet.Name, clrType);
entitySetTypeMap.Add(efEntitySet.Name, clrType);

ICollection<PropertyInfo> keyProperties = new List<PropertyInfo>();
foreach (var property in efEntityType.KeyProperties)
{
keyProperties.Add(clrType.GetProperty(property.Name));
}

entityTypeKeyPropertiesMapDictionary.Add(clrType, keyProperties);
entityTypeKeyPropertiesMap.Add(clrType, keyProperties);
}

context.EntitySetTypeMapDictionary = entitySetTypeMapDictionary;
context.EntityTypeKeyPropertiesMapDictionary = entityTypeKeyPropertiesMapDictionary;
context.EntitySetTypeMap = entitySetTypeMap;
context.EntityTypeKeyPropertiesMap = entityTypeKeyPropertiesMap;
return Task.FromResult<IEdmModel>(null);
}
}
Expand Down
Expand Up @@ -28,8 +28,8 @@ public async Task<IEdmModel> GetModelAsync(ModelContext context, CancellationTok
}
}

var entitySetTypeMapDictionary = context.EntitySetTypeMapDictionary;
if (entitySetTypeMapDictionary == null || entitySetTypeMapDictionary.Count == 0)
var entitySetTypeMap = context.EntitySetTypeMap;
if (entitySetTypeMap == null || entitySetTypeMap.Count == 0)
{
return null;
}
Expand All @@ -41,7 +41,7 @@ public async Task<IEdmModel> GetModelAsync(ModelContext context, CancellationTok
MethodInfo method = typeof(ODataConventionModelBuilder)
.GetMethod("EntitySet", BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy);

foreach (var pair in entitySetTypeMapDictionary)
foreach (var pair in entitySetTypeMap)
{
// Build a method with the specific type argument
var specifiedMethod = method.MakeGenericMethod(pair.Value);
Expand All @@ -53,12 +53,12 @@ public async Task<IEdmModel> GetModelAsync(ModelContext context, CancellationTok
specifiedMethod.Invoke(builder, parameters);
}

entitySetTypeMapDictionary.Clear();
entitySetTypeMap.Clear();

var entityTypeKeyPropertiesMapDictionary = context.EntityTypeKeyPropertiesMapDictionary;
if (entityTypeKeyPropertiesMapDictionary != null)
var entityTypeKeyPropertiesMap = context.EntityTypeKeyPropertiesMap;
if (entityTypeKeyPropertiesMap != null)
{
foreach (var pair in entityTypeKeyPropertiesMapDictionary)
foreach (var pair in entityTypeKeyPropertiesMap)
{
var edmTypeConfiguration = builder.GetTypeConfigurationOrNull(pair.Key) as EntityTypeConfiguration;
if (edmTypeConfiguration == null)
Expand All @@ -72,7 +72,7 @@ public async Task<IEdmModel> GetModelAsync(ModelContext context, CancellationTok
}
}

entityTypeKeyPropertiesMapDictionary.Clear();
entityTypeKeyPropertiesMap.Clear();
}

return builder.GetEdmModel();
Expand Down
Expand Up @@ -7,6 +7,8 @@
using System.Net.Http;
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Routing;
using System.Web.OData.Extensions;
using System.Web.OData.Routing;
using System.Web.OData.Routing.Conventions;
using Microsoft.Restier.Core;
Expand Down Expand Up @@ -54,7 +56,10 @@ public string SelectController(ODataPath odataPath, HttpRequestMessage request)
}

// If user has defined something like PeopleController for the entity set People,
// we should let the request being routed to that controller.
// Then whether there is an action in that controller is checked
// If controller has action for request, will be routed to that controller.
// Cannot mark EntitySetRoutingConversion has higher priority as there will no way
// to route to RESTier controller if there is EntitySet controller but no related action.
if (HasControllerForEntitySetOrSingleton(odataPath, request))
{
// Fall back to routing conventions defined by OData Web API.
Expand Down Expand Up @@ -156,14 +161,49 @@ private static bool IsMetadataPath(ODataPath odataPath)

if (controllerName != null)
{
IDictionary<string, HttpControllerDescriptor> controllers =
request.GetConfiguration().Services.GetHttpControllerSelector().GetControllerMapping();
var services = request.GetConfiguration().Services;

var controllers = services.GetHttpControllerSelector().GetControllerMapping();
HttpControllerDescriptor descriptor;
if (controllers.TryGetValue(controllerName, out descriptor) && descriptor != null)
{
// If there is a controller, check whether there is an action
if (HasSelectableAction(request, descriptor))
{
return true;
}
}
}

return false;
}

private static bool HasSelectableAction(HttpRequestMessage request, HttpControllerDescriptor descriptor)
{
var configuration = request.GetConfiguration();
var actionSelector = configuration.Services.GetActionSelector();

// Empty route as this is must and route data is not used by OData routing conversion
var route = new HttpRoute();
var routeData = new HttpRouteData(route);

var context = new HttpControllerContext(configuration, routeData, request)
{
ControllerDescriptor = descriptor
};

try
{
var action = actionSelector.SelectAction(context);
if (action != null)
{
return true;
}
}
catch (HttpResponseException)
{
// ignored
}

return false;
}
Expand Down
4 changes: 2 additions & 2 deletions test/Microsoft.Restier.TestCommon/PublicApi.bsl
Expand Up @@ -299,8 +299,8 @@ 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)

System.Collections.Generic.IDictionary`2[[System.String],[System.Type]] EntitySetTypeMapDictionary { [CompilerGeneratedAttribute(),]public get; [CompilerGeneratedAttribute(),]public set; }
System.Collections.Generic.IDictionary`2[[System.Type],[System.Collections.Generic.ICollection`1[[System.Reflection.PropertyInfo]]]] EntityTypeKeyPropertiesMapDictionary { [CompilerGeneratedAttribute(),]public get; [CompilerGeneratedAttribute(),]public set; }
System.Collections.Generic.IDictionary`2[[System.String],[System.Type]] EntitySetTypeMap { [CompilerGeneratedAttribute(),]public get; [CompilerGeneratedAttribute(),]public set; }
System.Collections.Generic.IDictionary`2[[System.Type],[System.Collections.Generic.ICollection`1[[System.Reflection.PropertyInfo]]]] EntityTypeKeyPropertiesMap { [CompilerGeneratedAttribute(),]public get; [CompilerGeneratedAttribute(),]public set; }
}

public interface Microsoft.Restier.Core.Query.IQueryExecutor {
Expand Down
Expand Up @@ -126,7 +126,7 @@ public async Task<IEdmModel> GetModelAsync(ModelContext context, CancellationTok
// EF Model builder does not build model any more but just entity set name and entity type map
if (model == null)
{
var collection = context.EntitySetTypeMapDictionary;
var collection = context.EntitySetTypeMap;
if (collection == null || collection.Count == 0)
{
return null;
Expand Down