Virtual Directory Inside Of ASP.NET Core App In IIS

ASP.NET developers, who are migrating to the work with ASP.NET Core, may face a situation of using virtual directories inside of ASP.NET Core application in IIS. Usually, public, static resources, such as CSS, JavaScript, and image files are stored in the web root ([content-root]/wwwroot). In this case, everything will work fine and no problems should occur. The only one thing you need to add to your code is to configure middleware, which enables the serving of static files within Startup.Configure.

  1. public void Configure(IApplicationBuilder app)  
  2. {  
  3.    app.UseStaticFiles();  
  4. }  

But, when you need to keep your files in any other folder outside the web root or even outside the application, like somewhere on your drive or file share, you may face the folder access problem.

To reveal the reason, it is important to understand some nuances of ASP.NET Core. ASP.NET Core ships with the next server implementations,

  • Kestrel is the default cross-platform HTTP server for ASP.NET Core.
  • HTTP.sys is a Windows-only HTTP server based on the HTTP.sys kernel driver and HTTP Server API.

So, ASP.NET Core actually does not require IIS to run; instead of this, cross-platform Kestrel Web Server or HTTP.sys can be used, which is a Windows-only alternative to Kestrel. The most popular among them is Kestrel. However, despite the fact that it’s fast, cross-platform and has been optimized for throughput performance, it does not provide all features as a full-featured Web service (like IIS) provides no port sharing, easy SSL configuration and so on. Some of these features may appear in the future, but today Kestrel is normally used with a reverse proxy server, such as IIS, Nginx, or Apache. IIS is more often than not used on the Windows machines.

In ASP.NET, developers are able to use a virtual directory within IIS website for storing static resources in the directory outside of the application. Unfortunately, with ASP.NET Core, this solution doesn’t work. A virtual directory in IIS won’t be recognized and users will see a 404 error. Kestrel runs beneath IIS (using the HttpPlatformHandler module) and IIS brokers only the requests to it, so Kestrel does not know about virtual directories from IIS.

In case you just use the folder outside the web root, without authorization, e.g. when you just need to access a usual folder on the drive, the only one thing you should do is to configure the Static File Middleware, as it is shown below:

  1. public void Configure(IApplicationBuilder app)   
  2. {  
  3.     app.UseStaticFiles(); // For the wwwroot folder  
  4.     app.UseStaticFiles(new StaticFileOptions   
  5.     {  
  6.         FileProvider = new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), "StaticFiles")),  
  7.             RequestPath = "/StaticFiles"  
  8.     }); //For the needed folder  
  9. }  

In the code above, the “StaticFiles” directory hierarchy is exposed publicly via the StaticFiles URI segment. A request to http://<server_address>/StaticFiles/images/image1.png will return image1.png file.

This case is very simple, but in the most common scenarios, you would never use a folder, which is publicly available. For example, the Static File Middleware will throw errors if you use a shared drive because the system does not have access to it. The most proper solution is to serve files through a controller action, returning a FileResult with correct authentication.

There are two common ways to resolve the problem with authentication, both are for Windows. The one way to access this folder is to run the webserver's AppPool as the identity that can access the share. This way is safer because the credentials are stored securely in the IIS config (rather than in code or in config files). For this, you just need to create a local user on both machines and ensure they both have the same name and password. You can also use Active Directory Domain Account, just make sure, that you correctly type in the name and domain. So, the full instruction is as follows,

  1. On your IIS machine, create a user and set a password.
  2. On your network share machine, create a user with the same name and password as in step 1.
  3. Give permission to a shared folder to this user.
  4. In IIS Manager, select the application pool that your web app uses or create a new one if you use the default one.
  5. Click on “Advanced Settings” in the right Actions bar. Under Process Model, click on the “Identity” value and select “Custom account”.
  6. Click on the “Edit” and enter user name and password from step 1. If you enter all information correctly, the pop-up will be closed successfully without any error messages.
  7. After that stop your Web Site.
  8. Back again to your application pool and click on the “Recycle”.
  9. Start your Web Site.

After that, you easily access the files in this folder as if they were on the web root.

If it is not important to you where to keep passwords and usernames (for any reason), you can store them in your code or, as a better way, in the config file, and use a WNetAddConnection2 function. This solution does not require identical users on the servers, the WNetAddConnection2 function makes a connection to a network resource and can redirect a local device to the network resource. The basic syntax is,

  1. DWORD WNetAddConnection2A(  
  2. LPNETRESOURCEA lpNetResource,  
  3. LPCSTR lpPassword,  
  4. LPCSTR lpUserName,  
  5. DWORD dwFlags  
  6. );   

Where lpNetResource is a pointer to a NETRESOURCE structure that specifies details of the connection:

  • the type of network resource to connect to;
  • a pointer to a null-terminated string that specifies the name of a local device to redirect;
  • a pointer to a null-terminated string that specifies the network resource to connect to;
  • a pointer to a null-terminated string that specifies the network provider to connect to.

The lpPassword is a string that represents a password to make the network connection.

The lpUserName is a string that represents a username to make the connection.

The dwFlags is a set of connection options, e.g. remembering connection, the interaction between the operating system and user, saving credentials by the credential manager and so on.

If the function succeeds, the return value is NO_ERROR, otherwise, the return value can be one of the error codes or one of the system error codes, a list of which can be found in the detailed Microsoft specification: "WNetAddConnection2A function".

Which method should be used depends on your project needs and environment. The first approach is more preferable for security reasons, in particular when you develop a website for a customer, who prefers to keep the ability to change the username and password without any changes in the code and this approach also is the easiest and fastest one for development. The second method can be more useful if you want to mount the shared folder to your local drive. The second method has been described in general terms. Still, this information can be helpful for developers, who migrate existing ASP.NET application into ASP.NET Core and want to understand the reason why the virtual folders do not work and what to do.