For my MVC project (Image Server Application), I cannot do caching using imageresizer. I can access my images like this and the image source could be either FileSystem/Database (Dependency injeciton) :
localhost/images/123.jpg?width=500
localhost/images/123?width=500
I have an MVC 3 project with routes like
routes.RouteExistingFiles = true;
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.IgnoreRoute("favicon.ico");
routes.MapRoute(
"ImagesWithExtension", // Route name
"images/{imageName}.{extension}/", // URL with parameters
new { controller = "Home", action = "ViewImageWithExtension", imageName = "", extension = "", id = UrlParameter.Optional } // Parameter defaults
);
routes.MapRoute(
"Images", // Route name
"images/{imageName}/", // URL with parameters
new { controller = "Home", action = "ViewImage", imageName = "", id = UrlParameter.Optional } // Parameter defaults
);
I have two controllers to deal with images
public ActionResult ViewImageWithExtension(string imageName, string extension) {}
public ActionResult ViewImage(string imageName) {}
The caching is done when the URL is like :
localhost/images/123.jpg?width=500 and the image source is FileSystem
localhost/images/123?width=500 Cache not working image source is Filesystem
localhost/images/123.jpg?width=500 Cache not working, image source DB
localhost/images/123?width=500 Cache not working , image source DB
My web config is like this:
<configSections>
<section name="resizer" type="ImageResizer.ResizerSection" /> </configSections>
<resizer>
<!-- Unless you (a) use Integrated mode, or (b) map all reqeusts to ASP.NET,
you'll need to add .ashx to your image URLs: image.jpg.ashx?width=200&height=20
Optional - this is the default setting -->
<diagnostics enableFor="AllHosts" />
<pipeline fakeExtensions=".ashx" />
<DiskCache dir="~/MyCachedImages" autoClean="false" hashModifiedDate="true" enabled="true" subfolders="32" cacheAccessTimeout="15000" asyncWrites="true" asyncBufferSize="10485760" />
<cleanupStrategy startupDelay="00:05" minDelay="00:00:20" maxDelay="00:05" optimalWorkSegmentLength="00:00:04" targetItemsPerFolder="400" maximumItemsPerFolder="1000" avoidRemovalIfCreatedWithin="24:00" avoidRemovalIfUsedWithin="4.00:00" prohibitRemovalIfUsedWithin="00:05" prohibitRemovalIfCreatedWithin="00:10" />
<plugins>
<add name="DiskCache" />
</plugins> </resizer>
Am I doing something wrong or Imageresizer doesnt support this scenario ? If not any good plugin to use disk based image cahce ?
Thanks in advance.
As I explained in the other public forums which you simultaneously posted this question, the ImageResizer supports dependency injection from the ground up. You're trying to wrap dependency injection with more dependency injection, but backwards.
A) ASP.NET MVC 3 and 4 prevent efficient disk caching, by design. You need to work with the ImageResizer HttpModule, not against it, to get good performance. That means using the URL API, not the Managed API wrapped by your own MVC ActionResults. Listen to my podcast with Scott Hanselman for more info.
B) SqlReader, S3Reader, MongoReader, VirtualFolder, and AzureReader all support dynamic injection, and can (with a tiny bit of configuration), all use the same path syntax. The ImageResizer is designed to allow easy migration between data stores.
C) You can use the Config.Current.Pipeline.Rewrite event to make the URL API use any syntax you want. This is much more flexible than MVC routes (and less buggy).
D) If you want to add another layer of dependency injection, implement IPlugin, and dynamically pick the appropriate data store and configure it from the Install method.
Related
I use an MVC folder structure where the URL routes happen to match the directory names, eg.:
<proj>\My\Cool\Thing\ThingController.cs
Needs to be accessible by this url:
http://blahblah/My/Cool/Thing
I have the MVC routing working but unfortunately when relying on default {action} & {id}, IIS Express is routing the request to DirectoryListingModule instead, since it directly matches a folder name. Directory listing is disabled of course so instead I get:
The Web server is configured to not list the contents of this directory.
Module DirectoryListingModule
Notification ExecuteRequestHandler
Handler StaticFile
To fix this I have already tried:
1. runAllManagedModulesForAllRequests = true
<system.webServer>
<modules runAllManagedModulesForAllRequests="true" >
//Makes no difference
2. Removing module
<system.webServer>
<modules runAllManagedModulesForAllRequests="true" >
<remove name="DirectoryListingModule"/>
// Won't let me as module is locked in IIS
</modules>
</system.webServer>
3. Removing lock & module
// applicationhost.config
<add name="DirectoryListingModule" lockItem="false" />
// web.config
<remove name="DirectoryListingModule"/>
// Causes startup error"Handler "StaticFile" has a bad module "DirectoryListingModule" in its module list"
4. Removing lock & removing/readding module (to change order) - makes no difference
// web.config
<remove name="DirectoryListingModule"/>
<add name="DirectoryListingModule"/>
Tearing my hair out. How can I get IIS to route this to my MVC app instead of DirectoryListingModue?? Preferably a solution in web.config so we don't need to reconfigure IIS in production.
(One workaround is to keep my folder structure but store it all under /Areas/... instead, just to break the match between folder path & url. This is a terrible hack & last resort.)
edit to add route mapping
I am creating custom routes relative to each controller's namespaces (namespaces always match folders). Note that everything is put under the "Modules" namespace / folder currently just to avoid the problem described above.
private static void RegisterAllControllers(RouteCollection routes)
{
const string controllerSuffix = "Controller";
const string namespacePrefix = "My.Cool.Websire.UI.Modules.";
var controllerTypes = Assembly.GetExecutingAssembly().GetTypes().Where(x => x.IsSubclassOf(typeof(Controller))).ToList();
foreach (var controllerType in controllerTypes)
{
// Turn My.Cool.Website.UI.Modules.X.Y.Z.Abc.AbcController into a route for url /X/Y/Z/Abc/{action}/{id}
var fullNamespace = controllerType.Namespace ?? "";
var relativeNamespace = fullNamespace.Substring(namespacePrefix.Length, fullNamespace.Length - namespacePrefix.Length);
var controllerName =
controllerType.Name.EndsWith(controllerSuffix)
? controllerType.Name.Substring(0, controllerType.Name.Length - controllerSuffix.Length)
: controllerType.Name;
var url = relativeNamespace.Replace(".", "/") + "/{action}/{id}";
var routeName = "Dedicated " + controllerName + " route";
routes.MapRoute(routeName, url, new { controller = controllerName, action = "Index", id = UrlParameter.Optional });
}
}
My solution at this stage is to place the MVC contents of the WebUI project under a /Modules/ folder:
My.Cool.Site.WebUI/Modules/Something/Blah/BlahController
My.Cool.Site.WebUI/Modules/Something/Blah/Views/...
My.Cool.Site.WebUI/Modules/Something/Blah/PartialViews/...
Then using the route code posted I can access this via url:
http://.../Something/Blah/[action]
Because the files are under /Modules/ folder, this breaks the match between the URL and the folder path which gets around my problem.
Not a great solution but does the job.
I think if you want to separate your controllers in folders, so they are not under the "controllers", you should use "areas" feature that MVC provides. It is designed for this purpose.
I've noticed that if I have a custom http module, pointed to from web.config like
<modules>
<remove name="WebDAVModule" />
<add name="CustomHttpModule" type="MyCompany.Types.CustomHttpModule" preCondition="managedHandler" />
</modules>
Then I can get code to run before DirectoryListingModule hijacks the process, but I haven't found out what to do about it when I've detected it's about to hit a physical folder.
using System.IO;
using System.Web;
using System.Linq;
using System.Linq.Expressions;
namespace MyCompany.Types
{
public class CustomHttpModule : IHttpModule
{
public void OnAcquireRequestState(object sender, EventArgs ea)
{
List<string> physicalFolders = Directory.EnumerateDirectories(AppContext.BaseDirectory).Select(f => f.Substring(1 + f.LastIndexOf('\\'))).ToList();
string projectBase = /* get from web.config */.TrimStart('/');
string possiblePhysicalFolder = application.Request.Url.AbsolutePath.TrimStart('/').Replace(projectBase, "").TrimStart('/');
if (physicalFolders.Exists(f => possiblePhysicalFolder.StartsWith(f)))
/* what to do??? */;
}
I'm trying to understand how RouteExistingFiles works.
So I've created a new MVC 3 internet project (MVC 4 behaves the same way) and put a HTMLPage.html file to the Content folder of my project.
Now I went to the Global.Asax file and edited the RegisterRoutes function so it looks like this:
public static void RegisterRoutes(RouteCollection routes)
{
routes.RouteExistingFiles = true; //Look for routes before looking if a static file exists
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new {controller = "Home", action = "Index", id = UrlParameter.Optional} // Parameter defaults
);
}
Now it should give me an error when I'm requesting a localhost:XXXX/Content/HTMLPage.html since there's no "Content" controller and the request definitely hits the default pattern. But instead I'm seeing my HTMLPage.
What am I doing wrong here?
Update:
I think I'll have to give up.
Even if I'm adding a route like this one:
routes.MapRoute("", "Content/{*anything}", new {controller = "Home", action = "Index"});
it still shows me the content of the HTMLPage.
When I request a url like ~/Content/HTMLPage I'm getting the Index page as expected, but when I add a file extenstion like .html or .txt the content is shown (or a 404 error if the file does not exist).
If anyone can check this in VS2012 please let me know what result you're getting.
Thank you.
To enabling routing for static files you must perform following steps.
In RouteConfig.cs enable routing for existing files
routes.RouteExistingFiles = true;
Add a route for your path ( Make sure specialized path are above generalized paths)
routes.MapRoute(
name: "staticFileRoute",
url: "Public/{file}/",
defaults: new { controller = "Home", action = "SomeAction" }
);
Next configure your application, so that request for static files are handeled by "TransferRequestHandler".In Webconfig under system.webServer>handlers add following entry.
<add name="MyCustomUrlHandler2" path="Public/*" verb="GET" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
The value of 'path' can be more generic or specific depending on your requirement. But i prefer it to be always very specific as per one's need. Keeping it very generic will block serving of other site specific resources such as .js or css files. For example if above is set as path="*", then request for even the css (inside the content folder) which is responsible for how your page would look will also end up in your Controller's action. Something that you will not like.
Visual Studio 2012 uses IIS Express. You need to tell IIS not to intercept requests for disk files before they are passed to the MVC routing system. You need set preCondition attribute to the empty string in config file:
<add name="UrlRoutingModule-4.0" type="System.Web.Routing.UrlRoutingModule"
preCondition="" />
In Win7/8 you can find config file on this path: %userprofile%\Documents\IISExpress\config\applicationhost.config
The RouteExistingFiles doesn't keep files from being viewed if there is no route for them, it just checks the routes before checking if the file exists. If there is no matching route, it will continue to check if there is a matching file.
I know how to return an image from the MVC controller but was wondering if returning an image in the CDN from the MVC controller will defeat the purpose of using CDN for images. Will using the following code result in the web server hosting the web application downloading from the CDN and then making it available to the user for download from the web server? Is the image downloaded twice, once from CDN to webserver and then webserver to user? If so that's worse than hosting the images directly on the webserver? Or is the image downloaded only once, directly from CDN to the end user?
How do I get to return an image from an MVC controller and at the same time take advantages of the CDN?
public ActionResult Thumb(int id)
{
//process
var file = Server.MapPath(" path to image in the CDN ");
//do your stuff
return File(file, "image/jpg", Path.GetFileName(file));
}
In your controller action you need to perform an HTTP request to fetch the image from the remote server:
public ActionResult Thumb(int id)
{
using (var client = new WebClient())
{
byte[] image = client.DownloadData("http://cdn.foo.com/myimage.jpg");
return File(image, "image/jpg");
}
}
and then:
<img src="#Url.Action("Thumb")" alt="" />
Obviously now the image is downloaded twice. Once in the controller from the CDN and once from the client. This completely defeats the purpose of this controller action and you could directly reference the image from the CDN:
<img src="http://cdn.foo.com/myimage.jpg" alt="" />
This obviously assumes that the client has access to the CDN.
Of course if your controller action does more than just fetching an image from a CDN and streaming it to the client like for example fetching the image from a CDN and resizing it, then you should definitely take the first approach.
Server.MapPath is used to map the physical path of an image, it's not going to do anything for a CDN image, because it doesn't exist locally at a physical path. And no, you don't want to return a file.
Your HTML would simply reference the CDN image in an <img> tag. ie
<img src="http://mycdn.com/image" />
Assuming you need to do some computing to figure out the resource first, like do a lookup based on some ID, to resolve the CDN url. You should absolutely not use a web client and download the image to a byte array. If you have to download the file, it essentially becomes a volatile resource and you need to decide how to deal with it, using a circuit breaker, probably with no cache, and you need to buffer it through so that it doesn't use too much memory.
In this case, you're best resolving the url and using a redirect 301/302 depending on the use case.
public ActionResult Thumb(int id)
{
//resolve url
var file = " path to image in the CDN ";
//redirect permanent
return RedirectPermanent(file);
//OR redirect
return Redirect(file);
}
Don't return the the actual image from the controller. That is worse because then you download it twice (CDN -> server -> client). You don't need a Thumb action at all.
If you need to generate the link to the file on the CDN in the controller, then just add a property to the view's model for the CDN link(s) and set it in the controller.
Create the link in your controller:
public ActionController SomeAction(int id){
var model = new SomeActionViewModel();
model.CDNLink = // do some stuff to generate CDN Link and set them on the model;
return View(model);
}
Then finally set it in your view
<img src="#Model.CDNLink" alt=""/>
I use URL Rewrite module 2.0 outboundRules.
Change response html Img(images),Link(css),Script(js) tags url.
URL Rewrite Module 2.0 Configuration Reference : URL Rewrite Module 2 : URL Rewrite Module : The Official Microsoft IIS Site
Current response
...
<img src="/images/photo1.jpg" />
<img src="/images/photo2.jpg" />
...
Set web.config urlrewrite configuration.
<rewrite>
<outboundRules>
<rule name="cdn" preCondition="html" enabled="true">
<match filterByTags="Img, Link, Script"
pattern="^/(images|csc|js)/(.*)" />
<action type="Rewrite" value="//cdn.local/{R:1}/{R:2}" />
</rule>
<preConditions>
<preCondition name="html">
<add input="{RESPONSE_CONTENT_TYPE}" pattern="text/html" />
</preCondition>
</preConditions>
</outboundRules>
</rewrite>
Rewrite response
...
<img src="//cdn.local/images/photo1.jpg" />
<img src="//cdn.local/images/photo2.jpg" />
...
Hope this help.
CDN configurations can vary, but the most common setup is that the CDN server will act as a large distributed proxy for your server (the "origin server").
The CDN doesn't care how you deliver your content. You can tell it to proxy for content by URL, by content type, or other factors.
So your situation, assuming the content, once delivered from a controller, won't change, can be set up as follows:
Request to CDN server
CDN server requests to your controller method on the origin server
Controller method generates content
CDN caches content for subsequent requests
CDN returns content
Subsequent requests for the same content by URL are returned from CDN
The most important factor in general when returning static content is the URL structure you use.
CDN does (can) care about the response headers in terms of caching and expires, but that is best avoided if you can finesse the URL correctly to provide unique content by URL (to solve versioning issues, for example).
If you are trying to secure the content, that is also possible with CDN but generally requires CDN-specific techniques.
Consider a StaticResourceController that locates and serves files.
I've set up an explicit route for "favicon.ico" that will handle the request for this file using StaticResourceController:
routes.MapRoute(
"favicon",
"favicon.ico",
new { controller = "StaticResource", action = "Get", file = "favicon.ico", area="root"},
new[] { "Dimebrain.Mvc.Controllers" }
);
In IIS6 the expected result occurs when making a request for http://localhost:8080/favicon.ico.
Unfortunately when I deploy to IIS7 http://localhost/favicon.ico returns an IIS-generated 404, presumably because it's actually looking for the favicon.ico in the web root folder, where it doesn't exist.
I have enough happening in StaticResourceController that this isn't a good thing for my application, especially since it is multi-tenant and the favicon.ico file can change. I've set my web server modules to handle every request and override the RouteCollection to disregard file checks with RouteExistingFiles.
Why is the UrlRoutingModule getting in my way in IIS7 and forcing serving the static file from disk (404)?
In case anyone else runs into this problem, the solution is you need you to let MVC know not to process requests in folders where your actual static files live:
// Make sure MVC is handling every request for static files
routes.RouteExistingFiles = true;
// Don't process routes where actual static resources live
routes.IgnoreRoute("content/{*pathInfo}");
routes.IgnoreRoute("scripts/{*pathInfo}");
routes.IgnoreRoute("areas/admin/content/{*pathInfo}");
routes.IgnoreRoute("areas/admin/scripts/{*pathInfo}");
In adiition to Daniel Crenna's answer, you need to add in web.confug file in system.webServer section:
<modules runAllManagedModulesForAllRequests="true"/>
I have a site built with MVC Preview 2 and have not got around to upgrading to latest release, mainly because of the number of changes required and I have not had time. Anyway, last night my host installed .NET 3.5 sp1 and it killed my site. It is an identified problem (thats what you get for using pre betas) on this site http://haacked.com/archive/2008/05/12/sp1-beta-and-its-effect-on-mvc.aspx and it says to go to this site for a work around http://www.asp.net/downloads/3.5-SP1/Readme/default.aspx.
Unfortunately the work around seems to have been taken down. Can anyone shed some light of what it did say and what the work arounds are.
Maybe this instructions are useful, they are to migrate the site on mvc preview 2 to mvc preview 3. As preview 3 sites are not affected by the beta sp1, I hope it helps:
Upgrading an Existing Preview2 Application to Preview 3
The information in this section describes the changes you must make to modify an ASP.NET MVC application that was created with the Preview 2 release so that it works with the Preview 3 release.
Code Changes
Update the references to the following assemblies to point to the new Preview 3 versions of the assemblies:
System.Web.Abstractions
System.Web.Routing
System.Web.Mvc
By default, these assemblies are located in the following folder:
%ProgramFiles%\Microsoft ASP.NET\ASP.NET MVC Preview 3
For all existing action methods, change the return type from void to ActionResult.
Anywhere you call RenderView, change it to a call to return View. You can search for RenderView( and replace it with return View(.
Anywhere you call RedirectToAction, prepend the call with the return keyword. Search for RedirectToAction( and replace it with return RedirectToAction(.
If you use a strongly typed page, replace <%= ViewData.PropertyName %> with <%= ViewData.Model.PropertyName %>. Rather than replacing the ViewData object with your strongly typed object, the MVC framework now sets the Model property to the instance that you provide.
In the Global.asax file, remove the route definition for Default.aspx. In the default Preview 2 template, the route looked like the following example:
routes.Add(new Route("Default.aspx", new MvcRouteHandler())
{
Defaults = new RouteValueDictionary(new { controller = "Home", action = "Index", id = "" }),
});
In the Global.asax file, find the following default MVC route:
routes.Add(new Route("{controller}/{action}/{id}", new MvcRouteHandler())
{
Defaults = new RouteValueDictionary(new { action = "Index", id = "" }),
});
Replace it with the following route:
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);
Add the following line at the very beginning of the RegisterRoutes method:
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
Edit the Default.aspx file and add the following line:
<% Response.Redirect("~/Home") %>
This redirect is not necessary for IIS 7. This is a workaround for an issue with how the Web server that is built into Visual Studio (the ASP.NET Development Server) works with routing.
Configuration Changes
In the Web.config file, you must change the type attribute of the httpHandler entry in the section for UrlRoutingHandler to System.Web.HttpForbiddenHandler.
To do this, search for the following string in the file:
path="UrlRouting.axd" type="System.Web.Routing.UrlRoutingHandler, System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"
Replace it with the following string:
path="UrlRouting.axd" type="System.Web.HttpForbiddenHandler, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
Because the version numbers of the System.Web.Abstractions and System.Web.Routing assemblies have been changed to 0.0.0.0, you must update version information in the Web.config file. In the Web.config file, search for the following string:
System.Web.Routing, Version=3.5.0.0
Replace it with the following string:
System.Web.Routing, Version=0.0.0.0
Search for the following string:
System.Web.Abstractions, Version=3.5.0.0
Replace it with the following string:
System.Web.Abstractions, Version=0.0.0.0