简体   繁体   English

C# - 执行验证的位置

[英]C# - Where to perform validation

I have a question about when to validate inputs on C# form applications. 我有一个关于何时验证C#表单应用程序输入的问题。

When taking inputs from a form application, you can run all the validation as you parse in the data from the text fields in the main form class, to then use wherever. 从表单应用程序获取输入时,您可以在解析主表单类中的文本字段中的数据时运行所有验证,然后在任何地方使用。 For example: 例如:

Method 1: 方法1:

class Car
{
    private string registration { set; get; } // NO VALIDATION HERE
}
// in the form class
private void add_Click(object sender, EventArgs e)
{
    // get registration
    int reg_valid = Validate.registration(txtReg.Text); // VALIDATION IS DONE HERE

    if(reg_valid)
    {
        Car.registration = txtReg.Text;
    } else {
        // Registration invalid - throw error
    }
}

There will be no validation in the actual Car object as it is all done in the form class. 实际的Car对象中没有验证,因为它都是在表单类中完成的。

The other way is to validate the input in the actual class it is stored in like: 另一种方法是验证存储在其中的实际类中的输入,如:


Method 2: 方法2:

class Car
{
    // out registration
    private string registration;

    // we can set our registration here and return false if there is an error
    bool set_registration(registration)
    {
        int reg_valid = Validate.registration(registration); // VALIDATION IS DONE HERE

        if(reg_valid) {
            this.registration = registration;
        } else {
            return false; // if error
        }

        return true; // if all goes well
    }
}
// in the form class
private void add_Click(object sender, EventArgs e)
{
    // get registration
    string registration = txtReg.Text; // NO VALIDATION DONE HERE

    // we can then store this in our object
    if( ! Car.set_registration(registration))
    {
        // Registration invalid - throw error
    }
}

Method 1 seems cleaner, but one of the reasons why we use private attributes is so that we can validate any parsing arguments which is why method 2 seems the better option. 方法1似乎更清晰,但我们使用私有属性的原因之一是我们可以验证任何解析参数,这就是方法2似乎更好的选择。 Also, using method 2, errors have to be carried up the stack possibly many times, which leads to repeated if statements. 此外,使用方法2,错误必须在堆栈中可能多次执行,这导致重复的if语句。

* Please note, this code has not been ran so may contain errors. *请注意,此代码尚未运行,因此可能包含错误。

You should have a Core of classes and also those classes related to the user interface. 您应该拥有类的核心以及与用户界面相关的那些类。 First, create the Core classes (the bussiness logic ) of your application. 首先,创建应用程序的Core类( 业务逻辑 )。 Of course, you should do validation here. 当然,你应该在这里做验证。 Actually, code as if you had no knowledge about who is going to implement the user interface. 实际上,代码就像你不知道谁将实现用户界面一样。 Also, take into account that users interfaces can change, while the core classes should remain untouched. 此外,考虑到用户界面可以更改,而核心类应保持不变。

When coding the user interface, you wil realise that if you don't do any validation, exceptions will be raised when the user inputs some data cases. 在对用户界面进行编码时,您将意识到如果您不进行任何验证,则在用户输入某些数据案例时会引发异常。 This will make you create validation also in the user interface classes. 这将使您在用户界面类中也创建验证。

So, the answer is, you will finally have validation on both parts of the application. 所以,答案是,您最终将对应用程序的两个部分进行验证。

Caution : Opinion-based answer ! 警告:基于意见的答案!

It depends on the requirements of the Project, its scale , and the time you have (and many other factors of importance like "User Friendly Application" or of no importance like "my own taste would be.." ) 这取决于项目的要求规模时间 (以及许多其他重要因素,如“用户友好应用程序”或不重要,如“我自己的品味会......”

Due to the wide range of possible cases per individual, the answer cannot be expressed in a general guideline which would fulfill the needs of everyone . 由于大范围的每个个体可能的情况下,答案不能在一般的准则 ,其中满足每个人的需求表达

Your question addressed two objects to manipulate : 您的问题解决了两个要操纵的对象:

  • one Form used as an User Interface where the user can input anything (assumption defined by "the user enter *any* value then click a Button" ) 一个用作用户界面的表单,用户可以在其中输入任何内容(假设由“用户输入*任意*值然后单击按钮”定义
  • a Class Property that is fed with the value (Setter) when it's valid. 一个Class属性,当它有效时,它被赋值(Setter)。

  • You have no time , requirements just tells "Input a value", this is a small task in the Project of no critical importance on a small class used within this Button, and you're very lazy : 没有时间 ,需求只是告诉“输入一个值”,这是Project中的一个小任务对于这个Button中使用的小类没有至关重要的意义,而且你很懒惰:

    => In Button Click, make a rigid validation with one or two forbidden checks that whip out any non allowed value, and shows a MessageBox depending on the nature of the Error. =>在按钮单击中,使用一个或两个禁止检查来进行严格验证,这些检查会删除任何非允许值,并根据错误的性质显示MessageBox。 Only pass a Value to the class and go further when you have a valid value. 只有将值传递给类,并在有有效值时更进一步。

.

  • You have no time , simple requirements, small task of no critical importance but with a class largely used , but you're lazy : 没有时间 ,简单的要求,没有关键重要性的小任务 ,但是主要使用了一个 ,但你很懒惰:

    => Move the validation above in the class then Throw one, two or three generic Exceptions with custom messages. => 在类中移动上面的验证,然后使用自定义消息抛出一个,两个或三个常规异常。 In any UI action using that class, use Try/Catch to capture the exception in the ButtonClick and display a MessageBox without going further in case of captured Exception. 在使用该类的任何UI操作中,使用Try / Catch捕获ButtonClick中的异常,并在捕获的异常情况下显示MessageBox而不进一步。

.

  • You have 5 minutes , simple requirements, small non critical task, important Class role : 您有5分钟 ,简单的要求, 小的非关键任务, 重要的类角色:

    => Like previously but, decide whether another way to show user is more recommended (depending of your target users) like changing the background color of a TextBox after you click a button, displaying the message in a Label, instead of annoying the user with a dialog box... =>像以前一样,但是,决定是否更推荐另一种显示用户的方式(取决于您的目标用户),例如在单击按钮后更改TextBox的背景颜色,在Label中显示消息,而不是让用户烦恼一个对话框......

.

  • You have 10 minutes , simple requirements, repetitive task , important Class role : 你有10分钟 ,简单的要求, 重复的任务重要的类角色

    => Find a way to quickly test a given value and creates a public or friend (static ?) function in your Class that returns the result of a validation attempt, like an UICarValidationEnum Enumeration (* see Edit below) of possible Validation Errors : =>找到一种快速测试给定值的方法,并在类中创建一个公共或朋友(静态?)函数该函数返回验证尝试的结果,如可能的验证错误的UICarValidationEnum枚举(*参见下面的编辑):
    a) UICarValidationEnum.Valid a) UICarValidationEnum.Valid
    b) .TooLong b) .TooLong
    c) .NullValue c) .NullValue
    d) .ForbiddenChars d)。 .ForbiddenChars
    e) ... e)......
    You can then use that validation Function inside and outside your Class. 然后,您可以在类的内部和外部使用该验证功能。 That way, you can handle invalid Setter attempts both before performing the Setter and while performing it , aswell as both using you class along with UI and without . 这样,您可以在执行Setter之前和执行Setter 时同时处理无效的Setter尝试,以及使用您的类以及UI不使用 The drawback is you can't avoid double validation if you check if the data is valid outside the class (In your Button for eg.) 缺点是,如果检查数据在类外是否有效,则无法避免双重验证(例如,在按钮中)

.

  • Time matters , but requirements is efficient inputs , repetitive task of great importance, class largely used - you can't be lazy : 时间的问题 ,但要求是高效的投入 ,非常重要的重复性任务,类大量使用 -你不能偷懒:

    => Handle both UI Validation and Class Validation. =>处理UI验证和类验证。 Why ? 为什么? The UI Part addresses the "efficient inputs" part, and the Class Part addresses the "role of the Class". UI部分解决“高效输入”部分,而类部分解决“类的角色”。 You can use the Validator Function above, or implement much more Exceptions upon Setter like in the other cases above. 您可以使用上面的验证器函数,或者在上面的其他情况下在Setter上实现更多的异常。 You're increasing the number of handled Exceptions/Invalid inputs by providing much more information to the user upon Button Click. 通过在按钮单击时向用户提供更多信息,您增加了处理的异常/无效输入的数量。
    Remember : Class largely used. 记住:类很常用。 The move here is to implement the maximum coding to handle different cases of invalid inputs, to reduce the amount of code to write elsewhere in your project while still be able to handle all those cases. 这里的举措是实现最大化编码以处理无效输入的不同情况, 以减少在项目中的其他位置写入的代码量,同时仍然能够处理所有这些情况。

