/// <summary> /// Creates the CodeDom CodeExpression for the given value. Returns null if unable to generate a CodeExpression. /// </summary> /// <remarks>This method exists solely to help generate code for all object types that can appear in an /// attribute declaration, such as typeof()</remarks> /// <param name="proxyGenerator">The context for generating code. It cannot be null.</param> /// <param name="referencingType">The referencing type</param> /// <param name="value">The value. Null is permitted.</param> /// <returns>The code expression</returns> private static CodeExpression CreateCodeExpression(CodeDomClientCodeGenerator proxyGenerator, CodeTypeDeclaration referencingType, object value) { Type typeOfValue = value == null ? null : value.GetType(); if (value == null || typeOfValue.IsPrimitive || value is string) { CodeExpression e = new CodePrimitiveExpression(value); // Workaround CodeDom issue -- it looks like CodePrimitiveExpression is fooled and generates double // literals as integers when there is no fraction. We take a general strategy of forcing an explicit // compile time cast to ensure we recompile the same type. if (value != null && (value is double || value is float)) { e = new CodeCastExpression(value.GetType(), e); } return(e); } // typeof(T) requires special handling Type valueAsType = value as Type; if (valueAsType != null) { // Verify the type is shared // Don't know counts as not shared CodeMemberShareKind shareKind = proxyGenerator.GetTypeShareKind(valueAsType); if ((shareKind & CodeMemberShareKind.Shared) == 0) { // Here we return a fully-qualified type name to ensure we don't cause compilation // errors by adding invalid 'using' statements into our codedom graph. CodeTypeReference valueTypeReference = CodeGenUtilities.GetTypeReference(valueAsType, proxyGenerator, referencingType, false, /*Use fully qualified name*/ true); valueTypeReference.Options = CodeTypeReferenceOptions.GlobalReference; return(new CodeTypeOfExpression(valueTypeReference)); } return(new CodeTypeOfExpression(CodeGenUtilities.GetTypeReference(valueAsType, proxyGenerator, referencingType))); } // Enum values need special handling if (typeOfValue.IsEnum) { string enumValueName = Enum.GetName(typeOfValue, value); string enumTypeName; if (proxyGenerator.ClientProxyCodeGenerationOptions.UseFullTypeNames) { enumTypeName = typeOfValue.FullName; } else { enumTypeName = typeOfValue.Name; } return(new CodeFieldReferenceExpression(new CodeTypeReferenceExpression(enumTypeName), enumValueName)); } return(null); }
/// <summary> /// Verifies that a <see cref="AttributeDeclaration"/>'s shared type requirements are met. /// </summary> /// <param name="proxyGenerator">The context for code generation</param> /// <param name="attributeDeclaration">The <see cref="AttributeDeclaration"/> to verify.</param> private static void ValidateAttributeDeclarationRequirements(CodeDomClientCodeGenerator proxyGenerator, AttributeDeclaration attributeDeclaration) { // Verify the attribute itself is shared. CodeMemberShareKind shareKind = proxyGenerator.GetTypeShareKind(attributeDeclaration.AttributeType); // If there is no PDB or this type has no human-authored code, we cannot determine // whether it is shared and get a null value. This requires a special message to // explain why we treat the type as not shared. if (shareKind == CodeMemberShareKind.Unknown) { attributeDeclaration.Errors.Add( string.Format( CultureInfo.CurrentCulture, Resource.ClientCodeGen_Attribute_RequiresShared_NoPDB, attributeDeclaration.AttributeType, attributeDeclaration.AttributeType.Assembly.GetName().Name, proxyGenerator.ClientProjectName)); } else if (shareKind == CodeMemberShareKind.NotShared) { attributeDeclaration.Errors.Add( string.Format( CultureInfo.CurrentCulture, Resource.ClientCodeGen_Attribute_RequiresShared, attributeDeclaration.AttributeType, proxyGenerator.ClientProjectName)); } // Verify shared types. Here, we order by type name so that any generated errors // are presented in a consistent order. foreach (var type in attributeDeclaration.RequiredTypes.OrderBy(t => t.FullName)) { shareKind = proxyGenerator.GetTypeShareKind(type); // Missing PDB or lack of user code means we cannot know -- issue special warning if (shareKind == CodeMemberShareKind.Unknown) { attributeDeclaration.Errors.Add( string.Format( CultureInfo.CurrentCulture, Resource.ClientCodeGen_Attribute_RequiresShared_Type_NoPDB, attributeDeclaration.AttributeType, type, type.Assembly.GetName().Name, proxyGenerator.ClientProjectName)); } else if (shareKind == CodeMemberShareKind.NotShared) { attributeDeclaration.Errors.Add( string.Format( CultureInfo.CurrentCulture, Resource.ClientCodeGen_Attribute_RequiresShared_Type, attributeDeclaration.AttributeType, type, proxyGenerator.ClientProjectName)); } } // Verify shared methods. Here, we order by method name so that any generated errors // are presented in a consistent order. foreach (var method in attributeDeclaration.RequiredMethods.OrderBy(p => p.Name)) { shareKind = proxyGenerator.GetMethodShareKind(method); if (shareKind == CodeMemberShareKind.NotShared) { attributeDeclaration.Errors.Add( string.Format( CultureInfo.CurrentCulture, Resource.ClientCodeGen_Attribute_RequiresShared_Method, attributeDeclaration.AttributeType, method.Name, method.DeclaringType, proxyGenerator.ClientProjectName)); } } // Verify shared properties. Here, we order by property name so that any generated errors // are presented in a consistent order. foreach (var property in attributeDeclaration.RequiredProperties.OrderBy(p => p.Name)) { shareKind = proxyGenerator.GetPropertyShareKind(property.DeclaringType, property.Name); if (shareKind == CodeMemberShareKind.NotShared) { attributeDeclaration.Errors.Add( string.Format( CultureInfo.CurrentCulture, Resource.ClientCodeGen_Attribute_RequiresShared_Property, attributeDeclaration.AttributeType, property.Name, property.DeclaringType, proxyGenerator.ClientProjectName)); } } }
/// <summary> /// Generates code for the given set of custom attributes /// </summary> /// <param name="proxyGenerator">Root client proxy generator</param> /// <param name="referencingType">The referencing type</param> /// <param name="getLogWarningMessage">The function to call to get the warning message to be logged</param> /// <param name="attributes">Collection of attributes for which to generate code</param> /// <param name="comments">Collection of comments that should be updated if errors are discovered.</param> /// <param name="customCommentHeader">A custom comment header that will be displayed for any generated comment errors.</param> /// <param name="forcePropagation">Indicates whether or not to force attribute propagation.</param> /// <returns>The collection of generated attribute declarations corresponding to <paramref name="attributes"/></returns> private static IEnumerable <CodeAttributeDeclaration> GenerateCustomAttributes(CodeDomClientCodeGenerator proxyGenerator, CodeTypeDeclaration referencingType, Func <AttributeBuilderException, string> getLogWarningMessage, IEnumerable <Attribute> attributes, CodeCommentStatementCollection comments, string customCommentHeader, bool forcePropagation) { bool emittedErrorCommentHeader = false; List <CodeAttributeDeclaration> result = new List <CodeAttributeDeclaration>(attributes.Count()); // Enumerate over attributes sorted by name. Here, we sort by name to ensure that our // generated baselines (including possible error comments!) are ordered consistently. foreach (Attribute attribute in attributes.OrderBy(a => a.GetType().Name)) { Type attributeType = attribute.GetType(); // Check if this attribute should be blocked if (IsAttributeBlocked(attributeType)) { continue; } bool attributePropagated = false; bool isDataAnnotationsAttribute = string.Equals(attributeType.Namespace, typeof(ValidationAttribute).Namespace, StringComparison.Ordinal); ICustomAttributeBuilder cab = GetCustomAttributeBuilder(attributeType); if (cab != null) { AttributeDeclaration attributeDeclaration = null; // If the attempt to build the attribute fails, log a clean error. // One common exception path is InvalidOperationException arising from // attributes that have been improperly constructed (see DisplayAttribute) try { attributeDeclaration = cab.GetAttributeDeclaration(attribute); } catch (AttributeBuilderException attributeBuilderException) { // Ensure we've generated the attribute generation failure error header GenerateCustomAttributesErrorCommentHeader(comments, customCommentHeader, ref emittedErrorCommentHeader); // Generate comments stating the attribute couldn't be generated comments.AddRange(ConstructCodeAttributeFailureComments(attributeBuilderException.Message)); // Log the build warning if a method was specified to get the warning message if (getLogWarningMessage != null) { string warningMessage = getLogWarningMessage(attributeBuilderException); proxyGenerator.LogWarning(warningMessage); } // Move on to the next attribute continue; } // Null is acceptable indicator that code-gen was not possible. if (attributeDeclaration != null) { if (!forcePropagation) { // Verify attribute's shared type|property|method requirements are met ValidateAttributeDeclarationRequirements(proxyGenerator, attributeDeclaration); } if (attributeDeclaration.HasErrors) { // Only generate comments if the attribute is a DataAnnotations attribute if (isDataAnnotationsAttribute) { // Ensure we've generated the attribute generation failure error header GenerateCustomAttributesErrorCommentHeader(comments, customCommentHeader, ref emittedErrorCommentHeader); // Generate attribute and an error message as comments comments.AddRange(ConstructCodeAttributeFailureComments(proxyGenerator, attributeDeclaration)); } } else { // Generate the attribute declaration CodeAttributeDeclaration codeAttributeDeclaration = CreateCodeAttributeDeclaration(proxyGenerator, referencingType, attributeDeclaration); result.Add(codeAttributeDeclaration); attributePropagated = true; } } } // We generate VS warnings in certain scenarios: // - A DataAnnotation attribute type was not available on the client, user needs to add a reference. // - An attribute subclassed ValidationAttribute (custom or framework) and we couldn't build it. if (!attributePropagated) { // Was it a DA attribute that wasn't available? If so, log a warning. if (isDataAnnotationsAttribute) { CodeMemberShareKind shareKind = proxyGenerator.GetTypeShareKind(attributeType); if (shareKind == CodeMemberShareKind.NotShared) { // Indicate that a reference to 'System.ComponentModel.DataAnnotations' is required. proxyGenerator.LogWarning( string.Format( CultureInfo.CurrentCulture, Resource.ClientCodeGen_Attribute_RequiresDataAnnotations, attributeType, proxyGenerator.ClientProjectName)); } } // Was it a validation attribute that we couldn't build? If so, log a warning. else if (cab == null && typeof(ValidationAttribute).IsAssignableFrom(attributeType)) { // Indicate that a builder was not found, attribute does not meet heuristics. proxyGenerator.LogWarning( string.Format( CultureInfo.CurrentCulture, Resource.ClientCodeGen_Attribute_RequiresBuilder, attributeType)); } } } // Issue -- CodeDom outputs the attributes in the order they are generated. // To allow consistent output for easy baseline comparisons, sort the list. result.Sort(new Comparison <CodeAttributeDeclaration>((x, y) => string.Compare(x.Name, y.Name, StringComparison.Ordinal))); return(result); }
/// <summary> /// Creates the CodeDom CodeExpression for the given value. Returns null if unable to generate a CodeExpression. /// </summary> /// <remarks>This method exists solely to help generate code for all object types that can appear in an /// attribute declaration, such as typeof()</remarks> /// <param name="proxyGenerator">The context for generating code. It cannot be null.</param> /// <param name="referencingType">The referencing type</param> /// <param name="value">The value. Null is permitted.</param> /// <returns>The code expression</returns> private static CodeExpression CreateCodeExpression(CodeDomClientCodeGenerator proxyGenerator, CodeTypeDeclaration referencingType, object value) { Type typeOfValue = value == null ? null : value.GetType(); if (value == null || typeOfValue.IsPrimitive || value is string) { CodeExpression e = new CodePrimitiveExpression(value); // Workaround CodeDom issue -- it looks like CodePrimitiveExpression is fooled and generates double // literals as integers when there is no fraction. We take a general strategy of forcing an explicit // compile time cast to ensure we recompile the same type. if (value != null && (value is double || value is float)) { e = new CodeCastExpression(value.GetType(), e); } return e; } // typeof(T) requires special handling Type valueAsType = value as Type; if (valueAsType != null) { // Verify the type is shared // Don't know counts as not shared CodeMemberShareKind shareKind = proxyGenerator.GetTypeShareKind(valueAsType); if ((shareKind & CodeMemberShareKind.Shared) == 0) { // Here we return a fully-qualified type name to ensure we don't cause compilation // errors by adding invalid 'using' statements into our codedom graph. CodeTypeReference valueTypeReference = CodeGenUtilities.GetTypeReference(valueAsType, proxyGenerator, referencingType, false, /*Use fully qualified name*/ true); valueTypeReference.Options = CodeTypeReferenceOptions.GlobalReference; return new CodeTypeOfExpression(valueTypeReference); } return new CodeTypeOfExpression(CodeGenUtilities.GetTypeReference(valueAsType, proxyGenerator, referencingType)); } // Enum values need special handling if (typeOfValue.IsEnum) { string enumValueName = Enum.GetName(typeOfValue, value); string enumTypeName; if (proxyGenerator.ClientProxyCodeGenerationOptions.UseFullTypeNames) { enumTypeName = typeOfValue.FullName; } else { enumTypeName = typeOfValue.Name; } return new CodeFieldReferenceExpression(new CodeTypeReferenceExpression(enumTypeName), enumValueName); } return null; }
/// <summary> /// Generates code for the given set of custom attributes /// </summary> /// <param name="proxyGenerator">Root client proxy generator</param> /// <param name="referencingType">The referencing type</param> /// <param name="getLogWarningMessage">The function to call to get the warning message to be logged</param> /// <param name="attributes">Collection of attributes for which to generate code</param> /// <param name="comments">Collection of comments that should be updated if errors are discovered.</param> /// <param name="customCommentHeader">A custom comment header that will be displayed for any generated comment errors.</param> /// <param name="forcePropagation">Indicates whether or not to force attribute propagation.</param> /// <returns>The collection of generated attribute declarations corresponding to <paramref name="attributes"/></returns> private static IEnumerable<CodeAttributeDeclaration> GenerateCustomAttributes(CodeDomClientCodeGenerator proxyGenerator, CodeTypeDeclaration referencingType, Func<AttributeBuilderException, string> getLogWarningMessage, IEnumerable<Attribute> attributes, CodeCommentStatementCollection comments, string customCommentHeader, bool forcePropagation) { bool emittedErrorCommentHeader = false; List<CodeAttributeDeclaration> result = new List<CodeAttributeDeclaration>(attributes.Count()); // Enumerate over attributes sorted by name. Here, we sort by name to ensure that our // generated baselines (including possible error comments!) are ordered consistently. foreach (Attribute attribute in attributes.OrderBy(a => a.GetType().Name)) { Type attributeType = attribute.GetType(); // Check if this attribute should be blocked if (IsAttributeBlocked(attributeType)) { continue; } bool attributePropagated = false; bool isDataAnnotationsAttribute = string.Equals(attributeType.Namespace, typeof(ValidationAttribute).Namespace, StringComparison.Ordinal); ICustomAttributeBuilder cab = GetCustomAttributeBuilder(attributeType); if (cab != null) { AttributeDeclaration attributeDeclaration = null; // If the attempt to build the attribute fails, log a clean error. // One common exception path is InvalidOperationException arising from // attributes that have been improperly constructed (see DisplayAttribute) try { attributeDeclaration = cab.GetAttributeDeclaration(attribute); } catch (AttributeBuilderException attributeBuilderException) { // Ensure we've generated the attribute generation failure error header GenerateCustomAttributesErrorCommentHeader(comments, customCommentHeader, ref emittedErrorCommentHeader); // Generate comments stating the attribute couldn't be generated comments.AddRange(ConstructCodeAttributeFailureComments(attributeBuilderException.Message)); // Log the build warning if a method was specified to get the warning message if (getLogWarningMessage != null) { string warningMessage = getLogWarningMessage(attributeBuilderException); proxyGenerator.LogWarning(warningMessage); } // Move on to the next attribute continue; } // Null is acceptable indicator that code-gen was not possible. if (attributeDeclaration != null) { if (!forcePropagation) { // Verify attribute's shared type|property|method requirements are met ValidateAttributeDeclarationRequirements(proxyGenerator, attributeDeclaration); } if (attributeDeclaration.HasErrors) { // Only generate comments if the attribute is a DataAnnotations attribute if (isDataAnnotationsAttribute) { // Ensure we've generated the attribute generation failure error header GenerateCustomAttributesErrorCommentHeader(comments, customCommentHeader, ref emittedErrorCommentHeader); // Generate attribute and an error message as comments comments.AddRange(ConstructCodeAttributeFailureComments(proxyGenerator, attributeDeclaration)); } } else { // Generate the attribute declaration CodeAttributeDeclaration codeAttributeDeclaration = CreateCodeAttributeDeclaration(proxyGenerator, referencingType, attributeDeclaration); result.Add(codeAttributeDeclaration); attributePropagated = true; } } } // We generate VS warnings in certain scenarios: // - A DataAnnotation attribute type was not available on the client, user needs to add a reference. // - An attribute subclassed ValidationAttribute (custom or framework) and we couldn't build it. if (!attributePropagated) { // Was it a DA attribute that wasn't available? If so, log a warning. if (isDataAnnotationsAttribute) { CodeMemberShareKind shareKind = proxyGenerator.GetTypeShareKind(attributeType); if (shareKind == CodeMemberShareKind.NotShared) { // Indicate that a reference to 'System.ComponentModel.DataAnnotations' is required. proxyGenerator.LogWarning( string.Format( CultureInfo.CurrentCulture, Resource.ClientCodeGen_Attribute_RequiresDataAnnotations, attributeType, proxyGenerator.ClientProjectName)); } } // Was it a validation attribute that we couldn't build? If so, log a warning. else if (cab == null && typeof(ValidationAttribute).IsAssignableFrom(attributeType)) { // Indicate that a builder was not found, attribute does not meet heuristics. proxyGenerator.LogWarning( string.Format( CultureInfo.CurrentCulture, Resource.ClientCodeGen_Attribute_RequiresBuilder, attributeType)); } } } // Issue -- CodeDom outputs the attributes in the order they are generated. // To allow consistent output for easy baseline comparisons, sort the list. result.Sort(new Comparison<CodeAttributeDeclaration>((x, y) => string.Compare(x.Name, y.Name, StringComparison.Ordinal))); return result; }