7

Is there an easy way to add a subdirectory in ASP.Net v4 Web API that would contain all of my client content? I have read a lot of articles today on virtual paths and routing, but nothing that quite describes this case.

For example, I want to store my images under wwwroot so when the the app receives this request:

http://myapp/img/logo.png

It fetches wwwroot\img\logo.png to handle the request. Obviously, I don't want to have to map out every file or folder individually.

There will be a Web API restful web service that will be handled by the regular routing functionality in WebApiConfig.cs.

(Note: I ask this because I plan to migrate our app to ASP.Net v5 when it is GA, and this would make moving the client-side code trivial)

Graham
  • 7,431
  • 18
  • 59
  • 84
  • actially, if I understood you correctly, you don't need to map you folder with routes (if we are talking about routing engine and usage of attribute routing). if you want to return or read content in folder you can use helper methods like `Url.Content` and get it – Andrew Nov 04 '15 at 07:41
  • That sounds just like what I'm looking for, Andrew. If you can reword this into an answer that shows how to use this to create a virtual root I'd give you the credit for the correct answer. – Graham Nov 05 '15 at 02:12
  • The term you are looking for here is `Static Content`. IIS or can be configured to serve up static content with little effort. I had also looked into something similar to this for a web server running python and solved this easily by adding 5 config lines to nginx. The webservers are meant/made to serve up static content and also have file caching and other options to optimize this. – Matt R Nov 09 '15 at 22:37

3 Answers3

10

You can use the Microsoft.Owin.FileSystem and Microsoft.Owin.StaticFiles NuGet Packages to achive what you want.

First add the two NuGet Packages.

Then add this Code to your Startup class:

    public void Configuration(IAppBuilder app)
    {
        // here your other startup code like app.UseWebApi(config); etc.

        ConfigureStaticFiles(app);
    }

    private void ConfigureStaticFiles(IAppBuilder app)
    {
        string root = AppDomain.CurrentDomain.BaseDirectory;
        string wwwroot = Path.Combine(root, "wwwroot");

        var fileServerOptions = new FileServerOptions()
        {
            EnableDefaultFiles = true,
            EnableDirectoryBrowsing = false,
            RequestPath = new PathString(string.Empty),
            FileSystem = new PhysicalFileSystem(wwwroot)
        };

        fileServerOptions.StaticFileOptions.ServeUnknownFileTypes = true;
        app.UseFileServer(fileServerOptions);
    }

Also you have to make sure the handler is registered in your Web.config file. It should look like this:

  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true">
      <remove name="FormsAuthentication" />
    </modules>
    <handlers>
      <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
      <remove name="OPTIONSVerbHandler" />
      <remove name="TRACEVerbHandler" />
      <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
      <add name="Owin" verb="" path="*" type="Microsoft.Owin.Host.SystemWeb.OwinHttpHandler, Microsoft.Owin.Host.SystemWeb"/>
    </handlers>
  </system.webServer>

Then every file in your "wwwroot" Folder will be automatically accessible.

For example your wwwroot/img/logo.png file will be accessible via http://yourdomain.com/img/logo.png, just like you want it :)

If you generate the content of the wwwroot folder with npm/gulp/grunt in a build event, then maybe you also have to edit your csproj file and add this ItemGroup:

  <ItemGroup>
    <Content Include="wwwroot\**\*" />
  </ItemGroup>
Benedikt Langer
  • 417
  • 4
  • 11
  • 1
    Only additional thing I needed to do here (for any other people who might have been in the same boat) is to pull in the Microsoft.Owin.Host.SystemWeb and Microsoft.Owin nugets and create an OWIN startup class with your above code. Works like a charm. – Andrew Jan 25 '16 at 20:51
3

Add img folder to the root directory of your application. Also you have to to include images in the project or application

enter image description here

