Jun302010

BUG: External configuration source file in Enterprise Library 5

Published by stephane at 3:58 PM under Patterns & Partices | .NET Framework

We just started to use Enterprise Library 5 in a project here. One thing that we almost always do is takeout the EntLib configuration out of the App.config/Web.config file. I already did that several times in the past using other version of Enterprise Library with no problem.

 

What was my surprise today when I tried the same thing I always did with previous version and found that it wasn’t working anymore!

 

This is the configuration I have in my configuration editor:

Configuration Sources

 

I am configuring a Web Application Project and I'm expecting EntLib to find my entlib.config at the root of the website… Wrong!

 

The problem reside in the fact that the file path resolution logic go through a new method in v5: GetRootedCurrentConfigurationFile, and the current implementation do not handle non rooted path correctly. The problem is that the method try to check the file existence before fixing it when dealing with a non rooted path. Here is the code directly from FileConfigurationSource.cs

 

private static string GetRootedCurrentConfigurationFile(string configurationFile)
{
    if (string.IsNullOrEmpty(configurationFile))
        throw new ArgumentException(Resources.ExceptionStringNullOrEmpty, "configurationFile");

    if (!File.Exists(configurationFile))
    {
        throw new FileNotFoundException(
            string.Format(
                CultureInfo.CurrentCulture,
                Resources.ExceptionConfigurationLoadFileNotFound,
                configurationFile));
    }

    return
        Path.IsPathRooted(configurationFile)
            ? configurationFile
            : Path.Combine(AppDomain.CurrentDomain.BaseDirectory, configurationFile);
}

 

A proposed fix is already available at CodePlex but it implies to modify the source of EntLib yourself and live with a home, recompiled version of EntLib. That directly means supporting another dependency which is something I don’t want.

Here is the link to the CodePlex page:
FileConfigurationSource::GetRootedCurrentConfigurationFile throws FileNotFoundException on relative path configuration file name

 

For us, it means that we can’t use Enterprise Library Configuration Sources until this bug is fixed. But… there’s a solution to keep the benefits of having the block configuration in external files. The solution resides in .Net and have been around for a while now: configSource! What we did is use the configSource attribute to point to external files for the desired Enterprise Library sections.

 

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <configSections>
        <section name="loggingConfiguration" type="Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.LoggingSettings, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" requirePermission="true" />
        <section name="exceptionHandling" type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Configuration.ExceptionHandlingSettings, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" requirePermission="true" />
	</configSections>
	<loggingConfiguration configSource="configs\entlib-logging.config" />
	<exceptionHandling configSource="configs\entlib-exception.config" />
</configuration>

 

This way we can keep the advantage of having our configuration outside the App.config/Web.config until this bug is fixed.

 

Note: One thing to keep in mind though… You cannot tell multiple configSource to use the same file. This is why I have entlib-logging.config and entlib-exception.config declared in my web.config.



[KickIt] [Dzone] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

Tags: , ,

E-mail | Permalink | Trackback | Post RSSRSS comment feed 2 Responses

May282010

Basket & Purchase errors bug in Commerce Server 2009

Published by david at 11:29 AM under Commerce Server | SharePoint 2007

While doing an implementation of Commerce Server 2209 and the extensibility kit for a client of mine, we noticed that during a checkout, no error messages are being returned by the pipelines.

Let me explain:

Let say you write a pipeline component that looks like this:

