Tuesday, September 7, 2010

Passing "arrays" to Stored Procedures - XML is the answer !

I had come across a situation where I had my code behind in C# making a call to a Stored Procedure in Transact-SQL in a database to return a result set based upon the Id coming in as SqlParameter(Which is a pretty common thing ... right ?!)
Alright, lets make this interesting, I had come across a situation where I had my code behind in C# make a call to a Stored Procedure which would return a result set given 'n' no. of Ids. For each of these n ids, I wanted the stored procedure to take each one of them and get a result set, all the result sets are then UNIONed together and returned as one result set. I wanted the parameters to the stored procedures work something like passing an array or like MethodName(params int[] parameterName)
One of the ways which this can be done, which I found really convenient is passing in XML as parameter to the stored procedure and then using "IN" in the SELECT statement.

ALTER PROCEDURE [dbo].[GetResultsUsingIds]
@ids_xml Text
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;

DECLARE @hDoc int

exec sp_xml_preparedocument @hDoc OUTPUT, @ids_xml

BEGIN

SELECT 
*
FROM studenttable
WHERE student_id IN 
(SELECT Id FROM OPENXML(@hDoc,'/Ids/StudentId',1) WITH(Id int))

UNION ALL

SELECT 
*
FROM studenttable
WHERE section_id IN 
(SELECT Id FROM OPENXML(@hDoc,'/Ids/SectionId',1) WITH(Id int))

END

EXEC sp_xml_removedocument @hDoc

END


The input parameter for the above stored procedure may look like :

ids_xml :







Will return from student table:
all result sets with student_Id as 1 +
all result sets with student_Id as 2 +
all result sets with section_Id as 7 +
all result sets with section_Id as 8

sp_xml_preparedocument & sp_xml_removedocument - "Reads the XML text provided as input, parses the text by using the MSXML parser (Msxmlsql.dll), and provides the parsed document in a state ready for consumption. This parsed document is a tree representation of the various nodes in the XML document: elements, attributes, text, comments, and so on.

sp_xml_preparedocument returns a handle that can be used to access the newly created internal representation of the XML document. This handle is valid for the duration of the session or until the handle is invalidated by executing sp_xml_removedocument." - MSDN

OPENXML(@hDoc,'/Ids/StudentId',1) -

hDoc is the document handle of the internal representation of an XML document. The internal representation of an XML document is created by calling sp_xml_preparedocument.

'/Ids/StudentId' is the XPath pattern used to identify the nodes (in the XML document whose handle is passed in the idoc parameter) to be processed as rows.

1 indicates the mapping that should be used between the XML data and the relational rowset, and how the spill-over column should be filled. flags is an optional input parameter, and can be one of these values.
Byte Value Description
0 Defaults to attribute-centric mapping.
1 Use the attribute-centric mapping.
Can be combined with XML_ELEMENTS; in which case, attribute-centric mapping is applied first, and then element-centric mapping is applied for all columns not yet dealt with.
2 Use the element-centric mapping.Can be combined with XML_ATTRIBUTES; in which case, attribute-centric mapping is applied first, and then element-centric mapping is applied for all columns not yet dealt with.
8 Can be combined (logical OR) with XML_ATTRIBUTES or XML_ELEMENTS.In context of retrieval, this flag indicates that the consumed data should not be copied to the overflow property @mp:xmltext.

For more description of OPENXML() see :
http://msdn.microsoft.com/en-us/library/aa276847(SQL.80).aspx

Tuesday, August 10, 2010

Aspect Oriented Programming with PostSharp and Gibraltar



Aspect Oriented Programming

with PostSharp and Gibraltar



Every programmer ....correction….every GOOD programmer tries to make sure that he codes the application or a website in such a way that it never fails.

Nevertheless, sometimes it does.

No matter how much effort is put in, it's a different story when the application is out in the real world and gets hammered and tested for real. Therefore, a good programmer should always be aware of the fact that he/she might not have considered all the variables in the equation. In fact, some variables can be completely out of control of the programmer itself. For instance, a website that relies on a web service provided by a third party can only hope that the web service is up and running and continues to behave the same way as his application expects it to. But, he can take measures to make sure that his application continues running properly and provide appropriate messages to the user if the failure of the web service or any other failure occurs. One way to do that is using exception handling. In fact, at times a programmer may even rely on exceptions to kick off a different behavior of a component.

Big companies have big projects; big projects can mean heavy software which may have multiple components working convolutedly and simultaneously with different functionalities. In a mess like that, if somewhere down the chain of events a failure occurs; it is VERY difficult to find it. Not only for the programmer who developed the application, but it's worse if somebody else is taking a look at it. Finding the failure is only a small part of the process. Even more important than finding the failure is to figure out why it occurred, where it occurred and what were all the variables involved which made it occur. The programmer can make sure that his system never again falls in such a state which caused the error. The easiest way to handle this is by event logging and software tracing.

Kicking off other processes on an event happening due to some circumstances simultaneously such as updating a UI or synchronizing with some other data may be required in a lot of places in the application. This is where multithreading and data binding come in.

To make the application failure resistant, validation and custom exceptions play an important role. Improper input will definitely result in improper output and we want to avoid such input. If an improper input is given, we can throw custom exceptions specifying exactly what was wrong and why was it considered wrong.

Another important issue of any application is performance. An application or website may do what it's supposed to do, but if it doesn't meet performance requirements, it's a failure. A good programmer keeps performance in mind while coding. Performance metrics can really help him to decide his way of programming.

These are all just few examples of the aspects of application development which play an important role towards the success of the application itself and building robust and high quality software.

But programmers do realize that all these "aspects" of application development are different than the actual business rules of the application the company is trying to develop. These aspects can be translated to rules such as "if calling the web service throws exception, catch the exception and pass back <server down> message to UI" or "if any error occurs, log it" or "the parameter of this method should always be an integer between 10 and 50".

These aspects are more technical and less business oriented. They are called "cross cutting concerns" and do not fit cleanly in object-oriented programming or procedural programming. These aspects or so called cross-cutting concerns are used all across the application in different conditions and under various situations but they do the same thing repetitively. As a result, the code addressing these cross-cutting concerns is usually scattered or duplicated across various related locations, which result in a loss of modularity.

Aspect – Oriented Programming:

Aspect-Oriented programming aims to encapsulate these cross-cutting concerns into aspects to retain modularity. This allows for clean isolation and reuse of code addressing the cross-cutting concern. By basing designs on cross-cutting concerns, software engineering benefits are affected, including modularity and simplified maintenance. It isolates these secondary concerns from the program's business logic.

PostSharp:

PostSharp is an Aspect-Oriented Programming framework from SharpCrafters that kill all the supporting code.

"With PostSharp you can easily write and apply custom attributes that add new behaviors to your code-tracing, thread management, exception handling, data binding, and much more." - http://www.sharpcrafters.com/

The Problem Addressed:

PostSharp says that there are two parts of a software project:

Functional requirements:

Functional Requirements are the business rules, the code that achieves the primary objective of the program, which are typically the requirements of the project.

Technical requirements:

This is the supporting code (boilerplate) that ensures the proper execution and maintainability of the functional code. It is not part of the project requirements, but the application will be unstable and unmaintainable without it.

Because this technical code crosscuts the functional code at various points, it's not properly isolated in maintainable modules. For instance, implementing tracing, exception handling, thread synchronization or validations and most other technical requirements may be required by many methods and thus redundant code is inevitable.

The Solution:

PostSharp encapsulates these technical requirements into aspects in plain C#/VB classes and are applied to business objects and methods without invading their source code. Aspects in .NET are just plain attributes.

Benefit:

The ultimate benefit is a reduced cost of development and maintenance. The team can just decide on one standard technical specification all across the project at one time, and reuse these aspects over and over again.

Developers get to concentrate more on the business logic without worrying about other noise and messy code. The code looks cleaner. The no. of lines of code to write is greatly reduced. Using aspects, automatic implementation of design patterns is possible. For instance, in WPF and WinForms a design pattern is followed, where the objects must implement the INotifyPropertyChanged to update the UI when the property in the object is changed. It's very simple and very boring. This can be done in an aspect for you and this aspect can then be applied to multiple objects.

Architects will realize substantial long term improvements in Reliability as the application will continue to behave as expected over time since no new code is written and automatic implementation is used, Evolvability due to improved separation one part of the application can be changed safely without breaking it, Understandability as every aspect can be completely modular and easily understood to do one single thing, Reusability as all of the aspects can be written independent of the application and Maintainability as the maintenance related aspects such as tracing, performance monitoring or exception handling without invading the original source code will help to detect, diagnose and fix issues that occur after release.

Managers will realize reduced development time, simplifying maintenance and saving money.

How does it work?

PostSharp works post-compilation, and enhances the .NET assemblies just after the C#/VB compiler completes. It adds the functionality the programmer specified in their implementation of the aspect.

The result is that the code is much faster than with run-time aspect weavers.

Anyone can view the enhanced assembly using Reflector.

How to Deploy?

Use the PostSharp installer or manually edit the .csproj files in a text editor to use PostSharp from certain location. (Source Control)

Examples:


 

Before we add the attributes, we need to add TextWriterTraceListeners to System.Diagnostics.Trace.Listeners and then we write to those listeners in the aspects:


//Log files will be written to C:\Program Files (x86)\Common Files\microsoft shared\DevServer\10.0,
// because that is where ASP.Net helpfully defaults the write directory to :-)
//Trace Logger
         var traceLoggerFs = new FileStream("TraceLogger.txt", FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite);
         var traceLoggerSw = new StreamWriter(traceLoggerFs, System.Text.Encoding.Unicode);
         //Trace Logger Listener
         System.Diagnostics.Trace.Listeners.Add(new System.Diagnostics.TextWriterTraceListener(traceLoggerSw, "TracingListener"));

         //Exception Logger
         var exceptionLoggerFs = new FileStream("ExceptionLogger.txt", FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite);
         var exceptionLoggerSw = new StreamWriter(exceptionLoggerFs);
         //Exception Logger Listener
         System.Diagnostics.Trace.Listeners.Add(new System.Diagnostics.TextWriterTraceListener(exceptionLoggerSw, "ExceptionListener"));

         //Performance Logger
         var performanceLoggerFs = new FileStream("PerformanceLogger.txt", FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite);
         var performanceLoggerSw = new StreamWriter(performanceLoggerFs);
         //Performance Logger Listener
         System.Diagnostics.Trace.Listeners.Add(new System.Diagnostics.TextWriterTraceListener(performanceLoggerSw, "PerformanceAnalyzerListener"));

         //Field Value Logger
         var fieldValueLoggerFs = new FileStream("FieldValueLogger.txt", FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite);
         var fieldValueLoggerSw = new StreamWriter(fieldValueLoggerFs);
         //Field Value Tracker Logger Listener
         System.Diagnostics.Trace.Listeners.Add(new System.Diagnostics.TextWriterTraceListener(fieldValueLoggerSw, "FieldValueListener"));

Following are some of the aspects written using PostSharp. They have been divided into two categories based on their purposes:

1) Logging :


 

i)TraceMeAttribute : A simple aspect which Traces the execution of the method:

using System;
using PostSharp.Aspects;

namespace CustomLoggingAttributes
{
   [Serializable]
   public class TraceMeAttribute : OnMethodBoundaryAspect
   {
      String _enteringMessage;
      String _leavingMessage;
      
      public override void CompileTimeInitialize(System.Reflection.MethodBase method, AspectInfo aspectInfo)
      {
         var methodName = method.DeclaringType.FullName + "." + method.Name;

         _enteringMessage = "Entering " + methodName;
         _leavingMessage = "Leaving " + methodName;
      }

      public override void OnEntry(MethodExecutionArgs args)
      {
         System.Diagnostics.Trace.Listeners["TracingListener"].WriteLine(DateTime.UtcNow);
         System.Diagnostics.Trace.Listeners["TracingListener"].WriteLine(this._enteringMessage);
         System.Diagnostics.Trace.Listeners["TracingListener"].IndentLevel++;
         System.Diagnostics.Trace.Listeners["TracingListener"].Flush();
      }

      public override void OnExit(MethodExecutionArgs args)
      {
         System.Diagnostics.Trace.Listeners["TracingListener"].IndentLevel--;
         System.Diagnostics.Trace.Listeners["TracingListener"].WriteLine(this._leavingMessage);
         System.Diagnostics.Trace.Listeners["TracingListener"].Flush();
      }

      public override void OnSuccess(MethodExecutionArgs args)
      {
         System.Diagnostics.Trace.Listeners["TracingListener"].WriteLine(args.Method.Name + " Succeeded");
         System.Diagnostics.Trace.Listeners["TracingListener"].Flush();
      }

      public override void OnException(MethodExecutionArgs args)
      {
         System.Diagnostics.Trace.Listeners["TracingListener"].WriteLine(args.Method.Name + " Exception");
         System.Diagnostics.Trace.Listeners["TracingListener"].Flush();
      }
   }
}


Usage:
[TraceMe]
      public int CreateA(A aToCreate)
      {
         aToCreate.EmailAddress = "lawfulevil3@batsite3.com";
         aToCreate.NumberWithBounds = 50;
         aToCreate.ShouldNotBeNull = "something still not null 3";

         return 123;
      }

ii)LogValueAttribute : A simple aspect which logs the value of a variable

using System;
using CustomValidationAttributes;

namespace CustomLoggingAttributes
{
   [Serializable]
   public class LogValueAttribute : FieldValidationAttribute
   {
      protected override void Validate(object value)
      {
         System.Diagnostics.Trace.Listeners["FieldValueListener"].WriteLine(FieldName + " = " + value);
         System.Diagnostics.Trace.Listeners["FieldValueListener"].Flush();
      }
   }
}

 


Usage:


using CustomLoggingAttributes;
using CustomValidationAttributes;

namespace CRM.A
{
   public class A
   {
      [LogValue]
      private string _emailAddress;

      [LogValue]
      private int _numberWithBounds;

      [LogValue]
      private object _shouldNotBeNull;

      public string EmailAddress
      {
         get { return _emailAddress; }
         set { _emailAddress = value; }
      }

      public int NumberWithBounds
      {
         get { return _numberWithBounds; }
         set { _numberWithBounds = value; }
      }

      public object ShouldNotBeNull
      {
         get { return _shouldNotBeNull; }
         set { _shouldNotBeNull = value; }
      }
   }
}
 


iii)RecordExceptionAttribute : A simple aspect which records whenever an exception occurs in a method.

using System;
using PostSharp.Aspects;

namespace CustomLoggingAttributes
{
   [Serializable]
   public class RecordExceptionAttribute : OnMethodBoundaryAspect
   {
      public override void OnException(MethodExecutionArgs args)
      {
         System.Diagnostics.Trace.Listeners["ExceptionListener"].WriteLine(args.Method.Name + " threw " + args.Exception.Message + Environment.NewLine + args.Exception.ToString());
         System.Diagnostics.Trace.Listeners["ExceptionListener"].WriteLine("_________________________________________");
         System.Diagnostics.Trace.Listeners["ExceptionListener"].Flush();
      }
   }
}

Usage :


 

[RecordException]
      public ActionResult IndexBUpdate(int number)
      {
         bool isUpdated;

         if (number == 0)
         {
            isUpdated = _bService.UpdateB(null);
         }
         {
            isUpdated = _bService.UpdateB(new B());
         }

         return new ImplementationOfActionResult("IndexBUpdate", isUpdated);
      }



 

iv)PerformanceAnalyzerAttribute : An aspect which records the time taken for the execution of a method.

using System;
using System.Diagnostics;
using System.Threading;
using PostSharp.Aspects;

namespace CustomLoggingAttributes
{
   [Serializable]
   public class PerformanceAnalyzerAttribute : MethodInterceptionAspect
   {
      private String _methodName;
      private long _hits;

      public override void CompileTimeInitialize(System.Reflection.MethodBase method, AspectInfo aspectInfo)
      {
         _methodName = method.DeclaringType.FullName + "." + method.Name;
      }

      public override void OnInvoke(MethodInterceptionArgs methodInterceptionArgs)
      {
         var stopwatch = Stopwatch.StartNew();

         try
         {
            methodInterceptionArgs.Proceed();
         }
         finally
         {
            stopwatch.Stop();
            Interlocked.Increment(ref _hits);

            Trace.Listeners["PerformanceAnalyzerListener"].WriteLine(_methodName + " : Last Call Took : " + stopwatch.ElapsedMilliseconds + "ms . No of hits so far : " + _hits);
            Trace.Listeners["PerformanceAnalyzerListener"].Flush();

            stopwatch.Reset();
         }
      }
   }
}

Usage :


[PerformanceAnalyzer]
      [RecordException]
      public ActionResult IndexCaCreate(int number)
      {
         int createdCaId;

         if (number == 0)
         {
            createdCaId = _caService.CreateCa(null);
         }
         else
         {
            var ca = new Ca()
            {
               EmailAddress = "lawfulevil@batsite.com",
               NumberWithBounds = 100,
               ShouldNotBeNull = "something not null"
            };

            createdCaId = _caService.CreateCa(ca);
         }

         return new ImplementationOfActionResult("IndexCaCreate", createdCaId);
      }


2)Validation :

All the Validation attributes need to first implement intercept the Accessors or Modifiers of the fields and then Validate it. The following attribute intercepts the modifiers for a field. All validations then inherit from this attribute to validate the value of the field.


