I am trying to use a custom HtmlHelper in my view to display a link, but it's getting HTML Encoded.
In my view, I'm calling my helper like this:
<td>
#Html.Urls(item.TaskUrl)
</td>
And my helper looks like this:
public static class MkpHelpers
{
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)
{
var linkBuilder = new TagBuilder("a");
linkBuilder.MergeAttribute("href",i);
linkBuilder.InnerHtml = i;
sb.Append(linkBuilder.ToString());
}
return sb.ToString();
}
}
Rendered out, it looks like this:
<a href="http://localhost:63595/project/reviewresource/99ddb0d8-238a-e511-8172-00215e466552">
http://localhost:63595/project/reviewresource/99ddb0d8-238a-e511-8172-00215e466552
</a>
I'm guessing I'm doing something wrong that should be pretty simple/obvious.
Be careful here of Injection attacks. That being said, you need to return an HtmlString:
public static class MkpHelpers
{
public static HtmlString Urls(this HtmlHelper helper, string value)
{
var items = value.Split(';'); // use your delimiter
var sb = new StringBuilder();
foreach (var i in items)
{
var linkBuilder = new TagBuilder("a");
linkBuilder.MergeAttribute("href",i);
linkBuilder.InnerHtml = i;
sb.Append(linkBuilder.ToString());
}
return new HtmlString(sb.ToString());
}
}
HtmlString derrives from IHtmlString:
Represents an HTML-encoded string that should not be encoded again.
Related
I need to use a custom format for a date (i.e. dddd dd MMMM yyyy). Is it possible to pass this format to Sitecore().Field()? I would like to do something like this:
#Html.Sitecore().Field("Day1", new { #format="dddd dd MMMM yyyy"})
However, after some Googling, I found that I either have to create a custom field helper to do this or a custom model. Is there really no way to do this using base Sitecore? It's important this be done through Sitecore().Field() as I need the content editor to be able to edit the value.
We're on Sitecore 7.5
As far I know Sitecore doesn't have such a functionality out of the box.
You can use a helper for this functionality, please check below code.
I used this code and is working fine. You can edit date field also from page editor because the field is edited through Sitecore pipelines.
public static class Helper
{
public static HtmlString RenderField(
this SC.Mvc.Helpers.SitecoreHelper sitecoreHelper,
string fieldNameOrId,
bool disableWebEdit = false,
SC.Collections.SafeDictionary<string> parameters = null)
{
if (parameters == null)
{
parameters = new SC.Collections.SafeDictionary<string>();
}
return sitecoreHelper.Field(
fieldNameOrId,
new
{
DisableWebEdit = disableWebEdit,
Parameters = parameters
});
}
public static HtmlString RenderField(
this SC.Mvc.Helpers.SitecoreHelper sitecoreHelper,
SC.Data.ID fieldId,
bool disableWebEdit = false,
SC.Collections.SafeDictionary<string> parameters = null)
{
return RenderField(
sitecoreHelper,
fieldId.ToString(),
disableWebEdit,
parameters);
}
public static HtmlString RenderDate(
this SC.Mvc.Helpers.SitecoreHelper sitecoreHelper,
string fieldNameOrId,
string format = "D",
bool disableWebEdit = false,
bool setCulture = true,
SC.Collections.SafeDictionary<string> parameters = null)
{
if (setCulture)
{
Thread.CurrentThread.CurrentUICulture =
new CultureInfo(SC.Context.Language.Name);
Thread.CurrentThread.CurrentCulture =
CultureInfo.CreateSpecificCulture(SC.Context.Language.Name);
}
if (parameters == null)
{
parameters = new SC.Collections.SafeDictionary<string>();
}
parameters["format"] = format;
return RenderField(
sitecoreHelper,
fieldNameOrId,
disableWebEdit,
parameters);
}
public static HtmlString RenderDate(
this SC.Mvc.Helpers.SitecoreHelper sitecoreHelper,
SC.Data.ID fieldId,
string format = "D",
bool disableWebEdit = false,
bool setCulture = true,
SC.Collections.SafeDictionary<string> parameters = null)
{
return RenderDate(
sitecoreHelper,
fieldId.ToString(),
format,
disableWebEdit,
setCulture,
parameters);
}
public static HtmlString TagField(
this SC.Mvc.Helpers.SitecoreHelper sitecoreHelper,
string fieldNameOrId,
string htmlElement,
bool disableWebEdit = false,
SC.Collections.SafeDictionary<string> parameters = null)
{
SC.Data.Items.Item item =
SC.Mvc.Presentation.RenderingContext.Current.ContextItem;
if (item == null || String.IsNullOrEmpty(item[fieldNameOrId]))
{
return new HtmlString(String.Empty);
}
string value = sitecoreHelper.RenderField(
fieldNameOrId,
disableWebEdit,
parameters).ToString();
return new HtmlString(String.Format(
"<{0}>{1}</{0}>",
htmlElement,
value));
}
public static HtmlString TagField(
this SC.Mvc.Helpers.SitecoreHelper sitecoreHelper,
SC.Data.ID fieldId,
string htmlElement,
bool disableWebEdit = false,
SC.Collections.SafeDictionary<string> parameters = null)
{
return TagField(
sitecoreHelper,
fieldId.ToString(),
htmlElement,
disableWebEdit,
parameters);
}
}
In your cshtml you will have:
#Html.Sitecore().RenderDate("Name of field or id", "your format")
John West write about how to extend sitecore helpers here:
http://www.sitecore.net/learn/blogs/technical-blogs/john-west-sitecore-blog/posts/2012/06/sitecore-mvc-playground-part-4-extending-the-sitecorehelper-class.aspx
You can format date using below Field render syntax which is easy and out of the box.
#Html.Sitecore().Field("Date Field", new {format="MMM dd, yyyy"})
That's it. The format value leveraged the standard date format specifications.
For those who had an issue while dealing with spaces, simply replace all the spaces with "\n".
An example here:
#Html.Sitecore().Field(datefield, new {format="MMM\ndd,\nyyyy"})
I have modified #SitecoreClimber answer, because it doesn't work for rendering children items since it uses default RenderingContext.Current
So I have updated every method arguments and added Item item and used that item for base field rendering, like this:
public static HtmlString RenderField(this SitecoreHelper sitecoreHelper, string fieldNameOrId, Item item, bool disableWebEdit = false, SafeDictionary<string> parameters = null)
{
if (parameters == null)
{
parameters = new SafeDictionary<string>();
}
return sitecoreHelper.Field(fieldNameOrId, item,
new
{
DisableWebEdit = disableWebEdit,
Parameters = parameters
});
}
So in my MVC view I can now have:
#foreach (Item item in #Model.Item.Children)
{
<div class="event-date">
#Html.Sitecore().RenderDate("Date", item, "d MMM")
</div>
}
In C#, I have a StringBuilder sb to which I am appending numerous times in for-loops.
Is there is a simple method for StringBuilders that spits out the last string that was appended to it?
Nope. You should probably use a list of strings and then later join it to replace the StringBuilder functionality like this:
List<string> strings = new List<string>();
strings.Add("...");
string result = string.Join("", strings);
Then you can access the most recently added string by accessing the last index of the string list or use the Last() LINQ extension like this:
string lastString = strings.Last();
You could create your own StringBuilder extension method that remembers the last string appended:
public static class StringBuilderExtensions
{
public static string LastStringAppended { get; private set; }
public static StringBuilder AppendRemember(this StringBuilder sb, object obj)
{
string s = obj == null ? "" : obj.ToString();
LastStringAppended = s;
return sb.Append(s);
}
}
Call it like this
sb.AppendRemember("hello").AppendRemember(" world");
string lastString = StringBuilderExtensions.LastStringAppended;
However, note that the last string will be remembered globally. It is not bound to a StringBuilder instance.
If you need to remember the last addition per StringBuilder instance, you can attach an additional "property" to the StringBuilder via a dictionary. I am using this implementation of ObjectReferenceEqualityComparer<T>. It is necessary beacuse StringBuilder overrides Equals and does not use reference equality.
public static class StringBuilderExtensions
{
private static Dictionary<StringBuilder, string> _lastStrings =
new Dictionary<StringBuilder, string>(
new ObjectReferenceEqualityComparer<StringBuilder>());
public static string LastAppended(this StringBuilder sb)
{
string s;
_lastStrings.TryGetValue(sb, out s);
return s; // s is null if not found in the dict.
}
public static StringBuilder AppendRemember(this StringBuilder sb, object obj)
{
string s = obj == null ? "" : obj.ToString();
_lastStrings[sb] = s;
return sb.Append(s);
}
}
Use it like this:
sb.AppendRemember("hello").AppendRemember(" world");
string lastString = sb.LastAppended();
As floele answered, you're probably better off just using a List and then joining it. But as Olivier Jacot-Descombes and Tim Schmelter hinted at in their commeents, you can have your own class with an internal StringBuilder, then you just have to recreate the methods from StringBuilder you want to use, and store the last appended string inside your class in a property. This has two key advantages over Olivier's answer: the remembered string isn't "global" (it's unique to each instance), and the code is many times simpler.
public class StringBuilderRemember
{
private StringBuilder sb=new StringBuilder();
public string LastString {get; private set;}
public void AppendLine(string s)
{
LastString=s;
sb.AppendLine(s);
}
public void Append(string s)
{
LastString=s;
sb.Append(s);
}
public string ToString()
{
return sb.ToString();
}
}
I know I can add html attributes to my tag by doing something like:
var htmlAttributes = new RouteValueDictionary { { "data-foo", "bar" } };
var tag = new TagBuilder("div");
tag.MergeAttributes(htmlAttributes );
#tag
Output:
<div data-foo="bar"></div>
I wonder if I can add attributes in a similar way by using markup instead of a tag builder. Maybe something like:
var htmlAttributes = new RouteValueDictionary { { "data-foo", "bar" } };
<div #htmlAttributes.ToHtmlAttributes() ></div>
Expected output:
<div data-foo="bar"></div>
Clearly, I wouldn't be able to handle merge conflicts this way. However, I think it's worth it because the second way is so much more readable.
You can write your own extension method:
namespace SomeNamespace
{
public static class RouteValueDictionaryExtensions
{
public static IHtmlString ToHtmlAttributes(this RouteValueDictionary dictionary)
{
var sb = new StringBuilder();
foreach (var kvp in dictionary)
{
sb.Append(string.Format("{0}=\"{1}\" ", kvp.Key, kvp.Value));
}
return new HtmlString(sb.ToString());
}
}
}
which will be used exactly how you've described:
#using SomeNamespace
#{
var htmlAttributes = new RouteValueDictionary
{
{"data-foo", "bar"},
{"data-bar", "foo"}
};
}
<div #htmlAttributes.ToHtmlAttributes()> </div>
the result is:
<div data-foo="bar" data-bar="foo" > </div>
Edit:
If you want to use TagBuilder, you can alternatively write another extension which uses it internally:
public static IHtmlString Tag(this HtmlHelper helper,
RouteValueDictionary dictionary,
string tagName)
{
var tag = new TagBuilder(tagName);
tag.MergeAttributes(dictionary);
return new HtmlString(tag.ToString());
}
and the usage shown below below gives the same output html as previously:
#Html.Tag(htmlAttributes, "div")
In this post I wondered about cleaner code when internationalising an app. that leads to this second query... supposing I wanted to call a function like this:
#Html.RenderWithTags("Help",
new Dictionary<string, string>() { "HelpPage", "#Html.ActionLink(...)" }
)
such that I look up a string in my local resource file containing embedded "tags" e.g. resource name "Help" contains:
We suggest you read our [HelpPage]
before proceeding
and then my .RenderWithTags() method will expand the tags but dynamically executing the code in the dictionary passed e.g. replace [HelpPage] with whatever #Html.ActionLink(...) produces.
I know I can use Microsoft.CSharp.CSharpCodeProvider().CreateCompiler() to compile C# code on the fly, but what about Razor code?
This will be rather difficult to do.
Instead, you should put delegates in your dictionary.
For example:
new Dictionary<string, Func<string>>() {
{ "HelpPage", () => Html.ActionLink(...).ToString() }
}
If you're creating the dictionary in a Razor page, you could also use inline helpers:
new Dictionary<string, Func<Something, HelperResult>>() {
{ "HelpPage", #Html.ActionLink(...) }
}
This will allow to use arbitrary Razor markup in the values.
However, I would probably recommend that you create a single global dictionary in code, so that you don't need to repeat definitions across pages. (Depending on how you use it)
in the end, the solution turned out pretty slick. would not have been possible without SLaks, I'm much obliged for the help (though I didn't end up using inline helpers (but thanks for the intro (they're very cool))).
Now my page contains this:
#{
Dictionary<string, MvcHtmlString> tokenMap = new Dictionary<string, MvcHtmlString>() {
{"HelpPage", Html.ActionLink("help page", "Help", "Home") }
};
}
and somewhere below I have:
#this.Resource("Epilogue", tokenMap)
To accomplish this simplicity:
public static class PageExtensions
{
public static MvcHtmlString Resource(this WebViewPage page, string key)
{
HttpContextBase http = page.ViewContext.HttpContext;
string ret = (string) http.GetLocalResourceObject(page.VirtualPath, key);
return MvcHtmlString.Create(ret);
}
public static MvcHtmlString Resource(
this WebViewPage page, string key,
Dictionary<string, MvcHtmlString> tokenMap
) {
HttpContextBase http = page.ViewContext.HttpContext;
string text = (string) http.GetLocalResourceObject(page.VirtualPath, key);
return new TagReplacer(text, tokenMap).ToMvcHtmlString();
}
}
...and:
public class TagReplacer
{
Dictionary<string, MvcHtmlString> tokenmap;
public string Value { get; set; }
public TagReplacer(string text, Dictionary<string, MvcHtmlString> tokenMap)
{
tokenmap = tokenMap;
Regex re = new Regex(#"\[.*?\]", RegexOptions.IgnoreCase);
Value = re.Replace(text, new MatchEvaluator(this.Replacer));
}
public string Replacer(Match m)
{
return tokenmap[m.Value.RemoveSet("[]")].ToString();
}
public MvcHtmlString ToMvcHtmlString()
{
return MvcHtmlString.Create(Value);
}
}
...with a little extra help:
public static class ObjectExtensions
{
public static string ReplaceSet(this string text, string set, string x)
{
for (int i = 0; i < set.Length; i++)
{
text = text.Replace(set[i].ToString(), x);
}
return text;
}
public static string RemoveSet(this string text, string set)
{
return text.ReplaceSet(set, "");
}
}
comments or feedback on how it could have been better most welcome!
I was wondering if anyone knows if it possible to use any of the "out of the box" ASP.NET MVC3 helpers to generate a "link button"...I currently use following:
<a class="button" title="My Action" href="#Url.Action("MyAction", "MyController", new { id = item.Id })">
<img alt="My Action" src="#Url.Content("~/Content/Images/MyLinkImage.png")" />
</a>
I am trying to avoid using MvcFutures, but even if I was able to use them, I don't think there is a extension method it there that will accomplish this either. (I believe solution in this case would be to roll custom helper as seen here)
Finally, this post also has a good idea to handle this via CSS, but that is not what I am asking...
I am using the following to generate action links:
using System;
using System.Linq.Expressions;
using System.Text;
using System.Web.Mvc;
using System.Web.Mvc.Html;
using System.Web.Routing;
using Fasterflect;
namespace StackOverflow.Mvc.Extensions
{
public static class HtmlExtensions
{
#region ActionImage
// href image link
public static string ActionImage( this HtmlHelper helper, string href, string linkText, object htmlAttributes,
string alternateText, string imageSrc, object imageAttributes )
{
var sb = new StringBuilder();
const string format = "<a href=\"{0}\"{1}>{2}</a>";
string image = helper.Image( imageSrc, alternateText, imageAttributes ).ToString();
string content = string.IsNullOrWhiteSpace( linkText ) ? image : image + linkText;
sb.AppendFormat( format, href, GetAttributeString( htmlAttributes ), content );
return sb.ToString();
}
// controller/action image link
public static string ActionImage( this HtmlHelper helper, string controller, string action, string linkText, object htmlAttributes,
string alternateText, string imageSrc, object imageAttributes )
{
bool isDefaultAction = string.IsNullOrEmpty( action ) || action == "Index";
string href = "/" + (controller ?? "Home") + (isDefaultAction ? string.Empty : "/" + action);
return ActionImage( helper, href, linkText, htmlAttributes, alternateText, imageSrc, imageAttributes );
}
// T4MVC ActionResult image link
public static string ActionImage( this HtmlHelper helper, ActionResult actionResult, string linkText, object htmlAttributes,
string alternateText, string imageSrc, object imageAttributes )
{
var controller = (string) actionResult.GetPropertyValue( "Controller" );
var action = (string) actionResult.GetPropertyValue( "Action" );
return ActionImage( helper, controller, action, linkText, htmlAttributes, alternateText, imageSrc, imageAttributes );
}
#endregion
#region Helpers
private static string GetAttributeString( object htmlAttributes )
{
if( htmlAttributes == null )
{
return string.Empty;
}
const string format = " {0}=\"{1}\"";
var sb = new StringBuilder();
htmlAttributes.GetType().Properties().ForEach( p => sb.AppendFormat( format, p.Name, p.Get( htmlAttributes ) ) );
return sb.ToString();
}
#endregion
}
}
Note that the GetAttributeString method relies on the Fasterflect library to make reflection tasks easier, but you can replace that with regular reflection if you prefer not to take the additional dependency.
The Image helper extension used to be part of MvcContrib but appears to have been removed, most likely because the functionality is now built in to MVC. Regardless, I've included it below for completeness:
public static class ImageExtensions {
public static MvcHtmlString Image(this HtmlHelper helper, string imageRelativeUrl, string alt, object htmlAttributes) {
return Image(helper, imageRelativeUrl, alt, new RouteValueDictionary(htmlAttributes));
}
public static MvcHtmlString Image(this HtmlHelper helper, string imageRelativeUrl, string alt, IDictionary<string, object> htmlAttributes) {
if (String.IsNullOrEmpty(imageRelativeUrl)) {
throw new ArgumentException(MvcResources.Common_NullOrEmpty, "imageRelativeUrl");
}
string imageUrl = UrlHelper.GenerateContentUrl(imageRelativeUrl, helper.ViewContext.HttpContext);
return MvcHtmlString.Create(Image(imageUrl, alt, htmlAttributes).ToString(TagRenderMode.SelfClosing));
}
public static TagBuilder Image(string imageUrl, string alt, IDictionary<string, object> htmlAttributes) {
if (String.IsNullOrEmpty(imageUrl)) {
throw new ArgumentException(MvcResources.Common_NullOrEmpty, "imageUrl");
}
TagBuilder imageTag = new TagBuilder("img");
if (!String.IsNullOrEmpty(imageUrl)) {
imageTag.MergeAttribute("src", imageUrl);
}
if (!String.IsNullOrEmpty(alt)) {
imageTag.MergeAttribute("alt", alt);
}
imageTag.MergeAttributes(htmlAttributes, true);
if (imageTag.Attributes.ContainsKey("alt") && !imageTag.Attributes.ContainsKey("title")) {
imageTag.MergeAttribute("title", (imageTag.Attributes["alt"] ?? "").ToString());
}
return imageTag;
}
}
The snippet you have looks quite good. You should wrap it in a general-purpose html helper and call it a day. I'm sure there are other more interesting aspects to your application than nit picking about UI helpers :)
Check at the bottom of this blog post for an example with HTML extension methods from Stephen Walther