Mapping static file directories in ServiceStack

Hello,

I’ve asked this question over at Stack Overflow, but I figured I bring it straight to the proverbial “horse’s mouth”. I’m sorry if this turns out to be something extremely basic, perhaps my Google-fu is failing me…

I’m building a self-host application in C# using Service Stack. I’d like the application to share content based on some configuration data.

During AppHost.Configure I’d like to read-in a configuration file and recursively share several directories. The directories may be local or shared folders depending on each element in the configuration.

For example, if my config looks like this:

[
    {
        "sourceId": "TEST1",
        "contentPath": "\\\\nas01\\files"
    },
    {
        "sourceId": "TEST2",
        "contentPath":  "d:\\files"
    }
]

I’d like the directories to be recursively accessible like this:

http://localhost/TEST1/....
http://localhost/TEST2/....

The content stored here is media, so I want to be able to benefit from ServiceStack’s Partial support. Also the content of these directories change quite often so I don’t want to map individual files on startup.

Reading the config file is no problem, really, I just want to know the right way to map these directories so I can use the built-in static handling capabilities of ServiceStack.

I’ve done a bunch of searching, here on the forum, on the Internet, and within the documentation but I’m at a loss as to how to do something which seems kind of simple. My question is:

What’s the right way, in Service Stack, to map a static content directory at run-time?

Many thanks!

-Z

There’s no built-in solution for multiple mapping static folders at different routes, the VirtualFileSystem lets you combine multiple file sources together but they’d be accessible under the same route, e.g:

public override List<IVirtualPathProvider> GetVirtualFileSources()
{
    var existingProviders = base.GetVirtualFileSources();
    existingProviders.Add(new FileSystemVirtualFiles("\\\\nas01\\files"));
    existingProviders.Add(new FileSystemVirtualFiles("d:\\files"));
    return existingProviders;
}

Which will the different folders be both be accessible from http://localhost/....

If you were using ServiceStack + .NET Core you could use .NET Core’s Static File Handler support.

I’ll look to see if there’s an easy way to register a file source under an alias.

Thanks for the answer, @mythz, I was feeling pretty stupid trying to solve this problem.

If I were to write this functionality as a service wherein I simply return the contents of the requested file, would that break support for HTTP Partial requests?

Cheers!

-Z

I’ve just added new support for FileSystem Mapping in this commit which will now let you register file system mappings by overriding GetVirtualFileSources() in your AppHost, e.g:

public override List<IVirtualPathProvider> GetVirtualFileSources()
{
    var existingProviders = base.GetVirtualFileSources();
    existingProviders.Add(new FileSystemMapping("TEST1", "\\\\nas01\\files"));
    existingProviders.Add(new FileSystemMapping("TEST2", "d:\\files"));
    return existingProviders;
}

This change is available from v4.5.5+ that’s now available on MyGet.

Alternatively you can have plugins register file system mappings by adding them to AppHost.AddVirtualFileSources in your plugins IPreInitPlugin callback, e.g:

public class FileTestPlugin : IPlugin, IPreInitPlugin
{
    public void Configure(IAppHost appHost)
    {
        appHost.AddVirtualFileSources.Add(new FileSystemMapping("TEST1", "d:\\files"));
    }

    public void Register(IAppHost appHost) {}
}

Which you can register as a normal plugin, e.g:

public override void Configure(Container container)
{
    Plugins.Add(new FileTestPlugin());
}

Simply writing a file to the response stream will return a file directly and bypass any of ServiceStack’s built-in functionality, to tap into enhanced functionality like Partial responses you can return file in a HttpResult(file) from your Service.

@mythz, thanks so much for this addition to the codebase. I really appreciate your responsiveness!

Unfortunately, I’m having a bit of trouble using the FileSystemMapping class.

I find that when I add it to my VirtualFileSources (as you demonstrate above) my application never starts. I also see a significant uptick in network traffic when I try to start the application.

I think that during Initialize the FileSystemMapping class is effectively recursively listing the contents of the newly added directory structure. As this directory structure is both deep, wide, and subject to change, I think it’s likely that this approach won’t work for me.

Following your advice above I’ve created a service to answer these requests instead:

[FallbackRoute("/{sourceId}/{filePath*}")]
public class LegacyContentFileRequest : IReturn<IHttpResult>
{
    public string sourceId { get; set; }
    public string filePath { get; set; }
}
public HttpResult Get(LegacyContentFileRequest request)
{
    //Make sure we have the configuration for this station
    SourceConfig sourceConf = LoadSourceConfig(request.sourceId);

    //Make sure we have the right path strucure.
    if (request.filePath.StartsWith("/") == false)
        request.filePath = "/{0}".FormatWith(request.filePath).Replace('/', Path.DirectorySeparatorChar);

    FileInfo streamingFile = new FileInfo("{0}{1}".FormatWith(sourceConf.contentPath, request.filePath));
    String contentMimeType = StreamingContent.ResolveContentType(streamingFile.Extension);

    return new HttpResult(streamingFile, contentMimeType);
}

This code works and seems simple enough. I just have to test to make sure Partial works correctly.

Is there anything (from a ServiceStack perspective) that I’m missing in this snippet?

As always, many thanks!

-Z

Ok that’s very odd, do you have symlinks that point to itself or something similar?

Your impl looks fine since you’re returning the FileInfo in a HttpResult so you should get HTTP Partial support. But since C# 6 I rarely use Fmt/FormatWith anymore, preferring intstead to write:

var streamingFile = new FileInfo($$"{sourceConf.contentPath}{request.filePath}");