CDN for Images in ASP.NET
I am in the process of moving all of the images in my web application over to a CDN but I want to easily be able to switch the CDN on or off without having to hard code the path to the images.
My first thought was to add an HttpHandler for image extensions that depending whether a variable in the web.config (something like ) will serve the image from the server or from the CDN. But after giving this a little though I think I've essentially ruled this out as it will cause ASP.NET to handle the request for every single image, thus adding overhead, and it might actually completely mitigate the benefits of using a CDN.
An alternative approach is, since all of my pages inherit from a base pag开发者_Python百科e class, I could create a function in the base class that determines what path to serve the files from based off the web.config variable. I would then do something like this in the markup:
<img src='<%= GetImagePath()/image.png' />
I think this is probably what I'll have to end up doing, but it seems a little clunky to me. I also envision problems with the old .NET error of not being able to modify the control collection because of the "<%=" though the "<%#" solution will probably work.
Any thoughts or ideas on how to implement this?
You've dismissed writing an HttpHandler
based on an assumption of pre-optimization. I would revisit this and definitely write a simple HttpHandler
and test it out. You might find that your Page method solution might even be slower, especially if you get the ASP preprocessor involved.
HttpHandlers are pretty close to the metal - it's a miniscule amount of overhead for IIS to hand the request to ASP.Net. It would be a more elegant solution than what you're proposing, and probably more scalable and I'm willing to bet - faster.
Have you considered a slightly simpler approach?
If your pages all inherit from a base class, you could expose a property on that which contains the prepend URL to your CDN (or, to your local server if you want to switch the CDN off). It is then a trivial matter of storing the prepend URL in the web.config:
public string PrependURLPath() {
get { return ConfigurationManager.AppSettings["ImagePrependURL"].ToString(); }
}
In your <appSettings/>
element, you can simply choose what the prepend URL would be, eg:
http://my.cdn.com/user/
or:
http://my.own.server.com/images/
Pretty simple!
You would then be able to code your image refernces as per your example, but calling your base page property to expose the desired path:
<img src='<%= this.BasePage.PrependURLPath() + [YourImagePath.png] %>'/>
I agree that setting the image source through the inline call is messy, but you could probably do as someone else has suggested and then iterate through the image controls on your page, changing the prepend URL as you go.
Even if your pages currently only inherit from System.Web.UI.Page, it's a simple matter to create your own base class which inherits System.Web.Page, then do a find/replace in your solution on all remaining pages.
Hope this helps.
weighing in pretty late here, but i've been looking for a similar solution myself. searched google to sanity check what i had done. didn't consider the HttpHandler
approach, what i did was simply extend the ASP.net Image
control:
public class Img : Image
{
public Img()
{
RelativePath = false;
}
public bool RelativePath { get; set; }
public override string ImageUrl
{
get
{
if (RelativePath)
return base.ImageUrl;
return "http://some.configurable-value.com" + base.ImageUrl;
}
set { base.ImageUrl = value; }
}
}
it's rough and ready, but it works :) obviously it should rely on some configurable value rather than a string literal, but that's not a big change
If you display your images using tags you could create a control adapter, these allow you to alter the way .net controls render or universally alter them something like this should do the trick:
using System.Web.UI.WebControls.Adapters;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace ExampleCode
{
public class ImageAdapter : WebControlAdapter
{
private bool UseCdn
{
get { return true; } // Get value from config or anywhere else
}
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
Image image = (Image)Control;
if (UseCdn)
{
// If using relative urls for images may need to handle ~
image.ImageUrl = String.Format("{0}/{1}", "CDN URL", image.ImageUrl);
}
}
}
}
Then add a browser file to the App_Browsers folder in your web project like below:
<browsers>
<browser refID="Default">
<controlAdapters>
<adapter
controlType="System.Web.UI.WebControls.Image"
adapterType="ExampleCode.ImageAdapter"
/>
</controlAdapters>
</browser>
</browsers>
you could loop all controls and change the images url in the prerender event on your base class...
The good thing about the HTTP Handler approach is that it's quite re-usable and configurable: you can identify img paths to handle based on location - assuming the structure they're in helps this.
The possible drawback is that image file extensions (.jpg, .png, etc) aren't automatically passed on to the asp.net pipe-line; you can easily config IIS to do so - but you need to have a certain level of contriol over IIS - so it might not be an option if you're on a shared hosting environment.
I will go for @Rhys approach for image control.
Most of the time, I try to use background image css than using image control.
After that I upload both css and images together to the cloud and working fine for relative path.
Doesn't look like there has been an accepted answer yet so here is my suggestion. I had similar problems dealing with modifying URL's transparently (to a different end, but I thought about using it for CDN support as well).
This is an old filter / module but it worked well for my needs with a little tuning: http://www.paraesthesia.com/archive/2007/12/14/urlabsolutifiermodule---convert-urls-in-asp.net-output-to-absolute.aspx
What you can do is make a response filter and hook it with an httpmodule (as this absolutifier does). If you use this module + response filter you could probably achieve what you need by modifying the source for it to replace the hostname / prefix all urls to use the CDN.
I had to solve your problem and another one, that is I do not want to take resources from the CDN during development but only when the website is deployed on the production server. To solve this I developed an ExpressionBuilder that prepends the CDN URL only in production.
<asp:Image ImageUrl="<%$ CdnUrl:/images/myimage.png %>" runat="server" />
In previous code the CDN URL will be prepended only in production.
namespace IdeaR.Web.Compilation
{
[ExpressionPrefix("CdnUrl")]
public class CdnUrlExpressionBuilder : ExpressionBuilder
{
public static object GetCdnUrl(string expression, Type target, string entry)
{
var retvalue = expression;
var productionUri = new Uri("http://www.myproductionurl.com",
UriKind.Absolute);
var currentUri = HttpContext.Current.Request.Url;
var cdnUrl = "http://cdn.mycdn.com";
// If this is a production website URL
if (currentUri.Scheme == productionUri.Scheme &&
currentUri.Host == productionUri.Host)
retvalue = cdnUrl + expression;
return retvalue;
}
public override CodeExpression GetCodeExpression(BoundPropertyEntry entry,
object parsedData, ExpressionBuilderContext context)
{
var componentType = entry.DeclaringType;
var expressionArray = new CodeExpression[3]
{
new CodePrimitiveExpression(entry.Expression.Trim()),
new CodeTypeOfExpression(componentType),
new CodePrimitiveExpression(entry.Name)
};
var descriptor = TypeDescriptor.GetProperties(componentType)
[entry.PropertyInfo.Name];
return new CodeCastExpression(descriptor.PropertyType,
new CodeMethodInvokeExpression(
new CodeTypeReferenceExpression(GetType()),
"GetCdnUrl", expressionArray));
}
}
}
For more information I wrote an article on this How to use a CDN in production but not during development
精彩评论