using System;
using System.Reflection;
using PostSharp.Laos;

namespace CustomValidationAttributes
{
   [Serializable]
   [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)]
   public abstract class FieldValidationAttribute : OnFieldAccessAspect
   {
      public string FieldName { get; private set; }

      public override void CompileTimeInitialize(FieldInfo field)
      {
         base.CompileTimeInitialize(field);

         FieldName = field.DeclaringType.Name + "." + field.Name;
      }

      public sealed override void OnSetValue(FieldAccessEventArgs eventArgs)
      {
         Validate(eventArgs.ExposedFieldValue);
         base.OnSetValue(eventArgs);
      }

      public override OnFieldAccessAspectOptions GetOptions()
      {
         return OnFieldAccessAspectOptions.GeneratePropertyAuto;
      }

      protected abstract void Validate(object value);
   }
}

The attributes :

i)FieldNullValidatorAttribute : Throws argument exception whenever the field is null.

using System;

namespace CustomValidationAttributes
{
   [Serializable]
   public class FieldNullValidatorAttribute : FieldValidationAttribute
   {
      protected override void Validate(object value)
      {
         if(value == null)
         {
            throw new ArgumentNullException("field : " + FieldName);
         }
      }
   }
}


Usage :


using CustomLoggingAttributes;
using CustomValidationAttributes;

namespace CRM.A
{
   public class A
   {
      [LogValue]
      private string _emailAddress;

      [LogValue]
      private int _numberWithBounds;

      [FieldNullValidator]
      [LogValue]
      private object _shouldNotBeNull;

      public string EmailAddress
      {
         get { return _emailAddress; }
         set { _emailAddress = value; }
      }

      public int NumberWithBounds
      {
         get { return _numberWithBounds; }
         set { _numberWithBounds = value; }
      }

      public object ShouldNotBeNull
      {
         get { return _shouldNotBeNull; }
         set { _shouldNotBeNull = value; }
      }
   }
}

ii)FieldRangeValidator: Makes sure that the field falls in a certain range for ints. Throws ArgumentOutOfRangeException otherwise.

using System;

namespace CustomValidationAttributes
{
   [Serializable]
   public class FieldRangeValidatorAttribute : FieldValidationAttribute
   {
      private readonly int _lb;
      private readonly int _ub;

      public FieldRangeValidatorAttribute(int lb,int ub)
      {
         _lb = lb;
         _ub = ub;
      }
      
      protected override void Validate(object value)
      {
         var field = (Int32) value;

         if(_lb > field)
         {
            throw new ArgumentOutOfRangeException(FieldName + "is lower than the lower bound.");
         }

         if(_ub < field)
         {
            throw new ArgumentOutOfRangeException(FieldName + "is higher than the upper bound.");
         }
      }
   }
}

Usage :

using CustomLoggingAttributes;
using CustomValidationAttributes;

namespace CRM.A
{
   public class A
   {
      [LogValue]
      private string _emailAddress;

      [FieldRangeValidator(10,50)]
      [LogValue]
      private int _numberWithBounds;

      [FieldNullValidator]
      [LogValue]
      private object _shouldNotBeNull;

      public string EmailAddress
      {
         get { return _emailAddress; }
         set { _emailAddress = value; }
      }

      public int NumberWithBounds
      {
         get { return _numberWithBounds; }
         set { _numberWithBounds = value; }
      }

      public object ShouldNotBeNull
      {
         get { return _shouldNotBeNull; }
         set { _shouldNotBeNull = value; }
      }
   }
}

iii)FieldRegexValidatorAttribute : Makes sure that a string follows a certain regex pattern (eg. Email). Else throws ArgumentException
using System;
using System.Text.RegularExpressions;

namespace CustomValidationAttributes
{
   [Serializable]
   public class FieldRegexValidatorAttribute : FieldValidationAttribute
   {
      private readonly String _pattern;
      private RegexOptions _regexOptions = RegexOptions.Compiled;

      [NonSerialized]
      private Regex _regex;

      public RegexOptions RegexOptions
      {
         get { return _regexOptions; }
         set { _regexOptions = value; }
      }

      public FieldRegexValidatorAttribute(string pattern)
      {
         _pattern = pattern;
      }

      public override void RuntimeInitialize(System.Reflection.FieldInfo fieldInfo)
      {
         base.RuntimeInitialize(fieldInfo);
         _regex = new Regex(_pattern,_regexOptions);
      }

      protected override void Validate(object value)
      {
         var field = (String) value;

         if(!_regex.IsMatch(field))
         {
            throw new ArgumentException("The value does not match the expected pattern.");
         }
      }
   }
}

Usage :

using CustomLoggingAttributes;
using CustomValidationAttributes;

namespace CRM.A
{
   public class A
   {
      [FieldRegexValidator(@"^((\""[^\""\f\n\r\t\v\b]+\"")|([\w\!\#\$\%\&\'\*\+\-\~\/\^\`\|\{\}]+(\.[\w\!\#\$\%\&\'\*\+\-\~\/\^\`\|\{\}]+)*))@((\[(((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9])))\])|(((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9])))|((([A-Za-z0-9\-])+\.)+[A-Za-z\-]+))$")]
      [LogValue]
      private string _emailAddress;

      [FieldRangeValidator(10,50)]
      [LogValue]
      private int _numberWithBounds;

      [FieldNullValidator]
      [LogValue]
      private object _shouldNotBeNull;

      public string EmailAddress
      {
         get { return _emailAddress; }
         set { _emailAddress = value; }
      }

      public int NumberWithBounds
      {
         get { return _numberWithBounds; }
         set { _numberWithBounds = value; }
      }

      public object ShouldNotBeNull
      {
         get { return _shouldNotBeNull; }
         set { _shouldNotBeNull = value; }
      }
   }
}

iv)NullParameterValidatorAttribute : Checks that the parameter passed to the method is not null. Else throws the specified exception in the constructor.
using System;
using PostSharp.Aspects;

namespace CustomValidationAttributes
{
   [Serializable]
   public class NullParameterValidatorAttribute : OnMethodBoundaryAspect
   {
      private readonly Type _type;
      private String _methodName;

      public NullParameterValidatorAttribute(Type type)
      {
         _type = type;
      }

      public override void CompileTimeInitialize(System.Reflection.MethodBase method, AspectInfo aspectInfo)
      {
         _methodName = method.Name;
      }

      public override void OnEntry(MethodExecutionArgs args)
      {
         var arguments = args.Arguments;

         if (arguments[0] == null)
         {
            var constructor = _type.GetConstructor(new[]{typeof(String)});
            throw (Exception) constructor.Invoke(new Object[] {_methodName});
         }
      }
   }
}

Usage :

[NullParameterValidatorAttribute(typeof(ArgumentNullException))]
      public int CreateA(A aToCreate)
      {
         aToCreate.EmailAddress = "lawfulevil2@batsite2.com";
         aToCreate.NumberWithBounds = 40;
         aToCreate.ShouldNotBeNull = "something not null 2";

         System.Threading.Thread.Sleep(TimeSpan.FromSeconds(.1));
         return _aRepository.CreateA(aToCreate);
      }

 

v)ZeroParameterValidatorAttribute : Makes sure that the parameter passed to the method is non-zero.
using System;
using PostSharp.Aspects;

namespace CustomValidationAttributes
{
   [Serializable]
   public class ZeroParameterValidatorAttribute : OnMethodBoundaryAspect
   {
      private readonly Type _type;
      private String _methodName;

      public ZeroParameterValidatorAttribute(Type type)
      {
         _type = type;
      }

      public override void CompileTimeInitialize(System.Reflection.MethodBase method, AspectInfo aspectInfo)
      {
         _methodName = method.Name;
      }

      public override void OnEntry(MethodExecutionArgs args)
      {
         var arguments = args.Arguments;

         switch ((Int32)arguments[0])
         {
            case 0:
               {
                  var constructor = _type.GetConstructor(new[] { typeof(String) });
                  throw (Exception)constructor.Invoke(new Object[] { _methodName});
               }
         }
      }
   }
}

Usage :

 

[ZeroParameterValidator(typeof(ArgumentException))]
      public A GetA(int aId)
      {
         System.Threading.Thread.Sleep(TimeSpan.FromSeconds(.2));
         return _aRepository.GetA(123);
      }

 

All the above aspects (technically called attributes) all are classes which are applied to either methods or fields in classes. All attributes inherit from classes such as OnMethodBoundaryAspect, MethodInterceptionAspect, OnFieldAccessAspect etc. These classes when inherited give the programmer access to methods which intercept method calls or accessors and modifiers. Those are just few examples of the built-in classes which PostSharp provides us, many more are available to use and tailor according to the situation and need.

Going from the Programming Paradigm to a library which helps do that, we now move on to complete full blown software which is based on the same theory and uses PostSharp dlls and more, but with a fantastic UI and a convenient way to monitor applications running remotely.

Gibraltar:

"Gibraltar is like an airplane "black box" for ASP.NET web apps, desktop apps and services. It efficiently and safely collects errors, metrics and usage patterns from your applications and provides robust infrastructure to transmit and analyze all that data. "– Gibraltarsoftware.com

Gibraltar continues the aspect oriented programming approach and silently logs and reports error from the application being monitored. It drastically reduces analysis time, by exactly pointing our when and where a certain event has occurred.

Its aim is to improve software quality, streamline customer support and enhance the UI experience.

Gibraltar integrates with Defect Tracking Systems like Gemini and FogBugz (Senera might work).

 

Product Overview:

Three aspects of integrating Gibraltar :

