C# 實現AOP 的幾種常見方式

来源:http://www.cnblogs.com/zuowj/archive/2017/09/10/7501896.html
-Advertisement-
Play Games

AOP為Aspect Oriented Programming的縮寫,意為:面向切麵編程,通過預編譯方式和運行期動態代理實現程式功能的中統一處理業務邏輯的一種技術,比較常見的場景是:日誌記錄,錯誤捕獲、性能監控等 AOP的本質是通過代理對象來間接執行真實對象,在代理類中往往會添加裝飾一些額外的業務代 ...


AOP為Aspect Oriented Programming的縮寫,意為:面向切麵編程,通過預編譯方式和運行期動態代理實現程式功能的中統一處理業務邏輯的一種技術,比較常見的場景是:日誌記錄,錯誤捕獲、性能監控等

AOP的本質是通過代理對象來間接執行真實對象,在代理類中往往會添加裝飾一些額外的業務代碼,比如如下代碼:

    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)

如下代碼:

    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實現方式,代碼也還是比較簡單的,效率相對第一種要慢一點,但對於普通的反射來說又高一些,代碼實現如下:

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的遠程訪問對象時的直實代理類來實現,代碼如下,這個可能相比以上兩種稍微複雜一點:

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等。

以下是實現的全部代碼,代碼中我已進行了一些基本的優化,可以直接使用:

    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,並重寫相應的方法,或在指定的方法上加上所需的過濾器特性,如下代碼所示:

    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;
        }
    }

最後前端實際調用就非常簡單了,代碼如下:

    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代碼執行效果如下:


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • ViewComponent 1、View 組件介紹 在ASP.NET CORE MVC中,View組件有點類似於partial views,但是他們更強大,View組件不能使用model binding,當你調用它的時候僅僅依賴與你提供的數據一個View組件特點: .呈現一大塊而不是一個整體的響應。 ...
  • 創建StringHelper類 首先資料庫里的資料是加密了的。 創建將指定的字元串加密為MD5密文方法 public static string ToMD5(string source) { StringBuilder sb = new StringBuilder(); MD5 md5 = MD5. ...
  • 背水一戰 Windows 10 之 控制項(媒體類): InkCanvas 保存和載入, InkCanvas 手寫識別 ...
  • 請訪問 http://balabiu.com/?p=16 後續本文更新將在這裡; 將設計伺服器端非同步接受客戶端連接和客戶端消息。 ...
  • 怎麼解決 Fiddler 抓包https配置 提示creation of the root certificate was not successful 證書安裝不成功 ...
  • C 是企業中廣泛使用的編程語言,特別是那些依賴微軟的程式語言。如果您使用C 構建應用程式,則最有可能使用Visual Studio,並且已經尋找了一些擴展來對您的開發進行管理。但是,這個工具列表可能會改變您編寫C 代碼的方式。 C 編程的最佳工具有以下幾類: "IDE" "VS擴展" "編譯器、編輯 ...
  • 設計原則:萬物皆對象 背景:在我的項目中,即需要與硬體通過Socket連接通訊,又需要給App提供Wcf服務操作介面,雖然都完成了,但是卻是一個控制台(雖然我很喜歡控制台,因為它簡單易用),把它放到伺服器運行,總有一個黑乎乎的視窗,總感覺不雅(原諒我的強迫症)。於是各種百度谷歌如何創建運行WIndo ...
  • 設計原則:萬物皆對象 背景:微軟提供了一套強大的通信框架Wcf,瞭解請看百度百科:ttps://baike.baidu.com/item/Wcf/7374854?fr=aladdin 雖然這套通信框架很強大,但是配置起來也不簡單,因此導致很多人望而卻步(包括我),我這人向來不喜歡麻煩,喜歡簡單,最好 ...
一周排行
    -Advertisement-
    Play Games
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...