Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / ASP.NET

Configurable Aspects for ASP.NET MVC/AJAX/REST Application Using AOP Container

4.67/5 (3 votes)
27 Sep 2011CPOL10 min read 24.3K   388  
Use AOP container to add AOP capabilities to ASP.NET MVC/AJAX/REST applications by configuration.

Introduction

The article AOP Container introduced an AOP model that enables any IoC container to have AOP capabilities by configuration. As examples, the model was applied to MEF, Unity, and Windsor containers with a simple console application.

Since then, I've enhanced this model to work with Web applications. In this article, I discuss how the AOP container together with Unity container is used to add AOP capabilities to an ASP.NET MVC/AJAX/REST Web application.

Background

There are quite a few IoC (Inversion of Control) containers out there, e.g., MEF, Unity, Windsor, etc. They encourage decoupled component development by providing mechanisms to wire these components into a single integrated application.

IoC containers do not provide AOP capabilities by themselves. They resort to other tools to provide AOP capabilities. AOPContainer is a tool that can be integrated with any IoC container to add aspects to objects by configuration. It is based on the Dynamic Decorator (see the articles Dynamic Decorator Pattern and Add Aspects to Object Using Dynamic Decorator).

To add AOP capabilities to your application, you define your aspect methods, configure them in the configuration file, and use an AOP Container customized for your IoC container to create objects. Behind the scenes, the aspects are attached to the objects.

Example

In the following sections, UnityContainer and AOPUnityContainer are used in an ASP.NET MVC/AJAX/REST Web application. The same example is used in the article Components, Aspects, and Dynamic Decorator for an MVC/AJAX/REST Application. But this time, instead of using Dynamic Decorator, we use AOP Container, which is a configurable version of the Dynamic Decorator. The problem definition is stated as follows.

Problem Definition

Displays employees based on the selection of a department.

Choose Application Type

In this article, an ASP.NET MVC application is chosen to address this problem. For an ASP.NET MVC application, a response from a Web application normally refreshes the entire page. For example, when a department is selected, the entire page is refreshed to display the employees associated with the selected department. It is more desirable that only part of the page, the employee table, is refreshed when a department is selected. This can be achieved using AJAX.

AJAX is a set of client-side technologies within a Web application, which can be applied to different types of Web applications: ASP.NET, ASP.NET MVC, PHP, etc. For this example, AJAX is applied to an ASP.NET MVC application. Also, we have a few choices in terms of implementing AJAX: native JavaScript, ASP.NET AJAX Framework, jQuery, etc. For our example, we use jQuery. Last, we have to decide on how to serve the AJAX requests. For this example, a WCF REST service is used to serve AJAX requests.

Define Components

Assume there are two components, Employee and Department. For Employee, there is a corresponding RepositoryEmployee component to contain a collection of objects of Employee. For Department, there is a corresponding RepositoryDepartment component to contain a collection of objects of Department. The code of these components is listed as follows.

C#
public interface IEmployee
{
    System.Int32? EmployeeID { get; set; }
    System.String FirstName { get; set; }
    System.String LastName { get; set; }
    System.DateTime DateOfBirth { get; set; }
    System.Int32? DepartmentID { get; set; }
    System.String DetailsByLevel(int iLevel);
    //1-full name; 2-full name plus birth day;
    //3-full name plus birth day and department id.
}

public interface INotifyPropertyChanged
{
    event PropertyChangedEventHandler PropertyChanged;
}

