private bool InitializeFunctionMetadata(Function function, EntitySet previousEntitySet, out EntitySet returningEntitySet) { // Push the function as the source of the response, and then // push further metadata describing the expected response, if appropriate this.MetadataStack.Push(function); EntitySet entitySet = null; bool foundExpectedEntitySet; ServiceOperationAnnotation serviceOperationAnnotation = function.Annotations.OfType <ServiceOperationAnnotation>().SingleOrDefault(); if (serviceOperationAnnotation == null) { foundExpectedEntitySet = function.TryGetExpectedServiceOperationEntitySet(out entitySet); } else { foundExpectedEntitySet = function.TryGetExpectedActionEntitySet(previousEntitySet, out entitySet); } if (foundExpectedEntitySet) { this.MetadataStack.Push(entitySet); returningEntitySet = entitySet; return(true); } if (function.ReturnType != null) { this.MetadataStack.Push(function.ReturnType); } returningEntitySet = null; return(false); }
/// <summary> /// Find out whether it is an action or WebInvoke request. /// </summary> /// <param name="uriString">The request uri string</param> /// <returns>Whether it is an action or WebInvoke request</returns> internal bool IsInvokeOperationRequest(string uriString) { int operationNameStartIndex = uriString.LastIndexOf('/'); if (operationNameStartIndex > 0) { string functionName = uriString.Substring(operationNameStartIndex + 1); int queryOptionStartIndex = functionName.IndexOf('?'); if (queryOptionStartIndex > 0) { functionName = functionName.Substring(0, queryOptionStartIndex); } // find out if the function is an action or WebInvoke Function function = this.model.Functions.Where(f => f.Name == functionName).SingleOrDefault(); if (function != null) { ServiceOperationAnnotation serviceOperationAnnotation = function.Annotations.OfType <ServiceOperationAnnotation>().SingleOrDefault(); if (serviceOperationAnnotation != null) { return(serviceOperationAnnotation.IsAction); } LegacyServiceOperationAnnotation legacyServiceOperationAnnotation = function.Annotations.OfType <LegacyServiceOperationAnnotation>().SingleOrDefault(); if (legacyServiceOperationAnnotation != null) { return(legacyServiceOperationAnnotation.Method == HttpVerb.Post); } } } return(false); }
/// <summary> /// Determines whether an entity set is expected to be returned from a function call /// </summary> /// <param name="function">the function being called</param> /// <param name="previousEntitySet">the binding entity set</param> /// <param name="returningEntitySet">the expected entity set, if appropriate</param> /// <returns>whether or not an entity set is expected</returns> public static bool TryGetExpectedActionEntitySet(this Function function, EntitySet previousEntitySet, out EntitySet returningEntitySet) { ExceptionUtilities.CheckArgumentNotNull(function, "function"); ServiceOperationAnnotation serviceOperationAnnotation = function.Annotations.OfType <ServiceOperationAnnotation>().Single(); EntityDataType entityDataType = function.ReturnType as EntityDataType; CollectionDataType collectionDataType = function.ReturnType as CollectionDataType; if (collectionDataType != null) { entityDataType = collectionDataType.ElementDataType as EntityDataType; } if (entityDataType != null) { if (serviceOperationAnnotation.EntitySetPath == null) { returningEntitySet = function.Model.GetDefaultEntityContainer().EntitySets.Single(es => es.EntityType == entityDataType.Definition); } else { string navigationPropertyName = serviceOperationAnnotation.EntitySetPath.Substring(serviceOperationAnnotation.EntitySetPath.LastIndexOf('/') + 1); NavigationProperty navigationProperty = previousEntitySet.EntityType.AllNavigationProperties.Single(np => np.Name == navigationPropertyName); var associationSets = function.Model.GetDefaultEntityContainer().AssociationSets.Where(a => a.AssociationType == navigationProperty.Association); AssociationSet associationSet = associationSets.Single(a => a.Ends.Any(e => e.EntitySet == previousEntitySet)); returningEntitySet = associationSet.Ends.Single(es => es.EntitySet != previousEntitySet).EntitySet; } return(true); } returningEntitySet = null; return(false); }
internal static ODataUri ConstructODataUriWithoutActionInformation(IEnumerable <EntitySet> entitySets, ODataUri uri) { var segmentsToInclude = new List <ODataUriSegment>(); ServiceOperationAnnotation serviceOperationAnnotation = null; string setName = null; foreach (var segment in uri.Segments) { var functionSegment = segment as FunctionSegment; if (functionSegment != null && functionSegment.Function.IsAction()) { serviceOperationAnnotation = functionSegment.Function.Annotations.OfType <ServiceOperationAnnotation>().Single(); var toggleBooleanAnnotation = functionSegment.Function.Annotations.OfType <ToggleBoolPropertyValueActionAnnotation>().SingleOrDefault(); if (toggleBooleanAnnotation != null) { setName = toggleBooleanAnnotation.SourceEntitySet; } break; } segmentsToInclude.Add(segment); } if (!serviceOperationAnnotation.BindingKind.IsBound()) { ExceptionUtilities.CheckObjectNotNull(setName, "Cannot find the set name that the action starts from"); var sourceEntitySet = entitySets.Single(es => es.Name == setName); segmentsToInclude.Add(new EntitySetSegment(sourceEntitySet)); } var actionlessODataUri = new ODataUri(segmentsToInclude); actionlessODataUri.CustomQueryOptions = uri.CustomQueryOptions; actionlessODataUri.Filter = uri.Filter; actionlessODataUri.InlineCount = uri.InlineCount; actionlessODataUri.OrderBy = uri.OrderBy; actionlessODataUri.Skip = uri.Skip; actionlessODataUri.SkipToken = uri.SkipToken; actionlessODataUri.Top = uri.Top; EntitySet expectedEntitySet = null; ExceptionUtilities.Assert(actionlessODataUri.TryGetExpectedEntitySet(out expectedEntitySet), "Expected entity set not found"); // expand all navigations for actionlessODataUri so that we do not need to send additional request when calculating expected action result foreach (NavigationProperty navigation in expectedEntitySet.EntityType.NavigationProperties) { actionlessODataUri.ExpandSegments.Add(new List <ODataUriSegment>() { new NavigationSegment(navigation) }); } return(actionlessODataUri); }
/// <summary> /// Declare the extension method given the function container class. /// </summary> /// <param name="functionExtensionClass">Function container class</param> /// <param name="func">Function to add</param> protected virtual void DeclareExtensionMethod(CodeTypeDeclaration functionExtensionClass, Function func) { // retrieve parameters and add the extension method ServiceOperationAnnotation actionAnnotation = func.Annotations.OfType <ServiceOperationAnnotation>().Single(); ExceptionUtilities.Assert(actionAnnotation.BindingKind.IsBound(), "Action function cannot generate extension method with out function having a binding"); FunctionParameter bindingTypeParameter = func.Parameters[0]; CodeMemberMethod method = functionExtensionClass.AddExtensionMethod( func.Name, new CodeParameterDeclarationExpression( this.GetParameterTypeOrFunctionReturnTypeReference(bindingTypeParameter.Annotations, bindingTypeParameter.DataType), bindingTypeParameter.Name)); // add non-binding type parameters foreach (var parameter in func.Parameters.Where(p => p.Name != bindingTypeParameter.Name)) { method.Parameters.Add( new CodeParameterDeclarationExpression( this.GetParameterTypeOrFunctionReturnTypeReference(parameter.Annotations, parameter.DataType), parameter.Name)); } // throw exception if the client code method is called method.Statements.Add( new CodeThrowExceptionStatement( new CodeObjectCreateExpression( new CodeTypeReference(typeof(InvalidOperationException)), new CodeExpression[] { }))); // add method return type if (func.ReturnType != null) { method.ReturnType = this.GetParameterTypeOrFunctionReturnTypeReference(func.Annotations, func.ReturnType); } }
private void VerifyEntityOperations(ODataRequest request, ODataResponse response, List <EntityInstance> entities, bool isTopLevelElement) { foreach (var entity in entities.Where(et => !et.IsNull)) { var instanceEntityType = this.model.EntityTypes.Single(et => et.FullName == entity.FullTypeName); // look at each action/function bound to the same type as entity, ensure it is advertised correctly int numberOfDescriptorsVerified = 0; var functionsWithBindings = this.model.Functions.Where(f => f.Annotations.OfType <ServiceOperationAnnotation>().Any(s => s.BindingKind.IsBound())).ToArray(); foreach (Function function in functionsWithBindings) { ServiceOperationAnnotation serviceOperationAnnotation = function.Annotations.OfType <ServiceOperationAnnotation>().SingleOrDefault(); if (serviceOperationAnnotation != null) { DataType bindingParameterDataType = function.Parameters[0].DataType; EntityDataType bindingEntityDataType = bindingParameterDataType as EntityDataType; if (bindingEntityDataType == null) { ExceptionUtilities.Assert(bindingParameterDataType is CollectionDataType, "Unsupported binding parameter data type."); ExceptionUtilities.Assert(entity.ServiceOperationDescriptors.SingleOrDefault(d => d.Title == function.Name) == null, "Unexpected feed-bound action advertised in entry."); continue; } // Check the base type as well as derived types bool beginServiceOpMatchMatching = bindingEntityDataType.Definition.Model.EntityTypes.Where(t => t.IsKindOf(bindingEntityDataType.Definition)).Where(a => a.FullName.Equals(entity.FullTypeName)).Any(); if (beginServiceOpMatchMatching) { // find ServiceOperationDescriptor that matches the function ServiceOperationDescriptor descriptor = entity.ServiceOperationDescriptors.SingleOrDefault(d => ExtractSimpleActionName(d.Title) == function.Name); bool expectActionDescriptor = true; if (request.Uri.SelectSegments.Count > 0) { expectActionDescriptor = this.ExpectActionWithProjection(request.Uri, function, isTopLevelElement); } if (descriptor == null) { ExceptionUtilities.Assert(!expectActionDescriptor, "Missing service operation descriptor for " + function.Name); } else { expectActionDescriptor = this.VerifyIfOpenType(function, bindingEntityDataType, expectActionDescriptor); ExceptionUtilities.Assert(expectActionDescriptor, "Unexpected service operation descriptor for " + function.Name); // JSONLight will always add the type segment if its a derived type now string derivedTypeFullName = string.Empty; var acceptHeaderValue = response.Headers[HttpHeaders.ContentType]; if (bindingEntityDataType.Definition.BaseType != null) { derivedTypeFullName = string.Concat(bindingEntityDataType.Definition.FullName, "/"); } if (bindingEntityDataType.Definition.FullName != instanceEntityType.FullName) { derivedTypeFullName = string.Concat(instanceEntityType.FullName, "/"); } // check if there is a possible name collision between a property and an action, if so add the Entity Container name to the expected result string containerName = this.BuildExpectedContainerName(function, bindingEntityDataType, instanceEntityType); string expectedMetadataContainerName = string.Concat(function.Model.GetDefaultEntityContainer().Name, "."); // When running in JSONLight descriptor returns full name, not partial if (IsJsonLightResponse(acceptHeaderValue)) { containerName = string.Concat(function.Model.GetDefaultEntityContainer().Name, "."); expectedMetadataContainerName = containerName; } string expectedTarget = string.Concat(entity.Id.TrimEnd('/'), "/", derivedTypeFullName, containerName, function.Name); string expectedMetadata = string.Concat(((ServiceRootSegment)request.Uri.RootSegment).Uri.AbsoluteUri.TrimEnd('/'), "/", Endpoints.Metadata, "#", expectedMetadataContainerName, function.Name); this.Assert(descriptor.Target == expectedTarget, "Expected target " + expectedTarget + " Actual " + descriptor.Target, request, response); this.Assert(descriptor.Metadata == expectedMetadata, "Expected Metadata " + expectedMetadata + " Actual " + descriptor.Metadata, request, response); // verify IsAction flag this.Assert(serviceOperationAnnotation.IsAction == descriptor.IsAction, "Expected action " + serviceOperationAnnotation.IsAction + " Actual " + descriptor.IsAction, request, response); numberOfDescriptorsVerified++; } } } } this.Assert(numberOfDescriptorsVerified == entity.ServiceOperationDescriptors.Count, "Wrong number of action/function.", request, response); this.VerifyExpandedEntityOperations(entity, request, response); } }
/// <summary> /// Builds the statements to call execute on the client /// </summary> /// <param name="continuationExpression">continuation variable</param> /// <param name="functionExpression">functionexpression to build</param> /// <param name="httpMethod">Method to execute</param> /// <returns>List of code statements</returns> protected internal virtual IList <CodeStatement> BuildExecuteUriCall(CodeExpression continuationExpression, QueryCustomFunctionCallExpression functionExpression, HttpVerb httpMethod) { List <CodeStatement> codeStatements = new List <CodeStatement>(); List <CodeExpression> inputParametersVariables = new List <CodeExpression>(); ExceptionUtilities.Assert(functionExpression.Arguments.Count == functionExpression.Function.Parameters.Count, "FunctionExpression Argument must have the same number of parameters as Function"); bool buildFirstParameter = true; if (functionExpression.Function.IsAction()) { ServiceOperationAnnotation serviceOperationAnnotation = functionExpression.Function.Annotations.OfType <ServiceOperationAnnotation>().Single(); if (serviceOperationAnnotation.BindingKind.IsBound()) { // Skip the first argument for bound action as this is the bound parameter buildFirstParameter = false; // build service operation parameters if calling action on servic operation results QueryCustomFunctionCallExpression serviceOperationExpression = functionExpression.Arguments[0] as QueryCustomFunctionCallExpression; if (serviceOperationExpression != null) { this.AppendFunctionParametersForExecuteUriCall(true, codeStatements, inputParametersVariables, serviceOperationExpression); } } } this.AppendFunctionParametersForExecuteUriCall(buildFirstParameter, codeStatements, inputParametersVariables, functionExpression); codeStatements.Add(Code.DeclareVariable("uri", Code.Primitive(this.Uri))); if (functionExpression.Function.ReturnType != null) { var queryClrType = functionExpression.ExpressionType as IQueryClrType; var collectionDataType = functionExpression.ExpressionType as QueryCollectionType; bool isSingleResult = true; if (collectionDataType != null) { queryClrType = collectionDataType.ElementType as IQueryClrType; isSingleResult = false; } ExceptionUtilities.CheckObjectNotNull(queryClrType, "Need to know the CLR Type to query it"); // (IAsyncContinuation continuation, bool isAsync, string uriString, HttpMethod method, object[] inputParameters, DataServiceContext dataContext, ExpectedClientErrorBaseline clientExpectedError) codeStatements.Add(new CodeExpressionStatement(this.ResultComparerVariable.Call( "ExecuteUriAndCompare", new CodeTypeReference[] { Code.TypeRef(queryClrType.ClrType) }, continuationExpression, Code.Primitive(this.IsAsync), Code.Variable("uri"), Code.ObjectValue(httpMethod), new CodeArrayCreateExpression(Code.TypeRef("Microsoft.OData.Client.OperationParameter"), inputParametersVariables.ToArray()), Code.Primitive(isSingleResult), this.ContextVariable, this.ExpectedClientErrorValue))); } else { // (IAsyncContinuation continuation, bool isAsync, string uriString, HttpMethod method, object[] inputParameters, DataServiceContext dataContext, ExpectedClientErrorBaseline clientExpectedError) codeStatements.Add(new CodeExpressionStatement(this.ResultComparerVariable.Call( "ExecuteUriAndCompare", continuationExpression, Code.Primitive(this.IsAsync), Code.Variable("uri"), Code.ObjectValue(httpMethod), new CodeArrayCreateExpression(Code.TypeRef("Microsoft.OData.Client.OperationParameter"), inputParametersVariables.ToArray()), this.ContextVariable, this.ExpectedClientErrorValue))); } return(codeStatements); }
internal static IList <KeyValuePair <string, ITypedValue> > ConvertActionArgumentsToTypedValues(this Function function, ServiceOperationAnnotation actionAnnotation, IList <KeyValuePair <string, QueryExpression> > parameters) { var parameterValues = new List <KeyValuePair <string, ITypedValue> >(); FunctionParameter boundParameter = null; if (actionAnnotation.BindingKind.IsBound()) { boundParameter = function.Parameters[0]; } foreach (var parameterPair in parameters) { var edmFunctionParameterDefinition = function.Parameters.Where(p => p.Name == parameterPair.Key).SingleOrDefault(); ExceptionUtilities.CheckObjectNotNull(edmFunctionParameterDefinition, "Cannot find parameter '{0}' defined in function '{1}'", parameterPair.Key, function.Name); ExceptionUtilities.Assert(edmFunctionParameterDefinition != boundParameter, "Bound parameters MUST not be passed in as they will have already been built for the ODataUri was built"); var primitiveDataType = edmFunctionParameterDefinition.DataType as PrimitiveDataType; var collectionDataType = edmFunctionParameterDefinition.DataType as CollectionDataType; if (primitiveDataType != null) { ExceptionUtilities.CheckObjectNotNull(primitiveDataType, "Expected a primitive data type not '{0}'", primitiveDataType); var primitiveValue = ConvertToPrimitiveValue(parameterPair.Value, primitiveDataType); parameterValues.Add(new KeyValuePair <string, ITypedValue>(edmFunctionParameterDefinition.Name, primitiveValue)); } else if (collectionDataType != null) { ExceptionUtilities.CheckObjectNotNull(collectionDataType, "expected a collection data type, actual '{0}", collectionDataType); var collectionValue = parameterPair.Value.ConvertToMultiValue(collectionDataType.ElementDataType); parameterValues.Add(new KeyValuePair <string, ITypedValue>(edmFunctionParameterDefinition.Name, collectionValue)); } else { var complexArgumentValue = parameterPair.Value; var complexInstance = complexArgumentValue.ConvertToComplexInstance(); parameterValues.Add(new KeyValuePair <string, ITypedValue>(edmFunctionParameterDefinition.Name, complexInstance)); } } return(parameterValues); }