[ComVisible(true)]
    [GuidAttribute ("D452305E-50C9-4031-BC94-6839BE6066EE")]
    public class MyPipelineComponentClass : IPipelineComponent
    {
        // Status codes for pipeline components
        private const Int32 StatusSuccess = 1; // success
        private const Int32 StatusWarning = 2; // warning
        private const Int32 StatusError = 3; // error

        #region IPipelineComponent Members
        void IPipelineComponent.EnableDesign(int fEnable){}

        int IPipelineComponent.Execute(object pdispOrder, object pdispContext, int lFlags)
        {
            Int32 ReturnValue = StatusWarning;
            // TODO: add code for the pipeline
			
			IDictionary Order = (IDictionary)pdispOrder;			

			string ErrorMessage = "Something is wrong"
			((ISimpleList)Order["_Basket_Errors").Add(ref ErrorMessage);
            return ReturnValue;
        }
        #endregion
    }

 

Very simple and to the point. Always return an error.

Now, since the return status is a warning, in the Order Review Page in the extensibility kit, you’ll always have a null Order. Your basket, however, will not have those error messages in its PurchaseErrors and BasketErrors properties.

Clearly, this is a bug (Which I submitted in Connect and waiting for an official fix)

But, there is a relatively simple solution. You can workaroud this issue by creating a simple operation sequence to copy those messages back to the order form like so:

 

 public override void ExecuteUpdate(Microsoft.Commerce.Contracts.Messages.CommerceUpdateOperation updateOperation,
            Microsoft.Commerce.Broker.OperationCacheDictionary operationCache,
            Microsoft.Commerce.Contracts.Messages.CommerceUpdateOperationResponse response)
    {

        OrderGroup cachedCommerceServerOrderGroup = operationCache.GetCachedCommerceServerOrderGroup();
        OrderForm defaultOrderForm = cachedCommerceServerOrderGroup.GetDefaultOrderForm();
        ISimpleList BasketErrorList = defaultOrderForm["_Basket_Errors"] as ISimpleList;
        ISimpleList PurchaseErrorList = defaultOrderForm["_Purchase_Errors"] as ISimpleList;

        if (((BasketErrorList != null) && (BasketErrorList.Count != 0)))
        {
            if (BasketErrorList.Count > 0)
            {
                List<string> basketerrorstringList = new List<string>();

                foreach (string error in BasketErrorList)
                {
                    basketerrorstringList.Add(error);
                }

                foreach (var entity in response.CommerceEntities)
                {
                    if (entity.ModelName == "Basket")
                    {
                        if (!entity.Properties.ContainsProperty("BasketErrors"))
                        {
                            entity.Properties.Add("BasketErrors", basketerrorstringList.ToArray());
                        }
                        else
                        {
                            entity.Properties["BasketErrors"] = basketerrorstringList.ToArray();
                        }
                    }
                }
            }
        }

        if (((PurchaseErrorList != null) && (PurchaseErrorList.Count != 0)))
        {
            if (PurchaseErrorList.Count > 0)
            {
                List<string> basketerrorstringList = new List<string>();

                foreach (string error in PurchaseErrorList)
                {
                    basketerrorstringList.Add(error);
                }

                foreach (var entity in response.CommerceEntities)
                {
                    if (entity.ModelName == "Basket")
                    {
                        if (!entity.Properties.ContainsProperty("PurchaseErrors"))
                        {
                            entity.Properties.Add("PurchaseErrors", basketerrorstringList.ToArray());
                        }
                        else
                        {
                            entity.Properties["PurchaseErrors"] = basketerrorstringList.ToArray();
                        }
                    }
                }
            }
        }
    }



    public override void ExecuteQuery(CommerceQueryOperation queryOperation, 
            Microsoft.Commerce.Broker.OperationCacheDictionary operationCache,
            CommerceQueryOperationResponse response)
    {



    }

 

Now, add your component just before the basket committer operation in your Channelconfiguration.config file int Basket Update Message section.

Voila, your message are now available.



[KickIt] [Dzone] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

Tags:

E-mail | Permalink | Trackback | Post RSSRSS comment feed 2 Responses

Mar232010

Improving performance of DataBinding, patience is gold

Published by eric at 7:33 AM under C#

I came across a performance problem lately. I have a WPF Smart Client application connected to a Dynamics CRM back end. At some point in my application I have add a lot of lines in a data table held by an ObservableCollection class. The problem was each time I add a line into the collection a CollectionChanged event was raised and that triggered the update of some other fields, approximately 10 in my case.

So when was doing batch update the screen freezes for way too much time. I found out the problem was the cascading effect of recalculating all the dependent fields every time I add a ne line in the collection, thanks to the new Visual Studio 2010 performance analysis tools.

In this scenario, I’m only interested to get the totals right when I do the last insert, all other intermediate values are useless. I didn’t wan to play with the data binding itself. I could have unbound the total calculation and have it rebound at the end but I didn’t. Instead I chose to delay the calculation with some kind of sliding expiration timer. Such a timer, set with a timeout of 10 ms, would be reset every time a new add would be made, so if I add a bunch of line in batch odds that the delay between two line add be less than 10 ms is good. In fact it is so good that because it’s almost always true the calculation happens only once at the end.

So there is this magic DelayRun class with its DelayController:

using System;
using System.Collections.Generic;
using System.Threading;

namespace Infrastructure
{
    public interface IDelayRun
    {
        void Reset();
        void Start(int ms);
        event EventHandler<DelayRunEventArgs> EventCompleted;
    }

    public class DelayRun<T> : IDisposable, IDelayRun
    {
        private static readonly ManualResetEvent _resetEvent = new ManualResetEvent(true);
        private readonly Action<T> _action;
        private readonly T _target;
        private int _delay;
        private volatile Timer _timer;

        private DelayRun(T target, Action<T> action)
        {
            _action = action;
            _target = target;
        }

        #region IDelayRun Members

        public void Start(int ms)
        {
            if (ms == 0)
                throw new ArgumentOutOfRangeException("ms", ms, "ms should be grater than 0.");

            if (_delay != 0)
                throw new InvalidOperationException("Can't start, already started.");

            _delay = ms;
            _timer = new Timer(Execute, this, _delay, Timeout.Infinite);
        }

        public void Reset()
        {
            lock (_timer)
            {
                if (_timer == null)
                    throw new InvalidOperationException("Can't reset the timer, already completed.");

                _timer.Change(_delay, Timeout.Infinite);
            }
        }

        #endregion

        public static DelayRun<T> CreateNew(T target, Action<T> action)
        {
            return new DelayRun<T>(target, action);
        }

        public static DelayRun<T> StartNew(T target, Action<T> action, int ms)
        {
            DelayRun<T> delayRun = CreateNew(target, action);
            delayRun.Start(ms);
            return delayRun;
        }

        public void Execute(object state)
        {
            _resetEvent.WaitOne();
            lock (_timer)
            {
                _timer.Change(Timeout.Infinite, Timeout.Infinite);
            }

            _action.Invoke(_target);
            _resetEvent.Set();
        }

        #region Event Handling

        public event EventHandler<DelayRunEventArgs> EventCompleted;

        public void InvokeEventCompleted(DelayRunEventArgs e)
        {
            EventHandler<DelayRunEventArgs> handler = EventCompleted;
            if (handler != null) handler(this, e);
        }

        #endregion

        #region IDisposable

        public void Dispose()
        {
            lock (_timer)
            {
                if (_timer != null)
                    _timer.Dispose();
            }
        }

        #endregion
    }

    public class DelayRunEventArgs : EventArgs
    {
        public DelayRunEventArgs(IDelayRun delayAction)
        {
            DelayAction = delayAction;
        }

        public IDelayRun DelayAction { get; set; }
    }

    public static class DelayController
    {
        private static readonly IDictionary<string, IDelayRun> _actions = new Dictionary<string, IDelayRun>();

        public static void Add<T>(string key, T target, Action<T> action, int ms)
        {
            if (_actions.ContainsKey(key))
                _actions[key].Reset();
            else
            {
                DelayRun<T> delayRun = DelayRun<T>.CreateNew(target, _ => {});
                _actions[key] = delayRun;
                string localKey = key;
                delayRun.EventCompleted += (sender, args) =>
                {
                    _actions.Remove(localKey);
                    action.BeginInvoke(target, ar => ar.AsyncWaitHandle.WaitOne(), delayRun);
                };
                delayRun.Start(ms);
            }
        }
    }
}

To use this class all you need to do is call the DelayController. The DelayController will handle the creation and the reset process of the DelayRun class. It can handle many delay class at once if they have different key value. Each time the controller Add method is called it either create a new instance of a DelayRun Class of reset the running one.

Here is how you can call it:

DelayController.Add("ValuesOnCollectionChanged", this, m => Recalc(m), 10);

This call will delay the Recalc method call for 10 ms. In the mean time if your program add other values to recalc you can recall this line and the delay will be reset for another 10 ms and so on.

Tell me if this can be helpful in your own projects.



[KickIt] [Dzone] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

Tags: , , ,

E-mail | Permalink | Trackback | Post RSSRSS comment feed 0 Responses

Mar182010

How to use strongly-typed name with INotifyPropertyChanged

Published by eric at 8:20 AM under .NET Framework | C# | Windows | WPF

You may already have read my posts about how to use INotifyPropertyChanged in a type-safe way (here and here), but sometime you don’t want to modify all your classes to use this method. All you want is avoid the use of a magic string to define the property. Your code will be refactoring proof.

For example let say you have this property:

public string FirstName
{
    get { return _firstName; }
    set 
    {
        if (_firstName == value)
            return;
        _firstName = value;
        RaisePropertyChanged("FirstName");
    }
}

Some refactoring tool, like Resharper, will be able to change the “FirstName” string but not Visual Studio itself. The solution? Replace this string with a strong type value. How? Let’s assume we can do this:

public string FirstName
{
    get { return _firstName; }
    set 
    {
        if (_firstName == value)
            return;
        _firstName = value;
        RaisePropertyChanged(this.NameOf(p => p.FirstName));
    }
}

Notice that you must specify the “this” keyword to make it work. That is exactly What this extension method let you do:

public static class ObjectExtensions
{
    public static string NameOf<T>(this T target, Expression<Func<T, object>> propertyExpression)
    {
        MemberExpression body = null;
        if (propertyExpression.Body is UnaryExpression)
        {
            var unary = propertyExpression.Body as UnaryExpression;
            if (unary.Operand is MemberExpression)
                body = unary.Operand as MemberExpression;
        }
        else if (propertyExpression.Body is MemberExpression)
        {
            body = propertyExpression.Body as MemberExpression;
        }
        if (body == null)
            throw new ArgumentException("'propertyExpression' should be a member expression");

        // Extract the right part (after "=>")
        var vmExpression = body.Expression as ConstantExpression;

        // Extract the name of the property to raise a change on
        return body.Member.Name;
    }
}

You can event use it in your property changed handler:

private void OnPropertyChanged(object sender, PropertyChangedEventArgs args)
{
    if (args.PropertyName == this.NameOf(p => p.FirstName))
    {
        // ...
    }
}

Enjoy!



[KickIt] [Dzone] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

Tags: , , ,

E-mail | Permalink | Trackback | Post RSSRSS comment feed 0 Responses

Mar022010

The cryptic CSApp.ini

Published by david at 5:39 PM under Commerce Server

Before ASP.Net membership providers, Commerce Server used ISAPI based authentication in conjunction with CommerceAuthentication HTTP module for authentication. This, of course, was rendered obsolete with the introduction of Membership providers and subsequently the UPMProvider. But, nonetheless, this little cryptic file (CSApp.ini) was left each and every time you un=package a Commerce Solution. If you dare delete it, you get a nasty error at your application start-up.

So, what’s the deal? Well, it stands in the fact, that the standard web.config file for Commerce Server 2009 still includes the HTTP Module. So, in order to get rid of the CSApp.ini file, you need to remove the CommerceAuthenticationModule from the HttpModules section in your web.config.

And there you go. Your application is not bound to your CSApp.ini file!



[KickIt] [Dzone] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

Tags:

E-mail | Permalink | Trackback | Post RSSRSS comment feed 0 Responses

Jan272010

You cannot target advertisements or discounts to specified page groups by using the Discount Ad Web Part in Commerce Server 2009

Published by david at 4:32 PM under Commerce Server | SharePoint 2007

While working on a Commerce Server 2009 solution, we were trying to create targeted advertisement only on the home page. We noticed that the Page group did not change anything. As it turns out, Microsoft has a fix for that. Here is the link:

http://support.microsoft.com/kb/968758



[KickIt] [Dzone] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

Tags:

E-mail | Permalink | Trackback | Post RSSRSS comment feed 1 Responses

Nov172009

SharePoint, Execution Context & Security

Published by Nicolas at 10:01 AM under C# | SharePoint 2007

At least once in his career, a SharePoint developer is going to be faced with a security problem.

When a user executes an action in SharePoint, i.e. modifies an item in a list, the request on the server executes using the security context of the user. So, if the user does not have the right privileges to accomplish specific actions, the request could fail with a security exception.

A way to overcome this behaviour is to use the RunWithElevatedPrivileges method from the SPUtility class.

An important thing to keep in mind when using this method is the initial context of the object on which you will execute specific actions.

In the example below, even if you use the RunWithElevatedPrivileges method, the security context of the SPWeb object (web variable) is inherited from the user permissions.
This behaviour is normal because the SPSite site variable is a reference to the properties variable which was created with the user’s security context.

public override void ItemUpdated(SPItemEventProperties properties)
{
	SPSecurity.RunWithElevatedPrivileges(delegate(){
		using (SPSite site = properties.ListItem.Web.Site)
		{
			using (SPWeb web = site.RootWeb)
			{
				web.AllowUnsafeUpdates = true;
                                // Do some actions
			}
		}
	});
}

To run in a context with full control, all objects should be instantiated inside the RunWithElevatedPrivileges delegate method.
By doing this, objects will inherit their permission from the RunWithElevatedPrivileges context which is full control:

public override void ItemUpdated(SPItemEventProperties properties)
{
	SPSecurity.RunWithElevatedPrivileges(delegate(){
		using (SPSite site = new SPSite(properties.ListItem.Web.Site.ID))
		{
			using (SPWeb web = site.RootWeb)
			{
				web.AllowUnsafeUpdates = true;
                                // Do some actions
			}
		}
	});
}


[KickIt] [Dzone] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

Tags: , ,

E-mail | Permalink | Trackback | Post RSSRSS comment feed 1 Responses

Nov042009

Firefox Windows Authentication

Published by mathieu.villeneuve at 11:17 AM under

To enable Windows Authentication in Firefox, to the following:

1. Open Firefox
2. Navigate to about:config
3. Edit the values of the following keys with the hostnames you want to enable.

- network.automatic-ntlm-auth.trusted-uris
- network.negotiate-auth.delegation-uris
- network.negotaite-auth.trusted-uris

Source: http://codebetter.com/blogs/eric.wise/archive/2006/11/16/Note-to-self_3A00_-Firefox-Windows-Authentication.aspx



[KickIt] [Dzone] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

Tags:

E-mail | Permalink | Trackback | Post RSSRSS comment feed 1 Responses

Oct302009

InfoPath Web based form and Windows SharePoint Services SP1 error

Published by Nicolas at 9:38 AM under SharePoint 2007 | InfoPath

Yesterday, I encountered a strange error when I was developping a custom InfoPath enabling some cascading dropdown menus.

Here is the exception I had:

Unexpected end of file while parsing Name has occurred. Line 1, position 708. System.Xml.XmlException: Unexpected end of file while parsing Name has occurred. Line 1, position 708. [...]

After a lot of searches on the Internet, I'd found a very helpful post on the InfoPathDev forums.

To summarise, if the server on which you're developing is running with Windows SharePoint Services SP1 (Service Pack 1), your InfoPath form must not have a useless secondary Data Connection (declared but not used at all on your form).

By the way, if you're interesting to know how to implement cascading dropdown menus in an InfoPath Web based Form, take a look at the Cascading Dropdowns in Browser Forms article from the InfoPath Team Blog).



[KickIt] [Dzone] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

Tags:

E-mail | Permalink | Trackback | Post RSSRSS comment feed 0 Responses

Oct022009

Using Decorator (or Wrapper) Design Patterns to add Validation to an object

Published by eric at 9:40 AM under .NET Framework | C#

Context

In most cases when someone write about the Decorator pattern it is usually related to UI stuff. The most common example is adding “decoration” to a control, for example a scroll bar. But in my humble opinion, this is not the most useful usage of Decorator. The purpose this pattern is:

“Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to sub classing for extending functionality”

Gang of Four

If you think about it, Validation is a responsibility and so it can be added by this pattern. Of course we can add the validation to the class itself or in its base class, but how would you reuse this validation across many unrelated objects and what if you object must derive from another base class?

Solution

Decorator The solution is the Decorator pattern. With this pattern we can add new responsibility to an object without changing its internals.

For simplicity purpose we will take a simple sample that anyone can understand but the concept shown here can apply to much more complex object.

Four our sample we will take a bank account. On this account we should be able to to money deposit and withdraw. The class diagram on the right illustrate this design.

Let’s start by defining our IAccount interface

namespace Model
{
    public interface IAccount {
        string AccountNumber { get; }
        decimal Balance { get; }
        bool Active { get; }
        void Deposit(decimal amount);
        void Withdraw(decimal amount);
        void Close();
    }
}

This simple interface will be the central abstraction of the system. The goal is to never depends on concrete class and this interface in enough to add a lot of functionality around an account.

Now here the interface implementation as an Account:

using System.Diagnostics;

namespace Model
{
    [DebuggerDisplay("Account = {_accountNumber}, Balance = {_balance}")]
    public class Account : IAccount
    {
        public string AccountNumber { get; private set; }
        public decimal Balance { get; private set; }
        public bool Active { get; set; }

        internal Account(string accountNumber, decimal balance)
        {
            AccountNumber = accountNumber;
            Balance = balance;
            Active = true;
        }

        public void Deposit(decimal amount)
        {
            if (OnBeforeDeposit())
                Balance += amount;
            OnAfterDeposit();
        }

        protected virtual bool OnBeforeDeposit()
        {
            return true;
        }

        protected virtual void OnAfterDeposit() { }

        public void Withdraw(decimal amount)
        {
            if (OnBeforeWithdraw())
                Balance -= amount;
            OnAfterWithdraw();
        }

        protected virtual bool OnBeforeWithdraw()
        {
            return true;
        }

        protected virtual void OnAfterWithdraw() { }

        public void Close()
        {
            if (OnBeforeClose())
                Active = false;
            OnAfterClose();
        }

        protected virtual bool OnBeforeClose()
        {
            return true;
        }

        protected virtual void OnAfterClose() { }
    }
}

If you expand the previous block of code you will see that the implementation of IAccount is only doing business stuff. There is no other responsibility in this class than the one that is meant for. We can clearly see “Template Method” design pattern here. All “OnSomething()” method are protected and any derided class can add implementation around the process. All “Before” method can cancel the process if the return value is false. This allow extension classes to add some specific behaviour.

But one of the main thing missing in this class is “validation”. We will use the decorator pattern to do that. A decorator class is a class thst implement all the member of an abstraction and forward all the calls to an internal instance of a real implementation that abstraction. In our case the abstraction is “IAccount” so we have to make a decorator that implement that interface.

namespace Model.Decorator
{
    public abstract class AccountDecorator : IAccount
    {
        private readonly IAccount _account;

        protected IAccount Account
        {
            get { return _account; }
        }

        protected AccountDecorator(IAccount account)
        {
            _account = account;
        }

        public string AccountNumber
        {
            get { return Account.AccountNumber; }
        }

        public decimal Balance
        {
            get { return Account.Balance; }
        }

        public bool Active
        {
            get { return Account.Active; }
        }

        public virtual void Deposit(decimal amount)
        {
            Account.Deposit(amount);
        }

        public virtual void Withdraw(decimal amount)
        {
            Account.Withdraw(amount);
        }

        public virtual void Close()
        {
            Account.Close();
        }
    }
}

As you can see the constructor of the decorator takes an instance of “IAccount”. All method forward their call to that instance. This base class simplifies the process of creating concrete decorator by allowing other decorator to implement some but not all members of the interface.

Now is the time to start implementing our validation class structure. For that purpose we will create a base validation class that will be responsible of applying the validation the IAccount instance.

using System.Collections.Generic;
using Model.Decorator;

namespace Model.Validator
{
    public abstract class AccountValidatorBase : AccountDecorator
    {
        protected abstract IEnumerable<IValidation> GetValidations(decimal amount);

        protected AccountValidatorBase(IAccount account) : base(account) {}

        protected void Validate(decimal amount)
        {
            foreach (var validation in GetValidations(amount))
                if (!validation.IsValid)
                    throw validation.Exception;
        }
    }
}

This simple base class define a “GetValidations” method that all derived class must override to add a list of validation to perform on method call.

Now to implement the “Deposit” validation we have to create this class:

using System.Collections.Generic;
using Model.Validation;

namespace Model.Validator
{
    public class AccountDepositValidator : AccountValidatorBase
    {
        public AccountDepositValidator(IAccount account) : base(account) {}

        protected override IEnumerable<IValidation> GetValidations(decimal amount)
        {
            return new List<IValidation>
            {
                new AmountGreaterThanZeroValidation(amount),
                new AmountShouldHaveOnlyTwoDecimals(amount),
                new AccountMustBeActiveValidation(Account),
                new DepositAmountNotExceedMaxValidation(Account, amount)
            };
        }

        public override void Deposit(decimal amount)
        {
            Validate(amount);
            Account.Deposit(amount);
        }
    }
}

This class is responsible for creating all validation instances. The “Deposit” method is overridden to call the base “Validate” method before doing the actual deposit.

To implement those validation we need to define the “IValidation” interface.

using System;

namespace Model.Validator
{
    public interface IValidation 
    {
        bool IsValid { get; }
        Exception Exception { get; }
    }
}

Here is a sample implementation:

using System;
using Model.Validator;

namespace Model.Validation
{
    public class AmountGreaterThanZeroValidation : IValidation
    {
        public AmountGreaterThanZeroValidation(decimal amount)
        {
            Amount = amount;
        }

        public decimal Amount { get; set; }

        public bool IsValid
        {
            get { return Amount > 0; }
        }

        public Exception Exception
        {
            get { return new ArgumentOutOfRangeException("amount", Amount, "Deposit amount should be greater than 0."); }
        }
    }
}

All other validations used in “AccountDepositValidator” can be described the same way.

The last step is to create an actual “IAccount” that will implement all this stuff. To do that we will need a Bank class. A bank is responsible of creating account. It will also be responsible of decorating it with all necessary decorators.

using System;
using System.Collections.Generic;
using Model.Logging;
using Model.Validator;

namespace Model
{
    public static class Bank
    {
        static readonly SortedDictionary<string, IAccount> _accounts = new SortedDictionary<string, IAccount>();
        private static int _accountSequence = 1;

        public static IAccount CreateAccount(decimal balance)
        {
            string accountNumber = String.Format("A{0:0000}", _accountSequence++);
            IAccount account = new Account(accountNumber, balance);
            account = new AccountDepositValidator(account);
            _accounts[account.AccountNumber] = account;
            return account;
        }
    }
}

To illustrate this process in action here is a sequence diagram:

Validation with Decorator

  1. Call Bank.CreateAccount.
  2. The Bank instantiate an Account class.
  3. The Bank create an AccountDepositValidator and wrap Account with it
  4. The Bank return an instance of IAccount.
  5. Deposit is called on IAccount which is an instance of AccountDepositValidator
  6. AccountDepositValidator call Validate
  7. AccountDepositValidator call GetValidations to retrieve the list of validation to evaluate
  8. An AmountGreaterThaZeroValidation is created
  9. IsValid returns true
  10. AccountDepositValidator call base Deposit method
  11. Balance value is updated

Next

With all this in place the only thing left to do is to implement all the other validators for all method of “IAccount”.



[KickIt] [Dzone] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

Tags: , ,

E-mail | Permalink | Trackback | Post RSSRSS comment feed 1 Responses