[PartCreationPolicy(CreationPolicy.NonShared)]
[Export]
public class Employee : IEmployee, INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public void NotifyPropertyChanged(String info)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(info));
        }
    }

    #region Properties

    public System.Int32? EmployeeID { get; set; }
    public System.String FirstName { get; set; }
    public System.String LastName { get; set; }
    public System.DateTime DateOfBirth { get; set; }
    public System.Int32? DepartmentID { get; set; }

    #endregion

    public Employee(
        System.Int32? employeeid
        , System.String firstname
        , System.String lastname
        , System.DateTime bDay
        , System.Int32? departmentID
    )
    {
        this.EmployeeID = employeeid;
        this.FirstName = firstname;
        this.LastName = lastname;
        this.DateOfBirth = bDay;
        this.DepartmentID = departmentID;
    }

    public Employee() { }

    public System.String DetailsByLevel(int iLevel)
    {
        System.String s = "";
            
        switch (iLevel)
        {
            case 1:
                s = "Name:" + FirstName + " " + LastName + ".";
                break;

            case 2:
                s = "Name: " + FirstName + " " + LastName + 
                    ", Birth Day: " + DateOfBirth.ToShortDateString() + ".";
                break;

            case 3:
                s = "Name: " + FirstName + " " + LastName + 
                    ", Birth Day: " + DateOfBirth.ToShortDateString() + 
                    ", Department:" + DepartmentID.ToString() + ".";
                break;

            default:
                break;
                    
        }
        return s;
    }
}
C#
public interface IDepartment
{
    System.Int32? DepartmentID { get; set; }
    System.String Name { get; set; }
}

public class Department : IDepartment
{
    #region Properties

    public System.Int32? DepartmentID { get; set; }
    public System.String Name { get; set; }

    #endregion

    public Department(
        System.Int32? departmentid
        , System.String name
    )
    {
        this.DepartmentID = departmentid;
        this.Name = name;
    }

    public Department() { }
}
C#
public interface IRepository<T>
{
    List<T> RepList { get; set; }
    void GetAll();
}

public class RepositoryEmployee : IRepository<IEmployee>
{
    private List<IEmployee> myList = null;

    public List<IEmployee> RepList
    {
        get { return myList; }
        set { myList = value; }
    }

    public RepositoryEmployee()
    {
    }

    public void GetAll()
    {
        myList = new List<IEmployee> { 
            new Employee(1, "John", "Smith", new DateTime(1990, 4, 1), 1), 
            new Employee(2, "Gustavo", "Achong", new DateTime(1980, 8, 1), 1), 
            new Employee(3, "Maxwell", "Becker", new DateTime(1966, 12, 24), 2), 
            new Employee(4, "Catherine", "Johnston", new DateTime(1977, 4, 12), 2), 
            new Employee(5, "Payton", "Castellucio", new DateTime(1959, 4, 21), 3), 
            new Employee(6, "Pamela", "Lee", new DateTime(1978, 9, 16), 4) };
    }
}

public class RepositoryDepartment : IRepository<IDepartment>
{
    private List<IDepartment> myList = null;

    public List<IDepartment> RepList
    {
        get { return myList; }
        set { myList = value; }
    }

    public RepositoryDepartment()
    {
    }

    public void GetAll()
    {
        myList = new List<IDepartment> { new Department(1, "Engineering"), 
            new Department(2, "Sales"), 
            new Department(3, "Marketing"), 
            new Department(4, "Executive") };
    }
}

In this application, the data for employees and departments is hard-coded in two lists to simplify our discussion. In a real world application, this data is normally persisted in a relational database. Then, you will need to create a data layer to retrieve it and put it in lists.

It is worth noting that the employee list is populated by the inserting order without any kind of sorting in place. It is difficult to anticipate what kinds of sorting to support for this component at this time. In applications, an object of the component may need to sort by last name. Another may need to sort by birth day. A third may not need to sort at all. So, it is better to defer the implementation of sorting until the component is used in an application. By designing the component RepositoryEmployee without concerning about sorting, the Principle "Design components to meet business requirements in a general way" is followed. This way, the component is stable and closed.

Define Aspects

Aspects are cross-cutting concerns. For Dynamic Decorator, an aspect is a method that takes an AspectContext type object as its first parameter and an object[] type object as its second parameter and returns void.