  1. Gibraltar Agent: Add the Gibraltar Agent (.dll) to you application and by modifying the app.config or web.config. This can be done through the analyst configuration wizard or manually. It also adds references. Or simply use the API.

    The Gibraltar Agents functionality is to gather data; it can record any events, metrics and any errors that occur.


     

  2. Hub Web Service: This is where all the data is gathered from the Gilbraltar Agents and can be sent to the different analysts or can send emails.


     

  3. Gibraltar Analyst: Analyze the data which the Gibraltar Agent has gathered in sessions and sent via the Hub. This is a desktop application.


     

Sessions:

 

Every package of data created by the Gibraltar Agent is called a session. Sessions are created and sent from the Gibraltar Agent and forwarded to the Gibraltar Hub Service, which then forwards the sessions to all the analysts which have subscribed to this Hub.

Every session can be opened by an analyst and can be easily navigated through filters. With analysts you can also play around with the metrics which have been recorded in the sessions to create graphs and charts to give a better representation of the performance of the application under observation.

 

Sending Sessions:

 

By default, a session is closed and sent when the application is properly shut down and started up again or when the app pool recycles (Every 23 hours by default). When restarted the Gibraltar Agent checks for any unsent sessions and sends them.

We can control this behavior. Following are three ways to do it:

  1. By simply setting Log.SendSessionsOnExit = true through code, the sessions are sent on a clean shut down of the application.
  2. The Log class provides us with an event called "MessageAlert". By subscribing to this event you can hook on to the condition when any kind of alert occurs which includes Error messages.
  3. With user's intervention and event can be triggered and on this event the session can be sent. Eg. A button on the website.


     

    When Message Alert event occurs, it results in a LogMessageAlertEventArgs which is categorized into either one of these LogMessageSeverity:


     

    LogMessageSeverity.Critical

    LogMessageSeverity.Error

    LogMessageSeverity.Warning

    LogMessageSeverity.Information

    LogMessageSeverity.Verbose

    LogMessageSeverity.Unknown


     

    Actions can be decided based on the levels of Message Severity.


     

    For example : On the Event Log.MessageAlert I hook up two event handlers, one event handler will send the current active session to the Gibraltar Hub Service, the other will send an email stating that an Error or Critical alert has occurred. Notice that sessions are sent only if

e.TopSeverity <= LogMessageSeverity.Error

 

This means if severity is at the level of error or critical.

 

Also, the Log.SendSessionsOnExit is set to true to send sessions on clean exit of application.

 

protected void Application_Start()
      {
         AreaRegistration.RegisterAllAreas();

         RegisterRoutes(RouteTable.Routes);

         Log.MessageAlert += LogMessageAlert;
         Log.MessageAlert += LogMessageAlertSendEmail;


         Log.SendSessionsOnExit = true;
      }

      private static void LogMessageAlert(object sender, LogMessageAlertEventArgs e)
      {
         //if there are any errors (or worse - criticals) we want to send the
         //up to date information on this session immediately.
         if (e.TopSeverity <= LogMessageSeverity.Error) //numeric values DROP for more severe enum values
         {
            //set our auto-send to true.
            e.SendSession = true;
            //and lets make sure we don't send again for at least a few minutes
            //to ensure we don't flood in the event of a storm of errors.
            //e.MinimumDelay = new TimeSpan(0, 5, 0); //5 minutes
         }
      }

      private static void LogMessageAlertSendEmail(object sender, LogMessageAlertEventArgs e)
      {
         var p = new PackagerDialog();
         p.SetEmailServer("server.domain.com", 0, true, null, null);
         
         //if we had an error or critical we want to send);
         if (e.TopSeverity <= LogMessageSeverity.Error) //numeric values DROP for more severe enum values
         {
            //get the set of messages that are the "worst" in this event.
            var badMessages = e.Messages.Where(message => message.Severity == e.TopSeverity).ToList();
            //now make us an email message describing these guys
            var messageBody = FormatMessageBody(badMessages);
            var subject = string.Format("{0} {1}: {2}", Log.SessionSummary.Application,
                e.TopSeverity.ToString().ToUpper(), badMessages[0].Caption);
            //and for safety's sake lets keep our subject from being crazy long.
            if (subject.Length > 120)
            {
               subject = subject.Substring(0, 120) + "...";
            }
            //now that we've done all of that, lets send our message using the Agent's email config
            using (var message = new MailMessage(new MailAddress("gibraltar@domain.com","Gibraltar"),new MailAddress("name@domain.com")))
            {
               message.Subject = subject;
               message.Body = messageBody;
               message.Priority = MailPriority.High;
               
               //now send our email!  I'm not bothering to catch exceptions since the Agent handles that nicely for us
               //Log.SendMessage(message); //synchronous OK because we're already async from the flow of logging.
               var smtpClient = new SmtpClient("server.domain.com ") {UseDefaultCredentials = true};
               smtpClient.Send(message);
            }
            //and lets make sure we don't send again for at least a few minutes
            //to ensure we don't flood in the event of a storm of errors.
            //e.MinimumDelay = new TimeSpan(0, 5, 0); //5 minutes
         }
      }

