Server-side RequiredFieldValidators not working - c#

I have a form representing a survey that is dynamically generated based on some database configuration. I have a custom server control for rendering the survey (SurveyRenderer) which contains custom server controls for rendering questions (QuestionRenderers). I dynamically add RequiredFieldValidators for questions if they are flagged as being required. I add these validators to the SurveyRenderer's control collection.
The gist of the code...
// In SurveyRenderer.CreateChildControls()...
foreach (QuestionRenderer questionRenderer in questionRenderers)
{
if (questionRenderer.Question.IsRequired)
{
Controls.Add(CreateRequiredValidator(questionRenderer));
}
}
The client-side validation works fine -- if someone has omitted a required question, the validators catch it and the form doesn't validate. However if I turn off JavaScript and submit an invalid form, the validators do not seem to work.
On the server-side I am calling Page.Validate() and checking Page.IsValid in the submit button click event handler. Despite submitting a form where required questions have been left blank - something that would be caught client-side - on the server-side Page.IsValid remains True.
// In SurveyPage.aspx...
public void btnSubmit_Click(object sender, EventArgs e)
{
Page.Validate();
if (Page.IsValid)
{
// Always get here, even though the form is not valid and would
// have been caught client-side...
}
}
Should I be adding the validators to the Page's Control collection, rather than the SurveyRenderer? How come it works on the client-side but not server-side?
UPDATE: My QuestionRenderer is annotated with:
[ValidationProperty("IsValid")]
And the IsValid get method is like so:
// QuestionRenderer.IsValid
public bool IsValid
{
get
{
EnsureChildControls();
if (Question.IsRequired && QuestionIsNotAnswered())
{
return false;
}
return true;
}
}
If I set a breakpoint and step through, I can see that QuestionRenderer.IsValid is being fired OK. It is returning false when it should do. If I go fine-grained and call in btn_submitClick:
// In SurveyPage.aspx...
public void btnSubmit_Click(object sender, EventArgs e)
{
foreach (IValidator validator in Page.Validators)
{
validator.Validate(); // this calls through to QuestionRenderer.IsValid, which returns false...
bool valIsValid = validator.IsValid; // yet this is set to True
}
}
So validator.IsValid is true, even though the call to QuestionRenderer.IsValid returns false. So maybe I haven't wired something up correctly? Is using [ValidationProperty("IsValid")] not enough?

actually, validation uses Page.Validators where all the validators are stored (the actual routine is quity tricky) - so it does not matter, where you add them.
source of BaseValidator
protected internal override void OnInit(EventArgs e)
{
base.OnInit(e);
this.Page.Validators.Add(this);
}
i would leave them in th view, as you could use object sender-parameter (which represents the validator) to get the associated control ...
i believe, your CreateChildControls - which does the attaching of the validators - is called to late, so it misses the validation phase ...
could you maybe try to call EnsureChildControls in OnLoad-event, to see if it changes something?
another chance might be, that your validators are not visible or disabled...
EDIT
according to your edits, i would encourage you to use a CustomValidator - a RequiredFieldValidator will return true on each case (property is true or false), because it is not empty :)

Related

Specified method is not supported in Aspx Web Page

I have a ASPxGridView and I am using its delete confirmation like:
grdList.SettingsBehavior.ConfirmDelete = true;
grdList.SettingsText.ConfirmDelete = "Record will be deleted. Do you want to continue?";
When the customer hits the delete button
"Specified method is not supported"
is throwed. When I test the page, its works how it should be.
Do you have any idea of what may cause that error? We both use IE.
Thank you.
Specified method is not supported thrown when deleting ASPxGridView row usually indicates that the corresponding command is not specified when the grid control tries to execute delete command against underlying data source. If you're using custom data source, take note to this explanation:
When binding ASPxGridView with custom/non-declarative data sources,
they may not have implemented the CRUD operations logic (i.e., there
are no rules that describe how to automatically update a particular
item).
To fix this issue, you can handle RowDeleting event and set Cancel property to true to cancel updating operation as follows:
protected void grdList_RowDeleting(object sender, DevExpress.Web.Data.ASPxDataDeletingEventArgs e)
{
var grid = sender as ASPxGridView;
// make sure Cancel property set to true
e.Cancel = true;
// row deleting code here
// data rebinding code here
grdList.DataBind();
}
Note that Cancel property should always be set to true, either in finally block or before any part of code which potentially raise exception.
protected void grdList_RowDeleting(object sender, DevExpress.Web.Data.ASPxDataDeletingEventArgs e)
{
var grid = sender as ASPxGridView;
try
{
// row deleting code here
// data rebinding code here
grdList.DataBind();
}
catch (Exception e)
{
// exception handling
}
finally
{
// make sure Cancel property set to true
e.Cancel = true;
}
}
Additional references:
Specified method is not supported on deleting GridView row
How to implement CRUD operations with a custom data source

Validate single validation groups step by step in code behind

I have three sections of a form that are contained in jquery tabs. I want to have each tab have it's own validator on it so that if there are validation errors on that form, it is easy to see which forms the user needs to re-do. The problem I am running into is that when I try something like this:
private void ValidateTabOne()
{
Page.Validate("t1");
if (!Page.IsValid)
cvt1.IsValid = false;
}
private void ValidateTabTwo()
{
Page.Validate("t2");
if (!Page.IsValid)
cvt2.IsValid = false;
}
protected void btnSave_Click(object sender, EventArgs e)
{
ValidateTabOne();
ValidateTabTwo();
if (Page.IsValid)
{
//do the save
}
}
cvt2 will always be invalid if anything in the t1 group is invalid (regardless of if t2 group is valid or not) because I Validate("t1") first.
I'd still like a way to do this in the code behind. How can I validate a single group at a time, or "reset" the validation to exclude the previous groups in the Page.IsValid check?
I know that worst case I can write a huge statement to check each validator for IsValid but would much rather use the validation groups.
The best way I have found to do this for now is to use Page.GetValidators as such:
foreach (IValidator v in Page.GetValidators("t1"))
{
if (!v.IsValid)
{
cvt1.IsValid = false;
return;
}
}
cvt1.IsValid = true;
Beforehand I do my custom validation, this just checks to make sure all the required validation is already accounted for.

Checking if a page IsValid even when CausesValidation is false

I need to check the value of Page.IsValid on every load/postback of a page in order to perform some other logic.
However, IsValid cannot be called if Page.Validate() has not been called.
Page.Validate() will not be called if the control that posted back had CausesValidation set to false.
If I call Page.Validate() myself, it causes all the Validators on the page to display.
I currently have two solutions to this problem.
First method, I use a try catch around IsValid. I catch the exception that will occur if validation has not occurred. I then call Page.Validate, check the value of IsValid, then loop through all the Validators to mark them all as Valid so they do not appear on the page.
bool isValid = false;
try
{
isValid = this.IsValid;
}
catch (System.Web.HttpException exception)
{
if(exception.Message == "Page.IsValid cannot be called before validation has taken place. It should be queried in the event handler for a control that has CausesValidation=True and initiated the postback, or after a call to Page.Validate.")
{
//Validation has NOT occurred so run it here, store the result, then set all the validators to valid.
this.Validate();
isValid = this.IsValid;
foreach (IValidator validator in this.Validators)
{
validator.IsValid = true;
}
}
}
Second method, is to use reflection to get the field _validated from the underlying page itself. Then I do the same as the first method in calling Validate if the page hasn't been validated and then resetting all the Validators afterwards.
bool isValidated = (bool)typeof(Page).GetField("_validated", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic).GetValue(this);
bool isValid = false;
if (isValidated)
{
isValid = this.IsValid;
}
else
{
this.Validate();
isValid = this.IsValid;
foreach (IValidator validator in this.Validators)
{
validator.IsValid = true;
}
}
I do not like either of this solutions as I don't like coding by exceptions and I don't like using reflection to get at the Validated property as there must have been some reason it was kept private in the first place.
Does anyone else have any better solutions or any ideas?
I'd suggest you simply use your validators with the EnableClientScript option set to false.
This way, you will be able to call
if (this.Page.IsValid)
in your code.
If you want to validate a specific validation group, just use this line in server-side:
Page.Validate("ValidationGroupName")
Look at these examples of group validation

Cancel all events from Page_Load