You may design your aspects in a more general way to use them in various situations. You may also design your aspects for some particular situations. For example, you can define your entering/exiting log aspects in a general way and put them in the class SysConcerns as follows:

C#
public class SysConcerns
{
    static SysConcerns()
    {
        ConcernsContainer.runtimeAspects.Add(
          "DynamicDecoratorAOP.SysConcerns.EnterLog", 
          new Decoration(SysConcerns.EnterLog, null));
        ConcernsContainer.runtimeAspects.Add(
          "DynamicDecoratorAOP.SysConcerns.ExitLog", 
          new Decoration(SysConcerns.ExitLog, null));
    }

    public static void EnterLog(AspectContext ctx, object[] parameters)
    {
        StackTrace st = new StackTrace(new StackFrame(4, true));
        Console.Write(st.ToString());
            
        IMethodCallMessage method = ctx.CallCtx;
        string str = "Entering " + ctx.Target.GetType().ToString() + 
                     "." + method.MethodName + "(";
        int i = 0;
        foreach (object o in method.Args)
        {
            if (i > 0)
                str = str + ", ";
            str = str + o.ToString();
        }
        str = str + ")";

        Console.WriteLine(str);
        Console.Out.Flush();

    }

    public static void ExitLog(AspectContext ctx, object[] parameters)
    {
        IMethodCallMessage method = ctx.CallCtx;
        string str = "Exiting " + ctx.Target.GetType().ToString() + 
                     "." + method.MethodName + "(";
        int i = 0;
        foreach (object o in method.Args)
        {
            if (i > 0)
                str = str + ", ";
            str = str + o.ToString();
        }
        str = str + ")";

        Console.WriteLine(str);
        Console.Out.Flush();
    }
}

As you can see, these methods access Target and CallCtx in a general way and can be shared by various types of objects to write entering/exiting logs.

On the other hand, some aspects may need to access more specific information. For example, you want to attach a change notification functionality only to an Employee object when its property is set. The following code defines some specific aspects:

C#
public class LocalConcerns
{
    class EmployeeLastnameComparer : IComparer<IEmployee>
    {
        public int Compare(IEmployee e1, IEmployee e2)
        {
            return String.Compare(e1.LastName, e2.LastName);
        }
    }

    static LocalConcerns()
    {
        ConcernsContainer.runtimeAspects.Add(
          "HRMVCAjax.Models.LocalConcerns.NotifyChange", 
          new Decoration(LocalConcerns.NotifyChange, null));
        ConcernsContainer.runtimeAspects.Add(
          "HRMVCAjax.Models.LocalConcerns.SecurityCheck", 
          new Decoration(LocalConcerns.SecurityCheck, null));
        ConcernsContainer.runtimeAspects.Add(
          "HRMVCAjax.Models.LocalConcerns.SortEmployee", 
          new Decoration(LocalConcerns.SortEmployee, null));
    }

    public static void NotifyChange(AspectContext ctx, object[] parameters)
    {
        ((Employee)ctx.Target).NotifyPropertyChanged(ctx.CallCtx.MethodName);
    }

    public static void SecurityCheck(AspectContext ctx, object[] parameters)
    {
        //Exception exInner = null;

        //try
        //{
        //    if (parameters != null && parameters[0] is WindowsPrincipal && 
        //       ((WindowsPrincipal)parameters[0]).IsInRole("BUILTIN\\" + "Administrators"))
        //    {
        //        return;
        //    }
        //}
        //catch (Exception ex)
        //{
        //    exInner = ex;
        //}

        //throw new Exception("No right to call!", exInner);
    }

    public static void SortEmployee(AspectContext ctx, object[] parameters)
    {
        object target = ctx.Target;
        if (target.GetType().ToString() == "ThirdPartyHR.RepositoryEmployee")
        {
            List<IEmployee> emps = ((IRepository<IEmployee>)target).RepList;
            IEnumerable<IEmployee> query = emps.OrderByDescending(emp => emp,
                new EmployeeLastnameComparer()).ToList<IEmployee>();
            ((IRepository<IEmployee>)target).RepList = (List<IEmployee>)query;
        }
    }
}

