public static AttributeDeclaration GetAttributeDeclaration(Attribute attribute, ClientCodeGenerator textTemplateClientCodeGenerator, bool forcePropagation) { Type attributeType = attribute.GetType(); // Check if this attribute should be blocked if (IsAttributeBlocked(attributeType)) { return(null); } ICustomAttributeBuilder cab = GetCustomAttributeBuilder(attributeType); AttributeDeclaration attributeDeclaration = null; if (cab != null) { try { attributeDeclaration = cab.GetAttributeDeclaration(attribute); } catch (AttributeBuilderException) { return(null); } if (attributeDeclaration != null) { if (!forcePropagation) { // Verify attribute's shared type|property|method requirements are met ValidateAttributeDeclarationRequirements(attributeDeclaration, textTemplateClientCodeGenerator); } } } return(attributeDeclaration); }
/// <summary> /// Retrieves the appropriate custom attribute builder for a given attribute instance /// </summary> /// <param name="attributeType">The attribute type. It cannot be null.</param> /// <returns>The custom attribute builder for it.</returns> private static ICustomAttributeBuilder GetCustomAttributeBuilder(Type attributeType) { if (attributeType == null) { throw new ArgumentNullException("attributeType"); } ICustomAttributeBuilder cab = null; // We maintain a cache of known builder instances, created lazily if (KnownBuilders.TryGetValue(attributeType, out cab)) { return(cab); } // Don't have a builder instance yet // See if we have a registered builder type for this attribute Type cabType = null; if (!KnownBuilderTypes.TryGetValue(attributeType, out cabType)) { // Don't have an explicit builder -- see if we this attribute derives from // a known builder type and assume it is okay to use it. foreach (KeyValuePair <Type, Type> pair in KnownBuilderTypes) { if (pair.Key.IsAssignableFrom(attributeType)) { cabType = pair.Value; break; } } } // If don't have a builder -- see if the attribute is visible to the // client. If so, we will attempt to build with our standard builder if (cabType == null) { cabType = typeof(StandardCustomAttributeBuilder); } // If we found a builder type, instantiate it now. We'll reuse it cab = Activator.CreateInstance(cabType) as ICustomAttributeBuilder; // Don't cache null builders, because we may be re-using this cache for a next code-gen // run where we may have a different set of shared types. // TODO: We need to get rid of our static caches, because caches look different between // builds of different DomainServices. E.g. for one DomainService an attribute may // be shared, while in another it's not, and thus the KnownBuilders mapping is // different between the two. // Instead of static caches, we should consider storing state per build in a context // object. The context object would not be shared against different builds of different // DomainServices. if (cab != null) { KnownBuilders[attributeType] = cab; } return(cab); }
/// <summary> /// Retrieves the appropriate custom attribute builder for a given attribute instance /// </summary> /// <param name="attributeType">The attribute type. It cannot be null.</param> /// <returns>The custom attribute builder for it.</returns> private static ICustomAttributeBuilder GetCustomAttributeBuilder(Type attributeType) { if (attributeType == null) { throw new ArgumentNullException("attributeType"); } ICustomAttributeBuilder cab = null; // We maintain a cache of known builder instances, created lazily if (KnownBuilders.TryGetValue(attributeType, out cab)) { return(cab); } // Don't have a builder instance yet // See if we have a registered builder type for this attribute Type cabType = null; if (!KnownBuilderTypes.TryGetValue(attributeType, out cabType)) { // Don't have an explicit builder -- see if we this attribute derives from // a known builder type and assume it is okay to use it. foreach (KeyValuePair <Type, Type> pair in KnownBuilderTypes) { if (pair.Key.IsAssignableFrom(attributeType)) { cabType = pair.Value; break; } } } // If don't have a builder -- see if the attribute is visible to the // client. If so, we will attempt to build with our standard builder if (cabType == null) { cabType = typeof(StandardCustomAttributeBuilder); } // If we found a builder type, instantiate it now. We'll reuse it if (cabType != null) { cab = Activator.CreateInstance(cabType) as ICustomAttributeBuilder; } // Don't cache null builders, because we may be re-using this cache for a next code-gen // run where we may have a different set of shared types. if (cab != null) { KnownBuilders[attributeType] = cab; } return(cab); }
/// <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); }