/// <summary>Writes all the properties of the specified resource or complex object.</summary> /// <param name="expanded">Expanded properties for the result.</param> /// <param name="customObject">Resource or complex object with properties to write out.</param> /// <param name="resourceType">resourceType containing metadata about the current custom object</param> /// <param name="absoluteUri">absolute uri for the given resource</param> /// <param name="relativeUri">relative uri for the given resource</param> /// <param name="item">Item in which to place links / expansions.</param> /// <param name="content">Content in which to place values.</param> /// <param name="currentSourceRoot">Epm source sub-tree corresponding to <paramref name="customObject"/></param> private void WriteObjectProperties(IExpandedResult expanded, object customObject, ResourceType resourceType, Uri absoluteUri, string relativeUri, SyndicationItem item, DictionaryContent content, EpmSourcePathSegment currentSourceRoot) { Debug.Assert(customObject != null, "customObject != null"); Debug.Assert(resourceType != null, "resourceType != null"); Debug.Assert(!String.IsNullOrEmpty(relativeUri), "!String.IsNullOrEmpty(relativeUri)"); if (absoluteUri == null && resourceType.ResourceTypeKind == ResourceTypeKind.EntityType) { // entity type should have an URI, complex type should not have an URI // If the static type of the object is "Object", we will mistreat an entity type as complex type and hit this situation throw new DataServiceException(500, Strings.BadProvider_InconsistentEntityOrComplexTypeUsage(resourceType.Name)); } this.RecurseEnter(); try { List<ResourcePropertyInfo> navProperties = null; IEnumerable<ProjectionNode> projectionNodes = null; if (resourceType.ResourceTypeKind == ResourceTypeKind.EntityType) { Debug.Assert(this.CurrentContainer != null, "this.CurrentContainer != null"); if (this.Provider.IsEntityTypeDisallowedForSet(this.CurrentContainer, resourceType)) { throw new InvalidOperationException(Strings.BaseServiceProvider_NavigationPropertiesOnDerivedEntityTypesNotSupported(resourceType.FullName, this.CurrentContainer.Name)); } navProperties = new List<ResourcePropertyInfo>(resourceType.Properties.Count); projectionNodes = this.GetProjections(); } if (projectionNodes == null) { var action = resourceType.DictionarySerializerDelegate; if (action == null && this.Provider.IsV1Provider) { Module module = typeof(SyndicationSerializer).Module; Type customObjectType = customObject.GetType(); Type[] parameterTypes = new Type[] { typeof(object), typeof(DictionaryContent) }; DynamicMethod method = new DynamicMethod("content_populator", typeof(void), parameterTypes, module, false /* skipVisibility */); ILGenerator generator = method.GetILGenerator(); MethodInfo methodWritePrimitiveValue = typeof(SyndicationSerializer).GetMethod("WritePrimitiveValue", BindingFlags.Static | BindingFlags.NonPublic); // Downcast the argument. generator.Emit(OpCodes.Ldarg_0); generator.Emit(OpCodes.Castclass, customObjectType); foreach (ResourceProperty property in resourceType.Properties.Where(p => p.TypeKind == ResourceTypeKind.Primitive)) { if (SyndicationSerializer.EpmNeedToSkip(currentSourceRoot, property.Name)) { continue; } // WritePrimitiveValue(propertyValue, property.Name, property.ResourceType, content); generator.Emit(OpCodes.Dup); generator.Emit(OpCodes.Call, resourceType.GetPropertyInfo(property).GetGetMethod()); if (property.Type.IsValueType) { generator.Emit(OpCodes.Box, property.Type); } generator.Emit(OpCodes.Ldstr, property.Name); generator.Emit(OpCodes.Ldstr, property.ResourceType.FullName); generator.Emit(OpCodes.Ldarg_1); generator.Emit(OpCodes.Call, methodWritePrimitiveValue); } generator.Emit(OpCodes.Pop); generator.Emit(OpCodes.Ret); action = (Action<object, DictionaryContent>)method.CreateDelegate(typeof(Action<object, DictionaryContent>), null); resourceType.DictionarySerializerDelegate = action; } if (action != null) { action(customObject, content); } else { foreach (ResourceProperty property in resourceType.Properties.Where(p => p.TypeKind == ResourceTypeKind.Primitive)) { object propertyValue = WebUtil.GetPropertyValue(this.Provider, customObject, resourceType, property, null); if (SyndicationSerializer.EpmNeedToSkip(currentSourceRoot, property.Name)) { continue; } WritePrimitiveValue(propertyValue, property.Name, property.ResourceType.FullName, content); } } foreach (ResourceProperty property in this.Provider.GetResourceProperties(this.CurrentContainer, resourceType)) { string propertyName = property.Name; if (property.TypeKind == ResourceTypeKind.EntityType) { Debug.Assert(navProperties != null, "navProperties list must be assigned for entity types"); object propertyValue = (this.ShouldExpandSegment(property.Name)) ? GetExpandedProperty(this.Provider, expanded, customObject, property) : null; navProperties.Add(new ResourcePropertyInfo(property, propertyValue)); } else { if (property.TypeKind == ResourceTypeKind.ComplexType) { object propertyValue = WebUtil.GetPropertyValue(this.Provider, customObject, resourceType, property, null); bool needPop = this.PushSegmentForProperty(property); this.WriteComplexObjectValue( propertyValue, propertyName, property.ResourceType, relativeUri + "/" + property.Name, content, SyndicationSerializer.EpmGetComplexPropertySegment(currentSourceRoot, property.Name)); this.PopSegmentName(needPop); } } } if (resourceType.IsOpenType) { IEnumerable<KeyValuePair<string, object>> properties = this.Provider.GetOpenPropertyValues(customObject); foreach (KeyValuePair<string, object> property in properties) { string propertyName = property.Key; if (String.IsNullOrEmpty(propertyName)) { throw new DataServiceException(500, Strings.Syndication_InvalidOpenPropertyName(resourceType.FullName)); } Type valueType; ResourceType propertyResourceType; object value = property.Value; if (value == null || value == DBNull.Value) { valueType = typeof(string); propertyResourceType = ResourceType.PrimitiveStringResourceType; } else { valueType = value.GetType(); propertyResourceType = WebUtil.GetResourceType(this.Provider, value); } // A null ResourceType indicates a ----ed type (eg, IntPtr or DateTimeOffset). So ignore it. if (propertyResourceType == null) { throw new DataServiceException(500, Strings.Syndication_InvalidOpenPropertyType(propertyName)); } if (propertyResourceType.ResourceTypeKind == ResourceTypeKind.Primitive) { if (value != null && SyndicationSerializer.EpmNeedToSkip(currentSourceRoot, propertyName)) { continue; } WritePrimitiveValue(value, propertyName, propertyResourceType.FullName, content); } else { if (propertyResourceType.ResourceTypeKind == ResourceTypeKind.ComplexType) { Debug.Assert(propertyResourceType.InstanceType == valueType, "propertyResourceType.Type == valueType"); this.WriteComplexObjectValue( value, propertyName, propertyResourceType, relativeUri + "/" + propertyName, content, SyndicationSerializer.EpmGetComplexPropertySegment(currentSourceRoot, propertyName)); } else { Debug.Assert( propertyResourceType.ResourceTypeKind == ResourceTypeKind.EntityType, "propertyResourceType.ResourceTypeKind == ResourceTypeKind.EntityType -- otherwise should have been processed as primitve or complex type."); // Open navigation properties are not supported on OpenTypes throw DataServiceException.CreateBadRequestError(Strings.OpenNavigationPropertiesNotSupportedOnOpenTypes(propertyName)); } } } } } else { foreach (ProjectionNode projectionNode in projectionNodes) { string propertyName = projectionNode.PropertyName; ResourceProperty property = resourceType.TryResolvePropertyName(propertyName); // First solve the normal entity type property - turn it into a nav. property record if (property != null && property.TypeKind == ResourceTypeKind.EntityType) { Debug.Assert(navProperties != null, "navProperties list must be assigned for entity types"); // By calling the GetResourceProperties we will use the cached list of properties // for the given type and set. But we have to search through it. // We could use the GetContainer (since that's what the GetResourceProperties does) and check // if it returns null, but result of that is only partially cached so it might be expensive // to evaluate for each item in the feed. if (this.Provider.GetResourceProperties(this.CurrentContainer, resourceType).Contains(property)) { object expandedPropertyValue = (this.ShouldExpandSegment(propertyName)) ? GetExpandedProperty(this.Provider, expanded, customObject, property) : null; navProperties.Add(new ResourcePropertyInfo(property, expandedPropertyValue)); } continue; } // Now get the property value object propertyValue = WebUtil.GetPropertyValue(this.Provider, customObject, resourceType, property, property == null ? propertyName : null); // Determine the type of the property ResourceType propertyResourceType; if (property != null) { propertyResourceType = property.ResourceType; } else { if (propertyValue == null || propertyValue == DBNull.Value) { propertyResourceType = ResourceType.PrimitiveStringResourceType; } else { propertyResourceType = WebUtil.GetResourceType(this.Provider, propertyValue); // A null ResourceType indicates a ----ed type (eg, IntPtr or DateTimeOffset). So ignore it. if (propertyResourceType == null) { throw new DataServiceException(500, Strings.Syndication_InvalidOpenPropertyType(propertyName)); } } } // And write out the value (depending on the type of the property) if (propertyResourceType.ResourceTypeKind == ResourceTypeKind.Primitive) { if (propertyValue == DBNull.Value) { propertyValue = null; } if (propertyValue != null && SyndicationSerializer.EpmNeedToSkip(currentSourceRoot, propertyName)) { continue; } WritePrimitiveValue(propertyValue, propertyName, propertyResourceType.FullName, content); } else if (propertyResourceType.ResourceTypeKind == ResourceTypeKind.ComplexType) { bool needPop = false; if (property != null) { needPop = this.PushSegmentForProperty(property); } this.WriteComplexObjectValue( propertyValue, propertyName, propertyResourceType, relativeUri + "/" + propertyName, content, SyndicationSerializer.EpmGetComplexPropertySegment(currentSourceRoot, propertyName)); if (property != null) { this.PopSegmentName(needPop); } } else { Debug.Assert( propertyResourceType.ResourceTypeKind == ResourceTypeKind.EntityType, "propertyResourceType.ResourceTypeKind == ResourceTypeKind.EntityType -- otherwise should have been processed as primitve or complex type."); // Open navigation properties are not supported on OpenTypes throw DataServiceException.CreateBadRequestError(Strings.OpenNavigationPropertiesNotSupportedOnOpenTypes(propertyName)); } } } if (resourceType.ResourceTypeKind == ResourceTypeKind.EntityType) { for (int i = 0; i < navProperties.Count; i++) { ResourcePropertyInfo propertyInfo = navProperties[i]; ResourceProperty navProperty = propertyInfo.Property; Debug.Assert( navProperty.IsOfKind(ResourcePropertyKind.ResourceReference) || navProperty.IsOfKind(ResourcePropertyKind.ResourceSetReference), "this must be nav property"); // Generate a link - see http://tools.ietf.org/html/rfc4287#section-4.2.7 string linkType = navProperty.IsOfKind(ResourcePropertyKind.ResourceReference) ? XmlConstants.AtomEntryElementName : XmlConstants.AtomFeedElementName; linkType = String.Format(CultureInfo.InvariantCulture, "{0};{1}={2}", XmlConstants.MimeApplicationAtom, XmlConstants.AtomTypeAttributeName, linkType); string segmentIdentifier = navProperty.Name; if (!this.ShouldExpandSegment(navProperty.Name)) { WriteDeferredContentElement( XmlConstants.DataWebRelatedNamespace + navProperty.Name, navProperty.Name, relativeUri + "/" + segmentIdentifier, linkType, item); } else { object propertyValue = propertyInfo.Value; IExpandedResult expandedResultPropertyValue = propertyValue as IExpandedResult; object expandedPropertyValue = expandedResultPropertyValue != null ? GetExpandedElement(expandedResultPropertyValue) : propertyValue; string propertyRelativeUri = relativeUri + "/" + segmentIdentifier; Uri propertyAbsoluteUri = RequestUriProcessor.AppendUnescapedSegment(absoluteUri, segmentIdentifier); SyndicationLink link = new SyndicationLink(); link.RelationshipType = XmlConstants.DataWebRelatedNamespace + navProperty.Name; link.Title = navProperty.Name; link.Uri = new Uri(propertyRelativeUri, UriKind.RelativeOrAbsolute); link.MediaType = linkType; item.Links.Add(link); bool needPop = this.PushSegmentForProperty(navProperty); // if this.CurrentContainer is null, the target set of the navigation property is hidden. if (this.CurrentContainer != null) { if (navProperty.IsOfKind(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"); SyndicationFeed feed = new SyndicationFeed(); InlineAtomFeed inlineFeedExtension = new InlineAtomFeed(feed, this.factory); link.ElementExtensions.Add(inlineFeedExtension); IEnumerator enumerator = enumerable.GetEnumerator(); try { bool hasMoved = enumerator.MoveNext(); this.WriteFeedElements( propertyValue as IExpandedResult, enumerator, navProperty.ResourceType, navProperty.Name, propertyAbsoluteUri, propertyRelativeUri, hasMoved, feed, true); } catch { WebUtil.Dispose(enumerator); throw; } } else { SyndicationItem inlineItem = new SyndicationItem(); this.WriteEntryElement(propertyValue as IExpandedResult, expandedPropertyValue, navProperty.ResourceType, propertyAbsoluteUri, propertyRelativeUri, inlineItem); InlineAtomItem inlineItemExtension = new InlineAtomItem(inlineItem, this.factory); link.ElementExtensions.Add(inlineItemExtension); } } this.PopSegmentName(needPop); } } } } 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> /// Creates an expression to access a property. /// </summary> /// <param name="source">The source expression which evaluates to the instance to access the property on.</param> /// <param name="sourceResourceType">The resource type of the source expression.</param> /// <param name="resourceProperty">The resource property to access.</param> /// <returns>An expression which evaluates to the property value.</returns> private static Expression CreatePropertyAccessExpression(Expression source, ResourceType sourceResourceType, ResourceProperty resourceProperty) { Debug.Assert(source != null, "source != null"); Debug.Assert(sourceResourceType != null, "sourceResourceType != null"); Debug.Assert(resourceProperty != null, "resourceProperty != null"); Debug.Assert(sourceResourceType.Properties.Contains(resourceProperty), "resourceProperty is not declared on sourceResourceType"); #if DEBUG Debug.Assert(TypeUtils.AreTypesEquivalent(source.Type, sourceResourceType.InstanceType), "source.Type != sourceResourceType.InstanceType"); #endif // TODO: Deal with null propagation??? if (resourceProperty.CanReflectOnInstanceTypeProperty) { return Expression.Property(source, sourceResourceType.GetPropertyInfo(resourceProperty)); } else { // TODO: Support for untyped and open properties throw new NotImplementedException(); } }