Make Foreach after meeting a condition in List - c#

I am creating a static sign in page using List to save the data. I am using a ForEach to loop through the list but the issue I am facing is I want my for loop to stop immediately the condition is true.
NB: I have tried using a break and a return but they are not working as expected.
The code is here:
List<User> users = new List<User>(3);
public MainWindow()
{
InitializeComponent();
User superAdmin = new User()
{
userType = "Super Admin",
uniqueCode = "123456",
password = "password1"
};
User admin = new User()
{
userType = "Admin",
uniqueCode = "654321",
password = "password16"
};
User userOperator = new User()
{
userType = "Operator",
uniqueCode = "109105",
password = "specialpassword"
};
users.Add(superAdmin);
users.Add(admin);
users.Add(userOperator);
}
private void login_OnClick(object sender, RoutedEventArgs e)
{
string userType = cmbAdminType.Text;
string uniqueCode = txtUniqueCode.Text;
string password = txtPassword.Text;
foreach (User userPick in users)
{
if (userPick.userType == userType && userPick.uniqueCode == uniqueCode)
{
MessageBox.Show("Cool you are in!");
break;
}
else
{
MessageBox.Show("Err, not found!");
break;
}
}
}
}
public class User
{
public string userType { get; set; }
public string uniqueCode { get; set; }
public string password { get; set; }
}
Please, what else can I do?

Others have pointed out the flaw in your current code, but I'd suggest using a LINQ approach here. It's much shorter and easier to read - at least when you're used to LINQ:
bool validUser = users.Any(user => user.userType == userType && user.uniqueCode == uniqueCode);
MessageBox.Show(validUser ? "Cool you are in!" : "Err, not found!");
Any is short-circuiting: it stops as soon as it finds a match.
As a side-note, I'd strongly encourage you to start following .NET naming conventions for your properties.

I believe that this could be because you have a logical error in the way that your code is working.
Currently, you break from your foreach loop on the first successful match, or the first unsucessful match. So basically, your enumeration will break after 1 iteration whether successful or not.
You could introduce a flag that you use to record success, or not, and then test this after the enumeration, as so:
private void login_OnClick(object sender, RoutedEventArgs e)
{
string userType = cmbAdminType.Text;
string uniqueCode = txtUniqueCode.Text;
string password = txtPassword.Text;
bool isMatched = false;
foreach (User userPick in users)
{
if (userPick.userType == userType && userPick.uniqueCode == uniqueCode)
{
isMatched = true;
break;
}
}
if (isMatched)
{
MessageBox.Show("Cool you are in!");
}
else
{
MessageBox.Show("Err, not found!");
}
}

Your loop stops after the first object found in the list. I think what you want to accomplish is that it breaks either when the object you looked for is found either at the end of the loop, if the object you were looking for had not been found. The code:
bool userInside = false;
foreach (User userPick in users)
{
if (userPick.userType == userType && userPick.uniqueCode == uniqueCode)
{
MessageBox.Show("Cool you are in!");
userInside=true;
break;
}
}
if (userInside==false)
{
MessageBox.Show("Err, not found!");
}
I hope this helps. ^^

Related

How to create separated class that detect if the global int is certain number, and then overwrites label.text in my form?

I want to create global class(So I have to write it only once, not in all forms )that should detect if the global int is certain number and then overwrite text in label that says what is wrong.
For example when your name is too short it will says that your name is too short.
I know that you can do this in form, but I want whole new separated class because I plan to do this for 3 forms, so I would have to copy and paste the same errors etc... in each of them.
example:
Code in form register_menu.cs
public void getErrorNumber()
{
if (char_amount_username < 3 || textBox_username.Text == "Username")
{
Variables.error_number = 1;
}
else if (email_adress.Contains("#") != true || textBox_emailadress.Text == "Email adress")
{
Variables.error_number = 2;
}
else if (char_amount_password < 7 || textBox_password.Text == "Password")
{
Variables.error_number = 3;
}
else if (textBox_password.Text != textBox_passwordconfirm.Text)
{
Variables.error_number = 4;
}
else
{
Variables.error_number = 0;
}
}
private void button1_Click_1(object sender, EventArgs e)
{
ErrorCheck();
}
Code in class named GlobalErrorChecker.cs
namespace Xenious
{
internal class ErrorVariable
{
public static int error_number;
public void ErrorCheck()
getErrorNumber();
{
if (ErrorVariable.error_number is 1) ;
{
register_menu.error_msg.Text = "Your username is invalid!\n ⬤ Username must be avaiable\n ⬤ Minimum lenght of username is 4 ";
register_menu.displayERROR(true);
}
if(ErrorVariable.error_number is 2);
{
register_menu.error_message.Visible = true;
register_menu.error_msg.Text = "Your password is invalid!\n ⬤ Minimum lenght of password is 8 ";
register_menu.ICON_password_error.Visible = true;
}
if (ErrorVariable.error_number is 6) ;
{
login_menu.error_message.Visible = true;
login_menu.error_msg.Text = "Your username and password do not match!";
login_menu.ICON_username_error.Visible = true;
login_menu.ICON_password_error.Visible = true;
}
}
}
}
This is just example code how I want to do this, I have multiple problems.
First problem is that class GlobalErrorChecker.cs doesnt detect any controls from forms at all. I tried to fix this by looking online but I could find anything.
Second problem is that it says register_menu.ICON_password_error is unavaiable due to protection error CS0122....
I tried quite a huge amount of different methods how to do this, but I only found how to do this between 2 diffent forms, not between form and new class
Consider using a validation library like FluentValidation.
First create a class with properties to validate e.g.
public class Person
{
public string UserName { get; set; }
public string EmailAddress { get; set; }
public string Password { get; set; }
public string PasswordConfirmation { get; set; }
}
Create a validator.
public class PersonValidator : AbstractValidator<Person>
{
public PersonValidator()
{
RuleFor(person => person.UserName)
.NotEmpty()
.MinimumLength(3);
RuleFor(person => person.EmailAddress).EmailAddress();
RuleFor(person => person.Password.Length).GreaterThan( 7);
RuleFor(person => person.Password).Equal(p => p.PasswordConfirmation);
}
}
Create a new instance of the class, in this case Person. Populate values from your controls and than perform validation via Validate off the PersonValidator.
Below is a mock-up which uses StringBuilder to create a string which can be presented to the user.
Person person = new Person()
{
UserName = "billyBob",
Password = "my#Password1",
EmailAddress = "billyBob#gmailcom",
PasswordConfirmation = "my#Password1"
};
PersonValidator validator = new PersonValidator();
ValidationResult result = validator.Validate(person);
if (result.IsValid)
{
// person is valid
}
else
{
StringBuilder builder = new StringBuilder();
foreach (var error in result.Errors)
{
builder.AppendLine(error.ErrorMessage);
}
}
Dig in to ValidationResult if you need to inspect individual errors like PropertyName (Name of the property being validated) and ComparisonValue (Value that the property should not equal).
Note The email address validator only checks if an email address has a # so if you need more custom work is needed.
Making a static Extension Method
is one way to "create global class (so I have to write it only once...)". This answer will explore this option step-by-step.
First ask "what info is needed to determine a valid form?" Define these requirements in an interface:
interface IValidate
{
// Inputs needed
public string Username { get; }
public string Email { get; }
public string Password { get; }
public string Confirm { get; }
}
Now declare an Extension Method for "any" form that implements IValidate (this is the global class you asked about).
static class Extensions
{
public static int ValidateForm(this IValidate #this, CancelEventArgs e)
{
if (#this.Username.Length < 3) return 1;
if (!#this.Email.Contains("#")) return 2;
if (#this.Password.Length < 7) return 3;
if (!#this.Password.Equals(#this.Confirm)) return 4;
return 0;
}
}
The Form classes will be able to call the extension using this.
var e = new new CancelEventArgs();
int error_number = this.ValidateForm(e);
Example of Form Validation
For each of your 3 forms, implement the interface. This just means that the 3 form classes are making a promise or contract to provide the information that the interface requires (in this case by retrieving the text from the textboxes).
public partial class MainForm : Form, IValidate
{
#region I M P L E M E N T I N T E R F A C E
public string Username => textBoxUsername.Text;
public string Email => textBoxEmail.Text;
public string Password => textBoxPassword.Text;
public string Confirm => textBoxConfirm.Text;
#endregion I M P L E M E N T I N T E R F A C E
.
.
.
}
Then call the extension method when any textbox loses focus or receives an Enter key.
public partial class MainForm : Form, IValidate
{
public MainForm()
{
InitializeComponent();
foreach (Control control in Controls)
{
if(control is TextBox textBox)
{
textBox.TabStop = false;
textBox.KeyDown += onAnyTextboxKeyDown;
textBox.Validating += onAnyTextBoxValidating;
textBox.TextChanged += (sender, e) =>
{
if (sender is TextBox textbox) textbox.Modified = true;
};
}
}
}
private void onAnyTextBoxValidating(object? sender, CancelEventArgs e)
{
if (sender is TextBox textBox)
{
// Call the extension method to validate.
ErrorInt #int = (ErrorInt)this.ValidateForm(e);
if (#int.Equals(ErrorInt.None))
{
labelError.Visible = false;
buttonLogin.Enabled = true;
return;
}
else if (textBox.Modified)
{
buttonLogin.Enabled = false;
BeginInvoke(() =>
{
switch (#int)
{
case ErrorInt.Username: textBoxUsername.Focus(); break;
case ErrorInt.Email: textBoxEmail.Focus(); break;
case ErrorInt.Password: textBoxPassword.Focus(); break;
case ErrorInt.Confirm: textBoxConfirm.Focus(); break;
}
labelError.Visible = true;
labelError.Text = typeof(ErrorInt)
.GetMember(#int.ToString())
.First()?
.GetCustomAttribute<DescriptionAttribute>()
.Description;
textBox.Modified = false;
textBox.SelectAll();
});
}
}
}
private void onAnyTextboxKeyDown(object? sender, KeyEventArgs e)
{
if (sender is TextBox textbox)
{
if (e.KeyData.Equals(Keys.Return))
{
// Handle the Enter key.
e.SuppressKeyPress = e.Handled = true;
MethodInfo? validate = typeof(TextBox).GetMethod("OnValidating", BindingFlags.Instance | BindingFlags.NonPublic);
CancelEventArgs eCancel = new CancelEventArgs();
validate?.Invoke(textbox, new[] { eCancel });
}
}
}
.
.
.
}
Where:
enum ErrorInt
{
None = 0,
[Description("Username must be at least 3 characters.")]
Username = 1,
[Description("Valid email is required.")]
Email = 2,
[Description("Password must be at least 7 characters.")]
Password = 3,
[Description("Passwords must match.")]
Confirm = 4,
}

C# QUery if user exists - Passing Domain name from Combobox returns default domain

I have a line of class with the following
namespace MYApp.ISec
{
public class ActiveDirectory
{
public static string DomainValue { get; set; }
public PrincipalContext mydom = new PrincipalContext(ContextType.Domain, DomainValue);
public bool DoesUserExist(string userName)
{
{
using (UserPrincipal user = UserPrincipal.FindByIdentity(mydom, userName))
{
if (user != null)
{
MessageBox.Show("User Found");
return true;
}
else
{
MessageBox.Show("No User");
return false;
}
}
}
}
}
}
and on my winform i have this
private void metroButton1_Click(object sender, EventArgs e)
{
if (comboBox1.SelectedIndex == -1 || metroTextBox2.Text == "")
{
MessageBox.Show("Domain or Username has not been completed, Please try again!", "Validation Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
else
{
ActiveDirectory.DomainValue = comboBox1.SelectedItem.ToString() ;
ActiveDirectory.DoesUserExist(metroTextBox2.Text);
}
}
there are two domain names within this combobox lets just say Domain1 and domain2
No matter what i select on the combobox it always defaults to my connected domain which is domain1 even if i select domain2
Does anyone have any solutions please because i have been at this for 5 hours now and it seems like i am missing something to easy
Try this:
public static bool DoesUserExist(string domain, string userName)
{
using (var context = new PrincipalContext(ContextType.Domain, domain))
{
var user = UserPrincipal.FindByIdentity(context, userName);
if (user != null)
{
return true;
}
}
return false;
}
And you will need to add into usings:
using System.DirectoryServices.AccountManagement;
PrincipalContext will be of type domain and particular domain will be set from param. Search is done trough identity (user name) on domain specified in context. PrincipalContext is IDisposable so should be properly disposed.
PrincipalContext dosen't change when change property
ActiveDirectory.DomainValue = comboBox1.SelectedItem.ToString() ;
Try to create your Principal at start of DoesUserExist method
public bool DoesUserExist(string userName)
{
mydom = new PrincipalContext(ContextType.Domain, DomainValue);
using (UserPrincipal user = UserPrincipal.FindByIdentity(mydom, userName))
{
if (user != null)
{
MessageBox.Show("User Found");
return true;
}
else
{
MessageBox.Show("No User");
return false;
}
}
}

How do I access variables from an object list?

I created a class User, which contain simple variables as shown below:
public class User
{
public string username; //Unique usernames
public string password;
}
I then instantiate a list of an object in another class:
List<User> user = new List<User>();
user.Add(new User {username = "admin", password = "123"});
How is it possible for me to retrieve the password's value by searching for the username using a foreach loop? I am probably just confused but this is what I came up with:
foreach(var item in user)
{
if(item.Equals(username_input))
{
//I try to store the password into a string pass_check
pass_check = item.password;
}
}
if (user_input.Equals(pass_check))
{
Console.WriteLine("Login successful");
}
Sorry if this seems like a dense question to anyone out there, still a beginner trying to learn!
You're pretty close..
if(item.username.Equals(username_input))
You need to check the property of the item in this case which is username.
You could even shorten it to:
foreach(var item in user)
{
if(item.username.Equals(username_input)
&& user_input.Equals(item.password))
{
Console.WriteLine("Login successful");
break; // no need to check more.. or is there?
}
}
You can get really fancy using Linq:
if (user.Any(i => i.username.Equals(username_input)
&& user_input.Equals(i.password))
{
Console.WriteLine("Login successful");
}
As juharr noted in the commend, best practices for exposing values from class/objects is to use Properties not Fields..
public class User
{
// not best practices
public string username;
// best practices
public string password { get; set; }
}
Even fancier:
using System.Linq;
public static class Extensions
{
// Extension method that works on List<User> to validate user && PW
// - returns true if user exists and pw is ok, else false
public static bool CheckUserPassword(this List<User> users, string user_name, string pw)
{
// add null checks for users, user_name and pw if you are paranoid of know your customers ;o)
return users.Any(u => u.username == user_name && u.password == pw);
}
}
public class User
{
public string password;
public string username; //Unique usernames
}
internal class Program
{
private static List<User> users = new List<User> { new User { username = "admin", password = "123" } };
static void Main(string[] args)
{
// use extension method like this:
var isValid = users.CheckUserPassword("Hello","Jeha");
Console.WriteLine($"user 'admin' with pw '8888' => {users.CheckUserPassword("admin", "8888")}");
Console.WriteLine($"user 'admin' with pw '123' => {users.CheckUserPassword("admin", "123")}");
Console.ReadLine();
}
}
Extension Methods can be executed on the this-part - in this case only on List<User>s. The Extensionmethod uses Linq to find if Any (at least 1) user of this name and pw exists.

Select and Match Data from List using LINQ C#

I have data in List, and i want to do login if data matches with any of records.
public HttpResponseMessage Post(form model)
{
List<form> user = new List<form>();
user.Add(new form { username = "admin", password = "admin" });
user.Add(new form { username = "Gopal", password = "123" });
user.Add(new form { username = "niit", password = "niit" });
if (model.username == user.Select(p => p.username.Equals(model.username))
{
}
}
I want to like this - (Done with Hard coded data)
if (model.username == "admin" && model.password == "admin")
{
return Request.CreateResponse(HttpStatusCode.Accepted);
}
else { return Request.CreateResponse(HttpStatusCode.InternalServerError); }
This is my Model Class - Form
public class form
{
[Required]
public string username { get; set; }
[Required]
public string password { get; set; }
}
I have done this with hard coded data but want to do with list. Please help me this out. How Can I do it?
Try this way
if (user.Where(a => a.username == model.username && a.password == model.password).Select(p => p).Count() != 0)
{
return Request.CreateResponse(HttpStatusCode.Accepted);
}
else
{
return Request.CreateResponse(HttpStatusCode.InternalServerError);
}
or you can simply use any
if (user.Any( a => a.username.Contains(model.username) && a.password.Contains(model.password)))
{
return Request.CreateResponse(HttpStatusCode.Accepted);
}
else
{
return Request.CreateResponse(HttpStatusCode.InternalServerError);
}
I hope this isn't production code! You will want to use password salting + hashing if that user data is being stored. Best not to write your own code with this kind of stuff if you aren't experienced.
BUT to answer your question, you most likely want this:
user.Any(u => u.username == model.username && u.password == model.password)
There are better data structures though. For example, a Dictionary will allow O(1) lookup of the user (form?) by username rather than needing to iterate through the whole collection.
You can do this,
if (user.Any(use => model.username.Contains(use.username) && model.username.password(use.password))
{
return Request.CreateResponse(HttpStatusCode.Accepted);
}
else { return Request.CreateResponse(HttpStatusCode.InternalServerError); }
Use following code:
if (user.Where(x=> x.username.Equals(model.username) && x.password.Equals(model.password)).FirstOrDefault() != null)
{
return Request.CreateResponse(HttpStatusCode.Accepted);
}
else { return Request.CreateResponse(HttpStatusCode.InternalServerError); }
hope it will help you.

Where's the best place for validation... constructor or leave to client to call?

This is really a generic (and probably a more subjective too) question. I have some classes where I use an interface to define a standard approach to validating the object state. When I did this, I got to scratching my head... is it best to
1.) allow the constructor (or initializing method) to silently filter out the errant information automatically or...
2.) allow the client to instantiate the object however and let the client also call the interface's IsValid property or Validate() method before moving forward?
Basically one approach is silent but could be misleading in that the client may not be aware that certain pieces of information were filtered away due to it not meeting the validation criteria. The other approach then would be more straight forward, but also adds a step or two? What's typical here?
Okay, after a long day of trying to keep up with some other things, I finally did come up with an example. Please for me for it as it's not ideal and by no means something wonderful, but hopefully should serve well enough to get the point across. My current project is just too complicated to put something simple out for this, so I made something up... and trust me... totally made up.
Alright, the objects in the example are this:
Client: representing client-side code (Console App btw)
IValidationInfo: This is the actual interface I'm using in my current project. It allows me to create a validation framework for the "back-end" objects not necessarily intended for the Client to use since the business logic could be complicated enough. This also allowed me to separate validation code and call as-needed for the business logic.
OrderManager: This is an object the client-side code can use to manage their orders. It's client-friendly so-to-speak.
OrderSpecification: This is an object the client-side code can use to request an order. But if the business logic doesn't work out, an exception can be raised (or if necessary the order not added and exceptions ignored...) In my real-world example I actually have an object that's not quite so black-and-white as to which side of this fence it goes... thus my original question when I realized I could push validation request (calling IsValid or Validate()) to the cilent.
CustomerDescription: represents customers to which I've classified (pretending to have been read from a DB.
Product: Represents a particular product which is classified also.
OrderDescription: Represents the official order request.The business rule is that the Customer cannot order anything to which they've not been classified (I know.. that's not very real-world, but it gave me something to work with...)
Ok... I just realized I can't attach a file here, so here's the code. I apologize for it's lengthy appearance. That was the best I could do to create a client-friendly front-end and business logic back-end using my Validation interface:
public class Client
{
static OrderManager orderMgr = new OrderManager();
static void Main(string[] args)
{
//Request a new order
//Note: Only the OrderManager and OrderSpecification are used by the Client as to keep the
// Client from having to know and understand the framework beyond that point.
OrderSpecification orderSpec = new OrderSpecification("Customer1", new Product(IndustryCategory.FoodServices, "Vending Items"));
orderMgr.SubmitOrderRequest(orderSpec);
Console.WriteLine("The OrderManager has {0} items for {1} customers.", orderMgr.ProductCount, orderMgr.CustomerCount);
//Now add a second item proving that the business logic to add for an existing customer works
Console.WriteLine("Adding another valid item for the same customer.");
orderSpec = new OrderSpecification("Customer1", new Product(IndustryCategory.FoodServices, "Sodas"));
orderMgr.SubmitOrderRequest(orderSpec);
Console.WriteLine("The OrderManager now has {0} items for {1} customers.", orderMgr.ProductCount, orderMgr.CustomerCount);
Console.WriteLine("Adding a new valid order for a new customer.");
orderSpec = new OrderSpecification("Customer2", new Product(IndustryCategory.Residential, "Magazines"));
orderMgr.SubmitOrderRequest(orderSpec);
Console.WriteLine("The OrderManager now has {0} items for {1} customers.", orderMgr.ProductCount, orderMgr.CustomerCount);
Console.WriteLine("Adding a invalid one will not work because the customer is not set up to receive these kinds of items. Should get an exception with message...");
try
{
orderSpec = new OrderSpecification("Customer3", new Product(IndustryCategory.Residential, "Magazines"));
orderMgr.SubmitOrderRequest(orderSpec);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
Console.ReadLine();
}
}
public interface IValidationInfo
{
string[] ValidationItems { get; }
bool IsValid { get; }
void Validate();
List<string> GetValidationErrors();
string GetValidationError(string itemName);
}
public class OrderManager
{
private List<OrderDescription> _orders = new List<OrderDescription>();
public List<OrderDescription> Orders
{
get { return new List<OrderDescription>(_orders); }
private set { _orders = value; }
}
public int ProductCount
{
get
{
int itemCount = 0;
this.Orders.ForEach(o => itemCount += o.Products.Count);
return itemCount;
}
}
public int CustomerCount
{
get
{
//since there's only one customer per order, just return the number of orders
return this.Orders.Count;
}
}
public void SubmitOrderRequest(OrderSpecification orderSpec)
{
if (orderSpec.IsValid)
{
List<OrderDescription> orders = this.Orders;
//Since the particular customer may already have an order, we might as well add to an existing
OrderDescription existingOrder = orders.FirstOrDefault(o => string.Compare(orderSpec.Order.Customer.Name, o.Customer.Name, true) == 0) as OrderDescription;
if (existingOrder != null)
{
List<Product> existingProducts = orderSpec.Order.Products;
orderSpec.Order.Products.ForEach(p => existingOrder.AddProduct(p));
}
else
{
orders.Add(orderSpec.Order);
}
this.Orders = orders;
}
else
orderSpec.Validate(); //Let the OrderSpecification pass the business logic validation down the chain
}
}
public enum IndustryCategory
{
Residential,
Textile,
FoodServices,
Something
}
public class OrderSpecification : IValidationInfo
{
public OrderDescription Order { get; private set; }
public OrderSpecification(string customerName, Product product)
{
//Should use a method in the class to search and retrieve Customer... pretending here
CustomerDescription customer = null;
switch (customerName)
{
case "Customer1":
customer = new CustomerDescription() { Name = customerName, Category = IndustryCategory.FoodServices };
break;
case "Customer2":
customer = new CustomerDescription() { Name = customerName, Category = IndustryCategory.Residential };
break;
case "Customer3":
customer = new CustomerDescription() { Name = customerName, Category = IndustryCategory.Textile };
break;
}
//Create an OrderDescription to potentially represent the order... valid or not since this is
//a specification being used to request the order
this.Order = new OrderDescription(new List<Product>() { product }, customer);
}
#region IValidationInfo Members
private readonly string[] _validationItems =
{
"OrderDescription"
};
public string[] ValidationItems
{
get { return _validationItems; }
}
public bool IsValid
{
get
{
List<string> validationErrors = GetValidationErrors();
if (validationErrors != null && validationErrors.Count > 0)
return false;
else
return true;
}
}
public void Validate()
{
List<string> errorMessages = GetValidationErrors();
if (errorMessages != null && errorMessages.Count > 0)
{
StringBuilder errorMessageReported = new StringBuilder();
errorMessages.ForEach(em => errorMessageReported.AppendLine(em));
throw new Exception(errorMessageReported.ToString());
}
}
public List<string> GetValidationErrors()
{
List<string> errorMessages = new List<string>();
foreach (string item in this.ValidationItems)
{
string errorMessage = GetValidationError(item);
if (!string.IsNullOrEmpty(errorMessage))
errorMessages.Add(errorMessage);
}
return errorMessages;
}
public string GetValidationError(string itemName)
{
switch (itemName)
{
case "OrderDescription":
return ValidateOrderDescription();
default:
return "Invalid item name.";
}
}
#endregion
private string ValidateOrderDescription()
{
string errorMessage = string.Empty;
if (this.Order == null)
errorMessage = "Order was not instantiated.";
else
{
if (!this.Order.IsValid)
{
List<string> orderErrors = this.Order.GetValidationErrors();
orderErrors.ForEach(ce => errorMessage += "\n" + ce);
}
}
return errorMessage;
}
}
public class CustomerDescription : IValidationInfo
{
public string Name { get; set; }
public string Street { get; set; }
public string City { get; set; }
public string State { get; set; }
public int ZipCode { get; set; }
public IndustryCategory Category { get; set; }
#region IValidationInfo Members
private readonly string[] _validationItems =
{
"Name",
"Street",
"City",
"State",
"ZipCode",
"Category"
};
public string[] ValidationItems
{
get { return _validationItems; }
}
public bool IsValid
{
get
{
List<string> validationErrors = GetValidationErrors();
if (validationErrors != null && validationErrors.Count > 0)
return false;
else
return true;
}
}
public void Validate()
{
List<string> errorMessages = GetValidationErrors();
if (errorMessages != null && errorMessages.Count > 0)
{
StringBuilder errorMessageReported = new StringBuilder();
errorMessages.ForEach(em => errorMessageReported.AppendLine(em));
throw new Exception(errorMessageReported.ToString());
}
}
public List<string> GetValidationErrors()
{
List<string> errorMessages = new List<string>();
foreach (string item in this.ValidationItems)
{
string errorMessage = GetValidationError(item);
if (!string.IsNullOrEmpty(errorMessage))
errorMessages.Add(errorMessage);
}
return errorMessages;
}
public string GetValidationError(string itemName)
{
//Validation methods should be called here... pretending nothings wrong for sake of discussion & simplicity
switch (itemName)
{
case "Name":
return string.Empty;
case "Street":
return string.Empty;
case "City":
return string.Empty;
case "State":
return string.Empty;
case "ZipCode":
return string.Empty;
case "Category":
return string.Empty;
default:
return "Invalid item name.";
}
}
#endregion
}
public class Product
{
public IndustryCategory Category { get; private set; }
public string Description { get; private set; }
public Product(IndustryCategory category, string description)
{
this.Category = category;
this.Description = description;
}
}
public class OrderDescription : IValidationInfo
{
public CustomerDescription Customer { get; private set; }
private List<Product> _products = new List<Product>();
public List<Product> Products
{
get { return new List<Product>(_products); }
private set { _products = value; }
}
public OrderDescription(List<Product> products, CustomerDescription customer)
{
this.Products = products;
this.Customer = customer;
}
public void PlaceOrder()
{
//If order valid, place
if (this.IsValid)
{
//Do stuff to place order
}
else
Validate(); //cause the exceptions to be raised with the validate because business rules were broken
}
public void AddProduct(Product product)
{
List<Product> productsToEvaluate = this.Products;
//some special read, validation, quantity check, pre-existing, etc here
// doing other stuff...
productsToEvaluate.Add(product);
this.Products = productsToEvaluate;
}
#region IValidationInfo Members
private readonly string[] _validationItems =
{
"Customer",
"Products"
};
public string[] ValidationItems
{
get { return _validationItems; }
}
public bool IsValid
{
get
{
List<string> validationErrors = GetValidationErrors();
if (validationErrors != null && validationErrors.Count > 0)
return false;
else
return true;
}
}
public void Validate()
{
List<string> errorMessages = GetValidationErrors();
if (errorMessages != null && errorMessages.Count > 0)
{
StringBuilder errorMessageReported = new StringBuilder();
errorMessages.ForEach(em => errorMessageReported.AppendLine(em));
throw new Exception(errorMessageReported.ToString());
}
}
public List<string> GetValidationErrors()
{
List<string> errorMessages = new List<string>();
foreach (string item in this.ValidationItems)
{
string errorMessage = GetValidationError(item);
if (!string.IsNullOrEmpty(errorMessage))
errorMessages.Add(errorMessage);
}
return errorMessages;
}
public string GetValidationError(string itemName)
{
switch (itemName)
{
case "Customer":
return ValidateCustomer();
case "Products":
return ValidateProducts();
default:
return "Invalid item name.";
}
}
#endregion
#region Validation Methods
private string ValidateCustomer()
{
string errorMessage = string.Empty;
if (this.Customer == null)
errorMessage = "CustomerDescription is missing a valid value.";
else
{
if (!this.Customer.IsValid)
{
List<string> customerErrors = this.Customer.GetValidationErrors();
customerErrors.ForEach(ce => errorMessage += "\n" + ce);
}
}
return errorMessage;
}
private string ValidateProducts()
{
string errorMessage = string.Empty;
if (this.Products == null || this.Products.Count <= 0)
errorMessage = "Invalid Order. Missing Products.";
else
{
foreach (Product product in this.Products)
{
if (product.Category != Customer.Category)
{
errorMessage += string.Format("\nThe Product, {0}, category does not match the required Customer category for {1}", product.Description, Customer.Name);
}
}
}
return errorMessage;
}
#endregion
}
Any reason you wouldn't want the constructor to noisily throw an exception if the information is valid? It's best to avoid ever creating an object in an invalid state, in my experience.
It's completely depends on the client. There's a trade-off as you already mentioned. By default approach number 1 is my favorite. Creating smart classes with good encapsulation and hiding details from client. The level of smartness depends who is going to use the object. If client is business aware you can reveal details according to the level of this awareness. This is a dichotomy and should not be treated as black or white.
Well if I correctly understood, there are basically two question - whether you should fail right away or later and whether you should omit/assume certain information.
1) I always prefer failing as soon as possible - good example is failing at compile time vs failing at run time - you always want to fail at compile time. So if something is wrong with the state of some object, as Jon said - throw exception right away as loudly as you can and deal with it - do not introduce additional complexity down the road as you'll be heading for if/elseif/elseif/elseif/else mumbo jumbo.
2) When it comes to user input, if you are in position to simply filter out errors automatically - just do it. For example, I almost never ask users for country - if I really need it, I automatically detect it from IP and display it in the form. It's way easier if user just needs to confirm/change the data - and I don't need to deal with null situation.
Now, in case we are talking about the data generated by code during some processing - for me situation is drastically different - I always want to know an much as possible (for easier debugging down the road) and ideally you never should destroy any piece of information.
To wrap up, in your case I would recommend that you keep IsValid as simple yes/no (not yes/no/maybe/kindaok/etc). If you can fix some problems automatically - do it, but consider that they keep object in IsValid yes. For everything else, you throw exception and go to IsValid=no.

Categories