How to build a modern best practice enterprise application on-premise or in the cloud

May 24, 2012

This is a summary of the key points of my presentation at the Victoria.Net users group, held at Microsoft in Melbourne, Australia, with a few added points thrown in for extra value.

Developing large scale applications is hard, and never before have we been under so much pressure to be efficient with our time. It is imperitive that we spend as little time as we can on unproductive infrastructure code and as much of our time actually implementing the domain/business services logic.

For some reason, many enterprise architects promote leaving performance to the end of the development process. They decide on an architecture, get all their developers to build it, and then when it gets to the end of the development process, wonder why the application runs so slowly. They haven’t even done a basic performance test, to see if any of their assumptions were correct, and at the end of the process sometimes discover serious flaws in the architecture of their sites requiring considerable rework, and occasionally a complete rewrite is required.

In a previous post, I showed statistics produced by Watt’s Humphrey, known as the father of Software Engineering, where he shows the cost of discovering and rectifying defects in their respective project phases. The later you find defects in the development process, the more expensive they are to rectify, so if you wait to the end of a project before dealing with an issue that should have been rectified at the start of the project, it could cost you magnitudes more than if you at least have an attempt at dealing with performance from the start. You can find that post here.

Another issue with large scale web sites is configuration. Most configuration is found in web.config files or app.config files within a .net application. Within a web site, if you modify the web.config file, it resets the web server to refresh the configuration settings. One environment I worked in had a web farm that consisted of 8 web servers. Because of the use of web.config settings and also Enterprise Application Blocks, which heavily utilise configuration files, they were always running into problems with servers that have their configuration out-of-synch, and also have an unstable state during the update. And they still are.

So on Tuesday night, I showed a comprehensive end-to-end application that demonstrates an n-tier, service-oriented architecture that is considered best practice. It also allows on-premise, hybrid and cloud-based tiers. For people that are struggling with the whole “to cloud or not to cloud” arguments, this allows you to hedge your bets. If this point has been holding back project development, you don’t need to worry. This architecture allows you to build an on-premise application, then move it to the cloud at a later date, for very little extra cost, if there is a movement to do so.

The features are:

A configuration block. The configuration block is at the heart of the application. It enables you to centralise configuration for all the tiers of your application. It provides a web based portal for making modifications. When you make changes to configuration, those changes are automatically updated on the server.

Centralised exception handling and errors.

Centralised logging.

A service map. The service map shows a visual representation of all your tiers, where they are hosted, what instances are running, what databases you are running, and it even allows you to drill down into a database and see latency figures and the least performing and most CPU intensive queries. Using the service map, you are able to see any servers that become unavailable, for whatever reason. The architecture actually implements polling of all the component instances, so you’ll know pretty much straight away if any of the services aren’t available.

A large scale application project structure that you can use, separating User Interface, Business Services Logic and Database.

A database layer that is optimised for performance. This was at the heart of the performance tests against IBM WebSphere, so if there was a faster way of interacting with the database, I would be absolutely shocked. Microsoft has to be able to push as many transactions per second as possible through this system. They have to outperform IBM WebSphere, which they do dramatically. (IBM WebSphere is 6 times more expensive for the same grunt.)

A benchmarking tool that allows you to test your architectural choices. What happens with this architecture if you make the wrong choice? Nothing. The application has been designed so that you can change where your tiers are hosted, at runtime, on the fly! So you are protected against making the wrong choice.

I should point out that I demonstrated all of this. I started by running the application fully in-proc. Then I moved the business services layer to be IIS hosted. I was able to show this on the visual service map. Then I switched the application over to a business service layer hosted in the cloud. When I showed the service map, it looked awesome.

There are full instructions on how to set up the application. The installer only takes about 5 to 10 minutes to install everything. It is rich with documentation and it includes full instructions on how to configure Azure to have different tiers, how to install digital certificates, and ensure that all the communication channels are encrypted.

I mentioned that if you host a business services layer in Azure, then you can supply digital certificates to any company that you would like to use that service. So any company can integrate with the business services layer without you having to worry about them gaining access to your network.

I explained how the load balancing works. You can have as many servers you like in the web farm, and the requests are round-robined to each of the services.

I spoke about how to integrate the configuration block into your own application, and also demonstrated the Visual Studio template provided to generate the basic application layers, which include the configuration block.

The application itself was written by Gregory Leake. Gregory is the Technical Product Manager at Microsoft for the SQL Azure and Azure AppFabric teams. The application was first written around 2006, and is now at version 5. Thousands and thousands and thousands of hours have gone into thinking through the mass of scenarios that you might need to consider, and spent on coding this application. At the time of writing, this application has been evolving for 6 years. The application, including a working demonstration of the running Azure version, may be found here: http://msdn.microsoft.com/stocktrader

I guess my final message here is that in order to be more efficient in your coding, you need to simplify. Forget over complicated tricky designs. All that will happen is that they will be difficult to maintain. Spend as little time as you can on infrastructure code. If you can find a framework such as this that has all these goodies already provided, then embrace it. There is no way you can develop all this efficiently in short timeframes, and why would you want to anyway? It’s just reinventing the wheel. Take it on and use it within your environment. Then spend most of your time building your application, and not the infrastructure to support it.


How to use ASP.Net Membership and Role providers with ADLDS / ADAM, with Code

February 26, 2010

There are scenarios as a developer where you need to connect to and use Active Directory from your ASP.Net or Silverlight application. For whatever reason, being able to immediately connect to your corporate domain may be difficult. Rather than let this hold you up, there is another possibility – you can install ADAM and configure it as a membership and role repository for your applications. ADAM (otherwise known as Active Directory Lightweight Directory Services), is a cut down version of Active Directory, meant specifically for Applications. It can be installed on your local machine or on a server somewhere, and enable you to get up and running quickly.

The first thing you need to do is install ADAM. On a Windows XP SP1, or Windows 2003 Server, you need to download and install ADAM from here: http://www.microsoft.com/downloads/details.aspx?familyid=9688f8b9-1034-4ef6-a3e5-2a2a57b5c8e4&displaylang=en

