Skip to content

Commit

Permalink
Add action to be executed without additional controller.
Browse files Browse the repository at this point in the history
  • Loading branch information
chinadragon0515 committed Jun 30, 2016
1 parent f402889 commit 2123cab
Show file tree
Hide file tree
Showing 25 changed files with 936 additions and 201 deletions.
2 changes: 2 additions & 0 deletions src/GlobalSuppressions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
#region CA1020 Few types in namespace
[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "Microsoft.Restier.Core")]
[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "Microsoft.Restier.Core.Model")]
[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "Microsoft.Restier.Core.Operation")]
[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "Microsoft.Restier.Providers.EntityFramework")]
[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "Microsoft.Restier.Providers.EntityFramework.Model")]
[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "Microsoft.Restier.Providers.EntityFramework.Query")]
Expand Down Expand Up @@ -183,6 +184,7 @@
[assembly: SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Scope = "member", Target = "Microsoft.Restier.Publishers.OData.RestierController.#GetQuery()")]
[assembly: SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Scope = "member", Target = "Microsoft.Restier.Publishers.OData.Filters.RestierExceptionFilterAttribute.#Handler403(System.Web.Http.Filters.HttpActionExecutedContext,System.Threading.CancellationToken)")]
[assembly: SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Scope = "member", Target = "Microsoft.Restier.Publishers.OData.Filters.RestierExceptionFilterAttribute.#Handler404(System.Web.Http.Filters.HttpActionExecutedContext,System.Threading.CancellationToken)")]
[assembly: SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Scope = "member", Target = "Microsoft.Restier.Publishers.OData.Filters.RestierExceptionFilterAttribute.#Handler501(System.Web.Http.Filters.HttpActionExecutedContext,System.Threading.CancellationToken)")]
#endregion

#region CA1812 Uninstantiated internal classes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public Task<bool> AuthorizeAsync(
bool result = true;

Type returnType = typeof(bool);
string methodName = ConventionBasedChangeSetItemAuthorizer.GetAuthorizeMethodName(item);
string methodName = GetAuthorizeMethodName(item);
MethodInfo method = this.targetType.GetQualifiedMethod(methodName);

