I am using ASP.MVC 3. I have a view that has a textarea on it. I captures data in it, and when I want a new paragraph I wil press enter twice. After all my data is entered I save the text to the database.
In my details view I would display the data in a like:
<tr>
<td valign="top"><label>Body:</label></td>
<td>#Model.Body</td>
</tr>
Now the text displays as 1 paragraph even though in my textarea (when I captured the data) it seemed liked paragraphs.
How would I get the data to display as paragraphs in my table like what I captured it in my textarea. I'm assuming that I have to search for carriage returns and replace them with break tags?
I'm assuming that I have to search for carriage returns and replace them with break tags?
Yes, your assumption is correct. You could use a custom HTML helper:
public static IHtmlString FormatBody(this HtmlHelper htmlHelper, string value)
{
if (string.IsNullOrEmpty(value))
{
return MvcHtmlString.Empty;
}
var lines = value.Split('\n'); // Might need to adapt
return htmlHelper.Raw(
string.Join("<br/>", lines.Select(line => htmlHelper.Encode(line)))
);
}
and then:
#Html.FormatBody(Model.Body)
UPDATE:
And here's an example of how this method could be unit tested:
[TestMethod]
public void FormatBody_should_split_lines_with_br_and_html_encode_them()
{
// arrange
var viewContext = new ViewContext();
var helper = new HtmlHelper(viewContext, MockRepository.GenerateStub<IViewDataContainer>());
var body = "line1\nline2\nline3<>\nline4";
// act
var actual = helper.FormatBody(body);
// assert
var expected = "line1<br/>line2<br/>line3<><br/>line4";
Assert.AreEqual(expected, actual.ToHtmlString());
}
Related
Quite simple question. I have the following code
#Html.Raw(following.Description).ToString()
when this comes from database it has some markup in it (its a forum post but i want to show a snippet in the list without the markup
is there any way to remove this and replace this line or shall I just regex it from the controller?
Here is a utility class extension method that is able to strip tags from fragments without using Regex:
public static string StripTags(this string markup)
{
try
{
StringReader sr = new StringReader(markup);
XPathDocument doc;
using (XmlReader xr = XmlReader.Create(sr,
new XmlReaderSettings()
{
ConformanceLevel = ConformanceLevel.Fragment
// for multiple roots
}))
{
doc = new XPathDocument(xr);
}
return doc.CreateNavigator().Value; // .Value is similar to .InnerText of
// XmlDocument or JavaScript's innerText
}
catch
{
return string.Empty;
}
}
In my MVC 5 app I need to be able to dynamically construct a list of fully qualified external URL hyperlinks, alone with some additional data, which will come from the Model passed in. I figure - I will need to construct my anchor tags something like this:
{{linkDisplayName}}
with AngularJS this would be natural, but, I have no idea how this is done in MVC.
Is there a templating library that can be used for this?
1) Create a model to Hold the Links
public class LinkObject
{
public string Link { get; set; }
public string Description { get; set; }
}
2) In your Action you can use ViewBag, ViewData or even pass the list inside you Model. I will show you how to do using ViewBag
public ActionResult MyDynamicView()
{
//Other stuff and code here
ViewBag.LinkList = new List<LinkObject>()
{
new LinkObject{ Link ="http://mylink1.com", Description = "Link 1"},
new LinkObject{ Link ="http://mylink2.com", Description = "Link 2"},
new LinkObject{ Link ="http://mylink3.com", Description = "Link 3"}
};
return View(/*pass the model if you have one*/);
}
3) In the View, just use a loop:
#foreach (var item in (List<LinkObject>)ViewBag.LinkList)
{
#item.Description
}
Just create a manual one for that, no need to do it from a template. For example, in javascript
function groupAnchor(url,display){
var a = document.createElement("a");
a.href = url;
a.className = "list-group-item";
a.target = "_blank";
a.innerHTML = display;
return a;
}
And then use that function to modify your html structure
<div id="anchors"></div>
<script>
document.getElementById("anchors").appendChild(groupAnchor("http://google.com","Google"));
</script>
Your approach to modification will more than likely be more advanced than this, but it demonstrates the concept. If you need these values to come from server side then you could always iterate over a set using #foreach() and issue either the whole html or script calls there -- or, pass the set from the server in as json and then use that in a function which is set up to manage a list of anchors.
To expand on this, it is important to avoid sending html to the view from a razor iteration. The reason being that html constructed by razor will increase the size of the page load, and if this is done in a list it can be a significant increase.
In your action, construct the list of links and then serialize them so they can be passed to the view
public ActionResult ViewWithLinks()
{
var vm = new ViewModel();
vm.Links = Json(LinkSource.ToList()).Data;
//or for a very simple test for proof of concept
var Numbers = Json(Enumerable.Range(0,100).ToList()).Data;
ViewData["numbers"] = Numbers ;
return View(vm);
}
where all you need is an object to hold the links in your view model
public class ViewModel
{
public ICollection<Link> Links { get; set; }
}
public class Link
{
public string text { get; set; }
public string href { get; set; }
}
and then in your view you can consume this json object
var allLinks = #Html.Raw(Json.Encode(Model.Links));
var numbersList = #Html.Raw(Json.Encode(ViewData["linkTest"]));//simple example
Now you can return to the above function in order to place it on the page by working with the array of link objects.
var $holder = $("<div>");
for(var i = 0; i < allLinks.length; i++){
$holder.append(groupAnchor(allLinks[i].href,allLinks[i].text));
}
$("#linkArea").append($holder);
The benefit is that all of this javascript can be cached for your page. It is loaded once and is capable of handling large amounts of links without having to worry about sending excessive html to the client.
I have multiselect jquery plagin (Choosen) and when I use it in 'Multiple Select' mode I expect in controller next values:
posted string = 'value1,value2...'
really have
posted string = 'value2'
only if I reffer directly to FormCollection I'll get expected values as below:
[HttpPost]
public ActionResult TagSearech(/*string tagSelect*/FormCollection c)
{
// only one value here
// string[] names = tagSelect.Split(',');
// as expected: value1,....
string expectedValue = c['tagSelect'];
return View();
}
I cant understand what might cause this behavior.
EDIT
Here is View:
#using (Html.BeginForm("TagSearech", "Tag"))
{
#Html.DropDownList("tagSelect", Model, new { #class = "chzn-select", data_placeholder = "tag names", multiple = "" })
<input type="submit"/>
}
MVC will attempt to bind the input data on the URL into the model. I haven't seen how Chosen.js posts the data back to the server, but essentially its coming in in the wrong format, so MVC binds the first element it sees to the string Model.
The FormsCollection retrieves all of the data that was posted in the URL, which is why all of your selected values can be seen there.
Did you try changing the incoming model from string to string[], and see if all of the items are bound to the array?
Is there a way to customize the validationSummary so that it can output anchor tags who's HREF is the name of the field that the validation message in teh summary is displaying for? This way, using jquery, i can add onclick events that focus the field when the anchor tag is clicked on the validation summary.
This is primarely for visually impaired people, so that when they have errors, the validation summary focuses, they tab to an error entry, the anchor tag with the field label focuses and the screen reader reads the anchor then the message, then they can click on the anchor to focus on the erroneous field.
First Name - Please enter your first name.
Thanks.
I don't think that there is any functionality within the framework for this so you would need to use a custom extension method. For example:
public static string AccessibleValidationSummary(this HtmlHelper htmlHelper, string message, IDictionary<string, object> htmlAttributes)
{
// Nothing to do if there aren't any errors
if (htmlHelper.ViewData.ModelState.IsValid)
{
return null;
}
string messageSpan;
if (!String.IsNullOrEmpty(message))
{
TagBuilder spanTag = new TagBuilder("span");
spanTag.MergeAttributes(htmlAttributes);
spanTag.MergeAttribute("class", HtmlHelper.ValidationSummaryCssClassName);
spanTag.SetInnerText(message);
messageSpan = spanTag.ToString(TagRenderMode.Normal) + Environment.NewLine;
}
else
{
messageSpan = null;
}
StringBuilder htmlSummary = new StringBuilder();
TagBuilder unorderedList = new TagBuilder("ul");
unorderedList.MergeAttributes(htmlAttributes);
unorderedList.MergeAttribute("class", HtmlHelper.ValidationSummaryCssClassName);
foreach (string key in htmlHelper.ViewData.ModelState.Keys)
{
ModelState modelState = htmlHelper.ViewData.ModelState[key];
foreach (ModelError modelError in modelState.Errors)
{
string errorText = htmlHelper.ValidationMessage(key);
if (!String.IsNullOrEmpty(errorText))
{
TagBuilder listItem = new TagBuilder("li");
TagBuilder aTag = new TagBuilder("a");
aTag.Attributes.Add("href", "#" + key);
aTag.InnerHtml = errorText;
listItem.InnerHtml = aTag.ToString(TagRenderMode.Normal);
htmlSummary.AppendLine(listItem.ToString(TagRenderMode.Normal));
}
}
}
unorderedList.InnerHtml = htmlSummary.ToString();
return messageSpan + unorderedList.ToString(TagRenderMode.Normal);
}
This is using the existing extension method from within the framework and changing the tag that is inserted into the list. This is a quick sample and there are some things to consider before using this:
This does not encode the error message as I have used the existing html.ValidationMessage. If you need to encode the message then you would need to include so code to provide default messages and any localization requirements.
Due to the use of the existing ValidationMessage method there is a span tag within the anchor. If you want to tidy your HTML then this should be replaced.
This is the most complicated of the usual overloads. If you want to use some of the simpler ones such as html.ValidationSummary() then you would need to create the relevant signatures and call to the method provided.
I'm implementing CAPTCHA in my form submission as per Sanderson's book Pro ASP.NET MVC Framework.
The view fields are generated with:
<%= Html.Captcha("testCaptcha")%>
<%= Html.TextBox("attemptCaptcha")%>
The VerifyAndExpireSolution helper is not working as his solution is implemented.
I'm adding validation and when it fails I add a ModelState error message and send the user back to the view as stated in the book:
return ModelState.IsValid ? View("Completed", appt) : View();
But, doing so, generates a new GUID which generates new CAPTCHA text.
The problem is, however, that the CAPTCHA hidden field value and the CAPTCHA image url both retain the original GUID. So, you'll never be able to enter the correct value. You basically only have one shot to get it right.
I'm new to all of this, but it has something to do with the view retaining the values from the first page load.
Captcha is generated with:
public static string Captcha(this HtmlHelper html, string name)
{
// Pick a GUID to represent this challenge
string challengeGuid = Guid.NewGuid().ToString();
// Generate and store a random solution text
var session = html.ViewContext.HttpContext.Session;
session[SessionKeyPrefix + challengeGuid] = MakeRandomSolution();
// Render an <IMG> tag for the distorted text,
// plus a hidden field to contain the challenge GUID
var urlHelper = new UrlHelper(html.ViewContext.RequestContext);
string url = urlHelper.Action("Render", "CaptchaImage", new{challengeGuid});
return string.Format(ImgFormat, url) + html.Hidden(name, challengeGuid);
}
And then I try to validate it with:
public static bool VerifyAndExpireSolution(HttpContextBase context,
string challengeGuid,
string attemptedSolution)
{
// Immediately remove the solution from Session to prevent replay attacks
string solution = (string)context.Session[SessionKeyPrefix + challengeGuid];
context.Session.Remove(SessionKeyPrefix + challengeGuid);
return ((solution != null) && (attemptedSolution == solution));
}
What about re-building the target field names with the guid? Then, each field is unique and won't retain the previous form generations' value?
Or do I just need a different CAPTCHA implementation?
I had the same problem using the captcha example from Sanderson's book. The problem is that the page gets cached by the browser and doesn't refresh after the captcha test fails. So it always shows the same image, even though a new captcha has been generated and stored for testing.
One solution is to force the browser to refresh the page when reloading after a failed attempt; this won't happen if you just return View(). You can do this using RedirectToAction("SubmitEssay") which will hit the action method accepting HttpVerbs.Get.
Of course, you lose the ability to use ViewData to notify your user of the error, but you can just include this in the query string, then just check the query string to display your message.
So, following the book's example,
if (!CaptchaHelper.VerifyAndExpireSolution(HttpContext, captcha, captchaAttempt)
{
RedirectToAction("SubmitEssay", new { fail = 1 });
}
Then just check if the QueryString collection contains 'fail' to deliver your error message.
So, I decided to implement reCaptcha. And I've customized my view likewise:
<div id="recaptcha_image"></div>
<a href="#" onclick="Recaptcha.reload();">
generate a new image
</a><br />
<input type="text" name="recaptcha_response_field"
id="recaptcha_response_field" />
<%= Html.ValidationMessage("attemptCaptcha")%>
<script type="text/javascript"
src="http://api.recaptcha.net/challenge?k=[my public key]"></script>
This creates two captchas- one in my image container, and another created by the script. So, I added css to hide the auto-generated one:
<style type="text/css">
#recaptcha_widget_div {display:none;}
</style>
Then, in my controller, I merely have to test for captchaValid:
[CaptchaValidator]
[AcceptVerbs(HttpVerbs.Post)]
public ViewResult SubmitEssay(Essay essay, bool acceptsTerms, bool captchaValid)
{
if (!acceptsTerms)
ModelState.AddModelError("acceptsTerms",
"You must accept the terms and conditions.");
else
{
try
{
// save/validate the essay
var errors = essay.GetRuleViolations(captchaValid);
if (errors.Count > 0)
throw new RuleException(errors);
}
catch (RuleException ex)
{
ex.CopyToModelState(ModelState, "essay");
}
}
return ModelState.IsValid ? View("Completed", essay) : View();
}
public NameValueCollection GetRuleViolations(bool captchaValid)
{
var errors = new NameValueCollection();
if (!captchaValid)
errors.Add("attemptCaptcha",
"Please enter the correct verification text before submitting.");
// continue with other fields....
}
And all of this assumes that you've implemented the Action Filter attribute and the view helper as detailed at recaptcha.net:
public class CaptchaValidatorAttribute : ActionFilterAttribute
{
private const string CHALLENGE_FIELD_KEY = "recaptcha_challenge_field";
private const string RESPONSE_FIELD_KEY = "recaptcha_response_field";
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var captchaChallengeValue =
filterContext.HttpContext.Request.Form[CHALLENGE_FIELD_KEY];
var captchaResponseValue =
filterContext.HttpContext.Request.Form[RESPONSE_FIELD_KEY];
var captchaValidtor = new Recaptcha.RecaptchaValidator
{
PrivateKey = "[my private key]",
RemoteIP = filterContext.HttpContext.Request.UserHostAddress,
Challenge = captchaChallengeValue,
Response = captchaResponseValue
};
var recaptchaResponse = captchaValidtor.Validate();
// this will push the result value into a parameter in our Action
filterContext.ActionParameters["captchaValid"] = recaptchaResponse.IsValid;
base.OnActionExecuting(filterContext);
}
}
html helper:
public static class Captcha
{
public static string GenerateCaptcha( this HtmlHelper helper )
{
var captchaControl = new Recaptcha.RecaptchaControl
{
ID = "recaptcha",
Theme = "clean",
PublicKey = "[my public key]",
PrivateKey = "[ my private key ]"
};
var htmlWriter = new HtmlTextWriter( new StringWriter() );
captchaControl.RenderControl(htmlWriter);
return htmlWriter.InnerWriter.ToString();
}
}
Hope this helps someone who got stuck with the implementation in the book.