Permalink
Browse files

[OData] Add the ODataPathParser feature to parse OData URIs into segm…

…ents with additional information about the EDM type and entity set for the path

Sample usage:
ODataPathParser parser = new ODataPathParser(GetImplicitModel());
var path = parser.Parse(new Uri("http://localhost/Customers(10)"), new Uri("http://localhost/"));
  • Loading branch information...
1 parent aec4b34 commit 814719252b85ec32848ba20d5f6ac10f16693d10 youssefm committed Nov 9, 2012
Showing with 2,444 additions and 33 deletions.
  1. +1 −0 src/System.Web.Http.OData/GlobalSuppressions.cs
  2. +12 −28 src/System.Web.Http.OData/OData/DefaultODataActionResolver.cs
  3. +0 −2 src/System.Web.Http.OData/OData/Formatter/Deserialization/ODataEntryDeserializer.cs
  4. +181 −0 src/System.Web.Http.OData/OData/Formatter/EdmLibHelpers.cs
  5. +67 −0 src/System.Web.Http.OData/OData/Routing/ActionPathSegment.cs
  6. +41 −0 src/System.Web.Http.OData/OData/Routing/BatchPathSegment.cs
  7. +77 −0 src/System.Web.Http.OData/OData/Routing/CastPathSegment.cs
  8. +52 −0 src/System.Web.Http.OData/OData/Routing/EntitySetPathSegment.cs
  9. +21 −0 src/System.Web.Http.OData/OData/Routing/IODataPathParser.cs
  10. +68 −0 src/System.Web.Http.OData/OData/Routing/KeyValuePathSegment.cs
  11. +43 −0 src/System.Web.Http.OData/OData/Routing/LinksPathSegment.cs
  12. +41 −0 src/System.Web.Http.OData/OData/Routing/MetadataPathSegment.cs
  13. +70 −0 src/System.Web.Http.OData/OData/Routing/NavigationPathSegment.cs
  14. +61 −0 src/System.Web.Http.OData/OData/Routing/ODataPath.cs
  15. +469 −0 src/System.Web.Http.OData/OData/Routing/ODataPathParser.cs
  16. +68 −0 src/System.Web.Http.OData/OData/Routing/ODataPathSegment.cs
  17. +23 −0 src/System.Web.Http.OData/OData/Routing/ODataSegmentKinds.cs
  18. +60 −0 src/System.Web.Http.OData/OData/Routing/PropertyAccessPathSegment.cs
  19. +53 −0 src/System.Web.Http.OData/OData/Routing/ServiceBasePathSegment.cs
  20. +57 −0 src/System.Web.Http.OData/OData/Routing/UnresolvedPathSegment.cs
  21. +43 −0 src/System.Web.Http.OData/OData/Routing/ValuePathSegment.cs
  22. +109 −1 src/System.Web.Http.OData/Properties/SRResources.Designer.cs
  23. +37 −1 src/System.Web.Http.OData/Properties/SRResources.resx
  24. +17 −0 src/System.Web.Http.OData/System.Web.Http.OData.csproj
  25. +1 −1 test/System.Web.Http.OData.Test/OData/DefaultODataActionResolverTest.cs
  26. +771 −0 test/System.Web.Http.OData.Test/OData/Routing/ODataPathParserTest.cs
  27. +1 −0 test/System.Web.Http.OData.Test/System.Web.Http.OData.Test.csproj
