0

Possible Duplicate:
.NET NUnit test - Assembly.GetEntryAssembly() is null

I'm writing a logging library. I want the library, by default, to write to a directory in the common application data folder named for the application. For example, if the application is called "MyApplication.exe", I want the data saved in "C:\ProgramData\MyApplication".

I'm using this code to construct the path:

   private static string loggingDataPath = 
      Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData) +
      Path.DirectorySeparatorChar + 
      Path.GetFileNameWithoutExtension(Assembly.GetEntryAssembly().CodeBase) +
      Path.DirectorySeparatorChar;

This works exactly as expected, with one problem. I can't unit test the library!

When I try to run the unit tests they all fail with a System.NullReferenceException. If I replace the "Assembly.GetEntryAssembly().CodeBase" call with a string the unit tests once again function properly.

I think I understand why this happens but I have no idea how to work around the problem. I hope someone will be able set me on the path of righteousness.

TIA!

UPDATE (5-24-12): I am not trying to unit test the contents of "loggingDataPath". The mere presence of the "Assembly.GetEntryAssembly().CodeBase" call causes ALL unit tests to fail with the above exception. Note that "loggingDataPath" is static (as it must be as this is a static library).

Community
  • 1
  • 1
casterle
  • 1,037
  • 2
  • 12
  • 27
  • What logging path would you want/expect when unit testing? – erikH May 24 '12 at 04:52
  • @RJ Lohan: This is not a dup of that question, which addresses unit testing of the result of the call. My problem is that the call is static and is thus run on class initialization. The failure of the call causes ALL unit tests on the library to fail. – casterle May 24 '12 at 13:46
  • @erikH: I don't care what value "loggingDataPath" contains. I've updaetd the question to reflect this. – casterle May 24 '12 at 13:48

2 Answers2

4

It's not only unit testing that will cause problems.

Given that GetEntryAssembly() can return null when a managed assembly has been loaded from an unmanaged application and also that CodeBase can contain a URL for assemblies downloaded from the Internet, and is not set for assemblies loaded from the GAC, I would avoid attempting this approach for a general-purpose logging library.

If that's not enough to convince you, other problems are (a) non-privileged users won't have write access to CommonApplicationData, and (b) multiple instances of your application attempting to write to the same log file will be a problem.

Instead, I would define the location of the log file in configuration.

Where would you suggest I put it to avoid this problem?

As I said, I would define it in configuration (e.g. an appSetting in app.config). This is the most flexible. If you want to put it under CommonApplicationData, you can use an environment variable that you expand using the Environment.ExpandEnvironmentVariables method when reading from the configuration file. For example:

<appSettings>
    <add key="logFile" value="%ALLUSERSPROFILE%\MyApp\MyLogFile.log" />
    ...
</appSettings>

You still have to solve the problem of giving access to non-privileged users, and avoiding contention when accessing from multiple instances. You say your underlying logging library supports concurrent access, but be aware that this will have a potential performance cost, depending on how verbose your logging is.

Joe
  • 122,218
  • 32
  • 205
  • 338
  • This is not a general purpose library, but one for use in a specific solution completely under my control. The library will never be called from an unmanaged app nor will assemblies be downloaded from the 'Net. – casterle May 24 '12 at 14:05
  • The non-privileged users issue is a problem. I'm trying to put the data somewhere where everyone who uses the computer will have access to it (rather than storing it on a per-user basis). Where would you suggest I put it to avoid this problem? – casterle May 24 '12 at 14:07
  • Finally, this library wraps another logging library (Raize CodeSite) which handles multiple simultaneous access to the log files. – casterle May 24 '12 at 14:09
  • Is there a way to get the path I want (apparently not CommonApplicationData) that will run properly while the library is being unit tested? – casterle May 24 '12 at 14:11
  • @casterle, if you control the whole solution, and aren't looking for a general-purpose solution, there are lots of options. You could, for example, have an installer that creates a directory for your log files under CommonApplicationData and gives read/write access to all users. Better make sure there's no sensitive data in the log in this case. You could also use Application.CommonAppDataPath for WinForms or Console apps. – Joe May 24 '12 at 14:26
  • Thanks for the help. This is a WPF app so I need to use a call which is compatible with WPF. All I really want to do is find a solution which won't prevent me from unit testing the library while allowing me to get the .exe name at runtime. – casterle May 24 '12 at 18:15
  • I would like to avoid setting this up in the config file or have the installer handle it. Also, I need to find a location that doesn't require special permissions to read/write. I believe that C:\Users\Public\Documents will work for this, no? – casterle May 24 '12 at 18:16
  • Finally, lest you think I’m a complete idiot, the software I’ve written in the past didn’t need to support anything past XP (per client request) so I’m not that familiar with security on modern OS’s. These were lab computers that never needed to be upgraded. – casterle May 24 '12 at 18:16
0

Ignoring the sage advice of previous answers and addressing only my question, here is how I fixed the problem:

   public static string LoggingDataPath { 
      get { 
         return loggingDataPath.Length > 0 ? loggingDataPath : 
         Environment.GetFolderPath(Environment.SpecialFolder.CommonDocuments) + 
         Path.DirectorySeparatorChar + 
         Path.GetFileNameWithoutExtension(Assembly.GetEntryAssembly().CodeBase) + 
         Path.DirectorySeparatorChar; 
      } 

      set { loggingDataPath = value; } 
   } 

This solution avoids initializing 'loggingDataPath' on the first access to the static class, thus avoiding the call to 'Assembly.GetEntryAssembly().CodeBase'.

casterle
  • 1,037
  • 2
  • 12
  • 27