1 // Licensed to the .NET Foundation under one or more agreements. 2 // The .NET Foundation licenses this file to you under the MIT license. 3 // See the LICENSE file in the project root for more information. 4 5 using System.Collections.Generic; 6 using System.ComponentModel.Composition.Primitives; 7 using System.Diagnostics; 8 using System.Diagnostics.CodeAnalysis; 9 using System.Diagnostics.Contracts; 10 using System.Globalization; 11 using System.Reflection; 12 using System.Threading; 13 using Microsoft.Internal; 14 15 namespace System.ComponentModel.Composition.Hosting 16 { 17 /// <summary> 18 /// An immutable ComposablePartCatalog created from a managed code assembly. 19 /// </summary> 20 /// <remarks> 21 /// This type is thread safe. 22 /// </remarks> 23 [DebuggerTypeProxy(typeof(AssemblyCatalogDebuggerProxy))] 24 public class AssemblyCatalog : ComposablePartCatalog, ICompositionElement 25 { 26 private readonly object _thisLock = new object(); 27 private readonly ICompositionElement _definitionOrigin; 28 private volatile Assembly _assembly = null; 29 private volatile ComposablePartCatalog _innerCatalog = null; 30 private int _isDisposed = 0; 31 32 private ReflectionContext _reflectionContext = default(ReflectionContext); 33 34 /// <summary> 35 /// Initializes a new instance of the <see cref="AssemblyCatalog"/> class 36 /// with the specified code base. 37 /// </summary> 38 /// <param name="codeBase"> 39 /// A <see cref="String"/> containing the code base of the assembly containing the 40 /// attributed <see cref="Type"/> objects to add to the <see cref="AssemblyCatalog"/>. 41 /// </param> 42 /// <exception cref="ArgumentNullException"> 43 /// <paramref name="codeBase"/> is <see langword="null"/>. 44 /// </exception> 45 /// <exception cref="ArgumentException"> 46 /// <paramref name="codeBase"/> is a zero-length string, contains only white space, 47 /// or contains one or more invalid characters. />. 48 /// </exception> 49 /// <exception cref="PathTooLongException"> 50 /// The specified path, file name, or both exceed the system-defined maximum length. 51 /// </exception> 52 /// <exception cref="SecurityException"> 53 /// The caller does not have path discovery permission. 54 /// </exception> 55 /// <exception cref="FileNotFoundException"> 56 /// <paramref name="codeBase"/> is not found. 57 /// </exception> 58 /// <exception cref="FileLoadException "> 59 /// <paramref name="codeBase"/> could not be loaded. 60 /// <para> 61 /// -or- 62 /// </para> 63 /// <paramref name="codeBase"/> specified a directory. 64 /// </exception> 65 /// <exception cref="BadImageFormatException"> 66 /// <paramref name="codeBase"/> is not a valid assembly 67 /// -or- 68 /// Version 2.0 or later of the common language runtime is currently loaded 69 /// and <paramref name="codeBase"/> was compiled with a later version. 70 /// </exception> 71 /// <remarks> 72 /// The assembly referenced by <paramref langword="codeBase"/> is loaded into the Load context. 73 /// </remarks> AssemblyCatalog(string codeBase)74 public AssemblyCatalog(string codeBase) 75 { 76 Requires.NotNullOrEmpty(codeBase, "codeBase"); 77 78 InitializeAssemblyCatalog(LoadAssembly(codeBase)); 79 _definitionOrigin = this; 80 } 81 82 /// <summary> 83 /// Initializes a new instance of the <see cref="AssemblyCatalog"/> class 84 /// with the specified code base. 85 /// </summary> 86 /// <param name="codeBase"> 87 /// A <see cref="String"/> containing the code base of the assembly containing the 88 /// attributed <see cref="Type"/> objects to add to the <see cref="AssemblyCatalog"/>. 89 /// </param> 90 /// <param name="reflectionContext"> 91 /// The <see cref="ReflectionContext"/> a context used by the catalog when 92 /// interpreting the types to inject attributes into the type definition<see cref="AssemblyCatalog"/>. 93 /// </param> 94 /// <exception cref="ArgumentNullException"> 95 /// <paramref name="codeBase"/> is <see langword="null"/>. 96 /// <para> 97 /// -or- 98 /// </para> 99 /// <paramref name="reflectionContext"/> is <see langword="null"/>. 100 /// </exception> 101 /// <exception cref="ArgumentException"> 102 /// <paramref name="codeBase"/> is a zero-length string, contains only white space, 103 /// or contains one or more invalid characters. />. 104 /// </exception> 105 /// <exception cref="PathTooLongException"> 106 /// The specified path, file name, or both exceed the system-defined maximum length. 107 /// </exception> 108 /// <exception cref="SecurityException"> 109 /// The caller does not have path discovery permission. 110 /// </exception> 111 /// <exception cref="FileNotFoundException"> 112 /// <paramref name="codeBase"/> is not found. 113 /// </exception> 114 /// <exception cref="FileLoadException "> 115 /// <paramref name="codeBase"/> could not be loaded. 116 /// <para> 117 /// -or- 118 /// </para> 119 /// <paramref name="codeBase"/> specified a directory. 120 /// </exception> 121 /// <exception cref="BadImageFormatException"> 122 /// <paramref name="codeBase"/> is not a valid assembly 123 /// -or- 124 /// Version 2.0 or later of the common language runtime is currently loaded 125 /// and <paramref name="codeBase"/> was compiled with a later version. 126 /// </exception> 127 /// <remarks> 128 /// The assembly referenced by <paramref langword="codeBase"/> is loaded into the Load context. 129 /// </remarks> AssemblyCatalog(string codeBase, ReflectionContext reflectionContext)130 public AssemblyCatalog(string codeBase, ReflectionContext reflectionContext) 131 { 132 Requires.NotNullOrEmpty(codeBase, "codeBase"); 133 Requires.NotNull(reflectionContext, "reflectionContext"); 134 135 InitializeAssemblyCatalog(LoadAssembly(codeBase)); 136 _reflectionContext = reflectionContext; 137 _definitionOrigin = this; 138 } 139 140 /// <summary> 141 /// Initializes a new instance of the <see cref="AssemblyCatalog"/> class 142 /// with the specified code base. 143 /// </summary> 144 /// <param name="codeBase"> 145 /// A <see cref="String"/> containing the code base of the assembly containing the 146 /// attributed <see cref="Type"/> objects to add to the <see cref="AssemblyCatalog"/>. 147 /// </param> 148 /// <param name="definitionOrigin"> 149 /// The <see cref="ICompositionElement"/> CompositionElement used by Diagnostics to identify the source for parts. 150 /// </param> 151 /// <exception cref="ArgumentNullException"> 152 /// <paramref name="codeBase"/> is <see langword="null"/>. 153 /// <para> 154 /// -or- 155 /// </para> 156 /// <paramref name="definitionOrigin"/> is <see langword="null"/>. 157 /// </exception> 158 /// <exception cref="ArgumentException"> 159 /// <paramref name="codeBase"/> is a zero-length string, contains only white space, 160 /// or contains one or more invalid characters. />. 161 /// </exception> 162 /// <exception cref="PathTooLongException"> 163 /// The specified path, file name, or both exceed the system-defined maximum length. 164 /// </exception> 165 /// <exception cref="SecurityException"> 166 /// The caller does not have path discovery permission. 167 /// </exception> 168 /// <exception cref="FileNotFoundException"> 169 /// <paramref name="codeBase"/> is not found. 170 /// </exception> 171 /// <exception cref="FileLoadException "> 172 /// <paramref name="codeBase"/> could not be loaded. 173 /// <para> 174 /// -or- 175 /// </para> 176 /// <paramref name="codeBase"/> specified a directory. 177 /// </exception> 178 /// <exception cref="BadImageFormatException"> 179 /// <paramref name="codeBase"/> is not a valid assembly 180 /// -or- 181 /// Version 2.0 or later of the common language runtime is currently loaded 182 /// and <paramref name="codeBase"/> was compiled with a later version. 183 /// </exception> 184 /// <remarks> 185 /// The assembly referenced by <paramref langword="codeBase"/> is loaded into the Load context. 186 /// </remarks> AssemblyCatalog(string codeBase, ICompositionElement definitionOrigin)187 public AssemblyCatalog(string codeBase, ICompositionElement definitionOrigin) 188 { 189 Requires.NotNullOrEmpty(codeBase, "codeBase"); 190 Requires.NotNull(definitionOrigin, nameof(definitionOrigin)); 191 192 InitializeAssemblyCatalog(LoadAssembly(codeBase)); 193 _definitionOrigin = definitionOrigin; 194 } 195 196 /// <summary> 197 /// Initializes a new instance of the <see cref="AssemblyCatalog"/> class 198 /// with the specified code base. 199 /// </summary> 200 /// <param name="codeBase"> 201 /// A <see cref="String"/> containing the code base of the assembly containing the 202 /// attributed <see cref="Type"/> objects to add to the <see cref="AssemblyCatalog"/>. 203 /// </param> 204 /// <param name="reflectionContext"> 205 /// The <see cref="ReflectionContext"/> a context used by the catalog when 206 /// interpreting the types to inject attributes into the type definition<see cref="AssemblyCatalog"/>. 207 /// </param> 208 /// <param name="definitionOrigin"> 209 /// The <see cref="ICompositionElement"/> CompositionElement used by Diagnostics to identify the source for parts. 210 /// </param> 211 /// <exception cref="ArgumentNullException"> 212 /// <paramref name="codeBase"/> is <see langword="null"/>. 213 /// <para> 214 /// -or- 215 /// </para> 216 /// <paramref name="reflectionContext"/> is <see langword="null"/>. 217 /// <para> 218 /// -or- 219 /// </para> 220 /// <paramref name="definitionOrigin"/> is <see langword="null"/>. 221 /// </exception> 222 /// <exception cref="ArgumentException"> 223 /// <paramref name="codeBase"/> is a zero-length string, contains only white space, 224 /// or contains one or more invalid characters. />. 225 /// </exception> 226 /// <exception cref="PathTooLongException"> 227 /// The specified path, file name, or both exceed the system-defined maximum length. 228 /// </exception> 229 /// <exception cref="SecurityException"> 230 /// The caller does not have path discovery permission. 231 /// </exception> 232 /// <exception cref="FileNotFoundException"> 233 /// <paramref name="codeBase"/> is not found. 234 /// </exception> 235 /// <exception cref="FileLoadException "> 236 /// <paramref name="codeBase"/> could not be loaded. 237 /// <para> 238 /// -or- 239 /// </para> 240 /// <paramref name="codeBase"/> specified a directory. 241 /// </exception> 242 /// <exception cref="BadImageFormatException"> 243 /// <paramref name="codeBase"/> is not a valid assembly 244 /// -or- 245 /// Version 2.0 or later of the common language runtime is currently loaded 246 /// and <paramref name="codeBase"/> was compiled with a later version. 247 /// </exception> 248 /// <remarks> 249 /// The assembly referenced by <paramref langword="codeBase"/> is loaded into the Load context. 250 /// </remarks> AssemblyCatalog(string codeBase, ReflectionContext reflectionContext, ICompositionElement definitionOrigin)251 public AssemblyCatalog(string codeBase, ReflectionContext reflectionContext, ICompositionElement definitionOrigin) 252 { 253 Requires.NotNullOrEmpty(codeBase, "codeBase"); 254 Requires.NotNull(reflectionContext, "reflectionContext"); 255 Requires.NotNull(definitionOrigin, "definitionOrigin"); 256 257 InitializeAssemblyCatalog(LoadAssembly(codeBase)); 258 _reflectionContext = reflectionContext; 259 _definitionOrigin = definitionOrigin; 260 } 261 262 /// <summary> 263 /// Initializes a new instance of the <see cref="AssemblyCatalog"/> class 264 /// with the specified assembly and reflection context. 265 /// </summary> 266 /// <param name="assembly"> 267 /// The <see cref="Assembly"/> containing the attributed <see cref="Type"/> objects to 268 /// add to the <see cref="AssemblyCatalog"/>. 269 /// </param> 270 /// <param name="reflectionContext"> 271 /// The <see cref="ReflectionContext"/> a context used by the catalog when 272 /// interpreting the types to inject attributes into the type definition. 273 /// </param> 274 /// <exception cref="ArgumentException"> 275 /// <paramref name="assembly"/> is <see langword="null"/>. 276 /// <para> 277 /// -or- 278 /// </para> 279 /// <paramref name="assembly"/> was loaded in the reflection-only context. 280 /// <para> 281 /// -or- 282 /// </para> 283 /// <paramref name="reflectionContext"/> is <see langword="null"/>. 284 /// </exception> AssemblyCatalog(Assembly assembly, ReflectionContext reflectionContext)285 public AssemblyCatalog(Assembly assembly, ReflectionContext reflectionContext) 286 { 287 Requires.NotNull(assembly, "assembly"); 288 Requires.NotNull(reflectionContext, "reflectionContext"); 289 290 InitializeAssemblyCatalog(assembly); 291 _reflectionContext = reflectionContext; 292 _definitionOrigin = this; 293 } 294 295 /// <summary> 296 /// Initializes a new instance of the <see cref="AssemblyCatalog"/> class 297 /// with the specified assembly, reflectionContext and definitionOrigin. 298 /// </summary> 299 /// <param name="assembly"> 300 /// The <see cref="Assembly"/> containing the attributed <see cref="Type"/> objects to 301 /// add to the <see cref="AssemblyCatalog"/>. 302 /// </param> 303 /// <param name="reflectionContext"> 304 /// The <see cref="ReflectionContext"/> a context used by the catalog when 305 /// interpreting the types to inject attributes into the type definition. 306 /// </param> 307 /// <param name="definitionOrigin"> 308 /// The <see cref="ICompositionElement"/> CompositionElement used by Diagnostics to identify the source for parts. 309 /// </param> 310 /// <exception cref="ArgumentException"> 311 /// <paramref name="assembly"/> is <see langword="null"/>. 312 /// <para> 313 /// -or- 314 /// </para> 315 /// <paramref name="assembly"/> was loaded in the reflection-only context. 316 /// <para> 317 /// -or- 318 /// </para> 319 /// <paramref name="reflectionContext"/> is <see langword="null"/>. 320 /// <para> 321 /// -or- 322 /// </para> 323 /// <paramref name="definitionOrigin"/> is <see langword="null"/>. 324 /// </exception> AssemblyCatalog(Assembly assembly, ReflectionContext reflectionContext, ICompositionElement definitionOrigin)325 public AssemblyCatalog(Assembly assembly, ReflectionContext reflectionContext, ICompositionElement definitionOrigin) 326 { 327 Requires.NotNull(assembly, "assembly"); 328 Requires.NotNull(reflectionContext, "reflectionContext"); 329 Requires.NotNull(definitionOrigin, "definitionOrigin"); 330 331 InitializeAssemblyCatalog(assembly); 332 _reflectionContext = reflectionContext; 333 _definitionOrigin = definitionOrigin; 334 } 335 336 /// <summary> 337 /// Initializes a new instance of the <see cref="AssemblyCatalog"/> class 338 /// with the specified assembly. 339 /// </summary> 340 /// <param name="assembly"> 341 /// The <see cref="Assembly"/> containing the attributed <see cref="Type"/> objects to 342 /// add to the <see cref="AssemblyCatalog"/>. 343 /// </param> 344 /// <exception cref="ArgumentException"> 345 /// <paramref name="assembly"/> is <see langword="null"/>. 346 /// <para> 347 /// -or- 348 /// </para> 349 /// <paramref name="assembly"/> was loaded in the reflection-only context. 350 /// </exception> AssemblyCatalog(Assembly assembly)351 public AssemblyCatalog(Assembly assembly) 352 { 353 Requires.NotNull(assembly, nameof(assembly)); 354 355 InitializeAssemblyCatalog(assembly); 356 _definitionOrigin = this; 357 } 358 359 /// <summary> 360 /// Initializes a new instance of the <see cref="AssemblyCatalog"/> class 361 /// with the specified assembly. 362 /// </summary> 363 /// <param name="assembly"> 364 /// The <see cref="Assembly"/> containing the attributed <see cref="Type"/> objects to 365 /// add to the <see cref="AssemblyCatalog"/>. 366 /// </param> 367 /// <param name="definitionOrigin"> 368 /// The <see cref="ICompositionElement"/> CompositionElement used by Diagnostics to identify the source for parts. 369 /// </param> 370 /// <exception cref="ArgumentException"> 371 /// <paramref name="assembly"/> is <see langword="null"/>. 372 /// <para> 373 /// -or- 374 /// </para> 375 /// <paramref name="assembly"/> was loaded in the reflection-only context. 376 /// <para> 377 /// -or- 378 /// </para> 379 /// <paramref name="definitionOrigin"/> is <see langword="null"/>. 380 /// </exception> AssemblyCatalog(Assembly assembly, ICompositionElement definitionOrigin)381 public AssemblyCatalog(Assembly assembly, ICompositionElement definitionOrigin) 382 { 383 Requires.NotNull(assembly, nameof(assembly)); 384 Requires.NotNull(definitionOrigin, nameof(definitionOrigin)); 385 386 InitializeAssemblyCatalog(assembly); 387 _definitionOrigin = definitionOrigin; 388 } 389 InitializeAssemblyCatalog(Assembly assembly)390 private void InitializeAssemblyCatalog(Assembly assembly) 391 { 392 if (assembly.ReflectionOnly) 393 { 394 throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, SR.Argument_AssemblyReflectionOnly, "assembly"), "assembly"); 395 } 396 _assembly = assembly; 397 } 398 399 /// <summary> 400 /// Returns the export definitions that match the constraint defined by the specified definition. 401 /// </summary> 402 /// <param name="definition"> 403 /// The <see cref="ImportDefinition"/> that defines the conditions of the 404 /// <see cref="ExportDefinition"/> objects to return. 405 /// </param> 406 /// <returns> 407 /// An <see cref="IEnumerable{T}"/> of <see cref="Tuple{T1, T2}"/> containing the 408 /// <see cref="ExportDefinition"/> objects and their associated 409 /// <see cref="ComposablePartDefinition"/> for objects that match the constraint defined 410 /// by <paramref name="definition"/>. 411 /// </returns> 412 /// <exception cref="ArgumentNullException"> 413 /// <paramref name="definition"/> is <see langword="null"/>. 414 /// </exception> 415 /// <exception cref="ObjectDisposedException"> 416 /// The <see cref="ComposablePartCatalog"/> has been disposed of. 417 /// </exception> 418 /// <remarks> 419 /// <note type="inheritinfo"> 420 /// Overriders of this property should never return <see langword="null"/>, if no 421 /// <see cref="ExportDefinition"/> match the conditions defined by 422 /// <paramref name="definition"/>, return an empty <see cref="IEnumerable{T}"/>. 423 /// </note> 424 /// </remarks> GetExports(ImportDefinition definition)425 public override IEnumerable<Tuple<ComposablePartDefinition, ExportDefinition>> GetExports(ImportDefinition definition) 426 { 427 return InnerCatalog.GetExports(definition); 428 } 429 430 private ComposablePartCatalog InnerCatalog 431 { 432 get 433 { 434 ThrowIfDisposed(); 435 436 if (_innerCatalog == null) 437 { 438 var catalogReflectionContextAttribute = _assembly.GetFirstAttribute<CatalogReflectionContextAttribute>(); 439 var assembly = (catalogReflectionContextAttribute != null) 440 ? catalogReflectionContextAttribute.CreateReflectionContext().MapAssembly(_assembly) 441 : _assembly; 442 lock (_thisLock) 443 { 444 if (_innerCatalog == null) 445 { 446 var catalog = (_reflectionContext != null) 447 ? new TypeCatalog(assembly.GetTypes(), _reflectionContext, _definitionOrigin) 448 : new TypeCatalog(assembly.GetTypes(), _definitionOrigin); 449 Thread.MemoryBarrier(); 450 _innerCatalog = catalog; 451 } 452 } 453 } 454 return _innerCatalog; 455 } 456 } 457 458 /// <summary> 459 /// Gets the assembly containing the attributed types contained within the assembly 460 /// catalog. 461 /// </summary> 462 /// <value> 463 /// The <see cref="Assembly"/> containing the attributed <see cref="Type"/> objects 464 /// contained within the <see cref="AssemblyCatalog"/>. 465 /// </value> 466 public Assembly Assembly 467 { 468 get 469 { 470 Contract.Ensures(Contract.Result<Assembly>() != null); 471 472 return _assembly; 473 } 474 } 475 476 /// <summary> 477 /// Gets the display name of the assembly catalog. 478 /// </summary> 479 /// <value> 480 /// A <see cref="String"/> containing a human-readable display name of the <see cref="AssemblyCatalog"/>. 481 /// </value> 482 [SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes")] 483 string ICompositionElement.DisplayName 484 { 485 get { return GetDisplayName(); } 486 } 487 488 /// <summary> 489 /// Gets the composition element from which the assembly catalog originated. 490 /// </summary> 491 /// <value> 492 /// This property always returns <see langword="null"/>. 493 /// </value> 494 [SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes")] 495 ICompositionElement ICompositionElement.Origin 496 { 497 get { return null; } 498 } 499 500 /// <summary> 501 /// Returns a string representation of the assembly catalog. 502 /// </summary> 503 /// <returns> 504 /// A <see cref="String"/> containing the string representation of the <see cref="AssemblyCatalog"/>. 505 /// </returns> ToString()506 public override string ToString() 507 { 508 return GetDisplayName(); 509 } 510 Dispose(bool disposing)511 protected override void Dispose(bool disposing) 512 { 513 try 514 { 515 if (Interlocked.CompareExchange(ref _isDisposed, 1, 0) == 0) 516 { 517 if (disposing) 518 { 519 if (_innerCatalog != null) 520 { 521 _innerCatalog.Dispose(); 522 } 523 } 524 } 525 } 526 finally 527 { 528 base.Dispose(disposing); 529 } 530 } 531 GetEnumerator()532 public override IEnumerator<ComposablePartDefinition> GetEnumerator() 533 { 534 return InnerCatalog.GetEnumerator(); 535 } 536 ThrowIfDisposed()537 private void ThrowIfDisposed() 538 { 539 if (_isDisposed == 1) 540 { 541 throw ExceptionBuilder.CreateObjectDisposed(this); 542 } 543 } 544 GetDisplayName()545 private string GetDisplayName() 546 { 547 return string.Format(CultureInfo.CurrentCulture, 548 "{0} (Assembly=\"{1}\")", // NOLOC 549 GetType().Name, 550 Assembly.FullName); 551 } 552 LoadAssembly(string codeBase)553 private static Assembly LoadAssembly(string codeBase) 554 { 555 Requires.NotNullOrEmpty(codeBase, "codeBase"); 556 557 AssemblyName assemblyName; 558 559 try 560 { 561 assemblyName = AssemblyName.GetAssemblyName(codeBase); 562 } 563 catch (ArgumentException) 564 { 565 assemblyName = new AssemblyName(); 566 assemblyName.CodeBase = codeBase; 567 } 568 569 return Assembly.Load(assemblyName); 570 } 571 } 572 } 573