Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add AppContext.ApplicationName #15618

Closed
davidfowl opened this issue Nov 4, 2015 · 27 comments
Closed

Add AppContext.ApplicationName #15618

davidfowl opened this issue Nov 4, 2015 · 27 comments
Assignees
Milestone

Comments

@davidfowl
Copy link
Member

There are cases where the exe name and the application are not the same. Consider a scenario where the entry point of the application is not user code, it could be a different host (like when you're running in windows service) that loads your application as a plugin (.dll). We do this today in ASP.NET and there are 2 modes of execution:

  • Your web application owns the application entry point.
  • Microsoft.AspNet.Hosting owner the entry point your web application is loaded as a plugin.

This name should be settable (and not only by the native host). This should default to the application with the entry point by default.

/cc @lodejard @nguerrera @joshfree @AlexGhiondea

@weshaggard
Copy link
Member

@davidfowl is this for display purposes or will there be decisions based upon it? If it is settable is it safe to assume it should fail if someone sets it more than once?

@davidfowl
Copy link
Member Author

@weshaggard not for display. Decisions will be made based on this for sure.

If it is settable is it safe to assume it should fail if someone sets it more than once?

I'm not sure it needs to be that strict. You could imagine a layered system that composes different entry points. Each of those entry points changes the name based on arguments passed into it. It shouldn't throw.

@weshaggard
Copy link
Member

Can you point me at code where you use it to make decisions? It seems like it would be trouble if decisions are made and someone changes it and now different decisions are made.

@davidfowl
Copy link
Member Author

@AlexGhiondea
Copy link
Contributor

I think it is fine to overwrite the value as long as no-one has read it. Once it is read it should be fixed to make sure all the decisions are made based on the same value.

@mellinoe
Copy link
Contributor

mellinoe commented Nov 4, 2015

I wonder how much usage this would get? My gut reaction is that it is a bit of a niche API, at least the way it's being described (global user-controlled string that is settable/resettable).

I think if I wanted to use an API like this, I would either:

  1. Already know the information, based on the context of the code I am writing
  2. Know it based on the EntryAssembly (which I think we should add back)
  3. Probably want to know something more specific than just the "name" of the application

I'm not really able to follow the example you linked closely, but that's probably because I'm not familiar with this area of ASP.NET. It seems like it's mainly being used as a fallback to a user-configurable string value, where the value has to be equal to the assembly name anyways (just going by the name of the variable "StartupAssemblyName"). Is there a use case in a more simplified context? It's hard to judge whether it would be useful in a general sense with an example that's so closely tied to a specific ASP.NET context.

@davidfowl
Copy link
Member Author

Don't think of it as ASP.NET. Here's another scenario. Lets say I did some assembly loading based on the application name, by default it's the app entry point. Now say that I'm writing a functional/unit test and I want to change that context when running form a unit test since the unit test binary is the host.

@davidfowl
Copy link
Member Author

Maybe of ApplicationName should be an AssemblyName instead.

@AlexGhiondea
Copy link
Contributor

I think AssemblyName is more confusing than ApplicationName but it depends on what you actually end up storing in there.

Can you give an example of the value this will hold?

Regardless of the name, my plan is to allow users to set the value as many times as they want until a read of the value happens. After that. Calls to set will throw.

@davidfowl let me know if that covers your scenario.

@terrajobst
Copy link
Contributor

@AlexGhiondea, can you drive this through the API review? It seems there are still some pending concerns around:

  • The return type
  • The semantics on how this property can be set, if it can be set, and by whom

@AlexGhiondea
Copy link
Contributor

@terrajobst - absolutely!

@AlexGhiondea
Copy link
Contributor

Reason for API

EntryAssemblyIdentity

There are certain scenarios where the assembly that CLR considers as the 'EntryAssembly' is not the assembly the application considers as the 'EntryAssembly'.

Here are two examples where the host considers the 'EntryAssembly different than what the CLR does:

  • Unit test frameworks which host the code to be tested would potentially consider the 'EntryAssembly' the dll that contains the tests
  • ASP.NET hosts would like to consider as 'EntryAssembly' the dll which contains the statup code

Proposed API

namespace System
{
    public static partial class AppContext
    {
        public static string EntryAssemblyIdentity { get { return default(string); } set { } }
    }
}

Details

EntryAssembly

By default (if the host does not overwrites it) this API will return the value of the 'EntryAssembly' from the CLR's perspective (which, if you are a host, will be the assembly for the host).

However, managed hosts are able to change this value to something else, depending on their requirements.

The semantics of this API allow setting it multiple times until the value is read once. This ensures that the value has a consistent value once it was read.

Assembly identity

The 'identity' of the assembly is defined as the FullyQualified name of the Assembly (including name, culture and public key) as a string.

The string returned from this property should be parseable by the constructor for 'AssemblyName'.

We chose to expose this information via a string in order to maintain the dependencies of AppContext to a minimum. Having this API return an AssemblyName would require a dependency to System.Reflection which we would like to avoid.

@mellinoe
Copy link
Contributor

Unit test frameworks which host the code to be tested would potentially consider the 'EntryAssembly' the dll that contains the tests

I dunno, would anyone actually want to do this? It seems to me like a stretch to call that the "Entry Assembly", considering how many other assemblies are typically entered before it in a typical testing framework. It feels like this would just be using the property as a "convenient global string".

The semantics of this API allow setting it multiple times until the value is read once. This ensures that the value has a consistent value once it was read.

I'm a bit concerned about the usability of this. For example, if a library wanted to use this value, it would have to make it clear to the application that it needed to set the EntryAssemblyIdentity before calling any of the functions in the library. Also, it would be very weird if you were writing some code, tried to set the EntryAssemblyIdentity, and got an exception because one of the libraries you called had already read the value (without telling you). It's a pretty bad experience, I think.

The Command Line parsing library we use (for GenFacades and others) could potentially use this property (right now it just uses Assembly.GetEntryAssembly). If it wanted to do so, applications would need to set the value before calling CommandLine.Parse, or not set it at all. It would be up to the library author to make it really obvious how that would work, which would end up being very confusing, I think (because people wouldn't document it).

Also, going back to the unit testing use case. If you can only set it once, how will that work for test runners that let you run multiple test assemblies? For example, xunit.console lets you pass multiple test assemblies into the runner, and it will run them one at a time. This wouldn't really work there, because you could only set the property once for the first test assembly.

@AlexGhiondea
Copy link
Contributor

Thanks @mellinoe for you comment! Those are some great points.

I think I heard two major areas for feedback. Let me try and address them:

Unit testing

I dunno, would anyone actually want to do this? It seems to me like a stretch to call that the "Entry Assembly", considering how many other assemblies are typically entered before it in a typical testing framework. It feels like this would just be using the property as a "convenient global string".

That is the point. If there are multiple assemblies loaded, the order in which they are loaded would be irrelevant. The host would be responsible for setting the value of the property to the assembly that contains the code to be tested.

I guess there is nothing that prevents the host from writting anything it wants in the field which would essentially turn it into a global string.

I'm a bit concerned about the usability of this. For example, if a library wanted to use this value, it would have to make it clear to the application that it needed to set the EntryAssemblyIdentity before calling any of the functions in the library.

Indeed.

However, the purpose of this property is to be set by the host and be read by the application. Once the host has correctly setup the AppDomain it should transfer control to the hosted application which would then be able to read the value. I don't expect that we would want libraries to set this value.

One way to think about this is similar to the 'EntryAssembly' property on AppDomain. That property is read-only and it is set at application startup by the runtime. That value cannot be changed by the application.

For example, xunit.console lets you pass multiple test assemblies into the runner, and it will run them one at a time. This wouldn't really work there, because you could only set the property once for the first test assembly.

This is interesting. Do you know if xunit will create a new AppDomain for each of the assemblies it has to test? If so, then for each AppDomain it uses it can correctly set this property corresponding to the tested assembly.

Allowing this to be changed at any time is somewhat problematic. If the value is not consistent across application calls, the application would get different values depending on when it reads the value.

Usability of the API

Also, it would be very weird if you were writing some code, tried to set the EntryAssemblyIdentity, and got an exception because one of the libraries you called had already read the value (without telling you). It's a pretty bad experience, I think.

The idea is that by the time the application has started running, this value is set to its final value and there should be no reason for the application to try and set it.

I did consider an approach where this scenario would be supported:

namespace System
{
    public static partial class AppContext
    {
        public static string EntryAssemblyIdentity { get { return default(string); } }
        public static bool TrySetEntryAssemblyIdentity(string entryAssemblyIdentity) { return default(bool); }
    }
}

If we think the current approach is too hard to figure out then we can remove the setter and add the above method.

@mellinoe
Copy link
Contributor

Ah, I think there's a bit of a misunderstanding / miscommunication. Is the EntryAssemblyIdentity settable by the application, or just by the runtime host? Your proposal has this:

public static string EntryAssemblyIndentity { get { return default(string); } set { } }

Which led me to believe that the application could call the set accessor as many times as it wanted before the get acessor was called. I'm also confused by the second iteration, which still has a TrySet method. If it is set-in-stone by the time the application is running, why is there any concept of setting it in the API?

That does clear things up a bit, but I still have the same general issues with it. It's still unclear how a testing framework would use it, especially if it's not actually settable within an application. xunit.console is just a runtime-agnostic .NET executable, it has no concept of a runtime host, so it therefore can't take any dependency on such a runtime concept. For example, if xunit wanted to use this now, you'd have to make sure you hosted the CLR with some special, magic string set somewhere, otherwise you wouldn't discover any tests, or get some weird error.

Do you know if xunit will create a new AppDomain for each of the assemblies it has to test?

Well, for our CoreCLR tests, everything is just in the same AppDomain, as far as I am aware. I don't think we spawn multiple processes to isolate tests (since we can't create new AppDomain's on .NET Core). I'd have to double check the runner code about that, though. So this would sort of prevent the API from being usable for that purpose, at least with the current xunit runner.

@bradwilson
Copy link

Do you know if xunit will create a new AppDomain for each of the assemblies it has to test?

Only for unit tests which directly and only target the desktop CLR (meaning, the net45+ TFMs). Everything else -- PCLs, UWP, DNX, CoreCLR, etc. -- runs without app domains.

@davidfowl
Copy link
Member Author

I don't think we should focus on unit testing as the main scenario. Here are some other ones:

  • Today when you run in the ServiceHost on .NET Framework you don't control the container. Your service is just a dll running in there.
  • When you run in a cloud service today, they have an exe that loads your entry point.

Those are the sorts of scenarios where the host process can set the real entry point.

@bradwilson
Copy link

I don't think we should focus on unit testing as the main scenario.

I agree on that. We (xUnit.net) currently have no intention of using this API.

@AlexGhiondea
Copy link
Contributor

There have been a couple of questions about what this API would be useful.

Please correct me if I am wrong but so far, the only scenario where this property would be helpful is in the cases where your application is a hosted application.

I think this covers all the scenarios @davidfowl mentioned (asp.net, service host, cloud service).

That is a valid scenario. What I am trying to understand is what are the use cases for this property so that we can figure out the best shape of this property/API.

Here are a couple of questions to try and gather some more data:

  • First and foremost, how is this value going to be used?
    • Is it only going to be used for display (like ApplicationName name suggests) or
    • Is it going to be used to make decisions and loading assemblies or types (like the EntryAssemblyIdentity name suggests)?
  • Right know the design calls out for exposing this property in a string that can be parsed by the AssemblyName's constructor.
    • Is that a good approximation of the use cases for this property? Is the host/app only interested in an AssemblyName?
    • Or is it that the first thing that the host/app will do is something like new AssemblyName(...).Name?
  • Who is most interested in using this property? Is it the application or the host?
  • Who should be responsible for setting this property? The application or the host?

@TheRealPiotrP
Copy link
Contributor

I've run across this in one place, in the xunit runner for DNX:

args = Enumerable.Repeat(Path.Combine(_appEnv.ApplicationBasePath, _appEnv.ApplicationName + ".dll"), 1).Concat(args).ToArray();

It looks like it was being used to figure out the name of the entry assembly. Since we're planning to add GetEntryAssembly, it seems this scenario would be resolved.

One scenario where this property or one like it would be very useful is in help content generation for Command Line apps. One typically wants to display something like:

MkDir [Drive :] path

This sort of help content can be generated by libraries intended to parse the cmd line. It is very inconvenient to tell the libraries what the application is called explicitly since this can change over time and there is no tools support for "refactoring" an output assembly's name across code. It would be very convenient, then, to have the ability to determine the name of the application at runtime, across renames.

@mellinoe
Copy link
Contributor

mellinoe commented Dec 2, 2015

One typically wants to display something like:

MkDir [Drive :] path

What would you expect the EntryAssembly to be in this case? Would it not be "MkDir" ? The command-line parser for our internal tools just uses the name of the EntryAssembly for this purpose, although I can perhaps imagine some times where you'd want something different.

If you want the "Application Name" to be something different, how would you envision it is set, changed, etc.? Is it settable at runtime, re-settable? Or is it static, based on a manifest, an assembly attribute, something else?

@OtherCrashOverride
Copy link

If this "Application Name" can be arbitrarily set, how is it in any way meaningful? You only end up in a "who got there first (or last) to change it" scenario. Some other code from a 3rd party host or library could have already changed it to meet its needs without any regard for others just as the intended benefactor would. Adding rules to when it can and can not be set does not matter as its still depends who got there first.

Why not just use an environment variable or YourClassHere.YourGlobalStringHere and avoid deceiving code authors? What else should we lie about to developers if we are going to lie about this?

If a developer can't trust your API calls, then they will not use them. An API should do what it says it will do not what arbitrary host/library authors decide it should do as a workaround for an issue.

#DirtyHack

@bradwilson
Copy link

@piotrpMSFT wrote:

Since we're planning to add GetEntryAssembly, it seems this scenario would be resolved.

I don't think so. Wouldn't the entry assembly in this case be xunit.runner.dnx? It is the one that defines the Main method, after all.

@davidfowl
Copy link
Member Author

Exactly! That was the whole reason for having that ApplicationName property... It's all about the hosting model...

@AlexGhiondea
Copy link
Contributor

@bradwilson @davidfowl If you are not writing a managed host (like dnx), then GetEntryAssembly will indeed return the right value since the runtime will load and set the right assembly as the entry assembly.

If you are writing a managed host then it is possible for the host to define a contract between itself and the apps that it is hosting and call out

  • which property to use for the name and
  • what format should the property be.

To me it seems like the managed host scenario requires an explicit contract between what the host puts in the value and how the hosted app uses it. And each host/hosted app could choose to use different things.

@AlexGhiondea
Copy link
Contributor

Is this still needed? If not, I will close the issue.

@davidfowl
Copy link
Member Author

closing, we added a custom api in asp.net

@msftgits msftgits transferred this issue from dotnet/corefx Jan 31, 2020
@msftgits msftgits added this to the 1.0.0-rtm milestone Jan 31, 2020
@ghost ghost locked as resolved and limited conversation to collaborators Jan 4, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants