Is it possible to get DLL's names and version information from a appmanifest.xml which resides in a VS2010 project corresponding to a PRISM Module ?
My Silverlight 4 application loads on demand all modules listed in the modules catalog. I guess this means that it has downloaded all the modules corresponding XAP files, appmanifest.xml files – to load the necessary resources (DLL’s, etc)
So, at this point, how can I access DLL's names and if possible DLL's version number of every module from within my "main" Silverlight project ??
Thanks for your feedback!
You can do this in the ModuleInit.cs of each PRISM module. Somewhat like:
public class ModuleInit : IModule
{
private readonly IUnityContainer _container;
private readonly IRegionManager _regionManager;
public ModuleInit(IUnityContainer container, IRegionManager regionManager)
{
_container = container;
_regionManager = regionManager;
// Add this assembly details to a global collection
Global.ClientAssemblies.Add(GeneralHelper.GetAssemblyInfo(System.Reflection.Assembly.GetExecutingAssembly()));
}...
Helper function:
public static string GetAssemblyInfo(Assembly assembly)
{
return assembly.ToString();
}
Related
To implement a plug-in system in a AspNet Core Mvc app, I would like a non-generic method to add a data context from a list of assemblies loaded dynamically at runtime, taking a Type parameter like this:
foreach(Type tp in pluginContexts)
{
services.AddDbContext(tp, options => ...);
}
instead of the usual
services.AddDbContext<PluginDataContext>(options => ...);
That's because for dynamically loaded assemblies, I can not provide the TContext type parameter to the AddDbContextPool method, since that's statically compiled and not available at compile time.
Background
This is for a larger Asp.Net Core MVC app. The plugins must be able to both access the main database of the overall app and a separate database of their own.
Plugin assemblies, containing domain code and their private database context are to be dropped in a specified directory.
The main app loads the plugin assembly dynamically upon startup.
The way I am solving this now is to have each controller get the IConfiguration instance injected, obtain the appropriate connection string from the config, and the database context is instantiated in the controller. Not so nice but does work.
One can easily inject a general class into the Services collection with AddScoped<>, and then use it as a sort of ServiceLocator - however, that is considered an antipattern.
I looked into the source code for AddDbContext but honestly I am lost.
Is there any simple way to achieve this?
Solved it by creating an extensibility point in the plugin assembly.
Define an interface in the main app, which all plugins must implement.
public interface IPluginContextRegistration
{
void RegisterContext(ref IServiceCollection services, Action<DbContextOptionsBuilder> optionsAction);
String GetDatabaseName();
}
Create a class implementing this interface (in the plugin). It has access to the type of its private database context, thus can use the generic AddDbContext method:
public class DatabaseRegistration : IPluginContextRegistration
{
public void RegisterContext(ref IServiceCollection services, Action<DbContextOptionsBuilder> optionsAction)
{
services.AddDbContext<Test1DbContext>(optionsAction);
}
public String GetDatabaseName()
{
return "test-plugin-db";
}
}
Then in the main app ASP.Net Startup.cs file, add following code, which calls the RegisterContext() method for each plugin. For example, if you want to use Sql Server:
void RegisterPluginDbContexts(ref IServiceCollection services, List<Assembly> assemblyList)
{
IEnumerable<IPluginContextRegistration> registrars = new List<IPluginContextRegistration>();
foreach (Assembly assembly in assemblyList)
{
registrars = registrars.Concat(GetClassInstances<IPluginContextRegistration>(assembly));
}
foreach (var reg in registrars)
{
String name = reg.GetDatabaseName();
String connStr = Configuration.GetConnectionString(name);
reg.RegisterContext(ref services, options => options.UseSqlServer(connStr));
}
}
For completeness - the method "GetClassInstances" is just a helper method using Reflection to obtain an instance of classes implementing the specified interface.
So it's simple after all - no need for re-writing framework code .
In ASP.NET Core 6 default template moves everything from Sturtup.cs into Program.cs, and uses top-level statements in Program.cs, so there's no more (speakable) Program class ether.
That looks awesome, but now, I need to test all of this. WebApplicationFactory<T> still expects me to pass entry-point-class, but I cannot do this (due to it's name now being unspeakable).
How integration tests are expected to be configured in ASP.NET Core 6?
Note that if you are trying to use xUnit and its IClassFixture<T> pattern, you will run into problems if you just use the InternalsVisibleTo approach. Specifically, you'll get something like this:
"Inconsistent accessibility: base class WebApplicationFactory<Program> is less accessible than class CustomWebApplicationFactory."
Of course you can solve this by making CustomWebApplicationFactory internal but it only moves the problem as now your unit test class will give the same error. When you try to change it there, you will find that xUnit requires that tests have a public constructor (not an internal one) and you'll be blocked.
The solution that avoids all of this and allows you to still use IClassFixture<Program> is to make the Program class public. You can obviously do this by getting rid of the magic no class version of Program.cs, but if you don't want to completely change that file you can just add this line:
public partial class Program { } // so you can reference it from tests
Of course once it's public you can use it from your test project and everything works.
As an aside, the reason why you typically want to prefer using IClassFixture is that it allows you to set up your WebApplicationFactory just once in the test class constructor, and grab an HttpClient instance from it that you can store as a field. This allows all of your tests to be shorter since they only need to reference the client instance, not the factory.
Example:
public class HomePage_Get : IClassFixture<CustomWebApplicationFactory>
{
private readonly HttpClient _client = new HttpClient();
public HomePage_Get(CustomWebApplicationFactory factory)
{
_client = factory.CreateClient();
}
[Fact]
public async Task IncludesWelcome()
{
HttpResponseMessage response = await _client.GetAsync("/");
response.EnsureSuccessStatusCode();
string stringResponse = await response.Content.ReadAsStringAsync();
Assert.Contains("Welcome.", stringResponse);
}
}
Finally note that Damian Edwards' MinimalAPIPlayground was updated to use this approach after we discussed the issue. See this commit
The problem is was solved on ASP.NET Core RC1, but as of now (September 20, 2021) the docs are incomplete.
The compiler generates a Program class behind the scenes that can be used with WebApplicationFactory<>. The class isn't public though so the InternalsVisibleTo project setting should be used.
Damien Edwards' Minimal API sample uses the latest nightly bits. The test web app class is declared as :
internal class PlaygroundApplication : WebApplicationFactory<Program>
{
private readonly string _environment;
public PlaygroundApplication(string environment = "Development")
{
_environment = environment;
}
protected override IHost CreateHost(IHostBuilder builder)
{
...
In the application project file,InternalsVisibleTo is used to make the Program class visible to the test project:
<ItemGroup>
<InternalsVisibleTo Include="MinimalApiPlayground.Tests" />
</ItemGroup>
RC1 is feature complete and, judging by previous major versions, it will probably be the first version to have a Go Live license, which means it's supported in production.
I tried
<InternalsVisibleTo Include="MinimalApiPlayground.Tests" />
but no cigar! Removed it and added a partial class to program.cs
#pragma warning disable CA1050 // Declare types in namespaces
public partial class Program
{
}
#pragma warning restore CA1050 // Declare types in namespaces
amazingly it worked.
I have a Visual Studio Package where items are dynamically added to the menu bar. However, only the fixed entries are shown because the extension is not loaded correctly.
The package is only loaded when you click on a fixed entry. But it should be loaded at the start of the studio.
I tried everything with ProvideAutoLoad, the dynamic items are not shown. I don't know why. What is the problem ?
I hope someone can help me here
thx
[ProvideAutoLoad(VSConstants.UICONTEXT.NoSolution_string, PackageAutoLoadFlags.BackgroundLoad)]
[ProvideAutoLoad(VSConstants.UICONTEXT.SolutionExists_string, PackageAutoLoadFlags.BackgroundLoad)]
should be enough to automatically load a package on Visual Studio startup.
using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using System.Windows.Forms;
namespace VSIXOpenSCE
{
[PackageRegistration(UseManagedResourcesOnly = true)]
[InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)] // Info on this package for Help/About
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "pkgdef, VS and vsixmanifest are valid VS terms")]
[ProvideMenuResource("Menus.ctmenu", 1)]
[Guid(MenuControlPackage.PackageGuidString)]
[ProvideAutoLoad(VSConstants.UICONTEXT.NoSolution_string, PackageAutoLoadFlags.BackgroundLoad)]
[ProvideAutoLoad(VSConstants.UICONTEXT.SolutionExists_string, PackageAutoLoadFlags.BackgroundLoad)]
public sealed class MenuControlPackage : Package
{
public const string PackageGuidString = "f5c6cb4a-bb86-48e4-92e6-f0ee6de2de3a";
public MenuControlPackage()
{
// Inside this method you can place any initialization code that does not require
// any Visual Studio service because at this point the package object is created but
// not sited yet inside Visual Studio environment. The place to do all the other
// initialization is the Initialize method.
}
#region Package Members
/// <summary>
/// Initialization of the package; this method is called right after the package is sited, so this is the place
/// where you can put all the initialization code that rely on services provided by VisualStudio.
/// </summary>
protected override void Initialize()
{
base.Initialize();
MenuControl.Initialize(this);
}
#endregion
}
}
I have a simple container hierarchy with a parent container which is defined in the Shell MEFBootstrapper using a Directory catalog, and child container(s) which get created off the parent with a distinct catalog.
My child containers also use DirectoryCatalog (different path than parent), and I can see that the container has assembly and part information during runtime.
However, the Initialize() method for the modules located in the child containers are never called.
My goal is to use the child containers as session constructs, allowing the user to create new sessions and toggle between them. But if I cannot get the constituent modules to initialize (and place their views into the regions), I am kind of stuck.
I had thought to raise an event from my session manager using event aggregator to allow the modules to listen for the event and self-initialize, but that doesn't seem to work either.
i. Why isn't Initialize getting called on modules loaded into a child container
ii. How can I "trigger" Initialize from the container instance (outside of the module context?) Can you iterate over the assemblies in the container and trigger Initialize that way???
[from MefBootstrapper in shell project]
protected override DependencyObject CreateShell()
{
ExportProvider ep = this.Container as ExportProvider;
this.Container.ComposeExportedValue(ep);
[from the service that manages my sessions (containers)]
[ImportingConstructor]
public SessionService(ExportProvider provider)
{
[constructor for new sessions (containers)]
private void Init(ComposablePartCatalog catalog, ExportProvider provider, string name, int callId, bool useContextProxy)
{
this._Name = name;
this._CallID = callId;
this.startTime = DateTime.Now;
this.appHost = new CompositionContainer(catalog, new ExportProvider[] { provider });
}
=====
Was asked to include my module code whose initialize method is not called (despite being loaded into the container in question...I can even lazy instantiate the module, but calling Initialize() directly causes the injection operation to fail in the method proper.
namespace Module1
{
//, InitializationMode = InitializationMode.OnDemand
[ModuleExport("Module1.ModuleInit", typeof(Module1.ModuleInit))]
public class ModuleInit : IModule
{
private readonly IRegionManager _regionManager;
public IServiceLocator _serviceLocator;
[ImportingConstructor]
public ModuleInit(IRegionManager regionManager, IServiceLocator serviceLocator)
{
_regionManager = regionManager;
_serviceLocator = serviceLocator;
}
#region IModule Members
public void Initialize()
{
// Use View Discovery to automatically display the MasterView when the TopLeft region is displayed.
_regionManager.RegisterViewWithRegion(RegionNames.TopLeftRegion, () => _serviceLocator.GetInstance<MasterView>());
}
#endregion
}
}
I downloaded your code and had a look at it. I immediately found the problem. The bootstrapper is actually getting exports thanks to a DirectoryCatalog like this:
DirectoryCatalog catalog = new DirectoryCatalog(".");
this.AggregateCatalog.Catalogs.Add(catalog);
This means that you will get the exports from the assemblies in this directory. So you simply need to copy all the the assemblies with the exported types in the directory ".", that is to say the executing directory (Debug/bin).
Just copy Module1 and Module2 in the bin directory and everthing will compose gracefully :)
Actually I found that the post-build events supposed to copy the modules in the bin directory were not working. Maybe because your renamed something. So if you want it automatically copy the assemblies after building just replace the actual post-build event by this one:
copy "$(TargetDir)\$(TargetFileName)" "$(TargetDir)\..\..\..\Shell\bin\$(ConfigurationName)\"
I already had this problem many times and resolving it is really simple.
Remove the constructor from the your module. Prism modules are not activated the same way it does for classical exported types, therefore modules cannot use ImportingConstructor to import the services you need. Instead intitialize them with the ServiceLocator within the Initialize method.
This will work:
[ModuleExport("Module1.ModuleInit", typeof(Module1.ModuleInit))]
public class ModuleInit : IModule
{
private readonly IRegionManager _regionManager;
public void Initialize()
{
_regionManager = ServiceLocator.Current.GetInstance<IRegionManager>();
_regionManager.RegisterViewWithRegion(RegionNames.TopLeftRegion, () => _serviceLocator.GetInstance<MasterView>());
}
}
I also think that this behavior is kind of disturbing.
I had this same problem where my modules Initialize() method was not being called... I realized I had left off the "override" keyword on my Initialize method that was declared virtual in the Module base class that all of my modules inherit from... added "override" and it worked!
I have a main Silverlight Shell project, which calls several Silverlight Module projects.
I need to pass parameters to my module projects through constructors.
Can anybody help me to solve this?
"Ask and yea shall receive" is the IOC motto :)
Prism uses injection via the UnityContainer. When a module is loaded it will resolve any registered interfaces specified in the constructor of the module.
Just specify an interface to an object that you have previously registered as a singleton and it will be passed to with any module. Place all your settings/parameters in that singleton.
If you need more information, just ask.
Register an object with the container.
class MyBootStrapper : UnityBootstrapper
{
protected override void ConfigureContainer()
{
base.ConfigureContainer();
this.Container.RegisterInstance(typeof(IMyInterface), new MyInterfaceImpl());
}
}
Now, the module constructor happily receives that object.
class ContentModule : IModule
{
private readonly IMyInterface _myInterfaceImpl;
public ContentModule(IMyInterface myInterfaceImpl)
{
_myInterfaceImpl = myInterfaceImpl;
}
#region IModule Members
//
#endregion
}
Courtesy: TrueBlueAussie