Then you follow the instructions from here to get the Membership Provider working:

http://erlend.oftedal.no/blog/?blogid=6

From Windows 2003 R2 and later, you need to go install the Active Directory Lightweight Directory Services role from the Server Manager. On later versions of windows (Windows 2008 R2) you can run the ADLSD Setup Wizard to enable you to install instances. Try to follow the instructions at Erlend’s blog above.

After following Erlend’s instructions, you should be able to click on the ASP.Net Configuration menu item in Visual Studio and use the Web Administration Tool to add users to ADAM.

Next you will want to configure ADAM to be used as a Role Provider. All the documentation talks about this separately from being used as a Membership provider, and you wouldn’t be wrong in forming the conclusion that it should just work, however, the reality is that to have both the users and the roles in the one ADAM instance actually requires you to write a custom role provider. We’ll get to that in a moment. Firstly, you need to follow the instructions to install the Authorization Manager. Those instructions are here:
How To: Use ADAM for Roles in ASP.NET 2.0
Make sure you install the Authorization Manager (AzMan) in the same instance as your users.

Now the best way to obtain a custom role provider is to find one that has already been written. I tried this, but didn’t succeed. The closest I could come to was an Active Directory Role Provider that I found on CodePlex. Everything else just wasn’t good enough or didn’t work. But I downloaded the Active Directory Provider so that I can switch over to Active Directory when I am finished my development and ADAM is no longer desired.

The Active Directory Role Provider may be found here: http://adroleprovider.codeplex.com/releases/view/34407

I found instructions on Erlend’s site on how to construct an ADAM role provider, but no code. So I have written some code to do this, which you can find here. I downloaded the code from codeplex and added it to a separate project, then I added my own code to that project. Basically, this implements pretty much everything you need to install an Authorization Provider for ADAM that works with a user store also in the same ADAM instance. You cannot get this working otherwise.

Here is the section of my web config containing references to these providers:

<membership defaultProvider="AspNetActiveDirectoryMembershipProvider">
      <providers>
 <clear/>
        <add name="AspNetActiveDirectoryMembershipProvider" 
type="CT.ActiveDirectoryMembershipProvider" connectionStringName="ADService" 
connectionUsername="CN=MyAppAdmin,OU=Users,OU=Auth,O=App,DC=MEGACORP,DC=COM" 
connectionPassword="123456." connectionProtection="None" enableSearchMethods="true"/>
      </providers>
</membership>
<profile>
      <providers>
        <clear/>
        <add name="AspNetSqlProfileProvider" connectionStringName="DEVELOPMENT" 
applicationName="/MegaCorp.App.Web" type="System.Web.Profile.SqlProfileProvider, 
System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"/>
      </providers>
      <properties>
        <add name="FriendlyName"/>
        <add name="Firstname"/>
        <add name="Surname"/>
        <add name="Company"/>
        <add name="Phone"/>
        <add name="MobilePhone"/>
        <add name="Address"/>
      </properties>
</profile>
<roleManager enabled="true" cacheRolesInCookie="true" 
defaultProvider="RoleManagerAzManADAMProvider">
       <providers>
          <add connectionStringName="AzManADAMServer" applicationName="MegaCorp.App"
             name="RoleManagerAzManADAMProvider" type="CT.AzManRoleProvider" />
       </providers>
</roleManager>

<connectionStrings>
    <add name="ADService" connectionString="LDAP://localhost:389/OU=Users,OU=Auth,O=App,DC=MEGACORP,DC=COM"/>
    <add name="AzManADAMServer" connectionString="msldap://localhost:389/CN=AzManADAMStore,OU=Auth,O=App,DC=MEGACORP,DC=COM"/>
</connectionStrings>

Before I continue, I should mention that I slipped in the Sql Profile Provider. This is because I am developing a Silverlight 3 application, and the Silverlight Authentication service requires some of these fields to be supplied. So I have installed the aspnet database just so I can get access to the provider table. I also had to change the User Id in the Silverlight Authentication service from a Guid to a string so it could handle the entire user SID.

Now, I was able to run the ASP.Net Web Administration Tool and successfully add roles, add users, and then add users to roles. I did discover one bug in the Web Administration Tool that caused an exception when I clicked the IsActive check box. This exception is caused when the resultant user object is deserialised to be sent over the wire to the application. The good news is, by the time this has happened, all the work has been done, and the error appears to have no other consequences.

Therefore, I decided to swallow up the exception from within the Web Administration Tool. Hey, most of you don’t use the tool anyway, prefering to re-invent the entire application administration.

To do this, you need to go to manageUsers.aspx in C:\Windows\Microsoft.NET\Framework\v2.0.50727\ASP.NETWebAdminFiles\Security\Users
or for 64-bit machines,
C:\Windows\Microsoft.NET\Framework64\v2.0.50727\ASP.NETWebAdminFiles\Security\Users
and wrap a try/catch block around the code causing the exception.

public void EnabledChanged(object sender, EventArgs e) 
{
	CheckBox checkBox = (CheckBox) sender;
	GridViewRow item = (GridViewRow)checkBox.Parent.Parent;
	Label label = (Label) item.FindControl("UserNameLink");
	string userID = label.Text;
	MembershipUser user = (MembershipUser) CallWebAdminHelperMethod(true, "GetUser", new 
	object[] {userID, false /* isOnline */}, new Type[] {typeof(string),typeof(bool)});
	user.IsApproved = checkBox.Checked;

	string typeFullName = "System.Web.Security.MembershipUser, " + typeof
	(HttpContext).Assembly.GetName().ToString();;
	Type tempType = Type.GetType(typeFullName);

	try
	{
		CallWebAdminHelperMethod(true, "UpdateUser", new object[] {(MembershipUser) user}, new 
		Type[] {tempType});
	}
	catch
	{
		//catch any exception occuring here. In most cases this is pure evil.
	}
}

If you want to see the actual source code of the AzMan Role Provider I created, I have added it in below:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Interop.Security.AzRoles;
using System.Web.Security;
using System.Diagnostics;
using System.Collections;

