简体   繁体   中英

How to modify ASP.NET MVC static file root

I want to be able to reorganize my ASP.NET MVC site structure to more closely match the way Rails does it (We do both rails and ASP.net at my company).

In Rails, there is a "public" folder that behaves as the root of the site. For example, I could drop in a "test.html" and access the file with the url http://domain.com/test.html which would serve up the static html file.

In asp.net MVC there is a "Content" folder that I want to behave as the root. So instead of accessing http://domain.com/content/myfile.html , i want to be able to do http://domain.com/myfile.html .

I know I can just drop the file in the root of the project, but i need to do this with many files including css, js, html, images, etc and want to share some standardized assets across rails and aspnetmvc.

Is there a way to do this?

There is another possible solution. Instead of using code, you can use a rewrite rule to handle this for you. If you are using IIS 7 or above, you can use Microsoft's URL Rewrite Module.

A rule like the following would probably do it:

<rule name="Rewrite static files to content" stopProcessing="true">
  <match url="^([^/]+(?:\.css|\.js))$" />
  <conditions>
      <add input="{APPL_PHYSICAL_PATH}content{SCRIPT_NAME}" matchType="IsFile" />
  </conditions>
  <action type="Rewrite" url="/content/{R:1}" />
</rule>

The rule checks for a request to a css or js file off the root of the site. Then it checks to see if the file exists in the content folder. If it exists, then the rewrite will return the file in the content folder. I've only tested this a little bit, but it seems to work. It certainly needs more testing, and possible refinement.

The only solution I can think of, is to use a custom controller and route to do this for you. But it isn't a clean solution.

First you need a PublicController class with a GetFile action method. This assumes that all files are in the public/content folder directly. Handling folders makes things more complicated.

public class PublicController : Controller
{
    private IDictionary<String, String> mimeTypes = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
                                                        {{"css", "text/css"}, {"jpg", "image/jpg"}};

    public ActionResult GetFile(string file)
    {
        var path = Path.Combine(Server.MapPath("~/Content"), file);

        if (!System.IO.File.Exists(path)) throw new HttpException(404, "File Not Found");
        var extension = GetExtension(file); // psuedocode
        var mimetype = mimeTypes.ContainsKey(extension) ? mimeTypes[extension] : "text/plain";
        return File(path, mimetype);
    }
}

Now, you just need a route near the bottom of your list of routes that looks like this:

routes.MapRoute("PublicContent", "{file}", new {controller = "Public", action = "GetFile"});

The problem is, now when you just put in a controller name like 'Home' instead of defaulting to the Index action method on the HomeController, it assumes you want to download a file called "Home" from the content directory. So, above the file route, you would need to add a route for each controller so it knows to get the Index action instead.

routes.MapRoute("HomeIndex", "Home", new { controller = "Home", action = "Index" });

So, one way around that is to change the route to this:

routes.MapRoute("PublicContent", "{file}.{extension}", new {controller = "Public", action = "GetFile"});

And the action method to this:

    public ActionResult GetFile(string file, string extension)
    {
        var path = Path.Combine(Server.MapPath("~/Content"), file + "." + extension);

        if (!System.IO.File.Exists(path)) throw new HttpException(404, "File Not Found");

        var mimetype = mimeTypes.ContainsKey(extension) ? mimeTypes[extension] : "text/plain";
        return File(path, mimetype);
    }

Like I said, this assumes that all files are in the content directory, and not in subfolders. But if you wanted to do subfolders like Content/css/site.css you could add your routes like this:

        routes.MapRoute("PublicContent_sub", "{subfolder}/{file}.{extension}", new { controller = "Public", action = "GetFileInFolder" });
        routes.MapRoute("PublicContent", "{file}.{extension}", new { controller = "Public", action = "GetFile"});

Now the action method has to change too.

    public ActionResult GetFile(string file, string extension)
    {
        return GetFileInFolder("", file, extension);
    }

    public ActionResult GetFileInFolder(string subfolder, string file, string extension)
    {
        var path = Path.Combine(Server.MapPath("~/Content"), subfolder, file + "." + extension);

        if (!System.IO.File.Exists(path)) throw new HttpException(404, "File Not Found");

        var mimetype = mimeTypes.ContainsKey(extension) ? mimeTypes[extension] : "text/plain";
        return File(path, mimetype);
    }

If you start getting multiple levels deep in the folder structure, this gets uglier and uglier. But maybe this will work for you. I'm sure you were hoping for a checkbox in the project properties, but if there is one, I don't know about it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM