How to I replace URLs in rendered HTML using an ASP.NET MVC ActionFilter
I'm trying to create an ActionFilter to replace some text in my HTML. Basically when the server is using SSL I want to replace references to my CDN (http://cdn.e开发者_如何学编程xample.com) with references directly to my server (https://www.example.com). So the structure is something like this (I assume OnResultExecuted is where I should start):
public class CdnSslAttribute : ActionFilterAttribute
{
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
if(filterContext.HttpContext.Request.IsSecureConnection)
{
// when the connection is secure,
// somehow replace all instances of http://cdn.example.com
// with https://www.example.com
}
}
}
This would be used in my secure controllers:
[CdnSsl]
public class SecureController : Controller
{
}
The reason I want to do this is my CDN doesn't support SSL. And there are references in the Master pages to CDN resources. Example:
<link href="http://cdn.example.com/Content/base.css" rel="stylesheet" type="text/css" />
I ended up using a variation on this blog post:
http://arranmaclean.wordpress.com/2010/08/10/minify-html-with-net-mvc-actionfilter/
with my own filter:
public class CdnSslAttribute : ActionFilterAttribute
{
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
if (filterContext.HttpContext.Request.IsSecureConnection)
{
var response = filterContext.HttpContext.Response;
response.Filter = new CdnSslFilter(response.Filter);
}
}
}
Then the filter looks like this (some code omitted for brevity):
public class CdnSslFilter : Stream
{
private Stream _shrink;
private Func<string, string> _filter;
public CdnSslFilter(Stream shrink)
{
_shrink = shrink;
_filter = s => Regex.Replace(s,@"http://cdn\.","https://www.", RegexOptions.IgnoreCase);
}
//overridden functions omitted for clarity. See above blog post.
public override void Write(byte[] buffer, int offset, int count)
{
// capture the data and convert to string
byte[] data = new byte[count];
Buffer.BlockCopy(buffer, offset, data, 0, count);
string s = Encoding.Default.GetString(buffer);
// filter the string
s = _filter(s);
// write the data to stream
byte[] outdata = Encoding.Default.GetBytes(s);
_shrink.Write(outdata, 0, outdata.GetLength(0));
}
}
I don't know but the answer from @Haacked at this question could help.
Performing replacements on the generated output inside of an action filter would be a bit complicated.
An easier approach (if you can edit your master pages) would be to write a new Html helper method (similar to the Html.Content()
helper) that would conditionally emit the correct url. If you wanted that replacement to happen only for certain controllers then you could still have an action filter, but all it would do would be to set some flag in Request.Items
and your helper could check that flag.
My suggestion is to follow @marcind's approach, one possibility is to use a custom extension method to generate the correct url depending on the current url scheme.
public static MvcHtmlString CdnActionLink(this HtmlHelper helper, string linkText, string actionName, string controllerName)
{
if(helper.ViewContext.HttpContext.Request.IsSecureConnection)
{
return helper.ActionLink(linkText, actionName, controllerName, "https", "www.yourhost.com"...);
}
return helper.ActionLink(linkText, actionName, controllerName);
}
One drawback of this approach is that you'll need to replace all your current ActionLink
invocations in your views (or at least the ones you need) with an invocation to this extension method.
精彩评论