namespace CT
{
    public sealed class AzManRoleProvider : RoleProvider
    {
        public override bool IsUserInRole(string username, string roleName)
        {
            var users = GetUsersInRole(roleName);
            foreach (string user in users)
            {
                if (user.Equals(username, StringComparison.OrdinalIgnoreCase))
                    return true;
            }
            return false;
        }

        private string connectionStringName;
        public string ConnectionStringName
        {
            get
            {
                if (this.connectionStringName == null)
                {
                    this.connectionStringName = "msldap://localhost:389/CN=AzManADAMStore,OU=Auth,O=SCP,DC=MEGACORP";
                }
                return this.connectionStringName;
            }
            set
            {
                this.connectionStringName = value;
            }
        }

        public IAzApplication2 ConnectToAzManApplication()
        {
            var _azStore = new AzAuthorizationStoreClass();
            _azStore.Initialize(0, this.ConnectionStringName, null);
            var _azApp = _azStore.OpenApplication2(this.ApplicationName, null);
            return _azApp;
        }

        public override string[] GetRolesForUser(string username)
        {
            var _azApp = ConnectToAzManApplication();
            var user = Membership.GetUser(username);
            var context = _azApp.InitializeClientContextFromStringSid(user.ProviderUserKey.ToString(), 1, null);
            var roles = context.GetRoles("") as object[];
            return Array.ConvertAll(roles, new Converter<object, string>(AzManRoleProvider.GetRoleStringRepresentation));
        }

        public static string GetRoleStringRepresentation(object role)
        {
            return role.ToString();
        }

        public override void CreateRole(string roleName)
        {
            var _azApp = ConnectToAzManApplication();
            var task = _azApp.CreateTask(roleName, null);
            task.IsRoleDefinition = 1;
            task.Submit(0, null);

            var role = _azApp.CreateRole(roleName, null);
            role.AddTask(roleName, null);
            role.Submit(0, null);
        }

        public override bool DeleteRole(string roleName, bool throwOnPopulatedRole)
        {
            var _azApp = ConnectToAzManApplication();
            _azApp.DeleteRole(roleName, null);
            _azApp.DeleteTask(roleName, null);
            return true;
        }

        public override bool RoleExists(string roleName)
        {
            var _azApp = ConnectToAzManApplication();
            foreach (IAzRole role in _azApp.Roles)
            {
                if (role.Name == roleName)
                {
                    return true;
                }
            }
            return false;
        }

        public override void AddUsersToRoles(string[] usernames, string[] roleNames)
        {
            var _azApp = ConnectToAzManApplication();
            foreach (var userName in usernames)
            {
                foreach (var roleName in roleNames)
                {
                    var currentUser = Membership.GetUser(userName);
                    var role = _azApp.OpenRole(roleName, null);
                    role.AddMember(currentUser.ProviderUserKey.ToString(), null);
                    role.Submit(0, null);
                }
            }
        }

        public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames)
        {
            var _azApp = ConnectToAzManApplication();
            foreach (var userName in usernames)
            {
                foreach (var roleName in roleNames)
                {
                    var currentUser = Membership.GetUser(userName);
                    var role = _azApp.OpenRole(roleName, null);
                    role.DeleteMember(currentUser.ProviderUserKey.ToString(), null);
                    role.Submit(0, null);
                }
            }
        }

        public override string[] GetAllRoles()
        {
            var _azApp = ConnectToAzManApplication();
            List<string> rolesList = new List<string>();
            foreach (IAzRole role in _azApp.Roles)
            {
                rolesList.Add(role.Name);
            }
            return rolesList.ToArray();
        }

        private string applicationName;
        public override string ApplicationName
        {
            get
            {
                if (applicationName == null) applicationName = "MegaCorp.App";
                return applicationName;
            }
            set
            {
                applicationName = value;
            }
        }

        public void CreateApplicationGroup(string groupName)
        {
            var _azApp = ConnectToAzManApplication();
            _azApp.CreateApplicationGroup(groupName, null);
        }


        public override string[] FindUsersInRole(string roleName, string usernameToMatch)
        {
            throw new NotImplementedException();

        }

        Converter<object, string> newConverter = new Converter<object, string>(Convert.ToString);

        public override string[] GetUsersInRole(string roleName)
        {
            List<string> userList = new List<string>();
            var _azApp = ConnectToAzManApplication();
            var role = _azApp.OpenRole(roleName, null);

            string[] members = Array.ConvertAll<object, string>((object[])role.Members, newConverter);
            foreach (string memberProviderKey in members)
            {
                System.Security.Principal.SecurityIdentifier sid = new System.Security.Principal.SecurityIdentifier(memberProviderKey);
                var user = Membership.GetUser(sid);
                userList.Add(user.UserName);
            }
            return userList.ToArray();
        }
    }
}

Note that you’ll need a reference to Microsoft.Interop.Security.AzRoles to be able to compile that code. On earlier systems, such as XP, you’ll need to download the Windows 2000 Authorization Manager runtime from here: http://www.microsoft.com/downloads/details.aspx?FamilyID=7edde11f-bcea-4773-a292-84525f23baf7&DisplayLang=en

This is actually referenced in the article: How To: Use ADAM for Roles in ASP.NET 2.0 found above. You need to install the Authorization Manager runtime, then find the assembly on your system, add it to the GAC, and reference the original assembly from within you web application.


How to Enumerate Strings using Resource Files in C#

February 14, 2010

Strings are not valid types within enumerations. So the coder usually has to create a whole bunch of constant string values and simply have them grouped together within their classes. It’s not very desirable for a few reasons. For starters, there is no enforcement that a string value has to belong to a particular class, and also, they are much harder to read in from resource files.

Enumerations are an excellent way to make code more reliable, readable and modifiable. They are a special case, where every value of a variable is already known, and you want to be able to describe them in a more readable way. They allow each value to be grouped together so that there is a reduced likelihood that an incorrect value is specified in code.

For example,

public enum Flowers
{
   Rose,
   Tulip,
   Daisy,
   Daffodil
}

