Quantcast
Channel: Coding Everything
Viewing all articles
Browse latest Browse all 25

Using Shared Properties in ASP.NET and WinForms

$
0
0

A good business library should be able to drive behavior on any number of devices, exposing a application programming interface that is agnostic of its implementation.

However, we recently had a problem when we wanted to re-use a business library that was designed for a Windows Forms application for use in an ASP.NET web application. The problem was that our original application used shared properties to manage the state of the application.

Note: it seems prudent to avoid global variables, but this was where we found ourselves.

Why was this a problem? The original design meant that each instance of the application was owned by a single user under a single process. However, in ASP.NET, all users share a single process hosted by the ASP.NET runtime. Since shared variables are unique to the process they live on, variables that managed state for a single user (i.e. ActiveClient) would now be shared, overwritten, and cleared all at once by every single user within the web application.

Without massive re-writing of the business and application logic, we needed a way to give windows app users their own global variables as well as giving web app users a way to access their own instance of the shared properties.

Here’s how we did it:

First, consider what a normal shared property would look like in VB.NET:

PrivateShared _activeClient As Client
PublicSharedProperty ActiveClient() As Client
Get
Return _activeClient
EndGet
Set(ByVal value As Client)
_activeClient = Value
EndSet
EndProperty

Note: For this example, I have a class named Client but this could be any variable that I wanted to share access to across the entire application for a single user.

It’s important to breakdown all the things that a property does here. The property doesn’t actually hold the value across all instances; the private shared field does. The property just provides global accessors and setters to retrieve and set the value.

ASP.NET Session Variables

In ASP.NET, we can store values unique to each user inside of the Session Variable. To figure out how to integrate this with the first case, let’s consider what a typical session variable looks like:

'setter
Session("ActiveClient") = new Client()
'getter
Dim myClient = DirectCast(Session("ActiveClient"), Client)

This works well enough, but it’s a little messy. We have to manually perform casting ourselves and also keep track of all the key strings across the entire application.

Here’s one way to improve this (and other projects) by adding strong typing to the ASP.NET session variable:

PublicSharedProperty ActiveClient() As Client
Get
If HttpContext.Current.Session("ActiveClient") IsNothingThen
HttpContext.Current.Session("ActiveClient") = New Client
EndIf
ReturnDirectCast(HttpContext.Current.Session("ActiveClient"), Client)
EndGet
Set(value As Client)
HttpContext.Current.Session("ActiveClient") = value
EndSet
EndProperty

We still have global accessors and setters provided by the shared property, but inside of the Get and Set operations we’re using the session variable to store the value for each individual user. Note that we’re using the staticHttpContext.Current Property so the code doesn’t not have to live on a page with it’s own HttpContext.

Merging Both Properties

We now have a property in a windows application that stores a value unique to each user / process and a property in a web application that stores a value unique to each user / session. All that is left to do is to merge the properties accordingly.

The first step in doing so is to determine whether or not the assembly is currently executing as a web or windows application. We can do so by checking if the HttpContext Current property exists:

Dim isWebDeployed asBoolean = System.Web.HttpContext.Current IsNotNothing

Using that information, we can just expand the logic in our getters and setters to first check the execution environment and then grab the appropriate value:

PrivateShared _activeClient AsNew Client
PublicSharedProperty ActiveClient() As Client
Get
'check if deployed as web application
If System.Web.HttpContext.Current IsNotNothingThen
'if we've never loaded, create new instance just for session
If System.Web.HttpContext.Current.Session("ActiveClient") IsNothingThen
System.Web.HttpContext.Current.Session("ActiveClient") = New Client
EndIf
ReturnDirectCast(System.Web.HttpContext.Current.Session("ActiveClient"), Client)
Else
'application is windows application
Return _activeClient
EndIf
EndGet
Set(ByVal value As Client)
'check if deployed as web application
If System.Web.HttpContext.Current IsNotNothingThen
System.Web.HttpContext.Current.Session("ActiveClient") = Value
Else
_activeClient = Value
EndIf
EndSet
EndProperty

Extended Solution

Based off the number of instances you’re dealing with, the previous solution might work just fine. However, if you need to repeat this across multiple shared properties, you might want something a little more reusable.

Here’s a generic getter method that takes in the shared private field for a windows application and the key string for a web application and returns the appropriate value:

PublicSharedFunction GetPropertyBasedOnEnvironment(Of T)(ByRef sharedMember As T, ByVal propName AsString) As T
'check if deployed as web application
If HttpContext.Current IsNotNothingThen
'application is web application
ReturnTryCast(HttpContext.Current.Session(propName), T)
Else
'application is windows application
Return sharedMember
EndIf
EndFunction

Conversely, here’s generic setter method that can access the old value and assign it a new value depending on the current environment:

PublicSharedSub SetPropertyBasedOnEnvironment(Of T)(ByRef sharedMember As T, ByVal propName AsString, newValue As T)
'check if deployed as web application
If HttpContext.Current IsNotNothingThen
'application is web application
HttpContext.Current.Session(propName) = newValue
Else
'application is windows application
sharedMember = newValue
EndIf
EndSub

With those two methods accessible, we can now simplify our shared property using the following code:

PrivateShared _activeClient AsNew Client
PublicSharedProperty ActiveClient() As Client
Get
Return GetPropertyBasedOnEnvironment(_activeClient, "ActiveClient")
EndGet
Set(value As Client)
SetPropertyBasedOnEnvironment(_activeClient, "ActiveClient", value)
EndSet
EndProperty

Bonus: if you’re using .NET 4.5 or above, you can further simplify the custom getter and setter methods by using CallerMemberNameAttribute to have the compiler pass the key string for you, like this:

PublicSharedFunction GetPropertyBasedOnEnvironment(Of T)(
ByRef sharedMember As T,
<CallerMemberName> Optional propName AsString = Nothing) As T
'check if deployed as web application
If HttpContext.Current IsNotNothingThen
ReturnDirectCast(HttpContext.Current.Session(propName), T)
Else
Return sharedMember
EndIf
EndFunction

Again, there might be more elegant architectural solutions, but this helped resolve the issue we were having based on the situation we had coded ourselves into.


Viewing all articles
Browse latest Browse all 25

Trending Articles