if (method != null && method.IsFamily &&
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// 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.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Restier.Core.Operation;

namespace Microsoft.Restier.Core.Conventions
{
/// <summary>
/// A convention-based operation authorizer.
/// </summary>
internal class ConventionBasedOperationAuthorizer : IOperationAuthorizer
{
private Type targetType;

private ConventionBasedOperationAuthorizer(Type targetType)
{
Ensure.NotNull(targetType, "targetType");
this.targetType = targetType;
}

public static void ApplyTo(
IServiceCollection services,
Type targetType)
{
Ensure.NotNull(services, "services");
Ensure.NotNull(targetType, "targetType");
services.AddService<IOperationAuthorizer>((sp, next) => new ConventionBasedOperationAuthorizer(targetType));
}

/// <inheritdoc/>
public Task<bool> AuthorizeAsync(
OperationContext context,
CancellationToken cancellationToken)
{
Ensure.NotNull(context, "context");
bool result = true;

Type returnType = typeof(bool);
var methodName = ConventionBasedChangeSetConstants.AuthorizeMethodActionInvocationExecute +
context.OperationName;
MethodInfo method = this.targetType.GetQualifiedMethod(methodName);

if (method != null && method.IsFamily &&
method.ReturnType == returnType)
{
object target = null;
if (!method.IsStatic)
{
target = context.GetApiService<ApiBase>();
if (target == null ||
!this.targetType.IsInstanceOfType(target))
{
return Task.FromResult(result);
}
}

var parameters = method.GetParameters();
if (parameters.Length == 0)
{
result = (bool)method.Invoke(target, null);
}
}

return Task.FromResult(result);
}
}
}
4 changes: 4 additions & 0 deletions src/Microsoft.Restier.Core/Microsoft.Restier.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
<Link>Shared\TypeExtensions.cs</Link>
</Compile>
<Compile Include="ApiConfiguratorAttributes.cs" />
<Compile Include="Conventions\ConventionBasedOperationAuthorizer.cs" />
<Compile Include="Conventions\ConventionBasedChangeSetItemAuthorizer.cs" />
<Compile Include="Conventions\ConventionBasedChangeSetConstants.cs" />
<Compile Include="Conventions\ConventionBasedChangeSetItemProcessor.cs" />
Expand All @@ -68,6 +69,9 @@
<Compile Include="InvocationContextExtensions.cs" />
<Compile Include="Model\EdmHelpers.cs" />
<Compile Include="Model\ModelContext.cs" />
<Compile Include="Operation\IOperationAuthorizer.cs" />
<Compile Include="Operation\IOperationExecutor.cs" />
<Compile Include="Operation\OperationContext.cs" />
<Compile Include="ServiceCollectionExtensions.cs" />
<Compile Include="Model\IModelBuilder.cs" />
<Compile Include="Model\IModelMapper.cs" />
Expand Down
31 changes: 31 additions & 0 deletions src/Microsoft.Restier.Core/Operation/IOperationAuthorizer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System.Threading;
using System.Threading.Tasks;
using Microsoft.Restier.Core.Submit;

namespace Microsoft.Restier.Core.Operation
{
/// <summary>
/// Represents a operation authorizer.
/// </summary>
public interface IOperationAuthorizer
{
/// <summary>
/// Asynchronously authorizes the Operation.
/// </summary>
/// <param name="context">
/// The operation context.
/// </param>
/// <param name="cancellationToken">
/// A cancellation token.
/// </param>
/// <returns>
/// A task that represents the asynchronous operation.
/// </returns>
Task<bool> AuthorizeAsync(
OperationContext context,
CancellationToken cancellationToken);
}
}
35 changes: 35 additions & 0 deletions src/Microsoft.Restier.Core/Operation/IOperationExecutor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// 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 System.Threading;
using System.Threading.Tasks;

namespace Microsoft.Restier.Core.Operation
{
/// <summary>
/// Represents a service that executes an operation.
/// </summary>
public interface IOperationExecutor
{
/// <summary>
/// Asynchronously executes an operation.
/// </summary>
/// <param name="instanceImplementMethod">
/// A class instance with have operation implemented and will be used for reflection call.
/// </param>
/// <param name="context">
/// The operation context.
/// </param>
/// <param name="cancellationToken">
/// A cancellation token.
/// </param>
/// <returns>
/// A task that represents the asynchronous
/// operation whose result is a operation result.
/// </returns>
Task<IQueryable> ExecuteOperationAsync(
object instanceImplementMethod, OperationContext context, CancellationToken cancellationToken);
}
}
96 changes: 96 additions & 0 deletions src/Microsoft.Restier.Core/Operation/OperationContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// 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;

namespace Microsoft.Restier.Core.Operation
{
/// <summary>
/// Represents context under which a operation is executed.
/// </summary>
public class OperationContext : InvocationContext
{
private readonly string operationName;
private readonly Func<string, object> getParameterValueFunc;
private readonly bool isFunction;
private readonly IQueryable bindingParameterValue;

/// <summary>
/// Initializes a new instance of the <see cref="OperationContext" /> class.
/// </summary>
/// <param name="apiContext">
/// An API context.
/// </param>
/// <param name="getParameterValueFunc">
/// The function that used to retrieve the parameter value name.
/// </param>
/// <param name="operationName">
/// The operation name.
/// </param>
/// <param name="isFunction">
/// A flag indicates this is a function call or action call.
/// </param>
/// <param name="bindingParameterValue">
/// A queryable for binding parameter value and if it is function/action import, the value will be null.
/// </param>
public OperationContext(
ApiContext apiContext,
Func<string, object> getParameterValueFunc,
string operationName,
bool isFunction,
IQueryable bindingParameterValue)
: base(apiContext)
{
this.getParameterValueFunc = getParameterValueFunc;
this.operationName = operationName;
this.isFunction = isFunction;
this.bindingParameterValue = bindingParameterValue;
}

/// <summary>
/// Gets the operation name.
/// </summary>
public string OperationName
{
get
{
return this.operationName;
}
}

/// <summary>
/// Gets the function that used to retrieve the parameter value name.
/// </summary>
public Func<string, object> GetParameterValueFunc
{
get
{
return this.getParameterValueFunc;
}
}

/// <summary>
/// Gets a value indicating whether it is a function call or action call.
/// </summary>
public bool IsFunction
{
get
{
return this.isFunction;
}
}

/// <summary>
/// Gets the queryable for binding parameter value,
/// and if it is function/action import, the value will be null.
/// </summary>
public IQueryable BindingParameterValue
{
get
{
return this.bindingParameterValue;
}
}
}
}
1 change: 1 addition & 0 deletions src/Microsoft.Restier.Core/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ public static IServiceCollection AddConventionBasedServices(this IServiceCollect
ConventionBasedChangeSetItemProcessor.ApplyTo(services, apiType);
services.AddService<IChangeSetItemValidator, ConventionBasedChangeSetItemValidator>();
ConventionBasedEntitySetProcessor.ApplyTo(services, apiType);
ConventionBasedOperationAuthorizer.ApplyTo(services, apiType);
return services;
}

Expand Down
2 changes: 1 addition & 1 deletion src/Microsoft.Restier.Core/Submit/SubmitContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public class SubmitContext : InvocationContext
public SubmitContext(ApiContext apiContext, ChangeSet changeSet)
: base(apiContext)
{
this.ChangeSet = changeSet;
this.changeSet = changeSet;
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ internal sealed class RestierExceptionFilterAttribute : ExceptionFilterAttribute
{
Handler400,
Handler403,
Handler404
Handler404,
Handler501
};

private delegate Task<HttpResponseMessage> ExceptionHandlerDelegate(
Expand Down Expand Up @@ -113,5 +114,26 @@ private static Task<HttpResponseMessage> Handler404(

return Task.FromResult<HttpResponseMessage>(null);
}

private static Task<HttpResponseMessage> Handler501(
HttpActionExecutedContext context,
CancellationToken cancellationToken)
{
if (context.Exception is NotImplementedException)
{
return Task.FromResult(
context.Request.CreateErrorResponse(HttpStatusCode.NotImplemented, context.Exception));
}

// For async call, the exception is wrapped.
if (context.Exception is AggregateException
&& context.Exception.InnerException is NotImplementedException)
{
return Task.FromResult(
context.Request.CreateErrorResponse(HttpStatusCode.NotImplemented, context.Exception));
}

return Task.FromResult<HttpResponseMessage>(null);
}
}
}
Loading

0 comments on commit 2123cab

Please sign in to comment.