If you declare a variable of type Flowers, then you know that only Rose, Tulip, Daisy, Daffodil can be selected.

To do the equivalent with strings would require, perhaps, a class

public class Vehicles
{
    public const string AlfaRomeo = "Alfa Romeo";
    public const string MercedesBenz = "Mercedes Benz";
    public const string Ford = "Ford";
    public const string Porche = "Porche";
    public const string AstonMartin = "Aston Martin";
}

But there is nothing to prevent you from using a string from the wrong class in your code. If you declare a variable for the car

string myCar;

There is nothing to prevent setting the value to something other than what exists in the class Cars.

myCar = "Honda";

Another major benefit of enumeration is that non-domain errors are caught at compile time, not runtime. Finding errors at compile time is significantly cheaper to resolve than finding the error after it has gone live. So it is highly desirable to find these sorts of errors as early as possible in the process.

And it can be done!

I will demonstrate how to better group your strings, and also to treat those strings as enumerations. Here, I will use attributes and reflection to retrieve the values from resource files.

So to start with, create a new string resources file. I have called mine MessageResources. Add a few strings to the resource file. I have added mine as follows:

Message Resources File containing Vehicle strings

Note that I have deliberately chosen names that include spaces. When you use the ToString() method on a standard enumeration, expecting to see their values, it is significantly harder to use those values in, say, a combo box, if they don’t have spaces in them.

Now let me skip to the enumeration itself.

Start with a typical enumeration:

public enum Vehicles
{
    AlfaRomeo,
    MercedesBenz,
    Ford,
    Porche,
    AstonMartin
}

Of course, you need to link this to the resource file, so we’ll do that via attributes. So I will create an attribute for the Enumeration, as follows:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace EnumHelperSample
{
    public class EnumAttribute : Attribute
    {
        public Type ResourceType { get; private set; }
        public string ResourceName { get; private set; }
        public int SortOrder { get; private set; }

        public EnumAttribute(Type ResourceType,
                             string ResourceName,
                             int SortOrder)
        {
            this.ResourceType = ResourceType;
            this.ResourceName = ResourceName;
            this.SortOrder = SortOrder;
        }
    }
}

I have decided to include the type of the resource file that contains the strings, the name of the string resource, and the order that I would like the enumerations to be sorted if returned as a list of strings.

However, this won’t work. If you have a look in the MessageResources.Designer.cs file, this generates the following code:


/// <summary>
///   Looks up a localized string similar to Alfa Romeo.
/// </summary>
internal static string AlfaRomeo {
   get {
       return ResourceManager
              .GetString("AlfaRomeo", resourceCulture);
   }
}

For a value to be passed via an attribute, it needs to be a constant, and the MessageResources generated code does not provide a constant string.

Enter Dmytro Kryvko and the ResXFileCodeGeneratorEx Custom Tool.

Dmytro has replaced the custom tool found on the standard Resources file with a new custom tool that he wrote. This tool generates a constant string for every resource name that is generated within the resource file.

You can download it from here:
http://www.codeproject.com/KB/dotnet/ResXFileCodeGeneratorEx.aspx

If you really want to go and improve his code, by all means, go ahead. For our purposes, it will be fine simply to run the installer.

After installing this on your machine, you select properties on the resources file, and change the custom tool to the extended version, ending in Ex.

Changing Message Resources Custom Tool to ResXFileCodeGeneratorEx

Now we can pass the resource names as constants.

The enumeration code then becomes:

namespace EnumHelperSample
{
    public enum Vehicles
    {
        [EnumAttribute(typeof(MessageResources),
                       MessageResources.ResourceNames.AlfaRomeo,
                       1)]
        AlfaRomeo,
        [EnumAttribute(typeof(MessageResources),
                       MessageResources.ResourceNames.MercedesBenz,
                       4)]
        MercedesBenz,
        [EnumAttribute(typeof(MessageResources),
                       MessageResources.ResourceNames.Ford,
                       3)]
        Ford,
        [EnumAttribute(typeof(MessageResources),
                       MessageResources.ResourceNames.Porche,
                       5)]
        Porche,
        [EnumAttribute(typeof(MessageResources),
                       MessageResources.ResourceNames.AstonMartin,
                       2)]
        AstonMartin
    }

}

Ok, that’s all very well, but how do we retrieve the values?

Well, I have written an EnumHelper class as a set of extensions to retrieve those values.

This first one is to retrieve a value from the associated resource. I do this by extending the Enum type. It allows me to write the following syntax:

Debug.WriteLine(Vehicles.AlfaRomeo.GetString());
Debug.WriteLine(Vehicles.MercedesBenz.GetString());

This outputs:

Alfa Romeo
Mercedes Benz

public static string GetString(this Enum value)
{
    EnumAttribute ea =
               (EnumAttribute)value.GetType()
                .GetField(value.ToString())
                .GetCustomAttributes(typeof(EnumAttribute), false)
                .FirstOrDefault();
     if (ea != null)
     {
           PropertyInfo pi = ea.ResourceType
                               .GetProperty("ResourceManager");
            if (pi != null)
            {
                ResourceManager rm = (ResourceManager)pi
                                     .GetValue(null, null);
                return rm.GetString(ea.ResourceName);
            }
     }

     return string.Empty;
}

And finally to retrieve the entire sorted list, I extended Type itself, as follows:

public static IList GetStrings(this Type enumType)
{
    SortedList<int, string> stringList = new SortedList<int, string>();
    FieldInfo[] fiArray = enumType.GetFields();
    foreach (FieldInfo fi in fiArray)
    {
        EnumAttribute ea =
            (EnumAttribute)fi
                 .GetCustomAttributes(typeof(EnumAttribute), false)
                 .FirstOrDefault();
         if (ea != null)
         {
              PropertyInfo pi = ea.ResourceType
                                  .GetProperty("ResourceManager");
              if (pi != null)
              {
                  ResourceManager rm = (ResourceManager)pi
                                        .GetValue(null, null);
                  stringList.Add(ea.SortOrder,
                                 rm.GetString(ea.ResourceName));
              }
        }
    }
    return stringList.ToList();
}