.

  • Time matters , User Friendly Application, repetitive task, important Class : 时间很重要用户友好的应用程序, 重复的任务, 重要的课程:

    => Reconsider the UI Layout and behaviour, validation is mainly UI, but Class is still doing it by itself : =>重新考虑UI布局和行为,验证主要是UI,但Class仍然自己做:
    a) User hates Dialog Boxes (whatever DialogBox, message, openfile, etc.. avoid them whenever possible) a)用户讨厌对话框(无论是DialogBox,消息,openfile等等......尽可能避免使用它们)
    b) User hates aggressive colors b)用户讨厌侵略性的颜色
    c) User hates validation c)用户讨厌验证
    d) ...... User hates a lot of things..! d)......用户讨厌很多东西..!

    The role of the Function Validation above is of importance here : Capture the User Input actions. 上面的函数验证的作用非常重要:捕获用户输入操作。 If it's a TextBox for example, capture the TextChanged Event, then call the Class validation Function. 例如,如果它是TextBox,则捕获TextChanged事件,然后调用类验证函数。 Update information in a Label notifying any encountered error, and change the background color of the TextBox to a friendly color, but related to an error standard, like light Pink. 更新标签中的信息,通知任何遇到的错误,并将TextBox的背景颜色更改为友好的颜色,但与错误标准相关,如浅粉红色。 Don't use Color.Red, that's too agressive for most users. 不要使用Color.Red,这对大多数用户来说过于激进。 Seeing Red 200 times a day can lead to unexpected behaviours at the end of the day. 每天看200次红色可能会在一天结束时导致意外行为。
    Only enable the Button when ALL Inputs are valid. 仅在所有输入有效时启用按钮。 Don't make a button per input, User hates endless validations. 不要为每个输入创建一个按钮,用户讨厌无休止的验证。

.

  • Time doesn't really matter , User Friendly Application, repetitive task, important Class : 时间并不重要用户友好的应用程序, 重复的任务, 重要的类:

    => Along with the previous option, improve UI response. =>与之前的选项一起,改进UI响应。
    Add graphic icons in your label, and/or consider the use of an ErrorProvider in your Form. 在标签中添加图形图标,和/或考虑在表单中使用ErrorProvider。 Users like simple icons rather than long similar messages. 用户喜欢简单的图标,而不是类似的长消息。
    Consider the use friendly sounds to notify poping errors (not everyone is "kind of visual receptive" ) 考虑使用友好的声音来通知弹出错误(不是每个人都是“视觉接受”

.

  • Time doesn't matter at all , User Friendly Application, repetitive task, important Class : 时间无关紧要用户友好的应用程序, 重复的任务, 重要的类:

    => Keep capturing user inputs live, but implement suggestive corrections related to. =>继续实时捕获用户输入,但实施与之相关的暗示性更正。 If it's a TextBox, use AutoCompleteCollection with predefined and fixed Inputs suggestions in the collection when user attempts to input invalid Datas (you'll need a Function for that). 如果它是TextBox,当用户尝试输入无效数据时,请在集合中使用带有预定义和固定输入建议的AutoCompleteCollection(您需要一个函数)。
    Live disable other inputs when they are incompatible with user's choices/actions. 当与用户的选择/操作不兼容时,实时禁用其他输入。

.

  • Time really matters , User Friendly recommended, repetitive task, important Class : 时间真的很重要用户友好推荐, 重复性任务, 重要的课程:

    => Don't overload your Form with validations routines. =>不要使用验证例程重载表单
    Create a Custom Control for that specific Input. 为该特定输入创建Custom Control Overload your custom control with the validations. 使用验证重载您的自定义控件 If your class cannot be used outside this UI, don't bother creating validations inside. 如果您的类无法在此UI之外使用,请不要在内部创建验证。 Move everything in your custom Control and, even forbid invalid chars (if it's a textbox) whenever possible. 移动自定义控件中的所有内容,甚至禁止无效字符(如果它是文本框)。 In this specific case, you're going to use the UserControl as the Validator component of your Datas. 在这种特定情况下, 您将使用UserControl作为Datas的Validator组件。
    Otherwise, use the Class validating Function scheme upon Input and display the appropriate error in an User Friendly way whenever possible through that user Control (case where your Class may be used without the UserControl, which would generally be the case) 否则,在输入时使用类验证功能方案,并尽可能通过该用户控件以用户友好的方式显示相应的错误(在没有UserControl的情况下可以使用您的类的情况,通常是这种情况)


