I am getting a string which already has a thymeleaf tags as below:
String html = "<span th:text="${fisrtText}"></span> Indicative Terms for a <span th:text="${currency}"></span><span th:text="${amount}"></span>M <span th:text="${type}"></span> Facility";
I am setting the above string to context variable as:
context.setVariable("topSection", html);
I am setting the context variable with values to be used to replace tags in above string:
org.thymeleaf.context.Context context = new org.thymeleaf.context.Context();
context.setVariable("fisrtText", "This is fisrt Text");
context.setVariable("currency", "$");
context.setVariable("amount", 256.10);
context.setVariable("type", "Loan");
Now in template.html I am trying to get it as below:
<span th:utext="#{__${topSection}__}"></span>
I am expecting to get the html string to be replaced with values available in context. But its returning the same html as it is without any processing:
<span th:text="${fisrtText}"></span> Indicative Terms for a <span th:text="${currency}"></span><span th:text="${amount}"></span>M <span th:text="${type}"></span> Facility"
Any help will be appreciated.
Better to use multiple template Engine bean one for String and other one for resource HTML files.
1) For resource File
#Bean(name ="templateEngine")
public SpringTemplateEngine getTemplateEngine() {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(new ClassLoaderTemplateResolver(););
templateEngine.setMessageSource(messageSource);
templateEngine.setTemplateEngineMessageSource(messageSource);
return templateEngine;
}
You can set prefix and suffix for ClassLoaderTemplateResolver.
2) String Template resolver :
#Bean(name ="stringTemplateEngine")
public SpringTemplateEngine getTemplateEngine() {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(new StringTemplateResolver(););
return templateEngine;
}
Now first parse string variable with thymeleaf tag using stringTemplateEngine.
String html = "<span th:text="${fisrtText}"></span>";
String parsedHtml = stringTemplateEngine.process(html,context);
Now put ParsedHtml in context.
context.setVariable("topSection", parsedHtml);
then As #holmis83 suggested directly access you variable in template
<span th:utext="${topSection}"></span>
#{...} is a link URL expression. I think you want to use a variable expression ${...}.
Your code adjusted:
<span th:utext="${__${topSection}__}"></span>
Note that ${topSection} must evaluate to an expression, it can't be arbitrary Thymeleaf markup (with th:text for example).
Related
I've looked into this question and found questions such as:
SO-Link1
SO-Link2
So the problem seems rather common - but i really don't like the solution.
Question:
I've started digging into sources but couldn't find a convenient solution yet. Is there a built in solution to this by now using attributes?
Having to use dynamic parameters in place instead of using attributes on the model is very inconvenient.
Guess i'll see what i can achieve by modifying the ModelBinder in the meantime - there has to be some way i guess.
Update:
Why do i want to do this?
I want to reduce network traffic because properties in code may be long + nested. Imagine 200 Checkboxes x 40 bytes generated names.
I can already make my modelbinder work with aliases - however in order to fully automate it, i need the TextBoxFor etc methods to use alias names instead of the actual property names.
Personally I don't think you should be totally concerned with the size of the the name attribute's values. As long as you're using compression through something like IIS then you aren't going to be saving that much.
You can however, achieve what you're after by creating custom HTML helpers which will create the HTML markup you desire.
Example Model
public class UserModel
{
public string FullName { get; set; }
}
Helper
public static MvcHtmlString CustomTextBoxFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, string name)
{
var fieldName = ExpressionHelper.GetExpressionText(expression);
//
// Pass in alias or call method to get alias here
//
var fullBindingName = String.IsNullOrWhiteSpace(name) ? html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(fieldName) : name;
var fieldId = TagBuilder.CreateSanitizedId(fullBindingName);
var metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
var value = metadata.Model;
var tag = new TagBuilder("input");
tag.Attributes.Add("name", fullBindingName);
tag.Attributes.Add("id", fieldId);
tag.Attributes.Add("type", "text");
tag.Attributes.Add("value", value == null ? "" : value.ToString());
var validationAttributes = html.GetUnobtrusiveValidationAttributes(fullBindingName, metadata);
foreach (var key in validationAttributes.Keys)
{
tag.Attributes.Add(key, validationAttributes[key].ToString());
}
return new MvcHtmlString(tag.ToString(TagRenderMode.SelfClosing));
}
Call the Method in your View
#Html.TextBoxFor(x => x.FullName)
#Html.CustomTextBoxFor(x => x.FullName, "FName")
Output
<input id="FullName" type="text" value="Heymega" name="FullName">
<input id="FName" type="text" value="Heymega" name="FName">
I have a razor view where I'm currently using code that looks something like this:
#if(Model.IsLink)
{
<a href="...">
}
Some text that needs to appear
#if(Model.IsLink)
{
</a>
}
This works but the code doesn't feel clean. Is there a better/more accepted way of accomplishing this pattern?
Here is a simple method I think is a little cleaner.
Set the text to a variable at the top of the view:
#{
var someText = "Some text that must appear";
}
Then output conditionally:
#if (Model.IsLink)
{
<a href='#'>#someText </a>
}
else
{
#someText
}
The multi-line if statement above avoids doing string construction with HTML, but if you want to condense the syntax down to one line you can do this.
#Html.Raw(Model.IsLink?String.Format("<a href='#'>{0}</a>",someText):someText)
You could create a custom HtmlHelper method. See the reference on how to do that here: http://www.asp.net/mvc/tutorials/older-versions/views/creating-custom-html-helpers-cs
Your method could take the "IsLink" boolean as a parameter, and use it to either output an anchor or plain text. Here's a sample of what that might look like:
namespace MvcApplication1.Helpers
{
public static class LabelExtensions
{
public static string LinkableText(this HtmlHelper helper, bool isLink, string text, string url = null)
{
return isLink ? string.Format("<a href='{0}'>{1}</a>", url, text) : text;
}
}
}
In HTML 5 it is valid to have markup like this if you do not have a link
<a>some text</a>
and markup like this if you do
some text
And as of razor v2 your null conditional attributes take care of the href, so your code can be like this.
some text
If #link is null the href attribute will be omitted.
Say I have a action method.......
public ActionResult DisplayXml(int viewId, Dictionary<string,string> parameter, string dataFormat )
{
string xml = "";
return Content(xml, "text/xml");
}
and in view I did.......
<iframe title="Xml" class="ResultDisplay"
src = "#Url.Action("DisplayXml", "OutputData", new {viewId = Model.ViewId, parameter = Model.Parameter, dataFormat = Model.DataFormat })">
</iframe>
Here parameter is a dictionary and I am getting null.
How I can send it?????
You're trying to pass and arbitrary dictionary as a parameter in a querystring?
Its a pretty unusual requirement to need to serialize the contents of a dictionary to a query string parameter and back again. When generating querystring parameters, MVC will just call .ToString() on the values, and the Dictionary<,> object just uses the default implementation (which returns it's type)
Since this requirement is so uncommon, there's nothing built in to do this.. You can quite easily serialize the dictionary yourself to a string (perhaps json?) and then, change the parameter variable in your action to be a string. You'll have to deserialize the value back to a dictionary after that.
Before I provide much more of an example, I want to check you're absolutely sure this is what you want to do
Update:
Here is way of doing that (requires json.net):
public ActionResult DisplayXml(int viewId, string parameterJson, string dataFormat )
{
var parameter = JsonConvert.DeserializeObject<Dictionary<string,string>>(parameterJson);
string xml = "";
return Content(xml, "text/xml");
}
And use:
<iframe title="Xml" class="ResultDisplay"
src = "#Url.Action("DisplayXml", "OutputData", new {viewId = Model.ViewId, parameterJson = Newtonsoft.Json.JsonConvert.SerializeObject(Model.Parameter), dataFormat = Model.DataFormat })">
</iframe>
I've got some data, and sometimes that data is blank. Instead of making a bunch of crazy logic on my view I'd rather use a helper method that will render the data if it exists, and render some HTML that just says "N/A" when the string is empty/null.
Ideal syntax: #Helpers.RenderThisOrThat(Model.CustomerPhone)
If the Model.CustomerPhone (a string) is empty it will render this alternate HTML instead: <span class='muted'>N/A</span>
Here's what we have so far:
#helper RenderThisOrThat(string stringToRender, string methodToGetAlternateText = null)
{
#RenderThisOrThat(MvcHtmlString.Create(stringToRender), methodToGetAlternateText)
}
#helper RenderThisOrThat(MvcHtmlString stringToRender, string methodToGetAlternateText = null)
{
if (string.IsNullOrWhiteSpace(stringToRender.ToHtmlString()))
{
if (!string.IsNullOrWhiteSpace(methodToGetAlternateText)) {
#methodToGetAlternateText
}
<span class='muted'>N/A</span>
}
#stringToRender
}
This works just fine until we want to pass something other than a string into either parameter. For example when we have an email address we want it to be a link to that email and not just the string of the email.
#Helpers.RenderThisOrThat(##Html.DisplayFor(m => m.CustomerEmail))
It gives us the error: "Cannot convert lambda expression to type 'string' because it is not a delegate type"
We are at a loss for how to make this work... any help here?
You're looking for a helpers that will take a string and:
If the string is not empty, render that string.
If the string is not empty, render a given template.
If the string is empty, render "N/A" html.
If the string is empty, render a given template.
When passing a razor block to a function as a parameter, razor packages the block as Func. Change the parameters in the helper functions to take that type of delegate and don't forget to call those delegates (I chose to pass null).
These helpers should handle those scenarios.
Solution
#helper RenderThisOrThat(string stringToRender, Func<object, IHtmlString> leftTemplate = null, Func<object, IHtmlString> rightTemplate = null)
{
var shouldRenderLeft = !string.IsNullOrWhiteSpace(stringToRender);
leftTemplate = leftTemplate ?? (o => MvcHtmlString.Create(stringToRender));
#RenderThisOrThat(shouldRenderLeft, leftTemplate, rightTemplate)
}
#helper RenderThisOrThat(bool shouldRenderLeft, Func<object, IHtmlString> leftTemplate, Func<object, IHtmlString> rightTemplate = null)
{
var shouldRenderRight = !shouldRenderLeft;
if (shouldRenderRight)
{
if (rightTemplate != null)
{
#rightTemplate(null)
}
else
{
<span class='muted'>N/A</span>
}
}
else
{
#leftTemplate(null)
}
}
Examples
1. #Helpers.RenderThisOrThat(Model.StringWithBob)
2. #Helpers.RenderThisOrThat(Model.StringWithNull)
3. #Helpers.RenderThisOrThat(Model.StringWithBob, #<span>I'm #Model.StringWithBob</span>)
4. #Helpers.RenderThisOrThat(Model.StringWithNull, #<span>I'm #Model.StringWithBob</span>)
5. #Helpers.RenderThisOrThat(Model.StringWithBob, #<span>I'm #Model.StringWithBob</span>, #<span>What about Bob?</span>)
6. #Helpers.RenderThisOrThat(Model.StringWithNull, #<span>I'm #Model.StringWithBob</span>, #<span>What about Bob?</span>)
Will output:
Bob
<span class='muted'>N/A</span>
<span>I'm Bob</span>
<span class='muted'>N/A</span>
<span>I'm Bob</span>
<span>What about Bob?</span>
This is an awfully complex solution to a simple problem. You don't need to create complex views, in fact, you should be using an Editor/DisplayTemplate, then you put your logic in the template and it's done once, without all the need for extra inclusion of helper functions, or anything else.
You can also go a step further here, because in this case you're rendering an email address. You apply a DataType attribute to your model and then specify an Phone Number rendering type.
public class MyModel {
[DataType(DataType.PhoneNumber)]
public string PhoneNumber {get;set;}
}
Then you create a folder in ~/Views/Shared called DisplayTemplates and in that folder create a file called PhoneNumber.cshtml, in it you do this:
#model string
#if (string.IsEmptyOrWhiteSpace(Model)) {
#:<span class='muted'>N/A</span>
} else {
#: <span>#Model</span>
}
Then, in your view:
#Html.DisplayFor(x => x.PhoneNumber)
That's it, no complex logic in your view, no convluted helper functions everywhere. This is simple, easy, and maintainable. You can do the same for Email address as there is an EmailAddress datatype as well.
MVC has a lot of very good functionality built-in that most people simply do not use, because they haven't spent any real time learning it.
I have a textarea control that accepts input. I am trying to later render that text to a view by simply using:
#Model.CommentText
This is properly encoding any values. However, I want to replace the line break characters with <br /> and I can't find a way to make sure that the new br tags don't get encoded. I have tried using HtmlString but haven't had any luck yet.
Use the CSS white-space property instead of opening yourself up to XSS vulnerabilities!
<span style="white-space: pre-line">#Model.CommentText</span>
Try the following:
#MvcHtmlString.Create(Model.CommentText.Replace(Environment.NewLine, "<br />"))
Update:
According to marcind's comment on this related question, the ASP.NET MVC team is looking to implement something similar to the <%: and <%= for the Razor view engine.
Update 2:
We can turn any question about HTML encoding into a discussion on harmful user inputs, but enough of that already exists.
Anyway, take care of potential harmful user input.
#MvcHtmlString.Create(Html.Encode(Model.CommentText).Replace(Environment.NewLine, "<br />"))
Update 3 (Asp.Net MVC 3):
#Html.Raw(Html.Encode(Model.CommentText).Replace("\n", "<br />"))
Split on newlines (environment agnostic) and print regularly -- no need to worry about encoding or xss:
#if (!string.IsNullOrWhiteSpace(text))
{
var lines = text.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
foreach (var line in lines)
{
<p>#line</p>
}
}
(remove empty entries is optional)
Omar's third solution as an HTML Helper would be:
public static IHtmlString FormatNewLines(this HtmlHelper helper, string input)
{
return helper.Raw(helper.Encode(input).Replace("\n", "<br />"));
}
Applying the DRY principle to Omar's solution, here's an HTML Helper extension:
using System.Web.Mvc;
using System.Text.RegularExpressions;
namespace System.Web.Mvc.Html {
public static class MyHtmlHelpers {
public static MvcHtmlString EncodedReplace(this HtmlHelper helper, string input, string pattern, string replacement) {
return new MvcHtmlString(Regex.Replace(helper.Encode(input), pattern, replacement));
}
}
}
Usage (with improved regex):
#Html.EncodedReplace(Model.CommentText, "[\n\r]+", "<br />")
This also has the added benefit of putting less onus on the Razor View developer to ensure security from XSS vulnerabilities.
My concern with Jacob's solution is that rendering the line breaks with CSS breaks the HTML semantics.
I needed to break some text into paragraphs ("p" tags), so I created a simple helper using some of the recommendations in previous answers (thank you guys).
public static MvcHtmlString ToParagraphs(this HtmlHelper html, string value)
{
value = html.Encode(value).Replace("\r", String.Empty);
var arr = value.Split('\n').Where(a => a.Trim() != string.Empty);
var htmlStr = "<p>" + String.Join("</p><p>", arr) + "</p>";
return MvcHtmlString.Create(htmlStr);
}
Usage:
#Html.ToParagraphs(Model.Comments)
I prefer this method as it doesn't require manually emitting markup. I use this because I'm rendering Razor Pages to strings and sending them out via email, which is an environment where the white-space styling won't always work.
public static IHtmlContent RenderNewlines<TModel>(this IHtmlHelper<TModel> html, string content)
{
if (string.IsNullOrEmpty(content) || html is null)
{
return null;
}
TagBuilder brTag = new TagBuilder("br");
IHtmlContent br = brTag.RenderSelfClosingTag();
HtmlContentBuilder htmlContent = new HtmlContentBuilder();
// JAS: On the off chance a browser is using LF instead of CRLF we strip out CR before splitting on LF.
string lfContent = content.Replace("\r", string.Empty, StringComparison.InvariantCulture);
string[] lines = lfContent.Split('\n', StringSplitOptions.None);
foreach(string line in lines)
{
_ = htmlContent.Append(line);
_ = htmlContent.AppendHtml(br);
}
return htmlContent;
}