What's the best way of validating if a RTE Field has any content?
I've tried to add the expression "^(?!\s*$).+" to the validation but it doesn't work. This happens because RTE adds some html tags (that authors cannot see unless they switch for the HTML view) and the value of the field is actually not empty.
Rich Text fields could have a variety of empty tags for example by default Sitecore will replace line breaks with empty p tags. See HtmlEditor.LineBreak setting in Web.config:
<!-- HTML EDITOR LINE BREAK
Specifies the tag that the HTML editor inserts on Enter. Values can be
"br", "div" and "p".
-->
<setting name="HtmlEditor.LineBreak" value="p" />
Or only entering a whitespace will save the field value as <p> </p>
There are two approaches that could be considered.
The first is whether to worry about the different scenarios that the content editor could enter content into the rich text editor. The content editor may not be detailed about worrying about the markup and may decide to remove it leaving line breaks or whitespaces. You could handle the value from the fields by using HtmlAgilityPack to check if any nodes has inner text:
public bool HasContent(string val)
{
var htmlVal = new HtmlDocument();
htmlVal.LoadHtml(val);
if (htmlVal.DocumentNode == null || !htmlVal.DocumentNode.ChildNodes.Any())
return false;
return htmlVal.DocumentNode.ChildNodes.Any(x => !string.IsNullOrWhiteSpace(x.InnerText));
}
If nothing comes back, you would not render the value to the page eliminating possible empty p tags.
The second approach would be to create a custom validation rule. To complete this, you will need to create a field rule, the custom validator class and associating the validation rule on any rich text fields. Below are the steps:
Open Content Editor and navigate to sitecore/System/Settings/ValidationRules/Field Rules/Text and add "Validation Rule" named "No Content For Rich Text"
Fill out Title, Description and Type
Create RichTextValidator class under Web project under Validators folder
RichTextValidator.cs
using HtmlAgilityPack;
using Sitecore.Data.Validators;
using System;
using System.Linq;
using System.Runtime.Serialization;
namespace MyProject.Web.Validators
{
[Serializable]
public class RichTextValidator : StandardValidator
{
public RichTextValidator() { }
public RichTextValidator(SerializationInfo info, StreamingContext context) : base(info, context)
{ }
private bool HasContent(string val)
{
var htmlVal = new HtmlDocument();
htmlVal.LoadHtml(val);
if (htmlVal.DocumentNode == null || !htmlVal.DocumentNode.ChildNodes.Any())
return false;
return htmlVal.DocumentNode.ChildNodes.Any(x => !string.IsNullOrWhiteSpace(x.InnerText) && x.InnerText != " ");
}
protected override ValidatorResult Evaluate()
{
string contextText = this.ControlValidationValue;
if (!HasContent(contextText))
return ValidatorResult.CriticalError;
return ValidatorResult.Valid;
}
protected override ValidatorResult GetMaxValidatorResult()
{
return GetFailedResult(ValidatorResult.CriticalError);
}
public override string Name
{
get { return "Rich text contains no content."; }
}
}
}
On the data template field, add the validation rule
Finally, the rich text field should indicate a critical error when there is either just empty tags or <p> </p>
Related
In a Visual Studio 2013 WebTest, I am using the "Selected Option" extraction rule to extract the value of a DropDown into a Context Parameter. It works fine as long as a selected option exists. If a selected option does not exist (which in my test is perfectly acceptable) the test throws an exception, "The select tag 'SuchAndSuch' was not found."
The error message is misleading. When I look at the Response I see that the select tag "SuchAndSuch" does indeed exist, it just does not have a selected option. That is to say, this Html tag exists:
<select name="SuchAndSuch">
But it does not have a child tag like this:
<option selected="selected">
I also tried using "Extract Attribute Value" Extraction Rule to extract the selected item in a dropdown, since this latter rule has a "Required" property.
The rule is to look for the first instance of the tag "option" that has the attribute "selected=selected," and then extract the value of the "value" attribute. I have "Required" false because this drop-down will not always have a selected item.
The properties of my Extract Attribute Value rule are as follows:
<RuleParameters>
<RuleParameter Name="TagName" Value="option" />
<RuleParameter Name="AttributeName" Value="value" />
<RuleParameter Name="MatchAttributeName" Value="selected" />
<RuleParameter Name="MatchAttributeValue" Value="selected" />
<RuleParameter Name="HtmlDecode" Value="True" />
<RuleParameter Name="Required" Value="False" />
<RuleParameter Name="Index" Value="0" />
</RuleParameters>
This works fine as long as the drop-down has a selected item. When it does not the WebTest throws the WebTestException
Context Parameter 'SuchAndSuch' not found in test context
and the Request does not execute.
My desired behavior is when this particular drop-down lacks a selected item I want that particular Request to continue to execute and I want the test to NOT log a WebTestException. Is that possible?
The answer in this case is to write a custom extraction rule: https://msdn.microsoft.com/en-us/library/ms243179(v=vs.120).aspx
The rule I created for my own use is below, it is based on code I stole from here: https://social.msdn.microsoft.com/Forums/en-US/df4f2a0b-2fc4-4d27-9380-ac80100ca0b7/need-help-with-complex-extraction-rule?forum=vstswebtest
I first created the rule in the same project as the webtest. When I ran the webtest it immediately errored out with a "FileNotFound" exception when it tried to load the extraction rule. I moved the extraction rule to a separate project and referenced it from the web test project and it solved that problem.
[DisplayName("Extract Single-Select Dropdown")]
[Description("Extracts the attribute value of AttributeName from the selected option if selected option exists.")]
public class ExtractSingleSelectDropdown : ExtractionRule
{
[Description("The 'name' attribute of the rendered 'select' tag.")]
public string DropdownName { get; set; }
[Description("The name of the attribute of the rendered 'option' tag that contains the value that is extracted to the Context Parameter.")]
public string OptionAttributeName { get; set; }
[Description("When true, sets Success 'false' if a selected option is not found.")]
public bool Required { get; set; }
public override void Extract(object sender, ExtractionEventArgs e)
{
if (e.Response.HtmlDocument != null)
{
var tags = e.Response.HtmlDocument.GetFilteredHtmlTags(new string[] { "select", "option" });
if (tags != null && tags.Count() > 0)
{
//find the first <select><option selected="selected"> tag
var selectedOption = tags.SkipWhile(tag => String.IsNullOrWhiteSpace(tag.GetAttributeValueAsString("name")) || !tag.GetAttributeValueAsString("name").Equals(this.DropdownName, StringComparison.InvariantCultureIgnoreCase)) // skip tags that aren't the select tag we're looking for
.Skip(1) // skip the <select> tag
.TakeWhile(tag => tag.Name.Equals("option"))
.Where(tag => String.IsNullOrWhiteSpace(tag.GetAttributeValueAsString("selected"))) //
.FirstOrDefault();
if (selectedOption == null)
{
e.Message = string.Format("Zero selected options in Dropdown {0}", DropdownName);
e.WebTest.Context[ContextParameterName] = string.Empty;
e.Success = (this.Required ? false : true);
}
else
{
e.WebTest.Context[ContextParameterName] = selectedOption.GetAttributeValueAsString(OptionAttributeName);
e.Success = true;
}
}
}
}
}
I'm trying to implement a custom client side validation, but it is not working. I'm basing myself on the article on Codeproject http://www.codeproject.com/Articles/275056/Custom-Client-Side-Validation-in-ASP-NET-MVC3
I also looked here on SO, but I think I'm implementing it in the correct manner, but I'm overlooking something.
My goal is to validate a date (required, date format and not earlier than another date on the form). The first two can be done with data annotations, the last I have to do with custom validation.
I have on my base class some dataannotations (ClassLibrary is in VB.NET):
Imports System.ComponentModel
Imports System.ComponentModel.DataAnnotations
<MetadataType(GetType(CM_CONTRACTVALIDATIONData))>
Partial Public Class CM_CONTRACTACTIVATION
'...
End Class
Public Class CM_CONTRACTVALIDATIONdata
'...
<DataType(DataType.Date)>
<Required()>
Public Property TakeBackDeviceWhen
'..
End Class
In the javascript file I have added the custom method:
//validation
$.validator.addMethod("checkPickupDate", function (value, element) {
return false;
});
$("#form").validate({
rules: {
TakeBackDeviceWhen: {
checkPickupDate: true
}
},
messages: {
TakeBackDeviceWhen: {
checkPickupDate: "Test"
}
}
}
);
My chtml file is as follow:
#Html.TextBox("TakeBackDeviceWhen", Model.TakeBackDeviceWhen.HasValue ? Model.TakeBackDeviceWhen.Value.ToShortDateString() : "", new { style = "Width: 200px" })
The resulting HTML is as follow:
<input id="TakeBackDeviceWhen" class="hasDatepicker" type="text" value="" style="Width: 200px" name="TakeBackDeviceWhen" data-val-required="The TakeBackDeviceWhen field is required." data-val="true">
It seems that neither my type validation and my custom validation isn't implemented.
What is going wrong?
OK, solved it. I hope :-)
What did I learned today:
(1) Don't use EditorFor: when you scaffold it from a MVC template, input fields are generated to EditorFor, it seems that you can't add custom unobtrusive validation tags. So, I was trying to get this fixed, untill I changed it to TextBoxFor.
(2) You can add custom validation methods in jQuery, but you can't mix them with unobtrusive validation. After adding a custom method, you have to also add it to the unobtrusive adapters. And don't forget to add jQuery on the bottom :-s (I got this from jQuery.validator.unobtrusive.adapters.addMinMax round trips, doesn't work in MVC3)
$(function () {
$.validator.addMethod("checkpickupdate", function (value, element) {
if (value == "20/09/2012") {
return false;
} else {
return true;
}
});
$.validator.unobtrusive.adapters.addBool("checkpickupdate");
} (jQuery));
(3) Add validation tags to the input field in the htmlAttributes:
#Html.TextBox("TakeBackDeviceWhen", Model.TakeBackDeviceWhen.HasValue ? Model.TakeBackDeviceWhen.Value.ToShortDateString() : "",
new {
style = "Width: 200px",
data_val = "true",
data_val_required = "verplicht!",
data_val_date = "moet datum zijn",
data_val_checkpickupdate = "wow"
})
(4) Datatype data annotations will not enforce a validation. You have to add it like in (3). You can add a custom ValidationAttribute like (for server side validation):
public class MustBeDateAttribute : ValidationAttribute {
public override bool IsValid(object value) {
try
{
DateTime dte = DateTime.Parse(value.ToString());
return true;
}
catch (Exception)
{
return false;
throw;
}
}
}
And this is the resulting html output:
<input type="text" value="" style="Width: 200px" name="TakeBackDeviceWhen" id="TakeBackDeviceWhen" data-val-required="required!" data-val-date="has to be a date" data-val-checkpickupdate="custom error" data-val="true" class="hasDatepicker valid">
As I'm using my ClassLibrary in different projects, I'm now going to try to seperate the dataannotations meta data from the class library (maybe with dependency resolver).
Is it possible to put a HTML link in validation summary message? For example I want to put a link to another page in case there is validation error:
#Html.ValidationSummary(False, "read more")
or
#Html.ValidationSummary(False, "read " &
Html.ActionLink("more", "helpforerror").ToHtmlString)
But in the browser the tag is escaped so it doesn't form a link.
I know you have accepted an answer, but i think my solution is more simple and will require less rewriting if you want to add links to existing validation summaries.
You need to put a {0} type format item in your validation message like below, which will be replaced by your link.
ModelState.AddModelError("", "Some error message with a link here {0}.");
then in your view call your validation summary like so:
#string.Format(Html.ValidationSummary().ToString(), Html.ActionLink("Click Here", "Action_To_Link_To")).ToHtmlString()
In this case i have used an extension method I added to the string object .ToHtmlString() that basically just converts the string to an HtmlString preventing any of the markup being escaped. it looks like this:
public static HtmlString ToHtmlString(this String str)
{
return new HtmlString(str);
}
Finally I chose another way to do it: create a div containing the link etc. outside of validation summary, and add the div only if modelstate is not valid:
#If Not ViewData.ModelState.IsValid Then
#<div>read more</div>
End If
This is inspired by an answer to similar question.
The validation text is encoded before the ValidationSumary or ValidationFor, etc...
you just need tu decode the html, then create an MvcHtmlString ...
Exemple :
#HttpUtility.HtmlDecode(Html.ValidationSummary().ToString()).ToMvcHtmlString()
this is an extension i have made to make MvcHtmlString :
namespace System
{
public static class StringExtension
{
public static System.Web.Mvc.MvcHtmlString ToMvcHtmlString(this string value)
{
return System.Web.Mvc.MvcHtmlString.Create(value);
}
}
}
or you can create an HtmlHelper if you plan to reuse this:
namespace System.Web.Mvc.Html
{
public static class FormHelper
{
public static MvcHtmlString ValidationSummaryEx(this HtmlHelper htmlHelper, bool excludePropertyErrors)
{
var original = htmlHelper.ValidationSummary(excludePropertyErrors);
var decoded = HttpUtility.HtmlDecode(original.ToString());
return decoded.ToMvcHtmlString();
}
}
}
Hope it help you or future viewer.
Note: it work for all validations Summary and ValidationFor ...
No, the default behaviour doesn't allow it, but you can make your own. This is what you need: Html raw in validationsummary
You can check if form is valid by jquery and update div with link text:
<div id="divToUpdate">
</div>
$('form').submit(function(){
if(!this.valid())
$('#divToUpdate').html("read <a href='anotherpage.html'>more</a>");
});
If you're sending back HTML in the ModelStateError, you can use this one liner:
#Html.Raw(HttpUtility.HtmlDecode(Html.ValidationSummary().ToHtmlString()))
It's very similar to what #Benoit had suggested, just without needing the extension.
How to only allow certain html tags in a text box
Example:
<Strong>
<p>
The code below is where I have been trying to implement the solution in a class created.
[Required]
(Code)
Public string car { get; set; }
How would I go about implementing the solution and is it possible at the point where (code) is written above.
First, you would need to disable the validation for you action with [ValidateInput(false)] attribute but you will need to use that carefully as it will turn off validation for the whole method. You may also disable validation for a particular attribute like :
[Required]
[AllowHtml]
Public string article { get; set; }
ASP.NET MVC3 has built-in attribute to disable validation at property level - so putting [AllowHtml] attribute on properties in model or view model will disable request validation. This is not safe and puts your site at risk. Now it's up to you to ensure that proper data format is provided so you may wan't to give a try a with Regular Expressions to filter out all html code except for the tags you need. You may wan't to take a look at this answer Regex to match all HTML tags except <p> and </p> to get you going.
example from msdn on how to use regex validation with data annotations :
public class Customer
{
[Required]
[RegularExpression(#"^[a-zA-Z''-'\s]{1,40}$",
ErrorMessage="Numbers and special characters are not allowed in the last name.")]
public string LastName { get; set; }
}
http://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.regularexpressionattribute(v=vs.95).aspx
you may also try the safer way - to implement BBCode like feature. So instead of html tags you use pseudo html tags like [b] instead of < b >
this is easy to accomplish with jQuery :
assuming #text is a field populated with bbcode like text (not visible) and text2 is formatted display - visible :
$(document).ready(function(){
var text = $('#text').html();
text = text.replace("[b]","<b>");
text = text.replace("[/b]","</b>");
$('#text2').html(text);
});
it's not the smartest code but it was a quick one to show you a direction you can take.
The following Regular Expression allows only the Html tags specified:
[RegularExpression(#"^([^<]|<p>|</p>|<strong>|</strong>|a z|A Z|1 9|(.\.))*$")}
This allows for the html <p> </p> <strong> </strong> to be entered while not allowing any other tags.
Add other tags if required.
use AllowHtml attribute and then validate the content using IValidatableObject and Regex,
or write a custom validation attribute to allow only some html tags with Regex, see Phil Haack article http://haacked.com/archive/2009/11/19/aspnetmvc2-custom-validation.aspx
[RegularExpression("^[^<>,<|>]+$", ErrorMessage = "Invalid entry.")]
public string FirstName { get; set; }
To avoid mvc error.
I've created a fairly straight forward page with a check box:
#using (Html.BeginForm("MyController", "MyAction", FormMethod.Get))
{
#Html.CheckBoxFor(x => x.MyCheckBox)
<input type="submit" value="Go!" />
}
The URL is populated with the MyCheckBox value twice!? As such:
MyAction?MyCheckBox=true&MyCheckBox=false
It only duplicates the value if the check box is true. If set to false it will only appear once in the query string.
The code above is simplified as I have a couple of drop downs and a textbox on the form which work fine. I don't think there's anything unusual about the code which I've left out from this question.
Has anyone had a similar issue with query string parameters being duplicated?
This behaviour is by design of the checkbox control. The standard HTML checkbox control passes no value if it is not checked. This is unintuitive. Instead, the ASP.Net checkbox control has 2 elements, the standard control which is visible and also a hidden control with a value of 'False'.
Therefore, if the checkbox is not checked, there will be one value passed: False.
If it is checked, there will be two values, True and False. You therefore need to use the following code to check for validity in your code:
bool checkboxChecked = Request.QueryString["MyCheckBox"].Contains("True");
Accepted answer is correct however in my case in a recent development the MVC behaviour is misleading.
The MVC Html.CheckBox(...) and Html.CheckBoxFor(...) generate an extra input of 'type=hidden' with the same ID as the checkbox control, leading to the duplicate URL parameters. I got around this problem by simply including the mark up desired as follows:
#if(checkTrue){
<input type="checkbox" id="MyCheckBox" name="MyCheckbox" checked="checked">
}else{
<input type="checkbox" id="MyCheckBox" name="MyCheckbox">
}
Would be better wrapped upin a helper to use in place of the MVC code so the value check is encapsulated.
As part of my application, the controller maintains sets of query parameters using both form injection and link injection using helpers in order to preserve state (of paging/filtering controls for example) when clicked to navigate within the same controller scope. As a result of this feature, the check box element is always set back to false if the standard MVC helpers are used. It's a good thing I noticed and did not waste much time on this bug.
In my model, I had a collection of checkboxes like so:
public class PrerequisitesViewModel
{
public List<StudentPrerequisiteStatusViewModel> PrerequisiteStatuses { get; set; }
}
public class StudentPrerequisiteStatusViewModel
{
public long Id { get; set; }
public string Name { get; set; }
public bool IsSelected { get; set; }
}
In order to get everything to bind correctly, I had to actually convert the values from the querystring and parse them manually with the following code:
// fix for how MVC binds checkboxes... it send "true,false" instead of just true, so we need to just get the true
for (int i = 0; i < model.PrerequisiteStatuses.Count(); i++)
{
model.PrerequisiteStatuses[i].IsSelected = bool.Parse((Request.QueryString[$"PrerequisiteStatuses[{i}].IsSelected"] ?? "false").Split(',')[0]);
}
Alas, it works, but I can't believe this is necessary in MVC! Hopefully, someone else knows of a better solution.
I solve this issue with use #Html.HiddenFor
<input id="checkboxId" type="checkbox" value="true" onchange="changeCheckboxValue()">
#Html.HiddenFor(m => m.MyCheckBox, new { #id = "hiddenId" } )
<script>
function changeCheckboxValue() {
document.getElementById("checkboxId").value = document.getElementById("hiddenId").checked;
}
</script>