In the above code, the NotifyChange method can only be used by a target of Employee while SortEmployee can only be used by a target of RepositoryEmployee.

Note: You may already have noticed that there is a static constructor in each of the classes SysConcerns and LocalConcerns. Inside the static classes, each of the aspect methods defined in the classes is used to create an instance of Decoration which is then added to a dictionary ConcernsContainer.runtimeAspects. The definition of ConcernsContainer is as follows.

C#
public class ConcernsContainer
{
    static public Dictionary<string, Decoration> runtimeAspects = 
           new Dictionary<string, Decoration>();
}

The purpose of this Dictionary and the static constructors is to keep an inventory for all Decoration objects based on the aspect methods defined in the application and make them accessible by the corresponding method names. It makes possible to configure aspects in the application configuration file by specifying the corresponding method names.

Configure Aspects

In the configuration file, you specify how aspects are associated with objects. Here are some examples to demonstrate how the aspects are configured:

XML
<configuration>
    <configSections>
        <section name="DynamicDecoratorAspect" 
          type="DynamicDecoratorAOP.Configuration.DynamicDecoratorSection,
                 DynamicDecoratorAOP.Configuration" />
    </configSections>
    <appSettings>
        <add key="applicationPath"
             value="C:\AspectContainerUsingDynamicDecorator\HRMVCAjax\bin\" />
    </appSettings>

    <DynamicDecoratorAspect>
        <objectTemplates>
            <add name="1"
                 type="ThirdPartyHR.RepositoryDepartment"
                 interface="ThirdPartyHR.IRepository`1[ThirdPartyHR.IDepartment]"
                 methods="GetAll"
                 predecoration="SharedLib.SysConcerns.EnterLog,SharedLib.SysConcerns"
                 postdecoration="SharedLib.SysConcerns.ExitLog,SharedLib.SysConcerns" />
            <add name="2"
                 type="ThirdPartyHR.RepositoryEmployee"
                 interface="ThirdPartyHR.IRepository`1[ThirdPartyHR.IEmployee]"
                 methods="GetAll"
                 predecoration=""
                 postdecoration="HRMVCAjax.Models.LocalConcerns.SortEmployee" />
            <add name="3"
                 type="ThirdPartyHR.Department"
                 interface="ThirdPartyHR.IDepartment"
                 methods="DetailsByLevel,get_EmployeeID"
                 predecoration="SharedLib.SysConcerns.EnterLog,SharedLib.SysConcerns"
                 postdecoration="" />
        </objectTemplates>
    </DynamicDecoratorAspect>
</configuration>

First of all, you need to add a section <DynamicDecoratorAspect> in your configuration file. You also need to add an applicationPath key to <appSettings> to specify the physical path of the Web application. It is required for a Web application since a Web application path is different from the execution program path. Then, inside <objectTemplates> of <DynamicDecoratorAspect>, you add individual elements. For each element inside <objectTemplates>, the following attributes need to be specified:

  • type - target type
  • interface - interface returned
  • methods - names of target methods which will be attached to the aspects specified by predecoration and postdecoration
  • predecoration - preprocessing aspect
  • postdecoration - post-processing aspect

Notes:

  1. The names in the value of the methods attribute are comma separated. For example, "DetailsByLevel,get_EmployeeID".
  2. The value of the predecoration attribute has two parts and is separated by a comma. The first part specifies the aspect name while the second part specifies the assembly name in which the aspect is defined, for example, "SharedLib.SysConcerns.EnterLog,SharedLib.SysConcerns". If the second part is not specified, it is assumed that the aspect is defined in the entry assembly, for example, "ConsoleUtil.LocalConcerns.SecurityCheck".
  3. The value of the postdecoration attribute has two parts and is separated by a comma. The first part specifies the aspect name while the second part specifies the assembly name in which the aspect is defined. If the second part is not specified, it is assumed that the aspect is defined in the entry assembly.

