|
#region Copyright (c) Microsoft Corporation
/// <copyright company='Microsoft Corporation'>
/// Copyright (c) Microsoft Corporation. All Rights Reserved.
/// Information Contained Herein is Proprietary and Confidential.
/// </copyright>
#endregion
using System.CodeDom;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Reflection;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Configuration;
using System.ServiceModel.Description;
using System.Xml;
using System.Xml.Schema;
using System.Security.Permissions;
using System.Linq;
#if WEB_EXTENSIONS_CODE
using System.Security;
using System.Web.Resources;
#else
using Microsoft.VSDesigner.WCF.Resources;
#endif
///
/// The VSWCFServiceContractGenerator takes a SvcMap file and it's associated metadata,
/// imports the metadata using a WsdlImporter and System.ServiceModel.ServiceContractGenerator
/// that are configured according to the options set in the SvcMap file
///
using Debug = System.Diagnostics.Debug;
using System.Diagnostics.CodeAnalysis;
#if WEB_EXTENSIONS_CODE
namespace System.Web.Compilation.WCFModel
#else
namespace Microsoft.VSDesigner.WCFModel
#endif
{
/// <summary>
/// Proxy and configuration generator
/// </summary>
#if WEB_EXTENSIONS_CODE
[PermissionSet(SecurityAction.InheritanceDemand, Name="FullTrust")]
[SecurityCritical]
internal class VSWCFServiceContractGenerator
#else
// We only check for CLS compliant for the public version of this class since the
// compiler will complain about CLS compliance not being checked for non-public classes
[CLSCompliant(true)]
[PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")]
[PermissionSet(SecurityAction.InheritanceDemand, Name = "FullTrust")]
public class VSWCFServiceContractGenerator
#endif
{
#region Private backing fields
private const string VB_LANGUAGE_NAME = "vb";
/// <summary>
/// Collection to hold all bindings generated by this generator
/// </summary>
private IEnumerable<System.ServiceModel.Channels.Binding> bindingCollection;
/// <summary>
/// Collection to hold all contracts generated by this generator
/// </summary>
private IEnumerable<ContractDescription> contractCollection;
/// <summary>
/// Collection to hold all endpoints generated by this generator
/// </summary>
private List<ServiceEndpoint> serviceEndpointList;
/// <summary>
/// Map from service endpoint to the channel endpoint that was actually imported
/// </summary>
private Dictionary<ServiceEndpoint, ChannelEndpointElement> serviceEndpointToChannelEndpointElementMap;
/// <summary>
/// List of contract types generate by this generator
/// </summary>
private List<GeneratedContractType> proxyGeneratedContractTypes;
/// <summary>
/// The target compile unit that contains the proxy and data contracts
/// </summary>
private CodeCompileUnit targetCompileUnit;
/// <summary>
/// Configuration object that we inserterd bindings and endpoints from the
/// current service into. May be Null/Nothing.
/// </summary>
private System.Configuration.Configuration targetConfiguration;
/// <summary>
/// Errors encountered while generating the proxy
/// </summary>
private IEnumerable<ProxyGenerationError> proxyGenerationErrors;
/// <summary>
/// Errors encountered while importing the metadata..
/// </summary>
private IList<ProxyGenerationError> importErrors;
/// <summary>
/// Helper property that is added to Out parameters for VB
/// </summary>
private static CodeAttributeDeclaration outAttribute;
/// <summary>
/// version number for 3.5 framework
/// </summary>
private const int FRAMEWORK_VERSION_35 = 0x30005;
/// <summary>
/// list of types which are new in the 3.5 framework.
/// </summary>
private static Type[] unsupportedTypesInFramework30 = new Type[] {
typeof(DateTimeOffset),
};
#endregion
#region Public read-only properties
/// <summary>
/// The collection of bindings generated by this generator
/// </summary>
/// <value></value>
/// <remarks>
/// </remarks>
public IEnumerable<System.ServiceModel.Channels.Binding> BindingCollection
{
get
{
System.Diagnostics.Debug.Assert(bindingCollection != null);
return bindingCollection;
}
}
/// <summary>
/// The collection of generated contract types
/// </summary>
public IEnumerable<GeneratedContractType> ProxyGeneratedContractTypes
{
get
{
System.Diagnostics.Debug.Assert(proxyGeneratedContractTypes != null);
return proxyGeneratedContractTypes;
}
}
/// <summary>
/// The collection of errors encountered while generating the
/// proxy. For errors related to the metadata import, use the
/// ImportErrors property
/// </summary>
public IEnumerable<ProxyGenerationError> ProxyGenerationErrors
{
get
{
System.Diagnostics.Debug.Assert(proxyGenerationErrors != null);
return proxyGenerationErrors;
}
}
/// <summary>
/// The collection of errors encountered while importing metadata.
/// For errors related to the proxy and config generation, use the
/// ProxyGenerationErrors property
/// </summary>
public IEnumerable<ProxyGenerationError> ImportErrors
{
get
{
System.Diagnostics.Debug.Assert(importErrors != null);
return importErrors;
}
}
/// <summary>
/// The collection of contracts imported by this generator
/// </summary>
/// <value></value>
/// <remarks></remarks>
public IEnumerable<ContractDescription> ContractCollection
{
get
{
System.Diagnostics.Debug.Assert(contractCollection != null);
return contractCollection;
}
}
/// <summary>
/// Collection of Endpoints in the service model generated by
/// this generator
/// </summary>
/// <value></value>
/// <remarks></remarks>
public IEnumerable<ServiceEndpoint> EndpointCollection
{
get
{
System.Diagnostics.Debug.Assert(serviceEndpointList != null);
return serviceEndpointList;
}
}
/// <summary>
/// Map from service endpoints to its corresponding channel endpoint configuration
/// element
/// </summary>
public Dictionary<ServiceEndpoint, ChannelEndpointElement> EndpointMap
{
get
{
System.Diagnostics.Debug.Assert(serviceEndpointToChannelEndpointElementMap != null);
return serviceEndpointToChannelEndpointElementMap;
}
}
/// <summary>
/// The configuratin into which we inject the bindings and endpoints. May be null/Nothing
/// if no target configuration was provided.
/// </summary>
public System.Configuration.Configuration TargetConfiguration
{
get
{
// Note: it is valid for this to be NULL. Caller beware!
return targetConfiguration;
}
}
/// <summary>
/// CodeCompileUnit containing the generated data contracts, service contracts
/// and WCF client.
/// </summary>
public CodeCompileUnit TargetCompileUnit
{
get
{
System.Diagnostics.Debug.Assert(targetCompileUnit != null);
return targetCompileUnit;
}
}
#endregion
/// <summary>
/// Cached instance of an Out attribute that we use to patch up
/// the codegen for VB projects (the VB code generates out parameters
/// as ByRef)
/// </summary>
private static CodeAttributeDeclaration OutAttribute
{
get
{
if (outAttribute == null)
{
outAttribute = new CodeAttributeDeclaration(typeof(System.Runtime.InteropServices.OutAttribute).FullName);
}
return outAttribute;
}
}
/// <summary>
/// protected constructor to block creating instance directly.
/// </summary>
/// <param name="importErrors"></param>
/// <param name="targetCompileUnit"></param>
/// <param name="targetConfiguration">May be null</param>
/// <param name="bindingCollection"></param>
/// <param name="contractCollection"></param>
/// <param name="serviceEndpointList"></param>
/// <param name="serviceEndpointToChannelEndpointElementMap"></param>
/// <param name="proxyGeneratedContractTypes"></param>
/// <param name="proxyGenerationErrors"></param>
protected VSWCFServiceContractGenerator(
List<ProxyGenerationError> importErrors,
CodeCompileUnit targetCompileUnit,
System.Configuration.Configuration targetConfiguration,
IEnumerable<System.ServiceModel.Channels.Binding> bindingCollection,
IEnumerable<ContractDescription> contractCollection,
List<ServiceEndpoint> serviceEndpointList,
Dictionary<ServiceEndpoint, ChannelEndpointElement> serviceEndpointToChannelEndpointElementMap,
List<GeneratedContractType> proxyGeneratedContractTypes,
IEnumerable<ProxyGenerationError> proxyGenerationErrors)
{
if (importErrors == null) throw new ArgumentNullException("importErrors");
if (targetCompileUnit == null) throw new ArgumentNullException("targetCompileUnit");
// Please note - target configuration may be NULL
if (bindingCollection == null) throw new ArgumentNullException("bindingCollection");
if (contractCollection == null) throw new ArgumentNullException("contractCollection");
if (serviceEndpointList == null) throw new ArgumentNullException("serviceEndpointList");
if (serviceEndpointToChannelEndpointElementMap == null) throw new ArgumentNullException("serviceEndpointToChannelEndpointElementMap");
if (proxyGeneratedContractTypes == null) throw new ArgumentNullException("proxyGeneratedContractTypes");
if (proxyGenerationErrors == null) throw new ArgumentNullException("proxyGenerationErrors");
this.importErrors = importErrors;
this.targetCompileUnit = targetCompileUnit;
this.targetConfiguration = targetConfiguration;
this.bindingCollection = bindingCollection;
this.contractCollection = contractCollection;
this.serviceEndpointList = serviceEndpointList;
this.serviceEndpointToChannelEndpointElementMap = serviceEndpointToChannelEndpointElementMap;
this.proxyGeneratedContractTypes = proxyGeneratedContractTypes;
this.proxyGenerationErrors = proxyGenerationErrors;
}
/// <summary>
/// Factory method: generate code and return the resulting VSWCFServiceContractGenerator.
/// </summary>
/// <param name="svcMapFile">
/// The SvcMapFile that lists the metadata and generation options for the service reference.
/// </param>
/// <param name="toolConfiguration">
/// Configuration from which we are going to pick up WSDL and policy importer extensions as well
/// as custom MEX bindings for metadata download. May be Null/Nothing.
/// </param>
/// <param name="codeDomProvider">
/// CodeDom provider that is to be used to generate the client code.
/// </param>
/// <param name="proxyNamespace">
/// CLR namespace in which to generate the client code.
/// </param>
/// <param name="targetConfiguration">
/// The configuration into which we will put bindings/endpoints for this service
/// reference. May be Null/Nothing.
/// </param>
/// <param name="configurationNamespace">
/// The namespace that is to be used in configuration for this service reference.
/// </param>
/// <param name="serviceProviderForImportExtensions">
/// Service provider that we'll pass on to import extensions that accept our site:ing
/// mechanism
/// </param>
/// <param name="typeLoader">
/// Type loader that can be used to find reference assemblies and/or resolve shared service and
/// data contract types.
/// </param>
/// <param name="targetFrameworkVersion">
/// The target framework version number. The higher 16 bits contains the major version number, and low 16 bits contains minor version number.
/// </param>
/// <param name="typedDataSetSchemaImporterExtension">
/// Schema importer extension to be used for typed datasets.
/// </param>
/// <returns>
/// A VSWCFServiceContractGenerator instance that contains the result of the generation. To get
/// hold of the generated information, you can query it's properties.
/// </returns>
public static VSWCFServiceContractGenerator GenerateCodeAndConfiguration(SvcMapFile svcMapFile,
System.Configuration.Configuration toolConfiguration,
System.CodeDom.Compiler.CodeDomProvider codeDomProvider,
string proxyNamespace,
System.Configuration.Configuration targetConfiguration,
string configurationNamespace,
IServiceProvider serviceProviderForImportExtensions,
IContractGeneratorReferenceTypeLoader typeLoader,
int targetFrameworkVersion,
System.Type typedDataSetSchemaImporterExtension)
{
if (svcMapFile == null) throw new ArgumentNullException("svcMapFile");
if (codeDomProvider == null) throw new ArgumentNullException("codeDomProvider");
if (typedDataSetSchemaImporterExtension == null) throw new ArgumentNullException("typedDataSetSchemaImporterExtension");
List<ProxyGenerationError> importErrors = new List<ProxyGenerationError>();
List<ProxyGenerationError> proxyGenerationErrors = new List<ProxyGenerationError>();
CodeCompileUnit targetCompileUnit = new CodeCompileUnit();
WsdlImporter wsdlImporter = CreateWsdlImporter(svcMapFile,
toolConfiguration,
targetCompileUnit,
codeDomProvider,
proxyNamespace,
serviceProviderForImportExtensions,
typeLoader,
targetFrameworkVersion,
importErrors,
typedDataSetSchemaImporterExtension);
ServiceContractGenerator contractGenerator = CreateContractGenerator(svcMapFile.ClientOptions,
wsdlImporter,
targetCompileUnit,
proxyNamespace,
targetConfiguration,
typeLoader,
targetFrameworkVersion,
importErrors);
try
{
List<ServiceEndpoint> serviceEndpointList = new List<ServiceEndpoint>();
IEnumerable<System.ServiceModel.Channels.Binding> bindingCollection;
IEnumerable<ContractDescription> contractCollection;
ImportWCFModel(wsdlImporter,
targetCompileUnit,
importErrors,
out serviceEndpointList,
out bindingCollection,
out contractCollection);
Dictionary<ServiceEndpoint, ChannelEndpointElement> serviceEndpointToChannelEndpointElementMap;
List<GeneratedContractType> proxyGeneratedContractTypes;
GenerateProxy(wsdlImporter,
contractGenerator,
targetCompileUnit,
proxyNamespace,
configurationNamespace,
contractCollection,
bindingCollection,
serviceEndpointList,
proxyGenerationErrors,
out serviceEndpointToChannelEndpointElementMap,
out proxyGeneratedContractTypes);
if (IsVBCodeDomProvider(codeDomProvider))
{
PatchOutParametersInVB(targetCompileUnit);
}
return new VSWCFServiceContractGenerator(importErrors,
targetCompileUnit,
targetConfiguration,
bindingCollection,
contractCollection,
serviceEndpointList,
serviceEndpointToChannelEndpointElementMap,
proxyGeneratedContractTypes,
proxyGenerationErrors);
}
catch (Exception ex)
{
// fatal error... (workaround for bug #135242)
// We want to convert fatal error exception to a normal code generator error message,
// so the user could find information from pervious errors to find KB topic.
proxyGenerationErrors.Add(new ProxyGenerationError(
ProxyGenerationError.GeneratorState.GenerateCode,
String.Empty,
ex,
false));
return new VSWCFServiceContractGenerator(importErrors,
new CodeCompileUnit(),
targetConfiguration,
new List<System.ServiceModel.Channels.Binding>(),
new List<ContractDescription>(),
new List<ServiceEndpoint>(),
new Dictionary<ServiceEndpoint, ChannelEndpointElement>(),
new List<GeneratedContractType>(),
proxyGenerationErrors);
}
}
/// <summary>
/// Instantiate and configure a ServiceContractGenerator to be used for code and config
/// generation.
/// </summary>
/// <param name="proxyOptions">
/// Options set in the SvcMap file to control the code/config generation.
/// </param>
/// <param name="wsdlImporter">
/// The WsdlImporter that is to be used to import the metadata for this service reference.
/// </param>
/// <param name="targetCompileUnit">
/// Compile unit into which we will generate the client code
/// </param>
/// <param name="proxyNamespace">
/// The CLR namespace into which we will generate the client code.
/// </param>
/// <param name="targetConfiguration">
/// Optional configuration into which we will generate the endpoints/bindings corresponding
/// to this service reference. May be Null/Nothing, in which case we will not generate config.
/// </param>
/// <param name="typeLoader">
/// Type loader that can be used to find reference assemblies and/or resolve shared service and
/// data contract types.
/// </param>
/// <param name="targetFrameworkVersion">
/// The target framework version number. The higher 16 bits contains the major version number, and low 16 bits contains minor version number.
/// </param>
/// <param name="importErrors">
/// The list into which we will add any errors while importing the metadata.
/// </param>
/// <returns></returns>
protected static ServiceContractGenerator CreateContractGenerator(ClientOptions proxyOptions,
WsdlImporter wsdlImporter,
CodeCompileUnit targetCompileUnit,
string proxyNamespace,
System.Configuration.Configuration targetConfiguration,
IContractGeneratorReferenceTypeLoader typeLoader,
int targetFrameworkVersion,
IList<ProxyGenerationError> importErrors)
{
ServiceContractGenerator contractGenerator = new ServiceContractGenerator(targetCompileUnit, targetConfiguration);
// We want to generate all types into the proxy namespace CLR namespace. We indicate
// this by adding a namespace mapping from all XML namespaces ("*") to the namespace
// the caller told us to generate the client code in.
contractGenerator.NamespaceMappings.Add("*", proxyNamespace);
if (proxyOptions.GenerateInternalTypes)
{
contractGenerator.Options |= ServiceContractGenerationOptions.InternalTypes;
}
else
{
contractGenerator.Options &= ~ServiceContractGenerationOptions.InternalTypes;
}
// Make sure at most one of the async options will be set: AsynchronousMethods | TaskBasedAsynchronousMethod.
contractGenerator.Options &= ~ServiceContractGenerationOptions.AsynchronousMethods &
~ServiceContractGenerationOptions.EventBasedAsynchronousMethods &
~ServiceContractGenerationOptions.TaskBasedAsynchronousMethod;
if (proxyOptions.GenerateTaskBasedAsynchronousMethod)
{
contractGenerator.Options |= ServiceContractGenerationOptions.TaskBasedAsynchronousMethod;
}
else if (proxyOptions.GenerateAsynchronousMethods)
{
contractGenerator.Options |= ServiceContractGenerationOptions.AsynchronousMethods;
if (targetFrameworkVersion >= FRAMEWORK_VERSION_35)
{
contractGenerator.Options |= ServiceContractGenerationOptions.EventBasedAsynchronousMethods;
}
}
if (proxyOptions.GenerateMessageContracts)
{
contractGenerator.Options |= ServiceContractGenerationOptions.TypedMessages;
}
else
{
contractGenerator.Options &= ~ServiceContractGenerationOptions.TypedMessages;
}
// If we have a type loader, we tell the contract generator and wsdl importer about
// all shared types and assemblies that we've specified in the proxy options...
if (typeLoader != null)
{
foreach (ContractMapping mapping in proxyOptions.ServiceContractMappingList)
{
try
{
Type sharedType = typeLoader.LoadType(mapping.TypeName);
// verify that the type is shareable - if not, we generate an error...
if (!IsTypeShareable(sharedType))
{
importErrors.Add(
new ProxyGenerationError(
ProxyGenerationError.GeneratorState.GenerateCode,
String.Empty,
new FormatException(String.Format(CultureInfo.CurrentCulture, WCFModelStrings.ReferenceGroup_SharedTypeMustBePublic, mapping.TypeName)))
);
continue;
}
// Get a contract description corresponding to the type we wanted to share
ContractDescription contract = ContractDescription.GetContract(sharedType);
if (!String.Equals(mapping.Name, contract.Name, StringComparison.Ordinal) ||
!String.Equals(mapping.TargetNamespace, contract.Namespace, StringComparison.Ordinal))
{
// mismatch
importErrors.Add(
new ProxyGenerationError(
ProxyGenerationError.GeneratorState.GenerateCode,
String.Empty,
new FormatException(String.Format(CultureInfo.CurrentCulture, WCFModelStrings.ReferenceGroup_ServiceContractMappingMissMatch, mapping.TypeName, contract.Namespace, contract.Name, mapping.TargetNamespace, mapping.Name)))
);
}
XmlQualifiedName qname = new XmlQualifiedName(contract.Name, contract.Namespace);
wsdlImporter.KnownContracts.Add(qname, contract);
contractGenerator.ReferencedTypes.Add(contract, sharedType);
}
catch (Exception ex)
{
importErrors.Add(new ProxyGenerationError(
ProxyGenerationError.GeneratorState.GenerateCode,
String.Empty,
ex));
}
}
}
foreach (NamespaceMapping namespaceMapping in proxyOptions.NamespaceMappingList)
{
contractGenerator.NamespaceMappings.Add(namespaceMapping.TargetNamespace, namespaceMapping.ClrNamespace);
}
return contractGenerator;
}
/// <summary>
/// Generate Proxy Code and (if available) configuration
/// </summary>
/// <param name="contractGenerator">The contract generator to use</param>
/// <param name="targetCompileUnit">Compile unit into which we should generate the client code</param>
/// <param name="proxyNamespace">CLR namespace into which we should generate the client code</param>
/// <param name="configurationNamespace">Namespace to use in configuration</param>
/// <param name="contractCollection">The contracts for which we should generate code and optionally config</param>
/// <param name="bindingCollection">The bindings we should generate config for</param>
/// <param name="serviceEndpointList">The endpoints we should generate config for</param>
/// <param name="proxyGenerationErrors">A list of errors encountered while generating the client</param>
/// <param name="serviceEndpointToChannelEndpointElementMap">Map from service endpoint to the configuration element for the endpoint</param>
/// <param name="proxyGeneratedContractTypes">The generated contract types</param>
protected static void GenerateProxy(WsdlImporter importer,
ServiceContractGenerator contractGenerator,
CodeCompileUnit targetCompileUnit,
string proxyNamespace,
string configurationNamespace,
IEnumerable<ContractDescription> contractCollection,
IEnumerable<System.ServiceModel.Channels.Binding> bindingCollection,
List<ServiceEndpoint> serviceEndpointList,
IList<ProxyGenerationError> proxyGenerationErrors,
out Dictionary<ServiceEndpoint, ChannelEndpointElement> serviceEndpointToChannelEndpointElementMap,
out List<GeneratedContractType> proxyGeneratedContractTypes)
{
// Parameter checking
if (serviceEndpointList == null) throw new ArgumentNullException("serviceEndpointList");
if (bindingCollection == null) throw new ArgumentNullException("bindingCollection");
if (contractCollection == null) throw new ArgumentNullException("contractCollection");
if (proxyGenerationErrors == null) throw new ArgumentNullException("proxyGenerationErrors");
proxyGeneratedContractTypes = new List<GeneratedContractType>();
serviceEndpointToChannelEndpointElementMap = new Dictionary<ServiceEndpoint, ChannelEndpointElement>();
try
{
HttpBindingExtension httpBindingEx = importer.WsdlImportExtensions.Find<HttpBindingExtension>();
foreach (ContractDescription contract in contractCollection)
{
if (httpBindingEx == null || !httpBindingEx.IsHttpBindingContract(contract) || serviceEndpointList.Any(endpoint => endpoint.Contract == contract))
{
CodeTypeReference typeReference = contractGenerator.GenerateServiceContractType(contract);
if (typeReference != null)
{
// keep the (targetNamespace, portType) -> CLR type map table...
string baseType = typeReference.BaseType;
GeneratedContractType generatedType = new GeneratedContractType(contract.Namespace, contract.Name, baseType, baseType);
proxyGeneratedContractTypes.Add(generatedType);
}
}
}
// We should only import the Binding & Endpoints if there is a configuration storage...
if (contractGenerator.Configuration != null)
{
foreach (ServiceEndpoint endpoint in serviceEndpointList)
{
ChannelEndpointElement endpointElement = null;
contractGenerator.GenerateServiceEndpoint(endpoint, out endpointElement);
serviceEndpointToChannelEndpointElementMap[endpoint] = endpointElement;
}
foreach (System.ServiceModel.Channels.Binding bindingDescription in bindingCollection)
{
string bindingSectionName = null;
string bindingConfigurationName = null;
// Generate binding will change the state of the contractGenerator...
contractGenerator.GenerateBinding(bindingDescription, out bindingSectionName, out bindingConfigurationName);
}
}
PatchConfigurationName(proxyNamespace,
configurationNamespace,
proxyGeneratedContractTypes,
serviceEndpointToChannelEndpointElementMap.Values,
targetCompileUnit);
}
finally
{
foreach (MetadataConversionError error in contractGenerator.Errors)
{
proxyGenerationErrors.Add(new ProxyGenerationError(error));
}
}
}
/// <summary>
/// Create appropriate XmlSerializerImportOptions for the generator
/// </summary>
/// <param name="proxyOptions">Options for the code/config generation</param>
/// <param name="targetCompileUnit">Compile unit we are going to generate the client code in</param>
/// <param name="codeDomProvider">CodeDom provider for the language we are using</param>
/// <param name="proxyNamespace">CLR namespace we'll put the client code in</param>
/// <returns></returns>
protected static XmlSerializerImportOptions CreateXmlSerializerImportOptions(
ClientOptions proxyOptions,
CodeCompileUnit targetCompileUnit,
System.CodeDom.Compiler.CodeDomProvider codeDomProvider,
string proxyNamespace,
System.Type typedDataSetSchemaImporterExtension)
{
System.ServiceModel.Channels.XmlSerializerImportOptions xmlSerializerOptions = new XmlSerializerImportOptions(targetCompileUnit);
System.Web.Services.Description.WebReferenceOptions webReferenceOptions = new System.Web.Services.Description.WebReferenceOptions();
webReferenceOptions.CodeGenerationOptions = System.Xml.Serialization.CodeGenerationOptions.GenerateProperties | System.Xml.Serialization.CodeGenerationOptions.GenerateOrder;
if (proxyOptions.EnableDataBinding)
{
webReferenceOptions.CodeGenerationOptions |= System.Xml.Serialization.CodeGenerationOptions.EnableDataBinding;
}
webReferenceOptions.SchemaImporterExtensions.Add(typedDataSetSchemaImporterExtension.AssemblyQualifiedName);
webReferenceOptions.SchemaImporterExtensions.Add(typeof(System.Data.DataSetSchemaImporterExtension).AssemblyQualifiedName);
/*
*/
xmlSerializerOptions.WebReferenceOptions = webReferenceOptions;
xmlSerializerOptions.CodeProvider = codeDomProvider;
xmlSerializerOptions.ClrNamespace = proxyNamespace;
return xmlSerializerOptions;
}
/// <summary>
/// Create an appropriate XsdDataContractImporter for the generator
/// </summary>
/// <param name="proxyOptions">Code/config generation options to use</param>
/// <param name="targetCompileUnit">CodeCompileUnit into which we will generate the client code</param>
/// <param name="codeDomProvider">CodeDomProvider for the language we will use to generate the client</param>
/// <param name="proxyNamespace">CLR namespace in which the client code will be generated</param>
/// <param name="typeLoader">Service used to resolve type/assembly names (strings) to actual Types and Assemblies</param>
/// <param name="targetFrameworkVersion">Targetted Framework version number</param>
/// <param name="importErrors">List of errors encountered. New errors will be added to this list</param>
/// <returns></returns>
protected static XsdDataContractImporter CreateDataContractImporter(
ClientOptions proxyOptions,
CodeCompileUnit targetCompileUnit,
System.CodeDom.Compiler.CodeDomProvider codeDomProvider,
string proxyNamespace,
IContractGeneratorReferenceTypeLoader typeLoader,
int targetFrameworkVersion,
IList<ProxyGenerationError> importErrors)
{
System.Runtime.Serialization.XsdDataContractImporter xsdDataContractImporter = new System.Runtime.Serialization.XsdDataContractImporter(targetCompileUnit);
System.Runtime.Serialization.ImportOptions options = new System.Runtime.Serialization.ImportOptions();
options.CodeProvider = codeDomProvider;
// We specify that we want to generate all types from all XML namespaces into
// our proxy namespace. By default, each XML namespace get's its own CLR namespace
options.Namespaces.Add("*", proxyNamespace);
options.GenerateInternal = proxyOptions.GenerateInternalTypes;
options.GenerateSerializable = proxyOptions.GenerateSerializableTypes;
options.EnableDataBinding = proxyOptions.EnableDataBinding;
options.ImportXmlType = proxyOptions.ImportXmlTypes;
if (typeLoader != null)
{
IEnumerable<Type> referencedTypes = LoadSharedDataContractTypes(proxyOptions, typeLoader, targetFrameworkVersion, importErrors);
if (referencedTypes != null)
{
foreach (Type sharedType in referencedTypes)
{
options.ReferencedTypes.Add(sharedType);
}
}
IEnumerable<Type> referencedCollectionTypes = LoadSharedCollectionTypes(proxyOptions, typeLoader, importErrors);
if (referencedCollectionTypes != null)
{
foreach (Type collectionType in referencedCollectionTypes)
{
options.ReferencedCollectionTypes.Add(collectionType);
}
}
}
foreach (NamespaceMapping namespaceMapping in proxyOptions.NamespaceMappingList)
{
options.Namespaces.Add(namespaceMapping.TargetNamespace, namespaceMapping.ClrNamespace);
}
xsdDataContractImporter.Options = options;
return xsdDataContractImporter;
}
/// <summary>
/// Load DataContract types which could be used in the code generator (shared types)
/// </summary>
/// <param name="proxyOptions">Options controlling the generation</param>
/// <param name="typeLoader">Type loader to resolve referenced assemblies and types</param>
/// <param name="targetFrameworkVersion">Targetted Framework version number</param>
/// <param name="importErrors">Errors encountered while loading the shared data contracts</param>
/// <return>
/// A list of CLR types from referenced assemblies and/or specific types that we want to share
/// </return>
/// <remarks></remarks>
[SuppressMessage("Microsoft.Usage", "CA2301:EmbeddableTypesInContainersRule", MessageId = "sharedTypeTable", Justification = "This is used within VS to get the types from reference assemblies i.e., the reference assembly for a given assembly but not across assemblies - so com interop is not an issue.")]
protected static IEnumerable<Type> LoadSharedDataContractTypes(
ClientOptions proxyOptions, IContractGeneratorReferenceTypeLoader typeLoader, int targetFrameworkVersion, IList<ProxyGenerationError> importErrors)
{
if (typeLoader == null) throw new ArgumentNullException("typeLoader");
// the value in sharedTypeTable is why we add this type in the shared type list.
// if it is only added because it is in the referenced assembly, the value will be null, otherwise it contains the entry in the type inclusion list
// if the type is also in the exclusion list, we will report an error if the type is comming from the inclusion list, but no error if it comes from a referenced assembly only.
Dictionary<Type, ReferencedType> sharedTypeTable = new Dictionary<Type, ReferencedType>();
// load all types in referencedAssemblies
IEnumerable<Assembly> referencedAssemblies = LoadReferenedAssemblies(proxyOptions, typeLoader, importErrors);
if (referencedAssemblies != null)
{
foreach (Assembly referencedAssembly in referencedAssemblies)
{
var typeLoader2 = typeLoader as IContractGeneratorReferenceTypeLoader2;
if (typeLoader2 != null)
{
foreach (Type sharedType in typeLoader2.LoadExportedTypes(referencedAssembly))
{
sharedTypeTable.Add(sharedType, null);
}
}
else
{
// Fall back to the original approach using IContractGeneratorReferenceTypeLoader.LoadType().
foreach (Type typeInAssembly in referencedAssembly.GetExportedTypes())
{
try
{
// Do multi-targeting check by calling IContractGeneratorReferenceTypeLoader.LoadType().
if (typeLoader.LoadType(typeInAssembly.FullName) != null)
{
sharedTypeTable.Add(typeInAssembly, null);
}
}
catch (NotSupportedException)
{
// NotSupportedException is thrown by multi-targeting check. It's normal if some types not existing in the current FX.
// So we can safely ---- it.
}
catch (Exception ex)
{
// fail to load one type in an assembly: warning message
importErrors.Add(
new ProxyGenerationError(
ProxyGenerationError.GeneratorState.GenerateCode,
String.Empty,
ex,
true));
}
}
}
}
}
// load types in DataContractTypeList
foreach (ReferencedType referencedType in proxyOptions.ReferencedDataContractTypeList)
{
try
{
Type sharedType = typeLoader.LoadType(referencedType.TypeName);
// verify...
if (!IsTypeShareable(sharedType))
{
importErrors.Add(
new ProxyGenerationError(
ProxyGenerationError.GeneratorState.GenerateCode,
String.Empty,
new FormatException(String.Format(CultureInfo.CurrentCulture, WCFModelStrings.ReferenceGroup_SharedTypeMustBePublic, referencedType.TypeName)))
);
continue;
}
sharedTypeTable[sharedType] = referencedType;
}
catch (Exception ex)
{
importErrors.Add(new ProxyGenerationError(
ProxyGenerationError.GeneratorState.GenerateCode,
String.Empty,
ex));
}
}
// remove excluded types
foreach (ReferencedType excludedType in proxyOptions.ExcludedTypeList)
{
try
{
Type sharedType = typeLoader.LoadType(excludedType.TypeName);
if (sharedTypeTable.ContainsKey(sharedType))
{
if (sharedTypeTable[sharedType] != null)
{
// error message
importErrors.Add(new ProxyGenerationError(
ProxyGenerationError.GeneratorState.GenerateCode,
String.Empty,
new Exception(String.Format(CultureInfo.CurrentCulture, WCFModelStrings.ReferenceGroup_DataContractExcludedAndIncluded, excludedType.TypeName))));
}
sharedTypeTable.Remove(sharedType);
}
}
catch (Exception ex)
{
// waring message for excludedTypes
importErrors.Add(new ProxyGenerationError(
ProxyGenerationError.GeneratorState.GenerateCode,
String.Empty,
ex,
true));
}
}
// remove unsupported types
foreach (Type unsupportedType in GetUnsupportedTypes(targetFrameworkVersion))
{
sharedTypeTable.Remove(unsupportedType);
}
return sharedTypeTable.Keys;
}
/// <summary>
/// Get list of types which are not supported in the targetted framework.
/// </summary>
/// <param name="targetFrameworkVersion">Targetted Framework version number</param>
/// <return></return>
/// <remarks></remarks>
private static IEnumerable<Type> GetUnsupportedTypes(int targetFrameworkVersion)
{
if (targetFrameworkVersion < FRAMEWORK_VERSION_35)
{
// NOTE: do we need load those types with typeLoader?
return unsupportedTypesInFramework30;
}
return new Type[0];
}
/// <summary>
/// Ensure that the ConfigurationName attribute on service contracts and the channel endpoint elements all agree on the
/// name of the service contract.
/// We want to avoid having root/default namespace values persisted in config, since that would require us
/// to update config whenever the default/root namespace changes, so we make sure that we exclude it
/// from the ConfigurationName attribute and the channel endpoint element we generate.
///
/// For VB, the root namespace is not actually present in the generated code, so we typically don't have to
/// do anything (the configuration and proxy namespaces are equal) but for C#, we need to strip out the
/// default namespace.
/// </summary>
/// <param name="proxyNamespace">
/// CLR namespace into which we will generate the service in the CodeCompileUnit
/// </param>
/// <param name="configNamespace">
/// The namespace that we expect to use in configuration.
/// </param>
/// <param name="generatedContracts">
/// The contracts that we have generated
/// </param>
/// <param name="endpoints">
/// All channel endpoints we have generated
/// </param>
/// <param name="targetCompileUnit">
/// The compile unit into which we generated the client
/// </param>
private static void PatchConfigurationName(
string proxyNamespace,
string configNamespace,
IEnumerable<GeneratedContractType> generatedContracts,
IEnumerable<ChannelEndpointElement> endpoints,
CodeCompileUnit targetCompileUnit
)
{
// Since the name has to match between configuration and the name we put in the ConfigurationName
// attribute in code, we may have some patching to do - but only if the proxy namespace is not equal
// to the configuration namespace...
if (configNamespace != null && !configNamespace.Equals(proxyNamespace, StringComparison.Ordinal))
{
string proxyNamespaceHead = MakePeriodTerminatedNamespacePrefix(proxyNamespace);
string configNamespaceHead = MakePeriodTerminatedNamespacePrefix(configNamespace);
// We need to fix up the configuration name for all generated contracts...
foreach (GeneratedContractType contract in generatedContracts)
{
contract.ConfigurationName = ReplaceNamespace(proxyNamespaceHead, configNamespaceHead, contract.ConfigurationName);
}
// ..and we need to fix up all elements in config...
foreach (ChannelEndpointElement endpoint in endpoints)
{
endpoint.Contract = ReplaceNamespace(proxyNamespaceHead, configNamespaceHead, endpoint.Contract);
}
// ...and all ConfigurationName values in service contract attributes in the generated code as well...
PatchConfigurationNameInServiceContractAttribute(targetCompileUnit, proxyNamespace, configNamespace);
}
}
/// <summary>
/// If the type name begins with the namespace specified in originalNamespace, replace the namespace
/// for the given type name with the namespace specified in the replacementNamespace parameter.
/// </summary>
/// <param name="originalNamespace">
/// Original namespace to look for.
/// Must either be an empty string ("") or end with a period (".")
/// </param>
/// <param name="replacementNamespace">
/// Namespace to replace original namespace with
/// Muse either be an empty string ("") or end with a period...
/// </param>
/// <param name="typeName">Typename on which to do the replacement</param>
/// <returns>
/// The new type name (potentially the same as passed in)
/// </returns>
private static string ReplaceNamespace(string originalNamespace, string replacementNamespace, string typeName)
{
Debug.Assert(originalNamespace.Length == 0 || originalNamespace.EndsWith(".", StringComparison.Ordinal));
Debug.Assert(replacementNamespace.Length == 0 || replacementNamespace.EndsWith(".", StringComparison.Ordinal));
Debug.Assert(typeName != null);
if (typeName.StartsWith(originalNamespace, StringComparison.Ordinal))
{
// Strip out the original namespace and replace it with the new namespace
return replacementNamespace + typeName.Substring(originalNamespace.Length);
}
else
{
return typeName;
}
}
/// <summary>
/// Given the namespace, return either an empty string (if the given namespace was NULL or emtpy)
/// or a period-terminated (".") string.
/// </summary>
/// <param name="ns"></param>
/// <returns>
/// Either an empty string or a string that ends with a period (".")
/// Can never return Null...
/// </returns>
private static string MakePeriodTerminatedNamespacePrefix(string ns)
{
if (String.IsNullOrEmpty(ns))
{
return "";
}
else if (!ns.EndsWith(".", StringComparison.Ordinal))
{
return ns + ".";
}
else
{
return ns;
}
}
/// <summary>
/// Determine if a type can be shared.
///
/// In order for a type to be shareable for service references, it has to be a
/// public type...
/// </summary>
/// <param name="t"></param>
/// <returns></returns>
private static bool IsTypeShareable(Type t)
{
if (t == null)
{
System.Diagnostics.Debug.Fail("Why are you asking if a NULL type is shareable?");
return false;
}
return t.IsPublic || t.IsNestedPublic;
}
/// <summary>
/// Load referenced assemblies
/// </summary>
/// <param name="proxyOptions"></param>
/// <param name="typeLoader"></param>
/// <param name="importErrors"></param>
/// <return></return>
/// <remarks></remarks>
private static IEnumerable<Assembly> LoadReferenedAssemblies(ClientOptions proxyOptions, IContractGeneratorReferenceTypeLoader typeLoader, IList<ProxyGenerationError> importErrors)
{
List<Assembly> referencedAssemblies = new List<Assembly>();
if (proxyOptions.ReferenceAllAssemblies)
{
try
{
IEnumerable<Exception> loadingErrors = null;
IEnumerable<Assembly> allAssemblies = null;
typeLoader.LoadAllAssemblies(out allAssemblies, out loadingErrors);
if (loadingErrors != null)
{
// treat as warning messages
foreach (Exception ex in loadingErrors)
{
importErrors.Add(new ProxyGenerationError(
ProxyGenerationError.GeneratorState.GenerateCode,
String.Empty,
ex,
true));
}
}
if (allAssemblies != null)
{
referencedAssemblies.AddRange(allAssemblies);
}
}
catch (Exception ex)
{
importErrors.Add(new ProxyGenerationError(
ProxyGenerationError.GeneratorState.GenerateCode,
String.Empty,
ex));
}
}
foreach (ReferencedAssembly referencedAssembly in proxyOptions.ReferencedAssemblyList)
{
try
{
Assembly refAssembly = typeLoader.LoadAssembly(referencedAssembly.AssemblyName);
if (refAssembly != null && !referencedAssemblies.Contains(refAssembly))
{
referencedAssemblies.Add(refAssembly);
}
}
catch (Exception ex)
{
importErrors.Add(new ProxyGenerationError(
ProxyGenerationError.GeneratorState.GenerateCode,
String.Empty,
ex));
}
}
return referencedAssemblies;
}
/// <summary>
/// Load the list of types that we have specified as collection types in our client options.
/// The collection types will be used to generate collections in the data contracts.
/// </summary>
/// <param name="proxyOptions">Options specifying the list of collection types</param>
/// <param name="typeLoader">Type loader that resolves type names to actual CLR types</param>
/// <param name="importErrors">Errors encountered while loading the collection types</param>
/// <return></return>
/// <remarks></remarks>
protected static IEnumerable<Type> LoadSharedCollectionTypes(ClientOptions proxyOptions, IContractGeneratorReferenceTypeLoader typeLoader, IList<ProxyGenerationError> importErrors)
{
List<Type> referencedCollectionTypes = new List<Type>();
foreach (ReferencedCollectionType referencedCollectionMapping in proxyOptions.CollectionMappingList)
{
try
{
Type collectionType = typeLoader.LoadType(referencedCollectionMapping.TypeName);
// verify...
if (!IsTypeShareable(collectionType))
{
importErrors.Add(
new ProxyGenerationError(
ProxyGenerationError.GeneratorState.GenerateCode,
String.Empty,
new FormatException(String.Format(CultureInfo.CurrentCulture, WCFModelStrings.ReferenceGroup_SharedTypeMustBePublic, referencedCollectionMapping.TypeName)))
);
continue;
}
referencedCollectionTypes.Add(collectionType);
}
catch (Exception ex)
{
importErrors.Add(new ProxyGenerationError(
ProxyGenerationError.GeneratorState.GenerateCode,
String.Empty,
ex));
}
}
return referencedCollectionTypes;
}
/// <summary>
/// Create an appropriate WsdlImporter for the generator
/// </summary>
/// <param name="svcMapFile"></param>
/// <param name="toolConfiguration"></param>
/// <param name="targetCompileUnit"></param>
/// <param name="codeDomProvider"></param>
/// <param name="targetNamespace"></param>
/// <param name="typeLoader"></param>
/// <param name="targetFrameworkVersion">Targetted Framework version number</param>
/// <param name="importErrors"></param>
/// <param name="typedDataSetSchemaImporterExtension"></param>
/// <returns></returns>
protected static WsdlImporter CreateWsdlImporter(SvcMapFile svcMapFile,
System.Configuration.Configuration toolConfiguration,
CodeCompileUnit targetCompileUnit,
System.CodeDom.Compiler.CodeDomProvider codeDomProvider,
string targetNamespace,
IServiceProvider serviceProviderForImportExtensions,
IContractGeneratorReferenceTypeLoader typeLoader,
int targetFrameworkVersion,
IList<ProxyGenerationError> importErrors,
System.Type typedDataSetSchemaImporterExtension)
{
List<MetadataSection> metadataSections = CollectMetadataDocuments(svcMapFile.MetadataList, importErrors);
WsdlImporter importer = null;
ClientOptions.ProxySerializerType serializerType = svcMapFile.ClientOptions.Serializer;
if (serializerType == ClientOptions.ProxySerializerType.Auto && ContainsHttpBindings(metadataSections))
{
// NOTE: HTTP Get/Post binding indicates an old web service. We use XmlSerializer to prevent generating dup classes.
// Please check devdiv bug 94078
serializerType = ClientOptions.ProxySerializerType.XmlSerializer;
}
if (toolConfiguration != null)
{
ServiceModelSectionGroup serviceModelSection = ServiceModelSectionGroup.GetSectionGroup(toolConfiguration);
if (serviceModelSection != null)
{
Collection<IWsdlImportExtension> wsdlImportExtensions = serviceModelSection.Client.Metadata.LoadWsdlImportExtensions();
Collection<IPolicyImportExtension> policyImportExtensions = serviceModelSection.Client.Metadata.LoadPolicyImportExtensions();
// If we have specified a specific serializer to use, we remove
// the other serializer...
switch (serializerType)
{
case ClientOptions.ProxySerializerType.DataContractSerializer:
RemoveExtension(typeof(XmlSerializerMessageContractImporter), wsdlImportExtensions);
break;
case ClientOptions.ProxySerializerType.XmlSerializer:
RemoveExtension(typeof(DataContractSerializerMessageContractImporter), wsdlImportExtensions);
break;
case ClientOptions.ProxySerializerType.Auto:
break;
default:
System.Diagnostics.Debug.Fail("Unknown serializer");
break;
}
ProvideImportExtensionsWithContextInformation(svcMapFile, serviceProviderForImportExtensions, wsdlImportExtensions, policyImportExtensions);
wsdlImportExtensions.Add(new HttpBindingExtension());
// Create Importer...
importer = new WsdlImporter(new MetadataSet(metadataSections), policyImportExtensions, wsdlImportExtensions);
}
}
if (importer == null)
{
importer = new WsdlImporter(new MetadataSet(metadataSections));
}
// DevDiv 124333 - Always add DataContract importer (even if we are in XmlSerializerMode) to
// enable importing Fault contracts...
importer.State.Add(typeof(System.Runtime.Serialization.XsdDataContractImporter),
CreateDataContractImporter(svcMapFile.ClientOptions, targetCompileUnit, codeDomProvider, targetNamespace, typeLoader, targetFrameworkVersion, importErrors));
if (serializerType != ClientOptions.ProxySerializerType.DataContractSerializer)
{
importer.State.Add(typeof(System.ServiceModel.Channels.XmlSerializerImportOptions),
CreateXmlSerializerImportOptions(svcMapFile.ClientOptions,
targetCompileUnit,
codeDomProvider,
targetNamespace,
typedDataSetSchemaImporterExtension));
}
// Read the UseSerializerForFaults from Reference.svcmap, create a FaultImportOptions using this information
// and pass it to WSDL Importer.
FaultImportOptions faultOptions = new FaultImportOptions();
faultOptions.UseMessageFormat = svcMapFile.ClientOptions.UseSerializerForFaults;
importer.State.Add(typeof(System.ServiceModel.FaultImportOptions), faultOptions);
// Read the WrappedOptions from Reference.svcmap, create a WrappedOptions using this information
// and pass it to WSDL Importer.
WrappedOptions wrappedOptions = new WrappedOptions();
wrappedOptions.WrappedFlag = svcMapFile.ClientOptions.Wrapped;
importer.State.Add(typeof(System.ServiceModel.Channels.WrappedOptions), wrappedOptions);
return importer;
}
/// <summary>
/// Look through all the import extensions to see if any of them want access to
/// the service reference's extension files. They tell us this by implementing
/// the interface IWcfReferenceReceiveContextInformation.
/// </summary>
/// <param name="svcMapFile"></param>
/// <param name="serviceProviderForImportExtensions"></param>
/// <param name="wsdlImportExtensions"></param>
/// <param name="policyImportExtensions"></param>
internal static void ProvideImportExtensionsWithContextInformation(SvcMapFile svcMapFile, IServiceProvider serviceProviderForImportExtensions, IEnumerable<IWsdlImportExtension> wsdlImportExtensions, IEnumerable<IPolicyImportExtension> policyImportExtensions)
{
// Only make this copy if we need to (not the mainline case)
Dictionary<string, byte[]> extensionFileContents = null;
foreach (IWsdlImportExtension wsdlImportExtension in wsdlImportExtensions)
{
System.Web.Compilation.IWcfReferenceReceiveContextInformation receiveContext =
wsdlImportExtension as System.Web.Compilation.IWcfReferenceReceiveContextInformation;
if (receiveContext != null)
{
if (extensionFileContents == null)
{
extensionFileContents = CreateDictionaryOfCopiedExtensionFiles(svcMapFile);
}
receiveContext.ReceiveImportContextInformation(
extensionFileContents,
serviceProviderForImportExtensions);
}
}
foreach (IPolicyImportExtension policyImportExtension in policyImportExtensions)
{
System.Web.Compilation.IWcfReferenceReceiveContextInformation receiveContext =
policyImportExtension as System.Web.Compilation.IWcfReferenceReceiveContextInformation;
if (receiveContext != null)
{
if (extensionFileContents == null)
{
extensionFileContents = CreateDictionaryOfCopiedExtensionFiles(svcMapFile);
}
receiveContext.ReceiveImportContextInformation(
extensionFileContents,
serviceProviderForImportExtensions);
}
}
}
/// <summary>
/// Remove specific wsdl importer extension
/// </summary>
/// <param name="extensionType">
/// The extension to remove
/// </param>
/// <param name="wsdlImportExtensions">
/// The collection to remove the extension from
/// </param>
/// <return></return>
/// <remarks></remarks>
private static void RemoveExtension(Type extensionType, Collection<IWsdlImportExtension> wsdlImportExtensions)
{
Debug.Assert(wsdlImportExtensions != null);
for (int i = 0; i < wsdlImportExtensions.Count; i++)
{
if (wsdlImportExtensions[i].GetType() == extensionType)
wsdlImportExtensions.RemoveAt(i);
}
}
/// <summary>
/// Creates a dictionary containing a copy of the contents of all of the extension files
/// </summary>
/// <returns></returns>
private static Dictionary<string, byte[]> CreateDictionaryOfCopiedExtensionFiles(SvcMapFile svcMapFile)
{
Dictionary<string, byte[]> extensionFileContents = new Dictionary<string, byte[]>();
foreach (ExtensionFile extensionFile in svcMapFile.Extensions)
{
// If the extension file was not successfully loaded, do not include it in the
// collection. Users are more likely to expect that the extension file won't
// be in the collection on an error than they are to assume they have to check
// if the byte array we return is null or not.
if (extensionFile.ContentBuffer != null && extensionFile.IsBufferValid)
{
extensionFileContents.Add(extensionFile.Name, (byte[])extensionFile.ContentBuffer.Clone());
}
}
return extensionFileContents;
}
/// <summary>
/// Merge metadata files to prepare code generation
/// </summary>
/// <returns>metadata collection</returns>
/// <remarks></remarks>
protected static List<MetadataSection> CollectMetadataDocuments(IEnumerable<MetadataFile> metadataList, IList<ProxyGenerationError> importErrors)
{
List<MetadataSection> metadataCollection = new List<MetadataSection>();
foreach (MetadataFile metadataItem in metadataList)
{
if (!metadataItem.Ignore)
{
try
{
MetadataSection metadataSection = metadataItem.CreateMetadataSection();
if (metadataSection != null)
{
metadataCollection.Add(metadataSection);
}
}
catch (Exception ex)
{
importErrors.Add(ConvertMetadataErrorToProxyGenerationError(metadataItem, ex));
}
}
}
RemoveDuplicatedSchemaItems(metadataCollection, importErrors);
CheckDuplicatedWsdlItems(metadataCollection, importErrors);
return metadataCollection;
}
/// <summary>
/// Convert metadata loading errors into proxy generation error messages
/// </summary>
/// <return></return>
/// <remarks></remarks>
internal static ProxyGenerationError ConvertMetadataErrorToProxyGenerationError(MetadataFile metadataItem, Exception ex)
{
ProxyGenerationError generationError = null;
if (ex is XmlSchemaException)
{
generationError = new ProxyGenerationError(ProxyGenerationError.GeneratorState.LoadMetadata, metadataItem.FileName, (XmlSchemaException)ex);
}
else if (ex is XmlException)
{
generationError = new ProxyGenerationError(ProxyGenerationError.GeneratorState.LoadMetadata, metadataItem.FileName, (XmlException)ex);
}
else if (ex is InvalidOperationException)
{
System.Xml.Schema.XmlSchemaException schemaException = ex.InnerException as System.Xml.Schema.XmlSchemaException;
if (schemaException != null)
{
generationError = new ProxyGenerationError(ProxyGenerationError.GeneratorState.LoadMetadata, metadataItem.FileName, schemaException);
}
else
{
System.Xml.XmlException xmlException = ex.InnerException as System.Xml.XmlException;
if (xmlException != null)
{
generationError = new ProxyGenerationError(ProxyGenerationError.GeneratorState.LoadMetadata, metadataItem.FileName, xmlException);
}
else
{
generationError = new ProxyGenerationError(ProxyGenerationError.GeneratorState.LoadMetadata, metadataItem.FileName, (InvalidOperationException)ex);
}
}
}
else
{
generationError = new ProxyGenerationError(ProxyGenerationError.GeneratorState.LoadMetadata, metadataItem.FileName, ex);
}
return generationError;
}
/// <summary>
/// Remove duplicated schema items from the metadata collection
/// </summary>
/// <remarks></remarks>
private static void RemoveDuplicatedSchemaItems(List<MetadataSection> metadataCollection, IList<ProxyGenerationError> importErrors)
{
Dictionary<XmlSchema, MetadataSection> schemaList = new Dictionary<XmlSchema, MetadataSection>();
// add independent schema files...
foreach (MetadataSection metadataSection in metadataCollection)
{
if (metadataSection.Dialect == MetadataSection.XmlSchemaDialect)
{
XmlSchema schema = (XmlSchema)metadataSection.Metadata;
schemaList.Add(schema, metadataSection);
}
}
// add embedded files...
foreach (MetadataSection metadataSection in metadataCollection)
{
if (metadataSection.Dialect == MetadataSection.ServiceDescriptionDialect)
{
System.Web.Services.Description.ServiceDescription wsdl = (System.Web.Services.Description.ServiceDescription)metadataSection.Metadata;
foreach (XmlSchema schema in wsdl.Types.Schemas)
{
schema.SourceUri = wsdl.RetrievalUrl;
schemaList.Add(schema, metadataSection);
}
}
}
IEnumerable<XmlSchema> duplicatedSchemas;
SchemaMerger.MergeSchemas(schemaList.Keys, importErrors, out duplicatedSchemas);
if (duplicatedSchemas != null)
{
foreach (XmlSchema schema in duplicatedSchemas)
{
Debug.Assert(schemaList.ContainsKey(schema), "The schema list should not contain any of the schemas returned in the duplicateSchemas...");
MetadataSection metadataSection = schemaList[schema];
if (metadataSection.Dialect == MetadataSection.XmlSchemaDialect)
{
metadataCollection.Remove(metadataSection);
}
else if (metadataSection.Dialect == MetadataSection.ServiceDescriptionDialect)
{
System.Web.Services.Description.ServiceDescription wsdl = (System.Web.Services.Description.ServiceDescription)metadataSection.Metadata;
wsdl.Types.Schemas.Remove(schema);
}
}
}
}
/// <summary>
/// check all wsdl files, and generate error messages if one contract have multiple different specifications
/// </summary>
/// <remarks></remarks>
private static void CheckDuplicatedWsdlItems(IList<MetadataSection> metadataCollection, IList<ProxyGenerationError> importErrors)
{
List<System.Web.Services.Description.ServiceDescription> wsdlFiles = new List<System.Web.Services.Description.ServiceDescription>();
foreach (MetadataSection metadataSection in metadataCollection)
{
if (metadataSection.Dialect == MetadataSection.ServiceDescriptionDialect)
{
System.Web.Services.Description.ServiceDescription wsdl = (System.Web.Services.Description.ServiceDescription)metadataSection.Metadata;
wsdlFiles.Add(wsdl);
}
}
WsdlInspector.CheckDuplicatedWsdlItems(wsdlFiles, importErrors);
}
/// <summary>
/// Given a WSDL importer, a set of metadata files and a compile unit, import the metadata
/// files.
/// </summary>
/// <param name="importer"></param>
/// <param name="compileUnit"></param>
/// <param name="generationErrors">
/// Errors encountered whie importing the model. Any new errors will be appended to this list.
/// </param>
/// <param name="serviceEndpointList">List of endpoints imported</param>
/// <param name="bindingCollection">The collection of bindings imported</param>
/// <param name="contractCollection">The collection of contracts imported</param>
protected static void ImportWCFModel(WsdlImporter importer,
System.CodeDom.CodeCompileUnit compileUnit,
IList<ProxyGenerationError> generationErrors,
out List<ServiceEndpoint> serviceEndpointList,
out IEnumerable<System.ServiceModel.Channels.Binding> bindingCollection,
out IEnumerable<ContractDescription> contractCollection)
{
// We want to remove soap1.2 endpoints for ASMX references, but we can't use the "normal" way
// of using a IWsdlImportExtension to do so since BeforeImport is called too late (DevDiv 7857)
// If DevDiv 7857 is fixed, we can remove the following two lines and instead add the
// AsmxEndpointPickerExtension to the importer's wsdl import extensions...
IWsdlImportExtension asmxFixerUpper = new AsmxEndpointPickerExtension();
asmxFixerUpper.BeforeImport(importer.WsdlDocuments, null, null);
// NOTE: we should import Endpoint before Contracts, otherwise some information (related to binding) will be lost in the model (devdiv: 22396)
serviceEndpointList = new List<ServiceEndpoint>();
//
// First we import all the endpoints (ports). This is required so that any WsdlImportExtension's BeforeImport
// gets called before we actually try to import anything from the WSDL object model.
// If we don't do this, we run into problems if any wsdl import extensions want to delete a specific port
// and this port happens to be the first port we try to import (you can't interrupt the import)
importer.ImportAllEndpoints();
//
// We need to go through each endpoint element and "re-import" it in order to get the mapping
// between the wsdlPort and the ServiceEndpoint... Importing the same endpoint twice is a no-op
// as far as the endpoint collection is concerned - it is simply a hashtable lookup to retreive
// the already generated information...
//
foreach (System.Web.Services.Description.ServiceDescription wsdlServiceDescription in importer.WsdlDocuments)
{
foreach (System.Web.Services.Description.Service wsdlService in wsdlServiceDescription.Services)
{
foreach (System.Web.Services.Description.Port servicePort in wsdlService.Ports)
{
try
{
ServiceEndpoint newEndpoint = importer.ImportEndpoint(servicePort);
serviceEndpointList.Add(newEndpoint);
}
catch (InvalidOperationException)
{
// Invalid operation exceptions should already be in the errors collection for the importer, so we don't
// need to add another generationError. The most probable cause for this is that the we failed to import
// the endpoint...
}
catch (Exception ex)
{ // It is bad, because WsdlImporter.WsdlImportException is a private class
generationErrors.Add(new ProxyGenerationError(ProxyGenerationError.GeneratorState.GenerateCode, wsdlServiceDescription.RetrievalUrl, ex));
}
}
}
}
bindingCollection = importer.ImportAllBindings();
System.Diagnostics.Debug.Assert(bindingCollection != null, "The importer should never return a NULL binding collection!");
contractCollection = importer.ImportAllContracts();
System.Diagnostics.Debug.Assert(contractCollection != null, "The importer should never return a NULL contract collection!");
foreach (MetadataConversionError error in importer.Errors)
{
generationErrors.Add(new ProxyGenerationError(error));
}
}
/// <summary>
/// This function patches ServiceContractAttribute in the generated proxy code. It replaces all proxyNamespace in the attribute
/// to configNamespace. The configNamespace does not depend on defaultNamespace of the project.
/// </summary>
/// <param name="proxyNamespace"></param>
/// <param name="configNamespace"></param>
/// <return></return>
/// <remarks></remarks>
private static void PatchConfigurationNameInServiceContractAttribute(CodeCompileUnit proxyCodeUnit, string proxyNamespace, string configNamespace)
{
if (proxyNamespace == null)
{
proxyNamespace = String.Empty;
}
string proxyNamespaceHead = MakePeriodTerminatedNamespacePrefix(proxyNamespace);
string configNamespaceHead = MakePeriodTerminatedNamespacePrefix(configNamespace);
if (proxyCodeUnit != null)
{
foreach (CodeNamespace proxyCodeNamespace in proxyCodeUnit.Namespaces)
{
// Find the namespace we are patching...
if (String.Equals(proxyNamespace, proxyCodeNamespace.Name, StringComparison.Ordinal))
{
// ...and all types in each namespace...
foreach (CodeTypeDeclaration typeDeclaration in proxyCodeNamespace.Types)
{
if (typeDeclaration.IsInterface)
{
// ...and each attribute on each interface...
foreach (CodeAttributeDeclaration codeAttribute in typeDeclaration.CustomAttributes)
{
// find System.ServiceModel.ServiceContractAttribute attribute.
if (String.Equals(codeAttribute.AttributeType.BaseType, typeof(System.ServiceModel.ServiceContractAttribute).FullName, StringComparison.Ordinal))
{
foreach (CodeAttributeArgument argument in codeAttribute.Arguments)
{
if (String.Equals(argument.Name, "ConfigurationName", StringComparison.Ordinal))
{
// we only fix the string here
CodePrimitiveExpression valueExpression = argument.Value as CodePrimitiveExpression;
if (valueExpression != null && valueExpression.Value is string)
{
valueExpression.Value = ReplaceNamespace(proxyNamespaceHead, configNamespaceHead, (string)valueExpression.Value);
}
}
}
}
}
}
}
}
}
}
}
/// <summary>
/// Patch VB code for output parameters.
///
/// Visual Basic doesn't support Out parameters - they are all generated as ByRef.
/// Unfortunately, the CodeDom provider doesn't add an Out attribute to the ByRef
/// parameters, so we have to do that ourselves...
/// </summary>
/// <param name="codeCompileUnit"></param>
/// <remarks></remarks>
private static void PatchOutParametersInVB(CodeCompileUnit codeCompileUnit)
{
foreach (CodeNamespace codeNamespace in codeCompileUnit.Namespaces)
{
foreach (CodeTypeDeclaration codeClass in codeNamespace.Types)
{
PatchTypeDeclaration(codeClass);
}
}
}
/// <summary>
/// Patch TypeDeclaration in VB code for output parameters
/// </summary>
/// <param name="codeClass"></param>
/// <remarks></remarks>
private static void PatchTypeDeclaration(CodeTypeDeclaration codeClass)
{
foreach (CodeTypeMember member in codeClass.Members)
{
if (member is CodeTypeDeclaration)
{
// Recurse down in nested types...
PatchTypeDeclaration((CodeTypeDeclaration)member);
}
else if (member is CodeMemberMethod)
{
CodeMemberMethod method = member as CodeMemberMethod;
foreach (CodeParameterDeclarationExpression parameter in method.Parameters)
{
if (parameter.Direction == FieldDirection.Out)
{
// Make sure that all Out parameters have an <Out> attribute
//
// First check for explicit <OutAttribute> declaration to avoid adding duplicate attributes.
if (!IsDefinedInCodeAttributeCollection(typeof(System.Runtime.InteropServices.OutAttribute), parameter.CustomAttributes))
{
parameter.CustomAttributes.Add(OutAttribute);
}
}
}
}
}
}
/// <summary>
/// check whether code attribuate has already been declared.
/// </summary>
/// <param name="type"></param>
/// <param name="metadata"></param>
/// <return></return>
/// <remarks></remarks>
private static bool IsDefinedInCodeAttributeCollection(Type type, CodeAttributeDeclarationCollection metadata)
{
foreach (CodeAttributeDeclaration attribute in metadata)
{
if (String.Equals(attribute.Name, type.FullName, StringComparison.Ordinal) || String.Equals(attribute.Name, type.Name, StringComparison.Ordinal))
{
return true;
}
}
return false;
}
/// <summary>
/// Check whether it is VB language
/// </summary>
/// <param name="codeDomProvider"></param>
/// <return></return>
/// <remarks></remarks>
private static bool IsVBCodeDomProvider(System.CodeDom.Compiler.CodeDomProvider codeDomProvider)
{
string fileExtension = codeDomProvider.FileExtension;
try
{
string language = System.CodeDom.Compiler.CodeDomProvider.GetLanguageFromExtension(fileExtension);
return String.Equals(language, VB_LANGUAGE_NAME, StringComparison.OrdinalIgnoreCase);
}
catch (System.Configuration.ConfigurationException)
{
// not defined extension
return false;
}
}
/// <summary>
/// check whether HTTP Binding is used in those metadata files
/// </summary>
/// <param name="metadataCollection">metadata files</param>
/// <return></return>
/// <remarks></remarks>
private static bool ContainsHttpBindings(IEnumerable<MetadataSection> metadataCollection)
{
foreach (MetadataSection metadataSection in metadataCollection)
{
if (metadataSection.Dialect == MetadataSection.ServiceDescriptionDialect)
{
System.Web.Services.Description.ServiceDescription wsdlFile = (System.Web.Services.Description.ServiceDescription)metadataSection.Metadata;
if (ContainsHttpBindings(wsdlFile))
{
return true;
}
}
}
return false;
}
/// <summary>
/// check whether HTTP Binding is used in one wsdl file
/// </summary>
/// <param name="wsdlFile">one wsdl</param>
/// <return></return>
/// <remarks></remarks>
internal static bool ContainsHttpBindings(System.Web.Services.Description.ServiceDescription wsdlFile)
{
foreach (System.Web.Services.Description.Binding binding in wsdlFile.Bindings)
{
foreach (object extension in binding.Extensions)
{
System.Web.Services.Description.HttpBinding httpBinding = extension as System.Web.Services.Description.HttpBinding;
if (httpBinding != null)
{
return true;
}
}
}
return false;
}
}
}
|