使用 AOP 容器配置 ASP.NET MVC/AJAX/REST 应用程序的方面






4.67/5 (3投票s)
使用 AOP 容器通过配置为 ASP.NET MVC/AJAX/REST 应用程序添加 AOP 功能。
引言
文章 AOP 容器 介绍了一个 AOP 模型,该模型通过配置使任何 IoC 容器都具备 AOP 功能。作为示例,该模型已应用于 MEF、Unity 和 Windsor 容器,并附带一个简单的控制台应用程序。
从那时起,我增强了这个模型,使其适用于 Web 应用程序。在本文中,我将讨论如何将 AOP 容器与 Unity 容器结合使用,为 ASP.NET MVC/AJAX/REST Web 应用程序添加 AOP 功能。
背景
目前有很多 IoC(控制反转)容器,例如 MEF、Unity、Windsor 等。它们通过提供将这些组件连接成一个单一集成应用程序的机制,鼓励解耦组件开发。
IoC 容器本身不提供 AOP 功能。它们依赖其他工具来提供 AOP 功能。AOPContainer 是一个工具,可以与任何 IoC 容器集成,通过配置向对象添加方面。它基于动态装饰器(参见文章 动态装饰器模式 和 使用动态装饰器向对象添加方面)。
要为应用程序添加 AOP 功能,您需要定义您的方面方法,在配置文件中配置它们,并使用为您的 IoC 容器定制的 AOP 容器来创建对象。在幕后,方面将附加到对象上。
示例
在以下各节中,UnityContainer 和 AOPUnityContainer 将用于 ASP.NET MVC/AJAX/REST Web 应用程序。文章 MVC/AJAX/REST 应用程序的组件、方面和动态装饰器 中使用了相同的示例。但这一次,我们不使用动态装饰器,而是使用 AOP 容器,它是动态装饰器的一个可配置版本。问题定义如下。
问题定义
根据部门选择显示员工。
选择应用程序类型
在本文中,我们选择了一个 ASP.NET MVC 应用程序来解决这个问题。对于 ASP.NET MVC 应用程序,Web 应用程序的响应通常会刷新整个页面。例如,当选择一个部门时,整个页面会刷新以显示与所选部门相关的员工。更理想的情况是,当选择一个部门时,只刷新页面的一部分,即员工表。这可以使用 AJAX 实现。
AJAX 是 Web 应用程序中的一组客户端技术,可以应用于不同类型的 Web 应用程序:ASP.NET、ASP.NET MVC、PHP 等。对于这个示例,AJAX 应用于 ASP.NET MVC 应用程序。此外,我们在实现 AJAX 方面有几种选择:原生 JavaScript、ASP.NET AJAX 框架、jQuery 等。对于我们的示例,我们使用 jQuery。最后,我们必须决定如何处理 AJAX 请求。对于这个示例,WCF REST 服务用于处理 AJAX 请求。
定义组件
假设有两个组件:Employee
和 Department
。对于 Employee
,有一个相应的 RepositoryEmployee
组件,其中包含 Employee
对象的集合。对于 Department
,有一个相应的 RepositoryDepartment
组件,其中包含 Department
对象的集合。这些组件的代码如下所示。
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;
}
}
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() { }
}
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") };
}
}
在此应用程序中,员工和部门的数据硬编码在两个列表中,以简化我们的讨论。在实际应用程序中,此数据通常持久化在关系数据库中。然后,您将需要创建一个数据层来检索它并将其放入列表中。
值得注意的是,员工列表是按插入顺序填充的,没有任何排序。目前很难预测该组件将支持哪种排序方式。在应用程序中,组件的对象可能需要按姓氏排序。另一个可能需要按生日排序。第三个可能根本不需要排序。因此,最好在组件用于应用程序之前推迟排序的实现。通过在设计 RepositoryEmployee
组件时不考虑排序,遵循了“以通用方式设计组件以满足业务需求”的原则。这样,组件是稳定且封闭的。
定义方面
方面是横切关注点。对于动态装饰器,方面是一个方法,它接受一个 AspectContext
类型的对象作为第一个参数,一个 object[]
类型的对象作为第二个参数,并返回 void
。
您可以以更通用的方式设计您的方面,以便在各种情况下使用它们。您也可以为某些特定情况设计您的方面。例如,您可以以通用的方式定义您的进入/退出日志方面,并将它们放入 SysConcerns
类中,如下所示:
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();
}
}
正如您所见,这些方法以通用方式访问 Target
和 CallCtx
,并且可以被各种类型的对象共享,以写入进入/退出日志。
另一方面,某些方面可能需要访问更具体的信息。例如,当 Employee
对象的属性被设置时,您只想将更改通知功能附加到 Employee
对象。以下代码定义了一些特定的方面:
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;
}
}
}
在上面的代码中,`NotifyChange` 方法只能由 `Employee` 的目标使用,而 `SortEmployee` 只能由 `RepositoryEmployee` 的目标使用。
注意:您可能已经注意到,SysConcerns
和 LocalConcerns
类中都有一个静态构造函数。在静态类中,类中定义的每个方面方法都用于创建一个 Decoration
实例,然后将其添加到字典 ConcernsContainer.runtimeAspects
中。ConcernsContainer
的定义如下。
public class ConcernsContainer
{
static public Dictionary<string, Decoration> runtimeAspects =
new Dictionary<string, Decoration>();
}
此 Dictionary
和静态构造函数的目的是根据应用程序中定义的方面方法保留所有 Decoration
对象的清单,并使它们可以通过相应的方法名称访问。这使得通过指定相应的方法名称在应用程序配置文件中配置方面成为可能。
配置方面
在配置文件中,您指定方面如何与对象关联。以下是一些示例,演示如何配置方面:
<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>
首先,您需要在配置文件中添加一个 <DynamicDecoratorAspect>
节。您还需要在 <appSettings>
中添加一个 applicationPath
键,以指定 Web 应用程序的物理路径。这是 Web 应用程序所必需的,因为 Web 应用程序路径与执行程序路径不同。然后,在 <DynamicDecoratorAspect>
的 <objectTemplates>
中,您添加单个元素。对于 <objectTemplates>
中的每个元素,需要指定以下属性:
type
- 目标类型interface
- 返回的接口methods
- 将附加到predecoration
和postdecoration
指定的方面目标方法的名称predecoration
- 预处理方面postdecoration
- 后处理方面
注释
methods
属性值中的名称用逗号分隔。例如,"DetailsByLevel,get_EmployeeID"
。predecoration
属性的值由两部分组成,并用逗号分隔。第一部分指定方面名称,第二部分指定定义该方面的程序集名称,例如"SharedLib.SysConcerns.EnterLog,SharedLib.SysConcerns"
。如果未指定第二部分,则假定该方面在入口程序集中定义,例如"ConsoleUtil.LocalConcerns.SecurityCheck"
。postdecoration
属性的值分为两部分,由逗号分隔。第一部分指定方面名称,第二部分指定定义该方面的程序集名称。如果未指定第二部分,则假定该方面在入口程序集中定义。
更改 Global.asax.cs
我们需要进行一些应用程序范围的更改,以便在我们的应用程序中使用方面和容器。Global.asax.cs 的代码如下所示
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();
}
}
在上面的代码中,Console
的输出被重定向到文件流。这是必需的,因为日志方面方法在 Web 应用程序中使用 Console
写入日志。然后,创建一个 UnityContainer
并向其注册一些类型。还通过传入 UnityContainer
实例来创建一个 AOPUnityContainer
。UnityContainer
对象和 AOPUnityContainer
对象分别分配给静态变量 Containers.iocContainer
和 Containers.aopContainer
,以供应用程序后续使用。
Containers
的定义如下
public class Containers
{
static public IUnityContainer iocContainer = null;
static public AOPContainer aopContainer = null;
}
使用容器
控制器代码如下所示
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);
}
}
正如您在代码中看到的,`rpDepartment` 和 `rpEmployee` 都是使用 `Containers.aopContainer` 解析的。因此,它们已经附加了配置文件中配置的方面。对于 `rpDepartment`,其 `GetAll` 已附加了预处理方面 `SharedLib.SysConcerns.EnterLog` 和后处理方面 `SharedLib.SysConcerns.ExitLog`。对于 `rpEmployee`,其 `GetAll` 已附加了后处理 `HRMVCAjax.Models.LocalConcerns.SortEmployee` 方面。
请注意,从 AOPContainer
的 Resolve
方法出来的 rpEmployee
仅附加了配置文件中配置的 HRMVCAjax.Models.LocalConcerns.SortEmployee
方面。如果您想为其添加进入/退出日志方面怎么办?您可以通过调用 ObjectProxyFactory.CreateProxy
来实现,如上面的代码所示。现在,rpEmployee
不仅具有排序功能,还具有进入/退出日志功能。结合使用配置和代码来为对象添加方面非常灵活。
REST 服务代码如下所示。它接受一个部门 ID 并将员工列表返回给客户端。
[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;
}
}
请注意,`rpDepartment` 是直接从 `IUnityContainer` 的 `Resolve` 方法解析的。因此,它没有附加任何方面,这意味着它原样使用,没有添加额外的功能。另一方面,`rpEmployee` 是从 `AOPContainer` 的 `Resolve` 解析的。因此,它具有配置文件中配置的排序功能。
最后,为选定的 Department 调用 `ObjectProxyFactory.CreateProxy`,以在其 `get_DepartmentID` 方法中添加进入日志记录。
最后,视图代码如下。
<%@ 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>
请注意上面用 jQuery 编写的 JavaScript 代码。当选择一个部门时,一个 AJAX 请求会发送到 HRRestService
服务,其中包含所选部门 ID 作为查询字符串。服务返回一个 XML 数据文件,其中包含与所选部门相关的员工列表。回调函数解析 XML 数据并使用它更新页面中的员工表。
当浏览器第一次访问应用程序时,MVC 控制器会创建部门列表和员工表,并将响应发送回客户端。当选择一个部门时,jQuery 代码会向 REST 服务发送一个 AJAX 请求,其中包含所选部门 ID。一旦服务响应,回调函数会刷新员工表以显示与所选部门相关的员工。
运行时,所有员工显示如下
选择部门后,将显示该部门的员工。
退出浏览器会话,停止应用程序,并打开文件 hrlog.txt。您将看到以下日志:
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()
关注点
AOP 容器与 IoC 容器相结合,使得为 Web 应用程序添加 AOP 功能变得非常容易。您定义您的方面方法并在配置文件中配置它们。如果需要一个带方面的对象,您可以使用 AOP 容器来创建它。如果需要一个不带方面的对象,只需使用 IoC 容器创建它即可。
您还可以混合使用配置添加方面和使用代码添加方面。这样,您可以将方面配置用于通用目的,同时使用代码链接更多方面以满足您的特定需求。
尽管本文讨论了 ASP.NET MVC 应用程序的 AOP 容器与 Unity IoC 容器,但您应该能够轻松地将 AOP 容器与其他 IoC 容器(如 MEF 和 Windsor)结合使用,以用于其他应用程序类型,如 ASP.NET、Silverlight、WinForms 等。