So to display the entire sorted list of values:

IList result = typeof(Vehicles).GetStrings();
foreach (KeyValuePair<int,string> kvp in result)
{
    Debug.WriteLine(kvp.Value);
}

Finally, a note. Allowing the enumerations to be specified via Resource Name constants passed as attributes still requires the use of strings. This means other string constants could be passed in to the attribute types.

I believe that the likelihood of this occurring is significantly less of a risk than if the developer was loading up and passing around string constants themselves. And anyway, you’ll need to know which resource file that the strings are coming from. If you are that worried about it, you could always modify Dmytro’s code!

Meanwhile, my code may be found here:

EnumHelperSample.zip


How to create an HTML table with frozen headers and columns

May 30, 2009

I have moved this article to a new blog location at my new company web site. The article may be found here: How to create an HTML table with frozen headers and columns


How to Style the Ajax Control Toolkit Calendar Extender control

May 9, 2009

I have moved this article to a new blog location at my new company web site. The article may be found here: How to Style the Ajax Control Toolkit Calendar Extender control


How to add a close button to the Ajax Control Toolkit Calendar Extender control

May 9, 2009

In this series of educational articles, I have been explaining how to enhancing the Ajax Control Toolkit Calendar Extender control to support a number of different modes, such as Day, Month, Quarter, Year, Decade. I have also introduced implemented the InitialView property, which allows you to select an initial mode that you want the calendar to display in. These features may be used to reduce the number of clicks required to select a birthdate from 9 to 4, which is a productivity improvement for anyone seriously using the calendar for data entry purposes

These are the articles so far:

How to patch the Ajax Control Toolkit CalendarExtender to add Decade support and InitialView – Part 1 

How to patch the Ajax Control Toolkit Calendar Extender control to show a Quarter view

How to patch the Ajax Control Toolkit Calendar Extender control to show a Day, Month, MonthYearDecade, Quarter, YearDecade and Decade view

The code for the starting point for these modifications is found here:

http://cid-5e237543fffb2891.skydrive.live.com/self.aspx/Public/AjaxControlToolkit-Framework3.5SP1withDecadeYearQuarterMonthDayandInitialView.zip

Unzip this code and place it in your projects folder. All code here is supplied under the Microsoft Public Licence, found here: http://ajaxcontroltoolkit.codeplex.com/license

The code:

To start with, you’ll need a Close Button. The close button is 15 pixels by 15 pixels. I have created one based on the Internet Explorer X button used to stop web pages from executing. You can download my image by right clicking this image and saving it onto your local drive:

Close Button for Ajax Control Toolkit calendar extender control

This close button image is called close-button.gif (note that it’s a dash, not an underscore).

Copy this image into the Calendar folder in the Ajax Control Toolkit, and set it’s Build Action to Embedded Resource. This is important, as without it, the image is not contained within the Ajax Control Toolkit dll.

Adding the close button image

Adding the close button image

Next, you need to tell the compiler about the image by making it a Web Resource. This is done by adding an entry at the top of the CalendarExtender.cs file, as follows. Note that I am using S5/E5 to tag changes.

Original code:


#region [ Resources ]

[assembly: System.Web.UI.WebResource("AjaxControlToolkit.Calendar.CalendarBehavior.js", "text/javascript")]
[assembly: System.Web.UI.WebResource("AjaxControlToolkit.Calendar.Calendar.css", "text/css", PerformSubstitution = true)]
[assembly: System.Web.UI.WebResource("AjaxControlToolkit.Calendar.arrow-left.gif", "image/gif")]
[assembly: System.Web.UI.WebResource("AjaxControlToolkit.Calendar.arrow-right.gif", "image/gif")]
#endregion

Modified code:


#region [ Resources ]

[assembly: System.Web.UI.WebResource("AjaxControlToolkit.Calendar.CalendarBehavior.js", "text/javascript")]
[assembly: System.Web.UI.WebResource("AjaxControlToolkit.Calendar.Calendar.css", "text/css", PerformSubstitution = true)]
[assembly: System.Web.UI.WebResource("AjaxControlToolkit.Calendar.arrow-left.gif", "image/gif")]
[assembly: System.Web.UI.WebResource("AjaxControlToolkit.Calendar.arrow-right.gif", "image/gif")]
//S5
[assembly: System.Web.UI.WebResource("AjaxControlToolkit.Calendar.close-button.gif", "image/gif")]
//E5
endregion

Next, we want to add a property to the Calendar Extender to allow us to turn the close button display on or off – not everyone wants a close button on their calendar. Most people are probably content with clicking off the calendar. I will add a property called ShowCloseButton to the extender control. In the CalendarExtender.cs file, I copy the EnabledOnClient property and paste it to the location next to it. Then I rename the variables to be consistent with the property I want.

Code added under EnabledOnClient property:


//S5
[DefaultValue(false)]
[ExtenderControlProperty]
[ClientPropertyName("showCloseButton")]
public virtual bool ShowCloseButton
{
    get { return GetPropertyValue("ShowCloseButton", false); }
    set { SetPropertyValue("ShowCloseButton", value); }
}
//E5

The ClientPropertyName attribute contains the client-side javascript variable name. So next we go into the CalendarBehavior.js file and add in a couple of javascript variables. The first is the showCloseButton variable, shown above, and the second is the placeholder variable for the close button html content that will be created when the close button is required to be displayed. This code goes in the declarations section of the CalendarBehavior.js file. I put mine just below the declaration for this._nextArrow = null;


/*S5*/
this._showCloseButton = false;
this._closeButton = null;
/*E5*/

Add the getter and setter next. I put mine just below the set_enabled function block.


//S5
get_showCloseButton: function() {
    /// &amp;amp;amp;amp;lt;value type="Boolean"&amp;amp;amp;amp;gt;
    /// Whether this behavior is available for the current element
    /// &amp;amp;amp;amp;lt;/value&amp;amp;amp;amp;gt;

    return this._showCloseButton;
},
set_showCloseButton: function(value) {
    if (this._showCloseButton != value) {
        this._showCloseButton = value;
        this.invalidate();
        this.raisePropertyChanged("showCloseButton");
    }
},
//E5

