I have a model with a text field. The text can contain several URLs. It does not have to contain URLs and it does not have a specific format.
Using
@Html.DisplayFor(model => model.TextWithSomeUrls)
the text and the URLs are displayed like normal text of course. I would like to get the URLs displayed as working individual links though. Is there a helper method for this in ASP.NET / Razor?
Edit: Right now the output is:
http://www.google.com, foo: bar; http://www.yahoo.com
Which is exactly the content of the text field.
But I want to get the URLs and only the URLs rendered as links like this:
<a href="http://www.google.com">http://www.google.com</a>, foo: bar; <a href="http://www.yahoo.com">http://www.yahoo.com</a>
My solution:
public static partial class HtmlExtensions
{
private const string urlRegEx = @"((http|ftp|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&:/~\+#]*[\w\-\@?^=%&/~\+#])?)";
public static MvcHtmlString DisplayWithLinksFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
{
string content = GetContent<TModel, TProperty>(htmlHelper, expression);
string result = ReplaceUrlsWithLinks(content);
return MvcHtmlString.Create(result);
}
private static string ReplaceUrlsWithLinks(string input)
{
Regex rx = new Regex(urlRegEx);
string result = rx.Replace(input, delegate(Match match)
{
string url = match.ToString();
return String.Format("<a href=\"{0}\">{0}</a>", url);
});
return result;
}
private static string GetContent<TModel, TProperty>(HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
{
Func<TModel, TProperty> func = expression.Compile();
return func(htmlHelper.ViewData.Model).ToString();
}
}
This extension can now be used in views:
@Html.DisplayWithLinksFor(model => model.FooBar)
There is no helper like that, but you can create your own custom helper or create a template for DisplayFor helper, which will contain logic you need.
I had some issues with the solution:
- It did not work for hostnames without dots, like localhost or any other LAN-URL
- It did not work for URLs with spaces (minor issue)
- It did not encode all the rest of my data. So if there is "< !--" in the database, the page would become truncated.
- The URI is not escaped
I used the code from above, extended it a bit and ended up with this:
private static readonly Regex urlRegEx = new Regex(@"(?<!="")((http|ftp|https|file):\/\/[\d\w\-_]+(\.[\w\-_]+)*([\w\-\.,@?^=%&:/~\+#]*[\w\-\@?^=%&/~\+#])?)");
private static readonly Regex quotedUrlRegEx = new Regex(@"(?<!=)([""']|"|')((http|ftp|https|file):\/\/[\d\w\-_]+(\.[\w\-_]+)*([\w\-\.,@?^=%&:/~\+# ])*)\1");
public static MvcHtmlString DisplayWithLinksFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression,
string templateName = null)
{
var encodedHTML = htmlHelper.DisplayFor(expression, templateName);
return MvcHtmlString.Create(ReplaceUrlsWithLinks(encodedHTML.ToHtmlString()));
}
private static string ReplaceUrlsWithLinks(string input)
{
input = input.Replace(@"\\", @"file://").Replace('\\', '/');
var result = quotedUrlRegEx.Replace(input, delegate(Match match)
{
string url = match.Groups[2].Value;
return String.Format("<a href=\"{0}\">{1}</a>", Uri.EscapeUriString(url), ShortenURL(url));
});
return urlRegEx.Replace(result, delegate(Match match)
{
string url = match.ToString();
return String.Format("<a href=\"{0}\">{1}</a>", Uri.EscapeUriString(url), ShortenURL(url));
});
}
private static string ShortenURL(string url)
{
url = url.Substring(url.IndexOf("//", StringComparison.Ordinal) + 2);
if (url.Length < 60)
return url;
var host = url.Substring(0, url.IndexOf("/", StringComparison.Ordinal));
return host + "/…";
}
Obviously not 100% tested for all URL schemes, but seems to work alright.
Example Input:
"\\02lanpc\abc\def\Bugs in the database.docx"
http://localhost:81/applications/2/?releaseNumber=1.1&buildNumber=2
Output:
<a href="file://02lanpc/abc/def/Bugs%20in%20the%20database.docx">02lanpc/abc/def/Bugs in the database.docx</a>
<a href="http://localhost:81/applications/2/?releaseNumber=1.1&buildNumber=2">localhost:81/…</a>
Try to write your own Html Helper, like the following.
public static string Urls(this HtmlHelper helper, string value)
{
var items = value.Split(';'); // use your delimiter
var sb = new StringBuilder();
foreach(var i in items)
{
if(IsUrl(i)) // write a static method that checks if the value is a valid url
sb.Append("<a href=\"" + i + "\">" + i + "</a>,");
else
sb.Append(i + ",");
}
return sb.ToString();
}
And use like that
@Html.Urls(myValue)
You may use @Html.Action(actionName)
if text contains mvc URL.