private string ConvertToEscapedUriValue(string paramName, object value) { Debug.Assert(!string.IsNullOrEmpty(paramName), "!string.IsNullOrEmpty(paramName)"); Object valueInODataFormat = null; // Literal values with single quotes need special escaping due to System.Uri changes in behavior between .NET 4.0 and 4.5. // We need to ensure that our escaped values do not change between those versions, so we need to escape values differently when they could contain single quotes. bool needsSpecialEscaping = false; if (value == null) { needsSpecialEscaping = true; } else { if (value is ODataNullValue) { valueInODataFormat = value; needsSpecialEscaping = true; } else { ClientEdmModel model = this.requestInfo.Model; IEdmType edmType = model.GetOrCreateEdmType(value.GetType()); Debug.Assert(edmType != null, "edmType != null"); switch (edmType.TypeKind) { case EdmTypeKind.Primitive: valueInODataFormat = value; needsSpecialEscaping = true; break; case EdmTypeKind.Enum: { ClientTypeAnnotation typeAnnotation = model.GetClientTypeAnnotation(edmType); string typeNameInEdm = this.requestInfo.GetServerTypeName(model.GetClientTypeAnnotation(edmType)); MemberInfo member = typeAnnotation.ElementType.GetMember(value.ToString()).FirstOrDefault(); if (member == null) { throw new NotSupportedException(Strings.Serializer_InvalidEnumMemberValue(typeAnnotation.ElementType.Name, value.ToString())); } string memberValue = ClientTypeUtil.GetServerDefinedName(member); valueInODataFormat = new ODataEnumValue(memberValue, typeNameInEdm ?? typeAnnotation.ElementTypeName); needsSpecialEscaping = true; } break; case EdmTypeKind.Complex: { ClientTypeAnnotation typeAnnotation = model.GetClientTypeAnnotation(edmType); Debug.Assert(typeAnnotation != null, "typeAnnotation != null"); valueInODataFormat = this.propertyConverter.CreateODataComplexValue(typeAnnotation.ElementType, value, null /*propertyName*/, false /*isCollectionItemType*/, null /*visitedComplexTypeObjects*/); // When using JsonVerbose to format query string parameters for Actions, // we cannot write out Complex values in the URI without the type name of the complex type in the JSON payload. // If this value is null, the client has to set the ResolveName property on the DataServiceContext instance. ODataComplexValue complexValue = (ODataComplexValue)valueInODataFormat; SerializationTypeNameAnnotation serializedTypeNameAnnotation = complexValue.GetAnnotation <SerializationTypeNameAnnotation>(); if (serializedTypeNameAnnotation == null || string.IsNullOrEmpty(serializedTypeNameAnnotation.TypeName)) { throw Error.InvalidOperation(Strings.DataServiceException_GeneralError); } } break; case EdmTypeKind.Collection: IEdmCollectionType edmCollectionType = edmType as IEdmCollectionType; Debug.Assert(edmCollectionType != null, "edmCollectionType != null"); IEdmTypeReference itemTypeReference = edmCollectionType.ElementType; Debug.Assert(itemTypeReference != null, "itemTypeReference != null"); ClientTypeAnnotation itemTypeAnnotation = model.GetClientTypeAnnotation(itemTypeReference.Definition); Debug.Assert(itemTypeAnnotation != null, "itemTypeAnnotation != null"); switch (itemTypeAnnotation.EdmType.TypeKind) { // We only support primitive, Enum or complex type as a collection item type. case EdmTypeKind.Primitive: case EdmTypeKind.Enum: case EdmTypeKind.Complex: break; default: throw new NotSupportedException(Strings.Serializer_InvalidCollectionParamterItemType(paramName, itemTypeAnnotation.EdmType.TypeKind)); } valueInODataFormat = this.propertyConverter.CreateODataCollection( itemTypeAnnotation.ElementType, null /*propertyName*/, value, null /*visitedComplexTypeObjects*/); break; default: // EdmTypeKind.Entity // EdmTypeKind.Row // EdmTypeKind.EntityReference throw new NotSupportedException(Strings.Serializer_InvalidParameterType(paramName, edmType.TypeKind)); } } Debug.Assert(valueInODataFormat != null, "valueInODataFormat != null"); } // When calling Execute() to invoke an Action, the client doesn't support parsing the target url // to determine which IEdmOperationImport to pass to the ODL writer. So the ODL writer is // serializing the parameter payload without metadata. Setting the model to null so ODL doesn't // do unecessary validations when writing without metadata. string literal = ODataUriUtils.ConvertToUriLiteral(valueInODataFormat, CommonUtil.ConvertToODataVersion(this.requestInfo.MaxProtocolVersionAsVersion), null /* edmModel */); // The value from ConvertToUriValue will not be escaped, but will already contain literal delimiters like single quotes, so we // need to use our own escape method that will preserve those characters instead of directly calling Uri.EscapeDataString that may escape them. // This is only necessary for primitives and nulls because the other structures are serialized using the JSON format and it uses double quotes // which have always been escaped. if (needsSpecialEscaping) { return(DataStringEscapeBuilder.EscapeDataString(literal)); } return(Uri.EscapeDataString(literal)); }
/// <summary> /// Gets vocabulary annotation that binds to a term and a qualifier from the metadata annotation dictionary in current data service context for a specified propertyInfo. /// </summary> /// <param name="context">The data service context.</param> /// <param name="propertyInfo">The specified annotated propertyInfo.</param> /// <param name="term">The term name.</param> /// <param name="qualifier">The qualifier name.</param> /// <returns>The vacabulary annotation that binds to a term and a qualifier for the specified annotated propertyInfo.</returns> private static IEdmValueAnnotation GetOrInsertCachedMetadataAnnotationForPropertyInfo(DataServiceContext context, PropertyInfo propertyInfo, string term, string qualifier) { var serviceModel = context.Format.ServiceModel; if (serviceModel == null) { return(null); } IEdmValueAnnotation edmValueAnnotation = GetCachedMetadataAnnotation(context, propertyInfo, term, qualifier); if (edmValueAnnotation != null) { return(edmValueAnnotation); } var severSidePropertyName = ClientTypeUtil.GetServerDefinedName(propertyInfo); if (string.IsNullOrEmpty(severSidePropertyName)) { return(null); } var declaringType = propertyInfo.DeclaringType; IEnumerable <IEdmValueAnnotation> edmValueAnnotations = null; if (declaringType.IsSubclassOf(typeof(DataServiceContext))) { var entityContainer = serviceModel.EntityContainer; var edmEntityContainerElements = entityContainer.Elements.Where(e => e.Name == severSidePropertyName); if (edmEntityContainerElements != null && edmEntityContainerElements.Count() == 1) { edmValueAnnotations = serviceModel.FindVocabularyAnnotations <IEdmValueAnnotation>( edmEntityContainerElements.Single(), term, qualifier).Where(a => a.Qualifier == qualifier); } } else { var serversideTypeName = context.ResolveName == null ? declaringType.FullName : context.ResolveName(declaringType); var edmType = serviceModel.FindDeclaredType(serversideTypeName); if (edmType != null) { var edmStructuredType = edmType as IEdmStructuredType; if (edmStructuredType != null) { var edmProperty = edmStructuredType.FindProperty(severSidePropertyName); if (edmProperty != null) { edmValueAnnotations = serviceModel.FindVocabularyAnnotations <IEdmValueAnnotation>( edmProperty, term, qualifier).Where(a => a.Qualifier == qualifier); } } } } if (edmValueAnnotations != null && edmValueAnnotations.Count() == 1) { edmValueAnnotation = edmValueAnnotations.Single(); InsertMetadataAnnotation(context, propertyInfo, edmValueAnnotation); return(edmValueAnnotation); } return(null); }
/// <summary> /// QueryableResourceExpression visit method. /// </summary> /// <param name="rse">QueryableResourceExpression expression to visit</param> /// <returns>Visited QueryableResourceExpression expression</returns> internal override Expression VisitQueryableResourceExpression(QueryableResourceExpression rse) { if ((ResourceExpressionType)rse.NodeType == ResourceExpressionType.ResourceNavigationProperty) { if (rse.IsOperationInvocation && !(rse.Source is QueryableResourceExpression)) { var normalizerRewrites = new Dictionary <Expression, Expression>(ReferenceEqualityComparer <Expression> .Instance); var e = Evaluator.PartialEval(rse.Source); e = ExpressionNormalizer.Normalize(e, normalizerRewrites); e = ResourceBinder.Bind(e, this.context); this.Visit(e); } else { this.Visit(rse.Source); } this.uriBuilder.Append(UriHelper.FORWARDSLASH).Append(this.ExpressionToString(rse.MemberExpression, /*inPath*/ true)); } else if (rse.MemberExpression != null) { // this is a resource set expression // we should be at the very beginning of // the URI Debug.Assert(this.uriBuilder.Length == 0, "The builder is not empty while we are adding a resourceset"); string entitySetName = (String)((ConstantExpression)rse.MemberExpression).Value; this.uriBuilder.Append(this.context.BaseUriResolver.GetEntitySetUri(entitySetName)); } else { this.uriBuilder.Append(this.context.BaseUriResolver.BaseUriOrNull); } WebUtil.RaiseVersion(ref this.uriVersion, rse.UriVersion); if (rse.ResourceTypeAs != null) { this.uriBuilder.Append(UriHelper.FORWARDSLASH); UriHelper.AppendTypeSegment(this.uriBuilder, rse.ResourceTypeAs, this.context, /*inPath*/ true, ref this.uriVersion); } if (rse.KeyPredicateConjuncts.Count > 0) { this.context.UrlKeyDelimiter.AppendKeyExpression(rse.GetKeyProperties(), kvp => ClientTypeUtil.GetServerDefinedName(kvp.Key), kvp => kvp.Value.Value, this.uriBuilder); } if (rse.IsOperationInvocation) { this.VisitOperationInvocation(rse); } if (rse.CountOption == CountOption.CountSegment) { // append $count segment: /$count this.uriBuilder.Append(UriHelper.FORWARDSLASH).Append(UriHelper.DOLLARSIGN).Append(UriHelper.COUNT); } this.VisitQueryOptions(rse); return(rse); }
/// <summary> /// Get Edm operation according to the MethodInfo from current data service context. /// </summary> /// <param name="context">The data service context.</param> /// <param name="methodInfo">The specified MethodInfo</param> /// <returns>The related <see cref="IEdmOperation"/> will be returned if it is found, or return null.</returns> internal static IEdmOperation GetEdmOperation(DataServiceContext context, MethodInfo methodInfo) { var serviceModel = context.Format.ServiceModel; if (serviceModel == null) { return(null); } var parameterTypes = methodInfo.GetParameters().Select(p => p.ParameterType).ToArray(); Type bindingType = null; IEnumerable <Type> clientParameters; if (methodInfo.IsDefined(typeof(ExtensionAttribute), false)) { bindingType = parameterTypes.First(); clientParameters = parameterTypes.Skip(1); } else { bindingType = methodInfo.DeclaringType; clientParameters = parameterTypes; } var declaringType = methodInfo.DeclaringType; string methodInfoNameSpacePrefix = declaringType.Namespace + "."; if (context.ResolveName != null) { string serverSideDeclaringTypeName = context.ResolveName(declaringType); if (serverSideDeclaringTypeName != null) { int index = serverSideDeclaringTypeName.LastIndexOf('.'); methodInfoNameSpacePrefix = index > 0 ? serverSideDeclaringTypeName.Substring(0, index + 1) : ""; } } var serverSideMethodName = ClientTypeUtil.GetServerDefinedName(methodInfo); var operations = serviceModel.FindOperations(methodInfoNameSpacePrefix + serverSideMethodName).Where(o => o.IsBound); while (bindingType != null) { foreach (var operation in operations) { Type bindingTypeFromTypeReference; if (TryGetClrTypeFromEdmTypeReference( context, operation.Parameters.First().Type, methodInfo.IsDefined(typeof(ExtensionAttribute), false), out bindingTypeFromTypeReference) && bindingTypeFromTypeReference == bindingType && clientParameters.SequenceEqual(GetNonBindingParameterTypeArray(context, operation.Parameters, true))) { return(operation); } } if (methodInfo.IsDefined(typeof(ExtensionAttribute), false) && bindingType.IsGenericType()) { var genericTypeDefinition = bindingType.GetGenericTypeDefinition(); var genericArguments = bindingType.GetGenericArguments().ToList(); if (genericArguments.Count == 1) { var genericArgumentBaseType = genericArguments[0].GetBaseType(); if (genericArgumentBaseType != null) { bindingType = genericTypeDefinition.MakeGenericType(genericArgumentBaseType); continue; } } } return(null); } return(null); }
/// <summary> /// MethodCallExpression visit method /// </summary> /// <param name="m">The MethodCallExpression expression to visit</param> /// <returns>The visited MethodCallExpression expression </returns> internal override Expression VisitMethodCall(MethodCallExpression m) { string methodName; if (TypeSystem.TryGetQueryOptionMethod(m.Method, out methodName)) { this.builder.Append(methodName); this.builder.Append(UriHelper.LEFTPAREN); // There is a single function, 'contains', which reorders its argument with // respect to the CLR method. Thus handling it as a special case rather than // using a more general argument reordering mechanism. if (methodName == "contains") { Debug.Assert(m.Method.Name == "Contains", "m.Method.Name == 'Contains'"); Debug.Assert(m.Object != null, "m.Object != null"); Debug.Assert(m.Arguments.Count == 1, "m.Arguments.Count == 1"); this.Visit(m.Object); this.builder.Append(UriHelper.COMMA); this.Visit(m.Arguments[0]); } else { if (m.Object != null) { this.Visit(m.Object); } if (m.Arguments.Count > 0) { if (m.Object != null) { this.builder.Append(UriHelper.COMMA); } for (int ii = 0; ii < m.Arguments.Count; ii++) { this.Visit(m.Arguments[ii]); if (ii < m.Arguments.Count - 1) { this.builder.Append(UriHelper.COMMA); } } } } this.builder.Append(UriHelper.RIGHTPAREN); } else if (m.Method.Name == "HasFlag") { Debug.Assert(m.Method.Name == "HasFlag", "m.Method.Name == 'HasFlag'"); Debug.Assert(m.Object != null, "m.Object != null"); Debug.Assert(m.Arguments.Count == 1, "m.Arguments.Count == 1"); this.Visit(m.Object); this.builder.Append(UriHelper.SPACE); this.builder.Append(UriHelper.HAS); this.builder.Append(UriHelper.SPACE); this.Visit(m.Arguments[0]); } else { SequenceMethod sequenceMethod; if (ReflectionUtil.TryIdentifySequenceMethod(m.Method, out sequenceMethod)) { if (ReflectionUtil.IsAnyAllMethod(sequenceMethod)) { // Raise the uriVersion each time we write any or all methods to the uri. WebUtil.RaiseVersion(ref this.uriVersion, Util.ODataVersion4); this.Visit(m.Arguments[0]); this.builder.Append(UriHelper.FORWARDSLASH); if (sequenceMethod == SequenceMethod.All) { this.builder.Append(XmlConstants.AllMethodName); } else { this.builder.Append(XmlConstants.AnyMethodName); } this.builder.Append(UriHelper.LEFTPAREN); if (sequenceMethod != SequenceMethod.Any) { // SequenceMethod.Any represents Enumerable.Any(), which has only source argument // AnyPredicate and All has a second parameter which is the predicate lambda. Debug.Assert(m.Arguments.Count == 2, "m.Arguments.Count() == 2"); LambdaExpression le = (LambdaExpression)m.Arguments[1]; string rangeVariable = le.Parameters[0].Name; this.builder.Append(rangeVariable); this.builder.Append(UriHelper.COLON); this.scopeCount++; this.Visit(le.Body); this.scopeCount--; } this.builder.Append(UriHelper.RIGHTPAREN); return(m); } else if (sequenceMethod == SequenceMethod.OfType && this.parent != null) { // check to see if this is an OfType filter for Any or All. // e.g. ctx.CreateQuery<Movie>("Movies").Where(m=>m.Actors.OfType<MegaStar>().Any()) // which translates to /Movies()?$filter=Actors/MegaStar/any() MethodCallExpression mce = this.parent as MethodCallExpression; if (mce != null && ReflectionUtil.TryIdentifySequenceMethod(mce.Method, out sequenceMethod) && ReflectionUtil.IsAnyAllMethod(sequenceMethod)) { Type filteredType = mce.Method.GetGenericArguments().SingleOrDefault(); if (ClientTypeUtil.TypeOrElementTypeIsEntity(filteredType)) { this.Visit(m.Arguments[0]); this.builder.Append(UriHelper.FORWARDSLASH); UriHelper.AppendTypeSegment(this.builder, filteredType, this.context, this.inPath, ref this.uriVersion); return(m); } } } else if (sequenceMethod == SequenceMethod.Count && this.parent != null) { if (m.Arguments.Any() && m.Arguments[0] != null) { this.Visit(m.Arguments[0]); } this.builder.Append(UriHelper.FORWARDSLASH).Append(UriHelper.DOLLARSIGN).Append(UriHelper.COUNT); return(m); } } else { if (m.Object != null) { this.Visit(m.Object); } if (m.Method.Name != "GetValue" && m.Method.Name != "GetValueAsync") { this.builder.Append(UriHelper.FORWARDSLASH); // writing functions in query options writingFunctionsInQuery = true; string declaringType = this.context.ResolveNameFromTypeInternal(m.Method.DeclaringType); if (string.IsNullOrEmpty(declaringType)) { throw new NotSupportedException(Strings.ALinq_CantTranslateExpression(m.ToString())); } int index = declaringType.LastIndexOf('.'); string fullNamespace = declaringType.Remove(index + 1); string serverMethodName = ClientTypeUtil.GetServerDefinedName(m.Method); this.builder.Append(fullNamespace + serverMethodName); this.builder.Append(UriHelper.LEFTPAREN); string[] argumentNames = m.Method.GetParameters().Select(p => p.Name).ToArray(); for (int i = 0; i < m.Arguments.Count; ++i) { this.builder.Append(argumentNames[i]); this.builder.Append(UriHelper.EQUALSSIGN); this.scopeCount++; this.Visit(m.Arguments[i]); this.scopeCount--; this.builder.Append(UriHelper.COMMA); } if (m.Arguments.Any()) { this.builder.Remove(this.builder.Length - 1, 1); } this.builder.Append(UriHelper.RIGHTPAREN); writingFunctionsInQuery = false; } return(m); } this.cantTranslateExpression = true; } return(m); }
/// <summary> /// ConstantExpression visit method /// </summary> /// <param name="c">The ConstantExpression expression to visit</param> /// <returns>The visited ConstantExpression expression </returns> internal override Expression VisitConstant(ConstantExpression c) { if (c.Value == null) { this.builder.Append(UriHelper.NULL); return(c); } // DEVNOTE: // Rather than forcing every other codepath to have the 'Try...' pattern for formatting, // we catch the InvalidOperationException here to change the exception type. // This is exceedingly rare, and not a scenario where performance is meaningful, so the // reduced complexity in all other call sites is worth the extra logic here. string result; BinaryExpression b = this.parent as BinaryExpression; MethodCallExpression m = this.parent as MethodCallExpression; if ((b != null && HasEnumInBinaryExpression(b)) || (m != null && m.Method.Name == "HasFlag")) { c = this.ConvertConstantExpressionForEnum(c); ClientEdmModel model = this.context.Model; IEdmType edmType = model.GetOrCreateEdmType(c.Type.IsEnum() ? c.Type : c.Type.GetGenericArguments()[0]); ClientTypeAnnotation typeAnnotation = model.GetClientTypeAnnotation(edmType); string typeNameInEdm = this.context.ResolveNameFromTypeInternal(typeAnnotation.ElementType); MemberInfo member = typeAnnotation.ElementType.GetField(c.Value.ToString()); string memberValue = ClientTypeUtil.GetServerDefinedName(member); ODataEnumValue enumValue = new ODataEnumValue(memberValue, typeNameInEdm ?? typeAnnotation.ElementTypeName); result = ODataUriUtils.ConvertToUriLiteral(enumValue, CommonUtil.ConvertToODataVersion(this.uriVersion), null); } else if (m != null && ReflectionUtil.IsSequenceMethod(m.Method, SequenceMethod.Contains)) { StringBuilder listExpr = new StringBuilder(); ODataVersion version = CommonUtil.ConvertToODataVersion(this.uriVersion); foreach (object item in (IEnumerable)c.Value) { if (listExpr.Length != 0) { listExpr.Append(UriHelper.COMMA); } string uriLiteral = ODataUriUtils.ConvertToUriLiteral(item, version); listExpr.Append(uriLiteral); } // Contains cannot be used with an empty static collection if (listExpr.Length == 0) { throw new InvalidOperationException(Strings.ALinq_ContainsNotValidOnEmptyCollection); } listExpr.Insert(0, UriHelper.LEFTPAREN); listExpr.Append(UriHelper.RIGHTPAREN); result = listExpr.ToString(); } else { try { result = LiteralFormatter.ForConstants.Format(c.Value); } catch (InvalidOperationException) { if (this.cantTranslateExpression) { // there's already a problem in the parents. // we should just return here, because caller somewhere up the stack will throw a better exception return(c); } throw new NotSupportedException(Strings.ALinq_CouldNotConvert(c.Value)); } } Debug.Assert(result != null, "result != null"); this.builder.Append(result); return(c); }
/// <summary> /// Creates and returns an ODataCollectionValue from the given value. /// </summary> /// <param name="collectionItemType">The type of the value.</param> /// <param name="propertyName">If the value is a property, then it represents the name of the property. Can be null, for non-property.</param> /// <param name="value">The value.</param> /// <param name="visitedComplexTypeObjects">Set of instances of complex types encountered in the hierarchy. Used to detect cycles.</param> /// <returns>An ODataCollectionValue representing the given value.</returns> internal ODataCollectionValue CreateODataCollection(Type collectionItemType, string propertyName, object value, HashSet <object> visitedComplexTypeObjects) { Debug.Assert(collectionItemType != null, "collectionItemType != null"); WebUtil.ValidateCollection(collectionItemType, value, propertyName); PrimitiveType ptype; bool isCollectionOfPrimitiveTypes = PrimitiveType.TryGetPrimitiveType(collectionItemType, out ptype); ODataCollectionValue collection = new ODataCollectionValue(); IEnumerable enumerablePropertyValue = (IEnumerable)value; string collectionItemTypeName; string collectionTypeName; if (isCollectionOfPrimitiveTypes) { collectionItemTypeName = ClientConvert.GetEdmType(Nullable.GetUnderlyingType(collectionItemType) ?? collectionItemType); collection.Items = Util.GetEnumerable( enumerablePropertyValue, (val) => { WebUtil.ValidateCollectionItem(val); WebUtil.ValidatePrimitiveCollectionItem(val, propertyName, collectionItemType); return(ConvertPrimitiveValueToRecognizedODataType(val, collectionItemType)); }); // TypeName for primitives should be the EDM name since that's what we will be able to look up in the model collectionTypeName = collectionItemTypeName; } else { Type collectionItemTypeTmp = Nullable.GetUnderlyingType(collectionItemType) ?? collectionItemType; bool areEnumItems = collectionItemTypeTmp.IsEnum; // Note that the collectionItemTypeName will be null if the context does not have the ResolveName func. collectionItemTypeName = this.requestInfo.ResolveNameFromType(collectionItemType); collection.Items = Util.GetEnumerable( enumerablePropertyValue, (val) => { if (areEnumItems) { if (val == null) { return(new ODataEnumValue(null, collectionItemType.FullName) as ODataValue); } MemberInfo member = collectionItemTypeTmp.GetMember(val.ToString()).FirstOrDefault(); if (member == null) { throw new NotSupportedException(Strings.Serializer_InvalidEnumMemberValue(collectionItemType.Name, value.ToString())); } string memberValue = ClientTypeUtil.GetServerDefinedName(member); return(new ODataEnumValue(memberValue, collectionItemType.FullName) as ODataValue); } else { WebUtil.ValidateCollectionItem(val); WebUtil.ValidateComplexCollectionItem(val, propertyName, collectionItemType); return(this.CreateODataComplexValue(collectionItemType, val, propertyName, true /*isCollectionItem*/, visitedComplexTypeObjects) as ODataValue); } }); // TypeName for complex types needs to be the client type name (not the one we resolved above) since it will be looked up in the client model collectionTypeName = collectionItemType.FullName; } // Set the type name to use for client type lookups and validation. Because setting this value can cause validation to occur, we will // only do it for JSON Light, in order to avoid breaking changes with the WCF Data Services 5.0 release, since it was already shipped without this. if (!this.requestInfo.Format.UsingAtom) { collection.TypeName = GetCollectionName(collectionTypeName); } string wireTypeName = GetCollectionName(collectionItemTypeName); collection.SetAnnotation(new SerializationTypeNameAnnotation { TypeName = wireTypeName }); return(collection); }
/// <summary> /// constructor /// </summary> /// <param name="edmProperty">Back reference to the EdmProperty this annotation is part of.</param> /// <param name="propertyInfo">propertyInfo instance.</param> /// <param name="model">The client model.</param> internal ClientPropertyAnnotation(IEdmProperty edmProperty, PropertyInfo propertyInfo, ClientEdmModel model) { Debug.Assert(edmProperty != null, "edmProperty != null"); Debug.Assert(propertyInfo != null, "null propertyInfo"); // Property should always have DeclaringType Debug.Assert(propertyInfo.DeclaringType != null, "Property without a declaring type"); this.EdmProperty = edmProperty; this.PropertyName = ClientTypeUtil.GetServerDefinedName(propertyInfo); this.PropertyInfo = propertyInfo; this.NullablePropertyType = propertyInfo.PropertyType; this.PropertyType = Nullable.GetUnderlyingType(this.NullablePropertyType) ?? this.NullablePropertyType; this.DeclaringClrType = propertyInfo.DeclaringType; MethodInfo propertyGetMethod = propertyInfo.GetGetMethod(); // Add the parameter to make set method is returned even it is not public. Portable lib does not support this. #if PORTABLELIB MethodInfo propertySetMethod = propertyInfo.GetSetMethod(); #else MethodInfo propertySetMethod = propertyInfo.GetSetMethod(true); #endif ParameterExpression instance = Expression.Parameter(typeof(Object), "instance"); ParameterExpression value = Expression.Parameter(typeof(Object), "value"); // instance => (Object)(((T)instance).get_PropertyName()); <-- we need to box the value back to object to return this.propertyGetter = propertyGetMethod == null ? null : (Func <object, object>)Expression.Lambda( Expression.Convert( Expression.Call( Expression.Convert(instance, this.DeclaringClrType), propertyGetMethod), typeof(Object)), instance).Compile(); // (instance, value) => ((T)instance).set_PropertyName((T1)value); this.propertySetter = propertySetMethod == null ? null : (Action <object, object>)Expression.Lambda( Expression.Call( Expression.Convert(instance, this.DeclaringClrType), propertySetMethod, Expression.Convert(value, this.NullablePropertyType)), instance, value).Compile(); this.Model = model; this.IsKnownType = PrimitiveType.IsKnownType(this.PropertyType); // non primitive types: dictionary/collections if (!this.IsKnownType) { var setMethodInfo = ClientTypeUtil.GetMethodForGenericType(this.PropertyType, typeof(IDictionary <,>), "set_Item", out this.DictionaryValueType); if (setMethodInfo != null) { ParameterExpression propertyNameParam = Expression.Parameter(typeof(String), "propertyName"); // (instance, propertyName, value) => ((IDictionary<string, DictionaryValueType>)instance)[propertyName] = (DictionaryValueType)value; this.dictionarySetter = (Action <Object, String, Object>)Expression.Lambda( Expression.Call( Expression.Convert(instance, typeof(IDictionary <,>).MakeGenericType(typeof(String), this.DictionaryValueType)), setMethodInfo, propertyNameParam, Expression.Convert(value, this.DictionaryValueType)), instance, propertyNameParam, value).Compile(); } else { var containsMethod = ClientTypeUtil.GetMethodForGenericType(this.PropertyType, typeof(ICollection <>), "Contains", out this.collectionGenericType); var addMethod = ClientTypeUtil.GetAddToCollectionMethod(this.PropertyType, out this.collectionGenericType); var removeMethod = ClientTypeUtil.GetMethodForGenericType(this.PropertyType, typeof(ICollection <>), "Remove", out this.collectionGenericType); var clearMethod = ClientTypeUtil.GetMethodForGenericType(this.PropertyType, typeof(ICollection <>), "Clear", out this.collectionGenericType); // (instance, value) => ((PropertyType)instance).Contains((CollectionType)value); returns boolean this.collectionContains = containsMethod == null ? null : (Func <Object, Object, Boolean>)Expression.Lambda( Expression.Call( Expression.Convert(instance, this.PropertyType), containsMethod, Expression.Convert(value, this.collectionGenericType)), instance, value).Compile(); // (instance, value) => ((PropertyType)instance).Add((CollectionType)value); this.collectionAdd = addMethod == null ? null : (Action <Object, Object>)Expression.Lambda( Expression.Call( Expression.Convert(instance, this.PropertyType), addMethod, Expression.Convert(value, this.collectionGenericType)), instance, value).Compile(); // (instance, value) => ((PropertyType)instance).Remove((CollectionType)value); returns boolean this.collectionRemove = removeMethod == null ? null : (Func <Object, Object, Boolean>)Expression.Lambda( Expression.Call( Expression.Convert(instance, this.PropertyType), removeMethod, Expression.Convert(value, this.collectionGenericType)), instance, value).Compile(); // (instance) => ((PropertyType)instance).Clear(); this.collectionClear = clearMethod == null ? null : (Action <Object>)Expression.Lambda( Expression.Call( Expression.Convert(instance, this.PropertyType), clearMethod), instance).Compile(); } } Debug.Assert(this.collectionGenericType == null || this.DictionaryValueType == null, "collectionGenericType and DictionaryItemType mutually exclusive. (They both can be null though)."); }