EDIT: for those who come here with a similar problem, now i know this was a BAD IDEA.
hi,
I have something like this:
bool preventEvents;
protected void Page_Load(object sender, eventargs e)
{
preventEvents = doSomeValidation();
}
protected void Button1_Click(object sender, EventArgs e)
{
if (preventEvents) return;
// ...
}
protected void Repeater1_DataBound(object sender, EventArgs e)
{
if (preventEvents) return;
// ...
}
The problem is that I have A LOT of events on the page.
Is it possible to just cancel all further events without adding the "if" line on every method?
EDIT:
got some interesting answers (thanks to everyone) but not what i was looking for, maybe i should be more specific:
given some condition, is it possible to skip all events after Page_Load and just jump to the rendering, without manually removing/mapping each event?
The problem is that I have A LOT of events on the page.
Yes, that is a problem. Many events in the same page are bad for performance (it means you're storing a lot of state and doing many http requests). They are bad for maintainability (you have a lot of code in the same class that's all jumbled together). They are bad for testability (asp.net events are notoriously hard to unit test). And they are bad for usability (can't bookmark, don't work with the back button, can lead to double posts).
The solution is to use the Post/Redirect/Get pattern. The downside is that it will mean re-thinking parts of your application design, but in the end you'll have an app that just works better and faster and is easier to maintain.
Be careful choosing to just skip event processing, as is your plan. Odds are your current page state is the result of several events, and not processing events can break the expected state of your page.
You can't "jump ahead" to the rendering because there aren't any conditionals in ProcessRequestMain to allow for it. The only option is to hack the event handlers of the relevant controls.
protected void Page_Load(object sender, EventArgs e) {
// DON'T DO THIS!! Years from now, some poor soul tasked with
// debugging your code will tear their hair out, until they
// discover the unholy magic you have conjured herein. Keep in mind
// this is the 21st century and this person knows where you live.
//
// Seriously, just use the validation built in to ASP.NET.
if ("true".Equals(Request.QueryString["disable_events"], StringComparison.OrdinalIgnoreCase)) {
// disable *all* event handlers on button controls
foreach (var b in this.GetControlDescendants().OfType<Button>()) {
var eventList = (EventHandlerList) typeof (Control)
.GetProperty("Events", BindingFlags.Instance | BindingFlags.NonPublic)
.GetValue(b, null);
typeof (EventHandlerList)
.GetField("head", BindingFlags.Instance | BindingFlags.NonPublic)
.SetValue(eventList, null);
}
}
}
Utility extension method for completeness:
/// <summary>
/// Performs a breadth-first traversal of a control's control tree. Unlike
/// FindControl, this method does not descend into controls that have not
/// called EnsureChildControls yet.
/// </summary>
/// <returns>Enumerable of the visited controls.</returns>
public static IEnumerable<Control> GetControlDescendants(this Control parent) {
// Don't force execution of EnsureChildControls
if (!parent.HasControls()) yield break;
foreach (Control child in parent.Controls) {
yield return child;
}
foreach (Control child in parent.Controls) {
foreach (var descendant in child.GetControlDescendants()) {
yield return descendant;
}
}
}
You can use the following but it will be almost the same cost
Protected void Page_Load() {
if (preventEvents) {
textbox1.TextChanged -= textbox1_TextChanged;
dropdownlist1.SelectedIndexChanged -= dropdownlist1_SelectedIndexChanged;
// and so on
}
}
You can create wrapping delegate over event handler, something like this:
private EventHandler CreateCancelableEventHandler(EventHandler handler)
{
return (sender, e) =>
{
if (!preventEvents)
{
handler.Invoke(sender, e);
}
};
}
The disadvantage of this solution is that you will need to subscribe to all events in code-behind, not in the markup. Usage of the subscribing would be like this:
button1.OnClick += CreateCancelableEventHandler(Button1_OnClick);
How about setting AutoEventWireup to False in your page directive? ie.
<%# Page Language="C#" AutoEventWireup="false" Inherits="MyWebApp.EventWireUpFalse" %>
That way only events you explicitly "wire-up" in OnInit will be called. This will give you far more control of what events are raised in the page life-cycle.
Use a custom validator, and then it just falls into your standard invalid check.
I am having the same problem. My current approach is to overwrite the RaisePostBackEvent Method and check a "cancelEvents" flag. RaisePostBackEvent is responsible to route the postback to its originator.
I'm still testing it for bad side effects - I'd appreciate a note if anybody has experience with this idea.

Does data binding work on invisible control?

