44

We have an application using ASP.NET Core 1.0 RC1 and hosted on IIS. It works fine. Now we have static content, that is available on a file share and should be accessible from the application.

Before ASP.NET 5, we added a virtual directory in IIS and could access the shared content easily. With our hosted ASP.NET 5 application, this unfortunately doesn't seem to work. We just get a 404 back when trying to access the static content.

Our application is using app.UseIISPlatformHandler() and app.UseStaticFiles(), but this doesn't work. We discovered that we could use app.UseFileServer() with custom FileServerOptions to get the desired behavior, but we are curious if it's also possible with the normal "old" way of adding a virtual directory in IIS.

7
  • 1
    Did you tried creating a symbolic link within the wwwroot folder which points to the networkshare? Commented Mar 16, 2016 at 8:24
  • 1
    @Matthias - did you figure out a way? I have exactly the same situation... Commented Jul 8, 2016 at 2:55
  • 5
    Yes: use the FileServerMiddleware with app.UseFileServer(). That's the way of doing it with ASP.NET Core, since IIS virtual directories and virtual applications are not working due to the AspNetCoreModule. Commented Jul 13, 2016 at 10:20
  • 2
    I'm interested in that too for the exact same reason.Could you please self-answer you question with details of what you did and the FileServerOptions you provided ? Commented Jan 6, 2017 at 9:03
  • 1
    Yes please. Post a valid answer. Many people need to know how this works Commented Aug 11, 2017 at 12:00

7 Answers 7

51

I have found a blog which I think must have been written by the OP.

The upshot is not to use a virtual directory in IIS at all but rather map your path in Startup.cs to the physical server directory. I hope the OP does not mind that I have pasted the blog below, but it helped me when I first encountered this problem today.

Source: https://www.jauernig-it.de/asp-net-coreiis-serving-content-from-a-file-share/

There are situations, when you want to serve static content with your application, that is not part of it, e.g. because it exists on a common file share. Website content, that is managed by a business division, could be such a use case. In ASP.NET before Core, this was no problem in IIS: just create a virtual directory within your IIS website and point it to the file share.

Unfortunately, with ASP.NET Core, this approach isn’t working any longer. If you add a virtual directory to your ASP.NET Core application in IIS, it isn’t recognized and a 404 is returned. It’s because of DNX/Kestrel, which is running beneath IIS (using the HttpPlatformHandler module) and to which IIS only brokers the requests. Kestrel doesn’t know anything of virtual directories from IIS. And because ASP.NET Core applications are independent from IIS and could also be run without it (e.g. running Kestrel standalone), that should be considered as a good thing.

But now we need another solution for our problem… fortunately, ASP.NET Core gives us a programmatic interface to serve files from anywhere. Just add the following code to your Startup.cs Configure() method:

app.UseFileServer(new FileServerOptions
{
    FileProvider = new PhysicalFileProvider(@"\\server\path"),
    RequestPath = new PathString("/MyPath"),
    EnableDirectoryBrowsing = false
});

What this essentially does, is adding a file server to a physical server path, that is then available on a certain request path, in this case with directory browsing disabled. You are also able to serve from a path relative to your application, using new PhysicalFileProvider(env.WebRootPath + "\path") (given env is of type IHostingEnvironment as parameter of Configure()). Voila, that’s it. There is no need to add a „virtual directory“ in IIS, this stuff is deprecated and a thing of the past. For me, this is a good thing, because we get more independent of the whole IIS…

Sign up to request clarification or add additional context in comments.

6 Comments

This worked for me. Needs using Microsoft.Extensions.FileProviders;
Briliant. Worked first time.
Exactly what I needed.
This should be the accepted answer. It's simply just perfect.
Is there a way to get the current default PhysicalFileProvider or default FileServerOptions first, so that I can just pass that and only adjust the RequestPath without effecting anything else?
|
34

I encountered this problem today, and finally managed to fix it. The trick (for me, probably not for everyone) is making sure the aspNetCore handler is disabled in the sub-application and enabled in the main (ASP.NET Core) application.

My ASP.NET Core app has a basic Web.config

