Skip to content

Commit

Permalink
Add bound flag for operation attribute
Browse files Browse the repository at this point in the history
  • Loading branch information
chinadragon0515 committed Jul 5, 2016
1 parent 8614595 commit 265736c
Show file tree
Hide file tree
Showing 17 changed files with 229 additions and 212 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using System.Web.OData.Formatter.Deserialization;
using Microsoft.OData.Core;
using Microsoft.OData.Edm;
Expand Down
21 changes: 17 additions & 4 deletions src/Microsoft.Restier.Publishers.OData/Model/OperationAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,30 +12,43 @@ namespace Microsoft.Restier.Publishers.OData.Model
public sealed class OperationAttribute : Attribute
{
/// <summary>
/// Gets or sets the name of the function.
/// Gets or sets the name of the operation.
/// The default name is same as method name.
/// </summary>
public string Name { get; set; }

/// <summary>
/// Gets or sets the namespace of the function.
/// Gets or sets the namespace of the operation.
/// The default value will be same as the namespace of entity type.
/// </summary>
public string Namespace { get; set; }

/// <summary>
/// Gets or sets the entity set associated with the function result.
/// Gets or sets the entity set associated with the operation result.
/// Only need to be set for unbound operations
/// when there are multiple entity sets with same entity type as result entity type.
/// </summary>
public string EntitySet { get; set; }

/// <summary>
/// Gets or sets a value indicating whether the function is composable.
/// The default value is false
/// </summary>
public bool IsComposable { get; set; }

/// <summary>
/// Gets or sets a value indicating whether the operation is bound or not.
/// If it is set to true, then no matter what's the first parameter, it will be considered as bound.
/// If it is set to false, then no matter what's the first parameter, it will be considered as unbound.
/// The default value is false
/// </summary>
public bool IsBound { get; set; }

/// <summary>
/// Gets or sets a value indicating whether the operation has side effects.
/// If a operation does not have side effect, it means it is a function.
/// If a operation has side effect, it means it is a action.
/// The default value is false in c#
/// The default value is false
/// </summary>
public bool HasSideEffects { get; set; }
}
Expand Down
28 changes: 10 additions & 18 deletions src/Microsoft.Restier.Publishers.OData/Model/RestierModelBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
Expand All @@ -10,9 +11,13 @@