Change Global.asax.cs

We need to make a few application wide changes to use the aspects and the containers in our application. The code of Global.asax.cs is listed below:

C#
public class MvcApplication : System.Web.HttpApplication
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

        routes.Add(new ServiceRoute("HRRestService", 
               new WebServiceHostFactory(), typeof(Services.HRRestService)));

        routes.MapRoute(
            "Default", // Route name
            "{controller}/{action}/{id}", // URL with parameters
            new { controller = "DepEmployees", 
                  action = "Index", id = UrlParameter.Optional }
                  // Parameter defaults
        );

    }

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

        RegisterRoutes(RouteTable.Routes);

        FileStream fileStream = null;

        string path = Path.GetDirectoryName(Server.MapPath("~"));
        if (!File.Exists(path + "\\hrlog.txt"))
        {
            fileStream = new FileStream(path + "\\hrlog.txt", FileMode.Create);
        }
        else
            fileStream = new FileStream(path + "\\hrlog.txt", FileMode.Truncate);

        TextWriter tmp = Console.Out;
        Application["origOut"] = tmp;

        StreamWriter sw1 = new StreamWriter(fileStream);
        Console.SetOut(sw1);

        Application["logStream"] = sw1;

        IUnityContainer container = null;
        container = new UnityContainer();
        ((IUnityContainer)container).RegisterType<IEmployee, Employee>(
                          new InjectionConstructor());
        ((IUnityContainer)container).RegisterType<IRepository<IDepartment>, 
                          RepositoryDepartment>(new InjectionConstructor());
        ((IUnityContainer)container).RegisterType<IRepository<IEmployee>, 
                          RepositoryEmployee>(new InjectionConstructor());

        Containers.iocContainer = container;
        Containers.aopContainer = new AOPUnityContainer(container);
    }

    protected void Application_End(object sender, EventArgs e)
    {
        TextWriter origStrm = (TextWriter)Application["origOut"];
        Console.SetOut(origStrm);

        StreamWriter tmp = (StreamWriter)Application["logStream"];
        Stream fileStream = tmp.BaseStream;

        tmp.Close();
        fileStream.Close();

        IDisposable container = Containers.iocContainer;
        container.Dispose();
    }
}

In the above code, Console's out is redirected to a file stream. It is required since the log aspect methods use Console to write logs in a Web application. Then, a UnityContainer is created and a few types are registered to it. An AOPUnityContainer is also created by passing in the instance of the UnityContainer. The UnityContainer object and the AOPUnityContainer object are assigned to static variables Containers.iocContainer and Containers.aopContainer, respectively, for later use in the application.

The definition of Containers is as follows:

C#
public class Containers
{
    static public IUnityContainer iocContainer = null;
    static public AOPContainer aopContainer = null;
}

Use Containers

The controller code is listed below:

C#
public class DepEmployeesController : Controller
{
    public DepEmployeesController()
    {
    }

    public ActionResult Index()
    {
        IRepository<IEmployee> rpEmployee = null;
        IRepository<IDepartment> rpDepartment = null;

        rpDepartment = Containers.aopContainer.Resolve<RepositoryDepartment, 
                                  IRepository<IDepartment>>();
        rpDepartment.GetAll();

        rpEmployee = Containers.aopContainer.Resolve<RepositoryEmployee, 
                                IRepository<IEmployee>>();

        //Add entering log to employee list
        rpEmployee = ObjectProxyFactory.CreateProxy<IRepository<IEmployee>>(
            rpEmployee,
            new String[] { "GetAll" },
            new Decoration(new DecorationDelegate(SysConcerns.EnterLog), null),
            new Decoration(new DecorationDelegate(SysConcerns.ExitLog), null));

        rpEmployee.GetAll();

        List<SelectListItem> depList = new List<SelectListItem>();
        SelectListItem sli = null;
        sli = new SelectListItem();
        sli.Value = "";
        sli.Text = "";
        depList.Add(sli);

        foreach (IDepartment d in rpDepartment.RepList)
        {
            sli = new SelectListItem();
            sli.Value = d.DepartmentID.Value.ToString();
            sli.Text = d.Name;
            depList.Add(sli);
        }

        ViewData["depSel"] = new SelectList(depList, "Value", "Text");
        return View(rpEmployee.RepList);
    }
}

