Skip to content

Commit

Permalink
Add etag support
Browse files Browse the repository at this point in the history
  • Loading branch information
chinadragon0515 committed Jul 5, 2016
1 parent 265736c commit d6ec25f
Show file tree
Hide file tree
Showing 30 changed files with 775 additions and 267 deletions.
12 changes: 6 additions & 6 deletions src/GlobalSuppressions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,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.Exceptions")]
[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")]
Expand Down Expand Up @@ -78,12 +79,15 @@
[assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "Microsoft.Restier.Publishers.OData.Routing.HttpConfigurationExtensions.#MapRestierRoute`1(System.Web.Http.HttpConfiguration,System.String,System.String,System.Func`1<Microsoft.Restier.Core.ApiBase>,Microsoft.Restier.Publishers.OData.Batch.RestierBatchHandler)")]
[assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "Microsoft.Restier.Publishers.OData.Routing.HttpConfigurationExtensions.#MapRestierRoute`1(System.Web.Http.HttpConfiguration,System.String,System.String,Microsoft.Restier.Publishers.OData.Batch.RestierBatchHandler)")]
[assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "Microsoft.Restier.Publishers.OData.Batch.RestierBatchHandler.#.ctor(System.Web.Http.HttpServer,System.Func`1<Microsoft.Restier.Core.ApiContext>)")]
[assembly: SuppressMessage("Microsoft.Design", "CA1032:ImplementStandardExceptionConstructors", Scope = "type", Target = "Microsoft.Restier.Core.Exceptions.PreconditionFailedException")]
[assembly: SuppressMessage("Microsoft.Design", "CA1032:ImplementStandardExceptionConstructors", Scope = "type", Target = "Microsoft.Restier.Core.Exceptions.ResourceNotFoundException")]
#endregion

#region CA1704 Identifiers spelling
[assembly: SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Sourcer", Scope = "type", Target = "Microsoft.Restier.Core.Query.IQueryExpressionSourcer")]
[assembly: SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Sourcer", Scope = "type", Target = "Microsoft.Restier.Providers.EntityFramework.Query.QueryExpressionSourcer")]
[assembly: SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Ef", Scope = "member", Target = "Microsoft.Restier.Providers.EntityFramework.ServiceCollectionExtensions.#AddEfProviderServices`1(Microsoft.Extensions.DependencyInjection.IServiceCollection)")]
[assembly: SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Etag", Scope = "member", Target = "Microsoft.Restier.Core.Submit.DataModificationItem.#ApplyEtag(System.Linq.IQueryable)")]
#endregion

#region CA1709 Identifiers case
Expand Down Expand Up @@ -176,14 +180,10 @@
#endregion

#region CA2000 Dispose objects
[assembly: SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Scope = "member", Target = "Microsoft.Restier.Publishers.OData.RestierController`1.#GetSource(System.Web.OData.Routing.ODataPath,Microsoft.OData.Edm.IEdmEntityType&)")]
[assembly: SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Scope = "member", Target = "Microsoft.Restier.Publishers.OData.RestierController`1.#CreateQueryResponse(System.Linq.IQueryable,Microsoft.OData.Edm.IEdmType)")]
[assembly: SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Scope = "member", Target = "Microsoft.Restier.Publishers.OData.RestierController.#CreateQueryResponse(System.Linq.IQueryable,Microsoft.OData.Edm.IEdmType)")]
[assembly: SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Scope = "member", Target = "Microsoft.Restier.Publishers.OData.RestierController.#GetQuery(System.Web.OData.Extensions.HttpRequestMessageProperties)")]
[assembly: SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Scope = "member", Target = "Microsoft.Restier.Publishers.OData.RestierController.#GetSource(System.Web.OData.Routing.ODataPath,Microsoft.OData.Edm.IEdmEntityType&)")]
[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.RestierController.#CreateQueryResponse(System.Linq.IQueryable,Microsoft.OData.Edm.IEdmType,System.Boolean,System.Web.OData.Formatter.ETag)")]
[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.#Handler412(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

Expand Down
10 changes: 5 additions & 5 deletions src/Microsoft.Restier.Core/ApiBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ public ApiContext Context
}
}

/// <summary>
/// Gets a value indicating whether this API has been disposed.
/// </summary>
public bool IsDisposed { get; private set; }

/// <summary>
/// Gets the API configuration for this API.
/// </summary>
Expand All @@ -86,11 +91,6 @@ protected ApiConfiguration Configuration
}
}

/// <summary>
/// Gets a value indicating whether this API has been disposed.
/// </summary>
protected bool IsDisposed { get; private set; }

