public void SharedCodeService_Ctors() { string projectPath = null; string outputPath = null; TestHelper.GetProjectPaths("STS5", out projectPath, out outputPath); string clientProjectPath = CodeGenHelper.ClientClassLibProjectPath(projectPath); ConsoleLogger logger = new ConsoleLogger(); using (SharedCodeService sts = CodeGenHelper.CreateSharedCodeService(clientProjectPath, logger)) { ConstructorInfo ctor = typeof(TestValidator).GetConstructor(new Type[] { typeof(string) }); Assert.IsNotNull("Failed to find string ctor on TestValidator"); CodeMemberShareKind shareKind = sts.GetMethodShareKind(typeof(TestValidator).AssemblyQualifiedName, ctor.Name, new string[] { typeof(string).AssemblyQualifiedName }); Assert.AreEqual(CodeMemberShareKind.SharedByReference, shareKind, "Expected TestValidator ctor to be shared by reference"); TestHelper.AssertNoErrorsOrWarnings(logger); } }
public void SharedCodeService_Properties() { string projectPath = null; string outputPath = null; TestHelper.GetProjectPaths("STS2", out projectPath, out outputPath); string clientProjectPath = CodeGenHelper.ClientClassLibProjectPath(projectPath); ConsoleLogger logger = new ConsoleLogger(); using (SharedCodeService sts = CodeGenHelper.CreateSharedCodeService(clientProjectPath, logger)) { CodeMemberShareKind shareKind = sts.GetPropertyShareKind(typeof(TestEntity).AssemblyQualifiedName, "ServerAndClientValue"); Assert.AreEqual(CodeMemberShareKind.SharedByReference, shareKind, "Expected TestEntity.ServerAndClientValue property to be shared by reference."); shareKind = sts.GetPropertyShareKind(typeof(TestEntity).AssemblyQualifiedName, "TheValue"); Assert.AreEqual(CodeMemberShareKind.NotShared, shareKind, "Expected TestEntity.TheValue property not to be shared in source."); } }
public void SharedCodeService_Types() { string projectPath = null; string outputPath = null; TestHelper.GetProjectPaths("", out projectPath, out outputPath); string clientProjectPath = CodeGenHelper.ClientClassLibProjectPath(projectPath); ConsoleLogger logger = new ConsoleLogger(); using (SharedCodeService sts = CodeGenHelper.CreateSharedCodeService(clientProjectPath, logger)) { // TestEntity is shared because it is linked CodeMemberShareKind shareKind = sts.GetTypeShareKind(typeof(TestEntity).AssemblyQualifiedName); Assert.AreEqual(CodeMemberShareKind.SharedByReference, shareKind, "Expected TestEntity type to be shared by reference"); // TestValidator is shared because it is linked shareKind = sts.GetTypeShareKind(typeof(TestValidator).AssemblyQualifiedName); Assert.AreEqual(CodeMemberShareKind.SharedByReference, shareKind, "Expected TestValidator type to be shared by reference"); // SharedClass is shared because it is linked shareKind = sts.GetTypeShareKind(typeof(SharedClass).AssemblyQualifiedName); Assert.IsTrue(shareKind == CodeMemberShareKind.SharedBySource, "Expected SharedClass type to be shared in source"); // DomainService exists only in the server and is not shared shareKind = sts.GetTypeShareKind(typeof(DomainService).AssemblyQualifiedName); Assert.IsTrue(shareKind == CodeMemberShareKind.NotShared, "Expected DomainService type not to be shared"); // TestValidatorServer exists only on the server and is not shared shareKind = sts.GetTypeShareKind(typeof(TestValidatorServer).AssemblyQualifiedName); Assert.IsTrue(shareKind == CodeMemberShareKind.NotShared, "Expected TestValidatorServer type not to be shared"); // CodelessType exists on both server and client, but lacks all user code necessary // to determine whether it is shared. Because it compiles into both projects, it should // be considered shared by finding the type in both assemblies shareKind = sts.GetTypeShareKind(typeof(CodelessType).AssemblyQualifiedName); Assert.IsTrue(shareKind == CodeMemberShareKind.SharedByReference, "Expected CodelessType type to be shared in assembly"); } }
/// <summary> /// Generates all of the properties for the type. /// </summary> private void GenerateProperties() { IEnumerable <PropertyDescriptor> properties = TypeDescriptor.GetProperties(this.Type) .Cast <PropertyDescriptor>() .OrderBy(p => p.Name); foreach (PropertyDescriptor pd in properties) { if (!this.ShouldDeclareProperty(pd)) { continue; } // Generate a property getter/setter pair for every property whose type // we support. Non supported property types will be skipped. if (this.CanGenerateProperty(pd)) { // Ensure the property is not virtual, abstract or new // If there is a violation, we log the error and keep // running to accumulate all such errors. This function // may return an "okay" for non-error case polymorphics. if (!this.CanGeneratePropertyIfPolymorphic(pd)) { continue; } if (!this.GenerateNonSerializableProperty(pd)) { Type propType = CodeGenUtilities.TranslateType(pd.PropertyType); List <Type> typesToCodeGen = new List <Type>(); bool isTypeSafeToGenerate = true; // Create a list containing the types we will require on the client if (TypeUtility.IsPredefinedDictionaryType(propType)) { typesToCodeGen.AddRange(CodeGenUtilities.GetDictionaryGenericArgumentTypes(propType)); } else { typesToCodeGen.Add(TypeUtility.GetElementType(propType)); } // We consider all predefined types as legal to code-gen *except* those // that would generate a compile error on the client due to missing reference. // We treat "don't know" and "false" as grounds for a warning. // Note that we do this *after* TranslateType so that types like System.Data.Linq.Binary // which cannot exist on the client anyway has been translated foreach (Type type in typesToCodeGen) { // Enum (and nullable<enum>) types may require generation on client Type nonNullableType = TypeUtility.GetNonNullableType(type); if (nonNullableType.IsEnum) { // Register use of this enum type, which could cause deferred generation this.ClientProxyGenerator.RegisterUseOfEnumType(nonNullableType); } // If this is not an enum or nullable<enum> and we're not generating the complex type, determine whether this // property type is visible to the client. If it is not, log a warning. else if (!this.ComplexTypes.Contains(type)) { // "Don't know" counts as "no" CodeMemberShareKind enumShareKind = this.ClientProxyGenerator.GetTypeShareKind(nonNullableType); if ((enumShareKind & CodeMemberShareKind.Shared) == 0) { this.ClientProxyGenerator.LogWarning(string.Format(CultureInfo.CurrentCulture, Resource.ClientCodeGen_PropertyType_Not_Shared, pd.Name, this.Type.FullName, type.FullName, this.ClientProxyGenerator.ClientProjectName)); isTypeSafeToGenerate = false; // Flag error but continue to allow accumulation of additional errors. } } } if (isTypeSafeToGenerate) { // Generate OnMethodXxxChanging/Changed partial methods. // Note: the parameter type reference needs to handle the possibility the // property type is defined in the project's root namespace and that VB prepends // that namespace. The utility helper gives us the right type reference. CodeTypeReference parameterTypeRef = CodeGenUtilities.GetTypeReference(propType, this.ClientProxyGenerator, this.ProxyClass); this.NotificationMethodGen.AddMethodFor(pd.Name + "Changing", new CodeParameterDeclarationExpression(parameterTypeRef, "value"), null); this.NotificationMethodGen.AddMethodFor(pd.Name + "Changed", null); this.GenerateProperty(pd); } } } else { this.OnPropertySkipped(pd); } } }
public SharedCodeDescription(CodeMemberShareKind shareKind, int[] sharedFileIds) { this._shareKind = shareKind; this._sharedFileIds = sharedFileIds; }
private static void ValidateAttributeDeclarationRequirements(AttributeDeclaration attributeDeclaration, ClientCodeGenerator textTemplateClientCodeGenerator) { // Verify the attribute itself is shared. CodeMemberShareKind shareKind = textTemplateClientCodeGenerator.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, textTemplateClientCodeGenerator.ClientProjectName)); } else if (shareKind == CodeMemberShareKind.NotShared) { attributeDeclaration.Errors.Add( string.Format( CultureInfo.CurrentCulture, Resource.ClientCodeGen_Attribute_RequiresShared, attributeDeclaration.AttributeType, textTemplateClientCodeGenerator.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 = textTemplateClientCodeGenerator.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, textTemplateClientCodeGenerator.ClientProjectName)); } else if (shareKind == CodeMemberShareKind.NotShared) { attributeDeclaration.Errors.Add( string.Format( CultureInfo.CurrentCulture, Resource.ClientCodeGen_Attribute_RequiresShared_Type, attributeDeclaration.AttributeType, type, textTemplateClientCodeGenerator.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 = textTemplateClientCodeGenerator.GetMethodShareKind(method); if (shareKind == CodeMemberShareKind.NotShared) { attributeDeclaration.Errors.Add( string.Format( CultureInfo.CurrentCulture, Resource.ClientCodeGen_Attribute_RequiresShared_Method, attributeDeclaration.AttributeType, method.Name, method.DeclaringType, textTemplateClientCodeGenerator.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 = textTemplateClientCodeGenerator.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, textTemplateClientCodeGenerator.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); }
private string GenerateProxyClass() { string generatedCode = string.Empty; // Analyze the assemblies to extract all the DomainServiceDescriptions ICollection <DomainServiceDescription> allDescriptions = this._domainServiceDescriptions; List <Type> generatedEntityTypes = new List <Type>(); List <Type> generatedComplexTypes = new List <Type>(); Dictionary <Type, CodeTypeDeclaration> typeMapping = new Dictionary <Type, CodeTypeDeclaration>(); // Used to queue CodeProcessor invocations Queue <CodeProcessorWorkItem> codeProcessorQueue = new Queue <CodeProcessorWorkItem>(); // Before we begin codegen, we want to register type names with our codegen // utilities so that we can avoid type name conflicts later. this.PreprocessProxyTypes(); // Generate a new domain service proxy class for each domain service we found. // OrderBy type name of DomainService to give code-gen predictability foreach (DomainServiceDescription dsd in allDescriptions.OrderBy(d => d.DomainServiceType.Name)) { // If we detect the client already has the DomainContext we would have // generated, skip it. This condition arises when the client has references // to class libraries as well as a code-gen link to the server which has // references to the server-side equivalent libraries. Without this check, we would // re-generate the same DomainContext that already lives in the class library. CodeMemberShareKind domainContextShareKind = this.GetDomainContextTypeMemberShareKind(dsd); if ((domainContextShareKind & CodeMemberShareKind.Shared) != 0) { this.LogMessage(string.Format(CultureInfo.CurrentCulture, Resource.Shared_DomainContext_Skipped, dsd.DomainServiceType.Name)); continue; } // Log information level message to help users see progress and debug code-gen issues this.LogMessage(string.Format(CultureInfo.CurrentCulture, Resource.CodeGen_Generating_DomainService, dsd.DomainServiceType.FullName)); new DomainServiceProxyGenerator(this, dsd, typeMapping).Generate(); // Generate all entities. this.GenerateDataContractTypes(dsd.EntityTypes, generatedEntityTypes, Resource.ClientCodeGen_EntityTypesCannotBeShared_Reference, t => { new EntityProxyGenerator(this, t, allDescriptions, typeMapping).Generate(); }); // Generate all complex types. this.GenerateDataContractTypes(dsd.ComplexTypes, generatedComplexTypes, Resource.ClientCodeGen_ComplexTypesCannotBeShared_Reference, t => { new ComplexTypeProxyGenerator(this, t, dsd, typeMapping).Generate(); }); DomainIdentifierAttribute domainIdentifier = dsd.Attributes.OfType <DomainIdentifierAttribute>().SingleOrDefault(); if (domainIdentifier != null) { Type codeProcessorType = domainIdentifier.CodeProcessor; if (codeProcessorType != null) { // Create a limited type-mapping dictionary that contains references to // the DomainService and related entity type CodeTypeDeclarations. Dictionary <Type, CodeTypeDeclaration> scopedTypeMapping = typeMapping.Where(kvp => kvp.Key == dsd.DomainServiceType || dsd.EntityTypes.Contains(kvp.Key)) .ToDictionary(kvp => kvp.Key, kvp => kvp.Value); // Register CodeProc codeProcessorQueue.Enqueue( new CodeProcessorWorkItem(codeProcessorType, dsd, scopedTypeMapping)); } } } // Generate an application-level WebContext class if (this.ClientProxyCodeGenerationOptions.IsApplicationContextGenerationEnabled) { new WebContextGenerator(this).Generate(); } // If there are no descriptions, we do not generate any client proxies // We don't consider this an error, since this task might be invoked before the user has created any. else if (allDescriptions.Count == 0) { return(generatedCode); } // Generate any enum types we have decided we need to generate this.GenerateAllEnumTypes(); // Fix up CodeDOM graph before invoking CodeProcessors this.FixUpCompileUnit(this.CompileUnit); // Invoke CodeProcessors after we've completed our CodeDOM graph while (codeProcessorQueue.Count > 0) { // Allow CodeProcessors to do post processing work CodeProcessorWorkItem workItem = codeProcessorQueue.Dequeue(); this.InvokeDomainServiceCodeProcessor(workItem); } // Write the entire "file" to a single string to permit us to redirect it // to a file, a TextBuffer, etc if (!this.CodeGenerationHost.HasLoggedErrors) { using (TextWriter t = new StringWriter(CultureInfo.InvariantCulture)) { this.Provider.GenerateCodeFromCompileUnit(this.CompileUnit, t, this._options); generatedCode = this.FixupVBOptionStatements(t.ToString()); } } return(generatedCode); }