As you see in the code, both rpDepartment and rpEmployee are resolved using Containers.aopContainer. Therefore, they have been attached the aspects configured in the configuration file. For rpDepartment, its GetAll has been attached a preprocessing aspect SharedLib.SysConcerns.EnterLog and a postprocessing aspect SharedLib.SysConcerns.ExitLog. For rpEmployee, its GetAll has been attached a postprocessing HRMVCAjax.Models.LocalConcerns.SortEmployee aspect.

Note that the rpEmployee coming out of the AOPContainer's Resolve method has only the HRMVCAjax.Models.LocalConcerns.SortEmployee aspect attached as configured in the configuration file. What if you want to add enter/exit logging aspects to it? You can achieve this by calling ObjectProxyFactory.CreateProxy as shown in the above code. Now, rpEmployee has not only the sorting capability but also the enter/exit logging capabilities. It is very flexible to use both configuration and code to add aspects to objects.

The REST service code is listed as follows. It accepts a department ID and returns a list of employees to the client.

C#
[ServiceContract]
[AspNetCompatibilityRequirements(RequirementsMode = 
       AspNetCompatibilityRequirementsMode.Allowed)]
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public class HRRestService
{
    private static int iStaticDep = 0;

    [WebGet(UriTemplate = "?dep={dep}")]
    public List<Employee> EmployeesByDepartment(string dep)
    {
        IRepository<IEmployee> rpEmployee = null;
        IRepository<IDepartment> rpDepartment = null;

        rpDepartment = Containers.iocContainer.Resolve<RepositoryDepartment>(); ;
        rpDepartment.GetAll();

        rpEmployee = Containers.aopContainer.Resolve<RepositoryEmployee, 
                     IRepository<IEmployee>>();
        rpEmployee.GetAll();

        IDepartment dpSel = rpDepartment.RepList[Convert.ToInt16(dep) - 1];
        dpSel = ObjectProxyFactory.CreateProxy<IDepartment>(
            dpSel,
            new String[] { "get_DepartmentID" },
            new Decoration(new DecorationDelegate(SysConcerns.EnterLog), null),
            null);

        iStaticDep = dpSel.DepartmentID.Value;

        List<IEmployee> empSel = null;
        if (rpEmployee.RepList != null)
        {
            empSel = rpEmployee.RepList.FindAll(
                (IEmployee emp) => { return emp.DepartmentID.Value == iStaticDep; });
        }

        List<Employee> empObj = new List<Employee>();
        foreach (IEmployee i in empSel)
        {
            empObj.Add((Employee)i);
        }

        return empObj;
    }
}

Note that rpDepartment is resolved directly from the IUnityContainer's Resolve method. Therefore, there is no aspect attached it, which means it is used as is without additional functionality added. On the other hand, rpEmployee is resolved from AOPContainer's, Resolve. Therefore, it has the sorting capability configured in the configuration file.

Last, ObjectProxyFactory.CreateProxy is called for a selected Department to add enter logging to its get_DepartmentID method.

Finally, the view code is as follows.

