Architecture Q - BLL for Web UI and Forms UI

M

Monty

Hello,

I have a class library of business objects that will accessed both by an
ASP.Net front end and a WinForms front-end. A typical business object in
class library, let's say "Employee", has a save method. In the save method,
after the employee record is successfully saved, a record is created in the
history table to record who saved the object when. The user name, IP address
(if it's web), and other information I would like to add to have in the
history record are already in the ASP.Net session, but I don't want my BLL
to be dependent on the HTTPContext object. I'd like to avoid having to pass
all this info as parameters to the Employee.Save method (or Delete and
Create methods for that matter), and I'd like to be able to use the object
from either the web or form UI. I thought about setting the fields in a
shared class, which would be fine for the Forms UI but wouldn't work (I
don't believe) for the Web UI because each user would over-write the
user-specific info in the shared class. Does anyone have any ideas for this?
TIA,

Monty
 
S

sloan

I suggest a Factory approach.

You create a custom object to store info. Actually, Id create an interface
and an implementation.

Then you create a factory to return an object (or an implementation of that
interface) based on whether you're in a winforms or web environment.

Check out my blog here:
12/1/2005
Understanding the Simple Factory Pattern
http://sholliday.spaces.live.com/blog/



and you care about "Factory By Environment"


Somewhere, you're gonna have to reference System.Web
If that's your cruz, then pick wisely. If you don't want you biz layer to
reference it, then you'll have to put it in its own assembly.

In this rare care, I actually do let the biz layer reference the System.Web
namespace/dll, knowing I don't use it anywhere else.

But there's an idea for you.
 
M

Monty

Hi Sloan (Ferris Bueller theme starts playing in the back of my brain),

Thanks for the demo, I think I will use that method. I'm not a native C#
user but I think I was able to figure everything out. I'm wondering why you
went through the trouble to create two different holder classes, an
IObjectHolder interface, and an ObjectHolderFactory when it seems it could
all be rolled into one pretty easily. I did this (below) and it seems to be
working (just from a very basic/quick test). Am I missing something obvious?

Public Class UserInfo
Private Shared singletonInstance As UserInfo
Private Shared ReadOnly SESSION_OBJECT_GUID As String =
"123503F8-6CBD-46F0-ACDC-98683A94360C"
Private _UserName As String = ""
Private _UserID As String = ""
Private _WorkstationID As String = ""

Private Sub New() 'a private constructor keeps anyone else from
creating this
End Sub

Public Shared Function GetInstance() As UserInfo
If singletonInstance Is Nothing Then
If System.Web.HttpContext.Current Is Nothing Then 'windows forms
environment:
singletonInstance = New UserInfo
Else 'web environment:
If System.Web.HttpContext.Current.Session IsNot Nothing Then
If
System.Web.HttpContext.Current.Session(SESSION_OBJECT_GUID) IsNot Nothing
Then
singletonInstance =
TryCast(System.Web.HttpContext.Current.Session(SESSION_OBJECT_GUID),
UserInfo)
Else
singletonInstance = New UserInfo
System.Web.HttpContext.Current.Session(SESSION_OBJECT_GUID)
= singletonInstance
End If
End If
End If
End If
Return singletonInstance
End Function

Public Property UserID() As String
Get
Return _UserID
End Get
Set(ByVal value As String)
_UserID = value
End Set
End Property
Public Property UserName() As String
Get
Return _UserName
End Get
Set(ByVal value As String)
_UserName = value
End Set
End Property
Public Property WorkstationID() As String
Get
Return _WorkstationID
End Get
Set(ByVal value As String)
_WorkstationID = value
End Set
End Property
End Class

I could also add a Dispose method to clean up, of course.

Thanks again.
 
M

Monty

I revised that GetInstance a bit (to make sure we're getting it from the
session when we're in the web scenario):


Public Shared Function GetInstance() As UserInfo
If System.Web.HttpContext.Current Is Nothing Then 'windows forms
environment:
If singletonInstance Is Nothing Then singletonInstance = New
UserInfo
Return singletonInstance
Else 'web environment:
If System.Web.HttpContext.Current.Session IsNot Nothing Then
If
System.Web.HttpContext.Current.Session(SESSION_OBJECT_GUID) Is Nothing Then
System.Web.HttpContext.Current.Session(SESSION_OBJECT_GUID)
= New UserInfo
End If
Return
TryCast(System.Web.HttpContext.Current.Session(SESSION_OBJECT_GUID),
UserInfo)
Else
Return Nothing 'Throw exception?
End If
End If
End Function
 
S

sloan

create two different holder classes, an
IObjectHolder interface, and an ObjectHolderFactory when it seems it could
all be rolled into one pretty easily


Because one should program to an interface, not an implementation.
Google the phrase
"to an interface" and "not an implementation"



I think what I had in mind was something like this

I'll try to vb.net improvise


public IUserInfo as interface
readonly property UserName as string
readonly property UserID as string
readonly property IPAddress as string


public class WinformsUserInfo : implements IUserInfo

public sub new
'here is where you figure out the winforms info you want to give
back..... set some member variables which you later expose as properties
end sub

UserName '' return the windows identity
UserID ''
IPAddress '' (return string.Empty since you weren't interested in this



public class WebUserInfo : implements IUserInfo

public sub new
'here is where you figure out the webform info you want to give
back..... set some member variables which you later expose as properties
end sub

UserName '' return ... Membership or whatever you keep as the UserName
in the web environment
UserID ''
IPAddress '' return the ... what is it? the Server object like
Server(REMOTE_IP_ADDRESS)


now you have 2 classes, both implement IUserInfo




' Here is your "factory"

Public Class UserInfoFactory

Private Sub New()
End Sub 'New


Public Shared Function GetUserInfo() As IUserInfo

If Nothing = System.Web.HttpContext.Current Then ' Add a reference to
System.Web.dll
'Non Web Environment
return new WinformsUserInfo
Else 'Web Environment
Return new WebUserInfo
End If
End Function
End Class

If you put that code in the biz layer, then it doesn't matter if you're in
the web or winforms environment, because the factory decides for you.
All you care about it getting ~a implementation of IUserInfo ... you don't
care which one.


What you did is create a single web version of the UserInfo class.
This isn't what you want.


I'd start over using the guide lines above.


DO NOT DO THIS............


Public Class UserInfo

public readonly property IPAddress as string

get


If Nothing = System.Web.HttpContext.Current Then ' Add a reference to
System.Web.dll
return "IPAddress not available in winforms"

Else 'Web Environment
Return Server(REMOTE_IP_ADDRESS)
End If

end get

end property


That's a whole lot of big nasty hack.


If you don't have alot of experience with Design Patterns, I'd go here
http://www.dofactory.com/Patterns/PatternFactory.aspx

and try a few out.

The power of design patterns is not knowing them, its knowing where to apply
them.
There are tons of articles about them on the web.
And design patterns are "above" any language.
I can talk to a java guy about a software solution, and we can talk about
how to use design patterns to solve it.
We don't care that I program in C# and he in java.

The above is your first lesson, I would try to learn more.
It'll get you on the road to what real OO is about.

..Good luck.
 
S

sloan

PS

Your factory ~can return an abstract class also


public mustinherit class UserInfo

property UserName as string '' we'll actually handle username here in
this class

public MustOverride Property IPAddress as string '' we let the concrete
classes handle this property




public class WebUserInfo : inherits UserInfo

public readonly property IPAddress as string
get
return Server(REMOTE_IP_ADDRESS)
end get
end property

end class


public class WinformsUserInfo : inherits UserInfo
public readonly property IPAddress as string
get
return string.Empty
end get
end property

end class



Your factory will return a UserInfo
and you'll still just pass back 1 or the 2 objects, based on that Nothing
check on the CurrentSession object.



Notice you don't have a bunch of "If" statements in the code saying what
the IP address is.
Each concrete class takes care of its own business.


While this is a simple situation you have, I'd actually spend a good amount
of time exploring it, and the factory approach.
If you can get this concept to "click", you start down a road of less
hacking, and more OO code.
But you gotta work through it, and figure out what is happening.


If you have biz logic with many many
"if this else this that the other"
lines... you can simplify your code by using factories.
The key of course (as stated at the very beginning) is that one should
"Code to an Interface, not an implementation"


...
 
M

Monty

Hi Sloan,

I am familiar with interface-based programming but, as you guessed, not up
on design patterns. I will research it more (though Rocky's VB 2005 BO book
is next in the queue), but in defiance of the sound principal "It's better
be quiet and be thought a fool than to open your mouth and prove it", my
initial impression on the difference between our two implementations (your
elegant one and my "whole lot of big nasty hack") is that after about the
3rd or 4th time that I had change something in ~both~ WebSessionObjectHolder
and InMemoryObjectHolder the honeymoon would be over. I am attracted to the
fact that my "hack" encapsulates all it's functionality into single, easily
maintainable class (as opposed to spread out between three classes and an
interface), does not duplicate the exact same properties and methods in two
different places, does not require a helper class and does not add four
files to my solution to maintain. In your implementation, since there will
never be more than two concrete classes that do 99% the same thing except
store themselves a little differently, is it worth all the overhead?

OK, I'm reading your posts again and wonder if you misunderstood my
implementation. You said:

=======================
DO NOT DO THIS............

Public Class UserInfo
public readonly property IPAddress as string
get
If Nothing = System.Web.HttpContext.Current Then
return "IPAddress not available in winforms"
Else 'Web Environment
Return Server(REMOTE_IP_ADDRESS)
End If
end get
end property

That's a whole lot of big nasty hack.
=======================

And in the next email you said:

=======================
Your factory will return a UserInfo
and you'll still just pass back 1 or the 2 objects, based on that
Nothing check on the CurrentSession object.

Notice you don't have a bunch of "If" statements in the code
saying what the IP address is. Each concrete class takes
care of its own business.
=======================

By my prototype doesn't do that at all. The only branching (between WebUI vs
WinForms) is in the GetInstance method, there is no branching in the
properties. Any relevant user info is stashed in the UserInfo object when
the user logs in (whether it be via web or windows), so there is no need for
the UserInfo class to reach out to the Server object to get the IP address
if it's in one environment or do something else if it's in another.
Regarding "Each concrete class takes care of its own business", the
"business" is 100% the same, with the only exception of how GetInstance does
it's magic depending on the environment.

Anyway, I will read up on patterns and perhaps I will see the error of my
ways. Thanks again for the sample app and blog post, it was very helpful.
 
S

Steven Cheng[MSFT]

Hi Monty,

Yes, I agree that if you want to put the code for both FormUI and ASP.NET
web application together in the same component class, you should use the
(HttpContext.Current == null) to detect and separate the code path. For
FormUI part, you can use a shared static class members while for ASP.NET,
you can use Session State or other per-user specific storage.


Sincerely,

Steven Cheng

Microsoft MSDN Online Support Lead


This posting is provided "AS IS" with no warranties, and confers no rights.
 
S

sloan

Ok.

Let me rephrase something.

When I say "big nasty hack", what I mean is that you don't want "if"
conditions in your UserInfo class based on the CurrentSession being
null/nothing or not.
Why?
Because every time you have a different property, you may have to add that
if statement to it.
Aka, if you have 100 properties, you would have 100 if checks about the
CurrentSession

That's where you code becomes non maintainable.




I think the cleanest solution is this.

Code up your UserInfo object.
Code it up so that it the properties are "dumb" in that you just put in
strings and bools and ints in it.
Make UserInfo serializable.

Create a UserInfoFactory class.

public class UserInfoFactory
private sub new
end sub

public shared function GetUserInfoBasedOnEnvironment as UserInfo

' use the CurrentSession check here
'populate properties of UserInfo based on whether you're in the web
or not


'at least with this approach you have encapsulated in ONE place where
all the if logic is.

'the main point im trying to make is DO NOT code up multiple "if"
statements in the UserInfo class itself.


end function

end class


I understand your concerns. And you're right about the duplication of
effort for the properties.


It looks like you just want to be able to "find the person who is doing the
action" whenever you need that info for a .Save operation.


So here is a little Employee.Save mockup


public class EmployeeController

public sub new
end sub

public sub SaveEmployee ( e as Employee ) 'notice the signature doesn't
have userInfo in it

dim ui as UserInfo
ui = UserInfoFactory.GetUserInfoBasedOnEnvironment()


dim dataLayerObject as EmployeeData
dataLayerObject = new EmployeeData


dataLayerObject.Save ( e.SSN, e. LastName, e.FirstName ,
u.UserID )

return

end sub

end class



Maybe that looks a little better.



I know you're getting flooded with info, but I would also read

6/5/2006
Custom Objects and Tiered Development II // 2.0


at my blog site (or the may 2006 version for 1.1)


If you still have questions, then maybe go back and reedit your original
post.


................

Just to add , the purpose of the
IObjectHolder

was not to be a template for creating other classes like that.

Its purpose was to give you a place to persist an object into memory,
WITHOUT having to write different code for asp.net or winforms.

I think you'd find it useful. Again, it would be for persisting an object
you've already got a hold of.


Lets say you find out that the
GetUserInfoBasedOnEnvironment

takes 10 minutes to run (in a winforms enviroment maybe) (this is all just
speculation to show a point) and generate a UserInfo object because it has
to check ActiveDirectory, and you're AD is slow.

So you decide "Man, I need that info, but its so slow I only want to that
price one time".

So you want to persist that object somewhere.

So in asp.net you say "I'll put it in the Session", And in winforms you say
"I'll throw into into memory as a singleton".

And you start coding away. And boom, you've got Session and winforms code
scattered out everywhere, aka, not easy to maintain.


Here is the method I would use ( talking about my blog)

''---------------------------------------------- abc
dim ui as UserInfo (code from above)

dim ioh as IObjectHolder
ioh = ObjectHolder.GetObjectHolder() '' it doesn't care whether or not I'm
in the web or winforms environment, the factory takes care of that for me

ioh.Add ( "SOMEKEY" , ui )



..............

Now its in the cache, and when I need it.


dim ui as UserInfo
ui = ObjectHolder.GetObjectHolder().Item("SOMEKEY")
if (not (ui is nothing) ) then
'I got the ui from the cache !!
end if

''---------------------------------def

You see the code between -------abc and -------------def

You put that in your biz layer AND ITS THE SAME CODE WHETHER YOURE IN
Asp.net Or winforms.


that's the beauty of the Factory. you're code on teh outside world becomes
cleaner.
because you're only got 1 "currentsession is nothing" check ( or 2 if you
count the other factory method)

That's what I mean by hacky code. When you can avoid writing the same "if"
statment all over the place, and have it in one place, and simplify your
code because you don't have intermixed asp.net and winforms code every AND
get reuse by pushing it into the biz layer, you're gaining significant
maintenance abilities.


I'll close with this.

The cost of software development is NOT the development time.
Its the maintenance costs.

So its fine to say "hmmm that doesn't look right". But at the same time
give it a serious try.
The argument "I'd have to add an extra class or extra interface" is not a
good one most times.

The design patterns give you the template for how to approach a common
problem. And sometimes you code up some extra files.
But the maintenance will be easier down the road.


Good luck. I think' you've hit some new concepts today!
 
M

Monty

Thanks Sloan, I really do appreciate your time. I'm pickin up what you're
puttin down, but I'm not sure that I've explained myself well. You said:
Why?
Because every time you have a different property, you may have to add that
if statement to it.
Aka, if you have 100 properties, you would have 100 if checks about the
CurrentSession

but if you look at my code (in my second post) and read my last post, you
should see that:
my prototype doesn't do that at all. The ~only~ branching (between WebUI
vs WinForms) is in the GetInstance method, there is no branching in the
properties. Any relevant user info is stashed in the UserInfo object when
the user logs in (whether it be via web or windows), so there is no need
for
the UserInfo class to reach out to the Server object to get the IP address
if it's in one environment or do something else if it's in another.

