Where to find references added to Roslyn's Session? - c#

I'm trying to code a console shell using Roslyn.Scripting.CSharp. Of course user is able to dynamically add references to the Session using methods of host object:
public void AddRef(string nameOrPath)
{
session.AddReference(nameOrPath);
}
public void AddRef(System.Reflection.Assembly asm)
{
session.AddReference(asm);
}
But I also want him to be able to list already loaded assemblies. How to do that?
Not working / bad solutions:
AppDomain.CurrentDomain.GetAssemblies() - yields all assemblies loaded to the AppDomain which are not necessarily avaible in Roslyn's scripting session
Using ScriptEngine's property GetReferences() - as I've understood, it returns session-wide list of references, and my AddRef method doesn't change it
Collecting own list of added references. But what if reference was added by display name? How to convert string nameOrPath to Assembly type? Using Roslyn.Compilers.MetadataReference does not help, because it hasn't got the reference to Assembly.

Related

How to load an assembly from byte array into a non-default AppDomain without accessing the app directory?

I have an in-memory assembly MyAssembly (class library) that is used in my main assembly MyApp.exe:
byte[] assemblyData = GetAssemblyDataFromSomewhere();
(For testing, the GetAssemblyDataFromSomewhere method can just do File.ReadAllBytes for an existing assembly file, but in my real app there is no file.)
MyAssembly has only .NET Framework references and has no dependencies to any other user code.
I can load this assembly into the current (default) AppDomain:
Assembly.Load(assemblyData);
// this works
var obj = Activator.CreateInstance("MyAssembly", "MyNamespace.MyType").Unwrap();
Now, I want to load this assembly into a different AppDomain and instantiate the class there. MyNamespace.MyType is derived from MarshalByRefObject, so I can share the instance across the app domains.
var newAppDomain = AppDomain.CreateDomain("DifferentAppDomain");
// this doesn't really work...
newAppDomain.Load(assemblyData);
// ...because this throws a FileNotFoundException
var obj = newAppDomain.CreateInstanceAndUnwrap("MyAssembly", "MyNamespace.MyType");
Yes, I know there is a note in the AppDomain.Load docs:
This method should be used only to load an assembly into the current application domain.
Yes, it should be used for that, but...
If the current AppDomain object represents application domain A, and the Load method is called from application domain B, the assembly is loaded into both application domains.
I can live with that. There's no problem for me if the assembly will be loaded into both app domains (because I actually load it into the default app domain anyway).
I can see that assembly loaded into the new app domain. Kind of.
var assemblies = newAppDomain.GetAssemblies().Select(a => a.GetName().Name);
Console.WriteLine(string.Join("\r\n", assemblies));
This gives me:
mscorlib
MyAssembly
But trying to instantiate the class always leads to a FileNotFoundException, because the CLR tries to load the assembly from file (despite it is already loaded, at least according to AppDomain.GetAssemblies).
I could do this in MyApp.exe:
newAppDomain.AssemblyResolve += CustomResolver;
private static Assembly CustomResolver(object sender, ResolveEventArgs e)
{
byte[] assemblyData = GetAssemblyDataFromSomewhere();
return Assembly.Load(assemblyData);
}
This works, but this causes the second app domain to load the calling assembly (MyApp.exe) from file. It happens because that app domain now needs the code (the CustomResolver method) form the calling assembly.
I could move the app domain creation logic and the event handler into a different assembly, e.g. MyAppServices.dll, so the new app domain will load that assembly instead of MyApp.exe.
However, I want to avoid the file system access to my app's directory at any cost: the new app domain must not load any user assemblies from files.
I also tried AppDomain.DefineDynamicAssembly, but that did't work either, because the return value's type System.Reflection.Emit.AssemblyBuilder is neither MarshalByRefObject nor marked with [Serializable].
Is there any way to load an assembly from byte array into a non-default AppDomain without loading the calling assembly from file into that app domain? Actually, without any file system access to my app's directory?
You first problem is the way you load the assembly into the second AppDomain.
You need some type loaded / shared between both AppDomains. You can't load assembly into the second AppDomain from the first AppDomain if the assembly is not already loaded into the first AppDomain (it also won't work if you load the assembly bytes into the first AppDomain uisng .Load(...)).
This should be a good starting point:
Lets say i have class library named Models with single class Person as follows:
namespace Models
{
public class Person : MarshalByRefObject
{
public void SayHelloFromAppDomain()
{
Console.WriteLine($"Hello from {AppDomain.CurrentDomain.FriendlyName}");
}
}
}
and console application as follows (the Models class library is NOT references from the project)
namespace ConsoleApp
{
internal class Program
{
[LoaderOptimizationAttribute(LoaderOptimization.MultiDomain)]
public static void Main(String[] args)
{
CrossAppDomain();
}
private static Byte[] ReadAssemblyRaw()
{
// read Models class library raw bytes
}
private static void CrossAppDomain()
{
var bytes = ReadAssemblyRaw();
var isolationDomain = AppDomain.CreateDomain("Isolation App Domain");
var isolationDomainLoadContext = (AppDomainBridge)isolationDomain.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().FullName, "ConsoleApp.AppDomainBridge");
// person is MarshalByRefObject type for the current AppDomain
var person = isolationDomainLoadContext.ExecuteFromAssembly(bytes);
}
}
public class AppDomainBridge : MarshalByRefObject
{
public Object ExecuteFromAssembly(Byte[] raw)
{
var assembly = AppDomain.CurrentDomain.Load(rawAssembly: raw);
dynamic person = assembly.CreateInstance("Models.Person");
person.SayHelloFromAppDomain();
return person;
}
}
}
The way it works is by creating instance of the AppDomainBridge from the ConsoleApp project which is loaded into both AppDomains. Now this instance is living into the second AppDomain. Then you can use the AppDomainBridge instance to actually load the assembly into the second AppDomain and skipping anything to do with the first AppDomain.
This is the output of the console when i execute the code (.NET Framework 4.7.2), so the Person instance is living in the second AppDomain:
Your second problem is sharing instances between AppDomains.
The main problem between AppDomains sharing the same code is the need to share the same JIT compiled code (method tables, type information ... etc).
From docs.microsoft:
JIT-compiled code cannot be shared for assemblies loaded into the
load-from context, using the LoadFrom method of the Assembly class, or
loaded from images using overloads of the Load method that specify
byte arrays.
So you won't be able to fully share the type information when you load assmebly from bytes, which means your object at this point is just MarshalByRefObject for the first AppDomain. This means that you can execute and access methods / properties only from the MarshalByRefObject type (it does not matter if you try to use dynamic / reflection - the first AppDomain does not have the type information of the instance).
What you can do is not to return the object from ExecuteFromAssembly, but to extend the AppDomainBridge class to be simple wrapper around the created Person instance and use it to delegate any method execution from the first AppDomain to the second if you really need it for those purposes.
I'm not quite sure what are you trying to achieve, but I would try the following.
In general, your approach seems OK. You have to make sure your probing paths (especially, the appbase path) for the secondary appdomain are set correctly. Otherwise, .NET Fusion will probe these locations for dependencies, and you'll have those unwanted file system access attempts, you're trying to avoid. (Well, at least make sure that these paths are configured to some temp folders with no real permissions set up).
PROPOSED SOLUTION
In any case, you can try adding to your dynamic (is this how I should call it?) assembly an entry point (ex. Main method in some Bootstrap class), and
try calling AppDomain.ExecuteAssemblyByName, after loading the assembly into the secondary AppDomain.
I would add to the Bootstrap class your CustomResolver method, and in the Main method, I would subscribe to AssemblyResolve.
This way, when the Main method is called (and hopefully it works as expected), the subscription to AppDomain's AssemblyResolve won't trigger fusion.
I didn't test this solution, and it could be a long shot, but worse trying.
P.S.:
I do see that documentation on this method does state, that the runtime will try to load the assembly first (by probably using regular probing logic), but it doesn't say anything about situation in which the assembly was pre-loaded into the AppDomain before the call was made.
Remarks
The ExecuteAssemblyByName method provides similar functionality to the ExecuteAssembly method, but specifies the assembly by display name or AssemblyName rather than by file location. Therefore, ExecuteAssemblyByName loads assemblies with the Load method rather than with the LoadFile method.
The assembly begins executing at the entry point specified in the .NET Framework header.
This method does not create a new process or application domain, and it does not execute the entry point method on a new thread.
Load method documentation doesn't provide a clear answer either.
P.P.S:
Calling the Unwrap method, may trigger the fusion in your main AppDomain, since a proxy is created for your class. I would think, that at this point your main AppDomain would try to locate that dynamically loaded assembly. Are you sure it's the secondary AppDomain that throws the exception?