/// <summary>
/// Performs application-defined tasks associated with
/// freeing, releasing, or resetting unmanaged resources.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,15 +80,15 @@ private static string GetAuthorizeMethodName(ChangeSetItem item)
case ChangeSetItemType.DataModification:
DataModificationItem dataModification = (DataModificationItem)item;
string operationName = null;
if (dataModification.IsNewRequest)
if (dataModification.ChangeSetItemAction == ChangeSetItemAction.Insert)
{
operationName = ConventionBasedChangeSetConstants.AuthorizeMethodDataModificationInsert;
}
else if (dataModification.IsUpdateRequest)
else if (dataModification.ChangeSetItemAction == ChangeSetItemAction.Update)
{
operationName = ConventionBasedChangeSetConstants.AuthorizeMethodDataModificationUpdate;
}
else if (dataModification.IsDeleteRequest)
else if (dataModification.ChangeSetItemAction == ChangeSetItemAction.Remove)
{
operationName = ConventionBasedChangeSetConstants.AuthorizeMethodDataModificationDelete;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,15 @@ private static string GetMethodName(ChangeSetItem item, string suffix)
case ChangeSetItemType.DataModification:
DataModificationItem dataModification = (DataModificationItem)item;
string operationName = null;
if (dataModification.IsNewRequest)
if (dataModification.ChangeSetItemAction == ChangeSetItemAction.Insert)
{
operationName = ConventionBasedChangeSetConstants.FilterMethodDataModificationInsert;
}
else if (dataModification.IsUpdateRequest)
else if (dataModification.ChangeSetItemAction == ChangeSetItemAction.Update)
{
operationName = ConventionBasedChangeSetConstants.FilterMethodDataModificationUpdate;
}
else if (dataModification.IsDeleteRequest)
else if (dataModification.ChangeSetItemAction == ChangeSetItemAction.Remove)
{
operationName = ConventionBasedChangeSetConstants.FilterMethodDataModificationDelete;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// 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.Core.Exceptions
{
/// <summary>
/// This exception is used for 412 Precondition Failed response.
/// </summary>
[Serializable]
public class PreconditionFailedException : System.Exception
{
/// <summary>
/// Initializes a new instance of the PreconditionFailedException class.
/// </summary>
public PreconditionFailedException()
: this(null, null)
{
}

/// <summary>
/// Initializes a new instance of the PreconditionFailedException class.
/// </summary>
/// <param name="message">Plain text error message for this exception.</param>
public PreconditionFailedException(string message)
: this(message, null)
{
}

/// <summary>
/// Initializes a new instance of the PreconditionFailedException class.
/// </summary>
/// <param name="message">Plain text error message for this exception.</param>
/// <param name="innerException">Exception that caused this exception to be thrown.</param>
public PreconditionFailedException(string message, System.Exception innerException)
: base(message, innerException)
{
}
}
}
41 changes: 41 additions & 0 deletions src/Microsoft.Restier.Core/Exceptions/ResourceNotFoundException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// 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.Core.Exceptions
{
/// <summary>
/// This exception is used for 404 Not found response.
/// </summary>
[Serializable]
public class ResourceNotFoundException : System.Exception
{
/// <summary>
/// Initializes a new instance of the ResourceNotFoundException class.
/// </summary>
public ResourceNotFoundException()
: this(null, null)
{
}

/// <summary>
/// Initializes a new instance of the ResourceNotFoundException class.
/// </summary>
/// <param name="message">Plain text error message for this exception.</param>
public ResourceNotFoundException(string message)
: this(message, null)
{
}

/// <summary>
/// Initializes a new instance of the ResourceNotFoundException class.
/// </summary>
/// <param name="message">Plain text error message for this exception.</param>
/// <param name="innerException">Exception that caused this exception to be thrown.</param>
public ResourceNotFoundException(string message, System.Exception innerException)
: base(message, innerException)
{
}
}
}
2 changes: 2 additions & 0 deletions src/Microsoft.Restier.Core/Microsoft.Restier.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@
<Compile Include="ApiContextExtensions.cs" />
<Compile Include="DataSourceStub.cs" />
<Compile Include="ApiConfiguratorAttribute.cs" />
<Compile Include="Exceptions\PreconditionFailedException.cs" />
<Compile Include="Exceptions\ResourceNotFoundException.cs" />
<Compile Include="InvocationContext.cs" />
<Compile Include="InvocationContextExtensions.cs" />
<Compile Include="Model\EdmHelpers.cs" />
Expand Down
73 changes: 37 additions & 36 deletions src/Microsoft.Restier.Core/Submit/ChangeSetItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ public bool HasChanged()
/// </summary>
public class DataModificationItem : ChangeSetItem
{
private const string IfNoneMatchKey = "@IfNoneMatchKey";

/// <summary>
/// Initializes a new instance of the <see cref="DataModificationItem" /> class.
/// </summary>
Expand All @@ -140,6 +142,9 @@ public class DataModificationItem : ChangeSetItem
/// <param name="actualEntityType">
/// The type of the actual entity type in question.
/// </param>
/// <param name="action">
/// The ChangeSetItemAction for the request.
/// </param>
/// <param name="entityKey">
/// The key of the entity being modified.
/// </param>
Expand All @@ -153,6 +158,7 @@ public DataModificationItem(
string entitySetName,
Type expectedEntityType,
Type actualEntityType,
ChangeSetItemAction action,
IReadOnlyDictionary<string, object> entityKey,
IReadOnlyDictionary<string, object> originalValues,
IReadOnlyDictionary<string, object> localValues)
Expand All @@ -166,7 +172,7 @@ public DataModificationItem(
this.EntityKey = entityKey;
this.OriginalValues = originalValues;
this.LocalValues = localValues;
this.ChangeSetItemAction = ChangeSetItemAction.Undefined;
this.ChangeSetItemAction = action;
}

/// <summary>
Expand Down Expand Up @@ -195,28 +201,6 @@ public DataModificationItem(
/// </summary>
public ChangeSetItemAction ChangeSetItemAction { get; set; }

/// <summary>
/// Gets a value indicating whether the modification is a new entity.
/// </summary>
public bool IsNewRequest
{
get
{
return this.OriginalValues == null && this.EntityKey == null;
}
}

/// <summary>
/// Gets a value indicating whether the modification is updating an entity.
/// </summary>
public bool IsUpdateRequest
{
get
{
return this.OriginalValues != null && this.LocalValues != null;
}
}

/// <summary>
/// Gets or sets a value indicating whether the entity should be fully replaced by the modification.
/// </summary>
Expand All @@ -226,17 +210,6 @@ public bool IsUpdateRequest
/// </remarks>
public bool IsFullReplaceUpdateRequest { get; set; }

/// <summary>
/// Gets a value indicating whether the modification is deleting an entity.
/// </summary>
public bool IsDeleteRequest
{
get
{
return this.LocalValues == null;
}
}

/// <summary>
/// Gets or sets the entity object in question.
/// </summary>
Expand Down Expand Up @@ -294,7 +267,7 @@ public IReadOnlyDictionary<string, object> LocalValues
public IQueryable ApplyTo(IQueryable query)
{
Ensure.NotNull(query, "query");
if (this.IsNewRequest)
if (this.ChangeSetItemAction == ChangeSetItemAction.Insert)
{
throw new InvalidOperationException(Resources.DataModificationNotSupportCreateEntity);
}
Expand All @@ -316,6 +289,25 @@ public IQueryable ApplyTo(IQueryable query)
throw new InvalidOperationException(Resources.DataModificationRequiresEntityKey);
}

LambdaExpression whereLambda = Expression.Lambda(where, param);
return ExpressionHelpers.Where(query, whereLambda, type);
}

/// <summary>
/// Applies the current DataModificationItem's OriginalValues to the
/// specified query and returns the new query.
/// </summary>
/// <param name="query">The IQueryable to apply the property values to.</param>
/// <returns>
/// The new IQueryable with the property values applied to it in a Where condition.
/// </returns>
public IQueryable ApplyEtag(IQueryable query)
{
Ensure.NotNull(query, "query");
Type type = query.ElementType;
ParameterExpression param = Expression.Parameter(type);
Expression where = null;

if (this.OriginalValues != null)
{
foreach (KeyValuePair<string, object> item in this.OriginalValues)
Expand All @@ -325,6 +317,11 @@ public IQueryable ApplyTo(IQueryable query)
where = ApplyPredicate(param, where, item);
}
}

if (this.OriginalValues.ContainsKey(IfNoneMatchKey))
{
where = Expression.Not(where);
}
}

LambdaExpression whereLambda = Expression.Lambda(where, param);
Expand Down Expand Up @@ -373,6 +370,9 @@ public class DataModificationItem<T> : DataModificationItem
/// <param name="actualEntityType">
/// The type of the actual entity type in question.
/// </param>
/// <param name="action">
/// The ChangeSetItemAction for the request.
/// </param>
/// <param name="entityKey">
/// The key of the entity being modified.
/// </param>
Expand All @@ -386,10 +386,11 @@ public DataModificationItem(
string entitySetName,
Type expectedEntityType,
Type actualEntityType,
ChangeSetItemAction action,
IReadOnlyDictionary<string, object> entityKey,
IReadOnlyDictionary<string, object> originalValues,
IReadOnlyDictionary<string, object> localValues)
: base(entitySetName, expectedEntityType, actualEntityType, entityKey, originalValues, localValues)
: base(entitySetName, expectedEntityType, actualEntityType, action, entityKey, originalValues, localValues)
{
}

Expand Down
Loading

0 comments on commit d6ec25f

Please sign in to comment.