C# 实现AOP 的几种常见方式

AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,经过预编译方式和运行期动态代理实现程序功能的中统一处理业务逻辑的一种技术,比较常见的场景是:日志记录,错误捕获、性能监控等html

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类的日志都会改变,是否是很爽。上述执行结果以下图示:app

 以上经过定义代理类的方式可以实如今方法中统一进行各类执行点的拦截代码逻辑处理,拦截点(或者称为:横切面,切面点)通常主要为:执行前,执行后,发生错误,虽然解决了以前直接调用真实类RealA时,须要重复增长各类逻辑代码的问题,但随之而来的新问题又来了,那就是当一个系统中的类很是多的时候,若是咱们针对每一个类都定义一个代理类,那么系统的类的个数会成倍增长,并且不一样的代理类中可能某些拦截业务逻辑代码都是相同的,这种状况一样是不能容许的,那有没有什么好的办法呢?答案是确定的,如下是我结合网上资源及我的总结的以下几种常见的实现AOP的方式,各位能够参考学习。ide

第一种:静态织入,即:在编译时,就将各类涉及AOP拦截的代码注入到符合必定规则的类中,编译后的代码与咱们直接在RealA调用属性或方法先后增长代码是相同的,只是这个工做交由编译器来完成。函数

PostSharp:PostSharp的Aspect是使用Attribute实现的,咱们只需事先经过继承自OnMethodBoundaryAspect,而后重写几个常见的方法便可,如:OnEntry,OnExit等,最后只须要在须要进行AOP拦截的属性或方法上加上AOP拦截特性类便可。因为PostSharp是静态织入的,因此相比其它的经过反射或EMIT反射来讲效率是最高的,但PostSharp是收费版本的,并且网上的教程比较多,我就不在此重复说明了,你们能够参见:使用PostSharp在.NET平台上实现AOPpost

第二种: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代码执行效果以下:

相关文章
相关标签/搜索