Está en la página 1de 17

10,559,255 members (36,752 online)

Member 1969759

637

Sign out

home help

articles

quick answers

discussions

features

community
Search for articles, questions, tips

Articles Languages C# General

Next

Article Browse Code Bugs / Suggestions Stats Revisions (36) Alternatives Comments (53)

Easier .NET settings


By Antonio Naki Alfirevi, 17 Apr 2014
4.90 (84 votes) Rate:

About Article
Creating a library for persisting the application state data between work sessions Type Licence Article CPOL 14 Oct 2012 59,409 2,089 273 times

Download demo and source

1. Introduction
4.90/5 - 84 votes 4.89, a 0.99 [?]

First Posted Views Downloads Bookmarked

A common requirement for both web and desktop applications is persisting some elements of the application state between work sessions. The user starts up an application, inputs some data, changes some settings, moves and re-sizes windows and then closes the application. The next time they start the application it would be very nice if the settings they entered were remembered, and UI elements showed up as they were before the application was closed.
View this article's Workspace Fork this Workspace

C# ASP.NET .NETCF .NET Dev WPF WebForms , +

This requires the application to persist this data (most likely in a file) before it shuts down, and applies it when it starts up again. For a desktop application, this data could include locations and sizes of movable and re-sizable UI elements, user input (for example last entered username), as well as application settings and user preferences. After coming across this requirement more times than I care to remember, I decided to spend some time and make a reusable library that automates most of the work of persisting and applying settings. The entire library is a few hundred lines of code and is not very hard to understand. In this article I present the solution I came up with and describe what it can do, what value it provides, how to use it and the basic ideas behind it.

Connect using Git

Add your own alternative version

Share

2. Platforms
This library can be used for WPF, Windows Forms and ASP.NET (WebForms/MVC) applications. The required version of .NET is 4.0 or higher.

3. Reasoning and motivation behind this


The usual approach to persisting settings in a .NET application would be to use .config and .settings files via the built in configuration API. It allows for type safe access to configuration data, defining complex configuration settings, separation of user-level and application-level settings, run-time reading and writing, as well as manual modification of the settings via an XML editor. It does however involve a little too much ceremony in my opinion, with stuff like sub-classing C o n f i g u r a t i o n S e c t i o n for complex settings and hacking when handling plug-ins with their own settings. Also, (to my knowledge) the Visual Studio tool that generates settings classes does not allow you to intervene in what it generates (suppose you want to implement I N o t i f y P r o p e r t y C h a n g e din your settings class). But the biggest problem is that maintaining and using a large set of settings this way is tedious. The settings objects are usually not the ones that use the data , they just store data from all over the application. This means that to use this data you must write code that copies the data from settings to the appropriate objects and later writes updated data back again to the settings some time before the application closes. Suppose your application has several re-sizable and movable UI elements, and you want to remember and apply these sizes and locations the next time the application starts. Suppose you have 10 such UI elements, and for each of those you want to persist 4 properties (Height, Width, Left, Top) - a total of 40 properties just for this. You could add all those properties to your settings file, and write code that applies them to the corresponding UI element, then write additional code that updates the settings before

Top News
How to protect yourself from the Heartbleed Bug

the application closes. But manually adding settings and writing that code would be rather tedious and error prone. It would be much nicer if we could just declare that we want certain properties of certain objects tracked and have it taken care of more-or-less automatically. The main purpose of this library is just that - to enable you to persist and apply data directly on the object that uses it, and to do so in a declarative manner with minimal coding (decorate a property with an attribute). In the following chapters I demonstrate the use of the library, and discuss it's implementation.

Get the Insider News free each morning.

Related Videos

4. Terminology
In this article I use two terms which I think might need explaining: tracking a property - saving the value of an object's property before the application shuts down, and identifying the object and re-applying the saved value to it's property once the application starts up again. persistent property - a property that is being tracked

Related Articles
Gios WORD .NET Library (using RTF specification) Multiple Subsequent "Main" Forms in C# Apps .NET - COM Interoperability .Net - Use The Framework How to make your Web Reference proxy URL dynamic C# RangeBar control Clean Up that Bloated Web.Config! An Application Settings Manager that's Easy to Use Using a Generic Type to Simplify Flags Enumeration Operations Modular InnoSetup Dependency Installer Handling Corrupt "user.config" Settings Application Settings the .NET way. INI, Registry, or XML Get a User's Full Name Don't Flicker! Double Buffer! Debugging Made Easier with Mole 2010 RegEx Tracer Why, Where, and How of .NET Configuration Files Shape Control for .NET DialogForm - An Extended WinForms Class C# Application to Create and Recognize Mouse Gestures (.NET) Lifecycle Profile Settings How to Easily Host WPF Control inside Windows Form Application

5. Usage
The S e t t i n g s T r a c k e r is the class that coordinates tracking. It takes care of applying any previously stored data to your objects, and it stores new data from the desired objects to a persistent store when appropriate. When creating it, you need to tell it how to serialize data and where to store it. This is done by providing it with implementations of the I S e r i l i z e r and I D a t a S t o r e interfaces. For example:
Collapse | Copy Code

s t r i n gs e t t i n g s F i l e P a t h=P a t h . C o m b i n e ( E n v i r o n m e n t . G e t F o l d e r P a t h ( E n v i r o n m e n t . S p e c i a l F o l d e r . A p p l i c a t i o n D a t a ) ,@ " V e n d o r N a m e \ A p p N a m e \ s e t t i n g s . x m l " ) ; I S e r i a l i z e rs e r i a l i z e r=n e wB i n a r y S e r i a l i z e r ( ) ;/ / u s eb i n a r ys e r i a l i z a t i o n I D a t a S t o r ed a t a S t o r e=n e wF i l e D a t a S t o r e ( l o c a l S e t t i n g s P a t h ) ;/ / u s eaf i l et os t o r ed a t a S e t t i n g s T r a c k e rt r a c k e r=n e wS e t t i n g s T r a c k e r ( d a t a S t o r e ,s e r i a l i z e r ) ;/ / c r e a t eo u rs e t t i n g s t r a c k e r

Now we have a S e t t i n g s T r a c k e r instance which can track properties. It will use binary serialization to serialize data, and store the serialized data in a file. We should make this instance available to the rest of the application preferably by storing it in an IOC container, or for the sake of simplicity perhaps via a public static property. All we need to do now is to tell it which properties of which object to track. There are several ways of doing this.

5.1. Example scenario 1: Persisting a WPF window location and size


The idea is best illustrated by an example. Consider the scenario where you want to track the location, size and W i n d o w S t a t e of a WPF window. The work you would need to do if you were using a .settings file is shown on the left, while the code you would need to write with this library to achieve the same effect is shown on the right: A) Using a .settings file Step 1: Define a setting for each property of the main window B) Using this library Steps 1 and 2: Configure tracking and apply state... and we're done.
Collapse | Copy Code

p u b l i cM a i n W i n d o w ( ) { I n i t i a l i z e C o m p o n e n t ( ) ; / / 1 .s e tu pt r a c k i n gf o rt h em a i n w i n d o w S e r v i c e s . T r a c k e r . C o n f i g u r e ( t h i s ) . A d d P r o p e r t i e s < m a i n w i n d o w > ( w= > w . H e i g h t , w= >w . W i d t h ,w= >w . L e f t , w= >w . T o p ,w= > w . W i n d o w S t a t e ) ; . S e t K e y ( " M a i n W i n d o w " ) . S e t M o d e ( P e r s i s t M o d e s . A u t o m a t i c ) ; / / 2 .a p p l yp e r s i s t e ds t a t et ot h e w i n d o w S e r v i c e s . T r a c k e r . A p p l y S t a t e ( t h i s ) ; }

Related Research

Step 2: Apply stored data to the window properties


Collapse | Copy Code

p u b l i cM a i n W i n d o w ( ) { I n i t i a l i z e C o m p o n e n t ( ) ; t h i s . L e f t=

Protecting Your Business Data: Five Dos and Donts

In this example the static property

M y S e t t i n g s . D e f a u l t . M a i n W i n d o w L e f t ; t h i s . T o p= M y S e t t i n g s . D e f a u l t . M a i n W i n d o w T o p ; t h i s . W i d t h= M y S e t t i n g s . D e f a u l t . M a i n W i n d o w W i d t h ; t h i s . H e i g h t= M y S e t t i n g s . D e f a u l t . M a i n W i n d o w H e i g h t ; t h i s . W i n d o w S t a t e= M y S e t t i n g s . D e f a u l t . M a i n W i n d o w W i n d o w S t a t e ; }

S e r v i c e s . T r a c k e r holds a S e t t i n g s T r a c k e r
instance. This is for simplicity sake, a better way would be to keep the instance in an IOC container and resolve it from there.

Step 3: Persist updated data before the window is closed


Collapse | Copy Code

Custom API Management for the Enterprise: Learn how to build a successful API strategy [Webinar]

p r o t e c t e do v e r r i d ev o i dO n C l o s e d ( E v e n t A r g s e ) { M y S e t t i n g s . D e f a u l t . M a i n W i n d o w L e f t= t h i s . L e f t ; M y S e t t i n g s . D e f a u l t . M a i n W i n d o w T o p= t h i s . T o p ; M y S e t t i n g s . D e f a u l t . M a i n W i n d o w W i d t h= t h i s . W i d t h ; M y S e t t i n g s . D e f a u l t . M a i n W i n d o w H e i g h t= t h i s . H e i g h t ; M y S e t t i n g s . D e f a u l t . M a i n W i n d o w W i n d o w S t a t e= t h i s . W i n d o w S t a t e ; M y S e t t i n g s . D e f a u l t . S a v e ( ) ; } b a s e . O n C l o s e d ( e ) ;

