UPDATED: Added some sample code to help clarify.
Hi, Feel like this shouldn't be that complicated, but I think I just don't know the proper name for what I'm trying to do. I'm dealing with an ASP.net project.
The concept is pretty simple: I have a library that supplies some ecomm functions. One class in the libary contains functions around calculating tax. One class in the library revolves around the cart header. This class is consumed by a web project.
There will be a method like: carthead.SaveCart. Inside SaveCart, it will need to call tax.CalculateTax
What I need to do is to figure out a way to allow the carthead.SavCart to always have access to a specific function such as tax.CalculateTax (e.g. that function must always be available to the library).
However, I then want to allow anyone to create a different version of the tax.CalculateTax method.
I've tried doing some stuff with an overridable base class, but what I find is that even though override the tax base class in the web project, it only calls the overridden version of the tax class when I make a call from the web project. I can't make the tax class into an interface as when I do that, I cannot define the results of tax.CalculateTax as a list of (because in this instance t is an interface, not an actual class) - this list has to be consumed by the carthead.SaveCart method.
Therefore, when you step through the code, what you find is that when the web project calls the carthead.SaveCart method, the carthead.SaveCart method doesn't have access back to the overridden code from the web project... and results in calling the non-overridden version of the tax.CalculateTax method.
I'm sure I'm missing something, but I'm not even sure what it is I should be researching or what exactly the proper name is for what I'm trying to accomplish.
Essentialy to boil it down, I need to be able to override a method and have the overridden version of the method called from the non overridable methods in the library.
Can anyone point out to me what I've messed up or what I should be looking at?
UPDATE: Added some code snippets to help clarify:
Here is a piece of the CartHeader class in question:
Public Class CartHeader
'other class stuff omitted
Public Function UpdateCartStep2(ByVal CartNo As Long, ByVal Username As String, _
ByVal PmtMethod As String, ByVal ShipMethod As String, _
ByVal ShipComplete As Boolean, ByVal ShipCost As Double, _
ByVal ShipInstr As String, Optional ByVal TaxGroup As String = "", _
Optional ByVal PickupLoc As String = "", _
Optional ByVal FuelSurcharge As Double = 0, _
Optional ByVal Misc As String = "", _
Optional ByVal TaxThisSomeTaxOrder As Boolean = False, _
Optional ByVal ShipToID As Long = 0, _
Optional ByVal ShipToZip As String = "", _
Optional ByVal mCustCode As String = "", _
Optional ByVal CustTax As Tax = Nothing) As Integer
'=================>
'note that the last parameter is new which is what we're currently using to pass in the customtax class so that we can consume it inside this method
'==================>
If IsNothing(CustTax) Then
CustTax = New Tax
End If
'6-29-08 this stored proc was updated to allow for fuel surcharge
'added fuel surcharge parameter
Dim Resultval As Integer
Dim strConnect As New SqlConnection(ConfigurationManager.ConnectionStrings("ConnectionString").ConnectionString)
Dim SqlCommand As New SqlCommand("sp_UpdateCartStep2", strConnect)
Try
Dim SubTotalAmt As Double
SubTotalAmt = GetCartSubTotal(CartNo)
GetCartHeader(CartNo)
Dim CartTax As Double
Dim SystemTypeID As Integer = CInt(ConfigurationManager.AppSettings("SystemTypeID").ToString)
Select Case SystemTypeID
Case 1
If profile.AllowTerms = False Then
CartTax = CalcTax(SubTotalAmt, ShipCost, FuelSurcharge, m_Ship_State_Province)
Else
CartTax = 0
End If
Case 2
'6-29-08 added to figure fuel surcharge
'Dim CustTax As New Tax
'Dim CustCode As String = System.Web.HttpContext.Current.Profile("CustCode")
Dim lCustTax As New List(Of Tax)
'=========================>
'note that this part of the header must always call into the calctax method.
'it should be able to call the custom method if it has been defined.
lCustTax = CustTax.wa_cc_CalcTax(mCustCode, ShipToID, SubTotalAmt, ShipCost, FuelSurcharge, CStr(m_Ship_State_Province), CStr(ShipToZip))
'==========================>
For Each ct As Tax In lCustTax
CartTax += ct.Tax
Next
'CartTax = CalcTax(SubTotalAmt, ShipCost, FuelSurcharge, m_Ship_State_Province, TaxGroup)
End Select
SqlCommand.CommandType = CommandType.StoredProcedure
strConnect.Open()
SqlCommand.Parameters.AddWithValue("@CartNo", SqlDbType.BigInt).Value = CartNo
SqlCommand.Parameters.AddWithValue("@Username", SqlDbType.VarChar).Value = Username
SqlCommand.Parameters.AddWithValue("@PmtMethod", SqlDbType.VarChar).Value = PmtMethod
SqlCommand.Parameters.AddWithValue("@ShipMethod", SqlDbType.VarChar).Value = ShipMethod
SqlCommand.Parameters.AddWithValue("@ShipCompleteFlag", SqlDbType.Bit).Value = ShipComplete
SqlCommand.Parameters.AddWithValue("@ShipCost", SqlDbType.Money).Value = ShipCost
SqlCommand.Parameters.AddWithValue("@Tax", SqlDbType.Money).Value = CartTax
SqlCommand.Parameters.AddWithValue("@ShipInstr", SqlDbType.VarChar).Value = ShipInstr
SqlCommand.Parameters.AddWithValue("@PickupLoc", SqlDbType.VarChar).Value = PickupLoc
SqlCommand.Parameters.AddWithValue("@FuelSurcharge", SqlDbType.Float).Value = FuelSurcharge
'1-30-08 Changed to accomodate holding the carrier number when selecting collect freight
'required modification of the sp_UpdateCartStep2 stored procedure.
SqlCommand.Parameters.AddWithValue("@Misc3", SqlDbType.VarChar).Value = Misc3
SqlCommand.ExecuteNonQuery()
Resultval = 0
Catch ex As Exception
Resultval = -1
System.Web.HttpContext.Current.Trace.Write(ex.Message)
Finally
strConnect.Close()
End Try
Return Resultval
End Function
End Class
This is the class we use as a base class... it have the wa_cc_calctax overridden if the base function calcs don't apply.
Public Class Tax
Private _Tax As Double
Public Property Tax() As Double
Get
Return _Tax
End Get
Set(ByVal value As Double)
_Tax = value
End Set
End Property
Private _TaxRate As Double
Public Property TaxRate() As Double
Get
Return _TaxRate
End Get
Set(ByVal value As Double)
_TaxRate = value
End Set
End Property
Private _TaxDesc As String
Public Property TaxDesc() As String
Get
Return _TaxDesc
End Get
Set(ByVal value As String)
_TaxDesc = value
End Set
End Property
Private _TaxGroupID As String
Public Property TaxGroupID() As String
Get
Return _TaxGroupID
End Get
Set(ByVal value As String)
_TaxGroupID = value
End Set
End Property
Private _TaxJurisdictionID As String
Public Property TaxJurisdictionID() As String
Get
Return _TaxJurisdictionID
End Get
Set(ByVal value As String)
_TaxJurisdictionID = value
End Set
End Property
Private _TaxCustCode As String
Public Property TaxCustCode() As String
Get
Return _TaxCustCode
End Get
Set(ByVal value As String)
_TaxCustCode = value
End Set
End Property
Private _TaxFreight As Boolean
Public Property taxFreight() As Boolean
Get
Return _TaxFreight
End Get
Set(ByVal value As Boolean)
_TaxFreight = value
End Set
End Property
Public Enum TaxableStatus
All
None
some
End Enum
''' <summary>
''' It will first try to figure out if we're shipping to the same zip as the ship to
''' if it is the same, then we'll use the ship-tos tax group
''' if it is different, then we'll go to manual tax.
''' in manual tax, the customer record is reviewed and the class_1id field is interogated.
''' The code selected tells us what states the customer is taxable for.
''' If we are in those states, then the customer tax group is chosed based on the state.
''' </summary>
''' <param name="mCustCode"></param>
''' <param name="mShipToID"></param>
''' <param name="SubTotalAmt"></param>
''' <param name="FreightCost"></param>
''' <param name="FuelSurcharge"></param>
''' <param name="m_Ship_State_Province"></param>
''' <param name="m_Zip"></param>
''' <param name="TaxGroup"></param>
''' <returns></returns>
''' <remarks></remarks>
Public Overridable Function wa_cc_CalcTax(ByVal mCustCode As String, _
ByVal mShipToID As String, _
ByVal SubTotalAmt As Double, _
ByVal FreightCost As Double, _
ByVal FuelSurcharge As Double, _
ByVal m_Ship_State_Province As String, _
ByVal m_Zip As String, _
Optional ByVal TaxGroup As String = "") As List(Of Tax)
'do some 'normal' tax calcs.
Return New List(Of Tax)
End Function
End Class
This is the CustomTax class that overrides the wa_cc_calctax function:
Public Class CustomTax
Inherits Tax
''' <summary>
''' It will first try to figure out if we're shipping to the same zip as the ship to
''' if it is the same, then we'll use the ship-tos tax group
''' if it is different, then we'll go to manual tax.
''' in manual tax, the customer record is reviewed and the class_1id field is interogated.
''' The code selected tells us what states the customer is taxable for.
''' If we are in those states, then the customer tax group is chosed based on the state.
''' </summary>
''' <param name="mCustCode"></param>
''' <param name="mShipToID"></param>
''' <param name="SubTotalAmt"></param>
''' <param name="FreightCost"></param>
''' <param name="FuelSurcharge"></param>
''' <param name="m_Ship_State_Province"></param>
''' <param name="m_Zip"></param>
''' <param name="TaxGroup"></param>
''' <returns></returns>
''' <remarks></remarks>
Public Overrides Function wa_cc_CalcTax(ByVal mCustCode As String, _
ByVal mShipToID As String, _
ByVal SubTotalAmt As Double, _
ByVal FreightCost As Double, _
ByVal FuelSurcharge As Double, _
ByVal m_Ship_State_Province As String, _
ByVal m_Zip As String, _
Optional ByVal TaxGroup As String = "") As List(Of Tax)
Dim lTX As New List(Of Tax)
Dim mUseP21Tax As Boolean = True
If mShipToID <= 0 Then
mUseP21Tax = False
End If
If FreightCost <= 0 Then
FreightCost = 0
End If
Dim tx As New CustomTax
Dim ZipMatch As Boolean
If mShipToID > 0 Then
'we're dealing with a selected ship to so we should see if it all still matches
ZipMatch = CheckZipAgainstShipTo(m_Zip, mCustCode, mShipToID)
Else
'this item is not a selected ship-to so no need to look for a match
ZipMatch = False
End If
If ZipMatch = True Then
lTX = LookupTaxForShipTo(mCustCode, mShipToID, SubTotalAmt, FreightCost)
Else
lTX = LookupManualTax(mCustCode, m_Ship_State_Province, SubTotalAmt, FreightCost, , m_Zip)
End If
Return lTX
End Function End Class
So the problem is in part: 1) If I make the class Tax into an Interface or an abstract class, then I have to 'new' it up as a new class inside the cartheader class so that I can call the wa_cc_lookupclass method. 2) When we new up a tax class inside the cartheader class, then we aren't newing up the custom instance of the tax class and therefore the custom code isn't used.
The goal is as follows: 1) Provide the base Tax class that has 'normal' functionality. 2) Allow a user to override the tax class in a web app's app_code folder to create new logic to calculate tax. It will still have the same input and output signatures as the original. 3) The cartheader.UpdateCartStep2 method must be able to access the wa_cc_calctax function from the base or overridden custom class (if it is overridden).
In this sample, we've clearly hacked it and passed in the custom version of the tax class (CustomTax) into the UpdateCartStep2 method as a parameter. This was a workaround we implemented based on a suggestion here... however we know it isn't the 'right' way to do it.