      private static string FormatMessageBody(IList messages)
      {
         var messageBody = new StringBuilder(1024);
         //we write out more detail about the first item, then just summarize.
         var firstMessage = messages[0];
         messageBody.AppendFormat("Timestamp: {0:g}\r\n", firstMessage.Timestamp);
         messageBody.AppendFormat("Category:  {0}\r\n", firstMessage.CategoryName);
         messageBody.AppendFormat("Class:     {0}\r\n------------------------------\r\n", firstMessage.ClassName);
         messageBody.AppendFormat("{0}\r\n", firstMessage.Caption);
         messageBody.AppendFormat("{0}\r\n\r\n", firstMessage.Description);
         //report any exceptions on this first object.
         var currentException = firstMessage.Exception;
         if (currentException != null)
         {
            messageBody.Append("Exceptions:\r\n");
            while (currentException != null)
            {
               messageBody.AppendFormat("{0}: {1}\r\n\r\n", currentException.TypeName, currentException.Message);
               //Each outer exception can point to an inner exception, we get null when there are no more.
               currentException = currentException.InnerException;
            }
         }
         //summarize the rest of the messages
         if (messages.Count > 1)
         {
            messageBody.AppendFormat("Other {0}s:\r\n", firstMessage.Severity);
            for (var curMessageIndex = 1; curMessageIndex < messages.Count; curMessageIndex++)
            {
               var currentMessage = messages[curMessageIndex];
               messageBody.AppendFormat("------------------------------\r\nMessage {0} of {1}: {2}: {3}\r\n\r\n",
                   curMessageIndex, messages.Count, currentMessage.Severity, currentMessage.Caption);
            }
         }
         return messageBody.ToString();
      }

The next example shows how to use the Packager class to send ActiveSessions and any unsent sessions.

 

var packager = new Packager();
         packager.SendToServerAsync(SessionCriteria.ActiveSession|SessionCriteria.NewSessions,true);

The above code can be put into an event handler for an event like Button.Click on User's intervention.

 

SessionCriteria has other options which are as following:

 

SessionCriteria.ActiveSession

SessionCriteria.AllSessions

SessionCriteria.CompletedSessions

SessionCriteria.CrashedSession

SessionCriteria.CriticalSession

SessionCriteria.ErrorSession

SessionCriteria.NewSessions

SessionCriteria.None

SessionCriteria.WarningSession

Gibraltar with PostSharp:

 

Gibraltar is shipped with PostSharp. This is due to the fact that Gibraltar has its own aspects for logging purposes. They are as following :

GException :

A PostSharp aspect that logs exceptions after they are thrown when they cause a method to exit. This allows for logging of handled as well as unhandled exceptions.

GFeature :

A PostSharp aspect used to record how often a particular method in your application is used, how long it takes to run, and whether it was ultimately succesful or not. It record both log messages and metrics to enable powerful analysis.

GTimer:

A PostSharp aspect that will log execution time for methods. Data is stored as a Gibraltar metric allowing charting and graphing in Gibraltar Analyst.

GTrace:

Enables the logging of method entry and exit at runtime complete with parameter information and results.

*GField:

Enables the logging of field values.

These attributes can be associated with a single method, a property or an entire class. Attribute multicasting can be used to apply it to all matching methods or fields or classes in the assembly.

// Log thrown exceptions for every method in this assembly.
[assembly: GException]
// Log entry and exit of every method with exceptions listed below
[assembly: GTrace(AttributePriority = -1)]
// Exclude constructors and a few excessively noisy classes & methods
[assembly: GTrace(AttributeTargetMembers = ".ctor", AttributeExclude = true)]
[assembly: GTrace(AttributeTargetMembers = "ToString", AttributeExclude = true)]

Attention Error Catchers: In case of exceptions, even if exceptions may be ultimately handled by a method higher up the call stack they are recorded. But because they are handled, they are logged as warnings. If they ultimately become unhandled exceptions then Gibraltar's Error Manager will automatically record them as error.

These are just out of the box PostSharp aspects which are provided by Gibraltar; custom logging and validation aspects can be easily added to use Gibraltar. For eg., the Log class provides most of the tracing functionalities.

Monday, May 17, 2010

C# : Unit Testing Private Methods

When a developer writes a piece of code, he(or she) would like to verify and validate that what was just written by him is exactly what he intended to do. How can he be sure ?

One of the possibilities is to run(execute) the code and see the output, which is possible when if its some small program/project. But when you are working on a huge project (e.g. a project with some back ends talking to a database or talking over the wire, a logical layer and multiple GUIs which may be using other assemblies) loading up the whole project and running it to verify if a small functionality is working isn't sensible. This is due to the facts that the project itself may take a long time to load and execute, it may require loading and linking multiple assemblies(.dll) which will take up resources and its an expensive operation. Wasting so much time and resources for a small functionality test is not worth it. And its definitely not sensible when you have to keep testing repetitively.

Instead. a developer can test a piece of his code by writing Unit Tests for that function. Its pretty simple when you are testing public methods.Following is a format for a normal public method of a public class being tested:

If you don't know what is a Unit Test, please take a look here :
http://en.wikipedia.org/wiki/Unit_testing

For e.g. :

//The Class you want to test

public class foo
{
    .
    .
    .
    //The PUBLIC method you want to test
    public int bar(int c)
    {
        //Logic and result returned
    }
}

//In the test class
public class fooTest
{
    //System Under Test (Class foo) whose method needs to be tested
    private static foo _sut = new foo();
    .
    .
    //Test for bar
    //TestMethod is the method attribute which tells CLR that this is a test
    //method and not a normal method
    [TestMethod]
    public void barTest()
    {
        //Arrange (Any arrangement which might be needed before calling the method
        //to test

        //Act (The actual call)
        int y = _sut.bar(x);

        //Assert for 'y' (Assert the result of the method under test)
    }
}

If you don't know what is a [TestMethod] attribute, please look here :
http://msdn.microsoft.com/en-us/library/microsoft.visualstudio.testtools.unittesting.testmethodattribute%28VS.80%29.aspx

The above format/set up is only possible if its a public method you are testing. Usually, it makes sense for the developer to test only the public methods. Private helper methods aren't usually tested.

But, there are times when a developer would like to write tests for a private method. This is possible when the private method contains some critical logic which plays an important part in many other public methods. For developer's satisfaction and verification testing private methods may be needed. You can use the built-in MSTest private method testing methods which creates an accessor to the private methods,but that accessor is not compatible to Team Build. In that case, the above set up will require modification. Two different scenarios depending on if you are testing just Private methods or Private Static methods.

If you don't know what is MSTest, please take a look here :
http://en.wikipedia.org/wiki/MSTest

If you don't know what is Team Build, please take a look here :
http://en.wikipedia.org/wiki/Team_Foundation_Server

Following is the scenario for a Private Method:

For e.g. :

//The Class you want to test

public class foo
{
    .
    .
    .
    //The PRIVATE method you want to test
    private int bar(int c)
    {
        //Logic and result returned
    }
}

//In the test class
public class fooTest
{
    .
    .
    .
    //Test for bar
    //TestMethod is the method attribute which tells CLR that this is a test
    //method and not a normal method
    [TestMethod]
    public void barTest()
    {
        //Arrange (Any arrangement which might be needed before calling the method
        //to test

        //Act (The actual call)
        //Creating an instance of PrivateObject class with the instance of the class
        //whose private method needs to be tested.
        PrivateObject privateObject = new PrivateObject(new foo());

        //Invoking the private method
        int y = privateObject.Invoke("bar", intParameter) as Integer;

        //Assert for 'y' (Assert the result of the method under test)
    }
}


If you don't know about PrivateObject, please take a look here :
http://msdn.microsoft.com/en-us/library/microsoft.visualstudio.testtools.unittesting.privateobject%28VS.80%29.aspx

The private method's name has to be spelled correctly followed by correct no. of parameter. The invocation of the method returns you back an Object which needs to be cast to the expected type.

To test Private Static methods replace the above PrivateObject instance creation with PrivateType as following:

PrivateType privateType = new PrivateType(typeof(foo));

and invoke the test method like this :

Integer y = (Integer)privateType.InvokeStatic("bar",intParameter);

Unit testing has become really popular with the practice of TDD (Test Driven Development).

If you don't know about the PrivateType class, please take a look here :
http://msdn.microsoft.com/en-us/library/microsoft.visualstudio.testtools.unittesting.privatetype%28VS.80%29.aspx

If you don't know what is TDD, please take a look here :
http://en.wikipedia.org/wiki/Test-driven_development

Unit tests also act as rules as to what should be expected from a method. Also its a great way of documenting what is expected from the method. A new developer can just look at the tests and see what is to be expected from the method under test.

Monday, April 26, 2010

Sharepoint : Custom Redirection HttpModule based on UserProfile Property

When a request is made to an ASP.NET application, an instance of HttpApplication class is made to process this request. One instance of the HttpApplication class can process multiple requests during its lifetime, however only one at a time.

When the request is being processed, the application raises a chain of events on to which an implementation of IHttpModule can be hooked on.

If you don’t know about HttpApplication class and the chain of events, please look here:
http://msdn.microsoft.com/en-us/library/system.web.httpapplication.aspx

If you don’t know about IHttpModule interface, please look here:
http://msdn.microsoft.com/en-us/library/system.web.ihttpmodule.aspx

Assuming that you are developing for Sharepoint 2007.

Install WSPBuilder on Visual Studio from here :
http://wspbuilder.codeplex.com/

Create new WSPBuilder Project in Visual Studio.

Add Sharepoint Resources : Microsoft.Office.Server.dll and Microsoft.Sharepoint.dll

Write a class which implement interface IHttpModule.

"PostAuthenticateRequest" is a EventHandler to which you can add an event (delegate) which here is fulfilled by OnAuthenticationCustomRedirect.

The HttpModule’s functionality is to redirect a user based on user’s chosen user profile property. To redirect the user or to write in the event logs on the server, the commands need to be run with elevated privileges and that is exactly what the private functions do.

The redirection happens only if the request is coming from a different host. If surfing within the website the redirection will not take place. This is done by comparing the hosts of the incoming url from current request and the previous url from previous request.

If you don't know about accessing previous urls, please look here :
http://msdn.microsoft.com/en-us/library/system.web.httprequest.urlreferrer.aspx

If the Sharepoint user doesn’t exist and userProfileManager.GetUserProfile(“name”) is called. An exception is thrown. This is the reason why the module is under try-catch block. If such an error occurs then an error is recorded in the Event Log on the server.

By comparing the value of the properties, we can then redirect the user in a customized manner.

public class RedirectModule : IHttpModule
{
#region Implementation of IHttpModule

/// 
/// Initializes a module and prepares it to handle requests.
/// 
/// An  that provides access to the methods, properties, and events common to all application objects within an ASP.NET application 
///                 public void Init(HttpApplication context)
{
//Adding an event handler to handle and redirect the incoming request to corresponding address
context.PostAuthenticateRequest += OnAuthenticationCustomRedirect;
}

/// 
/// Disposes of the resources (other than memory) used by the module that implements .
/// 
public void Dispose()
{

}

#endregion Implementation of IHttpModule

#region Redirection based on User Profile Module

private static void OnAuthenticationCustomRedirect(object sender,EventArgs eventArgs)
{
//The user who is trying to access tabnet
SPUser spUser = null;

try
{
//Getting the HttpRequest
HttpRequest request = ((HttpApplication) sender).Request;

//Host Domain
String requestUrlDomain = "http://" + request.Url.Host;

//Previous Host Domain
String previousRequestUrlDomain = String.Empty;
if(request.UrlReferrer != null)
{
previousRequestUrlDomain = "http://" + request.UrlReferrer.Host;
}

//If coming from within same host, no redirection required
if(!requestUrlDomain.Equals(previousRequestUrlDomain))
{
//Redirect only if going to the home page
if (request.Url.ToString().Equals(requestUrlDomain + “originalhomepage.aspx"))
{
//Getting the HttpContext
HttpContext context = ((HttpApplication)sender).Context;

//Creating SPSite object
SPSite spSite;
//Creating SPWeb object
SPWeb spWeb;

//Checking for the current SPContext
if (SPContext.Current != null)
{
//Getting the SPSite
spSite = SPContext.Current.Site;
//Getting the SPWeb
spWeb = spSite.RootWeb;
//Get the SPUser
spUser = spWeb.CurrentUser;

//Creating the UserProfileManager for the site
UserProfileManager userProfileManager = new UserProfileManager(ServerContext.GetContext(spSite));

//Getting the user profile from the name using the UserProfileManager
UserProfile userProfile = userProfileManager.GetUserProfile(spUser.LoginName);

//Getting all the properties from the UserProfileManager
PropertyCollection propertyCollection = userProfileManager.Properties;
//Filtering out the single property which we are interested in
Property property = propertyCollection.GetPropertyByName("PropertyName");

//Finding that property in the user profile for value
if (userProfile[property.Name].Value != null)
{
String propertyString = userProfile[property.Name].Value.ToString();

//Different actions depending on hosts of each user
switch (propertyString.ToUpper())
{
case "property value 1":
case "property value 2":
//Write the information with the user login name to eventlog
WriteToEventLog("OnAuthenticationCustomRedirect", spUser.LoginName + " has been redirected”,EventLogEntryType.Information);
//Actual redirection
ResponseRedirectElevatedPriviliges(context, requestUrlDomain + “newhomepage.aspx");
break;
//anything else no redirection
default:
break;
}
}
}
}
}
}
catch (Exception exception)
{
String message = "Exception Stack Trace : " + exception.StackTrace;

if(spUser!=null)
{
message += " User Login Name : " + spUser.LoginName;
}

//Write the error with stack trace to the event log
WriteToEventLog("OnAuthenticationCustomRedirect",message, EventLogEntryType.Error);
}
}

#endregion Redirection based on User Profile Module

#region Methods to Run With Elevated Priviliges

/// 
/// /// This method runs with elevated priviliges which writes a log entry to eventlog
/// 
/// /// /// private static void WriteToEventLog(String source, String message, EventLogEntryType eventLogEntryType)
{
SPSecurity.RunWithElevatedPrivileges(
() => EventLog.WriteEntry(source, message, eventLogEntryType));
}

/// 
/// This method runs with elevated privileges which redirects the user to the new URL via the response.
/// 
/// ///     private static void ResponseRedirectElevatedPriviliges(HttpContext context,String url)
    {
        SPSecurity.RunWithElevatedPrivileges(() => context.Response.Redirect(url, false));
    }

    #endregion Methods to Run With Elevated Priviliges
}

C# & Visual Studio: FTP Methods, Testing by Creating Resource Files and Using their Streams

I had come across a scenario where I needed to store certain files to a certain location on a server. This certain location, basically acts like a shelf to hold these files. Just like a neatly arranged shelf, all these files were arranged within a directory structure.

On the Server, I created an FTP site listening at the default port (Port 21).This FTP Server was mapped to the location of my interest.

If you don’t know how to create an FTP Site, please look here:
http://learn.iis.net/page.aspx/301/creating-a-new-ftp-site/

In Visual Studio, through my C# code I wrote a class which provided two public methods:

1) public String WriteFtpMyFileToServer(Stream fileStream)
2) public Boolean DeleteFtpMyFileFromServer(String fileNamePlusPath)

There were private helper methods as well to remove repeated coding (good practice).
Following is a code snippet from method (1) which would write the file to location of interest:

/// 
/// Method to Transfer the file via FTP
/// 
/// The stream on the file to be sent/// String
public String WriteFtpMyFileToServer (Stream fileStream)
{
    .
    .
    .
    .
    .
    //Get the FTP site Uri from ConfigurationManager in string including filepath
    Uri ftpUri = new Uri(String.Format(CultureInfo.CurrentCulture,"{0}{1}",ConfigurationManager.AppSettings["FtpSite"],filePath));

    //Creates an FTP web request
    FtpWebRequest request = (FtpWebRequest)WebRequest.Create(ftpUri);

    //Setting the request method
    request.Method = WebRequestMethods.Ftp.UploadFile;

    //Get the request stream
    Stream ftpStream = request.GetRequestStream();

    //Copy from fileStream to ftpStream
    int bufferLength = 2048; // 2K
    byte[] buffer = new byte[bufferLength];
    int count = 0;
    int readBytes = 0;
    //Byte by Byte
    do
    {
        readBytes = fileStream.Read(buffer, 0, bufferLength);
        ftpStream.Write(buffer, 0, readBytes);
        count += readBytes;
    }while (readBytes != 0);

    // Close both the streams
    fileStream.Close();
    fileStream.Dispose();
    ftpStream.Close();

    // Send the file and Get Response
    FtpWebResponse response = (FtpWebResponse)request.GetResponse();

    //Check the result of our upload and see if successful
    if (response.StatusCode == FtpStatusCode.ClosingData)
    {
        // Close the ftp session
        response.Close();
        fileSentSuccessfully = true;
    }

    .
    .
    .
    //Return String: Message of what happened or Null
    .
    .
    .
}

Its always a good thing to keep constant Uris (in this case FTP Site) in .config files. This is due to the fact that these Uris may change depending on your environments. It may be different in production, development and test environments. You can use the ConfigurationManager to access from these .config files.

Following is a code snippet from method (2) which would delete the file from the location of interest:

/// 
/// This method deletes the specified File via FTP
/// 
/// filepath followed by name/// bool sucess/failure

public Boolean DeleteFtpMyFileFromServer (String fileNamePlusPathOnServer)
{
    .
    .
    .
    //Creating Uri from string
    Uri ftpUri = new Uri(String.Format(CultureInfo.CurrentCulture,"{0}{1}",ConfigurationManager.AppSettings["FtpSite"],fileNamePlusPathOnServer));

    //Creates an FTP web request
    FtpWebRequest request = (FtpWebRequest)WebRequest.Create(ftpUri);

    //Method delete file
    request.Method = WebRequestMethods.Ftp.DeleteFile;

    // Send the command and Get Response
    FtpWebResponse response = (FtpWebResponse)request.GetResponse();

    //Check the result of our upload and see if successful
    if (response.StatusCode == FtpStatusCode.FileActionOK)
    {
        // Close the ftp session
        response.Close();

        return true;
    }

    return false;
}

Personally, I have always believed that throwing exceptions is a really good practice. Even though you don’t see any exceptions thrown in the code snippets above, you can always add code to throw exceptions to validate the parameters to the methods and for any other failure. This really helps to convey messages from the callee class to the caller class about what went wrong.

When this class is live, we would expect a real file stream to be passed to method (1). But how will you write unit tests for these methods?

Writing unit tests for each possible scenario in your methods is the best way for documenting and portraying what is expected from the method you just wrote. It validates your logic in the method, checks whether the method indeed returns what you expect and if the method is working the way you want it to. Overall, it makes the code more robust and reliable. It also helps other developers to understand what you intended to do in the method. Test Driven Development (TDD) and Code Coverage are really good techniques for the same.

If you don’t know what TDD is, please look here:
http://en.wikipedia.org/wiki/Test-driven_development

If you don’t know what Code Coverage is, please look here:
http://en.wikipedia.org/wiki/Code_coverage

To write a test for method (1), I needed to create a file in my test and pass its stream to method (1). It made no sense to create a file each time locally at a certain path and use it for test purposes. This is due to the fact that the underlying directory structure may change from host to host, depending on where the tests are run. Each test should be written independent of the location, environment and of other tests.

This is where resource files come in. A resource file can be any data such as text files, images, audio or video that you application might require. Once a resource file is added to the project, it always stays with the project just like other .cs files (your code).

In my test project, I created a folder within the project called “Resources” and added a text file to it with a test name called “TestFile.txt”. This file had some junk data in it. I clicked on the file and in the Properties dialog below it, changed the Build Action to “Embedded Resource”.

To get the stream of this file in your test class:

/// 
/// Summary description for Your Test Class
/// 

[TestClass]
public class YouTestClassName
{
    #region Private Variables
    .
    .
    .
    //Assembly giving reference to Resource File
    private static Assembly _thisAssembly;
    //File Stream of the file to be transferred (Resource Text File)
    private static Stream _TestFileStream;
    .
    .
    .
    #endregion Private Variables

    #region Test Context

    /// 
    ///Gets or sets the test context which provides
    ///information about and functionality for the current test run.
    ///
    public TestContext TestContext { get; set; }

    #endregion Test Context

    #region Additional test attributes

    // You can use the following additional attributes as you write your tests:
    //
    // Use ClassInitialize to run code before running the first test in the class
    [ClassInitialize]
    public static void MyClassInitialize(TestContext testContext) 
    {
        .
        .
        .
        //Getting the assembly
        _thisAssembly = Assembly.GetExecutingAssembly();
        .
        .
        .
    }

    // Use ClassCleanup to run code after all tests in a class have run
    [ClassCleanup]
    public static void MyClassCleanup() 
    {
        .
        .
        .
    }

    //// Use TestInitialize to run code before running each test 
    //[TestInitialize()]
    //public void MyTestInitialize() 
    //{ 

    //}

    //// Use TestCleanup to run code after each test has run
    //[TestCleanup()]
    //public void MyTestCleanup() 
    //{

    //}

    #endregion Additional test attributes

    /// 
    /// This method tests the FtpFile method successfully
    /// 
    [TestMethod]
    public void FtpFileTest_Success()
   {
        //ARRANGE
        //Getting the file stream
        if (_thisAssembly != null)
        {
            //Getting the stream from resource file
            _TestFileStream = _thisAssembly.GetManifestResourceStream("Namespace.TestFile.txt");
        }

        //ACT
        //Calling the WriteFtpFileToServer method (METHOD(1))
        //_sut is the instance of the class which provides these methods
        _returnString = _sut.WriteFtpFileToServer(_TestFileStream);

        //ASSERT
        Assert.IsNotNull(_returnString);
    }
    .
    .
    .
    //More tests
    .
    .
    .
}
This way, the tests can be run on any machine without worrying about the underlying directory structure of the machine.

Monday, March 8, 2010

C# : HostName, MachineName and IPAddresses

Host Name and Machine Name are not the same. A Host Name is a name given to a host(e.g. computer or phone) by the Domain Name System(DNS). Therefore, this name is in context with the DNS. For more details have a look here : http://en.wikipedia.org/wiki/Hostname

On the other hand, a Machine Name is more in the local context. which is established during system start up and when the name is ready from the registry.

Code to find the Host Name :

String HostName = System.Net.Dns.GetHostName();

Code to find the Machine Name :
String MachineName = System.Environment.MachineName;

Code to find the IPAddress (IPv4) of a host :

System.Net.Dns.GetHostName() and "" (Empty String) refer to the same host name.
"localhost" refers to the reserved IP addresses 127.0.0.1 in IPv4, or ::1 in IPv6.
For more details about localhost, take a look here :
http://en.wikipedia.org/wiki/Localhost

System.Net.IPAddress IpAddress = System.Net.Dns.GetHostAddresses("").Single(ipAddressFamily => ipAddressFamily.AddressFamily == AddressFamily.InterNetwork);

is the same as

System.Net.IPAddress IpAddress = System.Net.Dns.GetHostAddresses(System.Net.Dns.GetHostName()).Single(ipAddressFamily => ipAddressFamily.AddressFamily == AddressFamily.InterNetwork);

but is NOT the same as

System.Net.IPAddress IpAddress = System.Net.Dns.GetHostAddresses("localhost").Single(ipAddressFamily => ipAddressFamily.AddressFamily == AddressFamily.InterNetwork);

.Single(ipAddress => ipAddress.AddressFamily == AddressFamily.InterNetwork) is a function which takes a Predicate Delegate as an argument. The predicate determines whether the object to which it is applied to meets the criteria stated in the Predicate. The predicate here is a lambda function/inline function. For e.g. here the predicate checks whether each ipAddress(from AddressList which is returned by System.Net.Dns.GetHostAddress(...)) satisfies the fact that the ipAddress.AddressFamily == AddressFamily.InterNetwork (IPv4). Since we are using "Single" here it means that, it will return only the one which satisfied the condition, if there are more than one which satisfy the condition, it will throw an error.

Look here for more of different delegates offered by C#:
http://msdn.microsoft.com/en-us/library/system.aspx

Look here for more on lambda expressions :
http://msdn.microsoft.com/en-us/library/bb397687.aspx

Look here for more on delegates :
http://msdn.microsoft.com/en-us/library/ms173171%28VS.80%29.aspx

More on the power of Delegates later ...

Thursday, March 4, 2010

Installing SharePoint Server 2010 in VirtualBox


Guest OS : Windows Server 2008 SP2 64-bit Operating System

Host OS : Windows 7 Enterprise 64-bit Operating System


Installing SharePoint on a normal Windows Machine itself can be a bit messy, let alone installing it on a Virtual Machine (VM).
If you don’t know what is SharePoint, please look here : 
If you don’t know what is a Virtual Machine, please look here: 
Developers prefer installing SharePoint on a VM since it requires a lot of other installations and configurations which are prerequisites to install SharePoint. This eats up a lot of resources and gives you the feeling that SharePoint is all over the place. Therefore, it makes sense to install SharePoint on a VM which keeps it isolated and will not hinder any production or other kind of development on your host machine. This is really important in the case of SharePoint 2010 as it is still in the beta stage and should not be used in a production environment (As of 3/2/2010).
If you don’t know what is a Host Machine, please look here:   
If you don’t know the differences between a Development Environment and Production Environment, please look here (Garron Selliken explains it very simply and nicely on his blog): 
SharePoint Server 2010 Hardware and Software requirements can be found here:
http://technet.microsoft.com/en-us/library/cc262485%28office.14%29.aspx
If you looked at the Hardware and Software requirements link, you would understand when I say that I would like to create a ‘Single server with built-in database’, therefore I will install the 64-bit edition of Windows Server 2008 Standard with SP2 on the VM. Also, Windows Server 2008 SP 2 provides infrastructure  and management tools that enable to create multiple server environments on single host which can’t be on Windows 7. If you have only Windows Server 2008 and no Service Pack 2, then the installation of SharePoint will figure out that the OS requires Service Pack 2 and will download and install it.
Following are my host machine specifications :
  • Windows 7 Enterprise 64-bit Operating System
  • Processor : Intel® Core™ 2 Quad CPU Q6700 @ 2.66GHz 2.67GHz
  • RAM : 8.00GB
  • HDD : 150GB
  • High- Speed Internet Connection

 

Decide the Virtualization Software Package to use:


It goes without saying that which ever virtualization software package is used, it should allow to fulfill the minimum hardware and software requirements as stated by Microsoft for SharePoint Server 2010.
If you don’t know what is Virtualization, please look here :        
Virtual PC, which comes along with Windows 7 (basically to virtualize Windows XP) can only run 32-bit operating systems. But as we know SharePoint 2010 can currently be installed only on a 64-bit operating system. Therefore, we can strike out VirtualPC.
If you don’t know differences between 32-bit OS and 64-bit OS, please look here :
http://windows.microsoft.com/en-US/windows-vista/32-bit-and-64-bit-Windows-frequently-asked-questions
There are a lot of virtualization software packages out there which can allow you to install a 64-bit Operating System (the 64-bit edition of Windows Server 2008 Standard with SP2 as decided).
You can find differences between different virtualization software packages here: 
I decided on using VirtualBox as I am already familiar with it. The recommended virtualization software package  by Microsoft for SharePoint 2010 is Hyper-V.

Creating a VM and Virtual Hard Disk Drive :


Close all other applications and have a clean desktop.
Download and install VirtualBox like any other software package. It can be found at the following link:
http://www.virtualbox.org/wiki/Downloads (Use the one for Windows hosts and 64-bit)
Start the VirtualBox application.
Click on New and follow the wizard.
Give a name for the VM you want to create and select the appropriate OS Type. This is how it looked in my case:

Click next.
Select the amount of base memory(RAM). Remember  that we should keep in mind the requirements of SharePoint Server 2010 and not the requirements to merely install Windows Server 2008. Therefore 4GB.

Click Next.
Make sure ‘Boot Hard Disk (Primary Master)’ is checked and radio button is on ‘Create new hard disk’

Click Next.
A wizard to create new virtual disk will start up.
Click Next.
Storage Type should be ‘Dynamically Expanding Storage’. When I used this option, I thought that I would be allowed to change the size of the Virtual Hard Disk (VHDD) even after installation is complete. But I couldn’t find a way to do that. But even if there is a slightest possibility that I can do this (if I find out the way later on) then I would like to keep this option open.
Click Next.
Give a name for the VHDD you will create for your VM.  Keep the size of the VHDD to 100GB. This is how my window looks like :

Click Next.
Confirm and Click Finish.
You will be back to ‘Create New Virtual Machine’ window with your VHDD selected.
Click Next.
Confirm and Click Finish.
You will see your VM added to VirtualBox with the new VHDD. It will look similar to this :



Booting from DVD Drive or Iso :


Put in the Windows Server 2008 SP2 DVD in the CD Tray to install from your DVD.
OR If you have an Iso image of the Operating System.
  • Click on File
  • Start Virtual Media Manager
  • Click on CS/DVD Images tab
  • Click on ‘Add’
  • Hunt for the Iso file
  • Click ‘Open’.
  • Click ‘OK’
  
If you don’t know what is ISO image file, please look here :
To make sure that the boot order of the VM is CD/DVD before VHDD :
Highlight the VM by clicking on it once and click on ‘Settings’.
Click on ‘System’ and make sure that CD/DVD-ROM is on top of Hard Disk. You can use the check boxes  and use arrows to change the boot order.
If you don’t know what is booting, please look here : 


Click OK.

Install Windows Server 2008 SP2 :


Double Click on the VM you just created OR Highlight the VM you just created and click Start.
The Virtual Machine will boot up with a blue Sun VirtualBox Screen and will start the install of the Operating System from your Iso image or the DVD in your tray (According to your set up).
Following this, installation of the Operating System on the VM is the same as how you would normally install an Operating System on a normal machine except that this installation will be running in your VM Window.
Enter the product key information of the Operating System when asked.
There shouldn’t be any problem installing the Windows Server 2008 SP2 Operating System as VirtualBox provides all virtualization support, hardware drivers can also be installed without any effort.

VM and Internet :


If this VM is on a host machine which is on an internal network which is monitored by a  firewall then this VM will require permissions to access the internet.This may involved working with the Active Directory too. In this case make sure that an Anti-Virus is installed on the VM. Unless this is done Windows Updates and SharePoint install will not work as they need to connect to the internet and download files during installation.
If you don’t know what is a Firewall, please look here : 
If you don’t know what is an Active Directory, please look here :  
If you don’t require your VM to be on some internal network and just want the VM like an independent sandbox and your host machine has an independent internet connection, then all updates and downloads should work fine. Since we will take the snapshot of the VM when its clean and new(“Clean State”) before we start doing anything on it, we don’t really require an Anti-Virus since you can always set back your VM to the “Clean State”.
Make sure that once the installation of the OS is complete then do all the Windows Updates.

Setting up Shared Folders between Host and VM :


You might be still comfortable to use the internet connection and browser on your host machine to download files which can be required to install SharePoint 2010 or any other software you need on your VM. In that case you need to transfer your files from  your host machine to your VM. You need to set up a share folder between them.
Once the VM has started and you have come to your desktop on your VM. Click on the “Devices” menu on the window in which your VM is being run by VirtualBox. This window should be saying its name as “(…) [Running] – Sun VirtualBox”.
Click “Shared Folders”.
Click the plus folder icon on the top right, which will open up a dialog box asking you to choose a folder from your host machine which you would like to share with this VM. If you don’t already have a separate folder for this purpose, you can go ahead and make one on your host machine and then come back to this window and find the folder you just created by clicking on the down arrow on the Folder Path and then Clicking “Other”.
Give an appropriate folder name. Click OK.

You will come to the following window.

If you choose read-only, it should reflect that access in the “Access” column.
If you don’t know about File system permissions, please look here :  
Click OK.
In your VM, goto Control Panel and then to Network and Sharing Center and make sure that Network Discovery is on. Make it a private network.
In your VM, goto the windows explorer and type the following command.
net use Z:\vboxsvr\ (Name of your shared folder)
Press enter.
It will take you back to your “My Computer” and under Network Location you should see the folder you shared from your host machine mapped as drive letter Z. If this doesn’t happen then you manually need to map the Z letter to the shared folder.
Click on Map Network Drive in Explorer and choose the Drive as Z: and Folder can be chosen after browsing. Since your network discovery is on, you should be able to see VBOXSVR. Expand it and you will find your folder which you shared from your host OS.Select this folder and Click OK. Then Click Finish.
Now you can see the shared folder under Network Location of your VM.
If you don’t know about Drive letter mapping, please look here :  

Install Guest Additions :


It will really make things convenient for you if you Install the Guest Additions by VirtualBox (Especially with the mouse). Click on “Devices”, Click on “Install Guest Additions”. Let it do its work. Make sure that when you click on “Install Guest Additions” you are on the desktop of your VM, else nothing will happen.

Download and install WCF Hotfix :

Before you actually install SharePoint Server 2010 on Windows Server 2008 SP2, you need to install the WCF HotFix (KB971831). It is provided by Microsoft and can be found here :
You can download it straight away from your VM’s browser or download to your host machine, put it in your shared folder and then take it to your VM from your shared folder.
Install the WCF HotFix.

 

Download SharePoint Server 2010 :


If you don’t have SharePoint Server 2010 installation file (called “OfficeServer.exe”), then you will need to go and download it from the following site :
I chose the SharePoint Server 2010 (Enterprise Client Access License features) because I believe it’s the whole deal.
You will get the product key of this software in your email which will be asked to you before you download.

Getting Started :


Once you get OfficeServer.exe on your VM, make a folder in your C: Drive called “SharePointFiles” and keep your OfficeServer.exe file in there.
Open Command Prompt and extract the SharePoint Files from OfficeServer.exe to the SharePointFiles folder.
To do this, in the command prompt do the following :
 c:\SharePointFiles\OfficeServer /extract:c:\SharePointFiles
All files will now be extracted to SharePointFiles.
If you Run Setup.exe in SharePointFiles you might get error of Hardware and Software Requirements if everything required is not there.

Installing Prerequisities (Preparation Tool) :


Run the Prerequisite Installer(Preparation Tool) before running the Setup.exe which will check and install all the required softwares for Sharepoint 2010 on Windows Server 2008 SP2.
If at any given point, the Installation fails, you might want to check the log files to see what failed. Most of the time it might be that the installer was not able to download the required files. In this case you need to reset the network adapter. This can be done by going to Network and Sharing Center and clickin on View Status, then clicking on Diagnose. This might be happening only because of the fact that VM is using host internet connection and is not consistent.

Installing SharePoint Server 2010 :


Then run Setup.exe. Choose Standalone. Since you are installing it on a VM, you can run multiple servers to give you the farm effect (Still have to work on that).
If at any given point, the Installation fails, you might want to check the log files to see what failed. Most of the time it might be that the installer was not able to download the required files. In this case you need to reset the network adapter. This might be happening only because of the fact that VM is using host internet connection and is not consistent.
If everything is runs fine and your SharePoint Server 2010 installation is complete, you should be brought to the “Configuration Wizard” window. WAIT. You need to install the SQL Server 2008 KB Hotfixes before you continue.  Let the wizard stay as it is in the background.

Increase VHDD Size :


If you have given a VHDD size lesser than what is required to install SharePoint(which happened to me initially, I had given only 20 GB) the installer will stop and say configuration failed.
The ones which are already configured wont be rolled back, on restarting it will just continue from there.
To increase the VHDD space. I believe there are many softwares out there (e.g. CloneZilla).You can refer VirtualBox Forums (Storage) here :                 
I tried using clonevdi (has an option to increase vdi size while cloning, but didn’t work for me) and researched and came across Gparted. But going through all that is way too much effort and time consuming , instead just do the whole thing all over again with enough space. You can go through all that pain if you have installed a lot of stuff on your VHDD and cannot risk deleting it. (Here we have only installed an OS on a VM as yet).
This time make sure you have enough space on your host computer to dedicate atleast 80 GB for  VHDD (100GB to be safe).

Download and install SQL Server 2008 KB Hotfixes :


Before you continue on the wizard, you need to get the SQL Server 2008 KB Hotfixes which will be emailed to you with passwords. They can be found here : 
http://support.microsoft.com/kb/970315 (Click on View and request hotfix downloads)
Get these files on your VM and install them. There should be 2 hotfixes.

Completing Configuration Wizard :


You can now go ahead and complete the Configuration Wizard.
You will get to the website which you will asked to choose the template, You can choose what you like depending upon what you require it for or match it to your organization. I just made it Blank because I would like to custom build it.

 

Development Tools :


We are not done yet. To do any kind of development you will need to :
Download and install Visual Studio 2010 which can be found at: 
I installed Visual Studio 2010 Ultimate RC.
Download and install the Microsoft SharePoint Platform SDK which can be found at:

 

Taking Snapshot of VM:


Now that all the installation is done. You want to save this state of the VM so that you can always revert to this “Clean State”. VirtualBox allows you to do that by taking a snapshot of the VM.
Clear your desktop and click on “Machine” menu on the window which is running your VM. Click on “Take Snapshot”.
Enter this Snapshot’s name and description if you would like. Click OK.
You are all set and ready to play in the sandbox.
If at any point of time you would like to come back to the “Clean State” of the VM :
When the VM is “Powered Off”. Click on the Snapshots tab. You should see that it says “Snapshots(1)”. You can right click on the Snapshot name which you gave and click on “Restore Snapshot”.

You’re all set !