@@ -5,5 +5,6 @@
[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "System.Web.Http", Justification = "Follows System.Web.Http naming")]
[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "System.Web.Http.OData.Formatter", Justification = "Follows System.Web.Http naming")]
[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "System.Web.Http.OData.Formatter.Serialization", Justification = "Follows System.Web.Http naming")]
+[assembly: SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Scope = "type", Target = "System.Web.Http.OData.Formatter.EdmLibHelpers", Justification = "Mostly extension methods")]
[assembly: SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Scope = "member", Target = "System.Web.Http.OData.Formatter.EdmLibHelpers.#.cctor()", Justification = "Class coupling necessary in this class")]
[assembly: SuppressMessage("Microsoft.Web.FxCop", "MW1000:UnusedResourceUsageRule", MessageId = "172567", Justification = "Resource used by framework")]
@@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
+using System.Web.Http.OData.Formatter;
using System.Web.Http.OData.Formatter.Deserialization;
using System.Web.Http.OData.Properties;
using Microsoft.Data.Edm;
@@ -25,45 +26,28 @@ public IEdmFunctionImport Resolve(ODataDeserializerContext context)
throw Error.InvalidOperation(SRResources.DefaultODataActionResolverRequirementsNotSatisfied);
}
- string actionName = null;
- string containerName = null;
- string nspace = null;
-
- string lastSegment = context.Request.RequestUri.Segments.Last();
- string[] nameParts = lastSegment.Split('.');
-
IEdmEntityContainer[] entityContainers = context.Model.EntityContainers().ToArray();
- Contract.Assert(entityContainers.Length == 1);
- IEnumerable<IEdmFunctionImport> matchingActionsQuery = entityContainers[0].FunctionImports();
-
- if (nameParts.Length == 1)
- {
- actionName = nameParts[0];
- matchingActionsQuery = matchingActionsQuery.Where(f => f.Name == actionName && f.IsSideEffecting == true);
- }
- else if (nameParts.Length == 2)
- {
- actionName = nameParts[nameParts.Length - 1];
- containerName = nameParts[nameParts.Length - 2];
- matchingActionsQuery = matchingActionsQuery.Where(f => f.Name == actionName && f.IsSideEffecting == true && f.Container.Name == containerName);
- }
- else if (nameParts.Length > 2)
+ if (entityContainers.Length != 1)
{
- actionName = nameParts[nameParts.Length - 1];
- containerName = nameParts[nameParts.Length - 2];
- nspace = String.Join(".", nameParts.Take(nameParts.Length - 2));
- matchingActionsQuery = matchingActionsQuery.Where(f => f.Name == actionName && f.IsSideEffecting == true && f.Container.Name == containerName && f.Container.Namespace == nspace);
+ throw Error.InvalidOperation(SRResources.ParserModelMustHaveOneContainer, entityContainers.Length);
}
+ IEnumerable<IEdmFunctionImport> matchingActionsQuery = entityContainers[0].FunctionImports();
+ string lastSegment = context.Request.RequestUri.Segments.Last();
+ matchingActionsQuery = matchingActionsQuery.GetMatchingActions(lastSegment);
+
IEdmFunctionImport[] possibleMatches = matchingActionsQuery.ToArray();
if (possibleMatches.Length == 0)
{
- throw Error.InvalidOperation(SRResources.ActionNotFound, actionName, context.Request.RequestUri.AbsoluteUri);
+ throw Error.InvalidOperation(SRResources.ActionNotFound, lastSegment, context.Request.RequestUri.AbsoluteUri);
}
else if (possibleMatches.Length > 1)
{
- throw Error.InvalidOperation(SRResources.ActionResolutionFailed, actionName, context.Request.RequestUri.AbsoluteUri);
+ throw Error.InvalidOperation(
+ SRResources.ActionResolutionFailed,
+ lastSegment,
+ String.Join(", ", possibleMatches.Select(match => match.Container.FullName() + "." + match.Name)));
}
return possibleMatches[0];
}
@@ -298,8 +298,6 @@ private static object ConvertCollectionValue(ODataCollectionValue collection, IE
IEdmCollectionTypeReference collectionType = propertyType as IEdmCollectionTypeReference;
Contract.Assert(collectionType != null, "The type for collection must be a IEdmCollectionType.");
- IEdmTypeReference itemType = collectionType.ElementType();
-
ODataEntryDeserializer deserializer = deserializerProvider.GetODataDeserializer(collectionType);
return deserializer.ReadInline(collection, readContext);
}
@@ -167,6 +167,187 @@ public static IEdmTypeReference GetEdmTypeReference(this IEdmModel edmModel, Typ
return null;
}
+ public static IEdmCollectionType GetCollection(this IEdmEntityType entityType)
+ {
+ return new EdmCollectionType(new EdmEntityTypeReference(entityType, isNullable: false));
+ }
+
+ public static bool CanBindTo(this IEdmFunctionImport function, IEdmEntityType entity)
+ {
+ if (function == null)
+ {
+ throw Error.ArgumentNull("function");
+ }
+ if (entity == null)
+ {
+ throw Error.ArgumentNull("entity");
+ }
+ if (!function.IsBindable)
+ {
+ return false;
+ }
+
+ // The binding parameter is the first parameter by convention
+ IEdmFunctionParameter bindingParameter = function.Parameters.FirstOrDefault();
+ if (bindingParameter == null)
+ {
+ return false;
+ }
+
+ IEdmEntityType bindingParameterType = bindingParameter.Type.Definition as IEdmEntityType;
+ if (bindingParameterType == null)
+ {
+ return false;
+ }
+
+ return entity.IsOrInheritsFrom(bindingParameterType);
+ }
+
+ public static bool CanBindTo(this IEdmFunctionImport function, IEdmCollectionType collection)
+ {
+ if (function == null)
+ {
+ throw Error.ArgumentNull("function");
+ }
+ if (collection == null)
+ {
+ throw Error.ArgumentNull("collection");
+ }
+ if (!function.IsBindable)
+ {
+ return false;
+ }
+
+ // The binding parameter is the first parameter by convention
+ IEdmFunctionParameter bindingParameter = function.Parameters.FirstOrDefault();
+ if (bindingParameter == null)
+ {
+ return false;
+ }
+
+ IEdmCollectionType bindingParameterType = bindingParameter.Type.Definition as IEdmCollectionType;
+ if (bindingParameterType == null)
+ {
+ return false;
+ }
+
+ IEdmEntityType bindingParameterElementType = bindingParameterType.ElementType.Definition as IEdmEntityType;
+ IEdmEntityType entity = collection.ElementType.Definition as IEdmEntityType;
+ if (bindingParameterElementType == null || entity == null)
+ {
+ return false;
+ }
+
+ return entity.IsOrInheritsFrom(bindingParameterElementType);
+ }
+
+ public static IEnumerable<IEdmFunctionImport> GetMatchingActions(this IEnumerable<IEdmFunctionImport> functions, string actionIdentifier)
+ {
+ if (functions == null)
+ {
+ throw Error.ArgumentNull("functions");
+ }
+ if (actionIdentifier == null)
+ {
+ throw Error.ArgumentNull("actionIdentifier");
+ }
+
+ string[] nameParts = actionIdentifier.Split('.');
+ Contract.Assert(nameParts.Length != 0);
+
+ if (nameParts.Length == 1)
+ {
+ // Name
+ string name = nameParts[0];
+ return functions.Where(f => f.IsSideEffecting && f.Name == name);
+ }
+ else if (nameParts.Length == 2)
+ {
+ // Container.Name
+ string name = nameParts[nameParts.Length - 1];
+ string container = nameParts[nameParts.Length - 2];
+ return functions.Where(f => f.IsSideEffecting && f.Name == name && f.Container.Name == container);
+ }
+ else
+ {
+ // Namespace.Container.Name
+ string name = nameParts[nameParts.Length - 1];
+ string container = nameParts[nameParts.Length - 2];
+ string nspace = String.Join(".", nameParts.Take(nameParts.Length - 2));
+ return functions.Where(f => f.IsSideEffecting && f.Name == name && f.Container.Name == container && f.Container.Namespace == nspace);
+ }
+ }
+
+ public static IEdmFunctionImport FindBindableAction(this IEnumerable<IEdmFunctionImport> functions, IEdmEntityType entityType, string actionIdentifier)
+ {
+ if (functions == null)
+ {
+ throw Error.ArgumentNull("functions");
+ }
+ if (entityType == null)
+ {
+ throw Error.ArgumentNull("entityType");
+ }
+ if (actionIdentifier == null)
+ {
+ throw Error.ArgumentNull("actionIdentifier");
+ }
+
+ IEdmFunctionImport[] matches = functions.GetMatchingActions(actionIdentifier).Where(fi => fi.CanBindTo(entityType)).ToArray();
+
+ if (matches.Length > 1)
+ {
+ throw Error.InvalidOperation(
+ SRResources.ActionResolutionFailed,
+ actionIdentifier,
+ String.Join(", ", matches.Select(match => match.Container.FullName() + "." + match.Name)));
+ }
+ else if (matches.Length == 1)
+ {
+ return matches[0];
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ public static IEdmFunctionImport FindBindableAction(this IEnumerable<IEdmFunctionImport> functions, IEdmCollectionType collectionType, string actionIdentifier)
+ {
+ if (functions == null)
+ {
+ throw Error.ArgumentNull("functions");
+ }
+ if (collectionType == null)
+ {
+ throw Error.ArgumentNull("collectionType");
+ }
+ if (actionIdentifier == null)
+ {
+ throw Error.ArgumentNull("actionIdentifier");
+ }
+
+ IEdmFunctionImport[] matches = functions.GetMatchingActions(actionIdentifier).Where(fi => fi.CanBindTo(collectionType)).ToArray();
+
+ if (matches.Length > 1)
+ {
+ IEdmEntityType elementType = collectionType.ElementType as IEdmEntityType;
+ Contract.Assert(elementType != null);
+ throw Error.InvalidOperation(
+ SRResources.ActionResolutionFailed,
+ actionIdentifier,
+ String.Join(", ", matches.Select(match => match.Container.FullName() + "." + match.Name)));
+ }
+ else if (matches.Length == 1)
+ {
+ return matches[0];
+ }
+ else
+ {
+ return null;
+ }
+ }
+
public static Type GetClrType(IEdmTypeReference edmTypeReference, IEdmModel edmModel)
{
return GetClrType(edmTypeReference, edmModel, _defaultAssemblyResolver);
@@ -0,0 +1,67 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
+
+using Microsoft.Data.Edm;
+
+namespace System.Web.Http.OData.Routing
+{
+ /// <summary>
+ /// An <see cref="ODataPathSegment"/> implementation representing an action invocation.
+ /// </summary>
+ public class ActionPathSegment : ODataPathSegment
+ {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ActionPathSegment" /> class.
+ /// </summary>
+ /// <param name="previous">The previous segment in the path.</param>
+ /// <param name="action">The action being invoked.</param>
+ public ActionPathSegment(ODataPathSegment previous, IEdmFunctionImport action)
+ : base(previous)
+ {
+ if (action == null)
+ {
+ throw Error.ArgumentNull("action");
+ }
+
+ IEdmTypeReference returnType = action.ReturnType;
+ EdmType = returnType == null ? null : returnType.Definition;
+
+ IEdmEntitySet functionEntitySet = null;
+ if (action.TryGetStaticEntitySet(out functionEntitySet))
+ {
+ EntitySet = functionEntitySet;
+ }
+ Action = action;
+ }
+
+ /// <summary>
+ /// Gets the segment kind for the current segment.
+ /// </summary>
+ public override string SegmentKind
+ {
+ get
+ {
+ return ODataSegmentKinds.Action;
+ }
+ }
+
+ /// <summary>
+ /// Gets the action being invoked.
+ /// </summary>
+ public IEdmFunctionImport Action
+ {
+ get;
+ private set;
+ }
+
+ /// <summary>
+ /// Returns a <see cref="System.String" /> that represents this instance.
+ /// </summary>
+ /// <returns>
+ /// A <see cref="System.String" /> that represents this instance.
+ /// </returns>
+ public override string ToString()
+ {
+ return Action.Container.FullName() + "." + Action.Name;
+ }
+ }
+}
@@ -0,0 +1,41 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
+
+namespace System.Web.Http.OData.Routing
+{
+ /// <summary>
+ /// An <see cref="ODataPathSegment"/> implementation representing a $batch segment.
+ /// </summary>
+ public class BatchPathSegment : ODataPathSegment
+ {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="BatchPathSegment" /> class.
+ /// </summary>
+ /// <param name="previous">The previous segment in the path.</param>
+ public BatchPathSegment(ODataPathSegment previous)
+ : base(previous)
+ {
+ }
+
+ /// <summary>
+ /// Gets the segment kind for the current segment.
+ /// </summary>
+ public override string SegmentKind
+ {
+ get
+ {
+ return ODataSegmentKinds.Batch;
+ }
+ }
+
+ /// <summary>
+ /// Returns a <see cref="System.String" /> that represents this instance.
+ /// </summary>
+ /// <returns>
+ /// A <see cref="System.String" /> that represents this instance.
+ /// </returns>
+ public override string ToString()
+ {
+ return ODataSegmentKinds.Batch;
+ }
+ }
+}
Oops, something went wrong.

0 comments on commit 8147192

Please sign in to comment.