在 ASP.NET MVC 应用程序中何处放置特定于视图的 javascript 文件?

在 ASP.NET MVC 应用程序中放置特定于视图的 javascript 文件的最佳位置是什么(哪个文件夹等) ?

为了让我的项目井井有条,我真的很希望能够将它们与视图并排放置。Aspx 文件,但是我还没有找到一个好的方法来引用它们,而不暴露 ~/视图/操作/文件夹结构。让文件夹结构的细节泄露真的是件坏事吗?

另一种方法是将它们放在 ~/Scripts 或 ~/Content 文件夹中,但这种方法有点烦人,因为现在我不得不担心文件名冲突。不过,如果这是“正确的事情”,我可以克服这种恼怒

31330 次浏览

One way of achieving this is to supply your own ActionInvoker. Using the code included below, you can add to your controller's constructor:

ActionInvoker = new JavaScriptActionInvoker();

Now, whenever you place a .js file next to your view:

enter image description here

You can access it directly:

http://yourdomain.com/YourController/Index.js

Below is the source:

namespace JavaScriptViews {
public class JavaScriptActionDescriptor : ActionDescriptor
{
private string actionName;
private ControllerDescriptor controllerDescriptor;


public JavaScriptActionDescriptor(string actionName, ControllerDescriptor controllerDescriptor)
{
this.actionName = actionName;
this.controllerDescriptor = controllerDescriptor;
}


public override object Execute(ControllerContext controllerContext, IDictionary<string, object> parameters)
{
return new ViewResult();
}


public override ParameterDescriptor[] GetParameters()
{
return new ParameterDescriptor[0];
}


public override string ActionName
{
get { return actionName; }
}


public override ControllerDescriptor ControllerDescriptor
{
get { return controllerDescriptor; }
}
}


public class JavaScriptActionInvoker : ControllerActionInvoker
{
protected override ActionDescriptor FindAction(ControllerContext controllerContext, ControllerDescriptor controllerDescriptor, string actionName)
{
var action = base.FindAction(controllerContext, controllerDescriptor, actionName);
if (action != null)
{
return action;
}


if (actionName.EndsWith(".js"))
{
return new JavaScriptActionDescriptor(actionName, controllerDescriptor);
}


else
return null;
}
}


public class JavaScriptView : IView
{
private string fileName;


public JavaScriptView(string fileName)
{
this.fileName = fileName;
}


public void Render(ViewContext viewContext, TextWriter writer)
{
var file = File.ReadAllText(viewContext.HttpContext.Server.MapPath(fileName));
writer.Write(file);
}
}




public class JavaScriptViewEngine : VirtualPathProviderViewEngine
{
public JavaScriptViewEngine()
: this(null)
{
}


public JavaScriptViewEngine(IViewPageActivator viewPageActivator)
: base()
{
AreaViewLocationFormats = new[]
{
"~/Areas/{2}/Views/{1}/{0}.js",
"~/Areas/{2}/Views/Shared/{0}.js"
};
AreaMasterLocationFormats = new[]
{
"~/Areas/{2}/Views/{1}/{0}.js",
"~/Areas/{2}/Views/Shared/{0}.js"
};
AreaPartialViewLocationFormats = new []
{
"~/Areas/{2}/Views/{1}/{0}.js",
"~/Areas/{2}/Views/Shared/{0}.js"
};
ViewLocationFormats = new[]
{
"~/Views/{1}/{0}.js",
"~/Views/Shared/{0}.js"
};
MasterLocationFormats = new[]
{
"~/Views/{1}/{0}.js",
"~/Views/Shared/{0}.js"
};
PartialViewLocationFormats = new[]
{
"~/Views/{1}/{0}.js",
"~/Views/Shared/{0}.js"
};
FileExtensions = new[]
{
"js"
};
}


public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
{
if (viewName.EndsWith(".js"))
viewName = viewName.ChopEnd(".js");
return base.FindView(controllerContext, viewName, masterName, useCache);
}




protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath)
{
return new JavaScriptView(partialPath);
}


protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
{
return new JavaScriptView(viewPath);
}
}
}