This is a .net problem with winforms, not asp.net.
I have a windows form with several tabs. I set data bindings of all controls when the form is loaded. But I have noticed that the data bindings of controls on the second tab do not work. Those bindings work only when the form is loaded and when I select the second tab. This brings the suspicion to me: data bindings work only when bound controls become visible.
Anyone can tell me whether this is true or not? It is not hard to test this but I would like to know some confirmation.
Thanks
You are correct. A data-bound control are not updated until the control is made visible.
The only reference I can find for this at the moment is this MSDN thread.
Your issue has to do with the behavior of the TabControl. See Microsoft bug report. I posted a workaround for that problem which subclasses the TabControl and 'Iniatalizes' all the tab pages when the control is created or the handle is created. Below is the code for the workaround.
public partial class TabControl : System.Windows.Forms.TabControl
{
protected override void OnHandleCreated(EventArgs e_)
{
base.OnHandleCreated(e_);
foreach (System.Windows.Forms.TabPage tabPage in TabPages)
{
InitializeTabPage(tabPage, true, Created);
}
}
protected override void OnControlAdded(ControlEventArgs e_)
{
base.OnControlAdded(e_);
System.Windows.Forms.TabPage page = e_.Control as System.Windows.Forms.TabPage;
if ((page != null) && (page.Parent == this) && (IsHandleCreated || Created))
{
InitializeTabPage(page, IsHandleCreated, Created);
}
}
protected override void OnCreateControl()
{
base.OnCreateControl();
foreach (System.Windows.Forms.TabPage tabPage in TabPages)
{
InitializeTabPage(tabPage, IsHandleCreated, true);
}
}
//PRB: Exception thrown during Windows Forms data binding if bound control is on a tab page with uncreated handle
//FIX: Make sure all tab pages are created when the tabcontrol is created.
//https://connect.microsoft.com/VisualStudio/feedback/details/351177
private void InitializeTabPage(System.Windows.Forms.TabPage page_, bool createHandle_, bool createControl_)
{
if (!createControl_ && !createHandle_)
{
return;
}
if (createHandle_ && !page_.IsHandleCreated)
{
IntPtr handle = page_.Handle;
}
if (!page_.Created && createControl_)
{
return;
}
bool visible = page_.Visible;
if (!visible)
{
page_.Visible = true;
}
page_.CreateControl();
if (!visible)
{
page_.Visible = false;
}
}
}
We've encountered a similar problem. We're trying to write to 2 bound, invisible fields so that we can change the format that we write to our dataset. This works fine when the objects are visible, but stops working when the visible property was changed to false.
To get round it, I added the following code:
// Stop our screen flickering.
chSplitContainer.Panel2.SuspendLayout();
// Make the bound fields visible or the binding doesn't work.
tbxValueCr.Visible = true;
tbxValueDb.Visible = true;
// Update the fields here.
<DO STUFF>
// Restore settings to how they were, so you don't know we're here.
tbxValueCr.Visible = false;
tbxValueDb.Visible = false;
chSplitContainer.Panel2.ResumeLayout();
I've struggled with this myself and concluded that the only workaround, besides subclassing apparently (see hjb417's answer), was to make the other tab visible. Switching to the other tab and going back to the previous immediately before the form is visible doesn't work. If you do not want to have the second tab visible, I've used the following code as a workaround:
this.tabControl.SelectedTab = this.tabPageB;
this.tabPageB.BindingContextChanged += (object sender, EventArgs e) => {
this.tabContainerMain.SelectedTab = this.tabPageA;
};
Assuming tabPageA is the visible tab, and tabPageB is the invisible one you want to initialize. This switches to pageB, and switches back once the data binding is complete. This is invisible to the user in the Form.
Still an ugly hack, but at least this works. Off course, he code gets even uglier when you have multiple tabs.
Sorry for necromancing this thread, but it is easy to force the invisible controls' databinding/handles to be ready using this method:
https://social.msdn.microsoft.com/Forums/vstudio/en-US/190296c5-c3b1-4d67-a4a7-ad3cdc55da06/problem-with-binding-and-tabcontrol?forum=winforms
Simply, let's say if your controls are in tab page tpg_Second (or tabCtl.TabPages[1]), before you do anything with their data, call this first:
tpg_Second.Show()
This will not activate any of the tab pages, but viola, the databinding of the controls should work now.
This is not something I've come across directly. However, you might be experiencing a problem with the BindingContext. Without more details it's hard to say, but if I were you I'd set a breakpoint and make sure the controls are all bound in the same context.
Based on the answers, I made this method that works for me:
public partial class Form1: Form
{
private void Form1_Load(object sender, EventArgs e)
{
...
forceBindTabs(tabControl1);
}
private void forceBindTabs(TabControl ctl)
{
ctl.SuspendLayout();
foreach (TabPage tab in ctl.TabPages)
tab.Visible = true;
ctl.ResumeLayout();
}
}
In addition to solving the problem, the tabs are loaded at the beginning and are displayed faster when the user clicks on them.

Categories

Resources