namespace Microsoft.Restier.Publishers.OData.Model
{
public class RestierModelBuilder : IModelBuilder
/// <summary>
/// This is a RESTier model build which retrieve information from providers like entity framework provider,
/// then build entity set and entity type based on retrieved information.
/// </summary>
internal class RestierModelBuilder : IModelBuilder
{
internal IModelBuilder InnerModelBuilder { get; set; }
public IModelBuilder InnerModelBuilder { get; set; }

/// <inheritdoc/>
public async Task<IEdmModel> GetModelAsync(ModelContext context, CancellationToken cancellationToken)
Expand All @@ -34,15 +39,14 @@ public async Task<IEdmModel> GetModelAsync(ModelContext context, CancellationTok
return null;
}

string namespaceName = collection[0].Value.Namespace;

// Collection of entity type and set name is set by EF now, and EF provider will not build model any more
// Collection of entity type and set name is set by EF now,
// and EF model producer will not build model any more
// Web Api OData conversion model built is been used here,
// refer to Web Api OData document for the detail conversions been used for model built.
var builder = new ODataConventionModelBuilder();

// This namespace is used by container
builder.Namespace = namespaceName;
builder.Namespace = entitySetTypeMap.First().Value.Namespace;

MethodInfo method = typeof(ODataConventionModelBuilder)
.GetMethod("EntitySet", BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
Expand Down Expand Up @@ -81,19 +85,7 @@ public async Task<IEdmModel> GetModelAsync(ModelContext context, CancellationTok
entityTypeKeyPropertiesMap.Clear();
}

ExtendModel(builder);

return builder.GetEdmModel();
}

/// <summary>
/// This method allow user to extend the model with more entity type and entity set before operation model builder starts
/// A sub class need to be implemented and register as DI service like "AddTransient&lt;RestierModelBuilder, CustomizedRestierModelBuilder&gt;"
/// </summary>
/// <param name="builder"></param>
public virtual void ExtendModel(ODataConventionModelBuilder builder)
{

}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ namespace Microsoft.Restier.Publishers.OData.Model
internal class RestierOperationModelBuilder : IModelBuilder
{
private readonly Type targetType;
private readonly ICollection<OperationMethodInfo> actionInfos = new List<OperationMethodInfo>();
private readonly ICollection<OperationMethodInfo> functionInfos = new List<OperationMethodInfo>();
private readonly ICollection<OperationMethodInfo> operationInfos = new List<OperationMethodInfo>();

private RestierOperationModelBuilder(Type targetType)
{
Expand Down Expand Up @@ -58,9 +57,8 @@ public async Task<IEdmModel> GetModelAsync(ModelContext context, CancellationTok
{
existingNamespace = model.DeclaredNamespaces.FirstOrDefault();
}

this.BuildFunctions(model, existingNamespace);
this.BuildActions(model, existingNamespace);

this.BuildOperations(model, existingNamespace);
return model;
}

Expand All @@ -78,34 +76,16 @@ private static void BuildOperationParameters(EdmOperation operation, MethodInfo
}
}

private static bool TryGetBindingParameter(
MethodInfo method, IEdmModel model, out ParameterInfo bindingParameter)
{
bindingParameter = null;
var firstParameter = method.GetParameters().FirstOrDefault();
if (firstParameter == null)
{
return false;
}

Type parameterType;
if (!firstParameter.ParameterType.TryGetElementType(out parameterType))
{
parameterType = firstParameter.ParameterType;
}

if (!parameterType.GetTypeReference(model).IsEntity())
{
return false;
}

bindingParameter = firstParameter;
return true;
}

private static EdmPathExpression BuildEntitySetPathExpression(
IEdmTypeReference returnTypeReference, ParameterInfo bindingParameter)
{
// Bound actions or functions that return an entity or a collection of entities
// MAY specify a value for the EntitySetPath attribute
// if determination of the entity set for the return type is contingent on the binding parameter.
// The value for the EntitySetPath attribute consists of a series of segments
// joined together with forward slashes.
// The first segment of the entity set path MUST be the name of the binding parameter.
// The remaining segments of the entity set path MUST represent navigation segments or type casts.
if (returnTypeReference != null &&
returnTypeReference.IsEntity() &&
bindingParameter != null)
Expand Down Expand Up @@ -138,6 +118,25 @@ private static EdmEntitySetReferenceExpression BuildEntitySetReferenceExpression
return null;
}

private static string GetNamespaceName(OperationMethodInfo methodInfo, string modelNamespace)
{
// customized the namespace logic, customized namespace is P0
string namespaceName = methodInfo.OperationAttribute.Namespace;

if (namespaceName != null)
{
return namespaceName;
}

if (modelNamespace != null)
{
return modelNamespace;
}

// This returns defined class namespace
return methodInfo.Namespace;
}

private void ScanForOperations()
{
var methods = this.targetType.GetMethods(
Expand All @@ -148,113 +147,88 @@ private void ScanForOperations()

foreach (var method in methods)
{
var functionAttribute = method.GetCustomAttributes<OperationAttribute>(true).FirstOrDefault();
if (functionAttribute != null)
var operationAttribute = method.GetCustomAttributes<OperationAttribute>(true).FirstOrDefault();
if (operationAttribute != null)
{
if (!functionAttribute.HasSideEffects)
operationInfos.Add(new OperationMethodInfo
{
functionInfos.Add(new OperationMethodInfo
{
Method = method,
OperationAttribute = functionAttribute
});
}
else
{
actionInfos.Add(new OperationMethodInfo
{
Method = method,
OperationAttribute = functionAttribute
});
}
Method = method,
OperationAttribute = operationAttribute
});
}
}
}

private void BuildActions(EdmModel model, string modelNamespace)
private void BuildOperations(EdmModel model, string modelNamespace)
{
foreach (OperationMethodInfo actionInfo in this.actionInfos)
foreach (OperationMethodInfo operationMethodInfo in this.operationInfos)
{
var returnTypeReference = actionInfo.Method.ReturnType.GetReturnTypeReference(model);

ParameterInfo bindingParameter;
bool isBound = TryGetBindingParameter(actionInfo.Method, model, out bindingParameter);

string namespaceName = GetNamespaceName(actionInfo, modelNamespace);

var action = new EdmAction(
namespaceName,
actionInfo.Name,
returnTypeReference,
isBound,
BuildEntitySetPathExpression(returnTypeReference, bindingParameter));
BuildOperationParameters(action, actionInfo.Method, model);
model.AddElement(action);
// With this method, if return type is nullable type,it will get underlying type
var returnType = TypeHelper.GetUnderlyingTypeOrSelf(operationMethodInfo.Method.ReturnType);
var returnTypeReference = returnType.GetReturnTypeReference(model);
bool isBound = operationMethodInfo.IsBound;
var bindingParameter = operationMethodInfo.Method.GetParameters().FirstOrDefault();

if (!isBound)
if (bindingParameter == null && isBound)
{
var entitySetReferenceExpression = BuildEntitySetReferenceExpression(
model, actionInfo.EntitySet, returnTypeReference);
var entityContainer = model.EnsureEntityContainer(this.targetType);
entityContainer.AddActionImport(
action.Name, action, entitySetReferenceExpression);
// Ignore the method which is marked as bounded but no parameters
continue;
}
}
}

private void BuildFunctions(EdmModel model, string modelNamespace)
{
foreach (OperationMethodInfo functionInfo in this.functionInfos)
{
// With this method, if return type is nullable type,it will get underlying type
var returnType = TypeHelper.GetUnderlyingTypeOrSelf(functionInfo.Method.ReturnType);
var returnTypeReference = returnType.GetReturnTypeReference(model);
string namespaceName = GetNamespaceName(operationMethodInfo, modelNamespace);

ParameterInfo bindingParameter;
bool isBound = TryGetBindingParameter(functionInfo.Method, model, out bindingParameter);
EdmOperation operation = null;
EdmPathExpression path = null;
if (isBound)
{
// Unbound actions or functions should not have EntitySetPath attribute
path = BuildEntitySetPathExpression(returnTypeReference, bindingParameter);
}

string namespaceName = GetNamespaceName(functionInfo, modelNamespace);
if (operationMethodInfo.HasSideEffects)
{
operation = new EdmAction(
namespaceName,
operationMethodInfo.Name,
returnTypeReference,
isBound,
path);
}
else
{
operation = new EdmFunction(
namespaceName,
operationMethodInfo.Name,
returnTypeReference,
isBound,
path,
operationMethodInfo.IsComposable);
}

var function = new EdmFunction(
namespaceName,
functionInfo.Name,
returnTypeReference,
isBound,
BuildEntitySetPathExpression(returnTypeReference, bindingParameter),
functionInfo.IsComposable);
BuildOperationParameters(function, functionInfo.Method, model);
model.AddElement(function);
BuildOperationParameters(operation, operationMethodInfo.Method, model);
model.AddElement(operation);

if (!isBound)
{
// entitySetReferenceExpression refer to an entity set containing entities returned
// by this function/action import.
var entitySetReferenceExpression = BuildEntitySetReferenceExpression(
model, functionInfo.EntitySet, returnTypeReference);
model, operationMethodInfo.EntitySet, returnTypeReference);
var entityContainer = model.EnsureEntityContainer(this.targetType);
entityContainer.AddFunctionImport(
function.Name, function, entitySetReferenceExpression);
if (operationMethodInfo.HasSideEffects)
{
entityContainer.AddActionImport(
operation.Name, (EdmAction)operation, entitySetReferenceExpression);
}
else
{
entityContainer.AddFunctionImport(
operation.Name, (EdmFunction)operation, entitySetReferenceExpression);
}
}
}
}

private static string GetNamespaceName(OperationMethodInfo methodInfo, string modelNamespace)
{
// customized the namespace logic, customized namespace is P0
string namespaceName = methodInfo.OperationAttribute.Namespace;

if (namespaceName != null)
{
return namespaceName;
}

if (modelNamespace != null)
{
return modelNamespace;
}

// This returns defined class namespace
return methodInfo.Namespace;
}

private class OperationMethodInfo
{
public MethodInfo Method { get; set; }
Expand All @@ -280,6 +254,16 @@ public bool IsComposable
{
get { return this.OperationAttribute.IsComposable; }
}

public bool IsBound
{
get { return this.OperationAttribute.IsBound; }
}

public bool HasSideEffects
{
get { return this.OperationAttribute.HasSideEffects; }
}
}
}
}
Loading

0 comments on commit 265736c

Please sign in to comment.