The function that builds the calendar in the first place is called _buildHeader. This is where we need to add code to test if the close button is supposed to display, create the div that contains the image, and attach the event handlers. This is also where the stylesheet class is attached to the div. The image itself is referenced from that stylesheet class, called ajax__calendar_close.


/*S5 - because the close button is floated right, it needs to go before the nextArrow div as that is
also floated right, and right floating reverses the order of display*/
if (this._showCloseButton) {
    var closeButtonWrapper = $common.createElementFromTemplate({ nodeName: "div" }, this._header);
    this._closeButton = $common.createElementFromTemplate({
        nodeName: "div",
        properties: {
            id: id + "_closeButton",
            mode: "close"
        },
        events: this._cell$delegates,
        cssClasses: ["ajax__calendar_close"]
    }, closeButtonWrapper);
}
/*E5*/

 

 Note in that code the reference to the mode property. That is the string that gets passed into the _cell_onclick event handler. So on clicking the close button image, we want the calendar to execute the same code that is executed when the Escape button is clicked. So I had a look in the _button_onkeypress function and saw the code that is being executed when the Escape key is pressed. I will copy that code into the _cell_onclick function, and add a case for the ‘close’ mode, as follows:

Original code:


case "prev":
case "next":
    this._switchMonth(target.date);
    break;

Modified code:


case "prev":
case "next":
    this._switchMonth(target.date);
    break;
/*S5*/
case "close":
    e.stopPropagation();
    e.preventDefault();
    this.hide();
    break;
/*E5*/

Finally, you need to dispose of the close button content correctly. In the dispose function, add the following modifications:

Original code:

if (this._nextArrow) {
    $common.removeHandlers(this._nextArrow, this._cell$delegates);
    this._nextArrow = null;
}

Modified code:

if (this._nextArrow) {
    $common.removeHandlers(this._nextArrow, this._cell$delegates);
    this._nextArrow = null;
}
/*S5*/
if (this._showCloseButton &amp;amp;amp;amp;amp;&amp;amp;amp;amp;amp; this._closeButton) {
    $common.removeHandlers(this._closeButton, this._cell$delegates);
    this._closeButton = null;
}
/*E5*/

Finally, you need to add the calendar extender css class for the close button. Open the Calendar.css file, and just below the entry for .ajax__calendar_next, add the following line:


/*S5*/
.ajax__calendar_close {cursor:pointer;width:15px;height:15px;float:right;background-repeat:no-repeat;background-position:50% 50%;background-image:url(&amp;amp;amp;amp;lt;%=WebResource("AjaxControlToolkit.Calendar.close-button.gif")%&amp;amp;amp;amp;gt;);}
/*E5*/

 

Run it up, add a calendar extender, set it’s ShowCloseButton property to true, and this is what you see:

Calendar Close Button Image

Calendar Close Button Image

Note that I have deliberatelychosen a non-intrusive close button, the same size as the prev and next buttons. The close button just needs to be there – it doesn’t need to out-weigh the other elements on the calendar. I used to have a bright red button, but that is so 20th century.

If you would like to see if you’ve got the code right, I have a completed version to compare with here:

AjaxControlToolkit3.5SP1withModesAndCloseButton.zip


How to patch the Ajax Control Toolkit Calendar Extender control to show a Day, Month, MonthYearDecade, Quarter, YearDecade and Decade view

April 23, 2009

In this series of educational articles, I am demonstrating how you might enhance the Calendar Extender control found in the publicly available Ajax Control Toolkit. All code here is an extension of my previous work, first of which was to enhance the Ajax Control Toolkit Calendar Extender so that it could be better utilised to select a birthdate. It introduced the decade view, and also allowed the setting of a initial view of the control, which can be Day, Month, Year and Decade. That article is available here: https://tonesdotnetblog.wordpress.com/2009/04/15/how-to-patch-the-ajax-control-toolkit-calendarextender-to-add-decade-support-and-initialview-part-1/

Secondly, I enhanced the calendar extender control so that it showed a quarter, and quarter/year/decade view. That article is available here: https://tonesdotnetblog.wordpress.com/2009/04/17/how-to-patch-the-ajax-control-toolkit-calendar-extender-control-to-show-a-quarter-view/

You can download the code for the latest version (20820) of the Ajax Control Toolkit from here: http://www.codeplex.com/AjaxControlToolkit.

My enhanced decade, initial view and quarter view source code can be found here: http://cid-5e237543fffb2891.skydrive.live.com/self.aspx/Public/AjaxControlToolkit-DecadeQuarterInitialView.zip This enhancement is an extension of that control, so you’ll need to download that code to start this exercise.

All code here is published under the Microsoft Public Licence, found here: http://ajaxcontroltoolkit.codeplex.com/license

What we are trying to achieve:

As long as computers have been around, there have been scenarios where selecting a date, a month, a year or some other timeframe has been necessary.  Zhi-Qiang Ni, from Microsoft, wrote a modification to display a month view of the control, so that a month and year can be selected using the Ajax Control Toolkit. I have implemented Zhi-Qiang’s version, which may be found here: https://tonesdotnetblog.wordpress.com/2009/04/21/how-to-create-a-month-view-with-the-ajax-control-toolkit-calendar-extender-control/

In this situtation, I extend my existing enhancement to select a month, or a month and a year, or a year or even a decade. I have also added a few extra modes, where I hide the title and today bar under certain conditions, for example when you want the user to select a month only.

Selection of a Decade:

Decade View in the Ajax Control Toolkit Calendar Extender control

Decade View in the Ajax Control Toolkit Calendar Extender control

Selection of a Month:

Month Selection

Month Selection

Selection of a Month and Year:

Month Year Selection

Month Year Selection

The code:

Ok, the first thing to do is to modify the already existing CalendarView class, found in the Calendar folder in the Ajax Control Toolkit source code. I added this class in the previous article, which enhanced the Calendar Extender to show a quarter view. I will be using tags S4/E4 for this enhancement. Any other tags are from previous enhancements and should only be changed if it is required by S4.