Etc. etc. etc. 等等

I know I'm more considering the user who's going to use the Application than the one who's going to write the code. 我知道我更倾向于考虑使用应用程序的用户而不是要编写代码的用户。 But have a look at Baltasarq's answer . 但看看Baltasarq的答案 In fact, best move is to do both. 事实上,最好的举措就是两者兼而有之。 The three first options are there only for a simple Class of no real importance in your whole Project. 这三个第一选项只适用于整个项目中没有真正重要性的简单类。 We've all created that tiny Class to begin with, and we didn't bother to make strong validation controls. 我们都开始创建那个小类,我们没有打算做出强有力的验证控制。 I think that most of us still use those kind of classes. 我认为我们大多数人仍然使用这类课程。 However, as times goes by, some of those classes become more and more used.. then we were faced to the need of stronger validations schemes. 然而,随着时间的推移, 其中一些类的使用越来越多。然后我们面临着更强的验证方案的需要。

That's the cutting corners way . 那是偷工减料的方式 It's unsafe, but it works... until something happens... 这是不安全的,但它有效......直到发生一些事情......

=> Try to do both whenever possible. =>尽可能尝试两者兼顾。 You don't know what may happen, who is going to use your Class, whether your boss will change his mind and ask you to create a specific UI for that Class to be user friendly for everyone... 你不知道会发生什么,谁会使用你的班级,你的老板是否会改变主意并要求你为该班级创建一个特定的用户界面,以便每个人都能使用...

Since a while, when I have time upon creating a Class, I'm in fact creating at least two classes : 有一段时间,当我有时间创建一个类时,我实际上创建了至少两个类:

