C# 實現AOP 的幾種常見方式
AOP為Aspect Oriented Programming的縮寫,意為:面向切面編程,通過預編譯方式和運行期動態代理實現程序功能的中統一處理業務邏輯的一種技術,比較常見的場景是:日志記錄,錯誤捕獲、性能監控等
AOP的本質是通過代理對象來間接執行真實對象,在代理類中往往會添加裝飾一些額外的業務代碼,比如如下代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
class
RealA
{
public
virtual
string
Pro {
get
;
set
; }
public
virtual
void
ShowHello(
string
name)
{
Console.WriteLine($
"Hello!{name},Welcome!"
);
}
}
//調用:
var
a =
new
RealA();
a.Pro =
"測試"
;
a.ShowHello(
"夢在旅途"
);
|
這段代碼很簡單,只是NEW一個對象,然后設置屬性及調用方法,但如果我想在設置屬性前后及調用方法前后或報錯都能收集日志信息,該如何做呢?可能大家會想到,在設置屬性及調用方法前后都加上記錄日志的代碼不就可以了,雖然這樣是可以,但如果很多地方都要用到這個類的時候,那重復的代碼是否太多了一些吧,所以我們應該使用代理模式或裝飾模式,將原有的真實類RealA委托給代理類ProxyRealA來執行,代理類中在設置屬性及調用方法時,再添加記錄日志的代碼就可以了,這樣可以保證代碼的干凈整潔,也便於代碼的后期維護。(注意,在C#中若需被子類重寫,父類必需是虛方法或虛屬性virtual)
如下代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
class
ProxyRealA : RealA
{
public
override
string
Pro
{
get
{
return
base
.Pro;
}
set
{
ShowLog(
"設置Pro屬性前日志信息"
);
base
.Pro = value;
ShowLog($
"設置Pro屬性后日志信息:{value}"
);
}
}
public
override
void
ShowHello(
string
name)
{
try
{
ShowLog(
"ShowHello執行前日志信息"
);
base
.ShowHello(name);
ShowLog(
"ShowHello執行后日志信息"
);
}
catch
(Exception ex)
{
ShowLog($
"ShowHello執行出錯日志信息:{ex.Message}"
);
}
}
private
void
ShowLog(
string
log)
{
Console.WriteLine($
"{DateTime.Now.ToString()}-{log}"
);
}
}
//調用:
var
aa =
new
ProxyRealA();
aa.Pro =
"測試2"
;
aa.ShowHello(
"zuowenjun.cn"
);
|
這段代碼同樣很簡單,就是ProxyRealA繼承自RealA類,即可看成是ProxyRealA代理RealA,由ProxyRealA提供各種屬性及方法調用。這樣在ProxyRealA類內部屬性及方法執行前后都有統一記錄日志的代碼,不論在哪里用這個RealA類,都可以直接用ProxyRealA類代替,因為里氏替換原則,父類可以被子類替換,而且后續若想更改日志記錄代碼方式,只需要在ProxyRealA中更改就行了,這樣所有用到的ProxyRealA類的日志都會改變,是不是很爽。上述執行結果如下圖示:
以上通過定義代理類的方式能夠實現在方法中統一進行各種執行點的攔截代碼邏輯處理,攔截點(或者稱為:橫切面,切面點)一般主要為:執行前,執行后,發生錯誤,雖然解決了之前直接調用真實類RealA時,需要重復增加各種邏輯代碼的問題,但隨之而來的新問題又來了,那就是當一個系統中的類非常多的時候,如果我們針對每個類都定義一個代理類,那么系統的類的個數會成倍增加,而且不同的代理類中可能某些攔截業務邏輯代碼都是相同的,這種情況同樣是不能允許的,那有沒有什么好的辦法呢?答案是肯定的,以下是我結合網上資源及個人總結的如下幾種常見的實現AOP的方式,各位可以參考學習。
第一種:靜態織入,即:在編譯時,就將各種涉及AOP攔截的代碼注入到符合一定規則的類中,編譯后的代碼與我們直接在RealA調用屬性或方法前后增加代碼是相同的,只是這個工作交由編譯器來完成。
PostSharp:PostSharp的Aspect是使用Attribute實現的,我們只需事先通過繼承自OnMethodBoundaryAspect,然后重寫幾個常見的方法即可,如:OnEntry,OnExit等,最后只需要在需要進行AOP攔截的屬性或方法上加上AOP攔截特性類即可。由於PostSharp是靜態織入的,所以相比其它的通過反射或EMIT反射來說效率是最高的,但PostSharp是收費版本的,而且網上的教程比較多,我就不在此重復說明了,大家可以參見:使用PostSharp在.NET平台上實現AOP
第二種:EMIT反射,即:通過Emit反射動態生成代理類,如下Castle.DynamicProxy的AOP實現方式,代碼也還是比較簡單的,效率相對第一種要慢一點,但對於普通的反射來說又高一些,代碼實現如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
|
using
Castle.Core.Interceptor;
using
Castle.DynamicProxy;
using
NLog;
using
NLog.Config;
using
NLog.Win32.Targets;
using
System;
using
System.Collections.Generic;
using
System.Linq;
using
System.Text;
using
System.Threading.Tasks;
namespace
ConsoleApp
{
class
Program
{
static
void
Main(
string
[] args)
{
ProxyGenerator generator =
new
ProxyGenerator();
var
test = generator.CreateClassProxy<TestA>(
new
TestInterceptor());
Console.WriteLine($
"GetResult:{test.GetResult(Console.ReadLine())}"
);
test.GetResult2(
"test"
);
Console.ReadKey();
}
}
public
class
TestInterceptor : StandardInterceptor
{
private
static
NLog.Logger logger;
protected
override
void
PreProceed(IInvocation invocation)
{
Console.WriteLine(invocation.Method.Name +
"執行前,入參:"
+
string
.Join(
","
, invocation.Arguments));
}
protected
override
void
PerformProceed(IInvocation invocation)
{
Console.WriteLine(invocation.Method.Name +
"執行中"
);
try
{
base
.PerformProceed(invocation);
}
catch
(Exception ex)
{
HandleException(ex);
}
}
protected
override
void
PostProceed(IInvocation invocation)
{
Console.WriteLine(invocation.Method.Name +
"執行后,返回值:"
+ invocation.ReturnValue);
}
private
void
HandleException(Exception ex)
{
if
(logger ==
null
)
{
LoggingConfiguration config =
new
LoggingConfiguration();
ColoredConsoleTarget consoleTarget =
new
ColoredConsoleTarget();
consoleTarget.Layout =
"${date:format=HH\\:MM\\:ss} ${logger} ${message}"
;
config.AddTarget(
"console"
, consoleTarget);
LoggingRule rule1 =
new
LoggingRule(
"*"
, LogLevel.Debug, consoleTarget);
config.LoggingRules.Add(rule1);
LogManager.Configuration = config;
logger = LogManager.GetCurrentClassLogger();
//new NLog.LogFactory().GetCurrentClassLogger();
}
logger.ErrorException(
"error"
,ex);
}
}
public
class
TestA
{
public
virtual
string
GetResult(
string
msg)
{
string
str = $
"{DateTime.Now.ToString("
yyyy-mm-dd HH:mm:ss
")}---{msg}"
;
return
str;
}
public
virtual
string
GetResult2(
string
msg)
{
throw
new
Exception(
"throw Exception!"
);
}
}
}
|
簡要說明一下代碼原理,先創建ProxyGenerator類實例,從名字就看得出來,是代理類生成器,然后實例化一個基於繼承自StandardInterceptor的TestInterceptor,這個TestInterceptor是一個自定義的攔截器,最后通過generator.CreateClassProxy<TestA>(new TestInterceptor())動態創建了一個繼承自TestA的動態代理類,這個代理類只有在運行時才會生成的,后面就可以如代碼所示,直接用動態代理類對象實例Test操作TestA的所有屬性與方法,當然這里需要注意,若需要被動態代理類所代理並攔截,則父類的屬性或方法必需是virtual,這點與我上面說的直接寫一個代理類相同。
上述代碼運行效果如下:
第三種:普通反射+利用Remoting的遠程訪問對象時的直實代理類來實現,代碼如下,這個可能相比以上兩種稍微復雜一點:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
|
using
System;
using
System.Collections.Generic;
using
System.Linq;
using
System.Runtime.Remoting.Activation;
using
System.Runtime.Remoting.Messaging;
using
System.Runtime.Remoting.Proxies;
using
System.Text;
using
System.Threading.Tasks;
namespace
ConsoleApp
{
class
Program
{
static
void
Main(
string
[] args)
{
var
A =
new
AopClass();
A.Hello();
var
aop =
new
AopClassSub(
"夢在旅途"
);
aop.Pro =
"test"
;
aop.Output(
"hlf"
);
aop.ShowMsg();
Console.ReadKey();
}
}
[AopAttribute]
public
class
AopClass : ContextBoundObject
{
public
string
Hello()
{
return
"welcome"
;
}
}
public
class
AopClassSub : AopClass
{
public
string
Pro =
null
;
private
string
Msg =
null
;
public
AopClassSub(
string
msg)
{
Msg = msg;
}
public
void
Output(
string
name)
{
Console.WriteLine(name +
",你好!-->P:"
+ Pro);
}
public
void
ShowMsg()
{
Console.WriteLine($
"構造函數傳的Msg參數內容是:{Msg}"
);
}
}
public
class
AopAttribute : ProxyAttribute
{
public
override
MarshalByRefObject CreateInstance(Type serverType)
{
AopProxy realProxy =
new
AopProxy(serverType);
return
realProxy.GetTransparentProxy()
as
MarshalByRefObject;
}
}
public
class
AopProxy : RealProxy
{
public
AopProxy(Type serverType)
:
base
(serverType) { }
public
override
IMessage Invoke(IMessage msg)
{
if
(msg
is
IConstructionCallMessage)
{
IConstructionCallMessage constructCallMsg = msg
as
IConstructionCallMessage;
IConstructionReturnMessage constructionReturnMessage =
this
.InitializeServerObject((IConstructionCallMessage)msg);
RealProxy.SetStubData(
this
, constructionReturnMessage.ReturnValue);
Console.WriteLine(
"Call constructor"
);
return
constructionReturnMessage;
}
else
{
IMethodCallMessage callMsg = msg
as
IMethodCallMessage;
IMessage message;
try
{
Console.WriteLine(callMsg.MethodName +
"執行前。。。"
);
object
[] args = callMsg.Args;
object
o = callMsg.MethodBase.Invoke(GetUnwrappedServer(), args);
Console.WriteLine(callMsg.MethodName +
"執行后。。。"
);
message =
new
ReturnMessage(o, args, args.Length, callMsg.LogicalCallContext, callMsg);
}
catch
(Exception e)
{
message =
new
ReturnMessage(e, callMsg);
}
Console.WriteLine(message.Properties[
"__Return"
]);
return
message;
}
}
}
}
|
以上代碼實現步驟說明:
1.這里定義的一個真實類AopClass必需繼承自ContextBoundObject類,而ContextBoundObject類又直接繼承自MarshalByRefObject類,表明該類是上下文綁定對象,允許在支持遠程處理的應用程序中跨應用程序域邊界訪問對象,說白了就是可以獲取這個真實類的所有信息,以便可以被生成動態代理。
2.定義繼承自ProxyAttribute的代理特性標識類AopAttribute,以表明哪些類可以被代理,同時注意重寫CreateInstance方法,在CreateInstance方法里實現通過委托與生成透明代理類的過程,realProxy.GetTransparentProxy() 非常重要,目的就是根據定義的AopProxy代理類獲取生成透明代理類對象實例。
3.實現通用的AopProxy代理類,代理類必需繼承自RealProxy類,在這個代理類里面重寫Invoke方法,該方法是統一執行被代理的真實類的所有方法、屬性、字段的出入口,我們只需要在該方法中根據傳入的IMessage進行判斷並實現相應的攔截代碼即可。
4.最后在需要進行Aop攔截的類上標注AopAttribute即可(注意:被標識的類必需是如第1條說明的繼承自ContextBoundObject類),在實際調用的過程中是感知不到任何的變化。且AopAttribute可以被子類繼承,也就意味着所有子類都可以被代理並攔截。
如上代碼運行效果如下:
這里順便分享微軟官方如果利用RealProxy類實現AOP的,詳見地址:https://msdn.microsoft.com/zh-cn/library/dn574804.aspx
第四種:反射+ 通過定義統一的出入口,並運用一些特性實現AOP的效果,比如:常見的MVC、WEB API中的過濾器特性 ,我這里根據MVC的思路,實現了類似的MVC過濾器的AOP效果,只是中間用到了反射,可能性能不佳,但效果還是成功實現了各種攔截,正如MVC一樣,既支持過濾器特性,也支持Controller中的Action執行前,執行后,錯誤等方法實現攔截
實現思路如下:
A.過濾器及Controller特定方法攔截實現原理:
1.獲取程序集中所有繼承自Controller的類型;
2.根據Controller的名稱找到第1步中的對應的Controller的類型:FindControllerType
3.根據找到的Controller類型及Action的名稱找到對應的方法:FindAction
4.創建Controller類型的實例;
5.根據Action方法找到定義在方法上的所有過濾器特性(包含:執行前、執行后、錯誤)
6.執行Controller中的OnActionExecuting方法,隨后執行執行前的過濾器特性列表,如:ActionExecutingFilter
7.執行Action方法,獲得結果;
8.執行Controller中的OnActionExecuted方法,隨后執行執行后的過濾器特性列表,如:ActionExecutedFilter
9.通過try catch在catch中執行Controller中的OnActionError方法,隨后執行錯誤過濾器特性列表,如:ActionErrorFilter
10.最后返回結果;
B.實現執行路由配置效果原理:
1.增加可設置路由模板列表方法:AddExecRouteTemplate,在方法中驗證controller、action,並獲取模板中的占位符數組,最后保存到類全局對象中routeTemplates;
2.增加根據執行路由執行對應的Controller中的Action方法的效果: Run,在該方法中主要遍歷所有路由模板,然后與實行執行的請求路由信息通過正則匹配,若匹配OK,並能正確找到Controller及Action,則說明正確,並最終統一調用:Process方法,執行A中的所有步驟最終返回結果。
需要說明該模擬MVC方案並沒有實現Action方法參數的的綁定功能,因為ModelBinding本身就是比較復雜的機制,所以這里只是為了搞清楚AOP的實現原理,故不作這方面的研究,大家如果有空可以實現,最終實現MVC不僅是ASP.NET MVC,還可以是 Console MVC,甚至是Winform MVC等。
以下是實現的全部代碼,代碼中我已進行了一些基本的優化,可以直接使用:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
|
public
abstract
class
Controller
{
public
virtual
void
OnActionExecuting(MethodInfo action)
{
}
public
virtual
void
OnActionExecuted(MethodInfo action)
{
}
public
virtual
void
OnActionError(MethodInfo action, Exception ex)
{
}
}
public
abstract
class
FilterAttribute : Attribute
{
public
abstract
string
FilterType {
get
; }
public
abstract
void
Execute(Controller ctrller,
object
extData);
}
public
class
ActionExecutingFilter : FilterAttribute
{
public
override
string
FilterType =>
"BEFORE"
;
public
override
void
Execute(Controller ctrller,
object
extData)
{
Console.WriteLine($
"我是在{ctrller.GetType().Name}.ActionExecutingFilter中攔截發出的消息!-{DateTime.Now.ToString()}"
);
}
}
public
class
ActionExecutedFilter : FilterAttribute
{
public
override
string
FilterType =>
"AFTER"
;
public
override
void
Execute(Controller ctrller,
object
extData)
{
Console.WriteLine($
"我是在{ctrller.GetType().Name}.ActionExecutedFilter中攔截發出的消息!-{DateTime.Now.ToString()}"
);
}
}
public
class
ActionErrorFilter : FilterAttribute
{
public
override
string
FilterType =>
"EXCEPTION"
;
public
override
void
Execute(Controller ctrller,
object
extData)
{
Console.WriteLine($
"我是在{ctrller.GetType().Name}.ActionErrorFilter中攔截發出的消息!-{DateTime.Now.ToString()}-Error Msg:{(extData as Exception).Message}"
);
}
}
public
class
AppContext
{
private
static
readonly
Type ControllerType =
typeof
(Controller);
private
static
readonly
Dictionary<
string
, Type> matchedControllerTypes =
new
Dictionary<
string
, Type>();
private
static
readonly
Dictionary<
string
, MethodInfo> matchedControllerActions =
new
Dictionary<
string
, MethodInfo>();
private
Dictionary<
string
,
string
[]> routeTemplates =
new
Dictionary<
string
,
string
[]>();
public
void
AddExecRouteTemplate(
string
execRouteTemplate)
{
if
(!Regex.IsMatch(execRouteTemplate,
"{controller}"
, RegexOptions.IgnoreCase))
{
throw
new
ArgumentException(
"執行路由模板不正確,缺少{controller}"
);
}
if
(!Regex.IsMatch(execRouteTemplate,
"{action}"
, RegexOptions.IgnoreCase))
{
throw
new
ArgumentException(
"執行路由模板不正確,缺少{action}"
);
}
string
[] keys = Regex.Matches(execRouteTemplate,
@"(?<={)\w+(?=})"
, RegexOptions.IgnoreCase).Cast<Match>().Select(c => c.Value.ToLower()).ToArray();
routeTemplates.Add(execRouteTemplate,keys);
}
public
object
Run(
string
execRoute)
{
//{controller}/{action}/{id}
string
ctrller =
null
;
string
actionName =
null
;
ArrayList args =
null
;
Type controllerType =
null
;
bool
findResult =
false
;
foreach
(
var
r
in
routeTemplates)
{
string
[] keys = r.Value;
string
execRoutePattern = Regex.Replace(r.Key,
@"{(?<key>\w+)}"
, (m) =>
string
.Format(
@"(?<{0}>.[^/\\]+)"
, m.Groups[
"key"
].Value.ToLower()), RegexOptions.IgnoreCase);
args =
new
ArrayList();
if
(Regex.IsMatch(execRoute, execRoutePattern))
{
var
match = Regex.Match(execRoute, execRoutePattern);
for
(
int
i = 0; i < keys.Length; i++)
{
if
(
"controller"
.Equals(keys[i], StringComparison.OrdinalIgnoreCase))
{
ctrller = match.Groups[
"controller"
].Value;
}
else
if
(
"action"
.Equals(keys[i], StringComparison.OrdinalIgnoreCase))
{
actionName = match.Groups[
"action"
].Value;
}
else
{
args.Add(match.Groups[keys[i]].Value);
}
}
if
((controllerType = FindControllerType(ctrller)) !=
null
&& FindAction(controllerType, actionName, args.ToArray()) !=
null
)
{
findResult =
true
;
break
;
}
}
}
if
(findResult)
{
return
Process(ctrller, actionName, args.ToArray());
}
else
{
throw
new
Exception($
"在已配置的路由模板列表中未找到與該執行路由相匹配的路由信息:{execRoute}"
);
}
}
public
object
Process(
string
ctrller,
string
actionName,
params
object
[] args)
{
Type matchedControllerType = FindControllerType(ctrller);
if
(matchedControllerType ==
null
)
{
throw
new
ArgumentException($
"未找到類型為{ctrller}的Controller類型"
);
}
object
execResult =
null
;
if
(matchedControllerType !=
null
)
{
var
matchedController = (Controller)Activator.CreateInstance(matchedControllerType);
MethodInfo action = FindAction(matchedControllerType, actionName, args);
if
(action ==
null
)
{
throw
new
ArgumentException($
"在{matchedControllerType.FullName}中未找到與方法名:{actionName}及參數個數:{args.Count()}相匹配的方法"
);
}
var
filters = action.GetCustomAttributes<FilterAttribute>(
true
);
List<FilterAttribute> execBeforeFilters =
new
List<FilterAttribute>();
List<FilterAttribute> execAfterFilters =
new
List<FilterAttribute>();
List<FilterAttribute> exceptionFilters =
new
List<FilterAttribute>();
if
(filters !=
null
&& filters.Count() > 0)
{
execBeforeFilters = filters.Where(f => f.FilterType ==
"BEFORE"
).ToList();
execAfterFilters = filters.Where(f => f.FilterType ==
"AFTER"
).ToList();
exceptionFilters = filters.Where(f => f.FilterType ==
"EXCEPTION"
).ToList();
}
try
{
matchedController.OnActionExecuting(action);
if
(execBeforeFilters !=
null
&& execBeforeFilters.Count > 0)
{
execBeforeFilters.ForEach(f => f.Execute(matchedController,
null
));
}
var
mParams = action.GetParameters();
object
[] newArgs =
new
object
[args.Length];
for
(
int
i = 0; i < mParams.Length; i++)
{
newArgs[i] = Convert.ChangeType(args[i], mParams[i].ParameterType);
}
execResult = action.Invoke(matchedController, newArgs);
matchedController.OnActionExecuted(action);
if
(execBeforeFilters !=
null
&& execBeforeFilters.Count > 0)
{
execAfterFilters.ForEach(f => f.Execute(matchedController,
null
));
}
}
catch
(Exception ex)
{
matchedController.OnActionError(action, ex);
if
(exceptionFilters !=
null
&& exceptionFilters.Count > 0)
{
exceptionFilters.ForEach(f => f.Execute(matchedController, ex));
}
}
}
return
execResult;
}
private
Type FindControllerType(
string
ctrller)
{
Type matchedControllerType =
null
;
if
(!matchedControllerTypes.ContainsKey(ctrller))
{
var
assy = Assembly.GetAssembly(
typeof
(Controller));
foreach
(
var
m
in
assy.GetModules(
false
))
{
foreach
(
var
t
in
m.GetTypes())
{
if
(ControllerType.IsAssignableFrom(t) && !t.IsAbstract)
{
if
(t.Name.Equals(ctrller, StringComparison.OrdinalIgnoreCase) || t.Name.Equals($
"{ctrller}Controller"
, StringComparison.OrdinalIgnoreCase))
{
matchedControllerType = t;
matchedControllerTypes[ctrller] = matchedControllerType;
break
;
}
}
}
}
}
else
{
matchedControllerType = matchedControllerTypes[ctrller];
}
return
matchedControllerType;
}
private
MethodInfo FindAction(Type matchedControllerType,
string
actionName,
object
[] args)
{
string
ctrlerWithActionKey = $
"{matchedControllerType.FullName}.{actionName}"
;
MethodInfo action =
null
;
if
(!matchedControllerActions.ContainsKey(ctrlerWithActionKey))
{
if
(args ==
null
) args =
new
object
[0];
foreach
(
var
m
in
matchedControllerType.GetMethods(BindingFlags.Instance | BindingFlags.Public))
{
if
(m.Name.Equals(actionName, StringComparison.OrdinalIgnoreCase) && m.GetParameters().Length == args.Length)
{
action = m;
matchedControllerActions[ctrlerWithActionKey] = action;
break
;
}
}
}
else
{
action = matchedControllerActions[ctrlerWithActionKey];
}
return
action;
}
}
|
使用前,先定義一個繼承自Controller的類,如:TestController,並重寫相應的方法,或在指定的方法上加上所需的過濾器特性,如下代碼所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
public
class
TestController : Controller
{
public
override
void
OnActionExecuting(MethodInfo action)
{
Console.WriteLine($
"{action.Name}執行前,OnActionExecuting---{DateTime.Now.ToString()}"
);
}
public
override
void
OnActionExecuted(MethodInfo action)
{
Console.WriteLine($
"{action.Name}執行后,OnActionExecuted--{DateTime.Now.ToString()}"
);
}
public
override
void
OnActionError(MethodInfo action, Exception ex)
{
Console.WriteLine($
"{action.Name}執行,OnActionError--{DateTime.Now.ToString()}:{ex.Message}"
);
}
[ActionExecutingFilter]
[ActionExecutedFilter]
public
string
HelloWorld(
string
name)
{
return
($
"Hello World!->{name}"
);
}
[ActionExecutingFilter]
[ActionExecutedFilter]
[ActionErrorFilter]
public
string
TestError(
string
name)
{
throw
new
Exception(
"這是測試拋出的錯誤信息!"
);
}
[ActionExecutingFilter]
[ActionExecutedFilter]
public
int
Add(
int
a,
int
b)
{
return
a + b;
}
}
|
最后前端實際調用就非常簡單了,代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
class
MVCProgram
{
static
void
Main(
string
[] args)
{
try
{
var
appContext =
new
AppContext();
object
rs = appContext.Process(
"Test"
,
"HelloWorld"
,
"夢在旅途"
);
Console.WriteLine($
"Process執行的結果1:{rs}"
);
Console.WriteLine(
"="
.PadRight(50,
'='
));
appContext.AddExecRouteTemplate(
"{controller}/{action}/{name}"
);
appContext.AddExecRouteTemplate(
"{action}/{controller}/{name}"
);
object
result1 = appContext.Run(
"HelloWorld/Test/夢在旅途-zuowenjun.cn"
);
Console.WriteLine($
"執行的結果1:{result1}"
);
Console.WriteLine(
"="
.PadRight(50,
'='
));
object
result2 = appContext.Run(
"Test/HelloWorld/夢在旅途-zuowenjun.cn"
);
Console.WriteLine($
"執行的結果2:{result2}"
);
Console.WriteLine(
"="
.PadRight(50,
'='
));
appContext.AddExecRouteTemplate(
"{action}/{controller}/{a}/{b}"
);
object
result3 = appContext.Run(
"Add/Test/500/20"
);
Console.WriteLine($
"執行的結果3:{result3}"
);
object
result4 = appContext.Run(
"Test/TestError/夢在旅途-zuowenjun.cn"
);
Console.WriteLine($
"執行的結果4:{result4}"
);
}
catch
(Exception ex)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($
"發生錯誤:{ex.Message}"
);
Console.ResetColor();
}
Console.ReadKey();
}
}
|
可以看到,與ASP.NET MVC有點類似,只是ASP.NET MVC是通過URL訪問,而這里是通過AppContext.Run 執行路由URL 或Process方法,直接指定Controller、Action、參數來執行。
通過以上調用代碼可以看出路由配置還是比較靈活的,當然參數配置除外。如果大家有更好的想法也可以在下方評論交流,謝謝!
MVC代碼執行效果如下: