public override AttributeDeclaration GetAttributeDeclaration(Attribute attribute) { UIHintAttribute uiHintAttribute = (UIHintAttribute)attribute; // We override this build method only to deal with the control parameters // which cannot be generated by the standard builder. If there are no // control parameters, let the standard builder do the work. IDictionary <string, object> controlParams = uiHintAttribute.ControlParameters; if (controlParams == null || controlParams.Count == 0) { return(base.GetAttributeDeclaration(attribute)); } AttributeDeclaration attributeDeclaration = new AttributeDeclaration(typeof(UIHintAttribute)); // UIHint[("uiHint", "presentationLayer")] attributeDeclaration.ConstructorArguments.Add(uiHintAttribute.UIHint); attributeDeclaration.ConstructorArguments.Add(uiHintAttribute.PresentationLayer); // UIHint[("uiHint", "presentationLayer", ...)] -- fill in all the optional params from control parameters foreach (KeyValuePair <string, object> item in controlParams) { attributeDeclaration.ConstructorArguments.Add(item.Key); attributeDeclaration.ConstructorArguments.Add(item.Value); } return(attributeDeclaration); }
public override AttributeDeclaration GetAttributeDeclaration(Attribute attribute) { UIHintAttribute uiHintAttribute = (UIHintAttribute)attribute; // We override this build method only to deal with the control parameters // which cannot be generated by the standard builder. If there are no // control parameters, let the standard builder do the work. IDictionary<string, object> controlParams = uiHintAttribute.ControlParameters; if (controlParams == null || controlParams.Count == 0) { return base.GetAttributeDeclaration(attribute); } AttributeDeclaration attributeDeclaration = new AttributeDeclaration(typeof(UIHintAttribute)); // UIHint[("uiHint", "presentationLayer")] attributeDeclaration.ConstructorArguments.Add(uiHintAttribute.UIHint); attributeDeclaration.ConstructorArguments.Add(uiHintAttribute.PresentationLayer); // UIHint[("uiHint", "presentationLayer", ...)] -- fill in all the optional params from control parameters foreach (KeyValuePair<string, object> item in controlParams) { attributeDeclaration.ConstructorArguments.Add(item.Key); attributeDeclaration.ConstructorArguments.Add(item.Value); } return attributeDeclaration; }
/// <summary> /// Returns a representative <see cref="AttributeDeclaration"/> for a given <see cref="Attribute"/> instance. /// </summary> /// <param name="attribute">An attribute instance to create a <see cref="AttributeDeclaration"/> for.</param> /// <returns>A <see cref="AttributeDeclaration"/> representing the <paramref name="attribute"/>.</returns> public override AttributeDeclaration GetAttributeDeclaration(Attribute attribute) { ValidationAttribute validationAttribute = (ValidationAttribute)attribute; AttributeDeclaration attributeDeclaration = base.GetAttributeDeclaration(attribute); RegisterSharedResources(validationAttribute, attributeDeclaration); return(attributeDeclaration); }
/// <summary> /// Registers any resource type used by the given <see cref="ValidationAttribute"/> that must be shared and /// have a named resource property available. /// </summary> /// <param name="validationAttribute">The <see cref="ValidationAttribute"/> instance to check.</param> /// <param name="attributeDeclaration">The <see cref="AttributeDeclaration"/> used to describe <paramref name="validationAttribute"/>.</param> protected static void RegisterSharedResources(ValidationAttribute validationAttribute, AttributeDeclaration attributeDeclaration) { string resourceName = validationAttribute.ErrorMessageResourceName; Type resourceType = validationAttribute.ErrorMessageResourceType; bool isEmptyResourceName = string.IsNullOrEmpty(resourceName); Type validateionAttributeType = validationAttribute.GetType(); // At least one is non-null. If the other is null, we have a problem. We need both to // localize properly, or neither to signal we use the static string version if ((resourceType != null && isEmptyResourceName) || (resourceType == null && !isEmptyResourceName)) { string resourceTypeMessage = resourceType != null ? resourceType.Name : Resource.Unspecified_Resource_Element; string resourceNameMessage = isEmptyResourceName ? Resource.Unspecified_Resource_Element : resourceName; attributeDeclaration.Errors.Add( string.Format( CultureInfo.CurrentCulture, Resource.ClientCodeGen_ValidationAttribute_Requires_ResourceType_And_Name, validateionAttributeType, resourceTypeMessage, resourceNameMessage)); return; } if (resourceType != null) { PropertyInfo resourceProperty = resourceType.GetProperty(resourceName); if (resourceProperty == null) { attributeDeclaration.Errors.Add( string.Format( CultureInfo.CurrentCulture, Resource.ClientCodeGen_ValidationAttribute_ResourcePropertyNotFound, validateionAttributeType, resourceName, resourceType)); } else { attributeDeclaration.RequiredTypes.Add(resourceType); attributeDeclaration.RequiredProperties.Add(resourceProperty); } } }
public override AttributeDeclaration GetAttributeDeclaration(Attribute attribute) { RangeAttribute rangeAttribute = (RangeAttribute)attribute; AttributeDeclaration attributeDeclaration = new AttributeDeclaration(typeof(RangeAttribute)); // Register required resources for this ValidationAttribute RegisterSharedResources(rangeAttribute, attributeDeclaration); bool declareOperandType = rangeAttribute.Minimum == null || rangeAttribute.Minimum.GetType() == typeof(string); // OperandType if (declareOperandType) { attributeDeclaration.ConstructorArguments.Add(rangeAttribute.OperandType); } // Minimum attributeDeclaration.ConstructorArguments.Add(rangeAttribute.Minimum); // Maximum attributeDeclaration.ConstructorArguments.Add(rangeAttribute.Maximum); // ErrorMessage if (rangeAttribute.ErrorMessage != null) { attributeDeclaration.NamedParameters.Add("ErrorMessage", rangeAttribute.ErrorMessage); } // ErrorMessageResourceType if (rangeAttribute.ErrorMessageResourceType != null) { attributeDeclaration.NamedParameters.Add("ErrorMessageResourceType", rangeAttribute.ErrorMessageResourceType); } // ErrorMessageResourceName if (rangeAttribute.ErrorMessageResourceName != null) { attributeDeclaration.NamedParameters.Add("ErrorMessageResourceName", rangeAttribute.ErrorMessageResourceName); } return(attributeDeclaration); }
public override AttributeDeclaration GetAttributeDeclaration(Attribute attribute) { CustomValidationAttribute cva = (CustomValidationAttribute)attribute; // Our convention is that parameter validation in the CVA occurs when it is // first used. Simply ask the attribute to produce an error message, as this // will trigger an InvalidOperationException if the attribute is ill-formed cva.FormatErrorMessage(string.Empty); // Delegate to the base implementation to generate the attribute. // Note that the base implementation already checks that Types are // shared so we do not perform that check here. AttributeDeclaration attributeDeclaration = base.GetAttributeDeclaration(attribute); attributeDeclaration.RequiredTypes.Add(cva.ValidatorType); attributeDeclaration.RequiredMethods.Add(cva.ValidatorType.GetMethod(cva.Method)); return(attributeDeclaration); }
public override AttributeDeclaration GetAttributeDeclaration(Attribute attribute) { RangeAttribute rangeAttribute = (RangeAttribute)attribute; AttributeDeclaration attributeDeclaration = new AttributeDeclaration(typeof(RangeAttribute)); // Register required resources for this ValidationAttribute RegisterSharedResources(rangeAttribute, attributeDeclaration); bool declareOperandType = rangeAttribute.Minimum == null || rangeAttribute.Minimum.GetType() == typeof(string); // OperandType if (declareOperandType) { attributeDeclaration.ConstructorArguments.Add(rangeAttribute.OperandType); } // Minimum attributeDeclaration.ConstructorArguments.Add(rangeAttribute.Minimum); // Maximum attributeDeclaration.ConstructorArguments.Add(rangeAttribute.Maximum); // ErrorMessage if (rangeAttribute.ErrorMessage != null) { attributeDeclaration.NamedParameters.Add("ErrorMessage", rangeAttribute.ErrorMessage); } // ErrorMessageResourceType if (rangeAttribute.ErrorMessageResourceType != null) { attributeDeclaration.NamedParameters.Add("ErrorMessageResourceType", rangeAttribute.ErrorMessageResourceType); } // ErrorMessageResourceName if (rangeAttribute.ErrorMessageResourceName != null) { attributeDeclaration.NamedParameters.Add("ErrorMessageResourceName", rangeAttribute.ErrorMessageResourceName); } return attributeDeclaration; }
/// <summary> /// Generates a <see cref="AttributeDeclaration"/> representation of an /// <see cref="EditableAttribute"/> instance. /// </summary> /// <param name="attribute">The <see cref="EditableAttribute"/>.</param> /// <returns>A <see cref="AttributeDeclaration"/> representation of /// <paramref name="attribute"/>.</returns> /// <exception cref="InvalidCastException">if <paramref name="attribute"/> is /// not a <see cref="EditableAttribute"/>.</exception> public override AttributeDeclaration GetAttributeDeclaration(Attribute attribute) { EditableAttribute editableAttribute = (EditableAttribute)attribute; AttributeDeclaration attributeDeclaration = new AttributeDeclaration(typeof(EditableAttribute)); bool allowEdit = editableAttribute.AllowEdit; bool allowInitialValue = editableAttribute.AllowInitialValue; // [EditableAttribute( {true|false} )] attributeDeclaration.ConstructorArguments.Add(allowEdit); // Only add the 'AllowInitialValue' parameter if its value does not match with // the 'AllowEdit' value. See the documentation of EditableAttribute for more info. if (allowEdit != allowInitialValue) { // [EditableAttribute( {true|false}, AllowInitialValue = {true|false} )] attributeDeclaration.NamedParameters.Add("AllowInitialValue", allowInitialValue); } return(attributeDeclaration); }
/// <summary> /// Generates a <see cref="AttributeDeclaration"/> representation of an /// <see cref="EditableAttribute"/> instance. /// </summary> /// <param name="attribute">The <see cref="EditableAttribute"/>.</param> /// <returns>A <see cref="AttributeDeclaration"/> representation of /// <paramref name="attribute"/>.</returns> /// <exception cref="InvalidCastException">if <paramref name="attribute"/> is /// not a <see cref="EditableAttribute"/>.</exception> public override AttributeDeclaration GetAttributeDeclaration(Attribute attribute) { EditableAttribute editableAttribute = (EditableAttribute)attribute; AttributeDeclaration attributeDeclaration = new AttributeDeclaration(typeof(EditableAttribute)); bool allowEdit = editableAttribute.AllowEdit; bool allowInitialValue = editableAttribute.AllowInitialValue; // [EditableAttribute( {true|false} )] attributeDeclaration.ConstructorArguments.Add(allowEdit); // Only add the 'AllowInitialValue' parameter if its value does not match with // the 'AllowEdit' value. See the documentation of EditableAttribute for more info. if (allowEdit != allowInitialValue) { // [EditableAttribute( {true|false}, AllowInitialValue = {true|false} )] attributeDeclaration.NamedParameters.Add("AllowInitialValue", allowInitialValue); } return attributeDeclaration; }
public override AttributeDeclaration GetAttributeDeclaration(Attribute attribute) { DisplayAttribute displayAttribute = (DisplayAttribute)attribute; AttributeDeclaration attributeDeclaration = new AttributeDeclaration(typeof(DisplayAttribute)); // By convention, the attribute parameters are not validated until an attempt is made to // access the resources. We do the following probe merely to trigger this validation process. // An InvalidOperationException will be thrown if the attribute is ill-formed. Type attributeType = attribute.GetType(); try { displayAttribute.GetName(); } catch (InvalidOperationException ex) { throw new AttributeBuilderException(ex, attributeType, "Name"); } try { displayAttribute.GetShortName(); } catch (InvalidOperationException ex) { throw new AttributeBuilderException(ex, attributeType, "ShortName"); } try { displayAttribute.GetDescription(); } catch (InvalidOperationException ex) { throw new AttributeBuilderException(ex, attributeType, "Description"); } try { displayAttribute.GetPrompt(); } catch (InvalidOperationException ex) { throw new AttributeBuilderException(ex, attributeType, "Prompt"); } // Add AutoGenerateField if (displayAttribute.GetAutoGenerateField().HasValue) { attributeDeclaration.NamedParameters.Add("AutoGenerateField", displayAttribute.AutoGenerateField); } // Add AutoGenerateFilter if (displayAttribute.GetAutoGenerateFilter().HasValue) { attributeDeclaration.NamedParameters.Add("AutoGenerateFilter", displayAttribute.AutoGenerateFilter); } // Add Description if (!string.IsNullOrEmpty(displayAttribute.Description)) { attributeDeclaration.NamedParameters.Add("Description", displayAttribute.Description); } // Add GroupName if (!string.IsNullOrEmpty(displayAttribute.GroupName)) { attributeDeclaration.NamedParameters.Add("GroupName", displayAttribute.GroupName); } // Add Name if (!string.IsNullOrEmpty(displayAttribute.Name)) { attributeDeclaration.NamedParameters.Add("Name", displayAttribute.Name); } // Add Order if (displayAttribute.GetOrder().HasValue) { attributeDeclaration.NamedParameters.Add("Order", displayAttribute.Order); } // Add Prompt if (!string.IsNullOrEmpty(displayAttribute.Prompt)) { attributeDeclaration.NamedParameters.Add("Prompt", displayAttribute.Prompt); } // Add ResourceType if (displayAttribute.ResourceType != null) { attributeDeclaration.NamedParameters.Add("ResourceType", displayAttribute.ResourceType); } // Add ShortName if (!string.IsNullOrEmpty(displayAttribute.ShortName)) { attributeDeclaration.NamedParameters.Add("ShortName", displayAttribute.ShortName); } return attributeDeclaration; }
/// <summary> /// Returns a representative <see cref="AttributeDeclaration"/> for a given <see cref="Attribute"/> instance. /// </summary> /// <param name="attribute">An attribute instance to create a <see cref="AttributeDeclaration"/> for.</param> /// <returns>A <see cref="AttributeDeclaration"/> representing the <paramref name="attribute"/>.</returns> public virtual AttributeDeclaration GetAttributeDeclaration(Attribute attribute) { if (attribute == null) { throw new ArgumentNullException(nameof(attribute)); } Type attributeType = attribute.GetType(); AttributeDeclaration attributeDeclaration = new AttributeDeclaration(attributeType); // Strategy is as follows: // - Fetch all the public property values from the current attribute // - Determine the default value for all of these properties // - From these 2 lists, determine the set of "non-default" properties. These are what we must code gen. // - From this list, determine which of these can be set only through a ctor // - From the list of ctor properties and values, find the best ctor pattern for it and code gen that // - For all remaining non-default properties, code gen named argument setters List<PropertyMap> propertyMaps = this.BuildPropertyMaps(attribute); Dictionary<string, object> currentValues = GetPropertyValues(propertyMaps, attribute); Dictionary<string, object> defaultValues = GetDefaultPropertyValues(propertyMaps, attribute, currentValues); List<PropertyMap> nonDefaultProperties = GetNonDefaultProperties(propertyMaps, currentValues, defaultValues); List<PropertyMap> unsettableProperties = GetUnsettableProperties(nonDefaultProperties); // "Unsettable" properties are all those that can be set only through a ctor (they have no public setter). // Go find the best ctor pattern for them and code gen that much ParameterInfo[] ctorParameters = FindBestConstructor(unsettableProperties, currentValues, attributeType); if (ctorParameters == null) { // Return null, indicating we cannot build this attribute. return null; } // We found a ctor that will accept all our properties that need to be set. // Generate ctor arguments to match this signature. // Note: the ctor pattern obviously may require other arguments that are also settable, // so if we pass a value to the ctor, we omit it from the set of named parameters below foreach (ParameterInfo parameter in ctorParameters) { PropertyMap matchedPropertyMap = null; foreach (PropertyMap map in propertyMaps) { PropertyInfo propertyInfo = map.Setter; if (propertyInfo.Name.Equals(parameter.Name, StringComparison.OrdinalIgnoreCase) && CanValueBeAssignedToType(parameter.ParameterType, currentValues[propertyInfo.Name])) { matchedPropertyMap = map; break; } } object value = matchedPropertyMap != null ? currentValues[matchedPropertyMap.Getter.Name] : DefaultInstanceForType(parameter.ParameterType); attributeDeclaration.ConstructorArguments.Add(value); // Remove this from our list of properties we need to set so the code below skips it if (matchedPropertyMap != null) { nonDefaultProperties.Remove(matchedPropertyMap); } } // For all remaining non-default properties, generate a named argument setter. // We sort these so the named parameters appear in a predictable order in generated code -- primarily for unit testing nonDefaultProperties.Sort(new Comparison<PropertyMap>((x, y) => string.Compare(x.Setter.Name, y.Setter.Name, StringComparison.Ordinal))); foreach (PropertyMap map in nonDefaultProperties) { attributeDeclaration.NamedParameters.Add(map.Setter.Name, currentValues[map.Getter.Name]); } return attributeDeclaration; }
public override AttributeDeclaration GetAttributeDeclaration(Attribute attribute) { DisplayAttribute displayAttribute = (DisplayAttribute)attribute; AttributeDeclaration attributeDeclaration = new AttributeDeclaration(typeof(DisplayAttribute)); // By convention, the attribute parameters are not validated until an attempt is made to // access the resources. We do the following probe merely to trigger this validation process. // An InvalidOperationException will be thrown if the attribute is ill-formed. Type attributeType = attribute.GetType(); try { displayAttribute.GetName(); } catch (InvalidOperationException ex) { throw new AttributeBuilderException(ex, attributeType, "Name"); } try { displayAttribute.GetShortName(); } catch (InvalidOperationException ex) { throw new AttributeBuilderException(ex, attributeType, "ShortName"); } try { displayAttribute.GetDescription(); } catch (InvalidOperationException ex) { throw new AttributeBuilderException(ex, attributeType, "Description"); } try { displayAttribute.GetPrompt(); } catch (InvalidOperationException ex) { throw new AttributeBuilderException(ex, attributeType, "Prompt"); } // Add AutoGenerateField if (displayAttribute.GetAutoGenerateField().HasValue) { attributeDeclaration.NamedParameters.Add("AutoGenerateField", displayAttribute.AutoGenerateField); } // Add AutoGenerateFilter if (displayAttribute.GetAutoGenerateFilter().HasValue) { attributeDeclaration.NamedParameters.Add("AutoGenerateFilter", displayAttribute.AutoGenerateFilter); } // Add Description if (!string.IsNullOrEmpty(displayAttribute.Description)) { attributeDeclaration.NamedParameters.Add("Description", displayAttribute.Description); } // Add GroupName if (!string.IsNullOrEmpty(displayAttribute.GroupName)) { attributeDeclaration.NamedParameters.Add("GroupName", displayAttribute.GroupName); } // Add Name if (!string.IsNullOrEmpty(displayAttribute.Name)) { attributeDeclaration.NamedParameters.Add("Name", displayAttribute.Name); } // Add Order if (displayAttribute.GetOrder().HasValue) { attributeDeclaration.NamedParameters.Add("Order", displayAttribute.Order); } // Add Prompt if (!string.IsNullOrEmpty(displayAttribute.Prompt)) { attributeDeclaration.NamedParameters.Add("Prompt", displayAttribute.Prompt); } // Add ResourceType if (displayAttribute.ResourceType != null) { attributeDeclaration.NamedParameters.Add("ResourceType", displayAttribute.ResourceType); } // Add ShortName if (!string.IsNullOrEmpty(displayAttribute.ShortName)) { attributeDeclaration.NamedParameters.Add("ShortName", displayAttribute.ShortName); } return(attributeDeclaration); }
/// <summary> /// Generate comments indicating attribute propagation failure. /// </summary> /// <param name="proxyGenerator">The context for generating code. It cannot be <c>null</c>.</param> /// <param name="attributeDeclaration">The attribute declaration to generate as a comment.</param> /// <returns>A collection of comments.</returns> private static CodeCommentStatementCollection ConstructCodeAttributeFailureComments(CodeDomClientCodeGenerator proxyGenerator, AttributeDeclaration attributeDeclaration) { // We are generating failure comments in the following example form: // // // Unable to generate the following attribute(s) due to the following error(s): // // - The attribute 'System.ComponentModel.DataAnnotations.CustomValidationAttribute' references type 'TestDomainServices.ServerOnlyValidator'. This is not accessible in the client project. // // [CustomValidationAttribute(typeof(TestDomainServices.ServerOnlyValidator), "IsObjectValid")] // // CodeCommentStatementCollection comments = new CodeCommentStatementCollection(); foreach (string error in attributeDeclaration.Errors) { comments.Add(new CodeCommentStatement(string.Format(CultureInfo.CurrentCulture, Resource.ClientCodeGen_Attribute_FailedToGenerate_ErrorTemplate, error))); } comments.Add(new CodeCommentStatement(GenerateCodeAttribute(proxyGenerator, attributeDeclaration))); comments.Add(new CodeCommentStatement(string.Empty /* blank comment */)); return comments; }
/// <summary> /// Creates a <see cref="CodeAttributeDeclaration"/> for the given <see cref="AttributeDeclaration"/>. /// </summary> /// <param name="proxyGenerator">The context for generating code. It cannot be null.</param> /// <param name="referencingType">The referencing type.</param> /// <param name="attributeDeclaration">The <see cref="AttributeDeclaration"/> to build.</param> /// <returns>A <see cref="CodeAttributeDeclaration"/>.</returns> private static CodeAttributeDeclaration CreateCodeAttributeDeclaration(CodeDomClientCodeGenerator proxyGenerator, CodeTypeDeclaration referencingType, AttributeDeclaration attributeDeclaration) { CodeAttributeDeclaration codeAttributeDeclaration = CodeGenUtilities.CreateAttributeDeclaration(attributeDeclaration.AttributeType, proxyGenerator, referencingType); // Add ctor args foreach (object arg in attributeDeclaration.ConstructorArguments) { CodeExpression expression = CreateCodeExpression(proxyGenerator, referencingType, arg); codeAttributeDeclaration.Arguments.Add(new CodeAttributeArgument(expression)); } // Add named params foreach (KeyValuePair <string, object> pair in attributeDeclaration.NamedParameters) { CodeExpression expression = CreateCodeExpression(proxyGenerator, referencingType, pair.Value); codeAttributeDeclaration.Arguments.Add(new CodeAttributeArgument(pair.Key, expression)); } return(codeAttributeDeclaration); }
/// <summary> /// Generates an attribute declaration string. This is used in scenarios where an attribute declaration is needed /// in the form of a comment. CodeDOM does not support generation of standalone attributes. /// </summary> /// <param name="proxyGenerator">The context for generating code. It cannot be null.</param> /// <param name="attributeDeclaration">The <see cref="AttributeDeclaration"/> to represent.</param> /// <returns>An attribute declaration.</returns> private static string GenerateCodeAttribute(CodeDomClientCodeGenerator proxyGenerator, AttributeDeclaration attributeDeclaration) { StringBuilder result = new StringBuilder(); bool isCSharp = proxyGenerator.IsCSharp; result.Append(isCSharp ? '[' : '<'); result.Append(attributeDeclaration.AttributeType.Name); result.Append('('); // Add ctor args if (attributeDeclaration.ConstructorArguments.Count > 0) { foreach (object value in attributeDeclaration.ConstructorArguments) { result.Append(ConvertValueToCode(value, isCSharp)); result.Append(", "); } } // Add named params if (attributeDeclaration.NamedParameters.Count > 0) { foreach (KeyValuePair <string, object> pair in attributeDeclaration.NamedParameters) { result.Append(pair.Key); result.Append(isCSharp ? " = " : " := "); result.Append(ConvertValueToCode(pair.Value, isCSharp)); result.Append(", "); } } if (attributeDeclaration.ConstructorArguments.Count > 0 || attributeDeclaration.NamedParameters.Count > 0) { result.Remove(result.Length - 2, 2); } result.Append(')'); result.Append(isCSharp ? "]" : "> _"); return(result.ToString()); }
/// <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> /// Generate comments indicating attribute propagation failure. /// </summary> /// <param name="proxyGenerator">The context for generating code. It cannot be <c>null</c>.</param> /// <param name="attributeDeclaration">The attribute declaration to generate as a comment.</param> /// <returns>A collection of comments.</returns> private static CodeCommentStatementCollection ConstructCodeAttributeFailureComments(CodeDomClientCodeGenerator proxyGenerator, AttributeDeclaration attributeDeclaration) { // We are generating failure comments in the following example form: // // // Unable to generate the following attribute(s) due to the following error(s): // // - The attribute 'System.ComponentModel.DataAnnotations.CustomValidationAttribute' references type 'TestDomainServices.ServerOnlyValidator'. This is not accessible in the client project. // // [CustomValidationAttribute(typeof(TestDomainServices.ServerOnlyValidator), "IsObjectValid")] // // CodeCommentStatementCollection comments = new CodeCommentStatementCollection(); foreach (string error in attributeDeclaration.Errors) { comments.Add(new CodeCommentStatement(string.Format(CultureInfo.CurrentCulture, Resource.ClientCodeGen_Attribute_FailedToGenerate_ErrorTemplate, error))); } comments.Add(new CodeCommentStatement(GenerateCodeAttribute(proxyGenerator, attributeDeclaration))); comments.Add(new CodeCommentStatement(string.Empty /* blank comment */)); return(comments); }
/// <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> /// Returns a representative <see cref="AttributeDeclaration"/> for a given <see cref="Attribute"/> instance. /// </summary> /// <param name="attribute">An attribute instance to create a <see cref="AttributeDeclaration"/> for.</param> /// <returns>A <see cref="AttributeDeclaration"/> representing the <paramref name="attribute"/>.</returns> public virtual AttributeDeclaration GetAttributeDeclaration(Attribute attribute) { if (attribute == null) { throw new ArgumentNullException("attribute"); } Type attributeType = attribute.GetType(); AttributeDeclaration attributeDeclaration = new AttributeDeclaration(attributeType); // Strategy is as follows: // - Fetch all the public property values from the current attribute // - Determine the default value for all of these properties // - From these 2 lists, determine the set of "non-default" properties. These are what we must code gen. // - From this list, determine which of these can be set only through a ctor // - From the list of ctor properties and values, find the best ctor pattern for it and code gen that // - For all remaining non-default properties, code gen named argument setters List<PropertyMap> propertyMaps = this.BuildPropertyMaps(attribute); Dictionary<string, object> currentValues = GetPropertyValues(propertyMaps, attribute); Dictionary<string, object> defaultValues = GetDefaultPropertyValues(propertyMaps, attribute, currentValues); List<PropertyMap> nonDefaultProperties = GetNonDefaultProperties(propertyMaps, currentValues, defaultValues); List<PropertyMap> unsettableProperties = GetUnsettableProperties(nonDefaultProperties); // "Unsettable" properties are all those that can be set only through a ctor (they have no public setter). // Go find the best ctor pattern for them and code gen that much ParameterInfo[] ctorParameters = FindBestConstructor(unsettableProperties, currentValues, attributeType); if (ctorParameters == null) { // Return null, indicating we cannot build this attribute. return null; } // We found a ctor that will accept all our properties that need to be set. // Generate ctor arguments to match this signature. // Note: the ctor pattern obviously may require other arguments that are also settable, // so if we pass a value to the ctor, we omit it from the set of named parameters below foreach (ParameterInfo parameter in ctorParameters) { PropertyMap matchedPropertyMap = null; foreach (PropertyMap map in propertyMaps) { PropertyInfo propertyInfo = map.Setter; if (propertyInfo.Name.Equals(parameter.Name, StringComparison.OrdinalIgnoreCase) && CanValueBeAssignedToType(parameter.ParameterType, currentValues[propertyInfo.Name])) { matchedPropertyMap = map; break; } } object value = matchedPropertyMap != null ? currentValues[matchedPropertyMap.Getter.Name] : DefaultInstanceForType(parameter.ParameterType); attributeDeclaration.ConstructorArguments.Add(value); // Remove this from our list of properties we need to set so the code below skips it if (matchedPropertyMap != null) { nonDefaultProperties.Remove(matchedPropertyMap); } } // For all remaining non-default properties, generate a named argument setter. // We sort these so the named parameters appear in a predictable order in generated code -- primarily for unit testing nonDefaultProperties.Sort(new Comparison<PropertyMap>((x, y) => string.Compare(x.Setter.Name, y.Setter.Name, StringComparison.Ordinal))); foreach (PropertyMap map in nonDefaultProperties) { attributeDeclaration.NamedParameters.Add(map.Setter.Name, currentValues[map.Getter.Name]); } return attributeDeclaration; }
/// <summary> /// Creates a <see cref="CodeAttributeDeclaration"/> for the given <see cref="AttributeDeclaration"/>. /// </summary> /// <param name="proxyGenerator">The context for generating code. It cannot be null.</param> /// <param name="referencingType">The referencing type.</param> /// <param name="attributeDeclaration">The <see cref="AttributeDeclaration"/> to build.</param> /// <returns>A <see cref="CodeAttributeDeclaration"/>.</returns> private static CodeAttributeDeclaration CreateCodeAttributeDeclaration(CodeDomClientCodeGenerator proxyGenerator, CodeTypeDeclaration referencingType, AttributeDeclaration attributeDeclaration) { CodeAttributeDeclaration codeAttributeDeclaration = CodeGenUtilities.CreateAttributeDeclaration(attributeDeclaration.AttributeType, proxyGenerator, referencingType); // Add ctor args foreach (object arg in attributeDeclaration.ConstructorArguments) { CodeExpression expression = CreateCodeExpression(proxyGenerator, referencingType, arg); codeAttributeDeclaration.Arguments.Add(new CodeAttributeArgument(expression)); } // Add named params foreach (KeyValuePair<string, object> pair in attributeDeclaration.NamedParameters) { CodeExpression expression = CreateCodeExpression(proxyGenerator, referencingType, pair.Value); codeAttributeDeclaration.Arguments.Add(new CodeAttributeArgument(pair.Key, expression)); } return codeAttributeDeclaration; }
/// <summary> /// Generates an attribute declaration string. This is used in scenarios where an attribute declaration is needed /// in the form of a comment. CodeDOM does not support generation of standalone attributes. /// </summary> /// <param name="proxyGenerator">The context for generating code. It cannot be null.</param> /// <param name="attributeDeclaration">The <see cref="AttributeDeclaration"/> to represent.</param> /// <returns>An attribute declaration.</returns> private static string GenerateCodeAttribute(CodeDomClientCodeGenerator proxyGenerator, AttributeDeclaration attributeDeclaration) { StringBuilder result = new StringBuilder(); bool isCSharp = proxyGenerator.IsCSharp; result.Append(isCSharp ? '[' : '<'); result.Append(attributeDeclaration.AttributeType.Name); result.Append('('); // Add ctor args if (attributeDeclaration.ConstructorArguments.Count > 0) { foreach (object value in attributeDeclaration.ConstructorArguments) { result.Append(ConvertValueToCode(value, isCSharp)); result.Append(", "); } } // Add named params if (attributeDeclaration.NamedParameters.Count > 0) { foreach (KeyValuePair<string, object> pair in attributeDeclaration.NamedParameters) { result.Append(pair.Key); result.Append(isCSharp ? " = " : " := "); result.Append(ConvertValueToCode(pair.Value, isCSharp)); result.Append(", "); } } if (attributeDeclaration.ConstructorArguments.Count > 0 || attributeDeclaration.NamedParameters.Count > 0) { result.Remove(result.Length - 2, 2); } result.Append(')'); result.Append(isCSharp ? "]" : "> _"); return result.ToString(); }