- MyClass_Class - MyClass_Class
- MyClass_Form - MyClass_Form
- (and perhaps MyClass_UserControl ) - (也许是MyClass_UserControl
- (and perhaps MyChildClass WithUnsafeSetters - used to set value that has already been validated for performance needs) - (也许是MyChildClass WithUnsafeSetters - 用于设置已经验证了性能需求的值)

The core class always provide live Validation capabilities... 核心类始终提供实时验证功能......


EDIT : 编辑:

Property Validator Function sample using enums... 使用枚举的属性验证器函数示例...

[Flags]
public enum registrationValidation_Enum
{
    Valid = 0x01,
    TooLong = 0x02,
    InvalidChars = 0x04,
    NullEntry = 0x08
    // ...
}

This enum can be encapsulated within the class. 这个枚举可以封装在类中。 Enums are easier to handle/memorize/retrieve than Exceptions Classes. 枚举比异常类更容易处理/记忆/检索。

This is the Property Getter/Setter. 这是Property Getter / Setter。

class Car
{
    private string registration = "Unknown";

    public string Registration
    {
        get
        {
            return registration;
        }
        set
        {
            validate_registration(value, True);
            // Setter for the Property.
            // Throws an Exception upon invalid value.
        }
    }
}

This is a Validator Function which is Public : 这是一个公共的Validator函数:

    public registrationValidation_Enum test_registration(
        string newRegistration)
    {
        registrationValidation_Enum checkResult =
            registrationValidation_Enum.Valid;

        // Do the checks here
        if (newRegistration.Length > 10)
        {
            checkResult = checkResult | registrationValidation_Enum.TooLong;
        }
        if (containsNonAlphNumericChars(newRegistration))
        {
            checkResult = checkResult | registrationValidation_Enum.InvalidChars;
        }
        // ...
        return checkResult;
    }

And here is the Public version of the Setter : 这是塞特的公共版本:

    // this bypass the double check : attempts to set the value if Valid.
    // otherwise, either returns a validation result,
    // either throws an exception.
    public registrationValidation_Enum validate_registration(
        string newRegistration,
        bool canThrowException)
    {
        bool isValid = test_registration(newRegistration);
        if (isValid == registrationValidation_Enum.Valid)
        {
            registration = newRegistration;
            return registrationValidation_Enum.Valid;
        }
        else
        {
            if (canThrowException)
            {
                string exceptionMessage = "";
                if (isValid | registrationValidation_Enum.TooLong)
                {
                    exceptionMessage += "Registration too long" 
                                     + Environment.NewLine;
                }
                if (isValid | registrationValidation_Enum.InvalidChars)
                {
                    exceptionMessage += 
                        "Registration contains invalid characters"
                        + Environment.NewLine;
                }
                // ....
                Throw New Exception(exceptionMessage);
            }
            else
            {
                return isValid;
            }
        }
    }

The public validate_registration(string, false) is there in case : 公共 validate_registration(string, false)有以下情况:

  • you don't want to double the validation upon Button Click 你不想在Button Click上加倍验证
  • you don't live monitor the user Inputs 你没有监控用户输入
  • you don't want to handle cascading exceptions everywhere 你不想在任何地方处理级联异常
  • you want have control over custom error messages per UI context (you can't always have a label to write errors in) 你想要控制每个UI上下文的自定义错误消息(你不能总是有一个标签来写错误)
  • while attempting to set registration value, all in a row with a button click. 在尝试设置注册值时,单击按钮就连续排列。

Put the validation result in a variable in any UI-side and display the appropriate notifications/users choices depending on the UI components you can display... This wouldn't have been that simple with Exceptions : Imagine you have both TooLong and InvalidChars. 将验证结果放在任何UI端的变量中,并根据您可以显示的UI组件显示相应的通知/用户选择...这对于Exceptions来说不是那么简单:想象一下,您同时拥有TooLong和InvalidChars。 Are you going to show a dialogbox "Too Long" then click button then show another "Invalid Chars" dialog ? 你打算显示一个“Too Long”对话框,然后点击按钮,然后显示另一个“Invalid Chars”对话框?

Note : To make the class Localizable , with custom Exception messages using Culture, I would define Class Level Messages (string) variables which values depend on the loaded Culture. 注意:要使用Culture创建类Localizable ,使用自定义Exception消息,我将定义类级别消息(字符串)变量,其值取决于加载的Culture。

You would use public properties with private members, where you do the checking inside the properties and then assign it to class members. 您可以将公共属性与私有成员一起使用,您可以在其中对属性进行检查,然后将其分配给类成员。

class Car
{
    private string registration;

    public string Registration 
    {
        get { return registration;}
        set { 
                if(Validate.registration(value))
                    registration = value;
                else
                    throw new Exception("Your exception message here");
            }

    }
}

private void add_Click(object sender, EventArgs e)
{
    Car.Registration = txtReg.Text;
}

There are 2 validations: 有2个验证:

  • data type/property limits/parsing; 数据类型/属性限制/解析;
  • logical (values between properties); 逻辑(属性之间的值);

Have a look at PropertyGrid . 看看PropertyGrid It's basic (and pretty sufficient) validation is if your property accept value or throw: 如果您的属性接受值或抛出,那么基本(并且非常充分)验证是:

class Car
{
    private int _someValue;
    public int SomeValue
    {
        get { return _someValue; }
        set
        {
            if(value > 100)
                throw new OutOfRangeException(...);
            _someValue = value;
        }
    }
}

This ensures what Car can validate own properties no matter how they are set (deserialization, PropertyGrid , direct property value, reflection, etc). 这确保了Car可以验证自己的属性,无论它们如何设置(反序列化, PropertyGrid ,直接属性值,反射等)。

Another thing is some logical validation, which can't be performed at instance level, or you simply don't care about it (let instance to be created without exceptions). 另一件事是一些逻辑验证,它不能在实例级别执行,或者你根本不关心它(让实例创建没有例外)。 This one has to be put into editor code (form what is used to create instance). 这个必须放入编辑器代码(形成用于创建实例的内容)。

As for myself, talking about TextBox , I prefer to make custom MyTextBox control which has all parsing related validation (methods GetDouble() , GetInt() , GetIntTime() , etc) and possibility to setup/query it (properties IsDouble , IsInt , etc.). 至于我自己,谈论TextBox ,我更喜欢自定义MyTextBox控件,它具有所有解析相关的验证(方法GetDouble()GetInt()GetIntTime()等)以及设置/查询它的可能性(属性IsDoubleIsInt ,等等。)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM