I want to create a Banking Application that can handle errors and throw exceptions to handle does errors that occur. These are the Exceptions i want the program to handle :
Withdrawing more than the current balance from an account. This should print an error message.
Attempting a transaction (deposit, withdrawal, or balance) on an account that has not been created yet.
Trying to create more than the maximum number (19) of accounts.
Here is My Code :
using static System.Console;
namespace Bank
{
public partial class Bank : Form
{
public Bank()
{
InitializeComponent();
}
private int _nextIndex = 0;
Accounts[] arrayAccounts = new Accounts[19];
private void createAccountButton_Click(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(accountIDTexTBox.Text)) return;
var account = new Accounts();
int accountID;
int balance = 0;
bool success = int.TryParse(accountIDTexTBox.Text, out accountID);
if (!int.TryParse(amountTextBox.Text, out balance))
{
result.Text = "Invalid Format in the Amount Fields please correct";
// MessageBox.Show("Invalid Format in the Amount Fields please correct");
}
if (balance < 300)
{
label5.Text = ("initial deposit must be $300 or greater");
}
else if (success)
{
account.AccountId = accountID;
account.Balance = balance;
arrayAccounts[_nextIndex] = account;
OutPutLabel.Text = "Account # " + accountID + " open with balance of " + balance;
}
else
{
result.Text = ("invalid AccountID entered, Please Correct");
}
}
private Accounts GetAccounts(int id)
{
return arrayAccounts.Where(x => x.AccountId == id).FirstOrDefault();
}
private void DepositRadioButton_CheckedChanged(object sender, EventArgs e)
{
// if (string.IsNullOrEmpty(accountIDTexTBox.Text)) return;
int amount = 0;
int accountID;
bool succcess1 = int.TryParse(accountIDTexTBox.Text, out accountID);
bool success2 = int.TryParse(amountTextBox.Text, out amount);
try
{
if (succcess1 && success2 && amount > 0)
{
var selectedAccount = GetAccounts(accountID);
selectedAccount.Balance += amount;
OutPutLabel.Text = "Account # " + accountID + " deposit " + amount;
}
else if (!succcess1)
{
result.Text = "You are attempting to deposit to a non-number ID";
}
else if (!success2)
{
result.Text = "Youu are Attempting to deposit \n "+
"to a non_Number amount \n Please reenter the amount";
}
}
catch(NullReferenceException)
{
result.Text = "Account has not being Created , \n Please create an Account";
}
}
private void WithdrawRadioButton_CheckedChanged(object sender, EventArgs e)
{
// if (string.IsNullOrEmpty(accountIDTexTBox.Text)) return;
int amount = 0;
int accountID;
bool success1 = int.TryParse(accountIDTexTBox.Text, out accountID);
bool success2 = int.TryParse(amountTextBox.Text, out amount);
try
{
if (success1 && success2 && amount > 0)
{
var selectedAccount = GetAccounts(accountID);
selectedAccount.Balance -= amount;
OutPutLabel.Text = amount + " withdraw from account # " + accountID;
}
else if (!success1)
{
result.Text = "You are attempting to withdraw from a non-number ID";
}
else if (!success2)
{
result.Text = "Youu are Attempting to Withdraw \n " +
"a non_Number amount \n Please reenter the amount";
}
}
catch (NullReferenceException)
{
result.Text = "Account has not being created , \n Please Create Account";
}
}
private void exceuteButton_Click(object sender, EventArgs e)
{
/// if (string.IsNullOrEmpty(accountIDTexTBox.Text)) return;
}
private void balanceRadioButton_CheckedChanged(object sender, EventArgs e)
{
int amount = 0;
int accountID;
bool success1 = int.TryParse(accountIDTexTBox.Text, out accountID);
try
{
if (success1)
{
var selectedAccount = GetAccounts(accountID);
OutPutLabel.Text = "Account # " + accountID + " has a balance of " + selectedAccount.Balance;
}
}
catch (NullReferenceException)
{
result.Text = "Account has not being Created"
+ "\n Please create account.";
}
}
}
class NegativeNumberException : Exception
{
private static string msg = "The Amount you enter is a negative number";
public NegativeNumberException() : base(msg)
{
}
}
I have being able to handle some of the errors using TryParse and If/else statements. Is there a better way to handle those errors using Exceptions.
here is the code for Account Class:
public class Accounts
{
public int AccountId { get; set; }
public decimal Balance { get; set; }
public void Deposit(decimal amount)
{
Balance += amount;
}
public void Withdraw(decimal amount)
{
Balance -= amount;
}
}
}
I really need help on handle those errors using exceptions.
First you'll have to create the requested exception types in your code which we will be using later on.
public class InsufficientBalanceException : Exception
{
// Exception for when a user tries to perform a withdrawal/deposit on an account with an insufficient balance of funds.
public InsufficientBalanceException() { }
public InsufficientBalanceException(string message)
: base(message) { }
public InsufficientBalanceException(string message, Exception inner)
: base(message, inner) { }
}
public class InvalidAccountException : Exception
{
// Exception for when a user is trying to perform an operation on an invalid account.
public InvalidAccountException() { }
public InvalidAccountException(string message)
: base(message) { }
public InvalidAccountException(string message, Exception inner)
: base(message, inner) { }
}
public class InvalidNumberOfAccountsException : Exception
{
// Exception for when a user is trying to create an account beyond the given limit.
public InvalidNumberOfAccountsException() { }
public InvalidNumberOfAccountsException(string message)
: base(message) { }
public InvalidNumberOfAccountsException(string message, Exception inner)
: base(message, inner) { }
}
Then, you need to specify in what conditions you'll be throwing each one of those exceptions.
Keep in mind you do not want to do this in your entity class since those are designed to be kept as simple as possible.
You'll most likely want to put that logic into some sort of a helper class (and not in the UI as you display in your code). Regardless, your code should look similar to the following:
public class AccountHelper
{
public Account GetAccount(int accountID)
{
/* Put some logic in here that retrieves an account object based on the accountID.
* Return Account object if possible, otherwise return Null */
return new Account();
}
public bool IsValidAccount(int accountID)
{
/* Put some logic in here that validates the account.
* Return True if account exists, otherwise return False */
return true;
}
public bool IsValidAmount(decimal amount)
{
/* Put some logic in here that validates the amount.
* Return True if amount is valid, otherwise return False */
return amount > 0;
}
public bool IsSufficientAmount(Account account, decimal amount)
{
/* Put some logic in here that validates the requested amount against the given account.
* Return True if account balance is valid, otherwise return False */
if (account == null)
return false;
return account.Balance >= amount;
}
public void DepositToAccount(int accountID, decimal amount)
{
Account account = null;
if (!IsValidAmount(amount))
throw new InvalidAmountException();
if (!IsValidAccount(accountID))
throw new InvalidAccountException();
account = GetAccount(accountID);
account.Deposit(amount);
}
public void WithdrawFromAccount(int accountID, decimal amount)
{
Account account = null;
if (!IsValidAmount(amount))
throw new InvalidAmountException();
if (!IsValidAccount(accountID))
throw new InvalidAccountException();
account = GetAccount(accountID);
if (!IsSufficientAmount(account, amount))
throw new InsufficientBalanceException();
account.Withdraw(amount);
}
}
Additional Notes:
Your Accounts class should be renamed to Account, as each
instance of that object represents one single account.
You should be trying to separate your business logic from the UI. It
is not a good practice to mix things up together as later on you
might face problems in case changes will have to be made. It will be
much easier to locate the required lines of code if you keep all of
your logic in one file and outside the UI.
Related
public Program()
{
amount_bike = new ArrayList();
}
public void push(int value)
{
this.amount_bike.Add(value);
}
public int amount_bike_pop()
{
if (this.amount_bike.Count == 0)
{
return -100000;
}
int lastItem = (int)this.amount_bike[this.amount_bike.Count - 1];
this.amount_bike.RemoveAt(this.amount_bike.Count - 1);
return lastItem;
}
public static void Bike_status()
{
bool exit = false;
Program available = new Program();
available.push(0);
available.push(0);
available.push(50);
WriteLine("E-bike available for rent is : " + available.amount_bike_pop() + " bikes.");
WriteLine("Rented E-bike is : " + available.amount_bike_pop() + " bikes.");
WriteLine("Broke E-bike is : " + available.amount_bike_pop() + " bikes.");
WriteLine("\n");
WriteLine("Please enter a number: 1 is back to pervoius menu or 0 to Exit");
int input = Convert.ToInt32(ReadLine());
while (exit == false)
{
if (input == 1)
{
Clear();
exit = true;
continue;
}
else if (input == 0)
{
Clear();
Exit();
}
else
{
Clear();
Bike_status();
}
}
}
public static void Add_bike()
{
}
I study data structures and Algorithms. In this code, I keep the value in an ArrayList named "available" in the Bike_status method. I need to pass a value in an ArrayList to the Add_bike method. How do I pass a value from one method to another? Actually, I need to pass valus such as 50 to plus some number that I push in Console.ReadLine.
Try to slow down.at starting point of programming sometimes it's confusing.
How do I pass a value from one method to another?
The simple answer is easy ,you want something to use in the function then in your method(s) you create parameter and pass the things you want to it.
like
//edit method to get int value -> public static void Bike_status()
public static void Bike_status(int value)
//edit when to call
else
{
Clear();
Bike_status(input);//send int value in
}
But really, what is that do you really want to learn?
if it OOP? I recommend you study this
https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/object-oriented/
To put it simply you has bicycle shop class separate from main program then use the method in that class
e.g.
//bicycle class
public class Bicycles{
public int ID {get;set;}
pulibc string status {get;set; }
public Bicycles(int p_id, string p_status)
{
ID = p_id;
status=p_status;
}
}
//bicycle shop class
public class BicyclesShop{
public List<Bicycle> available {get;set;} // class member
public BicyclesShop() // go search keyword "constructor"
{
available = new List<Bicycle> ();
}
//other method
public void Add_bike()
{
// I can access available object !
// do anything with this class member(s) !
}
public void Bike_status(int inputVal)
{
// do something with inputVal , change some properties?
}
//other methods
public int amount_bike_pop()
{
return available.Count();
}
public int amount_bike_broken_pop()
{
return available.Where(o=>o.status =="Broken").Count(); // go search linq
}
}
//To use in Main program method
BicyclesShop bs =new BicyclesShop();
bs.available.Add( new Bicycle(1 ,"OK") ); //add bicycle #1 in list
bs.available.Add( new Bicycle(2),"Broken" ); //add bicycle #2 in list
WriteLine("Broke E-bike is : " + bs.amount_bike_broken_pop() + " bikes.");
I’m new to coding and has been doing a Junior Software Development Bootcamp for less than a month. Currently, we are learning software fundamentals and C#.
To get in the groove of things and ease into it (as stress-free as possible) I decided to watch an ASMR video on Explaining Object-Oriented Programming with C# and follow along by copying the code into my Visual Studio program.
So here is the problem, I followed along exactly what the developer was typing in the video, however on my Visual Studio program I seem to be getting 2 red squiggly lines on 2 parts of the code I copied.
If someone could help me out here as to why the code is not working, I would be grateful.
This is the whole code
using System;
namespace ExplainingOOP
{
public class Program
{
public static void Main()
{
var checkingAccount = new Account();
}
public class Account {
private const double MAX_ALLOWED_Limit = 100000;
public string AccountOwnerName { get; set; }
private double _balance;
public double Balance { get { return _balance; } }
public void DepositMoney(double amount) {
CheckIfDepositedAmountLooksOkay(amount);
_balance += amount;
}
public void WithdrawMoney(double amount) {
CheckIfBalanceLooksRight(amount);
_balance -= amount;
}
private void CheckIfBalanceLooksRight(double amount) { {
if (_balance == 0)
{
throw new Exception("Insufficient balance");
}
if (_balance < amount)
{
throw new Exception("You are trying to withdraw money more than your balance. Please lower your balance.");
}
if (Balance < 0)
{
throw new Exception("There is something seriously wrong here. Contact the bank teller.");
}
}
if (amount > MAX_ALLOWED_Limit) {
throw new Exception("You cannot depost more than what's allowed daily. Please contact your bank agent for more infomation.");
}
if (amount <= 0) {
throw new Exception("This does not look right. You may not deposit 0 or less amount of money to your account. The eroor is being reported to the bank. Thanks for your patience");
}
}
}
private void CheckIfDepostiedAmountLooksOkay(double amount) {
if (amount > MAX_ALLOWED_LIMIT) {
throw new Exception("You cannot deposit more than what's allowed daily. Please contact your bank agent for more infomation");
}
if (amount <= 0) {
throw new Exception("this does not look right. You may deposit 0 or less amount of money to your account. The error is being reported to your bank. Thanks for your patience.");
}
}
}
}
and this error message i'm getting
/Users/shahzadh/Documents/Software Developer Bootcamp /Projects/ASMR Developer/Explaining Object Oriented Programming with C#/ExplainingOOP/ExplainingOOP/Program.cs(17,17): Error CS0103: The name 'CheckIfDepositedAmountLooksOkay' does not exist in the current context (CS0103) (ExplainingOOP)
/Users/shahzadh/Documents/Software Developer Bootcamp /Projects/ASMR Developer/Explaining Object Oriented Programming with C#/ExplainingOOP/ExplainingOOP/Program.cs(26,26): Error CS0103: The name 'MAX_ALLOWED_LIMIT' does not exist in the current context (CS0103) (ExplainingOOP)
you have a typo into CheckIfDepostiedAmountLooksOkay, so compiler was unable to find method;
You have a wrong case into MAX_ALLOWED_Limit. C# is case-sensitive;
You set curly braces wrong before method CheckIfDepostiedAmountLooksOkay, so this method was outside of the class Account.
The right code would be:
public class Program
{
public static void Main()
{
var checkingAccount = new Account();
}
public class Account
{
private const double MAX_ALLOWED_LIMIT = 100000;
public string AccountOwnerName { get; set; }
private double _balance;
public double Balance
{
get { return _balance; }
}
public void DepositMoney(double amount)
{
CheckIfDepositedAmountLooksOkay(amount);
_balance += amount;
}
public void WithdrawMoney(double amount)
{
CheckIfBalanceLooksRight(amount);
_balance -= amount;
}
private void CheckIfBalanceLooksRight(double amount)
{
{
if (_balance == 0)
{
throw new Exception("Insufficient balance");
}
if (_balance < amount)
{
throw new Exception(
"You are trying to withdraw money more than your balance. Please lower your balance.");
}
if (Balance < 0)
{
throw new Exception("There is something seriously wrong here. Contact the bank teller.");
}
}
if (amount > MAX_ALLOWED_LIMIT)
{
throw new Exception(
"You cannot depost more than what's allowed daily. Please contact your bank agent for more infomation.");
}
if (amount <= 0)
{
throw new Exception(
"This does not look right. You may not deposit 0 or less amount of money to your account. The eroor is being reported to the bank. Thanks for your patience");
}
}
private void CheckIfDepositedAmountLooksOkay(double amount)
{
if (amount > MAX_ALLOWED_LIMIT)
{
throw new Exception(
"You cannot deposit more than what's allowed daily. Please contact your bank agent for more infomation");
}
if (amount <= 0)
{
throw new Exception(
"this does not look right. You may deposit 0 or less amount of money to your account. The error is being reported to your bank. Thanks for your patience.");
}
}
}
}
In the code below, which does work, I would like to handle 5 different exceptions that could be created by user input.
I understand I should use an IF statement to handle these exceptions but the requirement is to handle the errors with exception handlers. So please I am only looking for input in doing that and not alternatives.
I would like to handle them with exception handlers.
The problem I am having is where to put the exception handling code.
Also being I have 5 exceptions I want to check for does that mean I need 5 different try/catch blocks or can I handle them all in the same block?
The exceptions I am looking for are, trying to create more than 19 accounts, trying to create an account with an initial balance below $300, withdrawing more than the current balance from an account, attempting a transaction on an account that hasn't been created and entering anything other than a number in the TextBox.
So if a user makes one of these errors I would like to throw the error and display a message to the user of the error they have made.
Any assistance is greatly appreciated.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace MoreRobustBankGUI
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private int _nextIndex = 0;
List<Account> accounts = new List<Account>();
decimal balance = 0;
private void createButton1_Click(object sender, EventArgs e)
{
if (accounts.Count < 19 && balance > 300)
{
_nextIndex++;
int accountId = _nextIndex;
decimal.TryParse(amountTextBox2.Text, out balance);
transactionLabel3.Text = "Account: #" + accountId + " created with a starting balance of $" + balance;
accountTextBox1.Text = "" + accountId;
accounts.Add(new Account(balance)
{
AccountId = accountId
});
}
else
{
transactionLabel3.Text = "Can only create up to 19 accounts and starting balance must be $300";
}
}
private void executeButton2_Click(object sender, EventArgs e)
{
decimal amount = 0;
int accountID;
string textAmount = amountTextBox2.Text == "" ? "0" : amountTextBox2.Text;
if (depositRadioButton3.Checked == true)
{
if (string.IsNullOrEmpty(accountTextBox1.Text)) return;
bool accountCanBeConverted = int.TryParse(accountTextBox1?.Text, out accountID);
bool ammountCanBeConverted = decimal.TryParse(amountTextBox2?.Text, out amount);
if (accountCanBeConverted && ammountCanBeConverted && amount > 0)
{
var selectedAccount = GetAccount(accountID);
selectedAccount.DepositFunds(amount);
transactionLabel3.Text = $"Account: #{selectedAccount.AccountId} You made a deposit of ${amount}";
}
}
else if (withdrawRadioButton2.Checked == true)
{
if (string.IsNullOrEmpty(accountTextBox1.Text)) return;
bool accountCanBeConverted = int.TryParse(accountTextBox1?.Text, out accountID);
bool ammountCanBeConverted = decimal.TryParse(amountTextBox2?.Text, out amount);
if (accountCanBeConverted && ammountCanBeConverted && amount > 0)
{
var selectedAccount = GetAccount(accountID);
if (selectedAccount.HasAvailableFunds)
{
selectedAccount.WithdrawFromAccount(amount);
transactionLabel3.Text = $"Account: #{selectedAccount.AccountId} You made a withdrawal of ${amount}";
}
else
{
transactionLabel3.Text = $"Account: #{selectedAccount.AccountId} Does not have available funds to withdraw";
}
}
}
else if (balanceRadioButton3.Checked == true)
{
if (string.IsNullOrEmpty(accountTextBox1.Text)) return;
bool accountCanBeConverted = int.TryParse(accountTextBox1?.Text, out accountID);
var selectedAccount = GetAccount(accountID);
var balance = selectedAccount.GetAvailableBalanceForAccount(accountID);
if (balance == -1234567890)
{
transactionLabel3.Text = $"Invalid account number passed.";
}
else
{
transactionLabel3.Text = $"Account: #{selectedAccount.AccountId} Balance: $ {selectedAccount.GetAvailableBalanceForAccount(accountID)}";
}
}
clearFields();
}
public void clearFields()
{
amountTextBox2.Text = "";
}
public Account GetAccount(int id)
{
return accounts.Where(x => x.AccountId == id).FirstOrDefault();
}
public class Account
{
public Account(decimal balance)
{
Balance = balance;
}
public int AccountId { get; set; }
public decimal Balance { get; set; }
public void WithdrawFromAccount(decimal deductionAmount)
{
Balance -= deductionAmount;
}
public void DepositFunds(decimal depositAmount)
{
Balance += depositAmount;
}
public bool HasAvailableFunds => Balance > 0;
public decimal GetAvailableBalanceForAccount(int accountId)
{
if (accountId == AccountId)
{
return Balance;
}
else
{
return -1234567890;
}
}
}
}
}
Sorry for this being a 'don't do that' answer...
Using exceptions for 'normal' business control flow is not good practice. Exceptions should be for exceptional events.
It is clearly normal for people to create accounts with too little balance, and (for me anyway) to try and withdraw more than they have in the account. These errors should be handled in by normal control flows ( (if balance < MIN_BALANCE) type things).
For a bigger discussion look here https://softwareengineering.stackexchange.com/questions/189222/are-exceptions-as-control-flow-considered-a-serious-antipattern-if-so-why
For a way forward maybe investigate raising events when business rules are broken.
There are a lot of ways you can do this... here is a simple thing you could try.
Understanding events and event handlers in C#
I agree that is a poor idea and hope you really know what you are doing. Proper exception handling is a pet peeve of mine and your idea does not sound remotely solid. Here are two articles on the matter I consider must reads and link a lot:
https://blogs.msdn.microsoft.com/ericlippert/2008/09/10/vexing-exceptions/
https://www.codeproject.com/Articles/9538/Exception-Handling-Best-Practices-in-NET
That all being said, somebody once had a problem that he could not use TryParse because he was running on .NET 1.1. So I quickly cobelled this tryParse alternative together:
//Parse throws ArgumentNull, Format and Overflow Exceptions.
//And they only have Exception as base class in common, but identical handling code (output = 0 and return false).
bool TryParse(string input, out int output){
try{
output = int.Parse(input);
}
catch (Exception ex){
if(ex is ArgumentNullException ||
ex is FormatException ||
ex is OverflowException){
//these are the exceptions I am looking for. I will do my thing.
output = 0;
return false;
}
else{
//Not the exceptions I expect. Best to just let them go on their way.
throw;
}
}
//I am pretty sure the Exception replaces the return value in exception case.
//So this one will only be returned without any Exceptions, expected or unexpected
return true;
}
I think the problem (very far apart exceptions with the exact same handling) is the same as yours.
Although i totally agree with #Loofer's answer (+1 for that).
Seems you have different Use case.
So giving answer to that
Also being I have 5 exceptions I want to check for does that mean I
need 5 different try/catch blocks or can I handle them all in the same
block?
You should use Multiple Catch block
Something Like
try
{
//
}
catch(Type1Exception exception)
{
}
catch(Type2Exception exception)
{
}
catch(Type3Exception exception)
{
}
And So On.
But there is another way also which answers both of your question.
which is personal suggestion too and that would be to create one helper method something like
private void HandleCustomException(Exception exception)
{
// Your Error Handling Code goes here
if(exception is Type1Exception)
{...}
...
}
And then put separate try catch in you click events, which will send any Exception received to this helper method
Something like this
private void createButton1_Click(object sender, EventArgs e)
{
try
{
if(Your Condition)
{
throw new Type1Exception();
}
}
catch(Exception exception)
{
HandleCustomException(exception);
}
}
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.
I have a class with many fields which represents different physical values.
class Tunnel
{
private double _length;
private double _crossSectionArea;
private double _airDensity;
//...
Each field is exposed using read/write property. I need to check on setter that the value is correct and generate exception otherwise. All validations are similar:
public double Length
{
get { return _length; }
set
{
if (value <= 0) throw new ArgumentOutOfRangeException("value",
"Length must be positive value.");
_length = value;
}
}
public double CrossSectionArea
{
get { return _crossSectionArea; }
set
{
if (value <= 0) throw new ArgumentOutOfRangeException("value",
"Cross-section area must be positive value.");
_crossSectionArea = value;
}
}
public double AirDensity
{
get { return _airDensity; }
set
{
if (value < 0) throw new ArgumentOutOfRangeException("value",
"Air density can't be negative value.");
_airDensity = value;
}
}
//...
Is there any elegant and flexible way to accomplish such validation?
Assuming you want this sort of behaviour, you might consider some helper methods, e.g.
public static double ValidatePositive(double input, string name)
{
if (input <= 0)
{
throw new ArgumentOutOfRangeException(name + " must be positive");
}
return input;
}
public static double ValidateNonNegative(double input, string name)
{
if (input < 0)
{
throw new ArgumentOutOfRangeException(name + " must not be negative");
}
return input;
}
Then you can write:
public double AirDensity
{
get { return _airDensity; }
set
{
_airDensity = ValidationHelpers.ValidateNonNegative(value,
"Air density");
}
}
If you need this for various types, you could even make it generic:
public static T ValidateNonNegative(T input, string name)
where T : IComparable<T>
{
if (input.CompareTo(default(T)) < 0)
{
throw new ArgumentOutOfRangeException(name + " must not be negative");
}
return input;
}
Note that none of this is terribly i18n-friendly...
All depends what technology you are using - if you're under MVC you can use Attributes, like this;
http://msdn.microsoft.com/en-us/library/ee256141(v=vs.98).aspx
Here's my version, it's a bit cleaner than Jon's version in some respects:
interface IValidator <T>
{
bool Validate (T value);
}
class IntValidator : IValidator <int>
{
public bool Validate (int value)
{
return value > 10 && value < 15;
}
}
class Int2Validator : IValidator<int>
{
public bool Validate (int value)
{
return value > 100 && value < 150;
}
}
struct Property<T, P> where P : IValidator<T>, new ()
{
public T Value
{
set
{
if (m_validator.Validate (value))
{
m_value = value;
}
else
{
Console.WriteLine ("Error validating: '" + value + "' is out of range.");
}
}
get { return m_value; }
}
T m_value;
static IValidator<T> m_validator=new P();
}
class Program
{
static void Main (string [] args)
{
Program
p = new Program ();
p.m_p1.Value = 9;
p.m_p1.Value = 12;
p.m_p1.Value = 25;
p.m_p2.Value = 90;
p.m_p2.Value = 120;
p.m_p2.Value = 250;
}
Property<int, IntValidator>
m_p1;
Property<int, Int2Validator>
m_p2;
}
Try to use such a method:
public void FailOrProceed(Func<bool> validationFunction, Action proceedFunction, string errorMessage)
{
// !!! check for nulls, etc
if (!validationFunction())
{
throw new ArgumentOutOfRangeException(errorMessage);
}
proceedFunction();
}
Yes, by creating your own validation attributes.
Read this article: Business Object Validation Using Attributes in C#
I will have the decency of NOT copying it here :)
Using the Validator function I mentioned in my comment above, I'd do something like this (untested code):
void textBox_Changed(object sender, EventArgs e) {
submitButton.Enabled = validator();
}
bool validator() {
const string NON_POSITIVE = "Value must be greater than Zero";
bool result = false;
string controlName = "Length";
try {
_length = Convert.ToDouble(txtLength.Text);
if (_length <= 0) throw new Exception(NON_POSITIVE);
controlName = "Cross Section Area";
_crossSectionArea = Convert.ToDouble(txtCrossSectionArea.Text);
if (_crossSectionArea <= 0) throw new Exception(NON_POSITIVE);
controlName = "Air Density";
_airDensity = Convert.ToDouble(txtAirDensity.Text);
if (_airDensity <= 0) throw new Exception(NON_POSITIVE);
result = true; // only do this step last
} catch (Exception err) {
MessageBox.Show(controlName + " Error: " + err.Message, "Input Error");
}
return result;
}
John Skeet probably has a better way, but this works. :)
You can achieve this using classes from System.ComponentModel.DataAnnotations
class Tunnel
{
[Range(0, double.MaxValue, ErrorMessage = "Length must be positive value.")]
public double Length { get; set; }
}
Validation:
var tunnel = new Tunnel { Length = 0 };
var context = new ValidationContext(tunnel, null, null);
Validator.ValidateObject(tunnel, context, true);
Also you can implement your own validation attributes overriding ValidationAttribute class