Exemplo n.º 1
0
        /// <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 the client proxy code for the given type.
        /// </summary>
        public override void Generate()
        {
            // ----------------------------------------------------------------
            // namespace
            // ----------------------------------------------------------------
            var ns = ClientProxyGenerator.GetOrGenNamespace(Type);

            // Missing namespace bails out of code-gen -- error has been logged
            if (ns == null)
            {
                return;
            }

            // ----------------------------------------------------------------
            // public partial class {Type} : (Base)
            // ----------------------------------------------------------------
            ProxyClass                = CodeGenUtilities.CreateTypeDeclaration(Type);
            ProxyClass.IsPartial      = true; // makes this a partial type
            ProxyClass.TypeAttributes = TypeAttributes.Public;

            // Abstract classes must be preserved as abstract to avoid explicit instantiation on client
            bool isAbstract = (Type.IsAbstract);

            if (isAbstract)
            {
                ProxyClass.TypeAttributes |= TypeAttributes.Abstract;
            }

            // Determine all types derived from this one.
            // Note this list does not assume the current type is the visible root.  That is a separate test.
            IEnumerable <Type> derivedTypes = GetDerivedTypes();

            // If this type doesn't have any derivatives, seal it.  Cannot seal abstracts.
            if (!isAbstract && !derivedTypes.Any())
            {
                ProxyClass.TypeAttributes |= TypeAttributes.Sealed;
            }

            // Add all base types including interfaces
            AddBaseTypes(ns);
            ns.Types.Add(ProxyClass);

            AttributeCollection typeAttributes = Type.Attributes();

            // Add <summary> xml comment to class
            string comment = GetSummaryComment();

            ProxyClass.Comments.AddRange(CodeGenUtilities.GenerateSummaryCodeComment(comment, ClientProxyGenerator.IsCSharp));

            // ----------------------------------------------------------------
            // Add default ctr
            // ----------------------------------------------------------------
            CodeConstructor constructor = new CodeConstructor();

            // Default ctor is public for concrete types but protected for abstracts.
            // This prevents direct instantiation on client
            constructor.Attributes = isAbstract ? MemberAttributes.Family : MemberAttributes.Public;

            // add default ctor doc comments
            comment = string.Format(CultureInfo.CurrentCulture, Resource.CodeGen_Default_Constructor_Summary_Comments, Type.Name);
            constructor.Comments.AddRange(CodeGenUtilities.GenerateSummaryCodeComment(comment, ClientProxyGenerator.IsCSharp));

            // add call to default OnCreated method
            constructor.Statements.Add(NotificationMethodGen.OnCreatedMethodInvokeExpression);
            ProxyClass.Members.Add(constructor);

            // ----------------------------------------------------------------
            // [KnownType(...), ...]
            // ----------------------------------------------------------------

            // We need to generate a [KnownType] for all derived entities on the visible root.
            if (!IsDerivedType)
            {
                // Generate a [KnownType] for every derived type.
                // We specifically exclude [KnownTypes] from the set of attributes we ask
                // the metadata pipeline to generate below, meaning we take total control
                // here for which [KnownType] attributes get through the metadata pipeline.
                //
                // Note, we sort in alphabetic order to give predictability in baselines and
                // client readability.  For cosmetic reasons, we sort by short or long name
                // depending on what our utility helpers will actually generated
                foreach (Type derivedType in derivedTypes.OrderBy(t => ClientProxyGenerator.ClientProxyCodeGenerationOptions.UseFullTypeNames ? t.FullName : t.Name))
                {
                    CodeAttributeDeclaration knownTypeAttrib = CodeGenUtilities.CreateAttributeDeclaration(typeof(System.Runtime.Serialization.KnownTypeAttribute), ClientProxyGenerator, ProxyClass);
                    knownTypeAttrib.Arguments.Add(new CodeAttributeArgument(new CodeTypeOfExpression(CodeGenUtilities.GetTypeReference(derivedType, ClientProxyGenerator, ProxyClass))));
                    ProxyClass.CustomAttributes.Add(knownTypeAttrib);
                }
            }

            ValidateTypeAttributes(typeAttributes);

            // ----------------------------------------------------------------
            // [DataContract(Namespace=X, Name=Y)]
            // ----------------------------------------------------------------
            CodeAttributeDeclaration dataContractAttrib = CodeGenUtilities.CreateDataContractAttributeDeclaration(Type, ClientProxyGenerator, ProxyClass);

            ProxyClass.CustomAttributes.Add(dataContractAttrib);

            // ----------------------------------------------------------------
            // Propagate all type-level Attributes across (except DataContractAttribute since that is handled above)
            // -----------------------------------------------------------------
            CustomAttributeGenerator.GenerateCustomAttributes(
                ClientProxyGenerator,
                ProxyClass,
                ex => string.Format(CultureInfo.CurrentCulture, Resource.ClientCodeGen_Attribute_ThrewException_CodeType, ex.Message, ProxyClass.Name, ex.InnerException.Message),
                FilterTypeAttributes(typeAttributes),
                ProxyClass.CustomAttributes,
                ProxyClass.Comments);

            // ----------------------------------------------------------------
            // gen proxy getter/setter for each property
            // ----------------------------------------------------------------
            GenerateProperties();

            // ----------------------------------------------------------------
            // gen additional methods/events
            // ----------------------------------------------------------------
            GenerateAdditionalMembers();

            // Register created CodeTypeDeclaration with mapping
            _typeMapping[Type] = ProxyClass;
        }
        /// <summary>
        /// Generates a property getter/setter pair into the given proxy class to match the given property info.
        /// </summary>
        /// <param name="propertyDescriptor">PropertyDescriptor for the property to generate for.</param>
        protected virtual void GenerateProperty(PropertyDescriptor propertyDescriptor)
        {
            string propertyName = propertyDescriptor.Name;
            Type   propertyType = CodeGenUtilities.TranslateType(propertyDescriptor.PropertyType);

            // ----------------------------------------------------------------
            // Property type ref
            // ----------------------------------------------------------------
            var propTypeReference = CodeGenUtilities.GetTypeReference(propertyType, ClientProxyGenerator, ProxyClass);

            // ----------------------------------------------------------------
            // Property decl
            // ----------------------------------------------------------------
            var property = new CodeMemberProperty();

            property.Name       = propertyName;
            property.Type       = propTypeReference;
            property.Attributes = MemberAttributes.Public | MemberAttributes.Final; // final needed, else becomes virtual
            List <Attribute> propertyAttributes = propertyDescriptor.ExplicitAttributes().Cast <Attribute>().ToList();

            // Generate <summary> for property
            string comment = string.Format(CultureInfo.CurrentCulture, Resource.CodeGen_Entity_Property_Summary_Comment, propertyName);

            property.Comments.AddRange(CodeGenUtilities.GenerateSummaryCodeComment(comment, ClientProxyGenerator.IsCSharp));

            // ----------------------------------------------------------------
            // [DataMember] -> Add if not already present.
            // ----------------------------------------------------------------
            // Add if not already present.

            if (!propertyAttributes.OfType <DataMemberAttribute>().Any())
            {
                CodeAttributeDeclaration dataMemberAtt = CodeGenUtilities.CreateAttributeDeclaration(typeof(DataMemberAttribute), ClientProxyGenerator, ProxyClass);
                property.CustomAttributes.Add(dataMemberAtt);
            }

            // Here, we check for the existence of [ReadOnly(true)] attributes generated when
            // the property does not not have a setter.  We want to inject an [Editable(false)]
            // attribute into the pipeline.
            ReadOnlyAttribute readOnlyAttr = propertyAttributes.OfType <ReadOnlyAttribute>().SingleOrDefault();

            if (readOnlyAttr != null && !propertyAttributes.OfType <EditableAttribute>().Any())
            {
                propertyAttributes.Add(new EditableAttribute(!readOnlyAttr.IsReadOnly));

                // REVIEW:  should we strip out [ReadOnly] attributes here?
            }

            // Here, we check for the presence of a complex type. If it exists we need to add a DisplayAttribute
            // if not already there. DataSources windows do not handle complex types
            if (TypeUtility.IsSupportedComplexType(propertyType) && !propertyAttributes.OfType <DisplayAttribute>().Any())
            {
                CodeAttributeDeclaration displayAttribute = CodeGenUtilities.CreateDisplayAttributeDeclaration(ClientProxyGenerator, ProxyClass);
                property.CustomAttributes.Add(displayAttribute);
            }

            // ----------------------------------------------------------------
            // Propagate the custom attributes
            // ----------------------------------------------------------------

            CustomAttributeGenerator.GenerateCustomAttributes(
                ClientProxyGenerator,
                ProxyClass,
                ex => string.Format(CultureInfo.CurrentCulture, Resource.ClientCodeGen_Attribute_ThrewException_CodeTypeMember, ex.Message, property.Name, ProxyClass.Name, ex.InnerException.Message),
                propertyAttributes.Cast <Attribute>(),
                property.CustomAttributes,
                property.Comments);

            // ----------------------------------------------------------------
            // backing private field (CodeDom doesn't yet know about auto properties)
            // ----------------------------------------------------------------
            string fieldName = CodeGenUtilities.MakeCompliantFieldName(propertyName);
            var    field     = new CodeMemberField(propTypeReference, fieldName);

            ProxyClass.Members.Add(field);
            var fieldRef = new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName);
            var valueRef = new CodePropertySetValueReferenceExpression();

            // ----------------------------------------------------------------
            // getter body
            // ----------------------------------------------------------------
            property.GetStatements.Add(new CodeMethodReturnStatement(fieldRef));

            // ----------------------------------------------------------------
            // setter body
            // ----------------------------------------------------------------
            List <CodeStatement> bodyStatements = new List <CodeStatement>();

            // this.OnPropertyXxxChanging(PropType value);
            bodyStatements.Add(NotificationMethodGen.GetMethodInvokeExpressionStatementFor(propertyName + "Changing"));

            bool propertyIsReadOnly = IsPropertyReadOnly(propertyDescriptor);

            if (!propertyIsReadOnly)
            {
                bodyStatements.Add(new CodeExpressionStatement(new CodeMethodInvokeExpression(new CodeThisReferenceExpression(), "RaiseDataMemberChanging", new CodePrimitiveExpression(propertyDescriptor.Name))));
            }

            // Generate the validation tests.
            CodeStatement validationCode = GeneratePropertySetterValidation(propertyDescriptor.Name);

            bodyStatements.Add(validationCode);

            // this._field = value
            bodyStatements.Add(new CodeAssignStatement(fieldRef, valueRef));

            if (!propertyIsReadOnly)
            {
                bodyStatements.Add(new CodeExpressionStatement(new CodeMethodInvokeExpression(new CodeThisReferenceExpression(), "RaiseDataMemberChanged", new CodePrimitiveExpression(propertyDescriptor.Name))));
            }
            else
            {
                // even read-only members need to raise PropertyChanged
                bodyStatements.Add(new CodeExpressionStatement(new CodeMethodInvokeExpression(new CodeThisReferenceExpression(), "RaisePropertyChanged", new CodePrimitiveExpression(propertyDescriptor.Name))));
            }

            // this.OnPropertyXxxChanged();
            bodyStatements.Add(NotificationMethodGen.GetMethodInvokeExpressionStatementFor(propertyName + "Changed"));

            // if (this._field != value)...
            CodeExpression valueTest = CodeGenUtilities.MakeNotEqual(propertyType, fieldRef, valueRef, ClientProxyGenerator.IsCSharp);

            CodeConditionStatement body = new CodeConditionStatement(valueTest, bodyStatements.ToArray <CodeStatement>());

            property.SetStatements.Add(body);

            // add property
            ProxyClass.Members.Add(property);
        }