BuildManager.GetReferencedAssemblies equivalent for non-web applications - asp.net

Compared to AppDomain.GetAssemblies(), BuildManager.GetReferencedAssemblies() (System.Web.Compilation.BuildManager) seems a more reliable way to get the assemblies that are referenced by an ASP.NET application at runtime, since AppDomain.GetAssemblies() only gets "the assemblies that have already been loaded into the execution context of this application domain".
Iterating through all assemblies is an important tool for dynamically registering types at application start-up in your DI container and especially during application start-up, chances are that other assemblies are not loaded (where not needed) yet, and the composition root is the first one that needs them. It is therefore very important to have a reliable method to get the application's referenced assemblies.
Although BuildManager.GetReferencedAssemblies() is a reliable method for ASP.NET applications, I'm wondering: what alternatives are available for other types of applications, such as desktop applications, windows services and self-hosted WCF services?

The only way I currently see is pre-fetching all referenced assemblies manually, just as the BuildManager does under the covers:
var assemblies =
from file in Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory)
where Path.GetExtension(file) == ".dll"
select Assembly.LoadFrom(file);

I've had the same problem. And after doing some research I've still not found a solid answer. The best I've come up with is to combine AppDomain.CurrentDomain.GetAssemblies() with the AppDomain.AssemblyLoad event.
In that way I can process all already loaded assemblies while getting notified for all new assemblies (which I then scan).

This solution is based on #steven's answer.
But would work in Web, WinForms, Consoles, and Windows Services.
var binDirectory = String.IsNullOrEmpty(AppDomain.CurrentDomain.RelativeSearchPath) ? AppDomain.CurrentDomain.BaseDirectory : AppDomain.CurrentDomain.RelativeSearchPath;
var assemblies = from file in Directory.GetFiles(binDirectory)
where Path.GetExtension(file) == ".dll"
select Assembly.LoadFrom(file);

Related

UWP API in ASP.NET

I'd like to use a piece of Windows 10 specific UWP API (specifically, the Windows.Graphics.Printing3D stuff) in an ASP.NET code-behind DLL. Is there any way to do so?
While looking for a .NET-only resolution to this one, I've found a moderately clean way - a Win32/64 C++ DLL that would consume UWP API and present a COM- or P/Invoke-based interface to .NET.
Create a regular Win32 DLL. Build an interface for .NET to consume - exported functions or objects, depends. In my case, a single exported function will do. In the project's C/C++ settings, make the following changes:
Under General, set Consume Windows Runtime Extensions to Yes.
Under General, set Additional #using Directories to: C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcpackages;C:\Program Files (x86)\Windows Kits\10\UnionMetadata (assuming Visual Studio 2015)
Under Code Generation, set Enable Minimal Rebuild to No (it's only Yes for Debug, not for Release).
Then instantiate and use UWP components in the DLL in the usual C++/CX manner, like you would in a Store app, via using namespace Windows::... and ref new.
In this approach, you lose bitness agnosticism; an unmanaged DLL can't be "Any CPU". You win some, you lose some. Also, the site will not run without the Visual C++ redistributable package on the system. On the other hand, it may run faster than a .NET app; less managed/native boundary crossings.
Inspiration: "Using C++/CX in Desktop apps" by Pavel Y.
Open the project file as XML, and paste the following line under the first <PropertyGroup>:
<TargetPlatformVersion>10.0</TargetPlatformVersion>
Once you do that, the Add reference dialog will include UWP libraries, and the file type options in the "Browse..." dialog there will include .winmd.
Load the project, do Add reference/Browse, locate C:\Program Files (x86)\Windows Kits\10\UnionMetadata\Windows.winmd, add that.
There are some helpful extension methods in the managed assembly System.Runtime.WindowsRuntime (e. g. IBuffer.AsStream()), but for some reason, it's not listed under Assemblies. To reference it, you'd need to edit the project file directly, and under the first <ItemGroup>, add the following:
<Reference Include="System.Runtime.WindowsRuntime" />
Unlike the guide states, you don't need to change the compilation target to x86 or x64; leave AnyCPU be.
For desktop .NET applications, this is sufficient. For ASP.NET, however, there's a catch. The way the ASP.NET runtime sets up its AppDomains not compatible with UWP. It's probably a bug deep down, but I've reported it, and a Microsoft rep said the whole thing was not a supported scenario to begin with.
Anyway, you have to change the LoaderOptimization policy of the AppDomain to SingleDomain. The quickest way to do so is via abusing a private method of AppDomain:
AppDomain ad = AppDomain.CurrentDomain;
MethodInfo mi = ad.GetType().GetMethod("SetupLoaderOptimization", BindingFlags.Instance | BindingFlags.NonPublic);
mi.Invoke(ad, new object[] { LoaderOptimization.SingleDomain });
A good place to do that would be in the app startup code.
A slightly less dangerous approach would involve creating a new AppDomain, which would inherit all setup properties from the current one but LoaderOptimization, which will be set to SingleDomain, and running the UWP dependent code in that domain. Like this:
AppDomain CreateUnsharingDomain()
{
AppDomain cad = AppDomain.CurrentDomain;
AppDomainSetup cads = cad.SetupInformation;
return AppDomain.CreateDomain("Dummy", cad.Evidence,
new AppDomainSetup
{
ApplicationName = cads.ApplicationName,
ApplicationBase = cads.ApplicationBase,
DynamicBase = cads.DynamicBase,
CachePath = cads.CachePath,
PrivateBinPath = cads.PrivateBinPath,
ShadowCopyDirectories = cads.ShadowCopyDirectories,
ShadowCopyFiles = cads.ShadowCopyFiles,
ApplicationTrust = cads.ApplicationTrust,
LoaderOptimization = LoaderOptimization.SingleDomain
});
//Not sure which other properties to copy...
}
CreateUnsharingDomain().DoCallBack(MyUWPDependentMethod);
Again, it would make sense to create the domain once and cache it for the app lifetime.
This approach might be faster than the one with the monkey-patched AppDomain. The MultiDomain optimization exists for a reason; if you leave most of the Web code in a MultiDomain world, the optimization will do its work as intended.
Inspiration: "Walkthrough: Using WinRT libraries from a Windows Desktop application" by David Moore.

Unable to load assembly in new appDomain

I have an mvc3 application.
I have a subfolder in my application project called "Plugins"
All of the dll's stored in that folder should be updateable during runtime, so then we an put down the appdomain and reload the new version of the dlls, so I am attemping to load all the plugin dll's in a different appDomain and set it's shadowCopy properties.
Looking through SO, msdn and some blogs, I got to this "solution". (this is called during my Application_Start)
static AppDomain pluginDomain;
static PluginHolder()
{
AppDomainSetup domainSetup = AppDomain.CurrentDomain.SetupInformation;
domainSetup.ApplicationName = "Plugins";
domainSetup.PrivateBinPathProbe = domainSetup.PrivateBinPath;
domainSetup.PrivateBinPath = GetPluginsDirectory();
domainSetup.ShadowCopyFiles = "true";
domainSetup.ShadowCopyDirectories = domainSetup.PrivateBinPath;
pluginDomain= AppDomain.CreateDomain("Plugins", null, domainSetup);
var item = pluginDomain.Load(File.ReadAllBytes(GetPluginsDirectory() + "Item.dll"));
}
"Item.dll" is the dll i am attemping to load. The last line throws a "Could not load file or assembly Item or one of its dependencies".
This seems to be the way other people have succeded with, but it just wont work for me.
I have no previous experience with AppDomains, so I am not sure on how to approach this problem, or if I am tackling it correctly.
Is my Assembly loading via the new AppDomain an ok approach?
To decypher these kind of error messages, you should use the Assembly Binding Log Viewer.
Also pay attention on the remarks section on the documentation page of AppDomain.Load(byte[]). If you want to load the assembly on the new AppDomain, then you should use CreateInstanceAndUnwrap instead.
If you want to implement a plugin engine in .NET, then you will need to understand AppDomains, assembly resolving and loading and a then choose on a mechanism for the communication between objects from different AppDomains. Most good books on .NET and CLR include a chapter or two on AppDomains and they will get you up to speed. Now as far as the cross-AppDomain communication is concerned, you could use WCF or .NET Remoting (MarshalByRefObject).
Good luck.

Why do ASP.NET resolve assembly references differently?

I really tried hard to find a similar issue to get some leads, but no one seems to describe the case we are having, so here it goes.
Background
We have a product with the following general design:
[Local installation folder]
Contains a set of .NET assemblies, implementing the bulk of our product functionality.
Example: Implementation1.dll, Implementation2.dll
[GAC]
ClientAPI.dll. Our client assembly, to be referenced from end user Visual Studio projects. Has strong references to the implementation dll's in the local installation folder.
In ClientAPI.dll, we have an entrypoint we require end user projects to invoke. Lets call it Initialize().
The very first thing we do in Initialize is to install a so called assembly resolve handler on the current domain, using the AssemblyResolve event. This handler will know how to locate the implementation dll's and load them into the client process, using Assembly.Load().
Consider a console application. It will look something like:
class Class1
{
void Main(string[] args)
{
ClientAPI.Initialize();
// Use other API's in the assembly, possibly internally referencing the
// implementation classes, that now will be resolved by our assembly
// resolve handler.
}
}
Now, all is good in the console/windows forms/WPF world. Our assembly resolve handler is properly installed and invoked, and it can successfully resolve references to the implementation DLL's once ClientAPI.dll require their functionality.
Problem statement
With that being said, we intend not to support only console or WPF applications, so we were relying on the same design in ASP.NET. Hence, creating a new ASP.NET Web Application project in VS 2010, we figured everything would be as straightforward as:
class Globals : HttpApplication
{
void Application_Start(object sender, EventArgs e)
{
ClientAPI.Initialize();
// ...
}
}
A few 20-30 hours of dwelling in the ASP.NET runtime universe, trying the above in both the development server and in IIS, we've learned that things there are not really as we expected.
It turns out that in ASP.NET, as soon as the ClientAPI class is referenced anywhere, all references it has to any other assemblies are instantly resolved. And not only that: the results are cached (by design, since .NET 2.0 we've found), meaning we never have a chance at all trying to assist the CLR.
Without further elaboration about the different things we've tried and learned, it basically comes down to this question we have:
Why is ASP.NET resolving references like this? It is not compatible with how other types of applications does it, and even more, it is not according to the documentation of .NET / the CLR runtime, specifying that references to external types / assemblies are to be resolve when first needed (i.e when first used in code).
Any kind of insight/ideas would be highly appreciated!
Windows Forms / WPF applications run on individual client machines (and therefore run in a single, local context), whereas ASP.Net runs within IIS, within an application pool, on a server or set of servers (in a web farm situation). Whatever is loaded in to the application pool is available to the entire application (and therefore is shared between all clients who connect to the application).
HttpApplication.Application_Start is executed once, when the application starts up. It is not executed per client as it would be with a Winforms application - if you need to initialize something for every client that connects, use Session_Start or Session_OnStart, but then you may run in to memory issues with the server, depending on how many clients are going to connect to your web application. This also depends on whether your class is a singleton, and if the Initialize() method is static. If you have either of those situations, you're going to run in to cross-threading problems fairly quickly.
Additionally, it's worth noting that an idle IIS application pool will reset itself after a period of time. If no one uses the web application overnight, for example, IIS will flush the application's application pool and free up memory. These settings can be changed within IIS administration, but you should be careful when doing so - changing these settings to circumvent a badly designed object (or an object that isn't designed for a web application) can create even more problems.
FYI - I'm being a little fussy, but for the avoidance of doubt, the object is not cached - yes, it is loaded in to memory, but how the memory is managed is up to how you've designed the object (caching in the web world is an entirely different thing, and can be implemented in many different layers of your application).
Don't try and make a web application act like a windows application; you'll just create yourself more problems!

When does the CLR try to load a referenced assembly?

I want to write a small installer app that installs a web site and creates IIS virtual directories. The app should run on Windows XP/Server 2003 (IIS 6) as well as on Vista/2008 (IIS 7).
The problem is: for IIS 6 we create virt dirs by calling WMI/Metabase API, for IIS 7 there is a much better API: Microsoft.Web.Administration, but its assembly is available only on IIS 7 systems.
Naive approach:
...
if (OperatingSystem == old)
{
call metabase API...
}
else
{
call Microsoft.Web.Administration...
}
...
Nice, isn't it? But how can I make sure that this does not crash on a old system just while trying to load the Microsoft.Web.Administration DLL? Or is an assembly just loaded, when it is first used? When a method that is calling into the assembly is first used?
I guess, testing does not help without some determinism being guaranteed by CLR/.NET spec.
I am really looking forward to hearing your experiences, hints or solutions for this topic. I have not found anything remotely usable on the web so far.
I have not been able to find the definitive answer as in a specification stating when assemblies must and must not be loaded. However, according to
http://msdn.microsoft.com/en-us/magazine/cc163655.aspx (section "Load Fewer Modules at Startup")
and the book extract at www.informit.com/articles/article.aspx?p=30601&seqNum=5 (extract from "Essential .NET, Volume I: The Common Language Runtime").
the JIT of the CLR will load a needed assembly only when needed to compile a method. Thus, you should move any use of Microsoft.Web.Administration... to a seperate method which is only called when your confident that the assembly exists on the system. That is,
setup()
{
if ( Operating.System == Old )
call metabase API
else
doIIS7Setup()
}
void doIIS7Setup()
{
call Microsoft.Web.Administration ....
}
Personally, rather than trying to rely on any inbuilt behaviour of the JIT, I'd move the dependency on Microsoft.Web.Administration to another assembly altogether.
Then, somewhere in the calling assembly, I'd check to see if %systemroot%\inetsrv\Microsoft.Web.Administration.dll is present. If so, then I'd assume I'm using the managed interface and call the assembly; if not, I'd revert to the metabase API.

How to show all ASP.NET web application's loaded/referenced assemblies with version numbers?

I'd like to show a list of all used assemblies on a dedicated web page inside a ASP.NET web application for debugging and maintainance purposes (there are many instances of this web application on many different servers). The assemblies could be called from the bin folder or from the GAC. There are some written by myself, but also third-party assemblies.
Is there a simple, automated way to get a list of all loaded or referenced assemblies of the web application including the version number (calling System.Reflection.Assembly - GetName().Version ...)? Do I have to explicitly pass all assembly names for version check or could I retrieve them automatically? Do I have to use Reflection for a comprehensive list of dependencies/references?
I think you can use AppDomain.CurrentDomain.GetAssemblies() for this, e.g something like this:
foreach (System.Reflection.Assembly a in AppDomain.CurrentDomain.GetAssemblies())
{
Response.Write(a.FullName + "<br/>");
}

Resources