ASP.NET MVC: returning a CDN images from controller - asp.net-mvc

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.

Related

MVC - FileContentResult sends corrupted pdf

I have a simple action method that returns a PDF document, that gets shown in an <iframe> with an <embed> tag, and every few calls to this method will return a corrupted PDF. (I've determined its corrupted by using dev tools to save the response from the server)
Action Method:
public FileContentResult GetPdfReport(string Id)
{
Response.AppendHeader("Content-Disposition", "inline; filename=report.pdf");
var content = System.IO.File.ReadAllBytes(Server.MapPath("~/Reports/Testfile.pdf"));
System.IO.File.WriteAllBytes(Server.MapPath("~/Reports/debugReport.pdf"), content);
return File(content, "application/pdf");
}
View Content:
<embed id="widgetResponsePdf" src="#Url.Action("GetPdfReport", "WidgetResponse", new { Id = "123" })" type="application/pdf" onmouseout="mouseOutHandler();" />
The files TestFile.pdf and debugReport.pdf open just fine when I get a corrupted PDF, and there is no difference in the request and response header between the normal request/response and the corrupted request/response.
Is there some setting in IIS that I am missing that could be causing the inconsistent behavior between requests, or could this be caused solely by a network issue?
In our case, the IFrame has a src attribute that points to a partial view that loads the <embed> content, which then has a src attribute that points to the actual PDF file instead of a PartialView that returns a FileContentResult. The example below has been simplified from our actual implementation
Partial View
<iframe> <!-- iframe is actually loaded from another partial view, it is show here to simplify things -->
<embed
src='#Url.Content(Model.ReportFileName)'
type="application/pdf">
</embed>
</iframe>
Controller
public PartialView GetPdfReport(string Id)
{
var model = PDFResponseModel
{
ReportFileName = Server.MapPath("~/Reports/Testfile.pdf")
};
return PartialView(model);
}
Due to a site restriction for IE support, our users (intranet site) are required to have AdobeAcrobat installed to view PDF's. While this solution works for us, this may not be desirable for internet facing websites.

DonutOutputCache not working for an image

I have an ASP.NET MVC controller action like the following:
[HttpGet]
[DonutOutputCache(CacheProfile = "banner")]
public async Task<ActionResult> Banner(string name, string size = "lg")
{
// snipped - work out which banner to show today.
return File(thePathToSomeBannerImageFile, "image/jpeg");
}
And the configuration file looks like:
<caching>
<outputCacheSettings>
<outputCacheProfiles>
...
<add name="banner" duration="31536000" varyByParam="*" />
...
</outputCacheProfiles>
</outputCacheSettings>
</caching>
Since adding DonutOutputCache the image that I used to render to my browser now won't load. I can see in the Fiddler trace that a largish binary file is getting to the browser, but I can't seem to get it to render.
Removing the DonutOutputCache makes the image appear again.
I've tried clearing the cache etc. Any ideas?
Ah, just figured it out from this link: Doesn't work w/ Files?.
Basically DonutOutputCache uses ContentResult internally which works only with text-based content. Since I'm returning a binary response in a FileResult it messes stuff up. Fortunately it works side-by-side with the built in OutputCache, so I could modify my action as follows and it works as expected. I really only needed DonutOutputCache for other bits of my site that are text-based. I tripped myself up trying to just have one caching attribute to rule them all.
[HttpGet]
[OutputCache(CacheProfile = "banner")]
public async Task<ActionResult> Banner(string name, string size = "lg")
{
}

DiskCache plugin imageresizer in MVC3

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.

ASP.NET MVC not using controller for explicit file route in IIS7

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"/>

Why is the HttpContext.Cache count always zero?

I set up a few pages with OutputCache profiles and confirmed that they are being cached by using multiple browsers and requests to retrieve the page with a timestamp which matched across all requests. When I try to enumerate the HttpContect.Cache it is always empty.
Any ideas what is going on here or where I should be going for this information instead?
Update:
It's not client cache because multiple browsers are seeing the same response. Here is a bit of code to explain what's happening.
Web.Config caching settings
<system.web>
<caching>
<outputCacheSettings>
<outputCacheProfiles>
<clear/>
<add name="StaticContent" duration="1200" varyByParam="none"/>
<add name="VaryByParam" duration="1200" varyByParam="*"/>
</outputCacheProfiles>
</outputCacheSettings>
</caching>
...
</system.web>
**Action Method With Caching
[OutputCache(CacheProfile = "StaticContent")]
public ActionResult Index()
{
return View(new CollaborateModel());
}
Code to enumerate the cache, yep it's rough, an this is defined in a controller action method
var sb = new StringBuilder();
foreach (KeyValuePair<string, object> item in HttpContext.Cache)
{
sb.AppendFormat("{0} : {1}<br />", item.Key, item.Value.ToString());
}
ViewData.Add("CacheContents", sb.ToString());
The HttpContext.Cache is where the count is always null, even though the cache seems to be working fine.
That's probably because the page has been cached downstream on the client browser and not on the server.
Instead of using the HttpCache I ended up rolling my own caching model for holding datasets in my Data Access layer. If I was looking up the AD profile of a given username and converting it to a DTO then I just put that profile in a rolling collection of profile DTOs that I would check before polling AD for the information.

Resources