Original code:


//S3 - Add Calendar View so that you can choose the calendar view for your date selection.
//Choosing a calendar view will close the calendar at the bottom view and return the selected date/value

namespace AjaxControlToolkit
{
    /// <summary>
    /// The calendar view of the calendar
    /// </summary>
    public enum CalendarView
    {
        DayMonthYearDecade = 0,   //the default view.
        Quarter = 1,              //shows Quarter selection but not Year or Decade.
        QuarterYearDecade = 2   //shows Quarter Year Decade.
    }
}
//E3

Modified code:


//S3 - Add Calendar View so that you can choose the calendar view for your date selection.
//Choosing a calendar view will close the calendar at the bottom view and return the selected date/value

namespace AjaxControlToolkit
{
    /// <summary>
    /// The calendar view of the calendar
    /// </summary>
    public enum CalendarView
    {
        DayMonthYearDecade = 0,   //the default view.
        Quarter = 1,              //shows Quarter selection but not Year or Decade.
//S4 - add in Month, MonthYearDecade, YearDecade and Decade views
//        QuarterYearDecade = 2   //shows Quarter Year Decade.
        QuarterYearDecade = 2,    //shows Quarter Year Decade.
        Month = 3,                //shows Month only.
        MonthYearDecade = 4,      //shows Month Year Decade.
        YearDecade = 5,           //shows Year Decade
        Decade = 6                //shows Decade only
//E4
    }
}
//E3

 Next, we need to make the changes to the enumeration within the javascript to support the new values. In the CalendarBehavior.js file, make the following changes to bring the two into line. The code is found at the bottom of the CalendarBehavior.js file.

Original code:

AjaxControlToolkit.CalendarView.prototype = {
    DayMonthYearDecade: 0,
    Quarter: 1,
    QuarterYearDecade: 2
}

Modified code:

AjaxControlToolkit.CalendarView.prototype = {
    DayMonthYearDecade: 0,
    Quarter: 1,
    //S4 add variables to be consistent with server side code
    //    QuarterYearDecade: 2
    QuarterYearDecade: 2,
    Month: 3,
    MonthYearDecade: 4,
    YearDecade: 5,
    Decade: 6
    //E4
}

Within the “show” function, we need to introduce new entry points for these views. Below the case statement within show for switching modes for QuarterYearDecade, add the following lines of code, which handle initial view for each of the types, and also switch to the correct modes based on the selected calendar view.

New code:

//S4 introduce new entry points                      
case AjaxControlToolkit.CalendarView.Month:
    this._switchMode("months", true);
    break;
case AjaxControlToolkit.CalendarView.MonthYearDecade:
    if (this._selectedDate == null) {
        switch (_initialView) {
            //case AjaxControlToolkit.InitialView.Quarter:     
            //    this._switchMode("quarters", true);     
            //    break;     
            case AjaxControlToolkit.InitialView.Year:
                this._switchMode("years", true);
                break;
            case AjaxControlToolkit.InitialView.Decade:
                this._switchMode("decades", true);
                break;
            default:
                this._switchMode("months", true);
                break;
        }
    }
    else {
        this._switchMode("months", true);
    }
    break;
case AjaxControlToolkit.CalendarView.YearDecade:
    if (this._selectedDate == null && _initialView == AjaxControlToolkit.InitialView.Decade) {
        this._switchMode("decades", true);
    }
    else {
        this._switchMode("years", true);
    }
    break;
case AjaxControlToolkit.CalendarView.Decade:
    this._switchMode("decades", true);
    break;
//E4                         

Now we want to hide the title display if the mode is Month. The title allows us to show the year and we don’t want that for month only view.

 Original code:

//S3 Where we don't want the title to display, we simply hide it
//this is only the case if you only want to select a value at the current mode
        if (this._calendarView == AjaxControlToolkit.CalendarView.Quarter)
            this._header.style.display = 'none';
//E3

 Modified code:

//S3 Where we don't want the title to display, we simply hide it
//this is only the case if you only want to select a value at the current mode
//S4 hide for Month and Quarter only selection. Year allows clicking decade view, I haven't created
//a year only view and you'll need to switch between decades
//        if (this._calendarView == AjaxControlToolkit.CalendarView.Quarter)
//            this._header.style.display = 'none';
if ((this._calendarView == AjaxControlToolkit.CalendarView.Quarter)
    || (this._calendarView == AjaxControlToolkit.CalendarView.Month))
    this._header.style.display = 'none';
//E4
//E3

Finally, in the _cell_onclick, modify the code to return the date on cell selection. This needs to happen if the mode is “month” and the CalendarView is set to a month type, the mode is “year” and the CalendarView is a year type, or the mode is “decade” and the CalendarView is a decade type. I refactored this code in Zhi-Qiang’s example, and now I’ve had to refactor it again because of all the extra modes added.

Original code:

