1 // -----------------------------------------------------------------------
\r
2 // Copyright (c) Microsoft Corporation. All rights reserved.
\r
3 // -----------------------------------------------------------------------
\r
5 using System.Collections.Generic;
\r
6 using System.ComponentModel.Composition.Hosting;
\r
7 using System.Diagnostics.CodeAnalysis;
\r
9 using System.Linq.Expressions;
\r
10 using Microsoft.Internal;
\r
11 using System.Globalization;
\r
13 namespace System.ComponentModel.Composition.Primitives
\r
16 /// Represents a contract name and metadata-based import
\r
17 /// required by a <see cref="ComposablePart"/> object.
\r
19 public class ContractBasedImportDefinition : ImportDefinition
\r
21 // Unlike contract name, required metadata has a sensible default; set it to an empty
\r
22 // enumerable, so that derived definitions only need to override ContractName by default.
\r
23 private readonly IEnumerable<KeyValuePair<string, Type>> _requiredMetadata = Enumerable.Empty<KeyValuePair<string, Type>>();
\r
24 private Expression<Func<ExportDefinition, bool>> _constraint;
\r
25 private readonly CreationPolicy _requiredCreationPolicy = CreationPolicy.Any;
\r
26 private readonly string _requiredTypeIdentity = null;
\r
27 private bool _isRequiredMetadataValidated = false;
\r
30 /// Initializes a new instance of the <see cref="ContractBasedImportDefinition"/> class.
\r
33 /// <note type="inheritinfo">
\r
34 /// Derived types calling this constructor can optionally override the
\r
35 /// <see cref="ImportDefinition.ContractName"/>, <see cref="RequiredTypeIdentity"/>,
\r
36 /// <see cref="RequiredMetadata"/>, <see cref="ImportDefinition.Cardinality"/>,
\r
37 /// <see cref="ImportDefinition.IsPrerequisite"/>, <see cref="ImportDefinition.IsRecomposable"/>
\r
38 /// and <see cref="RequiredCreationPolicy"/> properties.
\r
41 protected ContractBasedImportDefinition()
\r
46 /// Initializes a new instance of the <see cref="ContractBasedImportDefinition"/> class
\r
47 /// with the specified contract name, required metadataq, cardinality, value indicating
\r
48 /// if the import definition is recomposable and a value indicating if the import definition
\r
49 /// is a prerequisite.
\r
51 /// <param name="contractName">
\r
52 /// A <see cref="String"/> containing the contract name of the
\r
53 /// <see cref="Export"/> required by the <see cref="ContractBasedImportDefinition"/>.
\r
55 /// <param name="requiredTypeIdentity">
\r
56 /// The type identity of the export type expected. Use <see cref="AttributedModelServices.GetTypeIdentity(Type)"/>
\r
57 /// to generate a type identity for a given type. If no specific type is required pass <see langword="null"/>.
\r
59 /// <param name="requiredMetadata">
\r
60 /// An <see cref="IEnumerable{T}"/> of <see cref="String"/> objects containing
\r
61 /// the metadata names of the <see cref="Export"/> required by the
\r
62 /// <see cref="ContractBasedImportDefinition"/>; or <see langword="null"/> to
\r
63 /// set the <see cref="RequiredMetadata"/> property to an empty <see cref="IEnumerable{T}"/>.
\r
65 /// <param name="cardinality">
\r
66 /// One of the <see cref="ImportCardinality"/> values indicating the
\r
67 /// cardinality of the <see cref="Export"/> objects required by the
\r
68 /// <see cref="ContractBasedImportDefinition"/>.
\r
70 /// <param name="isRecomposable">
\r
71 /// <see langword="true"/> if the <see cref="ContractBasedImportDefinition"/> can be satisfied
\r
72 /// multiple times throughout the lifetime of a <see cref="ComposablePart"/>, otherwise,
\r
73 /// <see langword="false"/>.
\r
75 /// <param name="isPrerequisite">
\r
76 /// <see langword="true"/> if the <see cref="ContractBasedImportDefinition"/> is required to be
\r
77 /// satisfied before a <see cref="ComposablePart"/> can start producing exported
\r
78 /// objects; otherwise, <see langword="false"/>.
\r
80 /// <param name="requiredCreationPolicy">
\r
81 /// A value indicating that the importer requires a specific <see cref="CreationPolicy"/> for
\r
82 /// the exports used to satisfy this import. If no specific <see cref="CreationPolicy"/> is needed
\r
83 /// pass the default <see cref="CreationPolicy.Any"/>.
\r
85 /// <exception cref="ArgumentNullException">
\r
86 /// <paramref name="contractName"/> is <see langword="null"/>.
\r
88 /// <exception cref="ArgumentException">
\r
89 /// <paramref name="contractName"/> is an empty string ("").
\r
93 /// <paramref name="requiredMetadata"/> contains an element that is <see langword="null"/>.
\r
97 /// <paramref name="cardinality"/> is not one of the <see cref="ImportCardinality"/>
\r
100 [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")]
\r
101 public ContractBasedImportDefinition(string contractName, string requiredTypeIdentity, IEnumerable<KeyValuePair<string, Type>> requiredMetadata,
\r
102 ImportCardinality cardinality, bool isRecomposable, bool isPrerequisite, CreationPolicy requiredCreationPolicy)
\r
103 : base(contractName, cardinality, isRecomposable, isPrerequisite)
\r
105 Requires.NotNullOrEmpty(contractName, "contractName");
\r
107 this._requiredTypeIdentity = requiredTypeIdentity;
\r
109 if (requiredMetadata != null)
\r
111 this._requiredMetadata = requiredMetadata;
\r
114 this._requiredCreationPolicy = requiredCreationPolicy;
\r
118 /// The type identity of the export type expected.
\r
121 /// A <see cref="string"/> that is generated by <see cref="AttributedModelServices.GetTypeIdentity(Type)"/>
\r
122 /// on the type that this import expects. If the value is <see langword="null"/> then this import
\r
123 /// doesn't expect a particular type.
\r
125 public virtual string RequiredTypeIdentity
\r
127 get { return this._requiredTypeIdentity; }
\r
131 /// Gets the metadata names of the export required by the import definition.
\r
134 /// An <see cref="IEnumerable{T}"/> of pairs of metadata keys and types of the <see cref="Export"/> required by the
\r
135 /// <see cref="ContractBasedImportDefinition"/>. The default is an empty
\r
136 /// <see cref="IEnumerable{T}"/>.
\r
139 /// <note type="inheritinfo">
\r
140 /// Overriders of this property should never return <see langword="null"/>
\r
141 /// or return an <see cref="IEnumerable{T}"/> that contains an element that is
\r
142 /// <see langword="null"/>. If the definition does not contain required metadata,
\r
143 /// return an empty <see cref="IEnumerable{T}"/> instead.
\r
146 [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")]
\r
147 public virtual IEnumerable<KeyValuePair<string, Type>> RequiredMetadata
\r
151 // NOTE : unlike other arguments, we validate this one as late as possible, because its validation may lead to type loading
\r
152 this.ValidateRequiredMetadata();
\r
154 return this._requiredMetadata;
\r
158 private void ValidateRequiredMetadata()
\r
160 if (!this._isRequiredMetadataValidated)
\r
162 foreach (KeyValuePair<string, Type> metadataItem in this._requiredMetadata)
\r
164 if ((metadataItem.Key == null) || (metadataItem.Value == null))
\r
166 throw new InvalidOperationException(
\r
167 string.Format(CultureInfo.CurrentCulture, Strings.Argument_NullElement, "requiredMetadata"));
\r
170 this._isRequiredMetadataValidated = true;
\r
175 /// Gets or sets a value indicating that the importer requires a specific
\r
176 /// <see cref="CreationPolicy"/> for the exports used to satisfy this import. T
\r
179 /// <see cref="CreationPolicy.Any"/> - default value, used if the importer doesn't
\r
180 /// require a specific <see cref="CreationPolicy"/>.
\r
182 /// <see cref="CreationPolicy.Shared"/> - Requires that all exports used should be shared
\r
183 /// by everyone in the container.
\r
185 /// <see cref="CreationPolicy.NonShared"/> - Requires that all exports used should be
\r
186 /// non-shared in a container and thus everyone gets their own instance.
\r
188 public virtual CreationPolicy RequiredCreationPolicy
\r
190 get { return this._requiredCreationPolicy; }
\r
194 /// Gets an expression that defines conditions that must be matched for the import
\r
195 /// described by the import definition to be satisfied.
\r
198 /// A <see cref="Expression{TDelegate}"/> containing a <see cref="Func{T, TResult}"/>
\r
199 /// that defines the conditions that must be matched for the
\r
200 /// <see cref="ImportDefinition"/> to be satisfied by an <see cref="Export"/>.
\r
204 /// This property returns an expression that defines conditions based on the
\r
205 /// <see cref="ImportDefinition.ContractName"/>, <see cref="RequiredTypeIdentity"/>,
\r
206 /// <see cref="RequiredMetadata"/>, and <see cref="RequiredCreationPolicy"/>
\r
210 public override Expression<Func<ExportDefinition, bool>> Constraint
\r
214 if (this._constraint == null)
\r
216 this._constraint = ConstraintServices.CreateConstraint(this.ContractName, this.RequiredTypeIdentity, this.RequiredMetadata, this.RequiredCreationPolicy);
\r
219 return this._constraint;
\r
224 /// Executes an optimized version of the contraint given by the <see cref="Constraint"/> property
\r
226 /// <param name="exportDefinition">
\r
227 /// A definition for a <see cref="Export"/> used to determine if it satisfies the
\r
228 /// requirements for this <see cref="ImportDefinition"/>.
\r
231 /// <see langword="True"/> if the <see cref="Export"/> satisfies the requirements for
\r
232 /// this <see cref="ImportDefinition"/>, otherwise returns <see langword="False"/>.
\r
235 /// <note type="inheritinfo">
\r
236 /// Overrides of this method can provide a more optimized execution of the
\r
237 /// <see cref="Constraint"/> property but the result should remain consistent.
\r
240 /// <exception cref="ArgumentNullException">
\r
241 /// <paramref name="exportDefinition"/> is <see langword="null"/>.
\r
243 public override bool IsConstraintSatisfiedBy(ExportDefinition exportDefinition)
\r
245 Requires.NotNull(exportDefinition, "exportDefinition");
\r
247 if (!StringComparers.ContractName.Equals(this.ContractName, exportDefinition.ContractName))
\r
252 return MatchRequiredMatadata(exportDefinition);
\r
255 private bool MatchRequiredMatadata(ExportDefinition definition)
\r
257 if (!string.IsNullOrEmpty(this.RequiredTypeIdentity))
\r
259 string exportTypeIdentity = definition.Metadata.GetValue<string>(CompositionConstants.ExportTypeIdentityMetadataName);
\r
261 if (!StringComparers.ContractName.Equals(this.RequiredTypeIdentity, exportTypeIdentity))
\r
267 foreach (KeyValuePair<string, Type> metadataItem in this.RequiredMetadata)
\r
269 string metadataKey = metadataItem.Key;
\r
270 Type metadataValueType = metadataItem.Value;
\r
272 object metadataValue = null;
\r
273 if (!definition.Metadata.TryGetValue(metadataKey, out metadataValue))
\r
278 if (metadataValue != null)
\r
280 // the metadata value is not null, we can rely on IsInstanceOfType to do the right thing
\r
281 if (!metadataValueType.IsInstanceOfType(metadataValue))
\r
288 // this is an unfortunate special case - typeof(object).IsInstanceofType(null) == false
\r
289 // basically nulls are not considered valid values for anything
\r
290 // We want them to match anything that is a reference type
\r
291 if (metadataValueType.IsValueType)
\r
293 // this is a pretty expensive check, but we only invoke it when metadata values are null, which is very rare
\r
299 if (this.RequiredCreationPolicy == CreationPolicy.Any)
\r
304 CreationPolicy exportPolicy = definition.Metadata.GetValue<CreationPolicy>(CompositionConstants.PartCreationPolicyMetadataName);
\r
305 return exportPolicy == CreationPolicy.Any ||
\r
306 exportPolicy == this.RequiredCreationPolicy;
\r