/// <summary>Gets ODataProperty for the given <paramref name="property"/>.</summary> /// <param name="entityToSerialize">Entity that is currently being serialized.</param> /// <param name="property">ResourceProperty instance in question.</param> /// <returns>A instance of ODataProperty for the given <paramref name="property"/>.</returns> private ODataProperty GetODataPropertyForEntityProperty(EntityToSerialize entityToSerialize, ResourceProperty property) { Debug.Assert(entityToSerialize != null, "entityToSerialize != null"); Debug.Assert(property != null && entityToSerialize.ResourceType.Properties.Contains(property), "property != null && currentResourceType.Properties.Contains(property)"); ODataValue odataPropertyValue; if (property.IsOfKind(ResourcePropertyKind.Stream)) { odataPropertyValue = this.GetNamedStreamPropertyValue(entityToSerialize, property); } else { object propertyValue = WebUtil.GetPropertyValue(this.Provider, entityToSerialize.Entity, entityToSerialize.ResourceType, property, null); odataPropertyValue = this.GetPropertyValue(property.Name, property.ResourceType, propertyValue, false /*openProperty*/); } ODataProperty odataProperty = new ODataProperty() { Name = property.Name, Value = odataPropertyValue }; ODataPropertyKind kind = property.IsOfKind(ResourcePropertyKind.Key) ? ODataPropertyKind.Key : property.IsOfKind(ResourcePropertyKind.ETag) ? ODataPropertyKind.ETag : ODataPropertyKind.Unspecified; if (kind != ODataPropertyKind.Unspecified) { odataProperty.SetSerializationInfo(new ODataPropertySerializationInfo { PropertyKind = kind }); } return(odataProperty); }
public OperationLinkBuilderTests() { ResourceType intType = ResourceType.GetPrimitiveResourceType(typeof(int)); var customerType = new ResourceType(typeof(object), ResourceTypeKind.EntityType, null, "FQ.NS", "Customer", false); customerType.CanReflectOnInstanceType = false; customerType.AddProperty(new ResourceProperty("Id", ResourcePropertyKind.Primitive | ResourcePropertyKind.Key, intType) { CanReflectOnInstanceTypeProperty = false }); customerType.SetReadOnly(); var operation = new ServiceAction("Action", intType, OperationParameterBindingKind.Sometimes, new[] { new ServiceActionParameter("P1", customerType), new ServiceActionParameter("P2", intType) }, null); operation.SetReadOnly(); this.operationWithParameters = new OperationWrapper(operation); var typeWithEscapedName = new ResourceType(typeof(object), ResourceTypeKind.ComplexType, null, "FQ NS", "+ /", false); typeWithEscapedName.CanReflectOnInstanceType = false; typeWithEscapedName.AddProperty(new ResourceProperty("Number", ResourcePropertyKind.Primitive, intType) { CanReflectOnInstanceTypeProperty = false }); typeWithEscapedName.SetReadOnly(); operation = new ServiceAction("Action", intType, OperationParameterBindingKind.Sometimes, new[] { new ServiceActionParameter("P1", customerType), new ServiceActionParameter("P2", typeWithEscapedName) }, null); operation.SetReadOnly(); this.operationWithEscapedParameter = new OperationWrapper(operation); var bestCustomerType = new ResourceType(typeof(object), ResourceTypeKind.EntityType, customerType, "FQ.NS", "BestCustomer", false); bestCustomerType.SetReadOnly(); operation = new ServiceAction("Action", intType, OperationParameterBindingKind.Sometimes, new[] { new ServiceActionParameter("P1", customerType) }, null); operation.SetReadOnly(); this.operationBoundToBaseType = new OperationWrapper(operation); this.entityToSerialize = EntityToSerialize.CreateFromExplicitValues(new object(), bestCustomerType, new TestSerializedEntityKey("http://odata.org/Service.svc/Customers/", bestCustomerType.FullName)); var metadataUri = new Uri("http://odata.org/Service.svc/$metadata"); this.testSubject = new OperationLinkBuilder("MyContainer", metadataUri); }
/// <summary> /// Get the ODataStreamReferenceValue instance containing the metadata for named stream property. /// </summary> /// <param name="entityToSerialize">Entity that is currently being serialized.</param> /// <param name="namedStreamProperty">Named stream property for which the link element needs to be written.</param> /// <returns> /// An instance of ODataStreamReferenceValue containing all the metadata about the named stream property. /// </returns> private ODataStreamReferenceValue GetNamedStreamPropertyValue(EntityToSerialize entityToSerialize, ResourceProperty namedStreamProperty) { Debug.Assert(entityToSerialize != null, "entityToSerialize != null"); Debug.Assert(namedStreamProperty != null, "namedStreamProperty != null"); Debug.Assert(namedStreamProperty.IsOfKind(ResourcePropertyKind.Stream), "namedStreamProperty.IsOfKind(ResourcePropertyKind.Stream)"); string mediaETag; Uri readStreamUri; string mediaContentType; this.Service.StreamProvider.GetStreamDescription(entityToSerialize.Entity, namedStreamProperty, this.Service.OperationContext, out mediaETag, out readStreamUri, out mediaContentType); Debug.Assert(WebUtil.IsETagValueValid(mediaETag, true), "WebUtil.IsETagValueValid(mediaETag, true)"); ODataStreamReferenceValue streamReferenceValue = new ODataStreamReferenceValue(); this.PayloadMetadataPropertyManager.SetContentType(streamReferenceValue, mediaContentType); this.PayloadMetadataPropertyManager.SetEditLink( streamReferenceValue, () => { Uri relativeUri = entityToSerialize.SerializedKey.RelativeEditLink; return(RequestUriProcessor.AppendUnescapedSegment(relativeUri, namedStreamProperty.Name)); }); if (!string.IsNullOrEmpty(mediaETag)) { this.PayloadMetadataPropertyManager.SetETag(streamReferenceValue, mediaETag); } // Note if readStreamUri is null, the self link for the named stream will be omitted. this.PayloadMetadataPropertyManager.SetReadLink(streamReferenceValue, () => readStreamUri); return(streamReferenceValue); }
public OperationSerializerTests() { ResourceType intType = ResourceType.GetPrimitiveResourceType(typeof(int)); var customerType = new ResourceType(typeof(object), ResourceTypeKind.EntityType, null, "FQ.NS", "Customer", false); customerType.CanReflectOnInstanceType = false; customerType.AddProperty(new ResourceProperty("Id", ResourcePropertyKind.Primitive | ResourcePropertyKind.Key, intType) { CanReflectOnInstanceTypeProperty = false }); customerType.SetReadOnly(); var operation = new ServiceAction("Action", intType, OperationParameterBindingKind.Sometimes, new[] { new ServiceActionParameter("P1", customerType), new ServiceActionParameter("P2", intType) }, null); operation.SetReadOnly(); this.baseTypeOperation = new OperationWrapper(operation); var bestCustomerType = new ResourceType(typeof(object), ResourceTypeKind.EntityType, customerType, "FQ.NS", "BestCustomer", false); bestCustomerType.SetReadOnly(); operation = new ServiceAction("Action", intType, OperationParameterBindingKind.Sometimes, new[] { new ServiceActionParameter("P1", bestCustomerType) }, null); operation.SetReadOnly(); this.derivedTypeOperation = new OperationWrapper(operation); operation = new ServiceAction("Unambiguous", intType, OperationParameterBindingKind.Sometimes, new[] { new ServiceActionParameter("P1", customerType) }, null); operation.SetReadOnly(); this.unambiguousOperation = new OperationWrapper(operation); this.entityToSerialize = EntityToSerialize.CreateFromExplicitValues(new object(), bestCustomerType, new TestSerializedEntityKey("http://odata.org/Service.svc/Customers(0)/", bestCustomerType.FullName)); this.testSubject = CreateOperationSerializer(AlwaysAdvertiseActions); }
/// <summary>Gets properties of the given entity type instance.</summary> /// <param name="entityToSerialize">Entity that is currently being serialized.</param> /// <param name="projectionNodesForCurrentResourceType">List of all the properties that are being projected for the resource type, or null if no projections are applied. /// The list must be filtered to only nodes which apply to the current resource type.</param> /// <returns>The list of properties for the specified entity.</returns> private IEnumerable <ODataProperty> GetEntityProperties(EntityToSerialize entityToSerialize, IEnumerable <ProjectionNode> projectionNodesForCurrentResourceType) { Debug.Assert(entityToSerialize != null, "entityToSerialize != null"); Debug.Assert( projectionNodesForCurrentResourceType == null || projectionNodesForCurrentResourceType.All(projectionNode => projectionNode.TargetResourceType.IsAssignableFrom(entityToSerialize.ResourceType)), "The projection nodes must be filtered to the current resource type only."); this.RecurseEnter(); try { Debug.Assert(this.CurrentContainer != null, "this.CurrentContainer != null"); if (projectionNodesForCurrentResourceType == null) { return(this.GetAllEntityProperties(entityToSerialize)); } else { // Fill only the properties which we know we will need values for. return(this.GetProjectedEntityProperties(entityToSerialize, projectionNodesForCurrentResourceType)); } } finally { // The matching call to RecurseLeave is in a try/finally block not because it's necessary in the // presence of an exception (progress will halt anyway), but because it's easier to maintain in the // code in the presence of multiple exit points (returns). this.RecurseLeave(); } }
/// <summary>Returns an IEnumerable of ODataProperty instance for all structural properties in the current resource type /// and populates the navigation property information along with association links.</summary> /// <param name="entityToSerialize">Entity that is currently being serialized.</param> /// <returns>Returns an IEnumerable of ODataProperty instance for all structural properties in the current resource type..</returns> private IEnumerable <ODataProperty> GetAllEntityProperties(EntityToSerialize entityToSerialize) { Debug.Assert(entityToSerialize != null, "entityToSerialize != null"); List <ODataProperty> properties = new List <ODataProperty>(entityToSerialize.ResourceType.Properties.Count); foreach (ResourceProperty property in this.Provider.GetResourceSerializableProperties(this.CurrentContainer, entityToSerialize.ResourceType)) { if (property.TypeKind != ResourceTypeKind.EntityType) { properties.Add(this.GetODataPropertyForEntityProperty(entityToSerialize, property)); } } if (entityToSerialize.ResourceType.IsOpenType) { foreach (KeyValuePair <string, object> property in this.Provider.GetOpenPropertyValues(entityToSerialize.Entity)) { string propertyName = property.Key; if (string.IsNullOrEmpty(propertyName)) { throw new DataServiceException(500, Microsoft.OData.Service.Strings.Syndication_InvalidOpenPropertyName(entityToSerialize.ResourceType.FullName)); } properties.Add(this.GetODataPropertyForOpenProperty(propertyName, property.Value)); } } return(properties); }
/// <summary>Returns an IEnumerable of ODataProperty instance for all projected properties in the <paramref name="projectionNodesForCurrentResourceType"/> /// and populates the navigation property information along with association links.</summary> /// <param name="entityToSerialize">Entity that is currently being serialized.</param> /// <param name="projectionNodesForCurrentResourceType">List of all the properties that are being projected for the resource type, or null if no projections are applied. /// The list must be filtered to only nodes which apply to the current resource type.</param> /// <returns>Returns an IEnumerable of ODataProperty instance for all structural properties in the current resource type.</returns> private IEnumerable <ODataProperty> GetProjectedEntityProperties(EntityToSerialize entityToSerialize, IEnumerable <ProjectionNode> projectionNodesForCurrentResourceType) { Debug.Assert(entityToSerialize != null, "entityToSerialize != null"); Debug.Assert( projectionNodesForCurrentResourceType == null || projectionNodesForCurrentResourceType.All(projectionNode => projectionNode.TargetResourceType.IsAssignableFrom(entityToSerialize.ResourceType)), "The projection nodes must be filtered to the current resource type only."); List <ODataProperty> properties = new List <ODataProperty>(entityToSerialize.ResourceType.Properties.Count); foreach (ProjectionNode projectionNode in projectionNodesForCurrentResourceType) { string propertyName = projectionNode.PropertyName; var resourceProperty = projectionNode.Property; if (resourceProperty != null) { if (resourceProperty.TypeKind != ResourceTypeKind.EntityType) { properties.Add(this.GetODataPropertyForEntityProperty(entityToSerialize, resourceProperty)); } } else { // Now get the property value object propertyValue = WebUtil.GetPropertyValue(this.Provider, entityToSerialize.Entity, entityToSerialize.ResourceType, null, propertyName); properties.Add(this.GetODataPropertyForOpenProperty(propertyName, propertyValue)); } } return(properties); }
/// <summary> /// Asks the provider if the action should be advertised in payloads. /// </summary> /// <param name="entityToSerialize">The entity to serialize.</param> /// <param name="resourceInstanceInFeed">Whether or not the entity is being serialized in a feed.</param> /// <param name="serviceOperationWrapper">The service operation wrapper.</param> /// <param name="lazyActionTargetUri">Target uri of the action, which will only be generated if needed.</param> /// <param name="entityHasMultipleActionsWithSameName">Whether or not there are multiple operations in the current scope with the same name as the current operation.</param> /// <param name="odataAction">The ODL object-model representation of the action.</param> /// <returns>Whether or not the action should be advertised.</returns> private bool AskProviderIfActionShouldBeAdvertised(EntityToSerialize entityToSerialize, bool resourceInstanceInFeed, OperationWrapper serviceOperationWrapper, SimpleLazy <Uri> lazyActionTargetUri, bool entityHasMultipleActionsWithSameName, ref ODataAction odataAction) { if (this.advertiseServiceAction(serviceOperationWrapper, entityToSerialize.Entity, resourceInstanceInFeed, ref odataAction)) { if (odataAction == null) { throw new DataServiceException(500, Microsoft.OData.Service.Strings.DataServiceActionProviderWrapper_AdvertiseServiceActionCannotReturnNullActionToSerialize); } // Always set target and title if there are overloaded actions. if (!entityHasMultipleActionsWithSameName) { this.metadataPropertyManager.CheckForUnmodifiedTitle(odataAction, serviceOperationWrapper.Name); this.metadataPropertyManager.CheckForUnmodifiedTarget(odataAction, () => lazyActionTargetUri.Value); } // make the target link relative this.MakeOperationTargetRelativeFromMetadataUriIfJsonLight(odataAction); return(true); } odataAction = null; return(false); }
/// <summary> /// Return the canonical uri (the edit link) of the element. /// </summary> /// <param name="element">Element whose canonical uri need to be returned.</param> /// <returns>Return the canonical uri of the element.</returns> private Uri GetEntityEditLink(object element) { Debug.Assert(element != null, "element != null"); ResourceType resourceType = WebUtil.GetNonPrimitiveResourceType(this.Provider, element); EntityToSerialize entity = EntityToSerialize.Create(element, resourceType, this.CurrentContainer, this.Provider, this.AbsoluteServiceUri); return(entity.SerializedKey.AbsoluteEditLink); }
/// <summary> /// Get the stream reference value for media resource (the default stream of an entity). /// </summary> /// <param name="entityToSerialize">Entity that is currently being serialized.</param> /// <param name="title">The title for the element being written.</param> /// <returns> /// An instance of ODataStreamReferenceValue containing the metadata about the media resource. /// </returns> private ODataStreamReferenceValue GetMediaResource(EntityToSerialize entityToSerialize, string title) { Debug.Assert(entityToSerialize.Entity != null, "element != null"); Debug.Assert(entityToSerialize.ResourceType != null && entityToSerialize.ResourceType.ResourceTypeKind == ResourceTypeKind.EntityType, "type != null && type.ResourceTypeKind == ResourceTypeKind.EntityType"); Debug.Assert(!string.IsNullOrEmpty(title), "!string.IsNullOrEmpty(title)"); ODataStreamReferenceValue mediaResource = null; // Handle MLE if (entityToSerialize.ResourceType.IsMediaLinkEntry) { string mediaETag; Uri readStreamUri; string mediaContentType; Debug.Assert(entityToSerialize.ResourceType.ResourceTypeKind == ResourceTypeKind.EntityType, "type.ResourceTypeKind == ResourceTypeKind.EntityType"); this.Service.StreamProvider.GetStreamDescription(entityToSerialize.Entity, null /*null for MLE*/, this.Service.OperationContext, out mediaETag, out readStreamUri, out mediaContentType); Debug.Assert(WebUtil.IsETagValueValid(mediaETag, true), "WebUtil.IsETagValueValid(mediaETag, true)"); Debug.Assert(!string.IsNullOrEmpty(mediaContentType), "!string.IsNullOrEmpty(mediaContentType)"); mediaResource = new ODataStreamReferenceValue(); // build the stream's edit link lazily to avoid creating the entity's edit link if it is not needed. SimpleLazy <Uri> lazyStreamEditLink = new SimpleLazy <Uri>(() => RequestUriProcessor.AppendEscapedSegment(entityToSerialize.SerializedKey.RelativeEditLink, XmlConstants.UriValueSegment)); this.PayloadMetadataPropertyManager.SetEditLink(mediaResource, () => lazyStreamEditLink.Value); this.PayloadMetadataPropertyManager.SetContentType(mediaResource, mediaContentType); // If the stream provider did not provider a read link, then we should use the edit link as the read link. this.PayloadMetadataPropertyManager.SetReadLink(mediaResource, () => readStreamUri ?? lazyStreamEditLink.Value); #pragma warning disable 618 if (this.contentFormat == ODataFormat.Atom) #pragma warning restore 618 { AtomStreamReferenceMetadata mediaResourceAtom = new AtomStreamReferenceMetadata() { EditLink = new AtomLinkMetadata { Title = title } }; mediaResource.SetAnnotation(mediaResourceAtom); } if (!string.IsNullOrEmpty(mediaETag)) { this.PayloadMetadataPropertyManager.SetETag(mediaResource, mediaETag); } } return(mediaResource); }
/// <summary> /// Tries to serialize the operation. /// </summary> /// <param name="entityToSerialize">The entity to serialize.</param> /// <param name="resourceInstanceInFeed">Whether or not the entity is being serialized in a feed.</param> /// <param name="entityHasMultipleActionsWithSameName">Whether or not there are multiple operations in the current scope with the same name as the current operation.</param> /// <param name="serviceOperationWrapper">The service operation wrapper.</param> /// <param name="odataAction">The ODL object-model representation of the action.</param> /// <returns>Whether or not to serialize the operation.</returns> private bool TrySerializeOperation(EntityToSerialize entityToSerialize, bool resourceInstanceInFeed, bool entityHasMultipleActionsWithSameName, OperationWrapper serviceOperationWrapper, out ODataAction odataAction) { Debug.Assert(serviceOperationWrapper != null, "serviceOperationWrapper != null"); // We only advertise actions. This is a debug assert because GetServiceOperationsByResourceType only returns actions. Debug.Assert(serviceOperationWrapper.Kind == OperationKind.Action, "Only actions can be advertised"); Uri metadata = this.operationLinkBuilder.BuildMetadataLink(serviceOperationWrapper, entityHasMultipleActionsWithSameName); // If the action has OperationParameterBindingKind set to "Always" then we advertise the action without calling "AdvertiseServiceAction". bool isAlwaysAvailable = serviceOperationWrapper.OperationParameterBindingKind == OperationParameterBindingKind.Always; odataAction = new ODataAction { Metadata = metadata }; // There is some subtlety to the interaction between action advertisement and whether or not to include title/target on the wire. // // 1) If an action is always available: // The provider author does not get a chance to customize the title/target values... // so the values will be based on conventions... // so by default do not write them on the wire // 2) If it is only sometimes available: // The values need to be computed to provide them on the instance given to the provider... // but they might not be changed by the provider author... // so compare them to the computed values, and do not write them if they match. // TODO: Action provider should be able to customize title/target even if the action is 'always' advertised // If this gets fixed, then all the behavior should collapse to emulate case #2 above // Create a lazy Uri for the target, because we may need it more than once (see case #2 above). SimpleLazy <Uri> lazyActionTargetUri = new SimpleLazy <Uri>(() => this.operationLinkBuilder.BuildTargetLink(entityToSerialize, serviceOperationWrapper, entityHasMultipleActionsWithSameName)); this.metadataPropertyManager.SetTitle(odataAction, isAlwaysAvailable, serviceOperationWrapper.Name); this.metadataPropertyManager.SetTarget(odataAction, isAlwaysAvailable, () => lazyActionTargetUri.Value); // If the operation is always available, // 1. Return true for MetadataQueryOption.All. // 2. Return false for MetadataQueryOption.None. // 3. Return false for MetadataQueryOption.Default. if (isAlwaysAvailable) { return(this.payloadMetadataParameterInterpreter.ShouldIncludeAlwaysAvailableOperation()); } return(this.AskProviderIfActionShouldBeAdvertised(entityToSerialize, resourceInstanceInFeed, serviceOperationWrapper, lazyActionTargetUri, entityHasMultipleActionsWithSameName, ref odataAction)); }
private void WriteAllNestedComplexProperties(EntityToSerialize entityToSerialize, IEnumerable <ProjectionNode> projectionNodesForCurrentResourceType) { Debug.Assert(entityToSerialize != null, "entityToSerialize != null"); var properties = projectionNodesForCurrentResourceType == null ? this.Provider.GetResourceSerializableProperties(this.CurrentContainer, entityToSerialize.ResourceType) : projectionNodesForCurrentResourceType.Select(p => p.Property); foreach (ResourceProperty property in properties) { if (property != null && (property.TypeKind == ResourceTypeKind.ComplexType || (property.TypeKind == ResourceTypeKind.Collection && property.ResourceType.ElementType().ResourceTypeKind == ResourceTypeKind.ComplexType))) { ODataWriterHelper.WriteNestedResourceInfo(this.dataServicesODataWriter.InnerWriter, this.GetODataNestedResourceForComplexProperty(entityToSerialize, property)); } } }
/// <summary> /// Gets the target link value for an <see cref="ODataOperation"/> /// </summary> /// <param name="entityToSerialize">The current entity being serialized.</param> /// <param name="operation">The operation to generate the link for.</param> /// <param name="entityHasMultipleActionsWithSameName">Whether or not there are multiple operations in the current scope with the same name as the current operation.</param> /// <returns>Uri representing link to use for invoking this operation.</returns> internal Uri BuildTargetLink(EntityToSerialize entityToSerialize, OperationWrapper operation, bool entityHasMultipleActionsWithSameName) { Debug.Assert(entityToSerialize != null, "entityToSerialize != null"); Debug.Assert(operation != null, "operation != null"); Debug.Assert(operation.BindingParameter != null, "operation.BindingParameter != null"); Debug.Assert(operation.BindingParameter.ParameterType != null, "operation.BindingParameter.ParameterType != null"); string targetSegment = operation.GetActionTargetSegmentByResourceType(entityToSerialize.ResourceType, this.namespaceName); // If there are multiple operations with the same name, then using the edit link of the entry would cause the target to potentially resolve to the wrong // operation. Instead, use the actual binding type of the specific operation. if (entityHasMultipleActionsWithSameName) { Uri editLinkWithBindingType = RequestUriProcessor.AppendUnescapedSegment(entityToSerialize.SerializedKey.AbsoluteEditLinkWithoutSuffix, operation.BindingParameter.ParameterType.FullName); return(RequestUriProcessor.AppendUnescapedSegment(editLinkWithBindingType, targetSegment)); } return(RequestUriProcessor.AppendUnescapedSegment(entityToSerialize.SerializedKey.AbsoluteEditLink, targetSegment)); }
/// <summary> /// Gets the target link value for an <see cref="ODataOperation"/> /// </summary> /// <param name="entityToSerialize">The current entity being serialized.</param> /// <param name="operation">The operation to generate the link for.</param> /// <param name="entityHasMultipleActionsWithSameName">Whether or not there are multiple operations in the current scope with the same name as the current operation.</param> /// <returns>Uri representing link to use for invoking this operation.</returns> internal Uri BuildTargetLink(EntityToSerialize entityToSerialize, OperationWrapper operation, bool entityHasMultipleActionsWithSameName) { Debug.Assert(entityToSerialize != null, "entityToSerialize != null"); Debug.Assert(operation != null, "operation != null"); Debug.Assert(operation.BindingParameter != null, "operation.BindingParameter != null"); Debug.Assert(operation.BindingParameter.ParameterType != null, "operation.BindingParameter.ParameterType != null"); string targetSegment = operation.GetActionTargetSegmentByResourceType(entityToSerialize.ResourceType, this.namespaceName); // If there are multiple operations with the same name, then using the edit link of the entry would cause the target to potentially resolve to the wrong // operation. Instead, use the actual binding type of the specific operation. if (entityHasMultipleActionsWithSameName) { Uri editLinkWithBindingType = RequestUriProcessor.AppendUnescapedSegment(entityToSerialize.SerializedKey.AbsoluteEditLinkWithoutSuffix, operation.BindingParameter.ParameterType.FullName); return RequestUriProcessor.AppendUnescapedSegment(editLinkWithBindingType, targetSegment); } return RequestUriProcessor.AppendUnescapedSegment(entityToSerialize.SerializedKey.AbsoluteEditLink, targetSegment); }
/// <summary> /// Serializes the given operations and returns the resulting instances of <see cref="ODataAction"/>. /// </summary> /// <param name="entityToSerialize">The entity to serialize.</param> /// <param name="resourceInstanceInFeed">Whether or not the entity is being serialized in a feed.</param> /// <param name="operationWrappers">The wrapped operations to serialize.</param> /// <returns>The serialized actions.</returns> internal IEnumerable <ODataAction> SerializeOperations(EntityToSerialize entityToSerialize, bool resourceInstanceInFeed, ICollection <OperationWrapper> operationWrappers) { Debug.Assert(operationWrappers != null, "serviceOperationWrapperList != null"); var odataActions = new List <ODataAction>(operationWrappers.Count); // Create a hashset containing the names of all actions in the current scope that are duplicates. HashSet <string> collidingActionNames = GetCollidingActionNames(operationWrappers); foreach (OperationWrapper wrapper in operationWrappers) { bool actionNameHasCollision = collidingActionNames.Contains(wrapper.Name); ODataAction odataAction; if (this.TrySerializeOperation(entityToSerialize, resourceInstanceInFeed, actionNameHasCollision, wrapper, out odataAction)) { odataActions.Add(odataAction); } } return(odataActions); }
/// <summary> /// Tries to build the set of <see cref="ODataAction"/> to be advertised for the given resource. /// </summary> /// <param name="entityToSerialize">Entity that is currently being serialized.</param> /// <param name="resourceInstanceInFeed">true if the resource instance being serialized is inside a feed; false otherwise.</param> /// <param name="actions">The actions to advertise.</param> /// <returns> /// Whether any actions should be advertised. /// </returns> private bool TryGetAdvertisedActions(EntityToSerialize entityToSerialize, bool resourceInstanceInFeed, out IEnumerable <ODataAction> actions) { Debug.Assert(entityToSerialize != null, "entityToSerialize != null"); if (!this.Service.ActionProvider.IsImplemented) { actions = null; return(false); } // If GetOperationProjections returns null it means no projections are to be applied and all operations // for the current segment should be serialized. If it returns non-null only the operations described // by the returned projection segments should be serialized. ExpandedProjectionNode expandedProjectionNode = this.GetCurrentExpandedProjectionNode(); List <OperationWrapper> serviceOperationWrapperList; if (expandedProjectionNode == null || expandedProjectionNode.ProjectAllOperations) { // Note that if the service does not implement IDataServiceActionProvider and the MaxProtocolVersion < V3, // GetServiceActionsByBindingParameterType() would simply return an empty ServiceOperationWrapper collection. serviceOperationWrapperList = this.Service.ActionProvider.GetServiceActionsByBindingParameterType(entityToSerialize.ResourceType); } else { serviceOperationWrapperList = expandedProjectionNode.SelectedOperations.GetSelectedOperations(entityToSerialize.ResourceType); } Debug.Assert(serviceOperationWrapperList != null, "serviceOperationWrappers != null"); if (serviceOperationWrapperList.Count <= 0) { actions = null; return(false); } actions = this.OperationSerializer.SerializeOperations(entityToSerialize, resourceInstanceInFeed, serviceOperationWrapperList); return(true); }
/// <summary> /// Creates the navigation link for the given navigation property. /// </summary> /// <param name="entityToSerialize">Entity that is currently being serialized.</param> /// <param name="navigationProperty">The metadata for the navigation property.</param> /// <returns>The navigation link for the given property.</returns> private ODataNavigationLink GetNavigationLink(EntityToSerialize entityToSerialize, ResourceProperty navigationProperty) { Debug.Assert(entityToSerialize != null, "entityToSerialize != null"); Debug.Assert(navigationProperty != null, "navigationProperty != null"); string navLinkName = navigationProperty.Name; ODataNavigationLink navLink = new ODataNavigationLink { Name = navLinkName, IsCollection = navigationProperty.Kind == ResourcePropertyKind.ResourceSetReference }; // Always pass the relative uri to the ODatalib. For Json, they will convert the relative uri // into absolute uri using the BaseUri property on the ODataWriterSettings. For atom, ODataLib // will write the relative uri. this.PayloadMetadataPropertyManager.SetUrl(navLink, () => RequestUriProcessor.AppendUnescapedSegment(entityToSerialize.SerializedKey.RelativeEditLink, navLinkName)); if (this.Service.Configuration.DataServiceBehavior.IncludeAssociationLinksInResponse) { this.PayloadMetadataPropertyManager.SetAssociationLinkUrl(navLink, () => GetAssociationLinkUrl(entityToSerialize, navigationProperty)); } return(navLink); }
private void WriteNavigationProperties(IExpandedResult expanded, EntityToSerialize entityToSerialize, bool resourceInstanceInFeed, IEnumerable <ProjectionNode> projectionNodesForCurrentResourceType) { Debug.Assert(entityToSerialize != null, "entityToSerialize != null"); Debug.Assert( projectionNodesForCurrentResourceType == null || projectionNodesForCurrentResourceType.All(projectionNode => projectionNode.TargetResourceType.IsAssignableFrom(entityToSerialize.ResourceType)), "The projection nodes must be filtered to the current resource type only."); IEnumerable <ResourceProperty> navProperties = projectionNodesForCurrentResourceType == null ? this.Provider.GetResourceSerializableProperties(this.CurrentContainer, entityToSerialize.ResourceType).Where(p => p.TypeKind == ResourceTypeKind.EntityType) : projectionNodesForCurrentResourceType.Where(p => p.Property != null && p.Property.TypeKind == ResourceTypeKind.EntityType).Select(p1 => p1.Property); foreach (ResourceProperty property in navProperties) { ResourcePropertyInfo navProperty = this.GetNavigationPropertyInfo(expanded, entityToSerialize.Entity, entityToSerialize.ResourceType, property); ODataNavigationLink navLink = this.GetNavigationLink(entityToSerialize, navProperty.Property); // WCF Data Services show performance degradation with JsonLight when entities have > 25 Navgations on writing entries // DEVNOTE: for performance reasons, if the link has no content due to the metadata level, and is not expanded // then don't tell ODataLib about it at all. if (navLink.Url == null && navLink.AssociationLinkUrl == null && !navProperty.Expand) { continue; } var linkArgs = new DataServiceODataWriterNavigationLinkArgs(navLink, this.Service.OperationContext); this.dataServicesODataWriter.WriteStart(linkArgs); if (navProperty.Expand) { object navPropertyValue = navProperty.Value; IExpandedResult navPropertyExpandedResult = navPropertyValue as IExpandedResult; object expandedPropertyValue = navPropertyExpandedResult != null? GetExpandedElement(navPropertyExpandedResult) : navPropertyValue; bool needPop = this.PushSegmentForProperty(navProperty.Property, entityToSerialize.ResourceType, navProperty.ExpandedNode); // if this.CurrentContainer is null, the target set of the navigation property is hidden. if (this.CurrentContainer != null) { if (navProperty.Property.Kind == ResourcePropertyKind.ResourceSetReference) { IEnumerable enumerable; bool collection = WebUtil.IsElementIEnumerable(expandedPropertyValue, out enumerable); Debug.Assert(collection, "metadata loading must have ensured that navigation set properties must implement IEnumerable"); QueryResultInfo queryResults = new QueryResultInfo(enumerable); try { queryResults.MoveNext(); Func <Uri> getNavPropertyRelativeUri = () => RequestUriProcessor.AppendUnescapedSegment(entityToSerialize.SerializedKey.RelativeEditLink, navLink.Name); Func <Uri> getNavPropertyAbsoluteUri = () => RequestUriProcessor.AppendUnescapedSegment(entityToSerialize.SerializedKey.AbsoluteEditLink, navLink.Name); this.WriteFeedElements(navPropertyExpandedResult, queryResults, navProperty.Property.ResourceType, navLink.Name, getNavPropertyRelativeUri, getNavPropertyAbsoluteUri, false); } finally { // After the navigation property is completely serialized, dispose the property value. WebUtil.Dispose(queryResults); } } else if (WebUtil.IsNullValue(expandedPropertyValue)) { // Write a null reference navigation property var entryArgs = new DataServiceODataWriterEntryArgs(null, null, this.Service.OperationContext); this.dataServicesODataWriter.WriteStart(entryArgs); this.dataServicesODataWriter.WriteEnd(entryArgs); } else { this.WriteEntry(navPropertyExpandedResult, expandedPropertyValue, resourceInstanceInFeed, navProperty.Property.ResourceType); } } this.PopSegmentName(needPop); } this.dataServicesODataWriter.WriteEnd(linkArgs); // end of navigation property } }
/// <summary>Gets properties of the given entity type instance.</summary> /// <param name="entityToSerialize">Entity that is currently being serialized.</param> /// <param name="projectionNodesForCurrentResourceType">List of all the properties that are being projected for the resource type, or null if no projections are applied. /// The list must be filtered to only nodes which apply to the current resource type.</param> /// <returns>The list of properties for the specified entity.</returns> private IEnumerable<ODataProperty> GetEntityProperties(EntityToSerialize entityToSerialize, IEnumerable<ProjectionNode> projectionNodesForCurrentResourceType) { Debug.Assert(entityToSerialize != null, "entityToSerialize != null"); Debug.Assert( projectionNodesForCurrentResourceType == null || projectionNodesForCurrentResourceType.All(projectionNode => projectionNode.TargetResourceType.IsAssignableFrom(entityToSerialize.ResourceType)), "The projection nodes must be filtered to the current resource type only."); this.RecurseEnter(); try { Debug.Assert(this.CurrentContainer != null, "this.CurrentContainer != null"); if (projectionNodesForCurrentResourceType == null) { return this.GetAllEntityProperties(entityToSerialize); } else { // Fill only the properties which we know we will need values for. return this.GetProjectedEntityProperties(entityToSerialize, projectionNodesForCurrentResourceType); } } finally { // The matching call to RecurseLeave is in a try/finally block not because it's necessary in the // presence of an exception (progress will halt anyway), but because it's easier to maintain in the // code in the presence of multiple exit points (returns). this.RecurseLeave(); } }
/// <summary>Returns an IEnumerable of ODataProperty instance for all structural properties in the current resource type /// and populates the navigation property information along with association links.</summary> /// <param name="entityToSerialize">Entity that is currently being serialized.</param> /// <returns>Returns an IEnumerable of ODataProperty instance for all structural properties in the current resource type..</returns> private IEnumerable<ODataProperty> GetAllEntityProperties(EntityToSerialize entityToSerialize) { Debug.Assert(entityToSerialize != null, "entityToSerialize != null"); List<ODataProperty> properties = new List<ODataProperty>(entityToSerialize.ResourceType.Properties.Count); foreach (ResourceProperty property in this.Provider.GetResourceSerializableProperties(this.CurrentContainer, entityToSerialize.ResourceType)) { if (property.TypeKind != ResourceTypeKind.EntityType) { properties.Add(this.GetODataPropertyForEntityProperty(entityToSerialize, property)); } } if (entityToSerialize.ResourceType.IsOpenType) { foreach (KeyValuePair<string, object> property in this.Provider.GetOpenPropertyValues(entityToSerialize.Entity)) { string propertyName = property.Key; if (string.IsNullOrEmpty(propertyName)) { throw new DataServiceException(500, Microsoft.OData.Service.Strings.Syndication_InvalidOpenPropertyName(entityToSerialize.ResourceType.FullName)); } properties.Add(this.GetODataPropertyForOpenProperty(propertyName, property.Value)); } } return properties; }
/// <summary>Returns an IEnumerable of ODataProperty instance for all projected properties in the <paramref name="projectionNodesForCurrentResourceType"/> /// and populates the navigation property information along with association links.</summary> /// <param name="entityToSerialize">Entity that is currently being serialized.</param> /// <param name="projectionNodesForCurrentResourceType">List of all the properties that are being projected for the resource type, or null if no projections are applied. /// The list must be filtered to only nodes which apply to the current resource type.</param> /// <returns>Returns an IEnumerable of ODataProperty instance for all structural properties in the current resource type.</returns> private IEnumerable<ODataProperty> GetProjectedEntityProperties(EntityToSerialize entityToSerialize, IEnumerable<ProjectionNode> projectionNodesForCurrentResourceType) { Debug.Assert(entityToSerialize != null, "entityToSerialize != null"); Debug.Assert( projectionNodesForCurrentResourceType == null || projectionNodesForCurrentResourceType.All(projectionNode => projectionNode.TargetResourceType.IsAssignableFrom(entityToSerialize.ResourceType)), "The projection nodes must be filtered to the current resource type only."); List<ODataProperty> properties = new List<ODataProperty>(entityToSerialize.ResourceType.Properties.Count); foreach (ProjectionNode projectionNode in projectionNodesForCurrentResourceType) { string propertyName = projectionNode.PropertyName; var resourceProperty = projectionNode.Property; if (resourceProperty != null) { if (resourceProperty.TypeKind != ResourceTypeKind.EntityType) { properties.Add(this.GetODataPropertyForEntityProperty(entityToSerialize, resourceProperty)); } } else { // Now get the property value object propertyValue = WebUtil.GetPropertyValue(this.Provider, entityToSerialize.Entity, entityToSerialize.ResourceType, null, propertyName); properties.Add(this.GetODataPropertyForOpenProperty(propertyName, propertyValue)); } } return properties; }
private void WriteNavigationProperties(IExpandedResult expanded, EntityToSerialize entityToSerialize, bool resourceInstanceInFeed, IEnumerable<ProjectionNode> projectionNodesForCurrentResourceType) { Debug.Assert(entityToSerialize != null, "entityToSerialize != null"); Debug.Assert( projectionNodesForCurrentResourceType == null || projectionNodesForCurrentResourceType.All(projectionNode => projectionNode.TargetResourceType.IsAssignableFrom(entityToSerialize.ResourceType)), "The projection nodes must be filtered to the current resource type only."); IEnumerable<ResourceProperty> navProperties = projectionNodesForCurrentResourceType == null ? this.Provider.GetResourceSerializableProperties(this.CurrentContainer, entityToSerialize.ResourceType).Where(p => p.TypeKind == ResourceTypeKind.EntityType) : projectionNodesForCurrentResourceType.Where(p => p.Property != null && p.Property.TypeKind == ResourceTypeKind.EntityType).Select(p1 => p1.Property); foreach (ResourceProperty property in navProperties) { ResourcePropertyInfo navProperty = this.GetNavigationPropertyInfo(expanded, entityToSerialize.Entity, entityToSerialize.ResourceType, property); ODataNavigationLink navLink = this.GetNavigationLink(entityToSerialize, navProperty.Property); // WCF Data Services show performance degradation with JsonLight when entities have > 25 Navgations on writing entries // DEVNOTE: for performance reasons, if the link has no content due to the metadata level, and is not expanded // then don't tell ODataLib about it at all. if (navLink.Url == null && navLink.AssociationLinkUrl == null && !navProperty.Expand) { continue; } var linkArgs = new DataServiceODataWriterNavigationLinkArgs(navLink, this.Service.OperationContext); this.dataServicesODataWriter.WriteStart(linkArgs); if (navProperty.Expand) { object navPropertyValue = navProperty.Value; IExpandedResult navPropertyExpandedResult = navPropertyValue as IExpandedResult; object expandedPropertyValue = navPropertyExpandedResult != null ? GetExpandedElement(navPropertyExpandedResult) : navPropertyValue; bool needPop = this.PushSegmentForProperty(navProperty.Property, entityToSerialize.ResourceType, navProperty.ExpandedNode); // if this.CurrentContainer is null, the target set of the navigation property is hidden. if (this.CurrentContainer != null) { if (navProperty.Property.Kind == ResourcePropertyKind.ResourceSetReference) { IEnumerable enumerable; bool collection = WebUtil.IsElementIEnumerable(expandedPropertyValue, out enumerable); Debug.Assert(collection, "metadata loading must have ensured that navigation set properties must implement IEnumerable"); QueryResultInfo queryResults = new QueryResultInfo(enumerable); try { queryResults.MoveNext(); Func<Uri> getNavPropertyRelativeUri = () => RequestUriProcessor.AppendUnescapedSegment(entityToSerialize.SerializedKey.RelativeEditLink, navLink.Name); Func<Uri> getNavPropertyAbsoluteUri = () => RequestUriProcessor.AppendUnescapedSegment(entityToSerialize.SerializedKey.AbsoluteEditLink, navLink.Name); this.WriteFeedElements(navPropertyExpandedResult, queryResults, navProperty.Property.ResourceType, navLink.Name, getNavPropertyRelativeUri, getNavPropertyAbsoluteUri, false); } finally { // After the navigation property is completely serialized, dispose the property value. WebUtil.Dispose(queryResults); } } else if (WebUtil.IsNullValue(expandedPropertyValue)) { // Write a null reference navigation property var entryArgs = new DataServiceODataWriterEntryArgs(null, null, this.Service.OperationContext); this.dataServicesODataWriter.WriteStart(entryArgs); this.dataServicesODataWriter.WriteEnd(entryArgs); } else { this.WriteEntry(navPropertyExpandedResult, expandedPropertyValue, resourceInstanceInFeed, navProperty.Property.ResourceType); } } this.PopSegmentName(needPop); } this.dataServicesODataWriter.WriteEnd(linkArgs); // end of navigation property } }
/// <summary> /// Creates the navigation link for the given navigation property. /// </summary> /// <param name="entityToSerialize">Entity that is currently being serialized.</param> /// <param name="navigationProperty">The metadata for the navigation property.</param> /// <returns>The navigation link for the given property.</returns> private ODataNavigationLink GetNavigationLink(EntityToSerialize entityToSerialize, ResourceProperty navigationProperty) { Debug.Assert(entityToSerialize != null, "entityToSerialize != null"); Debug.Assert(navigationProperty != null, "navigationProperty != null"); string navLinkName = navigationProperty.Name; ODataNavigationLink navLink = new ODataNavigationLink { Name = navLinkName, IsCollection = navigationProperty.Kind == ResourcePropertyKind.ResourceSetReference }; // Always pass the relative uri to the ODatalib. For Json, they will convert the relative uri // into absolute uri using the BaseUri property on the ODataWriterSettings. For atom, ODataLib // will write the relative uri. this.PayloadMetadataPropertyManager.SetUrl(navLink, () => RequestUriProcessor.AppendUnescapedSegment(entityToSerialize.SerializedKey.RelativeEditLink, navLinkName)); if (this.Service.Configuration.DataServiceBehavior.IncludeAssociationLinksInResponse) { this.PayloadMetadataPropertyManager.SetAssociationLinkUrl(navLink, () => GetAssociationLinkUrl(entityToSerialize, navigationProperty)); } return navLink; }
/// <summary>Write the entry element.</summary> /// <param name="expanded">Expanded result provider for the specified <paramref name="element"/>.</param> /// <param name="element">Element representing the entry element.</param> /// <param name="resourceInstanceInFeed">true if the resource instance being serialized is inside a feed; false otherwise.</param> /// <param name="expectedType">Expected type of the entry element.</param> private void WriteEntry(IExpandedResult expanded, object element, bool resourceInstanceInFeed, ResourceType expectedType) { Debug.Assert(element != null, "element != null"); Debug.Assert(expectedType != null && expectedType.ResourceTypeKind == ResourceTypeKind.EntityType, "expectedType != null && expectedType.ResourceTypeKind == ResourceTypeKind.EntityType"); this.IncrementSegmentResultCount(); ODataEntry entry = new ODataEntry(); if (!resourceInstanceInFeed) { entry.SetSerializationInfo(new ODataFeedAndEntrySerializationInfo { NavigationSourceName = this.CurrentContainer.Name, NavigationSourceEntityTypeName = this.CurrentContainer.ResourceType.FullName, ExpectedTypeName = expectedType.FullName }); } string title = expectedType.Name; #pragma warning disable 618 if (this.contentFormat == ODataFormat.Atom) #pragma warning restore 618 { AtomEntryMetadata entryAtom = new AtomEntryMetadata(); entryAtom.EditLink = new AtomLinkMetadata { Title = title }; entry.SetAnnotation(entryAtom); } ResourceType actualResourceType = WebUtil.GetNonPrimitiveResourceType(this.Provider, element); if (actualResourceType.ResourceTypeKind != ResourceTypeKind.EntityType) { // making sure that the actual resource type is an entity type throw new DataServiceException(500, Microsoft.OData.Service.Strings.BadProvider_InconsistentEntityOrComplexTypeUsage(actualResourceType.FullName)); } EntityToSerialize entityToSerialize = this.WrapEntity(element, actualResourceType); // populate the media resource, if the entity is a MLE. entry.MediaResource = this.GetMediaResource(entityToSerialize, title); // Write the type name this.PayloadMetadataPropertyManager.SetTypeName(entry, this.CurrentContainer.ResourceType.FullName, actualResourceType.FullName); // Write Id element this.PayloadMetadataPropertyManager.SetId(entry, () => entityToSerialize.SerializedKey.Identity); // Write "edit" link this.PayloadMetadataPropertyManager.SetEditLink(entry, () => entityToSerialize.SerializedKey.RelativeEditLink); // Write the etag property, if the type has etag properties this.PayloadMetadataPropertyManager.SetETag(entry, () => this.GetETagValue(element, actualResourceType)); IEnumerable <ProjectionNode> projectionNodes = this.GetProjections(); if (projectionNodes != null) { // Filter the projection nodes for the actual type of the entity // The projection node might refer to the property in a derived type. If the TargetResourceType of // the projection node is not a super type, then we do not want to serialize this property. projectionNodes = projectionNodes.Where(projectionNode => projectionNode.TargetResourceType.IsAssignableFrom(actualResourceType)); // Because we are going to enumerate through these multiple times, create a list. projectionNodes = projectionNodes.ToList(); // And add the annotation to tell ODataLib which properties to write into content (the projections) entry.SetAnnotation(new ProjectedPropertiesAnnotation(projectionNodes.Select(p => p.PropertyName))); } // Populate the advertised actions IEnumerable <ODataAction> actions; if (this.TryGetAdvertisedActions(entityToSerialize, resourceInstanceInFeed, out actions)) { foreach (ODataAction action in actions) { entry.AddAction(action); } } // Populate all the normal properties entry.Properties = this.GetEntityProperties(entityToSerialize, projectionNodes); // And start the entry var args = new DataServiceODataWriterEntryArgs(entry, element, this.Service.OperationContext); this.dataServicesODataWriter.WriteStart(args); // Now write all the navigation properties this.WriteNavigationProperties(expanded, entityToSerialize, resourceInstanceInFeed, projectionNodes); // And write the end of the entry this.dataServicesODataWriter.WriteEnd(args); #if ASTORIA_FF_CALLBACKS this.Service.InternalOnWriteItem(target, element); #endif }
/// <summary> /// Gets the association link URL. /// </summary> /// <param name="entityToSerialize">The entity to serialize.</param> /// <param name="navigationProperty">The navigation property.</param> /// <returns>The association link url.</returns> private static Uri GetAssociationLinkUrl(EntityToSerialize entityToSerialize, ResourceProperty navigationProperty) { Uri associationLinkUri = RequestUriProcessor.AppendUnescapedSegment(entityToSerialize.SerializedKey.RelativeEditLink, navigationProperty.Name); return(RequestUriProcessor.AppendEscapedSegment(associationLinkUri, XmlConstants.UriLinkSegment)); }
/// <summary>Gets ODataProperty for the given <paramref name="property"/>.</summary> /// <param name="entityToSerialize">Entity that is currently being serialized.</param> /// <param name="property">ResourceProperty instance in question.</param> /// <returns>A instance of ODataProperty for the given <paramref name="property"/>.</returns> private ODataProperty GetODataPropertyForEntityProperty(EntityToSerialize entityToSerialize, ResourceProperty property) { Debug.Assert(entityToSerialize != null, "entityToSerialize != null"); Debug.Assert(property != null && entityToSerialize.ResourceType.Properties.Contains(property), "property != null && currentResourceType.Properties.Contains(property)"); ODataValue odataPropertyValue; if (property.IsOfKind(ResourcePropertyKind.Stream)) { odataPropertyValue = this.GetNamedStreamPropertyValue(entityToSerialize, property); } else { object propertyValue = WebUtil.GetPropertyValue(this.Provider, entityToSerialize.Entity, entityToSerialize.ResourceType, property, null); odataPropertyValue = this.GetPropertyValue(property.Name, property.ResourceType, propertyValue, false /*openProperty*/); } ODataProperty odataProperty = new ODataProperty() { Name = property.Name, Value = odataPropertyValue }; ODataPropertyKind kind = property.IsOfKind(ResourcePropertyKind.Key) ? ODataPropertyKind.Key : property.IsOfKind(ResourcePropertyKind.ETag) ? ODataPropertyKind.ETag : ODataPropertyKind.Unspecified; if (kind != ODataPropertyKind.Unspecified) { odataProperty.SetSerializationInfo(new ODataPropertySerializationInfo { PropertyKind = kind }); } return odataProperty; }
/// <summary> /// Tries to build the set of <see cref="ODataAction"/> to be advertised for the given resource. /// </summary> /// <param name="entityToSerialize">Entity that is currently being serialized.</param> /// <param name="resourceInstanceInFeed">true if the resource instance being serialized is inside a feed; false otherwise.</param> /// <param name="actions">The actions to advertise.</param> /// <returns> /// Whether any actions should be advertised. /// </returns> private bool TryGetAdvertisedActions(EntityToSerialize entityToSerialize, bool resourceInstanceInFeed, out IEnumerable<ODataAction> actions) { Debug.Assert(entityToSerialize != null, "entityToSerialize != null"); if (!this.Service.ActionProvider.IsImplemented) { actions = null; return false; } // If GetOperationProjections returns null it means no projections are to be applied and all operations // for the current segment should be serialized. If it returns non-null only the operations described // by the returned projection segments should be serialized. ExpandedProjectionNode expandedProjectionNode = this.GetCurrentExpandedProjectionNode(); List<OperationWrapper> serviceOperationWrapperList; if (expandedProjectionNode == null || expandedProjectionNode.ProjectAllOperations) { // Note that if the service does not implement IDataServiceActionProvider and the MaxProtocolVersion < V3, // GetServiceActionsByBindingParameterType() would simply return an empty ServiceOperationWrapper collection. serviceOperationWrapperList = this.Service.ActionProvider.GetServiceActionsByBindingParameterType(entityToSerialize.ResourceType); } else { serviceOperationWrapperList = expandedProjectionNode.SelectedOperations.GetSelectedOperations(entityToSerialize.ResourceType); } Debug.Assert(serviceOperationWrapperList != null, "serviceOperationWrappers != null"); if (serviceOperationWrapperList.Count <= 0) { actions = null; return false; } actions = this.OperationSerializer.SerializeOperations(entityToSerialize, resourceInstanceInFeed, serviceOperationWrapperList); return true; }
/// <summary>Gets ODataProperty for the given <paramref name="property"/>.</summary> /// <param name="entityToSerialize">Entity that is currently being serialized.</param> /// <param name="property">ResourceProperty instance in question.</param> /// <returns>A instance of ODataProperty for the given <paramref name="property"/>.</returns> private ODataNestedResourceInfoWrapper GetODataNestedResourceForComplexProperty(EntityToSerialize entityToSerialize, ResourceProperty property) { Debug.Assert(entityToSerialize != null, "entityToSerialize != null"); Debug.Assert(property != null && entityToSerialize.ResourceType.Properties.Contains(property), "property != null && currentResourceType.Properties.Contains(property)"); ODataItemWrapper odataPropertyResourceWrapper; object propertyValue = WebUtil.GetPropertyValue(this.Provider, entityToSerialize.Entity, entityToSerialize.ResourceType, property, null); odataPropertyResourceWrapper = this.GetPropertyResourceOrResourceSet(property.Name, property.ResourceType, propertyValue, false /*openProperty*/); ODataNestedResourceInfo odataNestedInfo = new ODataNestedResourceInfo() { Name = property.Name, IsCollection = property.Kind == ResourcePropertyKind.Collection, }; odataNestedInfo.SetSerializationInfo(new ODataNestedResourceInfoSerializationInfo() { IsComplex = true }); return(new ODataNestedResourceInfoWrapper() { NestedResourceInfo = odataNestedInfo, NestedResourceOrResourceSet = odataPropertyResourceWrapper }); }
/// <summary> /// Asks the provider if the action should be advertised in payloads. /// </summary> /// <param name="entityToSerialize">The entity to serialize.</param> /// <param name="resourceInstanceInFeed">Whether or not the entity is being serialized in a feed.</param> /// <param name="serviceOperationWrapper">The service operation wrapper.</param> /// <param name="lazyActionTargetUri">Target uri of the action, which will only be generated if needed.</param> /// <param name="entityHasMultipleActionsWithSameName">Whether or not there are multiple operations in the current scope with the same name as the current operation.</param> /// <param name="odataAction">The ODL object-model representation of the action.</param> /// <returns>Whether or not the action should be advertised.</returns> private bool AskProviderIfActionShouldBeAdvertised(EntityToSerialize entityToSerialize, bool resourceInstanceInFeed, OperationWrapper serviceOperationWrapper, SimpleLazy<Uri> lazyActionTargetUri, bool entityHasMultipleActionsWithSameName, ref ODataAction odataAction) { if (this.advertiseServiceAction(serviceOperationWrapper, entityToSerialize.Entity, resourceInstanceInFeed, ref odataAction)) { if (odataAction == null) { throw new DataServiceException(500, Microsoft.OData.Service.Strings.DataServiceActionProviderWrapper_AdvertiseServiceActionCannotReturnNullActionToSerialize); } // Always set target and title if there are overloaded actions. if (!entityHasMultipleActionsWithSameName) { this.metadataPropertyManager.CheckForUnmodifiedTitle(odataAction, serviceOperationWrapper.Name); this.metadataPropertyManager.CheckForUnmodifiedTarget(odataAction, () => lazyActionTargetUri.Value); } // make the target link relative this.MakeOperationTargetRelativeFromMetadataUriIfJsonLight(odataAction); return true; } odataAction = null; return false; }
/// <summary> /// Tries to serialize the operation. /// </summary> /// <param name="entityToSerialize">The entity to serialize.</param> /// <param name="resourceInstanceInFeed">Whether or not the entity is being serialized in a feed.</param> /// <param name="entityHasMultipleActionsWithSameName">Whether or not there are multiple operations in the current scope with the same name as the current operation.</param> /// <param name="serviceOperationWrapper">The service operation wrapper.</param> /// <param name="odataAction">The ODL object-model representation of the action.</param> /// <returns>Whether or not to serialize the operation.</returns> private bool TrySerializeOperation(EntityToSerialize entityToSerialize, bool resourceInstanceInFeed, bool entityHasMultipleActionsWithSameName, OperationWrapper serviceOperationWrapper, out ODataAction odataAction) { Debug.Assert(serviceOperationWrapper != null, "serviceOperationWrapper != null"); // We only advertise actions. This is a debug assert because GetServiceOperationsByResourceType only returns actions. Debug.Assert(serviceOperationWrapper.Kind == OperationKind.Action, "Only actions can be advertised"); Uri metadata = this.operationLinkBuilder.BuildMetadataLink(serviceOperationWrapper, entityHasMultipleActionsWithSameName); // If the action has OperationParameterBindingKind set to "Always" then we advertise the action without calling "AdvertiseServiceAction". bool isAlwaysAvailable = serviceOperationWrapper.OperationParameterBindingKind == OperationParameterBindingKind.Always; odataAction = new ODataAction { Metadata = metadata }; // There is some subtlety to the interaction between action advertisement and whether or not to include title/target on the wire. // // 1) If an action is always available: // The provider author does not get a chance to customize the title/target values... // so the values will be based on conventions... // so by default do not write them on the wire // 2) If it is only sometimes available: // The values need to be computed to provide them on the instance given to the provider... // but they might not be changed by the provider author... // so compare them to the computed values, and do not write them if they match. // TODO: Action provider should be able to customize title/target even if the action is 'always' advertised // If this gets fixed, then all the behavior should collapse to emulate case #2 above // Create a lazy Uri for the target, because we may need it more than once (see case #2 above). SimpleLazy<Uri> lazyActionTargetUri = new SimpleLazy<Uri>(() => this.operationLinkBuilder.BuildTargetLink(entityToSerialize, serviceOperationWrapper, entityHasMultipleActionsWithSameName)); this.metadataPropertyManager.SetTitle(odataAction, isAlwaysAvailable, serviceOperationWrapper.Name); this.metadataPropertyManager.SetTarget(odataAction, isAlwaysAvailable, () => lazyActionTargetUri.Value); // If the operation is always available, // 1. Return true for MetadataQueryOption.All. // 2. Return false for MetadataQueryOption.None. // 3. Return false for MetadataQueryOption.Default. if (isAlwaysAvailable) { return this.payloadMetadataParameterInterpreter.ShouldIncludeAlwaysAvailableOperation(); } return this.AskProviderIfActionShouldBeAdvertised(entityToSerialize, resourceInstanceInFeed, serviceOperationWrapper, lazyActionTargetUri, entityHasMultipleActionsWithSameName, ref odataAction); }
/// <summary> /// Get the ODataStreamReferenceValue instance containing the metadata for named stream property. /// </summary> /// <param name="entityToSerialize">Entity that is currently being serialized.</param> /// <param name="namedStreamProperty">Named stream property for which the link element needs to be written.</param> /// <returns> /// An instance of ODataStreamReferenceValue containing all the metadata about the named stream property. /// </returns> private ODataStreamReferenceValue GetNamedStreamPropertyValue(EntityToSerialize entityToSerialize, ResourceProperty namedStreamProperty) { Debug.Assert(entityToSerialize != null, "entityToSerialize != null"); Debug.Assert(namedStreamProperty != null, "namedStreamProperty != null"); Debug.Assert(namedStreamProperty.IsOfKind(ResourcePropertyKind.Stream), "namedStreamProperty.IsOfKind(ResourcePropertyKind.Stream)"); string mediaETag; Uri readStreamUri; string mediaContentType; this.Service.StreamProvider.GetStreamDescription(entityToSerialize.Entity, namedStreamProperty, this.Service.OperationContext, out mediaETag, out readStreamUri, out mediaContentType); Debug.Assert(WebUtil.IsETagValueValid(mediaETag, true), "WebUtil.IsETagValueValid(mediaETag, true)"); ODataStreamReferenceValue streamReferenceValue = new ODataStreamReferenceValue(); this.PayloadMetadataPropertyManager.SetContentType(streamReferenceValue, mediaContentType); this.PayloadMetadataPropertyManager.SetEditLink( streamReferenceValue, () => { Uri relativeUri = entityToSerialize.SerializedKey.RelativeEditLink; return RequestUriProcessor.AppendUnescapedSegment(relativeUri, namedStreamProperty.Name); }); if (!string.IsNullOrEmpty(mediaETag)) { this.PayloadMetadataPropertyManager.SetETag(streamReferenceValue, mediaETag); } // Note if readStreamUri is null, the self link for the named stream will be omitted. this.PayloadMetadataPropertyManager.SetReadLink(streamReferenceValue, () => readStreamUri); return streamReferenceValue; }
/// <summary> /// Gets the association link URL. /// </summary> /// <param name="entityToSerialize">The entity to serialize.</param> /// <param name="navigationProperty">The navigation property.</param> /// <returns>The association link url.</returns> private static Uri GetAssociationLinkUrl(EntityToSerialize entityToSerialize, ResourceProperty navigationProperty) { Uri associationLinkUri = RequestUriProcessor.AppendUnescapedSegment(entityToSerialize.SerializedKey.RelativeEditLink, navigationProperty.Name); return RequestUriProcessor.AppendEscapedSegment(associationLinkUri, XmlConstants.UriLinkSegment); }
/// <summary> /// Get the stream reference value for media resource (the default stream of an entity). /// </summary> /// <param name="entityToSerialize">Entity that is currently being serialized.</param> /// <param name="title">The title for the element being written.</param> /// <returns> /// An instance of ODataStreamReferenceValue containing the metadata about the media resource. /// </returns> private ODataStreamReferenceValue GetMediaResource(EntityToSerialize entityToSerialize, string title) { Debug.Assert(entityToSerialize.Entity != null, "element != null"); Debug.Assert(entityToSerialize.ResourceType != null && entityToSerialize.ResourceType.ResourceTypeKind == ResourceTypeKind.EntityType, "type != null && type.ResourceTypeKind == ResourceTypeKind.EntityType"); Debug.Assert(!string.IsNullOrEmpty(title), "!string.IsNullOrEmpty(title)"); ODataStreamReferenceValue mediaResource = null; // Handle MLE if (entityToSerialize.ResourceType.IsMediaLinkEntry) { string mediaETag; Uri readStreamUri; string mediaContentType; Debug.Assert(entityToSerialize.ResourceType.ResourceTypeKind == ResourceTypeKind.EntityType, "type.ResourceTypeKind == ResourceTypeKind.EntityType"); this.Service.StreamProvider.GetStreamDescription(entityToSerialize.Entity, null /*null for MLE*/, this.Service.OperationContext, out mediaETag, out readStreamUri, out mediaContentType); Debug.Assert(WebUtil.IsETagValueValid(mediaETag, true), "WebUtil.IsETagValueValid(mediaETag, true)"); Debug.Assert(!string.IsNullOrEmpty(mediaContentType), "!string.IsNullOrEmpty(mediaContentType)"); mediaResource = new ODataStreamReferenceValue(); // build the stream's edit link lazily to avoid creating the entity's edit link if it is not needed. SimpleLazy<Uri> lazyStreamEditLink = new SimpleLazy<Uri>(() => RequestUriProcessor.AppendEscapedSegment(entityToSerialize.SerializedKey.RelativeEditLink, XmlConstants.UriValueSegment)); this.PayloadMetadataPropertyManager.SetEditLink(mediaResource, () => lazyStreamEditLink.Value); this.PayloadMetadataPropertyManager.SetContentType(mediaResource, mediaContentType); // If the stream provider did not provider a read link, then we should use the edit link as the read link. this.PayloadMetadataPropertyManager.SetReadLink(mediaResource, () => readStreamUri ?? lazyStreamEditLink.Value); #pragma warning disable 618 if (this.contentFormat == ODataFormat.Atom) #pragma warning restore 618 { AtomStreamReferenceMetadata mediaResourceAtom = new AtomStreamReferenceMetadata() { EditLink = new AtomLinkMetadata { Title = title } }; mediaResource.SetAnnotation(mediaResourceAtom); } if (!string.IsNullOrEmpty(mediaETag)) { this.PayloadMetadataPropertyManager.SetETag(mediaResource, mediaETag); } } return mediaResource; }
/// <summary> /// Wraps the entity in a structure which tracks its type and other information about it. /// </summary> /// <param name="entity">The entity to wrap.</param> /// <param name="resourceType">The type of the entity.</param> /// <returns>A structure containing the entity and some other information about it.</returns> private EntityToSerialize WrapEntity(object entity, ResourceType resourceType) { return(EntityToSerialize.Create(entity, resourceType, this.CurrentContainer, this.Provider, this.AbsoluteServiceUri)); }
/// <summary> /// Serializes the given operations and returns the resulting instances of <see cref="ODataAction"/>. /// </summary> /// <param name="entityToSerialize">The entity to serialize.</param> /// <param name="resourceInstanceInFeed">Whether or not the entity is being serialized in a feed.</param> /// <param name="operationWrappers">The wrapped operations to serialize.</param> /// <returns>The serialized actions.</returns> internal IEnumerable<ODataAction> SerializeOperations(EntityToSerialize entityToSerialize, bool resourceInstanceInFeed, ICollection<OperationWrapper> operationWrappers) { Debug.Assert(operationWrappers != null, "serviceOperationWrapperList != null"); var odataActions = new List<ODataAction>(operationWrappers.Count); // Create a hashset containing the names of all actions in the current scope that are duplicates. HashSet<string> collidingActionNames = GetCollidingActionNames(operationWrappers); foreach (OperationWrapper wrapper in operationWrappers) { bool actionNameHasCollision = collidingActionNames.Contains(wrapper.Name); ODataAction odataAction; if (this.TrySerializeOperation(entityToSerialize, resourceInstanceInFeed, actionNameHasCollision, wrapper, out odataAction)) { odataActions.Add(odataAction); } } return odataActions; }