From f6085df8c4927a59e515458e4e815595a84c5cc1 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Sat, 24 Jan 2009 08:45:51 -0800 Subject: [PATCH] Added the OpenIdLogin ASP.NET control. --- src/DotNetOpenAuth/DotNetOpenAuth.csproj | 1 + .../OpenId/RelyingParty/OpenIdLogin.cs | 804 +++++++++++++++++++++ 2 files changed, 805 insertions(+) create mode 100644 src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdLogin.cs diff --git a/src/DotNetOpenAuth/DotNetOpenAuth.csproj b/src/DotNetOpenAuth/DotNetOpenAuth.csproj index 2382f61..71bc702 100644 --- a/src/DotNetOpenAuth/DotNetOpenAuth.csproj +++ b/src/DotNetOpenAuth/DotNetOpenAuth.csproj @@ -265,6 +265,7 @@ + diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdLogin.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdLogin.cs new file mode 100644 index 0000000..aa6925d --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdLogin.cs @@ -0,0 +1,804 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Andrew Arnott. All rights reserved. +// +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.RelyingParty { + using System; + using System.ComponentModel; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Web.UI; + using System.Web.UI.HtmlControls; + using System.Web.UI.WebControls; + + /// + /// An ASP.NET control providing a complete OpenID login experience. + /// + [SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Login", Justification = "Legacy code")] + [DefaultProperty("Text"), ValidationProperty("Text")] + [ToolboxData("<{0}:OpenIdLogin runat=\"server\" />")] + public class OpenIdLogin : OpenIdTextBox { + #region Property defaults + + /// + /// The default value for the property. + /// + private const string RegisterToolTipDefault = "Sign up free for an OpenID with MyOpenID now."; + + /// + /// The default value for the property. + /// + private const string RememberMeTextDefault = "Remember me"; + + /// + /// The default value for the property. + /// + private const string ButtonTextDefault = "Login ยป"; + + /// + /// The default value for the property. + /// + private const string CanceledTextDefault = "Login canceled."; + + /// + /// The default value for the property. + /// + private const string FailedMessageTextDefault = "Login failed: {0}"; + + /// + /// The default value for the property. + /// + private const string ExamplePrefixDefault = "Example:"; + + /// + /// The default value for the property. + /// + private const string ExampleUrlDefault = "http://your.name.myopenid.com"; + + /// + /// The default value for the property. + /// + private const string LabelTextDefault = "OpenID Login:"; + + /// + /// The default value for the property. + /// + private const string RequiredTextDefault = "Provide an OpenID first."; + + /// + /// The default value for the property. + /// + private const string UriFormatTextDefault = "Invalid OpenID URL."; + + /// + /// The default value for the property. + /// + private const string RegisterTextDefault = "register"; + + /// + /// The default value for the property. + /// + private const string RegisterUrlDefault = "https://www.myopenid.com/signup"; + + /// + /// The default value for the property. + /// + private const string ButtonToolTipDefault = "Account login"; + + /// + /// The default value for the property. + /// + private const string ValidationGroupDefault = "OpenIdLogin"; + + /// + /// The default value for the property. + /// + private const bool RegisterVisibleDefault = true; + + /// + /// The default value for the property. + /// + private const bool RememberMeVisibleDefault = false; + + /// + /// The default value for the property. + /// + private const bool RememberMeDefault = UsePersistentCookieDefault; + + /// + /// The default value for the property. + /// + private const bool UriValidatorEnabledDefault = true; + + #endregion + + #region Property viewstate keys + + /// + /// The viewstate key to use for the property. + /// + private const string FailedMessageTextViewStateKey = "FailedMessageText"; + + /// + /// The viewstate key to use for the property. + /// + private const string CanceledTextViewStateKey = "CanceledText"; + + /// + /// The viewstate key to use for the property. + /// + private const string IdSelectorIdentifierViewStateKey = "IdSelectorIdentifier"; + + #endregion + + /// + /// The HTML to append to the property value when rendering. + /// + private const string RequiredTextSuffix = "
"; + + /// + /// The number to add to to get the tab index of the textbox control. + /// + private const short TextBoxTabIndexOffset = 0; + + /// + /// The number to add to to get the tab index of the login button control. + /// + private const short LoginButtonTabIndexOffset = 1; + + /// + /// The number to add to to get the tab index of the remember me checkbox control. + /// + private const short RememberMeTabIndexOffset = 2; + + /// + /// The number to add to to get the tab index of the register link control. + /// + private const short RegisterTabIndexOffset = 3; + + #region Controls + + /// + /// The control into which all other controls are added. + /// + private Panel panel; + + /// + /// The Login button. + /// + private Button loginButton; + + /// + /// The label that presents the text box. + /// + private HtmlGenericControl label; + + /// + /// The validator that flags an empty text box. + /// + private RequiredFieldValidator requiredValidator; + + /// + /// The validator that flags invalid formats of OpenID identifiers. + /// + private CustomValidator identifierFormatValidator; + + /// + /// The label that precedes an example OpenID identifier. + /// + private Label examplePrefixLabel; + + /// + /// The label that contains the example OpenID identifier. + /// + private Label exampleUrlLabel; + + /// + /// A link to allow the user to create an account with a popular OpenID Provider. + /// + private HyperLink registerLink; + + /// + /// The Remember Me checkbox. + /// + private CheckBox rememberMeCheckBox; + + /// + /// The javascript snippet that activates the ID Selector javascript control. + /// + private Literal idselectorJavascript; + + /// + /// The label that will display login failure messages. + /// + private Label errorLabel; + + #endregion + + /// + /// Initializes a new instance of the class. + /// + public OpenIdLogin() { + } + + #region Events + + /// + /// Fired after the user clicks the log in button, but before the authentication + /// process begins. Offers a chance for the web application to disallow based on + /// OpenID URL before redirecting the user to the OpenID Provider. + /// + [Description("Fired after the user clicks the log in button, but before the authentication process begins. Offers a chance for the web application to disallow based on OpenID URL before redirecting the user to the OpenID Provider.")] + public event EventHandler LoggingIn; + + /// + /// Fired when the Remember Me checkbox is changed by the user. + /// + [Description("Fires when the Remember Me checkbox is changed by the user.")] + public event EventHandler RememberMeChanged; + + #endregion + + #region Properties + /// + /// Gets or sets the caption that appears before the text box. + /// + [Bindable(true)] + [Category("Appearance")] + [DefaultValue(LabelTextDefault)] + [Localizable(true)] + [Description("The caption that appears before the text box.")] + public string LabelText { + get { return this.label.InnerText; } + set { this.label.InnerText = value; } + } + + /// + /// Gets or sets the text that introduces the example OpenID url. + /// + [Bindable(true)] + [Category("Appearance")] + [DefaultValue(ExamplePrefixDefault)] + [Localizable(true)] + [Description("The text that introduces the example OpenID url.")] + public string ExamplePrefix { + get { return this.examplePrefixLabel.Text; } + set { this.examplePrefixLabel.Text = value; } + } + + /// + /// Gets or sets the example OpenID Identifier to display to the user. + /// + [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "Property grid only supports primitive types.")] + [Bindable(true)] + [Category("Appearance")] + [DefaultValue(ExampleUrlDefault)] + [Localizable(true)] + [Description("The example OpenID Identifier to display to the user.")] + public string ExampleUrl { + get { return this.exampleUrlLabel.Text; } + set { this.exampleUrlLabel.Text = value; } + } + + /// + /// Gets or sets the text to display if the user attempts to login + /// without providing an Identifier. + /// + [Bindable(true)] + [Category("Appearance")] + [DefaultValue(RequiredTextDefault)] + [Localizable(true)] + [Description("The text to display if the user attempts to login without providing an Identifier.")] + public string RequiredText { + get { return this.requiredValidator.Text.Substring(0, this.requiredValidator.Text.Length - RequiredTextSuffix.Length); } + set { this.requiredValidator.ErrorMessage = this.requiredValidator.Text = value + RequiredTextSuffix; } + } + + /// + /// Gets or sets the text to display if the user provides an invalid form for an Identifier. + /// + [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "Property grid only supports primitive types.")] + [Bindable(true)] + [Category("Appearance")] + [DefaultValue(UriFormatTextDefault)] + [Localizable(true)] + [Description("The text to display if the user provides an invalid form for an Identifier.")] + public string UriFormatText { + get { return this.identifierFormatValidator.Text.Substring(0, this.identifierFormatValidator.Text.Length - RequiredTextSuffix.Length); } + set { this.identifierFormatValidator.ErrorMessage = this.identifierFormatValidator.Text = value + RequiredTextSuffix; } + } + + /// + /// Gets or sets a value indicating whether to perform Identifier + /// format validation prior to an authentication attempt. + /// + [Bindable(true)] + [Category("Behavior")] + [DefaultValue(UriValidatorEnabledDefault)] + [Description("Whether to perform Identifier format validation prior to an authentication attempt.")] + public bool UriValidatorEnabled { + get { return this.identifierFormatValidator.Enabled; } + set { this.identifierFormatValidator.Enabled = value; } + } + + /// + /// Gets or sets the text of the link users can click on to obtain an OpenID. + /// + [Bindable(true)] + [Category("Appearance")] + [DefaultValue(RegisterTextDefault)] + [Localizable(true)] + [Description("The text of the link users can click on to obtain an OpenID.")] + public string RegisterText { + get { return this.registerLink.Text; } + set { this.registerLink.Text = value; } + } + + /// + /// Gets or sets the URL to link users to who click the link to obtain a new OpenID. + /// + [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "Property grid only supports primitive types.")] + [Bindable(true)] + [Category("Appearance")] + [DefaultValue(RegisterUrlDefault)] + [Localizable(true)] + [Description("The URL to link users to who click the link to obtain a new OpenID.")] + public string RegisterUrl { + get { return this.registerLink.NavigateUrl; } + set { this.registerLink.NavigateUrl = value; } + } + + /// + /// Gets or sets the text of the tooltip to display when the user hovers + /// over the link to obtain a new OpenID. + /// + [Bindable(true)] + [Category("Appearance")] + [DefaultValue(RegisterToolTipDefault)] + [Localizable(true)] + [Description("The text of the tooltip to display when the user hovers over the link to obtain a new OpenID.")] + public string RegisterToolTip { + get { return this.registerLink.ToolTip; } + set { this.registerLink.ToolTip = value; } + } + + /// + /// Gets or sets a value indicating whether to display a link to + /// allow users to easily obtain a new OpenID. + /// + [Bindable(true)] + [Category("Appearance")] + [DefaultValue(RegisterVisibleDefault)] + [Description("Whether to display a link to allow users to easily obtain a new OpenID.")] + public bool RegisterVisible { + get { return this.registerLink.Visible; } + set { this.registerLink.Visible = value; } + } + + /// + /// Gets or sets the text that appears on the button that initiates login. + /// + [Bindable(true)] + [Category("Appearance")] + [DefaultValue(ButtonTextDefault)] + [Localizable(true)] + [Description("The text that appears on the button that initiates login.")] + public string ButtonText { + get { return this.loginButton.Text; } + set { this.loginButton.Text = value; } + } + + /// + /// Gets or sets the text of the "Remember Me" checkbox. + /// + [Bindable(true)] + [Category("Appearance")] + [DefaultValue(RememberMeTextDefault)] + [Localizable(true)] + [Description("The text of the \"Remember Me\" checkbox.")] + public string RememberMeText { + get { return this.rememberMeCheckBox.Text; } + set { this.rememberMeCheckBox.Text = value; } + } + + /// + /// Gets or sets the message display in the event of a failed + /// authentication. {0} may be used to insert the actual error. + /// + [Bindable(true)] + [Category("Appearance")] + [DefaultValue(FailedMessageTextDefault)] + [Localizable(true)] + [Description("The message display in the event of a failed authentication. {0} may be used to insert the actual error.")] + public string FailedMessageText { + get { return (string)ViewState[FailedMessageTextViewStateKey] ?? FailedMessageTextDefault; } + set { ViewState[FailedMessageTextViewStateKey] = value; } + } + + /// + /// Gets or sets the text to display in the event of an authentication canceled at the Provider. + /// + [Bindable(true)] + [Category("Appearance")] + [DefaultValue(CanceledTextDefault)] + [Localizable(true)] + [Description("The text to display in the event of an authentication canceled at the Provider.")] + public string CanceledText { + get { return (string)ViewState[CanceledTextViewStateKey] ?? CanceledTextDefault; } + set { ViewState[CanceledTextViewStateKey] = value; } + } + + /// + /// Gets or sets a value indicating whether the "Remember Me" checkbox should be displayed. + /// + [Bindable(true)] + [Category("Appearance")] + [DefaultValue(RememberMeVisibleDefault)] + [Description("Whether the \"Remember Me\" checkbox should be displayed.")] + public bool RememberMeVisible { + get { return this.rememberMeCheckBox.Visible; } + set { this.rememberMeCheckBox.Visible = value; } + } + + /// + /// Gets or sets a value indicating whether a successful authentication should result in a persistent + /// cookie being saved to the browser. + /// + [Bindable(true)] + [Category("Appearance")] + [DefaultValue(UsePersistentCookieDefault)] + [Description("Whether a successful authentication should result in a persistent cookie being saved to the browser.")] + public bool RememberMe { + get { return this.UsePersistentCookie; } + set { this.UsePersistentCookie = value; } + } + + /// + /// Gets or sets the starting tab index to distribute across the controls. + /// + public override short TabIndex { + get { + return base.TabIndex; + } + + set { + unchecked { + this.WrappedTextBox.TabIndex = (short)(value + TextBoxTabIndexOffset); + this.loginButton.TabIndex = (short)(value + LoginButtonTabIndexOffset); + this.rememberMeCheckBox.TabIndex = (short)(value + RememberMeTabIndexOffset); + this.registerLink.TabIndex = (short)(value + RegisterTabIndexOffset); + } + } + } + + /// + /// Gets or sets the tooltip to display when the user hovers over the login button. + /// + [Bindable(true)] + [Category("Appearance")] + [DefaultValue(ButtonToolTipDefault)] + [Localizable(true)] + [Description("The tooltip to display when the user hovers over the login button.")] + public string ButtonToolTip { + get { return this.loginButton.ToolTip; } + set { this.loginButton.ToolTip = value; } + } + + /// + /// Gets or sets the validation group that the login button and text box validator belong to. + /// + [Category("Behavior")] + [DefaultValue(ValidationGroupDefault)] + [Description("The validation group that the login button and text box validator belong to.")] + public string ValidationGroup { + get { + return this.requiredValidator.ValidationGroup; + } + + set { + this.requiredValidator.ValidationGroup = value; + this.loginButton.ValidationGroup = value; + } + } + + /// + /// Gets or sets the unique hash string that ends your idselector.com account. + /// + [Category("Behavior")] + [Description("The unique hash string that ends your idselector.com account.")] + public string IdSelectorIdentifier { + get { return (string)(ViewState[IdSelectorIdentifierViewStateKey]); } + set { ViewState[IdSelectorIdentifierViewStateKey] = value; } + } + + #endregion + + #region Properties to hide + + /// + /// Gets or sets a value indicating whether a FormsAuthentication + /// cookie should persist across user sessions. + /// + [Browsable(false), Bindable(false)] + public override bool UsePersistentCookie { + get { + return base.UsePersistentCookie; + } + + set { + base.UsePersistentCookie = value; + + // use conditional here to prevent infinite recursion + // with CheckedChanged event. + if (this.rememberMeCheckBox.Checked != value) { + this.rememberMeCheckBox.Checked = value; + } + } + } + + #endregion + + /// + /// Creates the child controls. + /// + protected override void CreateChildControls() { + // Don't call base.CreateChildControls(). This would add the WrappedTextBox + // to the Controls collection, which would implicitly remove it from the table + // we have already added it to. + + // Just add the panel we've assembled earlier. + this.Controls.Add(this.panel); + + if (ShouldBeFocused) { + WrappedTextBox.Focus(); + } + } + + /// + /// Initializes the child controls. + /// + protected override void InitializeControls() { + base.InitializeControls(); + + this.panel = new Panel(); + + Table table = new Table(); + TableRow row1, row2, row3; + TableCell cell; + table.Rows.Add(row1 = new TableRow()); + table.Rows.Add(row2 = new TableRow()); + table.Rows.Add(row3 = new TableRow()); + + // top row, left cell + cell = new TableCell(); + this.label = new HtmlGenericControl("label"); + this.label.InnerText = LabelTextDefault; + cell.Controls.Add(this.label); + row1.Cells.Add(cell); + + // top row, middle cell + cell = new TableCell(); + cell.Controls.Add(this.WrappedTextBox); + row1.Cells.Add(cell); + + // top row, right cell + cell = new TableCell(); + this.loginButton = new Button(); + this.loginButton.ID = "loginButton"; + this.loginButton.Text = ButtonTextDefault; + this.loginButton.ToolTip = ButtonToolTipDefault; + this.loginButton.Click += this.LoginButton_Click; + this.loginButton.ValidationGroup = ValidationGroupDefault; +#if !Mono + this.panel.DefaultButton = this.loginButton.ID; +#endif + cell.Controls.Add(this.loginButton); + row1.Cells.Add(cell); + + // middle row, left cell + row2.Cells.Add(new TableCell()); + + // middle row, middle cell + cell = new TableCell(); + cell.Style[HtmlTextWriterStyle.Color] = "gray"; + cell.Style[HtmlTextWriterStyle.FontSize] = "smaller"; + this.requiredValidator = new RequiredFieldValidator(); + this.requiredValidator.ErrorMessage = RequiredTextDefault + RequiredTextSuffix; + this.requiredValidator.Text = RequiredTextDefault + RequiredTextSuffix; + this.requiredValidator.Display = ValidatorDisplay.Dynamic; + this.requiredValidator.ControlToValidate = WrappedTextBox.ID; + this.requiredValidator.ValidationGroup = ValidationGroupDefault; + cell.Controls.Add(this.requiredValidator); + this.identifierFormatValidator = new CustomValidator(); + this.identifierFormatValidator.ErrorMessage = UriFormatTextDefault + RequiredTextSuffix; + this.identifierFormatValidator.Text = UriFormatTextDefault + RequiredTextSuffix; + this.identifierFormatValidator.ServerValidate += this.IdentifierFormatValidator_ServerValidate; + this.identifierFormatValidator.Enabled = UriValidatorEnabledDefault; + this.identifierFormatValidator.Display = ValidatorDisplay.Dynamic; + this.identifierFormatValidator.ControlToValidate = WrappedTextBox.ID; + this.identifierFormatValidator.ValidationGroup = ValidationGroupDefault; + cell.Controls.Add(this.identifierFormatValidator); + this.errorLabel = new Label(); + this.errorLabel.EnableViewState = false; + this.errorLabel.ForeColor = System.Drawing.Color.Red; + this.errorLabel.Style[HtmlTextWriterStyle.Display] = "block"; // puts it on its own line + this.errorLabel.Visible = false; + cell.Controls.Add(this.errorLabel); + this.examplePrefixLabel = new Label(); + this.examplePrefixLabel.Text = ExamplePrefixDefault; + cell.Controls.Add(this.examplePrefixLabel); + cell.Controls.Add(new LiteralControl(" ")); + this.exampleUrlLabel = new Label(); + this.exampleUrlLabel.Font.Bold = true; + this.exampleUrlLabel.Text = ExampleUrlDefault; + cell.Controls.Add(this.exampleUrlLabel); + row2.Cells.Add(cell); + + // middle row, right cell + cell = new TableCell(); + cell.Style[HtmlTextWriterStyle.Color] = "gray"; + cell.Style[HtmlTextWriterStyle.FontSize] = "smaller"; + cell.Style[HtmlTextWriterStyle.TextAlign] = "center"; + this.registerLink = new HyperLink(); + this.registerLink.Text = RegisterTextDefault; + this.registerLink.ToolTip = RegisterToolTipDefault; + this.registerLink.NavigateUrl = RegisterUrlDefault; + this.registerLink.Visible = RegisterVisibleDefault; + cell.Controls.Add(this.registerLink); + row2.Cells.Add(cell); + + // bottom row, left cell + cell = new TableCell(); + row3.Cells.Add(cell); + + // bottom row, middle cell + cell = new TableCell(); + this.rememberMeCheckBox = new CheckBox(); + this.rememberMeCheckBox.Text = RememberMeTextDefault; + this.rememberMeCheckBox.Checked = RememberMeDefault; + this.rememberMeCheckBox.Visible = RememberMeVisibleDefault; + this.rememberMeCheckBox.CheckedChanged += this.RememberMeCheckBox_CheckedChanged; + cell.Controls.Add(this.rememberMeCheckBox); + row3.Cells.Add(cell); + + // bottom row, right cell + cell = new TableCell(); + row3.Cells.Add(cell); + + // this sets all the controls' tab indexes + this.TabIndex = TabIndexDefault; + + this.panel.Controls.Add(table); + + this.idselectorJavascript = new Literal(); + this.panel.Controls.Add(this.idselectorJavascript); + } + + /// + /// Customizes HTML rendering of the control. + /// + /// An that represents the output stream to render HTML content on the client. + protected override void Render(HtmlTextWriter writer) { + // avoid writing begin and end SPAN tags for XHTML validity. + RenderContents(writer); + } + + /// + /// Renders the child controls. + /// + /// The object that receives the rendered content. + protected override void RenderChildren(HtmlTextWriter writer) { + if (!this.DesignMode) { + this.label.Attributes["for"] = this.WrappedTextBox.ClientID; + + if (!string.IsNullOrEmpty(this.IdSelectorIdentifier)) { + this.idselectorJavascript.Visible = true; + this.idselectorJavascript.Text = @" +"; + } else { + this.idselectorJavascript.Visible = false; + } + } + + base.RenderChildren(writer); + } + + /// + /// Adds failure handling to display an error message to the user. + /// + /// The response. + protected override void OnFailed(IAuthenticationResponse response) { + base.OnFailed(response); + + if (!string.IsNullOrEmpty(this.FailedMessageText)) { + this.errorLabel.Text = string.Format(this.FailedMessageText, response.Exception.Message); + this.errorLabel.Visible = true; + } + } + + /// + /// Adds authentication cancellation behavior to display a message to the user. + /// + /// The response. + protected override void OnCanceled(IAuthenticationResponse response) { + base.OnCanceled(response); + + if (!string.IsNullOrEmpty(this.CanceledText)) { + this.errorLabel.Text = this.CanceledText; + this.errorLabel.Visible = true; + } + } + + /// + /// Fires the event. + /// + /// + /// Returns whether the login should proceed. False if some event handler canceled the request. + /// + protected virtual bool OnLoggingIn() { + EventHandler loggingIn = this.LoggingIn; + if (this.Request == null) { + this.CreateRequest(); + } + + if (this.Request != null) { + OpenIdEventArgs args = new OpenIdEventArgs(this.Request); + if (loggingIn != null) { + loggingIn(this, args); + } + + return !args.Cancel; + } else { + return false; + } + } + + /// + /// Fires the event. + /// + protected virtual void OnRememberMeChanged() { + EventHandler rememberMeChanged = this.RememberMeChanged; + if (rememberMeChanged != null) { + rememberMeChanged(this, new EventArgs()); + } + } + + /// + /// Handles the ServerValidate event of the identifierFormatValidator control. + /// + /// The source of the event. + /// The instance containing the event data. + private void IdentifierFormatValidator_ServerValidate(object source, ServerValidateEventArgs args) { + args.IsValid = Identifier.IsValid(args.Value); + } + + /// + /// Handles the CheckedChanged event of the rememberMeCheckBox control. + /// + /// The source of the event. + /// The instance containing the event data. + private void RememberMeCheckBox_CheckedChanged(object sender, EventArgs e) { + this.RememberMe = this.rememberMeCheckBox.Checked; + this.OnRememberMeChanged(); + } + + /// + /// Handles the Click event of the loginButton control. + /// + /// The source of the event. + /// The instance containing the event data. + private void LoginButton_Click(object sender, EventArgs e) { + if (!this.Page.IsValid) { + return; + } + + if (this.OnLoggingIn()) { + this.LogOn(); + } + } + } +} -- 2.11.4.GIT