Fine-Tuning the Engines of SMB Growth: 4 strategies for growing your business

The amount of work required for option A is quite substantial, even for a single window. Most likely it would be done using copy-paste and would be quite error prone and tedious work. If we had to track many controls throughout the application, the .settings file and intellisense would quickly become cluttered with a jungle of similarly named properties. In option B we just declare which properties of the main window we want to track, and give the main window a tracking identifier so we don't mix it's properties up with properties of some other object. Calling A p p l y S t a t e applies previously persisted data (if any) to the window, while new data is automatically persisted to the store before the application closes. No writing code that copies data back and forth. We can also specify the list of properties to track by using a [ T r a c k a b l e ]attribute on the class and/or it's properties provided we control the source code of the class. I demonstrate this in the next example.

Insider Secrets on API Security From Experts at Securosis [Webinar]

Example scenario 2: Persisting application settings (configuring tracking via Attributes)


Suppose you want to use an instance of the following class to hold your application's settings:
Collapse | Copy Code

[ T r a c k a b l e ] / / a p p l i e dt oc l a s s-a l lp r o p e r t i e sw i l lb et r a c k e d p u b l i cc l a s sG e n e r a l S e t t i n g s { p u b l i ci n tF o n t S i z e {g e t ;s e t ;} p u b l i cC o l o rF o n t C o l o r {g e t ;s e t ;} p u b l i cs t r i n gB a c k g r o u n d I m a g e P a t h{g e t ;s e t ;} }

Here is how we would configure tracking an instance of this class:


Collapse | Copy Code

S e r v i c e s . T r a c k e r . C o n f i g u r e ( s e t t i n g s O b j ) . A d d P r o p e r t i e s < G e n e r a l S e t t i n g s > ( s= >s . F o n t S i z e ,s= >s . F o n t C o l o r ,s= >s . B a c k g r o u n d I m a g e P a t h ) ;

There is also a slightly simpler way to specify the list of properties to track using the [ T r a c k a b l e ] attribute. I applied it to the class to specify that all public properties of this class should be tracked. To exclude a property, we would decorate it with [ T r a c k a b l e ( f a l s e ) ] . Here is how to configure tracking based on the use of [ T r a c k a b l e ] :
Collapse | Copy Code

S e r v i c e s . T r a c k e r . C o n f i g u r e ( s e t t i n g s O b j ) . A d d M e t a D a t a ( ) ; S e r v i c e s . T r a c k e r . A p p l y S t a t e ( s e t t i n g s O b j ) ;

Note that the settings class does not need to inherit any specific class, it can subclass whatever we like, and implement interfaces as we see fit (e.g., I N o t i f y P r o p e r t y C h a n g e d ) . For extra coolness, if we use an IOC container to build up our objects, we can use it to set up tracking for all objects it builds up. Most IOC containers allow you to add custom steps when injecting an object with dependencies. We can use this to add tracking automatically to any object that implements I T r a c k a b l e (just an empty marker interface to mark which objects to automatically track). In that case, all a class

needs to do to have its properties persisted is apply tracking attributes to itself and/or it's properties. The rest of the work will be done automatically by the extension we added to the IOC container.