The only thing I don't understand is why you'd want a separate factory
object in this case, once given that there is only going to be one of these
classes ("UserInfo"). You said that this code is less hacky:
dim ui as UserInfo
ui = UserInfoFactory.GetUserInfoBasedOnEnvironment()
dim dataLayerObject as EmployeeData
dataLayerObject = new EmployeeData
dataLayerObject.Save (e.SSN, e. LastName, e.FirstName, _
u.UserID)

But with the creation method ("GetInstance") encapsulated in the object
itself my code would look like this:

dim dataLayerObject as EmployeeData
dataLayerObject = new EmployeeData
dataLayerObject.Save (e.SSN, e. LastName, e.FirstName, _
UserInfo.GetInstance.UserID)

Anyway, there is no need to reply if you don't like, you've certainly paid
your dues on this topic ;-). I thank you again for your time and your sample
code, it's been very helpful.
 
S

sloan

I think you want a UserInfo singleton.

But because you're in a hybrid environment, aspnet and winforms
picking how to implement that singleton is the issue.

I think you're clued into what's going on.
So take what I said with a grain of salt. Obviously, if we were sitting
next to each other at a comptuer, we could figure it out faster.

But I think you have the tools and warnings now.

Good luck.


dataLayerObject.Save (e.SSN, e. LastName, e.FirstName, _
UserInfo.GetInstance.UserID)


That's definately a UserInfo singleton syntax. Which is fine.
Sometimes I code out the objects just to make it clear what is going on.
The above syntax would be correct, the key being you made the UserInfo
singleton work in both environments.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,969
Messages
2,570,161
Members
46,705
Latest member
Stefkari24

Latest Threads

Top