<configuration>
  <system.webServer>
    <handlers>
        <add name="aspNetCore" path="*" verb="*" type="" modules="AspNetCoreModule" scriptProcessor="" resourceType="Unspecified" requireAccess="Script" allowPathInfo="false" preCondition="" responseBufferLimit="4194304" />
    </handlers>
    <aspNetCore processPath="dotnet" arguments=".\bin\Debug\netcoreapp2.0\myapp.dll" stdoutLogEnabled="true" stdoutLogFile=".\logs\stdout" />
  </system.webServer>
</configuration>

and the application added as a sub-application in IIS has

<configuration>
  <!-- removed -->
  <system.webServer>
      <handlers>
          <remove name="aspNetCore" />
       </handlers>
  </system.webServer>
</configuration>

5 Comments

Interesting that one can switch that off in the web.config. That's sort-of what you'd do in the nginx config file when not on IIS.
You need more credit for this solution. Very simple and easily to implement in anything that is under some kind of release management software.
I upvoted this because it exactly fixed my issue. I am running a dotnet core 2 web application on the root site in IIS Express and needed to set up a virtual application folder to point to a legacy .net 4.5 app. When I first tried the request would just hang on the redirect and I couldn't figure out why (no 404 it just died). Adding the above handler removal to the Web.config of my legacy app fixed the issue straight away! Very grateful that this works or I would have been in a tight spot.
Thanks alot, I am using smarterasp.net shared hosting basic plan.. On the root asp.net core 2.0 application is deployed. and under a virtual directory MVC5 rdlc reporting app is deployed, because .net core still does not support rdlc. I just remove aspNetCore settings from my MVC5 web config
This solution also works to fix the Plesk Web Statistics when using asp.net core. Because the Plesk Web Statistics accessable with domain.tld/plesk-stat/webstat is also a virtual directory, put this web.config inside C:\Inetpub\vhosts\domain.tld\.plesk\statistics\domain.tld\
6

I know its a question with 1,8 year, but if someone needs to resolve the same problem, try to use this:

public void Configure(IApplicationBuilder app)
{
    app.UseStaticFiles(); // For the wwwroot folder

    app.UseStaticFiles(new StaticFileOptions()
    {
        FileProvider = new PhysicalFileProvider(
            Path.Combine(Directory.GetCurrentDirectory(), @"wwwroot", "images")),
        RequestPath = new PathString("/MyImages")
    });
}

Is perfectly possible to change parameters of PhysicalFileProvider to any local or shared folder, and with this serve a file.

Doing that in this way is not SECURITY recommended. But, for study propose, its acceptable.

The static file module provides no authorization checks. Any files served by it, including those under wwwroot are publicly available. To serve files based on authorization: Store them outside of wwwroot and any directory accessible to the static file middleware and Serve them through a controller action, returning a FileResult where authorization is applied.

In Microsoft's Asp.Net documentations we can find more complete information to help with this issue.

Check this link: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/static-files

Comments

6

Not directly.

You see, the problem is, when you have a .NET-Core application, the application is run in Kestrell, not IIS (for .NET Core < 2.2).

Now, to host your .NET-Core application in IIS, the AspNetCoreModule starts your .NET-Core application with Kestrell on Port X of 127.0.0.1, and then reverse-proxies the traffic from your iis-domain+virtual directory to Port X on 127.0.0.1 (it might use something else than TCP).

Problem 1 is, Kestrell has pretty limited functionality, meaning no virtual directories.
Problem 2 is, unlike nginx, IIS does not really do the reverse-proxying properly, or should we say "completely".

IIS can forward domainxy:80 to 127.0.0.1:random alright. But what it doesn't do properly is rewrite domainxy:80/foo to 127.0.0.1:random (images, header, json-ajax-results, urls, return-urls, cookies, etc. and vice-versa). Instead it rewrites domain:80/foo to 127.0.0.1:random/foo, which is a problem if the server on 127.0.0.1:random (Kestrell) doesn't support virtual directories.

