Prevent full postback for UserControl with ValidationProperty attribute - c#

I have an ASP.NET usercontrol that implements the ValidationProperty attribute. This attribute successfully makes it possible for me to use a RequiredFieldValidator for my custom control, however on validation it causes a full postback rather than using client side javascript based validation.
Is there a way to prevent this and enable client side validation without using a custom validator?
This is the what my UserControl looks like.
<%# Control Language="C#" AutoEventWireup="true" CodeBehind="ucBooleanRadio.ascx.cs" Inherits="MyCompany.Web.UserControls.ucBooleanRadio" %>
<div class="BooleanRadio">
<input runat="server" id="radTrue" type="radio" name="BooleanRadio" value="True" /> Yes
<input runat="server" id="radFalse" type="radio" name="BooleanRadio" value="False" /> No
</div>
[ValidationProperty("Checked")]
public partial class ucBooleanRadio : System.Web.UI.UserControl
{
public Nullable<bool> Checked
{
get
{
if (radTrue.Checked || radFalse.Checked)
return radTrue.Checked;
else
return null;
}
set
{
radTrue.Checked = value != null ? value.Value : false;
radFalse.Checked = value != null ? !value.Value : false;
}
}
}
And this is how it is being used
<uc1:ucBooleanRadio ID="ucAgree" runat="server" />
<asp:RequiredFieldValidator ID="RequiredFieldValidator6" runat="server" CssClass="Validator" Display="Dynamic" ControlToValidate="ucAgree" InitialValue="" ErrorMessage="You must agree to continue."></asp:RequiredFieldValidator>
Page.Validate();
if (Page.IsValid)
{
//Do stuff
}

Turns out, ASP.NET don't event care about element tag.
I've just looked through validation code and found this
function ValidatorGetValue(id) {
var control;
control = document.getElementById(id);
if (typeof(control.value) == "string") {
return control.value;
}
return ValidatorGetValueRecursive(control);
}
So
<div class="BooleanRadio" id="<%= ClientID %>" value="<%= radTrue.Checked? "true" : radFalse.Checked? "false" : "" %>">
<% radTrue.Attributes["onclick"] = "document.getElementById('" + ClientID + "').value='true'"; %>
<% radFalse.Attributes["onclick"] = "document.getElementById('" + ClientID + "').value='false'"; %>
<input runat="server" id="radTrue" type="radio" name="BooleanRadio" value="True" /> Yes
<input runat="server" id="radFalse" type="radio" name="BooleanRadio" value="False" /> No
</div>
actually works :\, as does
<div class="BooleanRadio" id="<%= ClientID %>">
<input runat="server" id="radTrue" type="radio" name="BooleanRadio" value="True" /> Yes
<input runat="server" id="radFalse" type="radio" name="BooleanRadio" value="False" /> No
</div>
And there is events autohooking - validated element (one with ClientID) and its children are wired to cause validation automatically (look ValidatorHookupControl).
That may result in:
1. user does something
2. validation is performed
3. value to validate is updated (after validation!)
First example with value on div behaves this way.

For a simple client-side validation there should be an input element with corresponding name which value is to be validated. Example of how it could be done in your case:
<div class="BooleanRadio">
<% radTrue.Attributes["onclick"] = "document.getElementsByName('" + UniqueID + "')[0].value='+'"; %>
<input runat="server" id="radTrue" type="radio" name="BooleanRadio" value="True" /> Yes
<% radFalse.Attributes["onclick"] = "document.getElementsByName('" + UniqueID + "')[0].value='-'"; %>
<input runat="server" id="radFalse" type="radio" name="BooleanRadio" value="False" /> No
<input name="<%= UniqueID %>" type="hidden" value="<%= radTrue.Checked? "+" : radFalse.Checked? "-" : "" %>" />
</div>

