Code:
/ Dotnetfx_Win7_3.5.1 / Dotnetfx_Win7_3.5.1 / 3.5.1 / DEVDIV / depot / DevDiv / releases / Orcas / NetFXw7 / ndp / fx / src / DataWeb / Server / System / Data / Services / Providers / ReflectionServiceProvider.cs / 1 / ReflectionServiceProvider.cs
//----------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
//
// Provides the interface definition for web data service
// data sources.
//
//
// @owner [....]
//---------------------------------------------------------------------
namespace System.Data.Services.Providers
{
#region Namespaces.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data.Services.Caching;
using System.Data.Services.Common;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Xml;
#endregion Namespaces.
///
/// Provides a reflection-based IDataServiceProvider implementation.
///
[DebuggerDisplay("ReflectionServiceProvider: {Type}")]
internal class ReflectionServiceProvider : BaseServiceProvider
{
///
/// Initializes a new System.Data.Services.ReflectionServiceProvider instance.
///
/// Metadata for this provider.
/// instance of the data source provider.
internal ReflectionServiceProvider(MetadataCacheItem metadata, object dataSourceInstance)
: base(metadata, dataSourceInstance)
{
}
/// Gets a value indicating whether null propagation is required in expression trees.
public override bool NullPropagationRequired
{
get { return true; }
}
///
/// Provides a name for the context in which all resource containers are.
///
public override string ResourceContextName
{
get
{
return XmlConvert.EncodeName(this.Type.Name);
}
}
/// Gets the name of the container that holds this resource type.This method is called for open types only
/// Resource to get container for.
///
/// The name of the container for the specified resource; null if it cannot
/// be determined.
///
public override ResourceContainer GetContainerForResourceType(Type resourceType)
{
Debug.Assert(resourceType != null, "resourceType != null");
return InternalGetContainerForResourceType(resourceType, this.EntitySets.Values);
}
///
/// Gets the container corresponding to the given navigation property
/// The declaring type container name refers to the entity set that the declaring type belongs to
///
/// name of the entity container that the declaring type belongs to
/// declaring type
/// resource navigation property
/// name of the container that this property refers to
public override ResourceContainer GetContainer(string declaringTypeContainerName, Type declaringResourceType, ResourceProperty resourceProperty)
{
Debug.Assert(declaringTypeContainerName != null, "declaringTypeContainerName != null");
Debug.Assert(declaringResourceType != null, "declaringResourceType != null");
Debug.Assert(resourceProperty != null, "resourceProperty != null");
return InternalGetContainerForResourceType(resourceProperty.ResourceClrType, this.EntitySets.Values);
}
/// Gets the metadata document for this provider.
/// Writer to which metadata XML should be written.
public override void GetMetadata(XmlWriter xmlWriter)
{
Debug.Assert(xmlWriter != null, "xmlWriter != null");
TypeManager typeManager = new TypeManager(this.Types);
BaseServiceProvider.WriteTopLevelSchemaElements(xmlWriter);
Dictionary associations = new Dictionary(StringComparer.Ordinal);
List typesInEntityContainerNamespace = null;
// Write the schema Element for every namespace
foreach (KeyValuePair> namespaceAlongWithTypes in typeManager.NamespaceAlongWithTypes)
{
// If the types live in the same namespace as that of entity container and types that don't live in any namespace,
// should be written out in the service namespace. If the service type also doesn't have a namespace, we will use
// the service name as the namespace. See code below for that.
if (namespaceAlongWithTypes.Key == this.Type.Namespace || String.IsNullOrEmpty(namespaceAlongWithTypes.Key))
{
// We can't write the entity container until we have figured out all the associations.
// Hence storing the type information for types which live in the same namespace as that
// of entity container and writting them at the end along with the entity container.
if (typesInEntityContainerNamespace == null)
{
typesInEntityContainerNamespace = namespaceAlongWithTypes.Value;
}
else
{
typesInEntityContainerNamespace.AddRange(namespaceAlongWithTypes.Value);
}
}
else
{
var associationsInThisNamespace = new Dictionary(StringComparer.Ordinal);
BaseServiceProvider.WriteSchemaElement(xmlWriter, namespaceAlongWithTypes.Key, this.CompatibleWithV1Schema);
WriteTypes(xmlWriter, namespaceAlongWithTypes.Value, associations, associationsInThisNamespace);
WriteAssociations(xmlWriter, associationsInThisNamespace.Values);
xmlWriter.WriteEndElement();
}
}
// Write the entity container definition. Also if there are types in the same namespace,
// we need to write them out too.
string typeNamespace = this.Type.Namespace;
if (String.IsNullOrEmpty(typeNamespace))
{
typeNamespace = XmlConvert.EncodeName(this.Type.Name);
}
BaseServiceProvider.WriteSchemaElement(xmlWriter, typeNamespace, this.CompatibleWithV1Schema);
if (typesInEntityContainerNamespace != null)
{
var associationsInThisNamespace = new Dictionary(StringComparer.Ordinal);
WriteTypes(xmlWriter, typesInEntityContainerNamespace, associations, associationsInThisNamespace);
WriteAssociations(xmlWriter, associationsInThisNamespace.Values);
}
this.WriteEntityContainer(xmlWriter, this.ResourceContextName, this.EntitySets, associations);
xmlWriter.WriteEndElement();
// These end elements balance the elements written out in WriteTopLevelSchemaElements
xmlWriter.WriteEndElement();
xmlWriter.WriteEndElement();
xmlWriter.Flush();
}
///
/// Get the list of etag property names given the entity set name and the instance of the resource
///
/// name of the entity set
/// clr type of the resource whose etag properties need to be fetched
/// list of etag property names
public override ICollection GetETagProperties(string containerName, Type resourceClrType)
{
Debug.Assert(resourceClrType != null, "clrType cannot be null");
ResourceType resourceType = ((IDataServiceProvider)this).GetResourceType(resourceClrType);
Debug.Assert(resourceType != null, "resourceType != null");
return resourceType.ETagProperties;
}
///
/// Checks whether the specified is ordered.
///
/// Type to check.
/// true if the type may be ordered; false otherwise.
///
/// The ordering may still fail at runtime; this method is currently
/// used for cleaner error messages only.
///
public override bool GetTypeIsOrdered(Type type)
{
Debug.Assert(type != null, "type != null");
if (typeof(IComparable).IsAssignableFrom(type))
{
return true;
}
else
{
return base.GetTypeIsOrdered(type);
}
}
#region IUpdatable Members
///
/// Creates the resource of the given type and belonging to the given container
///
/// container name to which the resource needs to be added
/// full type name i.e. Namespace qualified type name of the resource
/// object representing a resource of given type and belonging to the given container
public override object CreateResource(string containerName, string fullTypeName)
{
if (typeof(IUpdatable).IsAssignableFrom(this.Type))
{
object resource = ((IUpdatable)this.CurrentDataSource).CreateResource(containerName, fullTypeName);
if (resource == null)
{
throw new InvalidOperationException(Strings.BadProvider_CreateResourceReturnedNull);
}
return resource;
}
else
{
throw DataServiceException.CreateMethodNotImplemented(Strings.DataSourceMustImplementIUpdatableToSupportUpdates);
}
}
///
/// Gets the resource of the given type that the query points to
///
/// query pointing to a particular resource
/// full type name i.e. Namespace qualified type name of the resource
/// object representing a resource of given type and as referenced by the query
public override object GetResource(IQueryable query, string fullTypeName)
{
object resource = null;
if (typeof(IUpdatable).IsAssignableFrom(this.Type))
{
try
{
resource = ((IUpdatable)this.CurrentDataSource).GetResource(query, fullTypeName);
}
catch (ArgumentException e)
{
throw DataServiceException.CreateBadRequestError(Strings.BadRequest_InvalidUriSpecified, e);
}
}
else
{
throw DataServiceException.CreateMethodNotImplemented(Strings.DataSourceMustImplementIUpdatableToSupportUpdates);
}
return resource;
}
///
/// Resets the value of the given resource to its default value
///
/// resource whose value needs to be reset
/// same resource with its value reset
public override object ResetResource(object resource)
{
if (typeof(IUpdatable).IsAssignableFrom(this.Type))
{
resource = ((IUpdatable)this.CurrentDataSource).ResetResource(resource);
}
else
{
throw DataServiceException.CreateMethodNotImplemented(Strings.DataSourceMustImplementIUpdatableToSupportUpdates);
}
return resource;
}
///
/// Sets the value of the given property on the target object
///
/// target object which defines the property
/// name of the property whose value needs to be updated
/// value of the property
public override void SetValue(object targetResource, string propertyName, object propertyValue)
{
if (typeof(IUpdatable).IsAssignableFrom(this.Type))
{
((IUpdatable)this.CurrentDataSource).SetValue(targetResource, propertyName, propertyValue);
}
else
{
throw DataServiceException.CreateMethodNotImplemented(Strings.DataSourceMustImplementIUpdatableToSupportUpdates);
}
}
///
/// Gets the value of the given property on the target object
///
/// target object which defines the property
/// name of the property whose value needs to be updated
/// the value of the property for the given target resource
public override object GetValue(object targetResource, string propertyName)
{
object resource = null;
if (typeof(IUpdatable).IsAssignableFrom(this.Type))
{
resource = ((IUpdatable)this.CurrentDataSource).GetValue(targetResource, propertyName);
}
else
{
throw DataServiceException.CreateMethodNotImplemented(Strings.DataSourceMustImplementIUpdatableToSupportUpdates);
}
if (resource == null)
{
throw DataServiceException.CreateResourceNotFound(propertyName);
}
return resource;
}
///
/// Sets the value of the given reference property on the target object
///
/// target object which defines the property
/// name of the property whose value needs to be updated
/// value of the property
public override void SetReference(object targetResource, string propertyName, object propertyValue)
{
if (typeof(IUpdatable).IsAssignableFrom(this.Type))
{
((IUpdatable)this.CurrentDataSource).SetReference(targetResource, propertyName, propertyValue);
}
else
{
throw DataServiceException.CreateMethodNotImplemented(Strings.DataSourceMustImplementIUpdatableToSupportUpdates);
}
}
///
/// Adds the given value to the collection
///
/// target object which defines the property
/// name of the property whose value needs to be updated
/// value of the property which needs to be added
public override void AddReferenceToCollection(object targetResource, string propertyName, object resourceToBeAdded)
{
if (typeof(IUpdatable).IsAssignableFrom(this.Type))
{
((IUpdatable)this.CurrentDataSource).AddReferenceToCollection(targetResource, propertyName, resourceToBeAdded);
}
else
{
throw DataServiceException.CreateMethodNotImplemented(Strings.DataSourceMustImplementIUpdatableToSupportUpdates);
}
}
/// Applies expansions to the specified .
/// object to expand.
/// A collection of ordered paths.
///
/// An object of the same type as the given ,
/// with the results including the specified .
///
///
/// This method may modify the to indicate which expansions
/// are included.
///
/// The returned may implement the
/// interface to provide enumerable objects for the expansions; otherwise, the expanded
/// information is expected to be found directly in the enumerated objects.
///
public override IEnumerable ApplyExpansions(IQueryable queryable, ICollection expandPaths)
{
IExpandProvider provider = this.CurrentDataSource as IExpandProvider;
if (provider != null)
{
return provider.ApplyExpansions(queryable, expandPaths);
}
else
{
foreach (ExpandSegmentCollection path in expandPaths)
{
if (path.HasFilter)
{
return new BasicExpandProvider(this, true).ApplyExpansions(queryable, expandPaths);
}
}
// This pass-through implementation is appropriate for providers that fault-in on demand.
return queryable;
}
}
///
/// Removes the given value from the collection
///
/// target object which defines the property
/// name of the property whose value needs to be updated
/// value of the property which needs to be removed
public override void RemoveReferenceFromCollection(object targetResource, string propertyName, object resourceToBeRemoved)
{
if (typeof(IUpdatable).IsAssignableFrom(this.Type))
{
((IUpdatable)this.CurrentDataSource).RemoveReferenceFromCollection(targetResource, propertyName, resourceToBeRemoved);
}
else
{
throw DataServiceException.CreateMethodNotImplemented(Strings.DataSourceMustImplementIUpdatableToSupportUpdates);
}
}
///
/// Delete the given resource
///
/// resource that needs to be deleted
public override void DeleteResource(object targetResource)
{
if (typeof(IUpdatable).IsAssignableFrom(this.Type))
{
((IUpdatable)this.CurrentDataSource).DeleteResource(targetResource);
}
else
{
throw DataServiceException.CreateMethodNotImplemented(Strings.DataSourceMustImplementIUpdatableToSupportUpdates);
}
}
///
/// Saves all the pending changes made till now
///
public override void SaveChanges()
{
if (typeof(IUpdatable).IsAssignableFrom(this.Type))
{
((IUpdatable)this.CurrentDataSource).SaveChanges();
}
else
{
throw DataServiceException.CreateMethodNotImplemented(Strings.DataSourceMustImplementIUpdatableToSupportUpdates);
}
}
///
/// Returns the actual instance of the resource represented by the given resource object
///
/// object representing the resource whose instance needs to be fetched
/// The actual instance of the resource represented by the given resource object
public override object ResolveResource(object resource)
{
object resolvedResource = null;
if (typeof(IUpdatable).IsAssignableFrom(this.Type))
{
resolvedResource = ((IUpdatable)this.CurrentDataSource).ResolveResource(resource);
}
else
{
throw DataServiceException.CreateMethodNotImplemented(Strings.DataSourceMustImplementIUpdatableToSupportUpdates);
}
if (resolvedResource == null)
{
throw new InvalidOperationException(Strings.BadProvider_ResolveResourceReturnedNull);
}
return resolvedResource;
}
///
/// Revert all the pending changes.
///
public override void ClearChanges()
{
if (typeof(IUpdatable).IsAssignableFrom(this.Type))
{
((IUpdatable)this.CurrentDataSource).ClearChanges();
}
else
{
throw DataServiceException.CreateMethodNotImplemented(Strings.DataSourceMustImplementIUpdatableToSupportUpdates);
}
}
#endregion
/// Checks whether the given property is a key property.
/// property to check
/// returns the key kind of the property, based on the heuristic it matches
/// true if this is a key property, else returns false
internal static bool IsPropertyKeyProperty(PropertyInfo property, out ResourceKeyKind keyKind)
{
keyKind = (ResourceKeyKind)(-1);
// Only primitive types are allowed to be keys.
// Checks for generic to exclude Nullable<> value-type primitives, since we don't allows keys to be null.
if (WebUtil.IsPrimitiveType(property.PropertyType) &&
!property.PropertyType.IsGenericType)
{
DataServiceKeyAttribute keyAttribute = property.ReflectedType.GetCustomAttributes(true).OfType().FirstOrDefault();
if (keyAttribute != null && keyAttribute.KeyNames.Contains(property.Name))
{
keyKind = ResourceKeyKind.AttributedKey;
return true;
}
// For now, the key property must be {TypeName}Id or Id and the property
// type must be primitive, since we do not support non-primitive types
// as keys
if (property.Name == property.DeclaringType.Name + "ID")
{
keyKind = ResourceKeyKind.TypeNameId;
return true;
}
else if (property.Name == "ID")
{
keyKind = ResourceKeyKind.Id;
return true;
}
}
return false;
}
#if ASTORIA_CONTAINMENT
///
/// Returns an object that can enumerate all
/// instances that apply to this model.
///
///
/// An object that can enumerate all
/// instances that apply to this model.
///
protected override IEnumerable EnumerateAccessPathAttributes()
{
Type provider = this.Type;
foreach (ResourceContainer container in this.EntitySets.Values)
{
PropertyInfo property = provider.GetProperty(container.Name, WebUtil.PublicInstanceBindingFlags);
Debug.Assert(property != null, "property != null -- otherwise " + container.Name + " shouldn't be in metadata.");
object[] attributes = property.GetCustomAttributes(true);
if (attributes == null || attributes.Length == 0)
{
continue;
}
foreach (AccessPathAttribute attribute in attributes.OfType())
{
attribute.AnnotatedContainer = container;
yield return attribute;
}
}
}
#endif
/// Populates the metadata for this provider.
///
/// Dictionary of known CLR to ResourceType entries, which is
/// populated as metadata is built.
///
///
/// Dictionary of name to ResourceContainer for entity sets, populated
/// as metadata is built.
///
protected override void PopulateMetadata(
IDictionary knownTypes, IDictionary entitySets)
{
Queue unvisitedTypes = new Queue();
// Get the list of properties to be ignored.
List propertiesToBeIgnored = new List(
IgnorePropertiesAttribute.GetProperties(this.Type, true /*inherit*/, WebUtil.PublicInstanceBindingFlags));
PropertyInfo[] properties = this.Type.GetProperties(WebUtil.PublicInstanceBindingFlags);
foreach (PropertyInfo property in properties)
{
if (!propertiesToBeIgnored.Contains(property.Name) && property.CanRead && property.GetIndexParameters().Length == 0)
{
Type elementType = BaseServiceProvider.GetIQueryableElement(property.PropertyType);
if (elementType != null)
{
// If the element type has key defined (in itself or one of its ancestors)
ResourceType resourceType = BuildHierarchyForEntityType(elementType, knownTypes, unvisitedTypes, true /* entity type candidate */);
if (resourceType != null)
{
// We do not allow MEST scenaria for reflection provider
foreach (KeyValuePair entitySetInfo in entitySets)
{
Type entitySetType = entitySetInfo.Value.ElementType;
if (entitySetType.IsAssignableFrom(elementType))
{
throw new InvalidOperationException(Strings.ReflectionProvider_MultipleEntitySetsForSameType(entitySetInfo.Value.Name, property.Name, entitySetType.FullName, resourceType.FullName));
}
else if (elementType.IsAssignableFrom(entitySetType))
{
throw new InvalidOperationException(Strings.ReflectionProvider_MultipleEntitySetsForSameType(property.Name, entitySetInfo.Value.Name, resourceType.FullName, entitySetType.FullName));
}
}
// Add the entity set to the list of entity sets.
ResourceContainer resourceContainer = new ResourceContainer(property.Name, resourceType);
entitySets.Add(property.Name, resourceContainer);
}
else
{
throw new InvalidOperationException(Strings.ReflectionProvider_InvalidEntitySetProperty(property.Name, this.ResourceContextName));
}
}
}
}
// Populate the metadata for all the types in unvisited types
// and also their properties and populates metadata about property types
PopulateMetadataForTypes(knownTypes, unvisitedTypes, entitySets.Values);
// At this point, we should have all the top level entity types and the complex types
PopulateMetadataForDerivedTypes(knownTypes, unvisitedTypes, entitySets.Values);
}
///
/// Creates the IQueryable instance for the given resource container and returns it
///
/// resource container for which IQueryable instance needs to be created
/// returns the IQueryable instance for the given resource container
protected override IQueryable GetResourceContainerInstance(ResourceContainer resourceContainer)
{
Debug.Assert(resourceContainer != null, "resourceContainer != null");
if (resourceContainer.ReadFromContextDelegate == null)
{
PropertyInfo propertyInfo = this.Type.GetProperty(resourceContainer.Name, WebUtil.PublicInstanceBindingFlags);
MethodInfo getValueMethod = propertyInfo.GetGetMethod();
// return ((TheContext)arg0).get_Property();
Type[] parameterTypes = new Type[] { typeof(object) };
System.Reflection.Emit.DynamicMethod readerMethod = new System.Reflection.Emit.DynamicMethod("queryable_reader", typeof(IQueryable), parameterTypes, false);
var generator = readerMethod.GetILGenerator();
generator.Emit(System.Reflection.Emit.OpCodes.Ldarg_0);
generator.Emit(System.Reflection.Emit.OpCodes.Castclass, this.Type);
generator.Emit(System.Reflection.Emit.OpCodes.Call, getValueMethod);
generator.Emit(System.Reflection.Emit.OpCodes.Ret);
resourceContainer.ReadFromContextDelegate = (Func)readerMethod.CreateDelegate(typeof(Func));
}
Debug.Assert(resourceContainer.ReadFromContextDelegate != null, "resourceContainer.ReadFromContextDelegate != null");
return resourceContainer.ReadFromContextDelegate(this.CurrentDataSource);
}
///
/// Populate types for metadata specified by the provider
///
/// list of types specified by the provider
/// list of already known types
/// list of entity sets as specified in the data source type
protected override void PopulateMetadataForUserSpecifiedTypes(
IEnumerable userSpecifiedTypes, IDictionary knownTypes, IEnumerable entitySets)
{
Queue unvisitedTypes = new Queue();
foreach (Type type in userSpecifiedTypes)
{
ResourceType resourceType;
if (knownTypes.TryGetValue(type, out resourceType))
{
continue;
}
if (IsEntityOrComplexType(type, knownTypes, unvisitedTypes) == null)
{
throw new InvalidOperationException(Strings.BadProvider_InvalidTypeSpecified(type.FullName));
}
}
PopulateMetadataForTypes(knownTypes, unvisitedTypes, entitySets);
}
///
/// Populate metadata for the given clr type.
///
/// type whose metadata needs to be loaded.
/// list of already known resource types.
/// list of entity sets as specified in the data source.
/// resource type containing metadata for the given clr type.
protected override ResourceType PopulateMetadataForType(Type type, IDictionary knownTypes, IEnumerable entitySets)
{
Queue unvisitedTypes = new Queue();
ResourceType resourceType;
if (!knownTypes.TryGetValue(type, out resourceType))
{
resourceType = IsEntityOrComplexType(type, knownTypes, unvisitedTypes);
if (resourceType != null)
{
PopulateMetadataForTypes(knownTypes, unvisitedTypes, entitySets);
}
}
return resourceType;
}
/// Checks whether the specified type is a complex type.
/// Type to check.
///
/// true if the specified type is a complex type; false otherwise. Note
/// that resources are not distinguished from complex types.
///
private static bool IsComplexType(Type type)
{
Debug.Assert(type != null, "type != null");
// Complex types are all types that contain public properties of primitive
// types.
//
// We purposefully ignore certain known classes which fit this description
// but we know are not meaningful for Astoria:
// - System.Array: what would get serialized would be Length, IsFixed, etc.
// - Pointers: we would otherwise serialize the pointer size
// - COM object wrappers
// - interface: since we will never know what the exact type of the instance will be.
if (!type.IsVisible || type.IsArray || type.IsPointer || type.IsCOMObject || type.IsInterface ||
type == typeof(IntPtr) || type == typeof(UIntPtr) || type == typeof(char) ||
type == typeof(TimeSpan) || type == typeof(DateTimeOffset) || type == typeof(Uri) ||
type.IsEnum)
{
return false;
}
// For nullable types, we care whether the underlying value type is
// complex type.
Type nullableUnderlyingType = Nullable.GetUnderlyingType(type);
if (nullableUnderlyingType != null)
{
return ReflectionServiceProvider.IsComplexType(nullableUnderlyingType);
}
return true;
}
///
/// Checks whether there is a key defined for the given type.
///
/// type to check
///
/// Whether is being considered as a possible
/// entity type.
///
/// returns true if there are one or key properties present else returns false
private static bool DoesTypeHaveKeyProperties(Type type, bool entityTypeCandidate)
{
Debug.Assert(type != null, "type != null");
// Check for properties declared on this element only
foreach (PropertyInfo property in type.GetProperties(WebUtil.PublicInstanceBindingFlags | BindingFlags.DeclaredOnly))
{
ResourceKeyKind keyKind;
if (IsPropertyKeyProperty(property, out keyKind))
{
if (keyKind == ResourceKeyKind.AttributedKey && !entityTypeCandidate)
{
throw new InvalidOperationException(Strings.ReflectionProvider_EntityTypeHasKeyButNoEntitySet(type.FullName));
}
if (!entityTypeCandidate)
{
return false;
}
return true;
}
}
return false;
}
///
/// Populates the metadata for the given unvisited types and all the associated types with this type
///
/// list of known types
/// list of unvisited type
/// Available entity sets.
private static void PopulateMetadataForTypes(
IDictionary knownTypes,
Queue unvisitedTypes,
IEnumerable entitySets)
{
Debug.Assert(knownTypes != null, "knownTypes != null");
Debug.Assert(unvisitedTypes != null, "unvisitedTypes != null");
Debug.Assert(entitySets != null, "entitySets != null");
// Start walking down all the types
while (unvisitedTypes.Count != 0)
{
// get the unvisited element
ResourceType type = unvisitedTypes.Dequeue();
// Go through all the properties and find out one or more complex types
BuildTypeProperties(type, knownTypes, unvisitedTypes, entitySets);
}
}
///
/// Walks through the list of ancestors and finds the root base type and collects metadata for the entire chain of ancestors
///
/// type whose ancestors metadata needs to be populated
/// list of already known types
/// list of unvisited types
/// Whether is a candidate to be an entity type.
/// return true if this given type is a entity type, otherwise returns false
private static ResourceType BuildHierarchyForEntityType(
Type type, IDictionary knownTypes, Queue unvisitedTypes, bool entityTypeCandidate)
{
List ancestors = new List();
if (!type.IsVisible)
{
return null;
}
Type baseType = type;
ResourceType baseResourceType = null;
// Since this method is also used on property types, which can be interfaces,
// Base types can be null
while (baseType != null)
{
// Try and check if the base type is already loaded
if (knownTypes.TryGetValue(baseType, out baseResourceType))
{
break;
}
ancestors.Add(baseType);
baseType = baseType.BaseType;
}
if (baseResourceType == null)
{
// If entityTypeCandidate is false, then it means that the current type can't
// be a entity type with keys. In other words, it must derive from an existing
// type. Otherwise, its not an entity type
if (entityTypeCandidate == false)
{
return null;
}
// Find the last ancestor which has key defined
for (int i = ancestors.Count - 1; i >= 0; i--)
{
if (DoesTypeHaveKeyProperties(ancestors[i], entityTypeCandidate))
{
break;
}
// Else this type is not interesting. Remove it from the ancestors list
ancestors.RemoveAt(i);
}
}
else if (baseResourceType.ResourceTypeKind != ResourceTypeKind.EntityType)
{
return null;
}
else if (ancestors.Count == 0)
{
// we might have found the top level element.So just return
return baseResourceType;
}
// For all the valid ancestors, add the type to the list of types encountered
// and unvisited types
// its important that we enqueue the ancestors first, since when we populate member metadata
// we can make sure that the base type is fully populated
for (int i = ancestors.Count - 1; i >= 0; i--)
{
ResourceType entityType = new ResourceType(ancestors[i], ResourceTypeKind.EntityType, baseResourceType);
unvisitedTypes.Enqueue(entityType);
knownTypes.Add(ancestors[i], entityType);
baseResourceType = entityType;
}
return baseResourceType;
}
///
/// Populates the metadata for the properties of the given resource type
///
/// resource type whose properties metadata needs to be populated
/// list of known types
/// list of unvisited type
/// Available entity sets.
private static void BuildTypeProperties(
ResourceType parentResourceType,
IDictionary knownTypes,
Queue unvisitedTypes,
IEnumerable entitySets)
{
Debug.Assert(parentResourceType != null, "parentResourceType != null");
Debug.Assert(knownTypes != null, "knownTypes != null");
Debug.Assert(unvisitedTypes != null, "unvisitedTypes != null");
Debug.Assert(entitySets != null, "entitySets != null");
BindingFlags bindingFlags = WebUtil.PublicInstanceBindingFlags;
// For non root types, we should only look for properties that are declared for this type
if (parentResourceType.BaseType != null)
{
bindingFlags = bindingFlags | BindingFlags.DeclaredOnly;
}
HashSet propertiesToBeIgnored = new HashSet(StringComparer.Ordinal);
#if ASTORIA_OPEN_OBJECT
if (parentResourceType.IsOpenType)
{
propertiesToBeIgnored.Add(OpenTypeAttribute.GetOpenPropertyName(parentResourceType.Type));
}
#endif
foreach (string propertyName in IgnorePropertiesAttribute.GetProperties(parentResourceType.Type, false /*inherit*/, bindingFlags))
{
propertiesToBeIgnored.Add(propertyName);
}
ResourceKeyKind keyKind = (ResourceKeyKind)Int32.MaxValue;
PropertyInfo[] properties = parentResourceType.Type.GetProperties(bindingFlags);
foreach (PropertyInfo property in properties)
{
// Ignore the properties which are specified in the IgnoreProperties attribute
if (propertiesToBeIgnored.Contains(property.Name))
{
continue;
}
if (property.CanRead && property.GetIndexParameters().Length == 0)
{
ResourcePropertyKind kind = (ResourcePropertyKind)(-1);
ResourceKeyKind currentKeyKind = (ResourceKeyKind)(-1);
ResourceType resourceType;
Type resourcePropertyType = Nullable.GetUnderlyingType(property.PropertyType) ?? property.PropertyType;
ResourceContainer container = null;
bool collection = false;
if (!knownTypes.TryGetValue(resourcePropertyType, out resourceType))
{
Type collectionType = GetIEnumerableElement(property.PropertyType);
if (collectionType != null)
{
knownTypes.TryGetValue(collectionType, out resourceType);
// Even if the above method returns false, we should set the
// following variable appropriately, so that we can use them below
collection = true;
resourcePropertyType = collectionType;
}
}
if (resourceType != null)
{
#region Already Known Type
if (resourceType.ResourceTypeKind == ResourceTypeKind.Primitive)
{
// Check for key property only on root types, since keys must be defined on the root types
if (parentResourceType.BaseType == null && parentResourceType.ResourceTypeKind == ResourceTypeKind.EntityType && IsPropertyKeyProperty(property, out currentKeyKind))
{
if ((int)currentKeyKind < (int)keyKind)
{
if (parentResourceType.KeyProperties != null)
{
// Remove the existing property as key property - mark it as non key property
parentResourceType.RemoveKeyProperties();
}
keyKind = currentKeyKind;
kind = ResourcePropertyKind.Key | ResourcePropertyKind.Primitive;
}
else if ((int)currentKeyKind == (int)keyKind)
{
Debug.Assert(currentKeyKind == ResourceKeyKind.AttributedKey, "This is the only way of specifying composite keys");
kind = ResourcePropertyKind.Key | ResourcePropertyKind.Primitive;
}
else
{
kind = ResourcePropertyKind.Primitive;
}
}
else
{
kind = ResourcePropertyKind.Primitive;
}
}
else if (resourceType.ResourceTypeKind == ResourceTypeKind.ComplexType)
{
kind = ResourcePropertyKind.ComplexType;
}
else if (resourceType.ResourceTypeKind == ResourceTypeKind.EntityType)
{
kind = collection ? ResourcePropertyKind.ResourceSetReference : ResourcePropertyKind.ResourceReference;
}
#endregion // Already Known Type
}
else
{
resourceType = IsEntityOrComplexType(resourcePropertyType, knownTypes, unvisitedTypes);
if (resourceType != null)
{
if (resourceType.ResourceTypeKind == ResourceTypeKind.ComplexType)
{
kind = ResourcePropertyKind.ComplexType;
}
else
{
Debug.Assert(resourceType.ResourceTypeKind == ResourceTypeKind.EntityType, "Must be an entity type");
kind = collection ? ResourcePropertyKind.ResourceSetReference : ResourcePropertyKind.ResourceReference;
}
}
}
// if resource type is null OR
// if resource type is a collection of primitive or complex types OR
// if complex type has a property of entity type
if (resourceType == null ||
(resourceType.ResourceTypeKind != ResourceTypeKind.EntityType && collection) ||
(resourceType.ResourceTypeKind == ResourceTypeKind.EntityType && parentResourceType.ResourceTypeKind == ResourceTypeKind.ComplexType))
{
if (resourceType == null)
{
throw new InvalidOperationException(Strings.ReflectionProvider_InvalidProperty(property.Name, parentResourceType.FullName));
}
else
{
// collection of complex types not supported
throw new InvalidOperationException(Strings.ReflectionProvider_CollectionOfPrimitiveOrComplexNotSupported(property.Name, parentResourceType.FullName));
}
}
if (resourceType.ResourceTypeKind == ResourceTypeKind.EntityType)
{
container = InternalGetContainerForResourceType(resourcePropertyType, entitySets);
Debug.Assert(container != null, "container != null");
}
ResourceProperty resourceProperty = new ResourceProperty(property, kind, MimeTypeAttribute.GetMemberMimeType(property), resourceType, container);
parentResourceType.AddProperty(resourceProperty);
}
else
{
throw new InvalidOperationException(Strings.ReflectionProvider_InvalidProperty(property.Name, parentResourceType.FullName));
}
}
if (parentResourceType.ResourceTypeKind == ResourceTypeKind.EntityType &&
(parentResourceType.KeyProperties == null || parentResourceType.KeyProperties.Count == 0))
{
throw new InvalidOperationException(Strings.ReflectionProvider_KeyPropertiesCannotBeIgnored(parentResourceType.FullName));
}
parentResourceType.SortKeyMembers();
LoadETagProperties(parentResourceType);
}
///
/// If the given type is a entity or complex type, it returns the resource type corresponding to the given type
///
/// clr type
/// list of already known types
/// list of unvisited types
/// resource type corresponding to the given clr type, if the clr type is entity or complex
private static ResourceType IsEntityOrComplexType(Type type, IDictionary knownTypes, Queue unvisitedTypes)
{
// Ignore values types here. We do not support resources of values type (entity or complex)
if (type.IsValueType)
{
return null;
}
ResourceType resourceType = BuildHierarchyForEntityType(type, knownTypes, unvisitedTypes, false /* entityTypeCandidate */);
if (resourceType == null && IsComplexType(type))
{
resourceType = new ResourceType(type, ResourceTypeKind.ComplexType);
knownTypes.Add(type, resourceType);
unvisitedTypes.Enqueue(resourceType);
}
return resourceType;
}
/// Get the resource container for the given clr type.
/// clr type for which resource container name needs to be returned
/// Available entity sets to consider.
/// The container for its type, null if not found.
private static ResourceContainer InternalGetContainerForResourceType(Type type, IEnumerable entitySets)
{
Debug.Assert(type != null, "type != null");
Debug.Assert(entitySets != null, "entitySets != null");
// For each entity set, find out which one matches the type of this resource
foreach (ResourceContainer entitySetInfo in entitySets)
{
if (entitySetInfo.ElementType.IsAssignableFrom(type))
{
return entitySetInfo;
}
}
return null;
}
///
/// Find out all the derived types in the list of assemblies and then populate metadata for those types
///
/// list of known types
/// list of unvisited types
/// Available entity sets.
private static void PopulateMetadataForDerivedTypes(
IDictionary knownTypes, Queue unvisitedTypes, IEnumerable entitySets)
{
Debug.Assert(knownTypes != null, "knownTypes != null");
Debug.Assert(unvisitedTypes != null, "unvisitedTypes != null");
Debug.Assert(entitySets != null, "entitySets != null");
// Find all the root resource entity types
List rootTypes = new List();
foreach (ResourceType resourceType in knownTypes.Values)
{
if (resourceType.BaseType == null &&
resourceType.ResourceTypeKind == ResourceTypeKind.EntityType)
{
rootTypes.Add(resourceType);
}
}
// Use the default comparer, which calls Assembly.Equals (not a simple reference comparison).
HashSet assemblies = new HashSet(EqualityComparer.Default);
List derivedTypes = new List();
// Walk through all the types in the assemblies and find all the derived types
foreach (ResourceType resourceType in knownTypes.Values)
{
// No need to look into primitive types, as these live in system assemblies.
if (resourceType.ResourceTypeKind == ResourceTypeKind.Primitive)
{
continue;
}
Assembly assembly = resourceType.Type.Assembly;
//// ignore if the assembly has already been scanned
if (assemblies.Contains(assembly))
{
continue;
}
// Walk all the types in that assembly
foreach (Type type in assembly.GetTypes())
{
// skip all the non visible types
if (!type.IsVisible)
{
continue;
}
// Skip the type if its already loaded
if (knownTypes.ContainsKey(type))
{
continue;
}
// Check if this type dervies from any one of the root types
for (int i = 0; i < rootTypes.Count; i++)
{
if (rootTypes[i].Type.IsAssignableFrom(type))
{
derivedTypes.Add(type);
}
}
}
assemblies.Add(assembly);
}
foreach (Type type in derivedTypes)
{
BuildHierarchyForEntityType(type, knownTypes, unvisitedTypes, false /* entityTypeCandidate */);
PopulateMetadataForTypes(knownTypes, unvisitedTypes, entitySets);
}
}
///
/// Write the facets for clr types
///
/// XmlWriter in which facets needs to be written
/// property which contains the primitive type for which facets needs to be written
private static void WritePrimitivePropertyFacets(XmlWriter xmlWriter, ResourceProperty resourceProperty)
{
Debug.Assert(
resourceProperty.IsOfKind(ResourcePropertyKind.Primitive),
"property must be of primitive type");
bool nullable = true;
Type propertyType = resourceProperty.Type;
// Key properties can't be nullable and can't be generic
if (!resourceProperty.IsOfKind(ResourcePropertyKind.Key))
{
if (propertyType.IsGenericType &&
resourceProperty.Type.GetGenericTypeDefinition() == typeof(Nullable<>))
{
propertyType = Nullable.GetUnderlyingType(propertyType);
}
else if (propertyType.IsValueType)
{
nullable = false;
}
}
else
{
nullable = false;
}
xmlWriter.WriteAttributeString(XmlConstants.Nullable, nullable ? XmlConstants.XmlTrueLiteral : XmlConstants.XmlFalseLiteral);
}
///
/// Write the metadata for the entityType in the xmlWriter
///
/// xmlWriter in which metadata needs to be written
/// entityType whose metadata needs to be written
/// list of associations present in the model.
/// list of associations in the current namespace.
private static void WriteEntityType(
XmlWriter xmlWriter,
ResourceType entityType,
Dictionary associations,
Dictionary associationsInThisNamespace)
{
Debug.Assert(xmlWriter != null, "XmlWriter cannot be null");
Debug.Assert(entityType.ResourceTypeKind == ResourceTypeKind.EntityType, "Type must be entityType");
xmlWriter.WriteStartElement(XmlConstants.EntityType);
xmlWriter.WriteAttributeString(XmlConstants.Name, XmlConvert.EncodeName(entityType.Name));
if (entityType.Type.IsAbstract)
{
xmlWriter.WriteAttributeString(XmlConstants.Abstract, "true");
}
#if ASTORIA_OPEN_OBJECT
if (entityType.IsOpenType && OpenTypeAttribute.DeclaresOpenType(entityType.Type))
{
xmlWriter.WriteAttributeString(
XmlConstants.DataWebOpenTypeAttributeName,
XmlConstants.DataWebNamespace,
OpenTypeAttribute.GetOpenPropertyName(entityType.Type));
}
#endif
if (entityType.BaseType != null)
{
xmlWriter.WriteAttributeString(XmlConstants.BaseType, XmlConvert.EncodeName(entityType.BaseType.FullName));
}
else
{
xmlWriter.WriteStartElement(XmlConstants.Key);
foreach (ResourceProperty property in entityType.KeyProperties)
{
xmlWriter.WriteStartElement(XmlConstants.PropertyRef);
xmlWriter.WriteAttributeString(XmlConstants.Name, property.Name);
xmlWriter.WriteEndElement();
}
xmlWriter.WriteEndElement();
}
WriteProperties(xmlWriter, entityType, associations, associationsInThisNamespace);
xmlWriter.WriteEndElement();
}
///
/// Write the metadata for the complexType in the xmlWriter
///
/// xmlWriter in which metadata needs to be written
/// complexType whose metadata needs to be written
private static void WriteComplexType(XmlWriter xmlWriter, ResourceType complexType)
{
Debug.Assert(xmlWriter != null, "XmlWriter cannot be null");
Debug.Assert(complexType.ResourceTypeKind == ResourceTypeKind.ComplexType, "Type must be complexType");
xmlWriter.WriteStartElement(XmlConstants.ComplexType);
xmlWriter.WriteAttributeString(XmlConstants.Name, XmlConvert.EncodeName(complexType.Name));
WriteProperties(xmlWriter, complexType, null, null);
xmlWriter.WriteEndElement();
}
///
/// Write the metadata of all the properties for the given typein the xmlWriter
///
/// xmlWriter in which metadata needs to be written
/// resource type whose property metadata needs to be written
/// list of associations present in the model.
/// list of associations in the current namespace.
private static void WriteProperties(
XmlWriter xmlWriter,
ResourceType type,
Dictionary associations,
Dictionary associationsInThisNamespace)
{
Debug.Assert(xmlWriter != null, "xmlWriter != null");
Debug.Assert(type != null, "type != null");
for (int i = 0; i < type.PropertiesDeclaredInThisType.Count; i++)
{
ResourceProperty resourceProperty = type.PropertiesDeclaredInThisType[i];
string elementName;
// For primitive types, get the corresponding edm typename
if (resourceProperty.TypeKind == ResourceTypeKind.Primitive)
{
xmlWriter.WriteStartElement(XmlConstants.Property);
xmlWriter.WriteAttributeString(XmlConstants.Name, resourceProperty.Name);
elementName = WebUtil.GetEdmTypeName(resourceProperty.Type);
xmlWriter.WriteAttributeString(XmlConstants.Type, elementName);
WritePrimitivePropertyFacets(xmlWriter, resourceProperty);
if (!String.IsNullOrEmpty(resourceProperty.MimeType))
{
BaseServiceProvider.WriteDataWebMetadata(xmlWriter, XmlConstants.DataWebMimeTypeAttributeName, resourceProperty.MimeType);
}
if (type.ResourceTypeKind == ResourceTypeKind.EntityType &&
type.ETagProperties.Contains(resourceProperty))
{
xmlWriter.WriteAttributeString(XmlConstants.ConcurrencyAttribute, XmlConstants.ConcurrencyFixedValue);
}
}
else if (resourceProperty.Kind == ResourcePropertyKind.ComplexType)
{
xmlWriter.WriteStartElement(XmlConstants.Property);
xmlWriter.WriteAttributeString(XmlConstants.Name, resourceProperty.Name);
elementName = resourceProperty.ResourceType.FullName;
xmlWriter.WriteAttributeString(XmlConstants.Type, elementName);
// Edm doesn't support nullable complex type properties
xmlWriter.WriteAttributeString(XmlConstants.Nullable, XmlConstants.XmlFalseLiteral);
}
else
{
Debug.Assert(
ResourceTypeKind.EntityType == resourceProperty.TypeKind,
"Unexpected property type kind");
bool multivalued = false;
xmlWriter.WriteStartElement(XmlConstants.NavigationProperty);
xmlWriter.WriteAttributeString(XmlConstants.Name, resourceProperty.Name);
elementName = resourceProperty.ResourceType.FullName;
if (resourceProperty.Kind == ResourcePropertyKind.ResourceSetReference)
{
multivalued = true;
}
// for every nav property, add a new association to the metadata
// the name of the assocation will be _
// there might be conflicts since there can be 2 types with the same name, but in different namespace.
// in that case, we will use the full type name (replace '.' by '_'). That should make it unique.
string associationName = type.Name + '_' + resourceProperty.Name;
if (associations.ContainsKey(associationName))
{
associationName = type.FullName.Replace('.', '_') + '_' + resourceProperty.Name;
}
AssociationInfo association = new AssociationInfo(
associationName,
type.Type.Namespace,
new EndInfo(resourceProperty.Name, resourceProperty.ResourceType, multivalued ? XmlConstants.Many : XmlConstants.ZeroOrOne),
new EndInfo(type.Name, type, XmlConstants.Many));
// Add it to both the global list and the current namespace list.
associationsInThisNamespace.Add(associationName, association);
associations.Add(associationName, association);
xmlWriter.WriteAttributeString(XmlConstants.Relationship, association.FullName);
xmlWriter.WriteAttributeString(XmlConstants.FromRole, type.Name);
xmlWriter.WriteAttributeString(XmlConstants.ToRole, resourceProperty.Name);
#if ASTORIA_CONTAINMENT
if (resourceProperty.ContainmentTarget != null)
{
string path = resourceProperty.ContainmentTargetCanonical ?
XmlConstants.DataWebAccessPathCanonicalValue :
XmlConstants.DataWebAccessPathNonCanonicalValue;
BaseServiceProvider.WriteDataWebMetadata(xmlWriter, XmlConstants.DataWebAccessPathAttribute, path);
BaseServiceProvider.WriteDataWebMetadata(
xmlWriter, XmlConstants.DataWebAccessPathTargetAttribute, resourceProperty.ContainmentTarget.Name);
BaseServiceProvider.WriteDataWebMetadata(
xmlWriter, XmlConstants.DataWebAccessPathChildKeysAttribute, string.Join(",", resourceProperty.ContainmentChildKeys));
BaseServiceProvider.WriteDataWebMetadata(
xmlWriter, XmlConstants.DataWebAccessPathParentKeysAttribute, string.Join(",", resourceProperty.ContainmentParentKeys));
}
#endif
}
xmlWriter.WriteEndElement();
}
}
///
/// Writes the definition of types in the XmlWriter
///
/// xmlWriter in which metadata needs to be written
/// resourceTypes whose metadata needs to be written
/// list of associations present in the model.
/// list of associations in the current namespace.
private static void WriteTypes(
XmlWriter xmlWriter,
List types,
Dictionary associations,
Dictionary associationsInThisNamespace)
{
foreach (ResourceType type in types)
{
if (ResourceTypeKind.EntityType == type.ResourceTypeKind)
{
WriteEntityType(xmlWriter, type, associations, associationsInThisNamespace);
}
else
{
Debug.Assert(ResourceTypeKind.ComplexType == type.ResourceTypeKind, "this must be a complex type");
WriteComplexType(xmlWriter, type);
}
}
}
///
/// Loads the etag properties for the given resource type
///
/// resource type whose etag property names need to be loaded.
private static void LoadETagProperties(ResourceType resourceType)
{
// if it is the root type, then we need to inherit the attribute from the base type.
// otherwise, we need not, since if the base type already has it, the appropriate properties
// must already have been marked as concurrency properties.
bool inherit = resourceType.BaseType == null;
// Read the etag attribute from the type and return it
ETagAttribute[] attributes = (ETagAttribute[])resourceType.Type.GetCustomAttributes(typeof(ETagAttribute), inherit);
Debug.Assert(attributes.Length <= 1, "Only one attribute can be specified per type");
if (attributes.Length == 1)
{
// Validate the property names
// we may need to cache them instead of reading them everytime
foreach (string propertyName in attributes[0].PropertyNames)
{
ResourceProperty property = resourceType.TryResolvePropertyNameDefinedInThisType(propertyName);
if (property == null)
{
throw new InvalidOperationException(Strings.ETagAttribute_PropertyNameNotValid(propertyName, resourceType.FullName));
}
if (property.TypeKind != ResourceTypeKind.Primitive)
{
throw new InvalidOperationException(Strings.ETagAttribute_ETagPropertiesMustBeOfPrimitiveType(propertyName, resourceType.FullName));
}
// Ignore key properties if they are part of etag, since the uri already has the key information
// and it makes no sense to duplicate them in etag
if (property.IsOfKind(ResourcePropertyKind.Key))
{
throw new InvalidOperationException(Strings.ETagAttribute_KeyPropertiesCannotBePartOfETag(propertyName, resourceType.FullName));
}
resourceType.AddETagProperty(property);
}
}
}
///
/// Writes the metadata for the given associations.
///
/// xmlWriter in which metadata needs to be written.
/// associations whose metadata need to be written.
private static void WriteAssociations(XmlWriter xmlWriter, IEnumerable associations)
{
foreach (AssociationInfo association in associations)
{
xmlWriter.WriteStartElement(XmlConstants.Association);
xmlWriter.WriteAttributeString(XmlConstants.Name, association.Name);
foreach (EndInfo endInfo in association.Ends)
{
xmlWriter.WriteStartElement(XmlConstants.End);
xmlWriter.WriteAttributeString(XmlConstants.Role, endInfo.Name);
// The ends are of reference type
xmlWriter.WriteAttributeString(XmlConstants.Type, endInfo.Type.FullName);
// Write the multiplicity value
xmlWriter.WriteAttributeString(XmlConstants.Multiplicity, endInfo.Multiplicity);
xmlWriter.WriteEndElement();
}
xmlWriter.WriteEndElement();
}
}
///
/// Writes the metadata for association sets for the given associations.
///
/// xmlWriter in which metadata needs to be written
/// associations for which association sets needs to be written.
private void WriteAssociationSets(XmlWriter xmlWriter, IEnumerable associations)
{
foreach (AssociationInfo association in associations)
{
xmlWriter.WriteStartElement(XmlConstants.AssociationSet);
xmlWriter.WriteAttributeString(XmlConstants.Name, association.Name);
xmlWriter.WriteAttributeString(XmlConstants.Association, association.FullName);
foreach (EndInfo endInfo in association.Ends)
{
xmlWriter.WriteStartElement(XmlConstants.End);
xmlWriter.WriteAttributeString(XmlConstants.Role, endInfo.Name);
// The ends are of reference type
ResourceContainer container = this.GetContainerForResourceType(endInfo.Type.Type);
xmlWriter.WriteAttributeString(XmlConstants.EntitySet, container.Name);
xmlWriter.WriteEndElement();
}
xmlWriter.WriteEndElement();
}
}
///
/// Writes the entity container definition
///
/// xmlWriter into which metadata is written
/// name of the entity container
/// list of entity set name and containing element type name
/// associations for which association sets metadata needs to be written.
private void WriteEntityContainer(
XmlWriter xmlWriter,
string entityContainerName,
IEnumerable> entitySets,
Dictionary associations)
{
xmlWriter.WriteStartElement(XmlConstants.EntityContainer);
xmlWriter.WriteAttributeString(XmlConstants.Name, entityContainerName);
// Since reflection based provider supports only one entity container, we should write the
// default entity container attribute for the only entity container
BaseServiceProvider.WriteDataWebMetadata(xmlWriter, XmlConstants.IsDefaultEntityContainerAttribute, XmlConstants.XmlTrueLiteral);
foreach (KeyValuePair entitySet in entitySets)
{
ResourceContainer container = entitySet.Value;
xmlWriter.WriteStartElement(XmlConstants.EntitySet);
xmlWriter.WriteAttributeString(XmlConstants.Name, entitySet.Key);
xmlWriter.WriteAttributeString(XmlConstants.EntityType, container.ResourceType.FullName);
#if ASTORIA_CONTAINMENT
if (container.ContainmentCanonicalParent != null)
{
xmlWriter.WriteAttributeString(
XmlConstants.DataWebAccessTopLevelAccessAttribute,
XmlConstants.DataWebMetadataNamespace,
XmlConvert.ToString(container.TopLevelAccess));
}
#endif
xmlWriter.WriteEndElement();
}
this.WriteAssociationSets(xmlWriter, associations.Values);
this.WriteServiceOperations(xmlWriter, this.ServiceOperations);
xmlWriter.WriteEndElement();
}
///
/// Stores information about an end of an association.
///
private struct EndInfo
{
/// Name of the relationship end
internal readonly string Name;
/// Type of the relationship end.
internal readonly ResourceType Type;
/// Mulitplicity of the relationship end
internal readonly string Multiplicity;
///
/// Creates a new instance of EndInfo.
///
/// name of the end.
/// resource type that the end refers to.
/// multiplicity of the end.
internal EndInfo(string name, ResourceType type, string multiplicity)
{
this.Name = name;
this.Type = type;
this.Multiplicity = multiplicity;
}
}
///
/// Stores information about a association and its ends
///
private class AssociationInfo
{
/// FullName of the association.
internal readonly string FullName;
/// Name of the association
internal readonly string Name;
/// collection of ends for this association.
internal readonly EndInfo[] Ends;
///
/// Creates a new instance of AssociationInfo to store information about an association.
///
/// name of the association.
/// namespaceName of the association.
/// first end of the association.
/// second end of the association.
internal AssociationInfo(string name, string namespaceName, EndInfo end1, EndInfo end2)
{
this.Name = name;
this.FullName = namespaceName + "." + name;
this.Ends = new EndInfo[] { end1, end2 };
}
}
///
/// Finds all the interesting types and their members given the top level type.
///
private class TypeManager
{
/// List of namespace along with the types in that namespace
private Dictionary> namespaceManager = new Dictionary>(StringComparer.Ordinal);
///
/// Constructs a new instance of TypeManager using the knownTypes
///
/// list of known resource types
internal TypeManager(IEnumerable knownTypes)
{
foreach (ResourceType resourceType in knownTypes)
{
// Ignore Primitive types
if (resourceType.ResourceTypeKind == ResourceTypeKind.Primitive)
{
continue;
}
List typesInSameNamespace;
string typeNamespace = WebUtil.GetModelTypeNamespace(resourceType.Type);
if (!this.namespaceManager.TryGetValue(typeNamespace, out typesInSameNamespace))
{
typesInSameNamespace = new List();
this.namespaceManager.Add(typeNamespace, typesInSameNamespace);
}
// Add the type to the list of types in the same namespace
typesInSameNamespace.Add(resourceType);
}
}
///
/// Returns the list of namespace and the types in those namespaces
///
internal IEnumerable>> NamespaceAlongWithTypes
{
get
{
return this.namespaceManager;
}
}
}
}
}
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
//----------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
//
// Provides the interface definition for web data service
// data sources.
//
//
// @owner [....]
//---------------------------------------------------------------------
namespace System.Data.Services.Providers
{
#region Namespaces.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data.Services.Caching;
using System.Data.Services.Common;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Xml;
#endregion Namespaces.
///
/// Provides a reflection-based IDataServiceProvider implementation.
///
[DebuggerDisplay("ReflectionServiceProvider: {Type}")]
internal class ReflectionServiceProvider : BaseServiceProvider
{
///
/// Initializes a new System.Data.Services.ReflectionServiceProvider instance.
///
/// Metadata for this provider.
/// instance of the data source provider.
internal ReflectionServiceProvider(MetadataCacheItem metadata, object dataSourceInstance)
: base(metadata, dataSourceInstance)
{
}
/// Gets a value indicating whether null propagation is required in expression trees.
public override bool NullPropagationRequired
{
get { return true; }
}
///
/// Provides a name for the context in which all resource containers are.
///
public override string ResourceContextName
{
get
{
return XmlConvert.EncodeName(this.Type.Name);
}
}
/// Gets the name of the container that holds this resource type.This method is called for open types only
/// Resource to get container for.
///
/// The name of the container for the specified resource; null if it cannot
/// be determined.
///
public override ResourceContainer GetContainerForResourceType(Type resourceType)
{
Debug.Assert(resourceType != null, "resourceType != null");
return InternalGetContainerForResourceType(resourceType, this.EntitySets.Values);
}
///
/// Gets the container corresponding to the given navigation property
/// The declaring type container name refers to the entity set that the declaring type belongs to
///
/// name of the entity container that the declaring type belongs to
/// declaring type
/// resource navigation property
/// name of the container that this property refers to
public override ResourceContainer GetContainer(string declaringTypeContainerName, Type declaringResourceType, ResourceProperty resourceProperty)
{
Debug.Assert(declaringTypeContainerName != null, "declaringTypeContainerName != null");
Debug.Assert(declaringResourceType != null, "declaringResourceType != null");
Debug.Assert(resourceProperty != null, "resourceProperty != null");
return InternalGetContainerForResourceType(resourceProperty.ResourceClrType, this.EntitySets.Values);
}
/// Gets the metadata document for this provider.
/// Writer to which metadata XML should be written.
public override void GetMetadata(XmlWriter xmlWriter)
{
Debug.Assert(xmlWriter != null, "xmlWriter != null");
TypeManager typeManager = new TypeManager(this.Types);
BaseServiceProvider.WriteTopLevelSchemaElements(xmlWriter);
Dictionary associations = new Dictionary(StringComparer.Ordinal);
List typesInEntityContainerNamespace = null;
// Write the schema Element for every namespace
foreach (KeyValuePair> namespaceAlongWithTypes in typeManager.NamespaceAlongWithTypes)
{
// If the types live in the same namespace as that of entity container and types that don't live in any namespace,
// should be written out in the service namespace. If the service type also doesn't have a namespace, we will use
// the service name as the namespace. See code below for that.
if (namespaceAlongWithTypes.Key == this.Type.Namespace || String.IsNullOrEmpty(namespaceAlongWithTypes.Key))
{
// We can't write the entity container until we have figured out all the associations.
// Hence storing the type information for types which live in the same namespace as that
// of entity container and writting them at the end along with the entity container.
if (typesInEntityContainerNamespace == null)
{
typesInEntityContainerNamespace = namespaceAlongWithTypes.Value;
}
else
{
typesInEntityContainerNamespace.AddRange(namespaceAlongWithTypes.Value);
}
}
else
{
var associationsInThisNamespace = new Dictionary(StringComparer.Ordinal);
BaseServiceProvider.WriteSchemaElement(xmlWriter, namespaceAlongWithTypes.Key, this.CompatibleWithV1Schema);
WriteTypes(xmlWriter, namespaceAlongWithTypes.Value, associations, associationsInThisNamespace);
WriteAssociations(xmlWriter, associationsInThisNamespace.Values);
xmlWriter.WriteEndElement();
}
}
// Write the entity container definition. Also if there are types in the same namespace,
// we need to write them out too.
string typeNamespace = this.Type.Namespace;
if (String.IsNullOrEmpty(typeNamespace))
{
typeNamespace = XmlConvert.EncodeName(this.Type.Name);
}
BaseServiceProvider.WriteSchemaElement(xmlWriter, typeNamespace, this.CompatibleWithV1Schema);
if (typesInEntityContainerNamespace != null)
{
var associationsInThisNamespace = new Dictionary(StringComparer.Ordinal);
WriteTypes(xmlWriter, typesInEntityContainerNamespace, associations, associationsInThisNamespace);
WriteAssociations(xmlWriter, associationsInThisNamespace.Values);
}
this.WriteEntityContainer(xmlWriter, this.ResourceContextName, this.EntitySets, associations);
xmlWriter.WriteEndElement();
// These end elements balance the elements written out in WriteTopLevelSchemaElements
xmlWriter.WriteEndElement();
xmlWriter.WriteEndElement();
xmlWriter.Flush();
}
///
/// Get the list of etag property names given the entity set name and the instance of the resource
///
/// name of the entity set
/// clr type of the resource whose etag properties need to be fetched
/// list of etag property names
public override ICollection GetETagProperties(string containerName, Type resourceClrType)
{
Debug.Assert(resourceClrType != null, "clrType cannot be null");
ResourceType resourceType = ((IDataServiceProvider)this).GetResourceType(resourceClrType);
Debug.Assert(resourceType != null, "resourceType != null");
return resourceType.ETagProperties;
}
///
/// Checks whether the specified is ordered.
///
/// Type to check.
/// true if the type may be ordered; false otherwise.
///
/// The ordering may still fail at runtime; this method is currently
/// used for cleaner error messages only.
///
public override bool GetTypeIsOrdered(Type type)
{
Debug.Assert(type != null, "type != null");
if (typeof(IComparable).IsAssignableFrom(type))
{
return true;
}
else
{
return base.GetTypeIsOrdered(type);
}
}
#region IUpdatable Members
///
/// Creates the resource of the given type and belonging to the given container
///
/// container name to which the resource needs to be added
/// full type name i.e. Namespace qualified type name of the resource
/// object representing a resource of given type and belonging to the given container
public override object CreateResource(string containerName, string fullTypeName)
{
if (typeof(IUpdatable).IsAssignableFrom(this.Type))
{
object resource = ((IUpdatable)this.CurrentDataSource).CreateResource(containerName, fullTypeName);
if (resource == null)
{
throw new InvalidOperationException(Strings.BadProvider_CreateResourceReturnedNull);
}
return resource;
}
else
{
throw DataServiceException.CreateMethodNotImplemented(Strings.DataSourceMustImplementIUpdatableToSupportUpdates);
}
}
///
/// Gets the resource of the given type that the query points to
///
/// query pointing to a particular resource
/// full type name i.e. Namespace qualified type name of the resource
/// object representing a resource of given type and as referenced by the query
public override object GetResource(IQueryable query, string fullTypeName)
{
object resource = null;
if (typeof(IUpdatable).IsAssignableFrom(this.Type))
{
try
{
resource = ((IUpdatable)this.CurrentDataSource).GetResource(query, fullTypeName);
}
catch (ArgumentException e)
{
throw DataServiceException.CreateBadRequestError(Strings.BadRequest_InvalidUriSpecified, e);
}
}
else
{
throw DataServiceException.CreateMethodNotImplemented(Strings.DataSourceMustImplementIUpdatableToSupportUpdates);
}
return resource;
}
///
/// Resets the value of the given resource to its default value
///
/// resource whose value needs to be reset
/// same resource with its value reset
public override object ResetResource(object resource)
{
if (typeof(IUpdatable).IsAssignableFrom(this.Type))
{
resource = ((IUpdatable)this.CurrentDataSource).ResetResource(resource);
}
else
{
throw DataServiceException.CreateMethodNotImplemented(Strings.DataSourceMustImplementIUpdatableToSupportUpdates);
}
return resource;
}
///
/// Sets the value of the given property on the target object
///
/// target object which defines the property
/// name of the property whose value needs to be updated
/// value of the property
public override void SetValue(object targetResource, string propertyName, object propertyValue)
{
if (typeof(IUpdatable).IsAssignableFrom(this.Type))
{
((IUpdatable)this.CurrentDataSource).SetValue(targetResource, propertyName, propertyValue);
}
else
{
throw DataServiceException.CreateMethodNotImplemented(Strings.DataSourceMustImplementIUpdatableToSupportUpdates);
}
}
///
/// Gets the value of the given property on the target object
///
/// target object which defines the property
/// name of the property whose value needs to be updated
/// the value of the property for the given target resource
public override object GetValue(object targetResource, string propertyName)
{
object resource = null;
if (typeof(IUpdatable).IsAssignableFrom(this.Type))
{
resource = ((IUpdatable)this.CurrentDataSource).GetValue(targetResource, propertyName);
}
else
{
throw DataServiceException.CreateMethodNotImplemented(Strings.DataSourceMustImplementIUpdatableToSupportUpdates);
}
if (resource == null)
{
throw DataServiceException.CreateResourceNotFound(propertyName);
}
return resource;
}
///
/// Sets the value of the given reference property on the target object
///
/// target object which defines the property
/// name of the property whose value needs to be updated
/// value of the property
public override void SetReference(object targetResource, string propertyName, object propertyValue)
{
if (typeof(IUpdatable).IsAssignableFrom(this.Type))
{
((IUpdatable)this.CurrentDataSource).SetReference(targetResource, propertyName, propertyValue);
}
else
{
throw DataServiceException.CreateMethodNotImplemented(Strings.DataSourceMustImplementIUpdatableToSupportUpdates);
}
}
///
/// Adds the given value to the collection
///
/// target object which defines the property
/// name of the property whose value needs to be updated
/// value of the property which needs to be added
public override void AddReferenceToCollection(object targetResource, string propertyName, object resourceToBeAdded)
{
if (typeof(IUpdatable).IsAssignableFrom(this.Type))
{
((IUpdatable)this.CurrentDataSource).AddReferenceToCollection(targetResource, propertyName, resourceToBeAdded);
}
else
{
throw DataServiceException.CreateMethodNotImplemented(Strings.DataSourceMustImplementIUpdatableToSupportUpdates);
}
}
/// Applies expansions to the specified .
/// object to expand.
/// A collection of ordered paths.
///
/// An object of the same type as the given ,
/// with the results including the specified .
///
///
/// This method may modify the to indicate which expansions
/// are included.
///
/// The returned may implement the
/// interface to provide enumerable objects for the expansions; otherwise, the expanded
/// information is expected to be found directly in the enumerated objects.
///
public override IEnumerable ApplyExpansions(IQueryable queryable, ICollection expandPaths)
{
IExpandProvider provider = this.CurrentDataSource as IExpandProvider;
if (provider != null)
{
return provider.ApplyExpansions(queryable, expandPaths);
}
else
{
foreach (ExpandSegmentCollection path in expandPaths)
{
if (path.HasFilter)
{
return new BasicExpandProvider(this, true).ApplyExpansions(queryable, expandPaths);
}
}
// This pass-through implementation is appropriate for providers that fault-in on demand.
return queryable;
}
}
///
/// Removes the given value from the collection
///
/// target object which defines the property
/// name of the property whose value needs to be updated
/// value of the property which needs to be removed
public override void RemoveReferenceFromCollection(object targetResource, string propertyName, object resourceToBeRemoved)
{
if (typeof(IUpdatable).IsAssignableFrom(this.Type))
{
((IUpdatable)this.CurrentDataSource).RemoveReferenceFromCollection(targetResource, propertyName, resourceToBeRemoved);
}
else
{
throw DataServiceException.CreateMethodNotImplemented(Strings.DataSourceMustImplementIUpdatableToSupportUpdates);
}
}
///
/// Delete the given resource
///
/// resource that needs to be deleted
public override void DeleteResource(object targetResource)
{
if (typeof(IUpdatable).IsAssignableFrom(this.Type))
{
((IUpdatable)this.CurrentDataSource).DeleteResource(targetResource);
}
else
{
throw DataServiceException.CreateMethodNotImplemented(Strings.DataSourceMustImplementIUpdatableToSupportUpdates);
}
}
///
/// Saves all the pending changes made till now
///
public override void SaveChanges()
{
if (typeof(IUpdatable).IsAssignableFrom(this.Type))
{
((IUpdatable)this.CurrentDataSource).SaveChanges();
}
else
{
throw DataServiceException.CreateMethodNotImplemented(Strings.DataSourceMustImplementIUpdatableToSupportUpdates);
}
}
///
/// Returns the actual instance of the resource represented by the given resource object
///
/// object representing the resource whose instance needs to be fetched
/// The actual instance of the resource represented by the given resource object
public override object ResolveResource(object resource)
{
object resolvedResource = null;
if (typeof(IUpdatable).IsAssignableFrom(this.Type))
{
resolvedResource = ((IUpdatable)this.CurrentDataSource).ResolveResource(resource);
}
else
{
throw DataServiceException.CreateMethodNotImplemented(Strings.DataSourceMustImplementIUpdatableToSupportUpdates);
}
if (resolvedResource == null)
{
throw new InvalidOperationException(Strings.BadProvider_ResolveResourceReturnedNull);
}
return resolvedResource;
}
///
/// Revert all the pending changes.
///
public override void ClearChanges()
{
if (typeof(IUpdatable).IsAssignableFrom(this.Type))
{
((IUpdatable)this.CurrentDataSource).ClearChanges();
}
else
{
throw DataServiceException.CreateMethodNotImplemented(Strings.DataSourceMustImplementIUpdatableToSupportUpdates);
}
}
#endregion
/// Checks whether the given property is a key property.
/// property to check
/// returns the key kind of the property, based on the heuristic it matches
/// true if this is a key property, else returns false
internal static bool IsPropertyKeyProperty(PropertyInfo property, out ResourceKeyKind keyKind)
{
keyKind = (ResourceKeyKind)(-1);
// Only primitive types are allowed to be keys.
// Checks for generic to exclude Nullable<> value-type primitives, since we don't allows keys to be null.
if (WebUtil.IsPrimitiveType(property.PropertyType) &&
!property.PropertyType.IsGenericType)
{
DataServiceKeyAttribute keyAttribute = property.ReflectedType.GetCustomAttributes(true).OfType().FirstOrDefault();
if (keyAttribute != null && keyAttribute.KeyNames.Contains(property.Name))
{
keyKind = ResourceKeyKind.AttributedKey;
return true;
}
// For now, the key property must be {TypeName}Id or Id and the property
// type must be primitive, since we do not support non-primitive types
// as keys
if (property.Name == property.DeclaringType.Name + "ID")
{
keyKind = ResourceKeyKind.TypeNameId;
return true;
}
else if (property.Name == "ID")
{
keyKind = ResourceKeyKind.Id;
return true;
}
}
return false;
}
#if ASTORIA_CONTAINMENT
///
/// Returns an object that can enumerate all
/// instances that apply to this model.
///
///
/// An object that can enumerate all
/// instances that apply to this model.
///
protected override IEnumerable EnumerateAccessPathAttributes()
{
Type provider = this.Type;
foreach (ResourceContainer container in this.EntitySets.Values)
{
PropertyInfo property = provider.GetProperty(container.Name, WebUtil.PublicInstanceBindingFlags);
Debug.Assert(property != null, "property != null -- otherwise " + container.Name + " shouldn't be in metadata.");
object[] attributes = property.GetCustomAttributes(true);
if (attributes == null || attributes.Length == 0)
{
continue;
}
foreach (AccessPathAttribute attribute in attributes.OfType())
{
attribute.AnnotatedContainer = container;
yield return attribute;
}
}
}
#endif
/// Populates the metadata for this provider.
///
/// Dictionary of known CLR to ResourceType entries, which is
/// populated as metadata is built.
///
///
/// Dictionary of name to ResourceContainer for entity sets, populated
/// as metadata is built.
///
protected override void PopulateMetadata(
IDictionary knownTypes, IDictionary entitySets)
{
Queue unvisitedTypes = new Queue();
// Get the list of properties to be ignored.
List propertiesToBeIgnored = new List(
IgnorePropertiesAttribute.GetProperties(this.Type, true /*inherit*/, WebUtil.PublicInstanceBindingFlags));
PropertyInfo[] properties = this.Type.GetProperties(WebUtil.PublicInstanceBindingFlags);
foreach (PropertyInfo property in properties)
{
if (!propertiesToBeIgnored.Contains(property.Name) && property.CanRead && property.GetIndexParameters().Length == 0)
{
Type elementType = BaseServiceProvider.GetIQueryableElement(property.PropertyType);
if (elementType != null)
{
// If the element type has key defined (in itself or one of its ancestors)
ResourceType resourceType = BuildHierarchyForEntityType(elementType, knownTypes, unvisitedTypes, true /* entity type candidate */);
if (resourceType != null)
{
// We do not allow MEST scenaria for reflection provider
foreach (KeyValuePair entitySetInfo in entitySets)
{
Type entitySetType = entitySetInfo.Value.ElementType;
if (entitySetType.IsAssignableFrom(elementType))
{
throw new InvalidOperationException(Strings.ReflectionProvider_MultipleEntitySetsForSameType(entitySetInfo.Value.Name, property.Name, entitySetType.FullName, resourceType.FullName));
}
else if (elementType.IsAssignableFrom(entitySetType))
{
throw new InvalidOperationException(Strings.ReflectionProvider_MultipleEntitySetsForSameType(property.Name, entitySetInfo.Value.Name, resourceType.FullName, entitySetType.FullName));
}
}
// Add the entity set to the list of entity sets.
ResourceContainer resourceContainer = new ResourceContainer(property.Name, resourceType);
entitySets.Add(property.Name, resourceContainer);
}
else
{
throw new InvalidOperationException(Strings.ReflectionProvider_InvalidEntitySetProperty(property.Name, this.ResourceContextName));
}
}
}
}
// Populate the metadata for all the types in unvisited types
// and also their properties and populates metadata about property types
PopulateMetadataForTypes(knownTypes, unvisitedTypes, entitySets.Values);
// At this point, we should have all the top level entity types and the complex types
PopulateMetadataForDerivedTypes(knownTypes, unvisitedTypes, entitySets.Values);
}
///
/// Creates the IQueryable instance for the given resource container and returns it
///
/// resource container for which IQueryable instance needs to be created
/// returns the IQueryable instance for the given resource container
protected override IQueryable GetResourceContainerInstance(ResourceContainer resourceContainer)
{
Debug.Assert(resourceContainer != null, "resourceContainer != null");
if (resourceContainer.ReadFromContextDelegate == null)
{
PropertyInfo propertyInfo = this.Type.GetProperty(resourceContainer.Name, WebUtil.PublicInstanceBindingFlags);
MethodInfo getValueMethod = propertyInfo.GetGetMethod();
// return ((TheContext)arg0).get_Property();
Type[] parameterTypes = new Type[] { typeof(object) };
System.Reflection.Emit.DynamicMethod readerMethod = new System.Reflection.Emit.DynamicMethod("queryable_reader", typeof(IQueryable), parameterTypes, false);
var generator = readerMethod.GetILGenerator();
generator.Emit(System.Reflection.Emit.OpCodes.Ldarg_0);
generator.Emit(System.Reflection.Emit.OpCodes.Castclass, this.Type);
generator.Emit(System.Reflection.Emit.OpCodes.Call, getValueMethod);
generator.Emit(System.Reflection.Emit.OpCodes.Ret);
resourceContainer.ReadFromContextDelegate = (Func)readerMethod.CreateDelegate(typeof(Func));
}
Debug.Assert(resourceContainer.ReadFromContextDelegate != null, "resourceContainer.ReadFromContextDelegate != null");
return resourceContainer.ReadFromContextDelegate(this.CurrentDataSource);
}
///
/// Populate types for metadata specified by the provider
///
/// list of types specified by the provider
/// list of already known types
/// list of entity sets as specified in the data source type
protected override void PopulateMetadataForUserSpecifiedTypes(
IEnumerable userSpecifiedTypes, IDictionary knownTypes, IEnumerable entitySets)
{
Queue unvisitedTypes = new Queue();
foreach (Type type in userSpecifiedTypes)
{
ResourceType resourceType;
if (knownTypes.TryGetValue(type, out resourceType))
{
continue;
}
if (IsEntityOrComplexType(type, knownTypes, unvisitedTypes) == null)
{
throw new InvalidOperationException(Strings.BadProvider_InvalidTypeSpecified(type.FullName));
}
}
PopulateMetadataForTypes(knownTypes, unvisitedTypes, entitySets);
}
///
/// Populate metadata for the given clr type.
///
/// type whose metadata needs to be loaded.
/// list of already known resource types.
/// list of entity sets as specified in the data source.
/// resource type containing metadata for the given clr type.
protected override ResourceType PopulateMetadataForType(Type type, IDictionary knownTypes, IEnumerable entitySets)
{
Queue unvisitedTypes = new Queue();
ResourceType resourceType;
if (!knownTypes.TryGetValue(type, out resourceType))
{
resourceType = IsEntityOrComplexType(type, knownTypes, unvisitedTypes);
if (resourceType != null)
{
PopulateMetadataForTypes(knownTypes, unvisitedTypes, entitySets);
}
}
return resourceType;
}
/// Checks whether the specified type is a complex type.
/// Type to check.
///
/// true if the specified type is a complex type; false otherwise. Note
/// that resources are not distinguished from complex types.
///
private static bool IsComplexType(Type type)
{
Debug.Assert(type != null, "type != null");
// Complex types are all types that contain public properties of primitive
// types.
//
// We purposefully ignore certain known classes which fit this description
// but we know are not meaningful for Astoria:
// - System.Array: what would get serialized would be Length, IsFixed, etc.
// - Pointers: we would otherwise serialize the pointer size
// - COM object wrappers
// - interface: since we will never know what the exact type of the instance will be.
if (!type.IsVisible || type.IsArray || type.IsPointer || type.IsCOMObject || type.IsInterface ||
type == typeof(IntPtr) || type == typeof(UIntPtr) || type == typeof(char) ||
type == typeof(TimeSpan) || type == typeof(DateTimeOffset) || type == typeof(Uri) ||
type.IsEnum)
{
return false;
}
// For nullable types, we care whether the underlying value type is
// complex type.
Type nullableUnderlyingType = Nullable.GetUnderlyingType(type);
if (nullableUnderlyingType != null)
{
return ReflectionServiceProvider.IsComplexType(nullableUnderlyingType);
}
return true;
}
///
/// Checks whether there is a key defined for the given type.
///
/// type to check
///
/// Whether is being considered as a possible
/// entity type.
///
/// returns true if there are one or key properties present else returns false
private static bool DoesTypeHaveKeyProperties(Type type, bool entityTypeCandidate)
{
Debug.Assert(type != null, "type != null");
// Check for properties declared on this element only
foreach (PropertyInfo property in type.GetProperties(WebUtil.PublicInstanceBindingFlags | BindingFlags.DeclaredOnly))
{
ResourceKeyKind keyKind;
if (IsPropertyKeyProperty(property, out keyKind))
{
if (keyKind == ResourceKeyKind.AttributedKey && !entityTypeCandidate)
{
throw new InvalidOperationException(Strings.ReflectionProvider_EntityTypeHasKeyButNoEntitySet(type.FullName));
}
if (!entityTypeCandidate)
{
return false;
}
return true;
}
}
return false;
}
///
/// Populates the metadata for the given unvisited types and all the associated types with this type
///
/// list of known types
/// list of unvisited type
/// Available entity sets.
private static void PopulateMetadataForTypes(
IDictionary knownTypes,
Queue unvisitedTypes,
IEnumerable entitySets)
{
Debug.Assert(knownTypes != null, "knownTypes != null");
Debug.Assert(unvisitedTypes != null, "unvisitedTypes != null");
Debug.Assert(entitySets != null, "entitySets != null");
// Start walking down all the types
while (unvisitedTypes.Count != 0)
{
// get the unvisited element
ResourceType type = unvisitedTypes.Dequeue();
// Go through all the properties and find out one or more complex types
BuildTypeProperties(type, knownTypes, unvisitedTypes, entitySets);
}
}
///
/// Walks through the list of ancestors and finds the root base type and collects metadata for the entire chain of ancestors
///
/// type whose ancestors metadata needs to be populated
/// list of already known types
/// list of unvisited types
/// Whether is a candidate to be an entity type.
/// return true if this given type is a entity type, otherwise returns false
private static ResourceType BuildHierarchyForEntityType(
Type type, IDictionary knownTypes, Queue unvisitedTypes, bool entityTypeCandidate)
{
List ancestors = new List();
if (!type.IsVisible)
{
return null;
}
Type baseType = type;
ResourceType baseResourceType = null;
// Since this method is also used on property types, which can be interfaces,
// Base types can be null
while (baseType != null)
{
// Try and check if the base type is already loaded
if (knownTypes.TryGetValue(baseType, out baseResourceType))
{
break;
}
ancestors.Add(baseType);
baseType = baseType.BaseType;
}
if (baseResourceType == null)
{
// If entityTypeCandidate is false, then it means that the current type can't
// be a entity type with keys. In other words, it must derive from an existing
// type. Otherwise, its not an entity type
if (entityTypeCandidate == false)
{
return null;
}
// Find the last ancestor which has key defined
for (int i = ancestors.Count - 1; i >= 0; i--)
{
if (DoesTypeHaveKeyProperties(ancestors[i], entityTypeCandidate))
{
break;
}
// Else this type is not interesting. Remove it from the ancestors list
ancestors.RemoveAt(i);
}
}
else if (baseResourceType.ResourceTypeKind != ResourceTypeKind.EntityType)
{
return null;
}
else if (ancestors.Count == 0)
{
// we might have found the top level element.So just return
return baseResourceType;
}
// For all the valid ancestors, add the type to the list of types encountered
// and unvisited types
// its important that we enqueue the ancestors first, since when we populate member metadata
// we can make sure that the base type is fully populated
for (int i = ancestors.Count - 1; i >= 0; i--)
{
ResourceType entityType = new ResourceType(ancestors[i], ResourceTypeKind.EntityType, baseResourceType);
unvisitedTypes.Enqueue(entityType);
knownTypes.Add(ancestors[i], entityType);
baseResourceType = entityType;
}
return baseResourceType;
}
///
/// Populates the metadata for the properties of the given resource type
///
/// resource type whose properties metadata needs to be populated
/// list of known types
/// list of unvisited type
/// Available entity sets.
private static void BuildTypeProperties(
ResourceType parentResourceType,
IDictionary knownTypes,
Queue unvisitedTypes,
IEnumerable entitySets)
{
Debug.Assert(parentResourceType != null, "parentResourceType != null");
Debug.Assert(knownTypes != null, "knownTypes != null");
Debug.Assert(unvisitedTypes != null, "unvisitedTypes != null");
Debug.Assert(entitySets != null, "entitySets != null");
BindingFlags bindingFlags = WebUtil.PublicInstanceBindingFlags;
// For non root types, we should only look for properties that are declared for this type
if (parentResourceType.BaseType != null)
{
bindingFlags = bindingFlags | BindingFlags.DeclaredOnly;
}
HashSet propertiesToBeIgnored = new HashSet(StringComparer.Ordinal);
#if ASTORIA_OPEN_OBJECT
if (parentResourceType.IsOpenType)
{
propertiesToBeIgnored.Add(OpenTypeAttribute.GetOpenPropertyName(parentResourceType.Type));
}
#endif
foreach (string propertyName in IgnorePropertiesAttribute.GetProperties(parentResourceType.Type, false /*inherit*/, bindingFlags))
{
propertiesToBeIgnored.Add(propertyName);
}
ResourceKeyKind keyKind = (ResourceKeyKind)Int32.MaxValue;
PropertyInfo[] properties = parentResourceType.Type.GetProperties(bindingFlags);
foreach (PropertyInfo property in properties)
{
// Ignore the properties which are specified in the IgnoreProperties attribute
if (propertiesToBeIgnored.Contains(property.Name))
{
continue;
}
if (property.CanRead && property.GetIndexParameters().Length == 0)
{
ResourcePropertyKind kind = (ResourcePropertyKind)(-1);
ResourceKeyKind currentKeyKind = (ResourceKeyKind)(-1);
ResourceType resourceType;
Type resourcePropertyType = Nullable.GetUnderlyingType(property.PropertyType) ?? property.PropertyType;
ResourceContainer container = null;
bool collection = false;
if (!knownTypes.TryGetValue(resourcePropertyType, out resourceType))
{
Type collectionType = GetIEnumerableElement(property.PropertyType);
if (collectionType != null)
{
knownTypes.TryGetValue(collectionType, out resourceType);
// Even if the above method returns false, we should set the
// following variable appropriately, so that we can use them below
collection = true;
resourcePropertyType = collectionType;
}
}
if (resourceType != null)
{
#region Already Known Type
if (resourceType.ResourceTypeKind == ResourceTypeKind.Primitive)
{
// Check for key property only on root types, since keys must be defined on the root types
if (parentResourceType.BaseType == null && parentResourceType.ResourceTypeKind == ResourceTypeKind.EntityType && IsPropertyKeyProperty(property, out currentKeyKind))
{
if ((int)currentKeyKind < (int)keyKind)
{
if (parentResourceType.KeyProperties != null)
{
// Remove the existing property as key property - mark it as non key property
parentResourceType.RemoveKeyProperties();
}
keyKind = currentKeyKind;
kind = ResourcePropertyKind.Key | ResourcePropertyKind.Primitive;
}
else if ((int)currentKeyKind == (int)keyKind)
{
Debug.Assert(currentKeyKind == ResourceKeyKind.AttributedKey, "This is the only way of specifying composite keys");
kind = ResourcePropertyKind.Key | ResourcePropertyKind.Primitive;
}
else
{
kind = ResourcePropertyKind.Primitive;
}
}
else
{
kind = ResourcePropertyKind.Primitive;
}
}
else if (resourceType.ResourceTypeKind == ResourceTypeKind.ComplexType)
{
kind = ResourcePropertyKind.ComplexType;
}
else if (resourceType.ResourceTypeKind == ResourceTypeKind.EntityType)
{
kind = collection ? ResourcePropertyKind.ResourceSetReference : ResourcePropertyKind.ResourceReference;
}
#endregion // Already Known Type
}
else
{
resourceType = IsEntityOrComplexType(resourcePropertyType, knownTypes, unvisitedTypes);
if (resourceType != null)
{
if (resourceType.ResourceTypeKind == ResourceTypeKind.ComplexType)
{
kind = ResourcePropertyKind.ComplexType;
}
else
{
Debug.Assert(resourceType.ResourceTypeKind == ResourceTypeKind.EntityType, "Must be an entity type");
kind = collection ? ResourcePropertyKind.ResourceSetReference : ResourcePropertyKind.ResourceReference;
}
}
}
// if resource type is null OR
// if resource type is a collection of primitive or complex types OR
// if complex type has a property of entity type
if (resourceType == null ||
(resourceType.ResourceTypeKind != ResourceTypeKind.EntityType && collection) ||
(resourceType.ResourceTypeKind == ResourceTypeKind.EntityType && parentResourceType.ResourceTypeKind == ResourceTypeKind.ComplexType))
{
if (resourceType == null)
{
throw new InvalidOperationException(Strings.ReflectionProvider_InvalidProperty(property.Name, parentResourceType.FullName));
}
else
{
// collection of complex types not supported
throw new InvalidOperationException(Strings.ReflectionProvider_CollectionOfPrimitiveOrComplexNotSupported(property.Name, parentResourceType.FullName));
}
}
if (resourceType.ResourceTypeKind == ResourceTypeKind.EntityType)
{
container = InternalGetContainerForResourceType(resourcePropertyType, entitySets);
Debug.Assert(container != null, "container != null");
}
ResourceProperty resourceProperty = new ResourceProperty(property, kind, MimeTypeAttribute.GetMemberMimeType(property), resourceType, container);
parentResourceType.AddProperty(resourceProperty);
}
else
{
throw new InvalidOperationException(Strings.ReflectionProvider_InvalidProperty(property.Name, parentResourceType.FullName));
}
}
if (parentResourceType.ResourceTypeKind == ResourceTypeKind.EntityType &&
(parentResourceType.KeyProperties == null || parentResourceType.KeyProperties.Count == 0))
{
throw new InvalidOperationException(Strings.ReflectionProvider_KeyPropertiesCannotBeIgnored(parentResourceType.FullName));
}
parentResourceType.SortKeyMembers();
LoadETagProperties(parentResourceType);
}
///
/// If the given type is a entity or complex type, it returns the resource type corresponding to the given type
///
/// clr type
/// list of already known types
/// list of unvisited types
/// resource type corresponding to the given clr type, if the clr type is entity or complex
private static ResourceType IsEntityOrComplexType(Type type, IDictionary knownTypes, Queue unvisitedTypes)
{
// Ignore values types here. We do not support resources of values type (entity or complex)
if (type.IsValueType)
{
return null;
}
ResourceType resourceType = BuildHierarchyForEntityType(type, knownTypes, unvisitedTypes, false /* entityTypeCandidate */);
if (resourceType == null && IsComplexType(type))
{
resourceType = new ResourceType(type, ResourceTypeKind.ComplexType);
knownTypes.Add(type, resourceType);
unvisitedTypes.Enqueue(resourceType);
}
return resourceType;
}
/// Get the resource container for the given clr type.
/// clr type for which resource container name needs to be returned
/// Available entity sets to consider.
/// The container for its type, null if not found.
private static ResourceContainer InternalGetContainerForResourceType(Type type, IEnumerable entitySets)
{
Debug.Assert(type != null, "type != null");
Debug.Assert(entitySets != null, "entitySets != null");
// For each entity set, find out which one matches the type of this resource
foreach (ResourceContainer entitySetInfo in entitySets)
{
if (entitySetInfo.ElementType.IsAssignableFrom(type))
{
return entitySetInfo;
}
}
return null;
}
///
/// Find out all the derived types in the list of assemblies and then populate metadata for those types
///
/// list of known types
/// list of unvisited types
/// Available entity sets.
private static void PopulateMetadataForDerivedTypes(
IDictionary knownTypes, Queue unvisitedTypes, IEnumerable entitySets)
{
Debug.Assert(knownTypes != null, "knownTypes != null");
Debug.Assert(unvisitedTypes != null, "unvisitedTypes != null");
Debug.Assert(entitySets != null, "entitySets != null");
// Find all the root resource entity types
List rootTypes = new List();
foreach (ResourceType resourceType in knownTypes.Values)
{
if (resourceType.BaseType == null &&
resourceType.ResourceTypeKind == ResourceTypeKind.EntityType)
{
rootTypes.Add(resourceType);
}
}
// Use the default comparer, which calls Assembly.Equals (not a simple reference comparison).
HashSet assemblies = new HashSet(EqualityComparer.Default);
List derivedTypes = new List();
// Walk through all the types in the assemblies and find all the derived types
foreach (ResourceType resourceType in knownTypes.Values)
{
// No need to look into primitive types, as these live in system assemblies.
if (resourceType.ResourceTypeKind == ResourceTypeKind.Primitive)
{
continue;
}
Assembly assembly = resourceType.Type.Assembly;
//// ignore if the assembly has already been scanned
if (assemblies.Contains(assembly))
{
continue;
}
// Walk all the types in that assembly
foreach (Type type in assembly.GetTypes())
{
// skip all the non visible types
if (!type.IsVisible)
{
continue;
}
// Skip the type if its already loaded
if (knownTypes.ContainsKey(type))
{
continue;
}
// Check if this type dervies from any one of the root types
for (int i = 0; i < rootTypes.Count; i++)
{
if (rootTypes[i].Type.IsAssignableFrom(type))
{
derivedTypes.Add(type);
}
}
}
assemblies.Add(assembly);
}
foreach (Type type in derivedTypes)
{
BuildHierarchyForEntityType(type, knownTypes, unvisitedTypes, false /* entityTypeCandidate */);
PopulateMetadataForTypes(knownTypes, unvisitedTypes, entitySets);
}
}
///
/// Write the facets for clr types
///
/// XmlWriter in which facets needs to be written
/// property which contains the primitive type for which facets needs to be written
private static void WritePrimitivePropertyFacets(XmlWriter xmlWriter, ResourceProperty resourceProperty)
{
Debug.Assert(
resourceProperty.IsOfKind(ResourcePropertyKind.Primitive),
"property must be of primitive type");
bool nullable = true;
Type propertyType = resourceProperty.Type;
// Key properties can't be nullable and can't be generic
if (!resourceProperty.IsOfKind(ResourcePropertyKind.Key))
{
if (propertyType.IsGenericType &&
resourceProperty.Type.GetGenericTypeDefinition() == typeof(Nullable<>))
{
propertyType = Nullable.GetUnderlyingType(propertyType);
}
else if (propertyType.IsValueType)
{
nullable = false;
}
}
else
{
nullable = false;
}
xmlWriter.WriteAttributeString(XmlConstants.Nullable, nullable ? XmlConstants.XmlTrueLiteral : XmlConstants.XmlFalseLiteral);
}
///
/// Write the metadata for the entityType in the xmlWriter
///
/// xmlWriter in which metadata needs to be written
/// entityType whose metadata needs to be written
/// list of associations present in the model.
/// list of associations in the current namespace.
private static void WriteEntityType(
XmlWriter xmlWriter,
ResourceType entityType,
Dictionary associations,
Dictionary associationsInThisNamespace)
{
Debug.Assert(xmlWriter != null, "XmlWriter cannot be null");
Debug.Assert(entityType.ResourceTypeKind == ResourceTypeKind.EntityType, "Type must be entityType");
xmlWriter.WriteStartElement(XmlConstants.EntityType);
xmlWriter.WriteAttributeString(XmlConstants.Name, XmlConvert.EncodeName(entityType.Name));
if (entityType.Type.IsAbstract)
{
xmlWriter.WriteAttributeString(XmlConstants.Abstract, "true");
}
#if ASTORIA_OPEN_OBJECT
if (entityType.IsOpenType && OpenTypeAttribute.DeclaresOpenType(entityType.Type))
{
xmlWriter.WriteAttributeString(
XmlConstants.DataWebOpenTypeAttributeName,
XmlConstants.DataWebNamespace,
OpenTypeAttribute.GetOpenPropertyName(entityType.Type));
}
#endif
if (entityType.BaseType != null)
{
xmlWriter.WriteAttributeString(XmlConstants.BaseType, XmlConvert.EncodeName(entityType.BaseType.FullName));
}
else
{
xmlWriter.WriteStartElement(XmlConstants.Key);
foreach (ResourceProperty property in entityType.KeyProperties)
{
xmlWriter.WriteStartElement(XmlConstants.PropertyRef);
xmlWriter.WriteAttributeString(XmlConstants.Name, property.Name);
xmlWriter.WriteEndElement();
}
xmlWriter.WriteEndElement();
}
WriteProperties(xmlWriter, entityType, associations, associationsInThisNamespace);
xmlWriter.WriteEndElement();
}
///
/// Write the metadata for the complexType in the xmlWriter
///
/// xmlWriter in which metadata needs to be written
/// complexType whose metadata needs to be written
private static void WriteComplexType(XmlWriter xmlWriter, ResourceType complexType)
{
Debug.Assert(xmlWriter != null, "XmlWriter cannot be null");
Debug.Assert(complexType.ResourceTypeKind == ResourceTypeKind.ComplexType, "Type must be complexType");
xmlWriter.WriteStartElement(XmlConstants.ComplexType);
xmlWriter.WriteAttributeString(XmlConstants.Name, XmlConvert.EncodeName(complexType.Name));
WriteProperties(xmlWriter, complexType, null, null);
xmlWriter.WriteEndElement();
}
///
/// Write the metadata of all the properties for the given typein the xmlWriter
///
/// xmlWriter in which metadata needs to be written
/// resource type whose property metadata needs to be written
/// list of associations present in the model.
/// list of associations in the current namespace.
private static void WriteProperties(
XmlWriter xmlWriter,
ResourceType type,
Dictionary associations,
Dictionary associationsInThisNamespace)
{
Debug.Assert(xmlWriter != null, "xmlWriter != null");
Debug.Assert(type != null, "type != null");
for (int i = 0; i < type.PropertiesDeclaredInThisType.Count; i++)
{
ResourceProperty resourceProperty = type.PropertiesDeclaredInThisType[i];
string elementName;
// For primitive types, get the corresponding edm typename
if (resourceProperty.TypeKind == ResourceTypeKind.Primitive)
{
xmlWriter.WriteStartElement(XmlConstants.Property);
xmlWriter.WriteAttributeString(XmlConstants.Name, resourceProperty.Name);
elementName = WebUtil.GetEdmTypeName(resourceProperty.Type);
xmlWriter.WriteAttributeString(XmlConstants.Type, elementName);
WritePrimitivePropertyFacets(xmlWriter, resourceProperty);
if (!String.IsNullOrEmpty(resourceProperty.MimeType))
{
BaseServiceProvider.WriteDataWebMetadata(xmlWriter, XmlConstants.DataWebMimeTypeAttributeName, resourceProperty.MimeType);
}
if (type.ResourceTypeKind == ResourceTypeKind.EntityType &&
type.ETagProperties.Contains(resourceProperty))
{
xmlWriter.WriteAttributeString(XmlConstants.ConcurrencyAttribute, XmlConstants.ConcurrencyFixedValue);
}
}
else if (resourceProperty.Kind == ResourcePropertyKind.ComplexType)
{
xmlWriter.WriteStartElement(XmlConstants.Property);
xmlWriter.WriteAttributeString(XmlConstants.Name, resourceProperty.Name);
elementName = resourceProperty.ResourceType.FullName;
xmlWriter.WriteAttributeString(XmlConstants.Type, elementName);
// Edm doesn't support nullable complex type properties
xmlWriter.WriteAttributeString(XmlConstants.Nullable, XmlConstants.XmlFalseLiteral);
}
else
{
Debug.Assert(
ResourceTypeKind.EntityType == resourceProperty.TypeKind,
"Unexpected property type kind");
bool multivalued = false;
xmlWriter.WriteStartElement(XmlConstants.NavigationProperty);
xmlWriter.WriteAttributeString(XmlConstants.Name, resourceProperty.Name);
elementName = resourceProperty.ResourceType.FullName;
if (resourceProperty.Kind == ResourcePropertyKind.ResourceSetReference)
{
multivalued = true;
}
// for every nav property, add a new association to the metadata
// the name of the assocation will be _
// there might be conflicts since there can be 2 types with the same name, but in different namespace.
// in that case, we will use the full type name (replace '.' by '_'). That should make it unique.
string associationName = type.Name + '_' + resourceProperty.Name;
if (associations.ContainsKey(associationName))
{
associationName = type.FullName.Replace('.', '_') + '_' + resourceProperty.Name;
}
AssociationInfo association = new AssociationInfo(
associationName,
type.Type.Namespace,
new EndInfo(resourceProperty.Name, resourceProperty.ResourceType, multivalued ? XmlConstants.Many : XmlConstants.ZeroOrOne),
new EndInfo(type.Name, type, XmlConstants.Many));
// Add it to both the global list and the current namespace list.
associationsInThisNamespace.Add(associationName, association);
associations.Add(associationName, association);
xmlWriter.WriteAttributeString(XmlConstants.Relationship, association.FullName);
xmlWriter.WriteAttributeString(XmlConstants.FromRole, type.Name);
xmlWriter.WriteAttributeString(XmlConstants.ToRole, resourceProperty.Name);
#if ASTORIA_CONTAINMENT
if (resourceProperty.ContainmentTarget != null)
{
string path = resourceProperty.ContainmentTargetCanonical ?
XmlConstants.DataWebAccessPathCanonicalValue :
XmlConstants.DataWebAccessPathNonCanonicalValue;
BaseServiceProvider.WriteDataWebMetadata(xmlWriter, XmlConstants.DataWebAccessPathAttribute, path);
BaseServiceProvider.WriteDataWebMetadata(
xmlWriter, XmlConstants.DataWebAccessPathTargetAttribute, resourceProperty.ContainmentTarget.Name);
BaseServiceProvider.WriteDataWebMetadata(
xmlWriter, XmlConstants.DataWebAccessPathChildKeysAttribute, string.Join(",", resourceProperty.ContainmentChildKeys));
BaseServiceProvider.WriteDataWebMetadata(
xmlWriter, XmlConstants.DataWebAccessPathParentKeysAttribute, string.Join(",", resourceProperty.ContainmentParentKeys));
}
#endif
}
xmlWriter.WriteEndElement();
}
}
///
/// Writes the definition of types in the XmlWriter
///
/// xmlWriter in which metadata needs to be written
/// resourceTypes whose metadata needs to be written
/// list of associations present in the model.
/// list of associations in the current namespace.
private static void WriteTypes(
XmlWriter xmlWriter,
List types,
Dictionary associations,
Dictionary associationsInThisNamespace)
{
foreach (ResourceType type in types)
{
if (ResourceTypeKind.EntityType == type.ResourceTypeKind)
{
WriteEntityType(xmlWriter, type, associations, associationsInThisNamespace);
}
else
{
Debug.Assert(ResourceTypeKind.ComplexType == type.ResourceTypeKind, "this must be a complex type");
WriteComplexType(xmlWriter, type);
}
}
}
///
/// Loads the etag properties for the given resource type
///
/// resource type whose etag property names need to be loaded.
private static void LoadETagProperties(ResourceType resourceType)
{
// if it is the root type, then we need to inherit the attribute from the base type.
// otherwise, we need not, since if the base type already has it, the appropriate properties
// must already have been marked as concurrency properties.
bool inherit = resourceType.BaseType == null;
// Read the etag attribute from the type and return it
ETagAttribute[] attributes = (ETagAttribute[])resourceType.Type.GetCustomAttributes(typeof(ETagAttribute), inherit);
Debug.Assert(attributes.Length <= 1, "Only one attribute can be specified per type");
if (attributes.Length == 1)
{
// Validate the property names
// we may need to cache them instead of reading them everytime
foreach (string propertyName in attributes[0].PropertyNames)
{
ResourceProperty property = resourceType.TryResolvePropertyNameDefinedInThisType(propertyName);
if (property == null)
{
throw new InvalidOperationException(Strings.ETagAttribute_PropertyNameNotValid(propertyName, resourceType.FullName));
}
if (property.TypeKind != ResourceTypeKind.Primitive)
{
throw new InvalidOperationException(Strings.ETagAttribute_ETagPropertiesMustBeOfPrimitiveType(propertyName, resourceType.FullName));
}
// Ignore key properties if they are part of etag, since the uri already has the key information
// and it makes no sense to duplicate them in etag
if (property.IsOfKind(ResourcePropertyKind.Key))
{
throw new InvalidOperationException(Strings.ETagAttribute_KeyPropertiesCannotBePartOfETag(propertyName, resourceType.FullName));
}
resourceType.AddETagProperty(property);
}
}
}
///
/// Writes the metadata for the given associations.
///
/// xmlWriter in which metadata needs to be written.
/// associations whose metadata need to be written.
private static void WriteAssociations(XmlWriter xmlWriter, IEnumerable associations)
{
foreach (AssociationInfo association in associations)
{
xmlWriter.WriteStartElement(XmlConstants.Association);
xmlWriter.WriteAttributeString(XmlConstants.Name, association.Name);
foreach (EndInfo endInfo in association.Ends)
{
xmlWriter.WriteStartElement(XmlConstants.End);
xmlWriter.WriteAttributeString(XmlConstants.Role, endInfo.Name);
// The ends are of reference type
xmlWriter.WriteAttributeString(XmlConstants.Type, endInfo.Type.FullName);
// Write the multiplicity value
xmlWriter.WriteAttributeString(XmlConstants.Multiplicity, endInfo.Multiplicity);
xmlWriter.WriteEndElement();
}
xmlWriter.WriteEndElement();
}
}
///
/// Writes the metadata for association sets for the given associations.
///
/// xmlWriter in which metadata needs to be written
/// associations for which association sets needs to be written.
private void WriteAssociationSets(XmlWriter xmlWriter, IEnumerable associations)
{
foreach (AssociationInfo association in associations)
{
xmlWriter.WriteStartElement(XmlConstants.AssociationSet);
xmlWriter.WriteAttributeString(XmlConstants.Name, association.Name);
xmlWriter.WriteAttributeString(XmlConstants.Association, association.FullName);
foreach (EndInfo endInfo in association.Ends)
{
xmlWriter.WriteStartElement(XmlConstants.End);
xmlWriter.WriteAttributeString(XmlConstants.Role, endInfo.Name);
// The ends are of reference type
ResourceContainer container = this.GetContainerForResourceType(endInfo.Type.Type);
xmlWriter.WriteAttributeString(XmlConstants.EntitySet, container.Name);
xmlWriter.WriteEndElement();
}
xmlWriter.WriteEndElement();
}
}
///
/// Writes the entity container definition
///
/// xmlWriter into which metadata is written
/// name of the entity container
/// list of entity set name and containing element type name
/// associations for which association sets metadata needs to be written.
private void WriteEntityContainer(
XmlWriter xmlWriter,
string entityContainerName,
IEnumerable> entitySets,
Dictionary associations)
{
xmlWriter.WriteStartElement(XmlConstants.EntityContainer);
xmlWriter.WriteAttributeString(XmlConstants.Name, entityContainerName);
// Since reflection based provider supports only one entity container, we should write the
// default entity container attribute for the only entity container
BaseServiceProvider.WriteDataWebMetadata(xmlWriter, XmlConstants.IsDefaultEntityContainerAttribute, XmlConstants.XmlTrueLiteral);
foreach (KeyValuePair entitySet in entitySets)
{
ResourceContainer container = entitySet.Value;
xmlWriter.WriteStartElement(XmlConstants.EntitySet);
xmlWriter.WriteAttributeString(XmlConstants.Name, entitySet.Key);
xmlWriter.WriteAttributeString(XmlConstants.EntityType, container.ResourceType.FullName);
#if ASTORIA_CONTAINMENT
if (container.ContainmentCanonicalParent != null)
{
xmlWriter.WriteAttributeString(
XmlConstants.DataWebAccessTopLevelAccessAttribute,
XmlConstants.DataWebMetadataNamespace,
XmlConvert.ToString(container.TopLevelAccess));
}
#endif
xmlWriter.WriteEndElement();
}
this.WriteAssociationSets(xmlWriter, associations.Values);
this.WriteServiceOperations(xmlWriter, this.ServiceOperations);
xmlWriter.WriteEndElement();
}
///
/// Stores information about an end of an association.
///
private struct EndInfo
{
/// Name of the relationship end
internal readonly string Name;
/// Type of the relationship end.
internal readonly ResourceType Type;
/// Mulitplicity of the relationship end
internal readonly string Multiplicity;
///
/// Creates a new instance of EndInfo.
///
/// name of the end.
/// resource type that the end refers to.
/// multiplicity of the end.
internal EndInfo(string name, ResourceType type, string multiplicity)
{
this.Name = name;
this.Type = type;
this.Multiplicity = multiplicity;
}
}
///
/// Stores information about a association and its ends
///
private class AssociationInfo
{
/// FullName of the association.
internal readonly string FullName;
/// Name of the association
internal readonly string Name;
/// collection of ends for this association.
internal readonly EndInfo[] Ends;
///
/// Creates a new instance of AssociationInfo to store information about an association.
///
/// name of the association.
/// namespaceName of the association.
/// first end of the association.
/// second end of the association.
internal AssociationInfo(string name, string namespaceName, EndInfo end1, EndInfo end2)
{
this.Name = name;
this.FullName = namespaceName + "." + name;
this.Ends = new EndInfo[] { end1, end2 };
}
}
///
/// Finds all the interesting types and their members given the top level type.
///
private class TypeManager
{
/// List of namespace along with the types in that namespace
private Dictionary> namespaceManager = new Dictionary>(StringComparer.Ordinal);
///
/// Constructs a new instance of TypeManager using the knownTypes
///
/// list of known resource types
internal TypeManager(IEnumerable knownTypes)
{
foreach (ResourceType resourceType in knownTypes)
{
// Ignore Primitive types
if (resourceType.ResourceTypeKind == ResourceTypeKind.Primitive)
{
continue;
}
List typesInSameNamespace;
string typeNamespace = WebUtil.GetModelTypeNamespace(resourceType.Type);
if (!this.namespaceManager.TryGetValue(typeNamespace, out typesInSameNamespace))
{
typesInSameNamespace = new List();
this.namespaceManager.Add(typeNamespace, typesInSameNamespace);
}
// Add the type to the list of types in the same namespace
typesInSameNamespace.Add(resourceType);
}
}
///
/// Returns the list of namespace and the types in those namespaces
///
internal IEnumerable>> NamespaceAlongWithTypes
{
get
{
return this.namespaceManager;
}
}
}
}
}
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.