Old question, but I wanted to put my answer incase anyone else comes looking for it.

I too wanted my view specific js/css files under the views folder, and here's how I did it:

In the web.config folder in the root of /Views you need to modify two sections to enable the webserver to serve the files:

    <system.web>
<httpHandlers>
<add path="*.js" verb="GET,HEAD" type="System.Web.StaticFileHandler" />
<add path="*.css" verb="GET,HEAD" type="System.Web.StaticFileHandler" />
<add path="*" verb="*" type="System.Web.HttpNotFoundHandler"/>
</httpHandlers>
<!-- other content here -->
</system.web>


<system.webServer>
<handlers>
<remove name="BlockViewHandler"/>
<add name="JavaScript" path="*.js" verb="GET,HEAD" type="System.Web.StaticFileHandler" />
<add name="CSS" path="*.css" verb="GET,HEAD" type="System.Web.StaticFileHandler" />
<add name="BlockViewHandler" path="*" verb="*" preCondition="integratedMode" type="System.Web.HttpNotFoundHandler" />
</handlers>
<!-- other content here -->
</system.webServer>

Then from your view file you can reference the urls like you expect:

@Url.Content("~/Views/<ControllerName>/somefile.css")

This will allow serving of .js and .css files, and will forbid serving of anything else.

You can invert davesw's suggestion and block only .cshtml

<httpHandlers>
<add path="*.cshtml" verb="*" type="System.Web.HttpNotFoundHandler"/>
</httpHandlers>

I know this is a rather old topic, but I have a few things I would like to add. I tried davesw's answer but it was throwing a 500 error when trying to load the script files, so I had to add this to the web.config:

<validation validateIntegratedModeConfiguration="false" />

to system.webServer. Here is what I have, and I was able to get it to work:

<system.webServer>
<handlers>
<remove name="BlockViewHandler"/>
<add name="JavaScript" path="*.js" verb="GET,HEAD" type="System.Web.StaticFileHandler" />
<add name="CSS" path="*.css" verb="GET,HEAD" type="System.Web.StaticFileHandler" />
<add name="BlockViewHandler" path="*" verb="*" preCondition="integratedMode" type="System.Web.HttpNotFoundHandler" />
</handlers>
<validation validateIntegratedModeConfiguration="false" />
</system.webServer>
<system.web>
<compilation>
<assemblies>
<add assembly="System.Web.Mvc, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
</assemblies>
</compilation>
<httpHandlers>
<add path="*.js" verb="GET,HEAD" type="System.Web.StaticFileHandler" />
<add path="*.css" verb="GET,HEAD" type="System.Web.StaticFileHandler" />
<add path="*" verb="*" type="System.Web.HttpNotFoundHandler"/>
</httpHandlers>
</system.web>

Here is more information on validation: https://www.iis.net/configreference/system.webserver/validation

add this code in web.config file inside system.web tag

<handlers>
<remove name="BlockViewHandler"/>
<add name="JavaScript" path="*.js" verb="GET,HEAD" type="System.Web.StaticFileHandler" />
<add name="CSS" path="*.css" verb="GET,HEAD" type="System.Web.StaticFileHandler" />
<add name="BlockViewHandler" path="*" verb="*" preCondition="integratedMode" type="System.Web.HttpNotFoundHandler" />
</handlers>

I also wanted to place js files related to a view in the same folder as the view.

I wasn't able to get the other solutions in this thread to work, not that they are broken but I am too new to MVC to get them working.

Using information given here and several other stacks I came up with a solution that:

  • Allows the javascript file to be placed in the same directory as the view it is associated with.
  • Script URL's don't give away the underlying physical site structure
  • Script URL's don't have to end with a trailing slash (/)
  • Doesn't interfere with static resources, eg: /Scripts/someFile.js still works
  • Doesn't require runAllManagedModulesForAllRequests to be enabled.

Note: I am also using HTTP Attribute Routing. It's possible that the route's used in my soultion could be modified to work without enabling this.

Given the following example directory/file structure:

Controllers
-- Example
-- ExampleController.vb