Retrieve <Version> tag written in csproj file from dll [duplicate]

I am trying to get the executing assembly version in C# 3.0 using the following code:
var assemblyFullName = Assembly.GetExecutingAssembly().FullName;
var version = assemblyFullName .Split(',')[1].Split('=')[1];
Is there another proper way of doing so?
Two options... regardless of application type you can always invoke:
Assembly.GetExecutingAssembly().GetName().Version
If a Windows Forms application, you can always access via application if looking specifically for product version.
Application.ProductVersion
Using GetExecutingAssembly for an assembly reference is not always an option. As such, I personally find it useful to create a static helper class in projects where I may need to reference the underlying assembly or assembly version:
// A sample assembly reference class that would exist in the `Core` project.
public static class CoreAssembly
{
public static readonly Assembly Reference = typeof(CoreAssembly).Assembly;
public static readonly Version Version = Reference.GetName().Version;
}
Then I can cleanly reference CoreAssembly.Version in my code as required.
In MSDN, Assembly.GetExecutingAssembly Method, is remark about method "getexecutingassembly", that for performance reasons, you should call this method only when you do not know at design time what assembly is currently executing.
The recommended way to retrieve an Assembly object that represents the current assembly is to use the Type.Assembly property of a type found in the assembly.
The following example illustrates:
using System;
using System.Reflection;
public class Example
{
public static void Main()
{
Console.WriteLine("The version of the currently executing assembly is: {0}",
typeof(Example).Assembly.GetName().Version);
}
}
/* This example produces output similar to the following:
The version of the currently executing assembly is: 1.1.0.0
Of course this is very similar to the answer with helper class "public static class CoreAssembly", but, if you know at least one type of executing assembly, it isn't mandatory to create a helper class, and it saves your time.
using System.Reflection;
{
string version = Assembly.GetEntryAssembly().GetName().Version.ToString();
}
Remarks from MSDN http://msdn.microsoft.com/en-us/library/system.reflection.assembly.getentryassembly%28v=vs.110%29.aspx:
The GetEntryAssembly method can return null when a managed assembly has been loaded from an unmanaged application. For example, if an unmanaged application creates an instance of a COM component written in C#, a call to the GetEntryAssembly method from the C# component returns null, because the entry point for the process was unmanaged code rather than a managed assembly.
Product Version may be preferred if you're using versioning via GitVersion or other versioning software.
To get this from within your class library you can call System.Diagnostics.FileVersionInfo.ProductVersion:
using System.Diagnostics;
using System.Reflection;
//...
var assemblyLocation = Assembly.GetExecutingAssembly().Location;
var productVersion = FileVersionInfo.GetVersionInfo(assemblyLocation).ProductVersion
This should do:
Assembly assem = Assembly.GetExecutingAssembly();
AssemblyName aName = assem.GetName();
return aName.Version.ToString();
I finally settled on typeof(MyClass).GetTypeInfo().Assembly.GetName().Version for a netstandard1.6 app. All of the other proposed answers presented a partial solution. This is the only thing that got me exactly what I needed.
Sourced from a combination of places:
https://msdn.microsoft.com/en-us/library/x4cw969y(v=vs.110).aspx
https://msdn.microsoft.com/en-us/library/2exyydhb(v=vs.110).aspx

ComposeParts method not working

I've got an ITagger and an IWpfTextViewMargin, both are exported as MEF components. I want to import the ITagger in my Margin code, and then use some members in that Tagger.
Now I tried to use ComponentContainer in the Margin class, then import the IViewTaggerProvider. I used the following code, which can be found in many MEF tutorials
[Import(typeof(IViewTaggerProvider))]
public IViewTaggerProvider vt_provider { get; set; }
var catalog = new AggregateCatalog();
catalog.Catalogs.Add(new AssemblyCatalog(typeof(TestMargin).Assembly));
_container = new CompositionContainer(catalog);
//Fill the imports of this object
try
{
this._container.ComposeParts(this);
}
catch (CompositionException compositionException)
{
System.Diagnostics.Trace.WriteLine(compositionException.Message);
}
and the export code.
[Export(typeof(IViewTaggerProvider))]
[ContentType...
The exported class is defined in another namespace but same assembly.
Here I got problem that ComposeParts(this) throws ImportCardinalityMismatchException. I don't know why the parameter is this. I tried to pass the catalog to it, there is no exception but the import is also null. I also referred to debug mef failures and believe that the exported class has the right contract name and export type identity.
After checking the assembly with Visual MEFx and debugging, I found that probably it's because the IViewTaggerProvider imports a Visual Studio IClassificationTypeRegistryService, which is also an MEF part and results in a rejection of the IViewTaggerProvider.
[Primary Rejection]
[Exception] System.ComponentModel.Composition.ImportCardinalityMismatchException: No valid exports were found that match the constraint '((exportDefinition.ContractName == "Microsoft.VisualStudio.Text.Classification.IClassificationTypeRegistryService") AndAlso (exportDefinition.Metadata.ContainsKey("ExportTypeIdentity") AndAlso "Microsoft.VisualStudio.Text.Classification.IClassificationTypeRegistryService".Equals(exportDefinition.Metadata.get_Item("ExportTypeIdentity"))))', invalid exports may have been rejected.
So one solution is to add the assembly that exports IClassificationTypeRegistryService. It's a Visual Studio core editor service but I cannot find which assembly exports it. Anyone knows this?
Or any better possible solutions?
Try VisualMEFx. Here is a short blog entry about getting started https://ihadthisideaonce.com/2012/02/22/getting-started-with-visual-mefx/. Once you have it up and running, use VisualMEFx to load the TestMargin assembly and see if any IViewTaggerProvider is exported from that assembly.
Also remember that ImportCardinalityMistmatch doesn't only mean that an export is missing. It can also mean that there are too many exports available that can satisfy the import and MEF has no way of choosing which one to use. So when you are examining your composition in VisualMEFx, check to see if there are too many.
This parameter:
void Bootstrap()
{
var catalog = new AggregateCatalog();
catalog.Catalogs.Add(new AssemblyCatalog(typeof(TestMargin).Assembly));
_container = new CompositionContainer(catalog);
//Fill the imports of this object
try
{
var objectToSatisfy = this;
// var objectToSatifsy = new SomeOtherObjectWithImports();
this._container.ComposeParts(objectToSatisfy);
}
catch (CompositionException compositionException)
{
System.Diagnostics.Trace.WriteLine(compositionException.Message);
}
}
When you call ComposeParts you pass an object to the method. MEF will take the object that you pass and see if there are any imports on it that need to be satisfied. If it finds any imports, it will look in the catalog and try to satisfy them. You can pass any object you want to the ComposeParts method. So I've modified your sample code a little to show two different options. One option is to create some object that needs to be satisfied, and then give it to the container for composition. This is what I have done in the commented out line var objectToSatisfy = new SomeOtherObjectWithImports(). But it is often the case that the object we want to compose is the same object that's calling ComposeParts. So we don't need to create a new object to pass to the container, we already have the object, we just need a reference to it. In C# we can get a reference to the current object instance using the keyword this (in VB.NET the keyword is Me). So, when we want to satisfy imports on the same object that is calling ComposeParts, we can do so by using the this reference as the argument to ComposeParts.
The argument to the ComposeParts method is a parameter array. Informally, this just means that when you write container.ComposeParts(this) it is interpreted as if you had written container.ComposeParts(new object[] { this }). In practice this means you can pass multiple objects to MEF at once, like this:
container.ComposeParts(this, objectToSatifsy, thirdObjectToCompose);
If the object calling ComposeParts has no imports on it, then you should not be using this as the argument. Instead, create an object of the type that you want to compose and pass that to the method. Also, unless all the parts that you want to compose are available in the TestMargin assembly, you need to create more AssemlbyCatalogs for the assemblies that do provide the parts and add them to your AggregateCatalog.

Referencing a different project in the same assembly, different namespaces

I have two projects : Menu and Module and they are both in the same namespace foobar.
I am currently referencing the module project from the Menu project to open up certain controls on a tab control in my menu. However I need to launch a new control from one of my controls which is located in the Module project.
When I try referencing the menu project, it does not show up in my intellisense when I try to reference it with a using. Am I doing something wrong logically here?
Here is an example of what it is :
Project Menu
Public Void LaunchWPFControl(string pHeader,string pPath)
{
//Code goes here to launch a WPF control in the browser
}
Project Module
//What I would love to do but doesn't work
Using Menu;
...
...
...
private void dgModule_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
Menu.LaunchWPFControl("Accounts","AccountsControl");
}
If you are talking about seperate projects then what you are trying to do here is a circular reference, this is not allowed. If Project Menu references Project Module, then Project Module cannot reference Project Menu.
If you need a class in Project Module to trigger something in the Menu project you need to look for an alternative way of doing it. One possible technique for achieving this is to create an event in the class in the Module project that the Menu project can subscribe to and perform the required action.
For example in Project Module:
private void dgModule_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
OnMyEvent();
}
private void OnMyEvent()
{
EventHandler localEvent = MyEvent;
if(localEvent != null)
{
localEvent(this, EventArgs.Empty);
}
}
Then in Project Menu you can subscribe to this event and perform your action:
...
...
...
moduleClass.MyEvent += SomeHandler;
...
...
...
private void SomeHandler(Object sender, EventArgs e)
{
Menu.LaunchWPFControl("Accounts","AccountsControl");
}
As Ray Burns mentions (see comments) another way would be to define an interface of the Menu class in some shared location (either there referenced project, or some other shared project) and than you can pass implementations of that interface to the Module project.
Which way is better often depends on the abstraction you are trying to achieve with each project.
If they're both in namespace foobar then you need
using foobar;
instead of using Menu.
It's important that you understand the terminology involved - you're talking about "a different project in the same assembly" - that's nonsensical. Visual Studio creates one assembly per project. You then talk about the projects having the same namespace in the text of your question. You need to understand the difference.
To work out the namespace of a type, open the class containing the type and look for namespace declarations:
namespace MyProject
{
...
}
To work out the assembly of a type, look in the project properties for the project in which it's declared - or if you're using the same solution, just add a reference from the project which wants to use the type to the project which declares the type.
Note that you specify a namespace with a using directive; you need to add a reference to an assembly in solution explorer. They're not doing the same thing. A using directive just says, "Within the code affected by this using directive, I want to be able to use types within this namespace without fully qualifying the names."
Next you've got code like this:
Menu.LaunchWPFControl("Accounts","AccountsControl");
I thought Menu was either a project name or a namespace - but now you're trying to use it as a type name. Which is it?
If this doesn't sort you out, please post full code and a more coherent description of the projects and namespaces involved. Take a step back, work out the types, namespaces and assemblies involved, and then lay it all out clearly.
So Project Menu references Project Module. Then you want Project Module to reference Project Menu so a module can directly call Menu functionality?
This isn't possible, this is a circular reference. If you try to add the Menu reference to the Module project, Visual Studio won't let you.
You need to pull the stuff out of Menu that both Menu and Module want to use into a third project, and have them both reference it. Or combine Menu and Module into one project.

Using the Web Application version number from an assembly (ASP.NET/C#)

How do I obtain the version number of the calling web application in a referenced assembly?
I've tried using System.Reflection.Assembly.GetCallingAssembly().GetName() but it just gives me the dynamically compiled assembly (returning a version number of 0.0.0.0).
UPDATE: In my case I needed a solution that did not require a reference back to a class within the web application assembly. Jason's answer below (marked as accepted) fulfils this requirement - a lot of others submitted here don't.
Here is some code I use that supports getting the application's "main" assembly from either Web or non-web apps, you can then use GetName().Version to get the version.
It first tries GetEntryAssembly() for non-web apps. This returns null under ASP.NET.
It then looks at HttpContext.Current to determine if this is a web application. It then uses the Type of the current HttpHandler - but this type's assembly might be a generated ASP.NET assembly if the call is made from with an ASPX page, so it traverses the HttpHandler's BaseType chain until it finds a type that isn't in the namespace that ASP.NET uses for its generated types ("ASP").
This will usually be a type in your main assembly (eg. The Page in your code-behind file). We can then use the Assembly of that Type.
If all else fails then fall back to GetExecutingAssembly().
There are still potential problems with this approach but it works in our applications.
private const string AspNetNamespace = "ASP";
private static Assembly getApplicationAssembly()
{
// Try the EntryAssembly, this doesn't work for ASP.NET classic pipeline (untested on integrated)
Assembly ass = Assembly.GetEntryAssembly();
// Look for web application assembly
HttpContext ctx = HttpContext.Current;
if (ctx != null)
ass = getWebApplicationAssembly(ctx);
// Fallback to executing assembly
return ass ?? (Assembly.GetExecutingAssembly());
}
private static Assembly getWebApplicationAssembly(HttpContext context)
{
Guard.AgainstNullArgument(context);
object app = context.ApplicationInstance;
if (app == null) return null;
Type type = app.GetType();
while (type != null && type != typeof(object) && type.Namespace == AspNetNamespace)
type = type.BaseType;
return type.Assembly;
}
UPDATE:
I've rolled this code up into a small project on GitHub and NuGet.
I find that the simplest one-liner way to get the version of your "main" assembly (instead of the dynamic one) is:
typeof(MyMainClass).Assembly.GetName().Version
Use your top-level class, which isn't likely to ever "change its meaning" or to be replaced as part of a refactoring effort, as MyMainClass. You know in which assembly this very class is defined and there can no longer be confusion as to where the version number comes from.
I prefer the Web.Config to store the current version of the site.
You can also try create an AssemblyInfo.cs file in the web application root that has the following:
using System.Reflection;
using System.Runtime.CompilerServices;
...
[assembly: AssemblyVersion("1.0.*")]
...
then access the value via the code like this:
System.Reflection.Assembly.GetExecutingAssembly()
Here is more informaiton on the AssemblyInfo class.
To add to the responders that have already posted. In order to get the assembly version in an ASP.Net web application you need to place a method in the code behind file similar to:
protected string GetApplicationVersion() {
return System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString();
}
In the ASPX page you want to display the version number simply place:
<%= GetApplicationVersion() %>
Just in case anyone is still interested; this should do the trick and should be a tad safer than just taking the BaseType of ApplicationInstance to get your hands on the Global.asax implementation.
Global.asax is always compiled into the same assembly as the assembly attributes from AssemblyInfo.cs, so this should work for all web applications that define a Global.asax.
For those that don't define their own Global.asax, it will fall back to the version of the generated global_asax type, which is always 0.0.0.0, and for applications that aren't web applications, it will just return no version at all.
Bonus; using the BuildManager class does not require an active HttpContext instance, which means you should be able to use this from application startup code as well.
public static Version GetHttpApplicationVersion() {
Type lBase = typeof(HttpApplication);
Type lType = BuildManager.GetGlobalAsaxType();
if (lBase.IsAssignableFrom(lType))
{
while (lType.BaseType != lBase) { lType = lType.BaseType; }
return lType.Assembly.GetName().Version;
}
else
{
return null;
}
}
HttpContext.Current.ApplicationInstance is derived from the class in the global.asax.cs. You can do the following
var instance = HttpContext.Current.ApplicationInstance;
Assembly asm = instance.GetType().BaseType.Assembly;
System.Version asmVersion = asm.GetName().Version;
It works both in ASP.NET (ASPX) and ASP.NET MVC
I encountered a similar problem, and thought you might find the solution useful.
I needed to report the current application version (of a web application project) from a custom server control, where the server control was contained in a different library. The problem was that the "easiest" assembly getters did not provide the right assembly.
Assembly.GetExecutingAssembly() returned the assembly containing the control; not the application assembly.
Assembly.GetCallingAssembly() returned different assemblies depending on where I was at in the call tree; usually System.Web, and sometimes the assembly containing the control.
Assembly.GetEntryAssembly() returned null.
new StackTrace().GetFrames()[idx].GetMethod().DeclaringType.Assembly retrieves the assembly of a frame in the stack trace at index idx; however, besides being inelegant, expensive, and prone to miscalculation on the frame index, it is possible for the stack trace to not contain any calls to the application assembly.
Assembly.GetAssembly(Page.GetType()) scored me the App_Web_##$##$%# assembly containing the dynamically generated page. Of course, the dynamic page inherits a class from my application assembly, so that led to the final solution:
Assembly.GetAssembly(Page.GetType().BaseType)
With the assembly reference in hand, you can drill to the version through its name:
var version = Assembly.GetAssembly(Page.GetType().BaseType)
.GetName()
.Version;
Now, this solution works because I had a reference to a type from the application assembly. We don't use any pages that do not inherit from a code behind, so it happens to be effective for us, but your mileage may vary if your organization's coding practices are different.
Happy coding!
Version version = new Version(Application.ProductVersion);
string message = version.ToString();
Some info here: http://www.velocityreviews.com/forums/showpost.php?p=487050&postcount=8
in asp.net 2.0 each page is built into it own assembly, so only the dll
the AssemblyInfo.cs is built into will
return the correct answer. just add a
static method to AssemblyInfo.cs that
returns the version info, and call
this method from your other pages.
-- bruce (sqlwork.com)
But I wrote a simple method to do that:
public static string GetSystemVersion(HttpServerUtility server)
{
System.Xml.XmlDocument doc = new System.Xml.XmlDocument();
doc.Load(server.MapPath("~/web.config"));
System.Xml.XmlNamespaceManager ns = new System.Xml.XmlNamespaceManager(doc.NameTable);
ns.AddNamespace("bla", "http://schemas.microsoft.com/.NetConfiguration/v2.0");
System.Xml.XmlNode node = doc.SelectSingleNode("/bla:configuration/bla:system.web/bla:authentication/bla:forms[#name]", ns);
string projectName = "";
if (node != null && node.Attributes != null && node.Attributes.GetNamedItem("name") != null)
projectName = node.Attributes.GetNamedItem("name").Value; //in my case, that value is identical to the project name (projetname.dll)
else
return "";
Assembly assembly = Assembly.Load(projectName);
return assembly.GetName().Version.ToString();
}
If you are looking for this from a web control, one hack is to find the type of the code-behind Page (ie. the class that inherits from System.Web.UI.Page). This is normally in the consumer's web assembly.
Type current, last;
current = Page.GetType();
do
{
last = current;
current = current.BaseType;
} while (current != null && current != typeof(System.Web.UI.Page));
return last;
I hope there is a better way.
The question states with no reference (instances) it did not (originally) say with no knowledge of web application types.
EDIT the OP clarified to state that yes they do really require no knowledge of types within the calling web assembly, so the answer is appropriate. However I would seriously consider refactoring such a solution such that the version is passed into the other assembly.
For most people in this scenario if you know the custom HttpApplication type:
typeof(MyHttpApplication).Assembly.GetName().Version
and if you only have a dynamic generated type:
typeof(DynamiclyGeneratedTypeFromWebApp).BaseType.Assembly.GetName().Version
Stop voting me down for this answer :)
So, I had to get the Assembly from a referenced dll.
In the asp.NET MVC/WebAPI world, there is always going to be at least one class which inherits from System.Web.HttpWebApplication. The implementation below searches for that class.
using System;
using System.Linq;
static Assembly GetWebAssembly() => AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.GetExportedTypes().Any(t => t.BaseType?.FullName == "System.Web.HttpApplication"));
The above uses System.Linq in order to find that relationship, but this can also be implemented without.
First, we get all loaded assemblies
AppDomain.CurrentDomain.GetAssemblies()
Then, enumerate through the IEnumerable<Assembly>, and get all of the types directly located in the assembly.
a.GetExportedTypes()
Then, see if any of the types inherit from System.Web.HttpWebApplication
t.BaseType?.FullName == "System.Web.HttpApplication"
In my implementation, I ensured this code would only be called once, but if that is not guaranteed, I'd highly wrapping this in a Lazy<T> or other cached lazy load implementation as it is rather expensive to keep performing the blind search.
using System;
using System.Linq;
// original method
private static Assembly GetWebAssembly() => AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.GetExportedTypes().Any(t => t.BaseType?.FullName == "System.Web.HttpApplication"));
// lazy load implementation
private static Lazy<Assembly> _webAssembly = new Lazy<Assembly>(GetWebAssembly);
public static Assembly WebAssembly { get => _webAssembly.Value; }

Categories

Resources