_cell_onclick: function(e) {
    /// <summary>
    /// Handles the click event of a cell
    /// </summary>
    ///
<param name="e" type="Sys.UI.DomEvent">The arguments for the event</param>

    e.stopPropagation();
    e.preventDefault();

    if (!this._enabled) return;

    var target = e.target;
    var visibleDate = this._getEffectiveVisibleDate();
    Sys.UI.DomElement.removeCssClass(target.parentNode, "ajax__calendar_hover");

    //S3 - now to return the date. If the CalendarView is Quarter or QuarterYear and the mode returned is
    //quarter then select the date, close the calendar and return
    var calendarView = this.get_calendarView();
    if (target.mode == "quarter" && (calendarView == AjaxControlToolkit.CalendarView.Quarter
      || calendarView == AjaxControlToolkit.CalendarView.QuarterYearDecade)) {
    {
        this.set_selectedDate(target.date);
        this._blur.post(true);
        this.raiseDateSelectionChanged();
        return;
    }
    //E3

Modified code:

_cell_onclick: function(e) {
    /// <summary>
    /// Handles the click event of a cell
    /// </summary>
    ///
<param name="e" type="Sys.UI.DomEvent">The arguments for the event</param>
    e.stopPropagation();
    e.preventDefault();
    if (!this._enabled) return;
    var target = e.target;
    var visibleDate = this._getEffectiveVisibleDate();
    Sys.UI.DomElement.removeCssClass(target.parentNode, "ajax__calendar_hover");
    var calendarView = this.get_calendarView();
    //S4 introduce new modes selected. Returns when the mode and view matches
    if ((target.mode == "quarter" && (calendarView == AjaxControlToolkit.CalendarView.Quarter
                                   || calendarView == AjaxControlToolkit.CalendarView.QuarterYearDecade))
      || (target.mode == "month" && (calendarView == AjaxControlToolkit.CalendarView.Month
                                  || calendarView == AjaxControlToolkit.CalendarView.MonthYearDecade))
      || (target.mode == "year" && (calendarView == AjaxControlToolkit.CalendarView.YearDecade))
      || (target.mode == "decade" && (calendarView == AjaxControlToolkit.CalendarView.Decade))
      || (target.mode == "today")
      || (target.mode == "day")
       ) {
        this._closeCalendar(target);
        return;
    }
    switch (target.mode) {
        case "month":
            if (target.month != visibleDate.getMonth()) {
                this._visibleDate = target.date;
            }
            this._switchMode("days");
            break;
        case "year":
            if (calendarView != AjaxControlToolkit.CalendarView.QuarterYearDecade
            && calendarView != AjaxControlToolkit.CalendarView.Quarter) {
                if (target.date.getFullYear() != visibleDate.getFullYear()) {
                    this._visibleDate = target.date;
                }
                this._switchMode("months");
            }
            else {
                if (target.date.getFullYear() != visibleDate.getFullYear()) {
                    this._visibleDate = target.date;
                }
                this._switchMode("quarters");
            }
            break;
        case "decade":
            if (target.date.getFullYear() != visibleDate.getFullYear()) {
                this._visibleDate = target.date;
            }
            this._switchMode("years");
            break;
        case "prev":
        case "next":
            this._switchMonth(target.date);
            break;
        case "title":
            switch (this._mode) {
                case "days": this._switchMode("months"); break;
                case "months": this._switchMode("years"); break;
                case "years": this._switchMode("decades"); break;
                case "quarters": this._switchMode("years"); break;
            }
            break;
    }
    //E4
},
//S4 - I introduced the _closeCalendar function to separate out the functionality for when the calendar closes.
//This was beneficial when determining how to refactor the code as I could then see what was common and what was not.
_closeCalendar: function(target) {
    this.set_selectedDate(target.date);
    this._blur.post(true);
    this.raiseDateSelectionChanged();
    return;
},
//E4

And that’s it! With only a handful of additional lines of code, there are now many more views that you can choose from. Note that I have also refactored the dispose code. There was a lot of repetition in the code, so I decided to make a method to do all the repeated work and call that to dispose of each of the views. That now looks like the following:

//E4 – refactor dispose code to remove repetition 
dispose: function() {
    ///

    /// Disposes this behavior’s resources
    ///

    if (this._popupBehavior) {
        this._popupBehavior.dispose();
        this._popupBehavior = null;
    }
    this._modes = null;
    this._modeOrder = null;
    if (this._modeChangeMoveTopOrLeftAnimation) {
        this._modeChangeMoveTopOrLeftAnimation.dispose();
        this._modeChangeMoveTopOrLeftAnimation = null;
    }
    if (this._modeChangeMoveBottomOrRightAnimation) {
        this._modeChangeMoveBottomOrRightAnimation.dispose();
        this._modeChangeMoveBottomOrRightAnimation = null;
    }
    if (this._modeChangeAnimation) {
        this._modeChangeAnimation.dispose();
        this._modeChangeAnimation = null;
    }
    if (this._container) {
        if (this._container.parentNode) { // added this check before calling removeChild WI: 8486
            this._container.parentNode.removeChild(this._container);
        }
        this._container = null;
    }
    if (this._popupDiv) {
        $common.removeHandlers(this._popupDiv, this._popup$delegates);
        this._popupDiv = null;
    }
    if (this._prevArrow) {
        $common.removeHandlers(this._prevArrow, this._cell$delegates);
        this._prevArrow = null;
    }
    if (this._nextArrow) {
        $common.removeHandlers(this._nextArrow, this._cell$delegates);
        this._nextArrow = null;
    }
    if (this._title) {
        $common.removeHandlers(this._title, this._cell$delegates);
        this._title = null;
    }
    if (this._today) {
        $common.removeHandlers(this._today, this._cell$delegates);
        this._today = null;
    }
    if (this._button) {
        $common.removeHandlers(this._button, this._button$delegates);
        this._button = null;
    }

   // disposeView is a new method I created to do all the work of cleaning up each view. Further refactoring of this method
// is possible.
    this.disposeView(this._daysBody);
    this.disposeView(this._monthsBody);
    this.disposeView(this._yearsBody);
    this.disposeView(this._decadesBody);
    this.disposeView(this._quartersBody);
    var elt = this.get_element();
    $common.removeHandlers(elt, this._element$delegates);
    AjaxControlToolkit.CalendarBehavior.callBaseMethod(this, “dispose”);
},
disposeView: function(viewBody) {
    if (viewBody) {
        for (var i = 0; i < viewBody.rows.length; i++) {             var row = viewBody.rows[i];             for (var j = 0; j < row.cells.length; j++) {                 $common.removeHandlers(row.cells[j].firstChild, this._cell$delegates);             }         }         viewBody = null;     } }, //E4 [/sourcecode] If you ran into trouble and need to have a look at the source code, it can be found here: AjaxControlToolkit-Framework3.5SP1withDecadeYearQuarterMonthDayandInitialView.zip

Still to go, I believe I have figured out a way to add date restriction to the component. It might take me a few days to figure this out, but I should be able to do it.

Edit: I just put in a fix to the _isSelected function in the CalendarBehavior.js file. I had misread the original code which allowed the date to return a true in the wrong case. This caused the selected date square to potentially be around more than one date on the calendar view. The zip file above now has the corrected code.