OK, So after significant digging around in the architecture of the ASP.NET field validators and some guidance from the poster above I have finally found the solution.
Basically the answer above is correct barring a couple of changes.
Firstly the id that is set for the hidden text should not be in the Name field but rather the ID field. Further the ID that is populated inside of the Hidden field should not be the Page.UniqueID but rather the Page.ClientID for the UserControl in question.
The reason for this is that when a page that contains validators is loaded the following function is called by the ASP.NET framework.
function ValidatorHookupControlID(controlID, val) {
if (typeof (controlID) != "string") {
return;
}
var ctrl = document.getElementById(controlID); //NOTE THIS LINE
if ((typeof (ctrl) != "undefined") && (ctrl != null)) {
ValidatorHookupControl(ctrl, val);
}
else {
val.isvalid = true;
val.enabled = false;
}
}
What the framework attempts to do is retrieve a control that has the same ID as the ControlToValidate property as set in the required field validator (which is actually Page.ClientID). It then uses this control in its validation functions (whether they be RequiredField, Compare, Regex and so on). If it finds such a control it enables the validator and performs validation against its value, if it doesn't it simply sets the validator to disabled.
In the end my code looks like this.
<%# Control Language="C#" AutoEventWireup="true" CodeBehind="ucBooleanRadio.ascx.cs" Inherits="MyCompany.Web.UserControls.ucBooleanRadio" %>
<input id="<% =this.ClientID %>" type="hidden" value="" />
<asp:RadioButton runat="server" ID="radTrue" Text="Yes" GroupName="radio" />
<asp:RadioButton runat="server" ID="radFalse" Text="No" GroupName="radio" />
<script language="javascript" type="text/javascript">
$(function (e) {
$("#<% =radTrue.ClientID %>").click(function (e) {
$("#<% =this.ClientID %>").val("true");
});
$("#<% =radFalse.ClientID %>").click(function (e) {
$("#<% =this.ClientID %>").val("false");
});
});
</script>
And the code behind remains unchanged. Hopefully this explains some of the mystery of whats going on to anyone who runs into the same problem.

Maxim, I've just been working on a similar problem.
I've found that you don't need the JavaScript the Validators will automatically iterate through the controls looking for a "value" that is set.
You must be using server side RadioButtons rather than HTML
If you're using CompositeControls then it will work automatically
If you're using Web User Controls (ascx files) it doesn't wrap the control with an ID so you need to add the following code to your control.
<div id='<%=ClientID %>'>
Your radio button controls here...
</div>
Edit: I've just uploaded some quick sample code. Hope this helps!
Sample Code

Related

ASP form parameter names being changed to include the master page contentplaceholder

Here is my page:
<%# Page Language="C#" MasterPageFile="~/FBMaster.master" CodeFile="ViewOffer.aspx.cs" Inherits="ViewOffer" %>
<asp:Content ContentPlaceHolderID="ContentPlaceHolder1" runat="server">
<%
FlightBookingWS.FlightBookingSoapClient client = new FlightBookingWS.FlightBookingSoapClient();
FlightBookingWS.Offer offer = client.GetOffer(Request.Form["OfferID"]);
if (offer != null)
{
%>
<div class="OfferDiv">
<span><b>Origin Airport: </b><%=offer.OriginAirport ?? "" %></span>
<span><b>Destination Airport: </b><%=offer.DestinationAirport ?? "" %></span>
<span><b>Airline: </b><%=offer.Airline ?? ""%></span>
<span><b>Available Seats: </b><%=offer.AvailableSeats%></span>
<span><b>Number Of Connections: </b><%=offer.NumberOfConnections%></span>
<%
if (offer.Fare != null)
{
%>
<span><b>Fare: </b><%=String.Format("{0:0.00} {1}", offer.Fare.Value, offer.Fare.Currency) %></span>
<form runat="server">
<span>
<input type="hidden" id="OfferIDField" runat="server" />
<input type="hidden" id="MessageField" runat="server" />
<b>Number of Seats: </b>
<asp:TextBox ID="NumSeatsField" runat="server" Text="1" />
<asp:Button runat="server" Text="Book now" />
</span>
</form>
<%
}
}
else
{
%>
Offer not found.
<%
}
%>
<div id="ErrorBox" runat="server"></div>
</div>
</asp:Content>
Whenever I submit the form, the keys used in the post data are changed from the IDs I wrote to the following ones:
Ideally I'd like to access them using the same keys as the IDs of the inputs they came from, like in normal HTML.
That's not how ASP.NET web forms work
When you put markup on a page with the runat="server" attribute, you are not actually writing page markup. You are defining server-side controls that emit page markup. You're not meant to use them like actual HTML elements.
When the page is posted back, the ASP.NET framework looks at the request message and parses all of the values. It then populates the server-side controls with the necessary data so you can retrieve it easily using ASP.NET syntax.
So, instead of
var offerID = Request.Form["ctl100$ContentPlaceHolder1#OfferIDField"]
you should simply use
var offerID = this.OfferID.Text;
This is the way ASP.NET web forms work.
The old-fashioned way
If you'd rather do it the old-fashioned way, remove the runat="server" attribute and write your markup like regular HTML:
<INPUT ID="OfferID" Name="OfferID">
...and then you can read it the "normal" way:
var offerID = Request.Form["OfferID"];

Trying to apply bootstrap styles in ASP.net webform

