While working with ASP.NET Framework we sometimes need to get the physical path to a folder on the filesystem. The most common way to do this is using Server.MapPath("~/relative-folder"). I've researched best practices around this several times just to forget the details a couple of months later so in this post I'll outline my findings are share some of my own best practices.

So when building a web app there is mainly two "contexts" in which I need to get file system information, in my actual application code and in some of my unit tests. Most of the time I strive to mock out IO from my unit tests but in some scenarios, I also need to perform actual testing with the filesystem to be more confident that my tests are not giving me false positives.

Physical file paths in web applications

The "goto" standard back in the days was to always use HttpContext.Current.Server.MapPath() which would "translate" a relative path into a full file system path. BUT. This object is request-bound, meaning that it will only exist inside the context of a web request. If we run inside a background job with something like Hangfire or Quartz this object will not exist. That's why I always recommend using HostingEnvironment.MapPath(path) that will work in both request-context and in background jobs.

I also wanted to know if and how these might differ from one another so I created this table to see how Server.MapPath() behaves.

Code Returns
HttpContext.Current.Server.MapPath("") D:\Dev\TestApp
HttpContext.Current.Server.MapPath("/") D:\Dev\TestApp\
HttpContext.Current.Server.MapPath("~/") D:\Dev\TestApp\
HttpContext.Current.Server.MapPath("App_Plugins") D:\Dev\TestApp\App_Plugins
HttpContext.Current.Server.MapPath("/App_Plugins") D:\Dev\TestApp\App_Plugins
HttpContext.Current.Server.MapPath("~/App_Plugins") D:\Dev\TestApp\App_Plugins
HttpContext.Current.Server.MapPath("App_Plugins/") D:\Dev\TestApp\App_Plugins\
HttpContext.Current.Server.MapPath("/App_Plugins/") D:\Dev\TestApp\App_Plugins\
HttpContext.Current.Server.MapPath("~/App_Plugins/") D:\Dev\TestApp\App_Plugins\


Note that it does not matter if the relative path starts with "/", "~/", or just the folder name. Also, note that any trailing slash in the relative path will be reflected with a trailing slash in the file system path.

Doing the same thing with HostingEnvironment.MapPath() reveals some differences.

Code Returns
HostingEnvironment.MapPath("") Throws exception
HostingEnvironment.MapPath("/") D:\Dev\TestApp\
HostingEnvironment.MapPath("~/") D:\Dev\TestApp\
HostingEnvironment.MapPath("App_Plugins") Throws exception
HostingEnvironment.MapPath("/App_Plugins") D:\Dev\TestApp\App_Plugins
HostingEnvironment.MapPath("~/App_Plugins") D:\Dev\TestApp\App_Plugins
HostingEnvironment.MapPath("App_Plugins/") Throws exception
HostingEnvironment.MapPath("/App_Plugins/") D:\Dev\TestApp\App_Plugins\
HostingEnvironment.MapPath("~/App_Plugins/") D:\Dev\TestApp\App_Plugins\


Note here that the relative path must start with either "/" or "~/" otherwise, the method will throw.

Overall conclusions and recommendations

  • Always use HostingEnvironment.MapPath().
  • A folder path is indicated by the trailing slash, otherwise, it's a file path. Consider always using trailing slash for folders.
  • Always make sure that the relative path passed to MapPath() starts with a slash.
  • Be aware that the method will respect and include any trailing slash from the relative path into the physical path.
  • Avoid using things like AppDomain.CurrentDomain.BaseDirectory and build paths based on this as any virtual directories configured in IIS will not be respected with this approach.

 

Physical Paths in unit tests

This one is a little harder as we want our unit tests to be "self-contained" and not be dependent on any magic path on the developer's filesystem or a build server. Inside a unit test or any .NET app, you can always find the path to the executing program with AppDomain.CurrentDomain.BaseDirectory, in the case of a unit test this would return something like d:\Dev\TestApp\My.UnitTest\Bin\Debug. One might be tempted to traverse the path with ..\..\ to get to the project root but this only works if the folders created have this exact nomenclature. I would argue that there is a better way:

Create a folder called "MockFileSystem" inside your test project, this will act as the "root" of your application similar to what you would get from HostingEnvironment.MapPath("/"). Inside this folder, we can replicate the relevant files and store them inside our test project. It's important that we set the "Build action" for each item to "Content" and choose the "Copy if newer" option. This way the folder structure and files will be copied to the application´s root folder.

Have your application code depend on an abstraction of the MapPath()-method, in my case, this is an interface like this:

internal interface IFileSystemHelper
{
    string MapPath(string path);    
}

​The implementation inside the web project would look like this:

internal sealed class FileSystemHelper : IFileSystemHelper
{
    public string MapPath(string path)
    {
        return HostingEnvironment.MapPath(path);        
    } 
}

And in my unit test project:

internal class MockFileSystemHelper : IFileSystemHelper
{
    public string MapPath(string path)
    {
        string baseDir = AppDomain.CurrentDomain.BaseDirectory + "MockFileSystem\\";

        path = path.TrimStart('~').Replace("/", "\\").TrimStart('\\');
        
        var full = Path.Combine(baseDir, path);

        return full;  
    }
}


To avoid the "issue" with some relative folders having trailing slashes and some not we could have our implementations strip any trailing slash from the returned path to be sure that we always get a full path without any trailing slash. Something like this:

public string MapPath(string path)
{
    return HostingEnvironment.MapPath(path).TrimEnd('\\');        
}

 

 

I was working on a ASP.NET-project the other day where we use a runtime cache (aka. application cache) that lives for the duration of the application lifetime. We use this to store some frequently used data and we update the cache when something changes.

The cache implementation is not state of the art and I figured I’ll share some learnings and pitfalls that I’ve fallen into over the years.

Mutable objects in the runtime cache

First of all: An mutable object, in contrast to an immutable object, is an object that can change it's state (aka properties on the object can change value without having to create a new object). Since an immutable object can't alter its state, we need to create a new instance of the object if we need to change any values. In .NET a standard class with get/set properties is mutable while DateTime, TimeSpan, and many others are immutable.

Years ago one of the biggest gotcha for me with using the MemoryCache in .NET is that it will actually store objects. Not serialized objects but real objects in memory and only pass the reference to any consumer.

This is of course great for performance, but it also means that one has to be very careful about how these objects are used. We could deep clone the object when fetching it from the cache to avoid many of the issues I’m going to point out here but in our case, we used the “vanilla memory cache” in .NET.

Since the objects are mutable, we can easily change the state, ie. change a property or add an item to a list – we just need to remember that the next time this object is accessed the new values will be there, and the old values are gone.


Updating values

Have a look at this code sample:

public class SomeService {

    public bool UpdateCustomer(CustomerViewModel vm)
    {
        // Getting the value from the CustomerService, which is wrapped in a 
        // caching-decorator that uses the .NET MemoryCache.
        var customer = _customerService.GetCustomer(vm.CustomerId);

        customer.Name = vm.Name;
        customer.City = vm.City;
        customer.MaxOrderAmount = vm.MaxOrderAmount;

        var saveResult = _customerService.Save(customer);

        if(saveResult.HasValidationErrors)
            return ValidationError(saveResult);

        return Success();

    }
}

As you can see, we’re applying the changes from the view model into the Customer model and then saving it with the CustomerService which will validate the Customer before saving it. Let’s say that there is a validation error, the service will set HasValidationErrors to true and we’ll return the issues to the view.

BUT! This code contains a nasty bug. Since the GetCustomer()-method returns an object from the cache, the changes we make to the object (setting the values from the view model) will be persisted in the cache no matter if the validation is successful or not. This is all very logical and makes sense but it’s a big “gotcha” in terms of how caching works.

Another thing that has happened to me over the years: I was reading an object from the cache that had related entities (think customers with a List<Order>). I wanted to pass a Customer together with only paid orders to another service so I modified the order-property on the Customer like so:

customer.Orders = customer.Orders.Where(x=>x.Paid == true).ToList();

This felt great and the service that I called could use the customer-object from the cache. The only problem is that the underlying collection of orders is modified and the next time I read the Customer from the cache only the paid orders will be in the collection.

Threading and runtime cache

Most of the time the in-memory runtime cache would be shared inside the application, since I’m mostly doing ASP.NET this would be all threads used by the webserver to process requests.

Here we need to keep in mind that while one thread might be reading the cache, getting a reference to an object to read it – another thread might be in the processing of updating values on the same object, it might even be in the middle of this processes and depending on implementation the object might be in an invalid state (one property has been updated but not the other) causing errors on the read-side since the values do not make sense.

Solutions?

Going forward I can see a couple of things that would make it harder to “do it wrong”.

  • Always use un-cached business objects when modifying state. (ie. the method above should not read from the cache). This way we can safely apply changes to mutable objects and validate like in the sample above.
  • Cache a “read-only”-representation of the underlying business object. This representation could be a CustomerReadOnly-class with private setters for all the properties. This way the consuming code can’t change the state by mistake.
  • Use C# 9 record types, they are immutable so it's impossible to change the state of the cached object. I changes are needed a new instance of the record would have to be created - which will not impact the cached object. This way, any "implicit" changes to the cache are impossible.

There is a lot more to this subject but I figured I’ll post this as a starting point.