Julius Depulla
  • 1,493
  • 1
  • 12
  • 27
  • Sorry, this doesn't answer the original question in any way. – Graham Nov 05 '15 at 03:28
  • @Graham, I would disagree. If you are looking for a structure that will port directly, this would be it. In the ASP.NET 5 boilerplate the API controllers have the route prefix `api/[controller]`. You match this now in your API controllers. The ASP.NET 5 static content base is nested inside `wwwroot`. The only difference now is that your current app will directly serve static content without it being nested down inside of a `wwwroot` folder. The transition will only involve moving your `img` folder from your current root to the `wwwroot` folder in the new project. – davidmdem Nov 10 '15 at 14:23
  • I have more than just an img folder. This also has nothing to do with controllers, only static content. – Graham Nov 11 '15 at 04:36
  • How does having more than one folder change the fundamentals of this approach? The only difference between the MVC 5 and MVC 6 projects will be that static content directories move from `root` to `wwwroot`. I mentioned the controller route setup only to illustrate that you can, today, mimic most of the MVC 6 way of setting up controller routes and static content. – davidmdem Nov 11 '15 at 18:36
2

For handling file routing I would:

  1. Create HttpHandler as workaround for Image handling/or some other static files.
  2. Bundle config for configuring js and css file path.

  1. Create HttpHandler for handling request to specific file extensions. And modify the file real path using provided file relative path from the URL.

HttpHandler for .jpg files:

public class ServiceSettings
{
    public static string RootStaticFolder = "\\wwwroot";
}

public class ImageHandler : IHttpHandler
{
    public bool IsReusable { get { return false; } }
    public void ProcessRequest(HttpContext context)
    {
        var fileSystemPath = context.Server.MapPath(Path.Combine("~") + ServiceSettings.RootStaticFolder);

        var file = Path.Combine(Path.GetDirectoryName(context.Request.FilePath), Path.GetFileName(context.Request.FilePath));
        var filePath = string.Concat(fileSystemPath, file);

        if(!File.Exists(filePath))
        {
            context.Response.StatusCode = (int)HttpStatusCode.NotFound;
            context.Response.Status = "404 Not Found";
        }

        context.Response.WriteFile(filePath);
    }
}

For making it work you must disable MVC routing for this king of files.
RouteConfig.cs:

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

    // Disable routing for */*.jpg files
    routes.IgnoreRoute("{*alljpg}", new { alljpg = @".*\.jpg(/.*)?" });

    routes.MapRoute(
        name: "Default",
        url: "{controller}/{action}/{id}",
        defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
    );

}

Then you have to add registration for your HttpHandler to web.config:

  <system.webServer>
    <modules runAllManagedModulesForAllRequests="false">
      <remove name="FormsAuthentication" />
    </modules>
    <handlers>
      <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
      <remove name="OPTIONSVerbHandler" />
      <remove name="TRACEVerbHandler" />
      <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
      <add name="jpgs" verb="*" path="*.jpg" type="WebApplication1.ImageHandler" preCondition="managedHandler"/>
    </handlers>
  </system.webServer>

Also pay attention to runAllManagedModulesForAllRequests="false" setting in modules tag.


  1. Bundle configuration for css/js files

    public static void RegisterBundles(BundleCollection bundles)
    {
        bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
                    "~/wwwroot/Scripts/jquery-{version}.js"));
    
        // Use the development version of Modernizr to develop with and learn from. Then, when you're
        // ready for production, use the build tool at http://modernizr.com to pick only the tests you need.
        bundles.Add(new ScriptBundle("~/bundles/modernizr").Include(
                    "~/wwwroot/Scripts/modernizr-*"));
    
        bundles.Add(new ScriptBundle("~/bundles/bootstrap").Include(
                  "~/wwwroot/Scripts/bootstrap.js",
                  "~/wwwroot/Scripts/respond.js"));
    
        bundles.Add(new StyleBundle("~/Content/css").Include(
                  "~/wwwroot/Content/bootstrap.css",
                  "~/wwwroot/Content/site.css"));
    }
    

With this approach it would be very easy during migration to asp.net 5. You will only need to remove HttpHandler and bundle configurations.

Andrii Tsok
  • 1,386
  • 1
  • 13
  • 26
  • AFAIK A handler is a poor way to go because they may not be available to him in ASP.Net 5 which the OP mentioned he would be migrating too. – Erik Philips Nov 11 '15 at 00:24
  • @ErikPhilips yes you are right, but this solution just can be used for supporting development in asp.net 5 way. Then during migration from Mvc 5 to Mvc 6 the author will remove my handler without any needs to move static files in solution. It's just temporary workaround that work perfectly :) – Andrii Tsok Nov 11 '15 at 07:27