ASP.NET
<%@ Page Title="" Language="C#" 
    MasterPageFile="~/Views/Shared/Site.Master" 
    Inherits="System.Web.Mvc.ViewPage<dynamic>" %>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<% Html.BeginForm("Index", "DepEmployees"); %>
    <span>Department</span><br />
        <%= Html.DropDownList("depSel")%>

    <br />    <br />
    <span>Employees</span><br />
    <div id="grid">
    <table cellspacing="0" rules="all" border="1" 
         id="GridView1" style="border-collapse:collapse;">
        <tr>
            <th>EmployeeID</th><th>FirstName</th><th>LastName</th>
               <th>DateOfBirth</th><th>DepartmentID</th>
        </tr>
<% foreach (var itm in Model) { %>
        <tr>
            <td><%= itm.EmployeeID %></td><td><%= itm.FirstName%></td>
              <td><%= itm.LastName%></td><td><%= itm.DateOfBirth%></td>
              <td><%= itm.DepartmentID%></td>
        </tr>
<% } %>
    </table>
    </div>
<% Html.EndForm(); %>

<script type='text/javascript'>
    $(document).ready(function () {
        $("select").change(function () {
            $.get("HRRestService?dep=" + $(this).val(), function (data) {
                $("#grid").html(
        '<table cellspacing="0" rules="all" border="1" 
               id="emp" style="border-collapse:collapse;">' +
        '<tr>' +
            '<th>EmployeeID</th><th>FirstName</th><th>LastName</th>
              <th>DateOfBirth</th><th>DepartmentID</th>' +
        '</tr>');

                $(data).find("Employee").each(function() {
                    $("#emp").append(
        '<tr>' +
            '<td>' + $(this).find("EmployeeID").text() + '</td><td>' + 
              $(this).find("FirstName").text() + '</td><td>' + 
              $(this).find("LastName").text() + '</td><td>' + 
              $(this).find("DateOfBirth").text() + '</td><td>' + 
              $(this).find("DepartmentID").text() + '</td>' +
        '</tr>'
                    );
                });
                $("#grid").append('</table>');
            });
        });
    })
</script>
</asp:Content>

Pay attention to the above JavaScript code written in jQuery. When a department is selected, an AJAX request is sent to the service HRRestService with the selected department ID as query string. The service returns an XML data file which contains a list of employees associated with the selected department. The callback function parses the XML data and uses that to update the employee table in the page.

When a browser accesses the application the first time, the MVC controller creates both the department list and the employee table, and sends the response back to the client. When a department is selected, the jQuery code makes an AJAX request to the REST service with the selected department ID. Once the service responds back, the callback function refreshes the employee table to display the employees associated with the selected department.

When it runs, all employees are displayed as follows:

Image 1

When a department is selected, the employees for that department are displayed.

Image 2

Quit the browser session, stop the application, and open the file hrlog.txt. You will see the following logs:

    at HRMVCAjax.Controllers.DepEmployeesController.Index() 
    in C:\AopContainerMvcAjax\HRMVCAjax\Controllers\DepEmployeesController.cs:line 45
Entering ThirdPartyHR.RepositoryEmployee.GetAll()
Exiting ThirdPartyHR.RepositoryEmployee.GetAll()
   at HRMVCAjax.Services.HRRestService.EmployeesByDepartment(String dep) 
   in C:\AopContainerMvcAjax\HRMVCAjax\Services\HRRestService.cs:line 48
Entering ThirdPartyHR.Department.get_DepartmentID()

Points of Interest

AOP Container combined with IoC container makes adding AOP capabilities to a Web application very easy. You define your aspect methods and configure them in the configuration file. If you need an object with aspects, you use the AOP container to create it. If you need an object without aspects, just create it using the IoC container.

You can also mix adding aspects using configuration and adding aspects using code. This way, you can use aspect configuration for general purpose while using code to chain more aspects to meet your specific requirements as needed.

Although this article discusses AOP container with Unity IoC container for ASP.NET MVC applications, you should be able to easily combine AOP Container with other IoC containers like MEF and Windsor for other application types like ASP.NET, Silverlight, WinForms, etc.

License

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