Views
-- Example
-- Test.vbhtml
-- Test.js

Using the configuration steps given below, combined with the example structure above, the test view URL would be accessed via: /Example/Test and the javascript file would be referenced via: /Example/Scripts/test.js

Step 1 - Enable Attribute Routing:

Edit your /App_start/RouteConfig.vb file and add routes.MapMvcAttributeRoutes() just above the existing routes.MapRoute:

Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Web
Imports System.Web.Mvc
Imports System.Web.Routing


Public Module RouteConfig
Public Sub RegisterRoutes(ByVal routes As RouteCollection)
routes.IgnoreRoute("{resource}.axd/{*pathInfo}")


' Enable HTTP atribute routing
routes.MapMvcAttributeRoutes()


routes.MapRoute(
name:="Default",
url:="{controller}/{action}/{id}",
defaults:=New With {.controller = "Home", .action = "Index", .id = UrlParameter.Optional}
)
End Sub
End Module

Step 2 -Configure your site to treat, and process, /{controller}/Scripts/*.js as an MVC path and not a static resource

Edit your /Web.config file, adding the following to the system.webServer --> handlers section of the file:

<add name="ApiURIs-ISAPI-Integrated-4.0" path="*/scripts/*.js" verb="GET" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />

Here it is again with context:

  <system.webServer>
<modules>
<remove name="TelemetryCorrelationHttpModule"/>
<add name="TelemetryCorrelationHttpModule" type="Microsoft.AspNet.TelemetryCorrelation.TelemetryCorrelationHttpModule, Microsoft.AspNet.TelemetryCorrelation" preCondition="managedHandler"/>
<remove name="ApplicationInsightsWebTracking"/>
<add name="ApplicationInsightsWebTracking" type="Microsoft.ApplicationInsights.Web.ApplicationInsightsHttpModule, Microsoft.AI.Web" preCondition="managedHandler"/>
</modules>
<validation validateIntegratedModeConfiguration="false"/>
<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="ApiURIs-ISAPI-Integrated-4.0" path="*/scripts/*.js" verb="GET" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
</handlers>
</system.webServer>

Step 3 - Add the following scripts action result to your Controller file

  • Be sure to edit the route path to match the {controller} name for the controller, for this example it's: <Route("Example/Scripts/{filename}")>
  • You will need to copy this into each of your Controller files. If you wanted, there is probably a way to do this as a single, one-time, route configuration somehow.

        ' /Example/Scripts/*.js
    <Route("Example/Scripts/{filename}")>
    Function Scripts(filename As String) As ActionResult
    ' ControllerName could be hardcoded but doing it this way allows for copy/pasting this code block into other controllers without having to edit
    Dim ControllerName As String = System.Web.HttpContext.Current.Request.RequestContext.RouteData.Values("controller").ToString()
    
    
    ' the real file path
    Dim filePath As String = Server.MapPath("~/Views/" & ControllerName & "/" & filename)
    
    
    ' send the file contents back
    Return Content(System.IO.File.ReadAllText(filePath), "text/javascript")
    End Function
    

For context, this is my ExampleController.vb file:

Imports System.Web.Mvc


Namespace myAppName
Public Class ExampleController
Inherits Controller


' /Example/Test
Function Test() As ActionResult
Return View()
End Function




' /Example/Scripts/*.js
<Route("Example/Scripts/{filename}")>
Function Scripts(filename As String) As ActionResult
' ControllerName could be hardcoded but doing it this way allows for copy/pasting this code block into other controllers without having to edit
Dim ControllerName As String = System.Web.HttpContext.Current.Request.RequestContext.RouteData.Values("controller").ToString()


' the real file path
Dim filePath As String = Server.MapPath("~/Views/" & ControllerName & "/" & filename)


' send the file contents back
Return Content(System.IO.File.ReadAllText(filePath), "text/javascript")
End Function




End Class
End Namespace

Final Notes There is nothing special about the test.vbhtml view / test.js javascript files and are not shown here.

I keep my CSS in the view file but you could easily add to this solution so that you can reference your CSS files in a similar way.