Background
I have an application where 3 views utilize html5 offline app functionality. As such, I have an app manifest generated in a razor view. A cut-down version of this view may look like the following:
CACHE MANIFEST
CACHE:
/site.min.css
/site.min.js
In order for the offline app to function correctly, the cached files must exactly match those requested by offline pages within the app. However, I would like to apply a 'cache-busting' version string to the js/css resources referenced in this manifest. For HTML tags, this is supported by the ScriptTagHelper but I have not found any helpers/extension methods which support this for plain URLs (as required in the above manifest).
With reference to this post, I have resolved this by injecting a FileVersionProvider into my manifest view and using the AddFileVersionToPath() method as follows:
#inject FileVersionProvider versionProvider
CACHE MANIFEST
CACHE:
#versionProvider.AddFileVersionToPath("/site.min.css")
#versionProvider.AddFileVersionToPath("/site.min.js")
However, the FileVersionProvider class is in the Microsoft.AspNetCore.Mvc.TagHelpers.Internal namespace which does not fill me with confidence form a maintenance perspective.
Finally, my implementation of the DI setup for this is not exactly ideal (see below). I don't like the fact that I need to make calls to GetService() and surely I should be specifying a specific MemoryCache?
services.AddSingleton<FileVersionProvider>(s =>
new FileVersionProvider(
s.GetService<IHostingEnvironment>()?.WebRootFileProvider,
s.GetService<IMemoryCache>(),
new PathString("") ));
Question
Has anyone had a requirement to create a link to a js/css resource with a version string before? If so, is there a more elegant solution?
Did you try this extension method? It worked for me on ASP.NET Core 2.1 LTS without any depencency injection, just the extension method. Should work in 2.0 as well if you still use it.
I had a similar use-case where SCEditor loads CSS from a path passed by JavaScript.
#{
// Without version hash cache buster, the editor internally cache the request and we don't get the new stylesheet - even not with CTRL + F5
string editorThemePath = this.AddFileVersionToPath("/css/my-editor-theme.css");
}
<script>
sceditor.create(editorTextarea, {
// ...
style: '#editorThemePath'
})
</script>
Generated HTML
<script>
sceditor.create(editorTextarea, {
style: '/css/my-editor-theme.css?v=sRU_qBg7xyQElPLskjekOlvSHdRF2Ap1leTP8ui4CaI',
})
</script>
Keep in mind that this implementation requires paths relative to wwwroot. So in this case, /css/my-editor-theme.css is in wwwroot/css/my-editor-theme.css.
Could be changed if you replace the first parameter of FileVersionProvider from hostingEnvironment.WebRootFileProvider to hostingEnvironment.ContentRootFileProvider. But I havent' tested what are the side effects and security concerns of this, so you should stay on wwwroot if possible since this folder is designed to be public accessable.
Related
I am transitioning to using Areas in a project, and each time I make a new one I have to change a few things to make it fit in with how I'd like things to work. The changes involve:
Removing one of the folders that gets created
Modifying the web.config file slightly
Adding a new file or two with some defaults
Modifying the AreaRegistration file slightly
I would rather not have to do these same things every time I create a new area because I'm afraid that the convention will be forgotten and something will get messed up. Is there a way to modify the defaults for New > Area? Or perhaps would it be possible to make a new file template that does what I'd like?
I am transitioning to using Areas in a project
I have been doing something similar for a few months now, but starting with an old WebForms project, adding MVC components via VS 2013's Add -> New Scaffolded Item... feature, in order to leverage MVC's structure and routing, on the server side. I have also been incorporating WebAPI and SignalR on the server side. I am definitely still a beginner, but being maybe a little further along in a similar process, here some of my thoughts and questions about what you want to achieve:
Removing one of the folders that gets created
Modifying the web.config file slightly
Adding a new file or two with some defaults
What environment are you developing in? For instance, if you're using VS 2013's scaffolding you can most likely modify the T4 templates to prevent or redirect the creation of folders, classes, and such. Here is a post addressing where you might find those templates, and here is just one of many intros out there for T4. The path to the templates on your machine will vary based on the VS version. By modifying or creating new templates you should be able to accomplish all 3 points above relatively easy. If you are working in such an environment I will try to expound on exactly how you might go about modifying the out of the box templates (there are probably already plenty of posts that you could refer to which would do a better job though).
Modifying the AreaRegistration file slightly
If you are working with MVC 5, or later, I would recommend using attribute routing. This seems more standard when working within the API paradigm (inheritting from ApiController instead of Controller), but I have found extending this to traditional MVC controllers to be very useful.
Assuming MVC 5, I would recommend modifying AppStart\RouteConfig.cs's RegisterRoutes() method, to enable attribute routing and then add a catch-all route such as:
routes.MapMvcAttributeRoutes();
routes.MapRoute(
"NotFound",
"{*url}",
new {
controller = "Error",
action = "Index"
});
At that point I would delete the xAreaRegistration.cs files, and remove the call to RegisterAllAreas() from Global.asax, but you don't have to go that far. Then I decorate my controllers' classes (wherever they live) and their methods, with attributes as needed. Using attribute routing I've been able to free myself from the traditional MVC conventions. I can still fall back to them, but I can then place my controllers anywhere in my project, and easily define their actions' routes.
That being said, you should probably follow some organizational standards for groups of similar routes, so that as the project grows you don't find yourself wasting a lot of time searching for all controllers/actions matching some set of routes.
Here's a simple example of how this actually translates from URI into method call:
If I had Controller ctl with action act, logically in Area ar, the URI being http://localhost/ar/ctl/a/1, would be routed to MyControllerNameDoesNotMatter.Index(1), below, by using attribute decoration such as:
[RouteArea("ar")]
[RoutePrefix("ctl")]
public class MyControllerNameDoesNotMatter : Controller {
...
[Route("a/{optionalParamDefaultsToNegativeOne=-1}")]
public ActionResult Index(int optionalParamDefaultsToNegativeOne) {
...
}
}
This way of looking at Area is more logical than physical, in that the MyControllerNameDoesNotMatter.cs file does not have to be in the Area\ar\Controllers folder. Since adopting this I have actually drifted away from using the Area scaffolding, other than to lay out application modules at a very high level - think sub-application that could be a stand alone SPA.
I have tried to make the "catch-all" route attribute based, and remove it from RouteConfig.cs, but I have not been able to do this successfully. The issue is route precedence. It works fine as a catch-all for invalid routes. But for a valid route, multiple (2) routes end up being matched because the two attribute routes have the same order of precedence. Whereas, with the catch-all defined in RouteConfig.cs, after registering all attribute routes, the attribute route takes precedence. I have found mixed answers that attempt to address this issue. There seems to have been a Number or Order parameter for the Route attribute at some point, but I have had no luck with that. This seems to be an unresolved issue. Just one of many SO questions, which have gone unanswered.
Edited to account for mvc4 tag
I had not seen the mvc4 tag prior to posting. There are nuget packages available that will achieve much of the same functionality, such as scaffolding and attribute routing for earlier versions of MVC.
We're setting up a bunch of json web services in ASP.NET which is served as .ashx (custom handlers) files. An example would be:
/mobile/json.ashx
We'd like to implement some form of versioning as well as to not break apps which has not upgraded. So we led down this path:
/mobile/json.ashx?v=1.0
Now, of course we have have a switch statement in our custom handlers to manage the differences between api version but this doesn't sound like a very maintainable solution to me.
What are the best practises for this kind of set up and what options are available for version control?
Thanks
Placing the version in the query parameters (that is, after the ?) suggests to the user that each endpoint is individually versioned. I would avoid this.
If your web service is structured such that there are larger logical units that are being individually versioned, then I would go with something like this:
/api1/1.0/some/endpoint
/api1/1.1/some/endpoint
/api2/1.0/some/other/endpoint
/api2/2.0/some/other/endpoint
...
The version portion of the path comes directly after the thing which is being versioned. This suggests to the user that everything underneath /api1/1.1/ is version 1.1 of API 1 and everything underneath /api2/2.0/ is version 2.0 of API 2.
If someone entirely omits the version portion of the path, the latest version should be implied. So /api2/some/other/endpoint would map to, say, /api2/2.0/some/other/endpoint.
If you're using ASP.NET MVC, all of this can be accomplished very easy using route configuration in the RegisterRoutes method in Global.asax.cs. For example:
routes.MapRoute("api1/1.1", "api1/1.1/some/endpoint",
new { controller = "Api1_1_1", action = "SomeEndpoint" });
where you have a controller class Api1_1_1 with method SomeEndpoint.
I'm having some trouble understanding a bit of HTML and was hoping that SO could help me in the process of figuring out what is going on, so that I might be able to do this myself in the future.
The markup is simple :
<p>
GET /api/function: returns list of info from database.
</p>
Now, this works fully and I am trying to understand what is going on. My understanding is that this would go to root directory, find a folder called api, find a function called function and run it.
The problem is that there is no folder called api - so what could be going on here ? I can find the C# function that is actually being called to retrieve the items from the DB, but I cannot figure out how the code calling this might be structured. I have a class which extends DbContext to retrieve information, but I cannot see how this is being called and it is not on the call stack when I insert a breakpoint.
Can anyone give me some information on how I might shed some light on this ?
(Apologies for the very general question, I will give more specifics as I begin to understand what is actually going on!)
There is actually no need for an api folder. It can be a simple route configured that maps a certain URI scheme to some files. It doesn't even have to be files, it can be methods on a class.
For example, in ASP.NET Web API, you have ApiController classes with methods. In your case the method would be called Function or GetFunction or similar.
The route config would contain something like that:
routes.MapHttpRoute("SomeRoute",
"api/{action}",
new { controller = "YourController", action = "Index" });
See the introduction to routes in ASP.NET Web API for more info.
I suggest that you also read the entire series about the ASP.NET WebAPI
"~/api/function" need not be an actual folder in your filesystem. It can be a virtual path defined in your webserver configuration, like web.xml in tomcat.
So I am reading about building MVC3 projects and there is one thing that seriously bugs me. The folder structure of the project in no way corresponds to the path of the HTTP request. There is a bunch of things I like and would want to use, but having a flat folder structure is not one of them.
Why is this a problem? Well, I see it becoming a problem when building a site with a heavy mix of content pages and various forms/dynamic pages (most of our sites are like that), which would typically be done by different people. It seem it would be too complicated for client-side developers to follow routing rules of dynamic pages and/or creating new ones.
What I would like to understand is if there is way to configure MVC3 application in such a way that:
it follows directory structure for finding controllers without explicit route map for each one
views live in the same folder as corresponding controller
routing magic still works for actions and parameters
For instance I'd like to have a request /fus/ro/dah/ to try to find DahController in the \webroot\fus\ro\dah\ folder and execute its Index action. If not found it would look for RoController with Dah action in the \webroot\fus\ro\ folder, etc.
It is entirely possible that MVC was not meant to be working this way at all and I am just trying to force a square peg into a round hole.
UPDATE:
Looks like I can drop a view file into the desired folder structure, and it will be executed. However layout would not work apparently because it is expecting a controller. Does this mean I have to create a controller for pure content pages? That is a pretty crappy design...
UPDATE 2:
Main issue right now is that creating "fus" folder means that MVC will not even attempt to look for FusController... not under "fus" folder, nor anywhere else. Is it possible to get around that?
For instance I'd like to have a request /fus/ro/dah/ to try to find
DahController in the \webroot\fus\ro\dah\ folder and execute its Index
action. If not found it would look for RoController with Dah action in
the \webroot\fus\ro\ folder, etc.
MVC is not designed for a particular need like this, it is a general framework for building applications using model-view-controller pattern.
If you can't bend the application for the framework you can bend the framework for the application and honestly MVC is very customizable. [As a proof, in the current project (migration from ASP to MVC) that I'm working we have models as xml and no classes also we are using XSLTs for rendering. With a little work we have created custom components like custom view engine, custom validation provider, custom model binder... to make the framework best fit for the application and it does]
MVC is not designed and not forces to use it as it is and you can customize/extend as much you want. In your case you may have to create a
custom controller factory (because you want to customize the way in which the controller is seleced),
custom view engine (because you want to customize where the view is placed)
and may be others.
For custom controller factory you have to extend the DefaultControllerFactory class. There are lot of articles you can find through Google that explains about how to create custom controller factories.
Depending upon the view engine you are using you have to extend the respective one. For ex. if you are using web forms then you have to extend the WebFormsViewEngine and it razor then RazorViewEngine.
For more info. check this link
http://codeclimber.net.nz/archive/2009/04/08/13-asp.net-mvc-extensibility-points-you-have-to-know.aspx
you can mixup Asp.net and Asp.net MVC. as LukLed said, MVC is convention over configuration pattern. if you follow the convention. you dont need to configure. you can check this link for mixing up the asp.net content with MVC3
Mixing Asp.net and Razor
I believe ASP.NET MVC is not meant to be used that way. While you can configure MVC to do it, it is better to keep standard /controller/action/parameters URL format. If you have complex website with many different functionalities, areas may be helpful http://msdn.microsoft.com/en-us/library/ee671793.aspx. Every area get its own set of controllers, models and views, so teams working on different parts of website won't disturb each other.
While it may sound convenient, that framework first searches for DahController and executes Index action, then searches for another one, I find it bad idea. URLs should be clearly defined and Fus controller with Ro action shouldn't just stop working, because someone created RoController with Index action.
Look into using Areas as well. I think that helped me get over my folder structure issues with MVC honestly. So i could use the base folder as my Home details, then i could have a 'Admin' area which was a separate folder, things like that.
How "regular ASP.net" do you want it to be? If you want to do "legacy" ASP.Net Web Forms mixed in with MVC, you certainly can - re: mixing MVC with "file based aspx" - aka "hybrid". At the end of the day, it's all ASP.Net.
This is a standard MVC Application generated by Visual Studio. I've added a folder somedirectory where I want to use the legacy folder/file.ext paradigm and have a default.aspx Web Forms file:
Can I navigate to it via http://foo.com/somedirectory? Yes.
Can I use "pretty urls" too? Yes
Vanilla Global.asax generated by VS, just added MapPageRoute:
....
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
//using "pretty urls" - ordering your routes matter.
routes.MapPageRoute("theWebForm", "legacy", "~/somedirectory/default.aspx");
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
so now I can navigate to it via http://foo.com/legacy
Just check the order of your routes, and perhaps plan on your naming conventions so you don't have "collisions"...
Hth....
I'm fairly decent with MVC3 and enjoy creating my sites with it, however, I am yet to think up and implement a decent method of a "plugin" system.
Basically, I aim to have a generic "blog-type" CMS which I can distribute across my sites, but with the option to have certain things as plugins.
For example:
Generic build:
User area
Basic blog/news editing
Plugins: (May be needed for one or two sites, but not all)
Chatroom plugin
Stats
and so on...
Currently I would just make it all and disable things through a config file, however it would be nice if i could just drop a folder into my FTP and have an MVC page which automatically picks it up!
I assume I would have to start with scanning the directory "/plugins" and picking up a "plugin.config" (Or similar) file which would contain the basic details.
But how would I get my main system to pick these things up and actually use them?!
You may be able to do this using MVC Areas, here are some links about them:
ASP.NET MVC 2 Areas
ASP.NET MVC Areas: Are they important to a large application?
https://stackoverflow.com/questions/462458/asp-net-mvc-areas-are-they-important-to-a-large-application
Try assembly scanning with StructureMap dependency injection.
Read this great tutorial: ASP.NET MVC2 Plugin Architecture Tutorial
It help me create a plugin architecture with MVC3.
Areas solve the problem for you providing you have everything in the original project/assembly. You could write your plugin system to allow the plugins to register their own areas, or alternatively you could register some new view search paths in a custom Razor view engine.
I chose the latter for a recent OS project I wrote called Spruce, which uses a whole plugin architecture you might find useful as a reference.
You can scan all the assemblies in the bin directory on startup to check for plugins, via reflection. You usually check for types that implement an interface or inherit from a class, and use these along side an IoC container such as TinyIoc, NInject, StructureMap or Unity. I'd recommend TinyIoC which is used by NancyFX.