I have a text box which has an asp:RegularExpressionValidator attached to it as so
<div class="form-group col-sm-6 <%= (revAlertValue.IsValid == false ? "has-error" : "") %>">
<label for="AlertValue" class="control-label col-sm-4">Alert Value</label>
<div class="col-sm-4 input-group">
<span class="input-group-btn">
<asp:Button ID="btnMediumValue" CssClass="btn btn-warning" runat="server" Text="Low" OnClientClick="btnMediumValue_Click" OnClick="btnMediumValue_Click" />
</span>
<asp:TextBox CssClass="form-control text-center" CausesValidation="true" ID="tbAlertValue" runat="server" MaxLength="4" />
<asp:RegularExpressionValidator ID="revAlertValue" runat="server"
ControlToValidate="tbAlertValue" ErrorMessage=""
ValidationExpression="^[1-9]\d*$" />
<asp:RequiredFieldValidator ID="rfvAlertValue" runat="server" ControlToValidate="tbAlertValue" />
<span class="input-group-btn">
<asp:Button ID="btnHighValue" CssClass="btn btn-danger" runat="server" Text="High" OnClientClick="btnHighValue_Click" OnClick="btnHighValue_Click" />
</span>
</div>
I'm basically trying to apply the has-error class to my form-group if the regular expression validator is not valid. However, when I go to test and input text that would fail the regex validator the class is not applied.
I've tried writing the expression in other ways:
<%= (revResendEvery.IsValid == false) ? "has-error" : "" %>
<%= (revAlertValue.IsValid == false ? "has-error" : "") %>
I've thought maybe it has to be triggered when the submit button is pressed, or more code has to be added to the .aspx.cs handling the class addition after submit is pressed but that doesn't do the trick either.
In order to do this client side you need to tap into the client side code rendered by ASP.NET. Here is an example of some script you might include on your page to do so.
(function () {
var originalRegularExpressionValidator = RegularExpressionValidatorEvaluateIsValid;
RegularExpressionValidatorEvaluateIsValid = function (val) {
var originalValidationResult = originalRegularExpressionValidator(val);
if (!originalValidationResult) {
var formGroup = $("#" + val.controltovalidate).closest(".form-group");
formGroup.addClass("has-error");
}
return originalValidationResult;
}
})();
This, of course, assumes you have jQuery on the page (for the closest and addClass methods), but you can replace that with whatever you want.
Additionally, you may want to look into overriding the Page_ClientValidate function ASP.NET renders. There are many questions on StackOverflow that address how to do that.

Aligning checkboxes in nested repeater

I have a nested repeater control and I am trying to list out the items underneath that nested repeater over a few spaces including the check box associated with it. But it seems like every other line is a checkbox without a label, can someone shed a little light?
<asp:Repeater ID="parentRepeater" runat="server">
<ItemTemplate>
<br />
<b>
<input type="checkbox" id="chk_ParentProgram" name="chk_ParentProgram" runat="server"
value='<%# ((programsRepeat)Container.DataItem).Level == 1 ? ((programsRepeat)Container.DataItem).ProgramID.ToString() : "" %> '
/>
<label for="chk_ParentProgram">
<%# ((programsRepeat)Container.DataItem).Level == 1 ? ((programsRepeat)Container.DataItem).ProgramName : "" %>
</label>
</b>
<asp:Repeater>
<ItemTemplate>
<br/>
<input type="checkbox" id="chk_ChildProgram" name="chk_Child" runat="server"
value='<%# ((programsRepeat)Container.DataItem).Level == 2 ? ((programsRepeat)Container.DataItem).ProgramID.ToString() + " from the child repeater" : "" %>'
/>
<label for="chk_ChildProgram">
<%# ((programsRepeat)Container.DataItem).Level == 2 ? ((programsRepeat)Container.DataItem).ProgramName : "" %>
</label>
</ItemTemplate>
</asp:Repeater>
</ItemTemplate>
</asp:Repeater>
The problem is whether you have a valid value or empty sting for (programsRepeat)Container.DataItem).ProgramID.ToString() you still rendering the checkbox like below
<input type="checkbox" value="" />
This will render the empty checkbox.
Better check the value and render the checkbox like
<% if((programsRepeat)Container.DataItem).Level == 2 &&
((programsRepeat)Container.DataItem).ProgramID!="")
{
//not sure about the inline syntax here.
//add checkbox code
}
%>
Inline Syntax Reference or check this

dynamic controls not populating