6. Benefits
So what are the benefits of all this? To sum it up: less code - you just specify what properties of what object you want to track, you don't need to write code that copies values property-by-property back and forth from settings to other objects you don't have to explicitly add new properties in the .config or .settings file (and you don't have to come up with a name for each property of each object you want to persist) you specify the list of properties just once (when configuring tracking), instead of three times (1when defining the settings in a .config or .settings file. 2- when copying data from settings to other objects, and 3- when copying data back to settings) it's declarative - you can use attributes (T r a c k a b l e and T r a c k i n g K e y ) to configure what needs to be tracked and to identify the object if using an IOC container you can apply tracking with virtually no code aside from attributes on appropriate properties - more on this in the "IOC integration" chapter for web applications it can make your controller/page properties stateful For details on how all this is implemented, and how it can be used and customized, please read on...

7. The implementation
As with any complex problem, a sensible way to approach it would be to break it down into simple components. My approach here uses two basic components: serialization, and data storing mechanisms. These are the basis of my persistence library. Here is the class diagram of the library:

7.1. Building block 1 - Serialization


OK, so first things first - in order to store any data, we need to be able to convert the data into a persistable format. The obvious candidates for this format would be a string and a byte array. Byte array seems to be the lowest common denominator for data so I would suggest we use that. Let's declare the interface for serializers:
Collapse | Copy Code

p u b l i ci n t e r f a c eI S e r i a l i z e r { b y t e [ ]S e r i a l i z e ( o b j e c to b j ) ; o b j e c tD e s e r i a l i z e ( b y t e [ ]b y t e s ) ; }

Each class that implements this interface represents a mechanism of turning an object into a byte array and vice versa. Now lets create a simple implementation of this interface:
Collapse | Copy Code

p u b l i cc l a s sB i n a r y S e r i a l i z e r:I S e r i a l i z e r {

B i n a r y F o r m a t t e r_ f o r m a t t e r=n e wB i n a r y F o r m a t t e r ( ) ; p u b l i cb y t e [ ]S e r i a l i z e ( o b j e c to b j ) { u s i n g( M e m o r y S t r e a mm s=n e wM e m o r y S t r e a m ( ) ) { _ f o r m a t t e r . S e r i a l i z e ( m s ,o b j ) ; r e t u r nm s . G e t B u f f e r ( ) ; } } p u b l i co b j e c tD e s e r i a l i z e ( b y t e [ ]b y t e s ) { u s i n g( M e m o r y S t r e a mm s=n e wM e m o r y S t r e a m ( b y t e s ) ) { r e t u r n_ f o r m a t t e r . D e s e r i a l i z e ( m s ) ; } }

There we go. Now we have a class which can take on object graph and turn it into a series of bytes. Serialization is tricky business though, and regarding this implementation I should note that the use of B i n a r y F o r m a t t e r does impose certain limitations: serialized classes must be decorated with the [ S e r i a l i z a b l e ] attribute, events must be explicitly ignored (via [ f i e l d : N o n S e r i a l i z e d ] attribute), complex object graphs with circular references may break the serialization. That being said I have used this implementation in my own projects in several different scenarios and have yet to run into serious issues. Other implementations of the I S e r i a l i z e rinterface might for example use: JSON (JSON.NET implementation of I S e r i a l i z e r included in the library)

S o a p F o r m a t t e r
YAML protobuf.net (a cool open source serialization library) T y p e C o n v e r t e r based solutions custom solutions

7.2. Building block 2 - DataStore


Now that we can turn an object into a series of bytes we need to be able to store the serialized data into a persistent location. We can declare our interface for data stores as follows:
Collapse | Copy Code

p u b l i ci n t e r f a c eI D a t a S t o r e { b y t e [ ]G e t D a t a ( s t r i n gi d e n t i f i e r ) ; v o i dS e t D a t a ( b y t e[ ]d a t a ,s t r i n gi d e n t i f i e r ) ; }

Like the I S e r i l i z e r interface, this interface is also rather minimal. Classes implementing it enable us to store and retrieve (named) binary data to/from a persistent location. Candidate locations to persist data might include: file system (current application directory, %appsettings%, %allusersprofile%), registry (I would not recommend this due to access rights issues) database cookie ASP.NET session state (can be used to add stateful properties to controllers and/or pages) ASP.NET user profile other The implementation I am using here stores the data in an XML file - each entry is stored as a Base64 encoded string inside an XML tag with an Id attribute. Here is the code for the implementation:
Collapse | Copy Code

p u b l i cc l a s sF i l e D a t a S t o r e:I D a t a S t o r e { X D o c u m e n t_ d o c u m e n t ; c o n s ts t r i n gR O O T _ T A G=" D a t a " ; c o n s ts t r i n gI T E M _ T A G=" I t e m " ; c o n s ts t r i n gI D _ A T T R I B U T E=" I d " ; p u b l i cs t r i n gF i l e P a t h{g e t ;p r i v a t es e t ;} p u b l i cF i l e D a t a S t o r e ( s t r i n gf i l e P a t h ) { F i l e P a t h=f i l e P a t h ; i f( F i l e . E x i s t s ( F i l e P a t h ) ) { _ d o c u m e n t=X D o c u m e n t . L o a d ( F i l e P a t h ) ; } e l s e { _ d o c u m e n t=n e wX D o c u m e n t ( ) ; _ d o c u m e n t . A d d ( n e wX E l e m e n t ( R O O T _ T A G ) ) ; }

p u b l i cb y t e [ ]G e t D a t a ( s t r i n gi d e n t i f i e r )

X E l e m e n ti t e m E l e m e n t=G e t I t e m ( i d e n t i f i e r ) ; i f( i t e m E l e m e n t= =n u l l ) r e t u r nn u l l ; e l s e r e t u r nC o n v e r t . F r o m B a s e 6 4 S t r i n g ( ( s t r i n g ) i t e m E l e m e n t . V a l u e ) ;

p u b l i cv o i dS e t D a t a ( b y t e [ ]d a t a ,s t r i n gi d e n t i f i e r ) { X E l e m e n ti t e m E l e m e n t=G e t I t e m ( i d e n t i f i e r ) ; i f( i t e m E l e m e n t= =n u l l ) { i t e m E l e m e n t= n e wX E l e m e n t ( I T E M _ T A G ,n e wX A t t r i b u t e ( I D _ A T T R I B U T E ,i d e n t i f i e r ) ) ; _ d o c u m e n t . R o o t . A d d ( i t e m E l e m e n t ) ; } i t e m E l e m e n t . V a l u e=C o n v e r t . T o B a s e 6 4 S t r i n g ( d a t a ) ; _ d o c u m e n t . S a v e ( F i l e P a t h ) ;

p r i v a t eX E l e m e n tG e t I t e m ( s t r i n gi d e n t i f i e r ) { r e t u r n_ d o c u m e n t . R o o t . E l e m e n t s ( I T E M _ T A G ) . S i n g l e O r D e f a u l t ( e l= >( s t r i n g ) e l . A t t r i b u t e ( I D _ A T T R I B U T E )= =i d e n t i f i e r ) ; } p u b l i cb o o lC o n t a i n s K e y ( s t r i n gi d e n t i f i e r ) { r e t u r nG e t I t e m ( i d e n t i f i e r )! =n u l l ; }

Depending on the location of the file we choose to use, the data will be persisted in a user specific location or a global location. For instance if the file is located somewhere under %appsettings% it will be user specific, while if it is located under %allusersprofile% it will be global for all users. So now we can take an object, get its binary representation, and store that in a persistent store. These are all the building blocks we need. Let's move on and see how we can use them. * %appsettings% and %allusersprofile% refer to environment variables.

7.3. ObjectStore class


Using these two building blocks, we can easily create a class which can store and retrieve entire objects an object store. To distinguish between objects in the store we need to provide an identifier for the object when storing/retrieving it. The code for the object store class looks like this:
Collapse | Copy Code

n a m e s p a c eT r a c k i n g . D a t a S t o r i n g { p u b l i cc l a s sO b j e c t S t o r e:I O b j e c t S t o r e { I D a t a S t o r e_ d a t a S t o r e ; I S e r i a l i z e r_ s e r i a l i z e r ; p u b l i cb o o lC a c h e O b j e c t s{g e t ;s e t ;} D i c t i o n a r y < s t r i n g ,o b j e c t >_ c r e a t e d I n s t a n c e s=n e wD i c t i o n a r y < s t r i n g ,o b j e c t > ( ) ; p u b l i cO b j e c t S t o r e ( I D a t a S t o r ed a t a S t o r e ,I S e r i a l i z e rs e r i a l i z e r ) { _ d a t a S t o r e=d a t a S t o r e ; _ s e r i a l i z e r=s e r i a l i z e r ; C a c h e O b j e c t s=t r u e ; } p u b l i cv o i dP e r s i s t ( o b j e c tt a r g e t ,s t r i n gk e y ) { _ c r e a t e d I n s t a n c e s [ k e y ]=t a r g e t ; _ d a t a S t o r e . S e t D a t a ( _ s e r i a l i z e r . S e r i a l i z e ( t a r g e t ) ,k e y ) ; } p u b l i cb o o lC o n t a i n s K e y ( s t r i n gk e y ) { r e t u r n_ d a t a S t o r e . C o n t a i n s K e y ( k e y ) ; } p u b l i co b j e c tR e t r i e v e ( s t r i n gk e y ) { i f( ! C a c h e O b j e c t s| |! _ c r e a t e d I n s t a n c e s . C o n t a i n s K e y ( k e y ) ) _ c r e a t e d I n s t a n c e s [ k e y ]=_ s e r i a l i z e r . D e s e r i a l i z e ( _ d a t a S t o r e . G e t D a t a ( k e y ) ) ; r e t u r n_ c r e a t e d I n s t a n c e s [ k e y ] ; }

The implementation of the O b j e c t S t o r e is pretty straightforward. It will use any implementation of I S e r i a l i z e rand I D a t a S t o r e you give it (those familiar with DI/IOC will recognize constructor injection). One more thing you have perhaps noticed is the dictionary which is there to handle object identity (1 key = 1 object) and caching. So, instances of this class can save entire objects in a persistent location. This can be rather handy on its

own, but we can do more...

7.4. SettingsTracker class


Suppose we want to persist the size and location of the main window of our application. It would not make sense to persist an entire window object just to maintain its size and location (even if it could be done). Instead we have to track just the values of specific properties. As it's name suggests, the S e t t i n g s T r a c k e r class is the one that orchestrates the tracking of the properties of objects. This class uses the previously described O b j e c t S t o r eto store and retrieve the values of tracked properties. To track your object you must first tell the S e t t i n g s T r a c k e r instance what properties of the target you want to track, and when to persist those properties to the store. To accomplish this you must call the C o n f i g u r e ( o b j e c tt a r g e t ) method. This method returns a T r a c k i n g C o n f i g u r a t i o n object which you use to specify how to track your object. Here is an example showing how to configure persisting the size and location of a window:
Collapse | Copy Code

p u b l i cM a i n W i n d o w ( S e t t i n g s T r a c k e rt r a c k e r ) { I n i t i a l i z e C o m p o n e n t ( ) ; / / c o n f i g u r et r a c k i n go ft h em a i nw i n d o w t r a c k e r . C o n f i g u r e ( t h i s ) . A d d P r o p e r t i e s ( " H e i g h t " ," W i d t h " ," L e f t " ," T o p " ," W i n d o w S t a t e " ) . S e t K e y ( " T h e M a i n W i n d o w K e y " ) . S e t M o d e ( P e r s i s t M o d e s . A u t o m a t i c ) ; / / a p p l yp e r s i s t e ds t a t et ot h ew i n d o w t r a c k e r . A p p l y S t a t e ( t h i s ) ; / / . . .

Here we fetch the configuration for tracking our window, we tell it which properties to persist, we specify the identifier (key) for the target object, and lastly we specify automatic mode which means persist the properties just before the application closes. If you don't like using hard coded strings when specifying properties, you can instead use the other overload of the A d d P r o p e r t i e s method like so:
Collapse | Copy Code

A d d P r o p e r t i e s < M a i n W i n d o w > ( w= >w . H e i g h t ,w= >w . W i d t h ,w= >w . L e f t ,w= >w . T o p ,w= > w . W i n d o w S t a t e )

This overload analyzes the expression trees to determine the correct properties, thus eliminating the need for hard coded strings. The S e t t i n g s T r a c k e r stores a list of all T r a c k i n g C o n f i g u r a t i o n objects it creates. It makes sure that there is exactly one configuration object per target, so each time you call Configure() for the same target, you always get the same T r a c k i n g C o n f i g u r a t i o nobject. Applying state: After you have configured what properties you want to track, you can apply any previously persisted state to those properties by calling the t r a c k e r . A p p l y S t a t e ( o b j e c tt a r g e t ) method. Storing state: In the configuration, you can set the tracking mode to be manual or automatic. If you have chosen the automatic tracking mode (this is the default), the values of the target's properties will be stored just before the application closes (or before the session ends for web apps). If, instead, you want to store them at some earlier time, use manual mode, and explicitly call the t r a c k e r . P e r s i s t S t a t e ( t a r g e t ) method when appropriate. When persisting a target object's properties, the settings tracker will: 1. locate the T r a c k i n g C o n f i g u r a t i o n for the target 2. for each property that is specified in the target's configuration: 1. construct a key by concatenating the target object type, the target's tracking key, and the property name ( [ T a r g e t O b j e t T y p e ] _ [ T a r g e t O b j e c t K e y ] . [ P r o p e r t y N a m e ] ) . 2. get the value of the property using reflection, and save it to the store using the constructed key as the identifier. So for the window in the previous example the P e r s i s t S t a t emethod would store 5 objects to the O b j e c t S t o r eand the keys would be:

D e m o T r a c k i n g . M a i n W i n d o w _ T h e M a i n W i n d o w K e y . H e i g h t D e m o T r a c k i n g . M a i n W i n d o w _ T h e M a i n W i n d o w K e y . W i d t h D e m o T r a c k i n g . M a i n W i n d o w _ T h e M a i n W i n d o w K e y . L e f t D e m o T r a c k i n g . M a i n W i n d o w _ T h e M a i n W i n d o w K e y . T o p D e m o T r a c k i n g . M a i n W i n d o w _ T h e M a i n W i n d o w K e y . W i n d o w S t a t e
Note: Since there will only ever be one instance of the M a i n W i n d o wclass in the application, we didn't really

have to specify the key for the window object (using the S e t K e ymethod) since it is already uniquely identified by it's class name. The A p p l y S t a t e method does almost the same thing as P e r s i s t S t a t ebut moves the data in the opposite direction, from the store to the object's properties. Ok, let's get back to the code, the following is the code for the T r a c k i n g C o n f i g u r a t i o nclass:
Collapse | Copy Code

n a m e s p a c eT r a c k i n g { p u b l i ce n u mP e r s i s t M o d e s { / / /< s u m m a r y > / / /S t a t ei sp e r s i s t e da u t o m a t i c a l l yu p o na p p l i c a t i o nc l o s e / / /< / s u m m a r y > A u t o m a t i c , / / /< s u m m a r y > / / /S t a t ei sp e r s i s t e do n l yu p o nr e q u e s t / / /< / s u m m a r y > M a n u a l } p u b l i cc l a s sT r a c k i n g C o n f i g u r a t i o n { p u b l i cs t r i n gK e y{g e t ;s e t ;} p u b l i cH a s h S e t < s t r i n g >P r o p e r t i e s{g e t ;s e t ;} p u b l i cW e a k R e f e r e n c eT a r g e t R e f e r e n c e{g e t ;s e t ;} p u b l i cP e r s i s t M o d e sM o d e{g e t ;s e t ;} p u b l i cs t r i n gT r a c k e r N a m e{g e t ;s e t ;} p u b l i cT r a c k i n g C o n f i g u r a t i o n ( o b j e c tt a r g e t ) { t h i s . T a r g e t R e f e r e n c e=n e wW e a k R e f e r e n c e ( t a r g e t ) ; P r o p e r t i e s=n e wH a s h S e t < s t r i n g > ( ) ; } / / /< s u m m a r y > / / /B a s e do nT r a c k a b l ea n dT r a c k i n g K e ya t t r i b u t e s ,a d d sp r o p e r t i e s / / /a n ds e t t st h ek e y . / / /< / s u m m a r y > / / /< r e t u r n s > < / r e t u r n s > p u b l i cT r a c k i n g C o n f i g u r a t i o nA d d M e t a D a t a ( ) { P r o p e r t y I n f ok e y P r o p e r t y=T a r g e t R e f e r e n c e . T a r g e t . G e t T y p e ( ) . G e t P r o p e r t i e s ( ) . S i n g l e O r D e f a u l t ( p i= >p i . I s D e f i n e d ( t y p e o f ( T r a c k i n g K e y A t t r i b u t e ) ,t r u e ) ) ; i f( k e y P r o p e r t y! =n u l l ) K e y=k e y P r o p e r t y . G e t V a l u e ( T a r g e t R e f e r e n c e . T a r g e t ,n u l l ) . T o S t r i n g ( ) ; / / s e ei fT r a c k a b l e A t t r i b u t e ( t r u e )e x i s t so nt h et a r g e tc l a s s b o o li s C l a s s M a r k e d A s T r a c k a b l e=f a l s e ; T r a c k a b l e A t t r i b u t et a r g e t C l a s s T r a c k a b l e A t t= T a r g e t R e f e r e n c e . T a r g e t . G e t T y p e ( ) . G e t C u s t o m A t t r i b u t e s ( t r u e ) . O f T y p e < T r a c k a b l e A t t r i b u t e > ( ) . W h e r e ( t a = > t a . T r a c k e r N a m e= =T r a c k e r N a m e ) . F i r s t O r D e f a u l t ( ) ; i f( t a r g e t C l a s s T r a c k a b l e A t t! =n u l l& &t a r g e t C l a s s T r a c k a b l e A t t . I s T r a c k a b l e ) i s C l a s s M a r k e d A s T r a c k a b l e=t r u e ; / / a d dp r o p e r t i e st h a tn e e dt ob et r a c k e d f o r e a c h( P r o p e r t y I n f op ii nT a r g e t R e f e r e n c e . T a r g e t . G e t T y p e ( ) . G e t P r o p e r t i e s ( ) ) { T r a c k a b l e A t t r i b u t ep r o p T r a c k a b l e A t t= p i . G e t C u s t o m A t t r i b u t e s ( t r u e ) . O f T y p e < T r a c k a b l e A t t r i b u t e > ( ) . W h e r e ( t a = > t a . T r a c k e r N a m e= =T r a c k e r N a m e ) . F i r s t O r D e f a u l t ( ) ; i f( p r o p T r a c k a b l e A t t= =n u l l ) { / / i ft h ep r o p e r t yi sn o tm a r k e dw i t hT r a c k a b l e ( t r u e ) ,c h e c ki ft h e i f ( i s C l a s s M a r k e d A s T r a c k a b l e ) A d d P r o p e r t i e s ( p i . N a m e ) ; } e l s e { i f ( p r o p T r a c k a b l e A t t . I s T r a c k a b l e ) A d d P r o p e r t i e s ( p i . N a m e ) ; }

c l a s si s

} r e t u r nt h i s ;

p u b l i cT r a c k i n g C o n f i g u r a t i o nA d d P r o p e r t i e s ( p a r a m ss t r i n g [ ]p r o p e r t i e s ) { f o r e a c h( s t r i n gp r o p e r t yi np r o p e r t i e s ) P r o p e r t i e s . A d d ( p r o p e r t y ) ; r e t u r nt h i s ; } p u b l i cT r a c k i n g C o n f i g u r a t i o nA d d P r o p e r t i e s ( p a r a m sE x p r e s s i o n < F u n c < o b j e c t > > [ ] p r o p e r t i e s ) { A d d P r o p e r t i e s ( p r o p e r t i e s . S e l e c t ( p= >( ( p . B o d ya s U n a r y E x p r e s s i o n ) . O p e r a n da sM e m b e r E x p r e s s i o n ) . M e m b e r . N a m e ) . T o A r r a y ( ) ) ; r e t u r nt h i s ; } p u b l i cT r a c k i n g C o n f i g u r a t i o nR e m o v e P r o p e r t i e s ( p a r a m ss t r i n g [ ]p r o p e r t i e s ) { f o r e a c h( s t r i n gp r o p e r t yi np r o p e r t i e s ) P r o p e r t i e s . R e m o v e ( p r o p e r t y ) ;

r e t u r nt h i s ; } p u b l i cT r a c k i n g C o n f i g u r a t i o nR e m o v e P r o p e r t i e s ( p a r a m sE x p r e s s i o n < F u n c < o b j e c t > > [ ] p r o p e r t i e s ) { R e m o v e P r o p e r t i e s ( p r o p e r t i e s . S e l e c t ( p= >( ( p . B o d ya s U n a r y E x p r e s s i o n ) . O p e r a n da sM e m b e r E x p r e s s i o n ) . M e m b e r . N a m e ) . T o A r r a y ( ) ) ; r e t u r nt h i s ; } p u b l i cT r a c k i n g C o n f i g u r a t i o nS e t M o d e ( P e r s i s t M o d e sm o d e ) { t h i s . M o d e=m o d e ; r e t u r nt h i s ; } p u b l i cT r a c k i n g C o n f i g u r a t i o nS e t K e y ( s t r i n gk e y ) { t h i s . K e y=k e y ; r e t u r nt h i s ; }

This class uses method chaining - each method returns the same T r a c k i n g C o n f i g u r a t i o nobject thus facilitating further method calls. The implementation is mostly straightforward. One thing to mention is the A d d M e t a D a t a method - it is used when tracking is configured via attributes. Note that the configuration object stores a W e a k R e f e r e n c eto the target so it does not make it live longer than it needs to. And here is the code for the S e t t i n g s T r a c k e r class:
Collapse | Copy Code

p u b l i cc l a s sS e t t i n g s T r a c k e r { L i s t < T r a c k i n g C o n f i g u r a t i o n >_ c o n f i g u r a t i o n s=n e wL i s t < T r a c k i n g C o n f i g u r a t i o n > ( ) ; p u b l i cs t r i n gN a m e{g e t ;s e t ;} I O b j e c t S t o r e_ o b j e c t S t o r e ; p u b l i cS e t t i n g s T r a c k e r ( I O b j e c t S t o r eo b j e c t S t o r e ) { _ o b j e c t S t o r e=o b j e c t S t o r e ; W i r e U p A u t o m a t i c P e r s i s t ( ) ; } # r e g i o na u t o m a t i cp e r s i s t i n g p r o t e c t e dv i r t u a lv o i dW i r e U p A u t o m a t i c P e r s i s t ( ) { i f( S y s t e m . W i n d o w s . A p p l i c a t i o n . C u r r e n t! =n u l l ) / / w p f S y s t e m . W i n d o w s . A p p l i c a t i o n . C u r r e n t . E x i t+ =( s ,e )= >{ P e r s i s t A u t o m a t i c T a r g e t s ( ) ;} ; e l s ei f( S y s t e m . W i n d o w s . F o r m s . A p p l i c a t i o n . O p e n F o r m s . C o u n t>0 ) / / w i n f o r m s S y s t e m . W i n d o w s . F o r m s . A p p l i c a t i o n . A p p l i c a t i o n E x i t+ =( s ,e )= >{ P e r s i s t A u t o m a t i c T a r g e t s ( ) ;} ; } p u b l i cv o i dP e r s i s t A u t o m a t i c T a r g e t s ( ) { f o r e a c h( T r a c k i n g C o n f i g u r a t i o nc o n f i gi n_ c o n f i g u r a t i o n s . W h e r e ( c f g= >c f g . M o d e= =P e r s i s t M o d e s . A u t o m a t i c& &c f g . T a r g e t R e f e r e n c e . I s A l i v e ) ) P e r s i s t S t a t e ( c o n f i g . T a r g e t R e f e r e n c e . T a r g e t ) ; } # e n d r e g i o n p u b l i cT r a c k i n g C o n f i g u r a t i o nC o n f i g u r e ( o b j e c tt a r g e t ) { T r a c k i n g C o n f i g u r a t i o nc o n f i g=F i n d E x i s t i n g C o n f i g ( t a r g e t ) ; i f( c o n f i g= =n u l l ) { c o n f i g=n e wT r a c k i n g C o n f i g u r a t i o n ( t a r g e t ){T r a c k e r N a m e=N a m e} ; _ c o n f i g u r a t i o n s . A d d ( c o n f i g ) ; } r e t u r nc o n f i g ; } p u b l i cv o i dA p p l y A l l S t a t e ( ) { f o r e a c h( T r a c k i n g C o n f i g u r a t i o nc o n f i gi n _ c o n f i g u r a t i o n s . W h e r e ( c = > c . T a r g e t R e f e r e n c e . I s A l i v e ) ) A p p l y S t a t e ( c o n f i g . T a r g e t R e f e r e n c e . T a r g e t ) ; } p u b l i cv o i dA p p l y S t a t e ( o b j e c tt a r g e t ) { T r a c k i n g C o n f i g u r a t i o nc o n f i g=F i n d E x i s t i n g C o n f i g ( t a r g e t ) ; D e b u g . A s s e r t ( c o n f i g! =n u l l ) ; I T r a c k i n g A w a r et r a c k i n g A w a r e T a r g e t=t a r g e ta sI T r a c k i n g A w a r e ; i f( ( t r a c k i n g A w a r e T a r g e t= =n u l l )| |t r a c k i n g A w a r e T a r g e t . O n A p p l y i n g S t a t e ( c o n f i g ) ) { f o r e a c h( s t r i n gp r o p e r t y N a m ei nc o n f i g . P r o p e r t i e s ) { P r o p e r t y I n f op r o p e r t y=t a r g e t . G e t T y p e ( ) . G e t P r o p e r t y ( p r o p e r t y N a m e ) ; s t r i n gp r o p K e y=C o n s t r u c t P r o p e r t y K e y ( t a r g e t . G e t T y p e ( ) . F u l l N a m e ,c o n f i g . K e y ,p r o p e r t y . N a m e ) ; t r y {

} c a t c h { D e b u g . W r i t e L i n e ( " A p p l y i n go fv a l u e' { p r o p K e y } 'f a i l e d ! " ) ; }

i f( _ o b j e c t S t o r e . C o n t a i n s K e y ( p r o p K e y ) ) { o b j e c ts t o r e d V a l u e=_ o b j e c t S t o r e . R e t r i e v e ( p r o p K e y ) ; p r o p e r t y . S e t V a l u e ( t a r g e t ,s t o r e d V a l u e ,n u l l ) ; }

p u b l i cv o i dP e r s i s t S t a t e ( o b j e c tt a r g e t ) { T r a c k i n g C o n f i g u r a t i o nc o n f i g=F i n d E x i s t i n g C o n f i g ( t a r g e t ) ; D e b u g . A s s e r t ( c o n f i g! =n u l l ) ; I T r a c k i n g A w a r et r a c k i n g A w a r e T a r g e t=t a r g e ta sI T r a c k i n g A w a r e ; i f( ( t r a c k i n g A w a r e T a r g e t= =n u l l )| |t r a c k i n g A w a r e T a r g e t . O n P e r s i s t i n g S t a t e ( c o n f i g ) ) { f o r e a c h( s t r i n gp r o p e r t y N a m ei nc o n f i g . P r o p e r t i e s ) { P r o p e r t y I n f op r o p e r t y=t a r g e t . G e t T y p e ( ) . G e t P r o p e r t y ( p r o p e r t y N a m e ) ; s t r i n gp r o p K e y=C o n s t r u c t P r o p e r t y K e y ( t a r g e t . G e t T y p e ( ) . F u l l N a m e ,c o n f i g . K e y ,p r o p e r t y . N a m e ) ; t r y { o b j e c tc u r r e n t V a l u e=p r o p e r t y . G e t V a l u e ( t a r g e t ,n u l l ) ; _ o b j e c t S t o r e . P e r s i s t ( c u r r e n t V a l u e ,p r o p K e y ) ; } c a t c h { D e b u g . W r i t e L i n e ( " P e r s i s t i n go fv a l u e' { p r o p K e y } 'f a i l e d ! " ) ; }

# r e g i o np r i v a t eh e l p e rm e t h o d s p r i v a t eT r a c k i n g C o n f i g u r a t i o nF i n d E x i s t i n g C o n f i g ( o b j e c tt a r g e t ) { / / . T a r g e t R e f e r e n c e . T a r g e t>( T r a c k e d T a r g e t ) . ( W e a k R e f e r e n c e T a r g e t ) r e t u r n_ c o n f i g u r a t i o n s . S i n g l e O r D e f a u l t ( c f g= >c f g . T a r g e t R e f e r e n c e . T a r g e t= = t a r g e t ) ; } / / h e l p e rm e t h o df o rc r e a t i n ga ni d e n t i f i e r / / f r o mt h eo b j e c tt y p e ,o b j e c tk e y ,a n dt h ep r o p e r yn a m e p r i v a t es t r i n gC o n s t r u c t P r o p e r t y K e y ( s t r i n gt a r g e t T y p e N a m e , s t r i n go b j e c t K e y ,s t r i n gp r o p e r t y N a m e ) { r e t u r ns t r i n g . F o r m a t ( " { 0 } _ { 1 } . { 2 } " ,t a r g e t T y p e N a m e ,o b j e c t K e y ,p r o p e r t y N a m e ) ; } # e n d r e g i o n

Depending on the type of application (WinForms, WPF, ASP.NET), the W i r e U p A u t o m a t i c P e r s i s t method subscribes to the appropriate event that indicates when targets with P e r s i s t M o d e . A u t o m a t i c should be persisted. All the other important methods (C o n f i g u r e ,A p p l y S t a t e , and P e r s i s t S t a t e ) have already been described...

7.5. Configuring tracking by attributes


An alternative way to configure tracking is to use the T r a c k a b l e and T r a c k i n g K e y attributes.
Collapse | Copy Code

/ / /< s u m m a r y > / / /I fa p p l i e dt oac l a s s ,m a k e sa l lp r o p e r t i e st r a c k a b l eb yd e f a u l t . / / /I fa p p l i e dt oap r o p e r t ys p e c i f i e si ft h ep r o p e r t ys h o u l db et r a c k e d . / / /< r e m a r k s > / / /A t t r i b u t e so np r o p e r t i e so v e r r i d ea t t r i b u t e so nt h ec l a s s . / / /< / r e m a r k s > / / /< / s u m m a r y > [ A t t r i b u t e U s a g e ( A t t r i b u t e T a r g e t s . P r o p e r t y| A t t r i b u t e T a r g e t s . C l a s s ,A l l o w M u l t i p l e=f a l s e ,I n h e r i t e d=t r u e ) ] p u b l i cc l a s sT r a c k a b l e A t t r i b u t e:A t t r i b u t e { p u b l i cb o o lI s T r a c k a b l e{g e t ;s e t ;} p u b l i cs t r i n gT r a c k e r N a m e{g e t ;s e t ;} p u b l i cT r a c k a b l e A t t r i b u t e ( ) { I s T r a c k a b l e=t r u e ; } p u b l i cT r a c k a b l e A t t r i b u t e ( b o o li s T r a c k a b e ) { I s T r a c k a b l e=i s T r a c k a b e ; }

/ / /< s u m m a r y > / / /M a r k st h ep r o p e r t ya st h et r a c k i n gi d e n t i f i e rf o rt h eo b j e c t . / / /T h ep r o p e r t yw i l li nm o s tc a s e sb eo ft y p eS t r i n g ,G u i do rI n t / / /< / s u m m a r y > [ A t t r i b u t e U s a g e ( A t t r i b u t e T a r g e t s . P r o p e r t y ,A l l o w M u l t i p l e=f a l s e ) ] p u b l i cc l a s sT r a c k i n g K e y A t t r i b u t e:A t t r i b u t e { }

Instead of calling c o n f i g u r a t i o n . A d d P r o p e r t i e s ( [ l i s to fp r o p e r t i e s ] ) for a target, we can mark the relevant properties of the target's class (or the entire class) with the T r a c k a b l e A t t r i b u t e . Also, instead of calling c o n f i g u r a t i o n . S e t K e y ( [ s o m ek e y ] ) , we can mark a property with the T r a c k i n g K e yattribute, which will cause that property to behave like an ID property the value of this property will be the identifier (key) of the target object. These two attributes allow us to specify which properties to track and the tracking key at the class level, instead of having to specify this data for every instance we want to track. Another benefit to this is that it enables automatic tracking if we are using an IOC container- we simply hook into the container so that after it has injected dependencies on an object we call A d d M e t a d a t a and A p p y S e t t i n g s if the object implements the marker interface I T r a c k a b l e .

7.6. The ITrackingAware interface


When defining a class, its not always possible to decorate the properties with attributes. For instance, when we subclass S y s t e m . W i n d o w s . W i n d o wwe don't have control over the properties that are defined in it (unless they are virtual) because we dont control the source code of the W i n d o wclass, so we can't decorate them with attributes. In this case, we can, instead, implement the I T r a c k i n g A w a r e interface which looks like this:
Collapse | Copy Code

/ / /< s u m m a r y > / / /A l l o w st h eo b j e c tt h a ti sb e i n gt r a c k e dt oc u s t o m i z e / / /i t sp e r s i t e n c e / / /< / s u m m a r y > p u b l i ci n t e r f a c eI T r a c k i n g A w a r e:I T r a c k a b l e { / / /< s u m m a r y > / / /C a l l e db e f o r ea p p l y i n gp e r s i s t e ds t a t et ot h eo b j e c t . / / /< / s u m m a r y > / / /< p a r a mn a m e = " c o n f i g u r a t i o n " > < / p a r a m > / / /< r e t u r n s > R e t u r nf a l s et oc a n c e la p p l y i n gs t a t e < / r e t u r n s > b o o lO n A p p l y i n g S t a t e ( T r a c k i n g C o n f i g u r a t i o nc o n f i g u r a t i o n ) ; / / /< s u m m a r y > / / /C a l l e da f t e rs t a t ea p l i e d . / / /< / s u m m a r y > / / /< r e t u r n s > < / r e t u r n s > v o i dO n A p p l i e d S t a t e ( ) ; / / /< s u m m a r y > / / /C a l l e db e f o r ep e r s i s t i n go b j e c ts t a t e . / / /< / s u m m a r y > / / /< p a r a mn a m e = " c o n f i g u r a t i o n " > < / p a r a m > / / /< r e t u r n s > R e t u r nf a l s et oc a n c e lp e r s i s t i n gs t a t e < / r e t u r n s > b o o lO n P e r s i s t i n g S t a t e ( T r a c k i n g C o n f i g u r a t i o nc o n f i g u r a t i o n ) ; / / /< s u m m a r y > / / /C a l l e da f t e rs t a t ep e r s i s t e d . / / /< / s u m m a r y > / / /< p a r a mn a m e = " c o n f i g u r a t i o n " > < / p a r a m > / / /< r e t u r n s > < / r e t u r n s > v o i dO n P e r s i s t e d S t a t e ( ) ;

This interface allows us to modify the tracking configuration before applying and persisting state, and even to cancel either of those. This can also come in handy for WindowsForms, where Forms have bogus sizes and locations when minimized in this case we can cancel persisting a minimized window.

7.7. IOC integration


Now for the cool part... When using an IOC container (Unity/Castle Windsor/Ninject/Lin Fu etc...) in an application, a lot of objects are either created or built up (have their dependencies injected) by the IOC container. So why not have the container automatically configure tracking and apply state to all trackable objects it builds up! This way, if your object is going to be built up by the container, all you need to do to make a property persistent is: 1. make sure the class that defines the property implements the empty marker interface I T r a c k a b l e and decorate the property with [ T r a c k a b l e ] , - or 2. implement the I T r a c k i n g A w a r e interface in the appropriate way The I T r a c k a b l e interface has no members, and serves only as a marker to let the IOC extension know you want to automatically track objects that have it. I opted to use an interface instead of an attribute for this because checking for the existence of an attribute is a little bit slower than checking for an interface. Note: I T r a c k i n g A w a r e already inherits from I T r a c k a b l e .

So far, I have used this approach with Unity and Ninject but I suspect it should not be hard to do with other IOC containers. Here is the code for the U n i t y C o n t a i n e r E x t e n s i o n which automatically adds tracking to objects:
Collapse | Copy Code

n a m e s p a c eT r a c k i n g { / / /< s u m m a r y > / / /M a r k e ri n t e r f a c ef o rc l a s s e st h a tw a n tt h e i rt r a c k i n gt ob eh a n d l e d / / /b yt h eI O Cc o n t a i n e r . / / /< r e m a r k s > / / /C h e c k i n gi fac l a s si m p l e m e n t sa ni n t e r f a c ei sf a s t e rt h a tc h e c k i n g / / /i fi t sd e c o r a t e dw i t ha na t t r i b u t e . / / /< / r e m a r k s > / / /< / s u m m a r y > p u b l i ci n t e r f a c eI T r a c k a b l e { } / / /< s u m m a r y > / / /U n i t ye x t e n s i o nf o ra d d i n g( a t t r i b u t eb a s e d )s t a t et r a c k i n gt oc r e t e do b j e c t s / / /< / s u m m a r y > p u b l i cc l a s sT r a c k i n g E x t e n s i o n:U n i t y C o n t a i n e r E x t e n s i o n { c l a s sT r a c k i n g S t r a t e g y:B u i l d e r S t r a t e g y { I U n i t y C o n t a i n e r_ c o n t a i n e r ; p u b l i cT r a c k i n g S t r a t e g y ( I U n i t y C o n t a i n e rc o n t a i n e r ) { _ c o n t a i n e r=c o n t a i n e r ; } p u b l i co v e r r i d ev o i dP o s t B u i l d U p ( I B u i l d e r C o n t e x tc o n t e x t ) { b a s e . P o s t B u i l d U p ( c o n t e x t ) ; I T r a c k a b l ea u t o T r a c k e d=c o n t e x t . E x i s t i n ga sI T r a c k a b l e ; i f( a u t o T r a c k e d! =n u l l ) { I E n u m e r a b l e < S e t t i n g s T r a c k e r >t r a c k e r s= _ c o n t a i n e r . R e s o l v e A l l < S e t t i n g s T r a c k e r > ( ) ; f o r e a c h( S e t t i n g s T r a c k e rt r a c k e ri nt r a c k e r s ) { t r a c k e r . C o n f i g u r e ( a u t o T r a c k e d ) . A d d M e t a D a t a ( ) . S e t M o d e ( P e r s i s t M o d e s . A u t o m a t i c ) ; t r a c k e r . A p p l y S t a t e ( a u t o T r a c k e d ) ; } } }

p r o t e c t e do v e r r i d ev o i dI n i t i a l i z e ( ) { C o n t e x t . S t r a t e g i e s . A d d ( n e wT r a c k i n g S t r a t e g y ( C o n t a i n e r ) ,U n i t y B u i l d S t a g e . C r e a t i o n ) ; }

This is how one would configure their Unity container for adding tracking support, using this extension:
Collapse | Copy Code

I U n i t y C o n t a i n e r_ c o n t a i n e r=n e wU n i t y C o n t a i n e r ( ) ; s t r i n gl o c a l S e t t i n g s F i l e P a t h=P a t h . C o m b i n e ( E n v i r o n m e n t . G e t F o l d e r P a t h ( E n v i r o n m e n t . S p e c i a l F o l d e r . A p p l i c a t i o n D a t a ) ," t e s t s e t t i n g s w i t h I O C . x m l " ) ; _ c o n t a i n e r . R e g i s t e r T y p e < I D a t a S t o r e ,F i l e D a t a S t o r e > ( n e wC o n t a i n e r C o n t r o l l e d L i f e t i m e M a n a g e r ( ) ,n e w I n j e c t i o n C o n s t r u c t o r ( l o c a l S e t t i n g s F i l e P a t h ) ) ; _ c o n t a i n e r . R e g i s t e r T y p e < I S e r i a l i z e r ,B i n a r y S e r i a l i z e r > ( n e w C o n t a i n e r C o n t r o l l e d L i f e t i m e M a n a g e r ( ) ) ; _ c o n t a i n e r . R e g i s t e r T y p e < I O b j e c t S t o r e ,O b j e c t S t o r e > ( n e w C o n t a i n e r C o n t r o l l e d L i f e t i m e M a n a g e r ( ) ) ; _ c o n t a i n e r . R e g i s t e r T y p e < S e t t i n g s T r a c k e r > ( n e wC o n t a i n e r C o n t r o l l e d L i f e t i m e M a n a g e r ( ) ) ; _ c o n t a i n e r . A d d E x t e n s i o n ( n e wT r a c k i n g E x t e n s i o n ( ) ) ;

8. What about web apps?


In web apps, objects have a very short lifespan indeed. They are created when the server starts processing a request and discarded as soon as the response is sent. With the exception of manually storing data on the server (for example by using the Session store or user profiles), the server does not keep any application state. Instead, (any) state is passed arround from client to server and back again with each request-response (inside the querry string, form data, cookies etc...). The "Session" object can, for example, be used to maintain state but it's clunky, and the compiler can't ensure type and name safety of the data inside it. Using this library in web applications however allows for having ASP.NET pages and MVC controllers whose properties seem to "survive" between postbacks. Depending on the I D a t a S t o r eimplementation used, the data can be stored in the Session state, an ASP.NET user profile or somewhere else. We don't need to do anything else but decorate the desired properties with the [Trackable] attribute, and ensure

that the page or controller is built up using an IOC container with an extension for managing tracking. Using IOC for resolving Pages and Controllers can be done with a custom ControllerFactory (for MVC) or with a custom I H t t p M o d u l e (for regular ASP.NET) I've included demo apps for both flavours of ASP.NET with comments on the important parts. So let's see how we could use this library to handle counting the number of visits to a page (MVC example). a) Using Session directly
Collapse | Copy Code

b) Using this library


Collapse | Copy Code

[ H a n d l e E r r o r ] p u b l i cc l a s sH o m e C o n t r o l l e r:C o n t r o l l e r { p u b l i cA c t i o n R e s u l tI n d e x ( ) { u i n tn u m b e r O f V i s i t s=0 ; / / 1 .G e tt h ev a l u ef r o ms e s s i o n / / i fp r e s e n t :n oc o m p i l et i m ec h e c k i n g / / o ft y p eo ri d e n t i f i e r i f( S e s s i o n [ " n u m b e r O f V i s i t s " ]! =n u l l ) n u m b e r O f V i s i t s= ( u i n t ) S e s s i o n [ " n u m b e r O f V i s i t s " ] ; / / 2 .d os o m e t h i n gw i t ht h ev a l u e . . . V i e w D a t a [ " N u m b e r O f V i s i t s _ U s e r " ]= n u m b e r O f V i s i t s ; / / 3 .i n c r e m e n tt h en u m b e ro fv i s i t s n u m b e r O f V i s i t s + + ; / / 4 .s t o r ei ti nt h eS e s s i o ns t a t e S e s s i o n [ " S o m e I d e n t i f i e r " ]= n u m b e r O f V i s i t s ; } r e t u r nV i e w ( ) ;

[ H a n d l e E r r o r ] p u b l i cc l a s sH o m e C o n t r o l l e r: C o n t r o l l e r ,I T r a c k a b l e { [ T r a c k a b l e ] p u b l i cu i n tN u m b e r O f V i s i t s{g e t ; s e t ;} p u b l i cA c t i o n R e s u l tI n d e x ( ) { / / n on e e dt od oa n y t h i n g / / t of e t c ho rs a v e N u m b e r O f V i s i t s / / 1 .D os o m e t h i n gw i t ht h e v a l u e . . . V i e w D a t a [ " N u m b e r O f V i s i t s " ]= N u m b e r O f V i s i t s ; / / 2 .i n c r e m e n tt h en u m b e ro f u s e r s N u m b e r O f V i s i t s + + ; } r e t u r nV i e w ( ) ;

In this scenario, option B has several advantages: simplicity (just apply the T r a c k a b l e attribute to desired properties) name safety (no need to worry about naming the data in the session store when saving/retrieving) type safety (no need to cast when retrieving data from the session store)

8.1. Configuring tracking in ASP.NET WebForms


In order to enable this behavior in ASP.NET (WebForms), I have created a custom I H t t p M o d u l eso I can do stuff before and after a page is processed. The module takes an I U n i t y C o n t a i n e r reference in it's constructor and does the following: 1. Adds the tracking extension to the IOC container (so every object the container creates or injects into gets tracked if it implements the I T r a c k a b l e marker interface) 2. Before the HttpHandler (the ASP.NET page) starts executing, it uses the IOC container to inject it with dependencies (and applies tracking to it and any other objects that are created in the process) 3. After the handler (the ASP.NET Page) is done processing, it calls P e r s i s t A u t o m a t i c T a r g e t son all S e t t i n g s T r a c k e r s that are registered in the container. This is the code for the http module:
Collapse | Copy Code

n a m e s p a c eT r a c k i n g . U n i t y . A S P N E T { p u b l i cc l a s sT r a c k i n g M o d u l e:I H t t p M o d u l e { I U n i t y C o n t a i n e r_ c o n t a i n e r ; p u b l i cT r a c k i n g M o d u l e ( I U n i t y C o n t a i n e rc o n t a i n e r ) { _ c o n t a i n e r=c o n t a i n e r ; _ c o n t a i n e r . A d d E x t e n s i o n ( n e wT r a c k i n g E x t e n s i o n ( ) ) ; } p u b l i cv o i dD i s p o s e ( ) { } p u b l i cv o i dI n i t ( H t t p A p p l i c a t i o nc o n t e x t ) { c o n t e x t . P r e R e q u e s t H a n d l e r E x e c u t e+ = n e wE v e n t H a n d l e r ( c o n t e x t _ P r e R e q u e s t H a n d l e r E x e c u t e ) ; c o n t e x t . P o s t R e q u e s t H a n d l e r E x e c u t e+ = n e wE v e n t H a n d l e r ( c o n t e x t _ P o s t R e q u e s t H a n d l e r E x e c u t e ) ; } v o i dc o n t e x t _ P r e R e q u e s t H a n d l e r E x e c u t e ( o b j e c ts e n d e r ,E v e n t A r g se ) { i f( H t t p C o n t e x t . C u r r e n t . H a n d l e ri sI R e q u i r e s S e s s i o n S t a t e| | H t t p C o n t e x t . C u r r e n t . H a n d l e ri sI R e a d O n l y S e s s i o n S t a t e ) { o b j e c tp a g e=H t t p C o n t e x t . C u r r e n t . H a n d l e r ; _ c o n t a i n e r . B u i l d U p ( p a g e . G e t T y p e ( ) ,p a g e ) ; }

} v o i dc o n t e x t _ P o s t R e q u e s t H a n d l e r E x e c u t e ( o b j e c ts e n d e r ,E v e n t A r g se ) { i f( H t t p C o n t e x t . C u r r e n t . H a n d l e ri sI R e q u i r e s S e s s i o n S t a t e| | H t t p C o n t e x t . C u r r e n t . H a n d l e ri sI R e a d O n l y S e s s i o n S t a t e ) { / / n a m e dt r a c k e r s f o r e a c h( S e t t i n g s T r a c k e rt r a c k e ri n_ c o n t a i n e r . R e s o l v e A l l < S e t t i n g s T r a c k e r > t r a c k e r . P e r s i s t A u t o m a t i c T a r g e t s ( ) ; / / u n n a m e dt r a c k e r i f ( _ c o n t a i n e r . I s R e g i s t e r e d < S e t t i n g s T r a c k e r > ( ) ) _ c o n t a i n e r . R e s o l v e < S e t t i n g s T r a c k e r > ( ) . P e r s i s t A u t o m a t i c T a r g e t s ( ) ;

( ) )

Since the module needs a reference to the I U n i t y C o n t a i n e r in it's constructor, it needs to be created in code (instead of in app.config). This must be done in the I n i t ( ) method in the global.asax file, like so:
Collapse | Copy Code

n a m e s p a c eW e b A p p l i c a t i o n 1 { p u b l i cc l a s sG l o b a l:S y s t e m . W e b . H t t p A p p l i c a t i o n { p u b l i cs t a t i cU n i t y C o n t a i n e r_ u c=n e wU n i t y C o n t a i n e r ( ) ; s t a t i cI H t t p M o d u l et r a c k i n g M o d u l e=n e wT r a c k i n g M o d u l e ( _ u c ) ; p u b l i co v e r r i d ev o i dI n i t ( ) { b a s e . I n i t ( ) ; / / R e g i s t e rs e r v i c e si nt h eI O Cc o n t a i n e r / / . . . / / r e g i s t e ra p p r o p r i a t eS e t t i n g s T r a c k e r s / / iu s eaf a c t o r ym e t h o di n s t e a do fas i n g l e / /i n s t a n c es oe a c hs e s s i o nc a nh a v ei t ' so w ni n s t a n c e / / s ot h e yd o n ' ti n t e r f e r ew i t he a c ho t h e r _ u c . R e g i s t e r T y p e < S e t t i n g s T r a c k e r > ( n e wS e s s i o n L i f e t i m e M a n a g e r ( ) , n e wI n j e c t i o n F a c t o r y ( c= >n e wS e t t i n g s T r a c k e r ( n e wO b j e c t S t o r e ( n e wP r o f i l e S t o r e ( " T r a c k i n g D a t a " ) , n e wB i n a r y S e r i a l i z e r ( ) ){C a c h e O b j e c t s=f a l s e} ) ) ) ; / / i n i t i a l i z et h et r a c k i n gm o d u l e t r a c k i n g M o d u l e . I n i t ( t h i s ) ;

8.2. Configuring tracking in ASP.NET MVC


In MVC, the controllers are not handlers, so the HttpModule approach is not applicable. Instead, the dependency injection and tracking can be set up using a custom controller factory. I've included one in the library and here is what it looks like:
Collapse | Copy Code

n a m e s p a c eT r a c k i n g . U n i t y . A S P N E T { p u b l i cc l a s sT r a c k i n g C o n t r o l l e r F a c t o r y:D e f a u l t C o n t r o l l e r F a c t o r y { I U n i t y C o n t a i n e r_ c o n t a i n e r ; p u b l i cT r a c k i n g C o n t r o l l e r F a c t o r y ( I U n i t y C o n t a i n e rc o n t a i n e r ) { _ c o n t a i n e r=c o n t a i n e r ; _ c o n t a i n e r . A d d E x t e n s i o n ( n e wT r a c k i n g E x t e n s i o n ( ) ) ; H t t p C o n t e x t . C u r r e n t . A p p l i c a t i o n I n s t a n c e . P o s t R e q u e s t H a n d l e r E x e c u t e+ = n e wE v e n t H a n d l e r ( A p p l i c a t i o n I n s t a n c e _ P o s t R e q u e s t H a n d l e r E x e c u t e ) ;

( ) )

v o i dA p p l i c a t i o n I n s t a n c e _ P o s t R e q u e s t H a n d l e r E x e c u t e ( o b j e c ts e n d e r ,E v e n t A r g se ) { / / n a m e dt r a c k e r s f o r e a c h( S e t t i n g s T r a c k e rt r a c k e ri n_ c o n t a i n e r . R e s o l v e A l l < S e t t i n g s T r a c k e r > t r a c k e r . P e r s i s t A u t o m a t i c T a r g e t s ( ) ; / / u n n a m e dt r a c k e r i f( _ c o n t a i n e r . I s R e g i s t e r e d < S e t t i n g s T r a c k e r > ( ) ) _ c o n t a i n e r . R e s o l v e < S e t t i n g s T r a c k e r > ( ) . P e r s i s t A u t o m a t i c T a r g e t s ( ) ;

# r e g i o nI C o n t r o l l e r F a c t o r yM e m b e r s p u b l i co v e r r i d eI C o n t r o l l e rC r e a t e C o n t r o l l e r ( S y s t e m . W e b . R o u t i n g . R e q u e s t C o n t e x tr e q u e s t C o n t e x t ,s t r i n gc o n t r o l l e r N a m e ) { I C o n t r o l l e rc o n t r o l l e r=b a s e . C r e a t e C o n t r o l l e r ( r e q u e s t C o n t e x t ,c o n t r o l l e r N a m e ) ; _ c o n t a i n e r . B u i l d U p ( c o n t r o l l e r ) ; r e t u r nc o n t r o l l e r ; } # e n d r e g i o n

The controller factory also needs to be set up in the I n i t ( ) method in global.asax, because it is subscribing to the P o s t R e q u e s t H a n d l e r E x e c u t eevent which can only work during Init. The global.asax file might look like this:
Collapse | Copy Code

n a m e s p a c eM v c A p p l i c a t i o n 1 { / /N o t e :F o ri n s t r u c t i o n so ne n a b l i n gI I S 6o rI I S 7c l a s s i cm o d e , / /v i s i th t t p : / / g o . m i c r o s o f t . c o m / ? L i n k I d = 9 3 9 4 8 0 1 p u b l i cc l a s sM v c A p p l i c a t i o n:S y s t e m . W e b . H t t p A p p l i c a t i o n { s t a t i cU n i t y C o n t a i n e r_ u c=n e wU n i t y C o n t a i n e r ( ) ; p u b l i co v e r r i d ev o i dI n i t ( ) { b a s e . I n i t ( ) ; / / r e g i s t e ra p p r o p r i a t eS e t t i n g s T r a c k e r s _ u c . R e g i s t e r T y p e < S e t t i n g s T r a c k e r > ( " U S E R " , n e wR e q u e s t L i f e t i m e M a n a g e r ( ) ,n e wI n j e c t i o n F a c t o r y ( c o n t a i n e r= > n e wS e t t i n g s T r a c k e r ( n e wO b j e c t S t o r e ( n e wP r o f i l e S t o r e ( " T r a c k i n g D a t a " ) , n e wJ s o n S e r i a l i z e r ( ) ) ){N a m e=" U S E R "} ) ) ; _ u c . R e g i s t e r T y p e < S e t t i n g s T r a c k e r > ( " S E S S I O N " , n e wS e s s i o n L i f e t i m e M a n a g e r ( ) ,n e wI n j e c t i o n F a c t o r y ( c o n t a i n e r= > n e wS e t t i n g s T r a c k e r ( n e wO b j e c t S t o r e ( n e wS e s s i o n S t o r e ( ) , n e wJ s o n S e r i a l i z e r ( ) ) ){N a m e=" S E S S I O N "} ) ) ; / / I M P O R T A N T :u s et h eT r a c k i n g C o n t r o l l e r F a c t o r yt oc r e a t ec o n t r o l l e r s / / s ow ec a ni n j e c td e p e n d e n c i e si n t ot h e ma n da p p l yt r a c k i n g C o n t r o l l e r B u i l d e r . C u r r e n t . S e t C o n t r o l l e r F a c t o r y ( n e w T r a c k i n g C o n t r o l l e r F a c t o r y ( _ u c ) ) ; } p u b l i cs t a t i cv o i dR e g i s t e r R o u t e s ( R o u t e C o l l e c t i o nr o u t e s ) { r o u t e s . I g n o r e R o u t e ( " { r e s o u r c e } . a x d / { * p a t h I n f o } " ) ; r o u t e s . M a p R o u t e ( " D e f a u l t " ,/ /R o u t en a m e " { c o n t r o l l e r } / { a c t i o n } / { i d } " ,/ /U R Lw i t hp a r a m e t e r s n e w{c o n t r o l l e r=" H o m e " ,a c t i o n=" I n d e x " , i d=U r l P a r a m e t e r . O p t i o n a l}/ /P a r a m e t e rd e f a u l t s ) ; } p r o t e c t e dv o i dA p p l i c a t i o n _ S t a r t ( ) { A r e a R e g i s t r a t i o n . R e g i s t e r A l l A r e a s ( ) ; R e g i s t e r R o u t e s ( R o u t e T a b l e . R o u t e s ) ; }

Now all we need to do is decorate properties we want to be persisted with [ T r a c k a b l e ] and implement the I T r a c k a b l e interface in the controllers. You may have noticed I have registered more than one settings tracker here. The brings me to the last point of interest...

9. Multiple trackers (named trackers)


There are times when you need to store some data e.g. at the user level, and other data at the machine level, or perhaps at the session level. In this case, we can create multiple trackers, give each one a name, register them in the IOC container by name, and specify the tracker name in the T r a c k a b l e attribute. An example is shown in the MVC demo, but here is what specifying the tracker name looks like:
Collapse | Copy Code

[ T r a c k a b l e ( T r a c k e r N a m e=" U S E R " ) ] p u b l i cu i n tN u m b e r O f V i s i t s _ U s e r{g e t ;s e t ;} [ T r a c k a b l e ( T r a c k e r N a m e=" S E S S I O N " ) ] p u b l i cu i n tN u m b e r O f V i s i t s _ S e s s i o n{g e t ;s e t ;}

I've included an example of using multiple trackers in the ASP.NET WebForms demo application where some properties are tracked on a per-user level, and some are tracked on a per-session level.

10. The demo apps


In the desktop demo apps I have used the tracking library for persisting UI state, as well as persisting application settings (without using the standard .NET configuration API). Note that I had no problem implementing I N o t i f y P r o p e r t y C h a n g e d in one of my settings classes. If my application was pluginenabled I would also not have any problems allowing the plugins to have settings of their own. In the demo, there is one app that uses Unity IOC Container, and one that doesn't. I have also included a ASP.NET WebForms application, and a MVC application with an example of using multiple trackers. The apps use ASP.NET user profiles with a aspnetdb.mdf file for storing user data.

Depending on the SQL server you have installed, you may need to adjust the connection string in the web.config for the demos to work.

11. Conclusion
The work of saving settings and applying them to concerned objects involves a lot of copying data back and forth, and can be quite monotonous and error prone. In this article I aimed to present a more declarative approach in which you only specify what needs to be persisted and when, and have the copying (the "how") taken care of automatically. This approach results in a lot less effort, code, and repetition.

12. Nuget
The package is available on NuGet, the Id of the project is "Deva.Tracking".

TODO
Optimization: cache tracking metadata for types Host source (SourceForge/Github... )

History
Update 2013-06-18: WinForms example added. Update 2013-06-12: usage in web apps, JSON serialization, multiple (named) trackers.

License
This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author


Antonio Naki Alfirevi
Software Developer (Senior) Recro-Net Croatia

I have been an a(tra)ctive software developer since 2005 mostly working on .NET. Currently living and working in Zagreb Croatia. I have earned my masters degree in Computer Science at the Faculty of Electrical Engineering and Computer Science in Zagreb in 2006. Article Top

Comments and Discussions


Add a Comment or Question
Profile popups Spacing Relaxed Noise Medium Search this forum Layout Normal Per page 25 Go Update

First Prev Next

An excellent work done..

Anupam Singh_

19-Apr-14 14:49

Custom Configuration Section My vote of 5 My vote of 5 small suggestion to use NoSQL Database Re: small suggestion to use NoSQL Database [modified] My vote of 5 My vote of 5 Sugestion My vote of 5 My vote of 5 Clarifcation on the built-in tool Re: Clarifcation on the built-in tool Re: Clarifcation on the built-in tool I still prefer a simpler solution by Jani My vote of 5 too many dependencies Re: too many dependencies nuget Re: nuget My vote of 5 My vote of 5 My vote of 5 My vote of 5 IOC example with Caliburn.Micro
Last Visit: 1-Jan-00 6:00 General News Last Update: 23-Apr-14 10:49 Suggestion Question Bug

melnac Martin Solovey okdone SakarSR Antonio Naki Alfirevi

18-Apr-14 9:31 18-Apr-14 1:15 23-Nov-13 17:26 29-Oct-13 5:00 29-Oct-13 11:15

M Rayhan holylust MiguelCouto CodeJarry deepu9890 jfos Antonio Naki Alfirevi jfos Darek Danielewski SagarRS benny856694 Antonio Naki Alfirevi benny856694 Antonio Naki Alfirevi benny856694 Renju Vinod Volynsky Alex Andy Eskridge Stiaan van der Westhuizen Refresh Answer Joke Rant

24-Oct-13 15:01 2-Aug-13 11:08 26-Jun-13 17:59 19-Jun-13 11:42 19-Jun-13 11:12 18-Jun-13 19:59 19-Jun-13 9:17 19-Jun-13 17:26 18-Jun-13 18:07 18-Jun-13 14:44 17-Jun-13 16:26 17-Jun-13 17:56 17-Jun-13 16:21 17-Jun-13 17:50 17-Jun-13 15:49 14-Jun-13 9:20 12-Jun-13 18:45 12-Jun-13 18:39 28-May-13 14:20

1 2 3 Next Admin

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.
Permalink | Advertise | Privacy | Mobile Web03 | 2.8.140421.2 | Last Updated 17 Apr 2014 Layout: fixed | fluid Article Copyright 2012 by Antonio Naki Alfirevi Everything else Copyright CodeProject, 1999-2014 Terms of Use

También podría gustarte