So if you want to run your application in your virtual-directory, you have two options (both involve modifying "your" application - if you can do that):

  1. Put all your stuff into directory "foo" (including the MVC controller route), if your application is going to be deployed only once.

  2. As suggested in https://github.com/aspnet/Hosting/issues/416#issuecomment-149046552 you can have the application-framework simulate that folder for you, kindof like in RoR:

    public void Configure(IApplicationBuilder app, 
                          IHostingEnvironment env,
                          ILoggerFactory loggerFactory)
    {
        string virtual_directory = "/Virt_DIR";
        // virtual_directory = "/";

        if (virtual_directory.EndsWith("/"))
            virtual_directory = virtual_directory.Substring(0, virtual_directory.Length - 1);

        if (string.IsNullOrWhiteSpace(virtual_directory))
            Configure1(app, env, loggerFactory); // Don't map if you don't have to 
            // (wonder what the framework does or does not  do for that case)
        else 
            app.Map(virtual_directory, delegate(IApplicationBuilder mappedApp) 
                {
                    Configure1(mappedApp, env, loggerFactory);
                }
            );
    }
    
    // Configure is called after ConfigureServices is called.
    public void Configure1(IApplicationBuilder app, 
                           IHostingEnvironment env, 
                           ILoggerFactory loggerFactory)
    {
         //  [...]  (here comes what used to be in your old Configure method)
    }

You will have to configure the name of the virtual-directory somewhere. Careful when you have/return URLs in JavaScript/ajax-requests, they won't be automagically mapped. You have to do this yourselfs, but that used to be this way with old ASP.NET, too.

Really, like RoR:

map Rails.application.config.relative_url_root || "/" do
    run RedmineApp::Application
end

As for a virtual directory within the application: No, this is not that simple.
IIS is a full webserver, which will serve the content of that mapped directory like it was there (if it can read the contents).

If you forward the entire parent directory to Kestrell, then IIS can't serve the subdirectory, and you're application would have to do that. That means you'll have to setup a static file server for that specific directory, and tell it where the files are, as you have done.

What you might be able to do, is tell IIS to not proxy that specific virtual sub-directory (just as you can define a static-file location in nginx - except that IIS probably does not support that feature).

However, you could create a symlink (or mount/junction) within your application directory that goes to the networked folder, if Windows is capable of that (mklink). Then .NET Core should be able to serve it statically. But really, that sounds like a hack.

If you can't configure IIS, you really should use app.UseFileServer() and define the document's location in the database. That way you can just delete&re-insert the application later.

Comments

2

.Net Core has very limited support for IIS virtual directories. As a workaround, you can use Microsoft.Web.Administration to get list of a Site ("Default Web Site") virtual directories. Replace Path with PhysicalPath to locate the resources

Create a .NetStandard lib project (>= 1.6.0), and use this example

Comments

0

My solution was using path="/" instead of path="*" on web.config file

Comments

0

I would recommend just adding a step to your deployment to run a powershell script and create a symbolic link to where the virtual directory exists.

function Test-ReparsePoint([string]$path) {
      $file = Get-Item $path -Force -ea SilentlyContinue
      return [bool]($file.Attributes -band [IO.FileAttributes]::ReparsePoint)
    }

    function createVirtualPath([string] $targetFolder,[string] $realDirectory){
        New-Item -Path $targetFolder -ItemType SymbolicLink -Value $realDirectory
    }
    $VirtualPath="C:\inetpub\Wwwroot\{yourApp}\StaticFiles"
    $RealPath="F:\WebFilesTest"
    if (test-path -Path $VirtualPath){
        write-host -ForegroundColor Cyan Folder Exists
        if( (Test-ReparsePoint $path) -eq $false){
            Write-Host -ForegroundColor Cyan Not a virtual Folder, deleting and recreating as symbolic link
            Remove-Item $VirtualPath
            createVirtualPath -targetFolder $VirtualPath -realDirectory $RealPath
        }
    }else{
        createVirtualPath -targetFolder $VirtualPath -realDirectory $RealPath
        
    }
function CopyACL([string] $VirtualDirectory, [string] $realDirectory){
    $virtualACL=get-acl -Path $VirtualDirectory
    if(($virtualACL.AreAccessRulesProtected) -eq $false){
        $virtualACL.SetAccessRuleProtection($true,$true)
        $virtualACL | Set-Acl -Path $VirtualDirectory
    }
    Get-acl -Path $realDirectory | set-acl -Path $VirtualDirectory
}
#need to copy the ACL of the symbolic path
CopyACL $VirtualPath $RealPath

In your Program.cs file set up your static files provider like so:

app.UseStaticFiles(new StaticFileOptions()
{
    FileProvider = new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), 
"StaticFiles")),
    RequestPath = "/StaticFiles"
});

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.