I have a simple user control that will be displayed numerous times on the page. So i have a panel which a loop over a dataset, creating the UC, populating a textbox and a checkbox, and then adding it to the panel.
The panel adds the UC but neither the textbox value nor the checkbox is changed...
foreach (Issue iss in Case.Issues)
{
Comments comment = (Comments)LoadControl("~/UserControls/Comments.ascx");
comment .ID = "Comment" + iss.IssueDetail.quality_control_issue_id.ToString();
comment .Populate(iss);
QCComments_list.Controls.Add(comment );
}
Do i have to do this on pre-render or Onit of the page or there a way of refreshing the controls of the UC?
Here's the UC markup. very simple.
<%# Control Language="C#" AutoEventWireup="true" CodeBehind="ROI_Comments.ascx.cs" Inherits="QualityControl_UserControls.ROI_Comments" %>
<!-- Field -->
<div id="ROI_Comment_DIV" class="field">
<label>Corrections</label>
<input type="text" runat="server" id="comtxt" name="comtxt" />
<asp:CheckBox ID="issue_critical" runat="server" Text="Critical" />
<asp:Button runat="server" id="SaveButton" Text="Add Comment" OnClientClick="SaveComment(this);return false;" />
<asp:Button runat="server" id="ROICancelButton" Text="Cancel" OnClientClick="return false;" />
<asp:HiddenField runat="server" ID="hIssueID" />
</div>
<!-- /Field -->
and the .cs
public partial class ROI_Comments : System.Web.UI.UserControl
{
public ROI_Comments()
{
}
public void Populate(cQuality_Control_Issue _comment)
{
try
{
hIssueID.Value = _comment.IssueDetail.quality_control_issue_id.ToString();
comtxt.Value = _comment.IssueDetail.quality_control_issue_description;
comtxt.Disabled = true;
issue_critical.Checked = _comment.IssueDetail.quality_control_issue_critical;
issue_critical.Enabled = false;
ROICancelButton.Text = "Delete";
}
catch(Exception ex)
{}
}
}
There's nothing obviously wrong from what you have shown but there could be a null reference exception occurring before the values are set. You would not know about it because of the empty try/catch block. Its never a good idea to use an empty try/catch because you would have no idea if an exception is occurring.

Get the selected value of a group of HtmlInputRadioButton controls

When using the RadioButtonList control, I can access the selected value in my codebehind with the following:
rbMyButtons.SelectedValue
However, I'd like to use HtmlInputRadioButton controls instead as these give me greater control over the rendered markup. If I'm using a list of <input type="radio" runat="server" /> then, as far as I know, I have to do something like this:
if (rbMyButtonsOption1.Checked)
{
...
}
else if (rbMyButtonsOption2.Checked)
{
...
}
else if ...
Is there a way I can mimic the behaviour of RadioButtonList.SelectedValue without using Request.Form["name"]?
Here's how I would do it:
First, wrap the Html Controls inside a panel or similar grouping object (the panel will be rendered as a div):
<asp:Panel runat="server" ID="ButtonList" ClientIDMode="Static">
<input type="radio" name="seasons" value="Spring" runat="server" />Spring <br/>
<input type="radio" name="seasons" value="Summer" runat="server" /> Summer <br/>
<input type="radio" name="seasons" value="Fall" runat="server" /> Fall <br/>
<input type="radio" name="seasons" value="Winter" runat="server" /> Winter <br/>
</asp:Panel>
Then, create an extension method to access the ControlCollection property of the panel and iterate through the collection:
public static class HelperFunctions
{
public static string GetRadioButtonValue(this ControlCollection collection)
{
foreach (var control in collection)
{
if (control is HtmlInputRadioButton)
{
var radioControl = ((HtmlInputRadioButton)control);
if (radioControl.Checked)
{
return radioControl.Value;
}
}
}
//If no item has been clicked or no Input Radio controls are present we return an empty string
return String.Empty;
}
}
Finally you can get the value with ease:
var selectedValue = ButtonList.Controls.GetRadioButtonValue();
I did some searching to find this, so thought it may be useful even though the question is over 1 year old...
I've just come across this issue. I needed to use use [input type="radio" runat="server" ...] as I had to include additional HTML "data-*" attributes against each selection.
I've used a composite of the two answers here to come with a solution that helped me.
My HTML looks like:
<div id="pnlOpts" runat="server">
<input type="radio" name="rblReason" runat="server" value="1" /> <label>Opt 1</label><br />
<input type="radio" name="rblReason" runat="server" value="3" data-att="1" /> <label>Supplied Reason 1 </label><br />
<input type="radio" name="rblReason" runat="server" value="3" data-att="2" /> <label>Supplied Reason 2 </label><br />
<input type="radio" name="rblReason" runat="server" value="3" data-att="3" /> <label>Supplied Reason 3 </label><br />
</div>
My Code-behind looks like:
if(pnlOpts.Controls.OfType<HtmlInputRadioButton>().Any(a=>a.Checked))
{
HtmlInputRadioButton selected = pnlOpts.Controls.OfType<HtmlInputRadioButton>().Where(a => a.Checked).FirstOrDefault();
var btnValue = selected.Value;
var dataAttVal = selected.Attributes["data-att"];
...
}
You can mimic this behavior using LINQ:
var radioButtons = new[] { rbMyButtonsOption1, rbMyButtonsOption2 };
var checkedRadioButton = radioButtons.FirstOrDefault(i => i.Checked);
if (checkedRadioButton != null)
{
// do something...
}

Categories