欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

白话系列之实现自己简单的mvc式webapi框架

程序员文章站 2022-05-01 09:25:52
前言:此文为极简mvc式的api框架,只当做入门api的解析方式,并且这里也不算是mvc框架,因为没有view层,毕竟现在大部分都属于前后端分离,当然也可以提供view层,因为只是将view当做文本返回. github地址:https://github.com/BestHYC/WebAPISolut ......

前言:此文为极简mvc式的api框架,只当做入门api的解析方式,并且这里也不算是mvc框架,因为没有view层,毕竟现在大部分都属于前后端分离,当然也可以提供view层,因为只是将view当做文本返回.

github地址:https://github.com/besthyc/webapisolution.git

演示例子:

白话系列之实现自己简单的mvc式webapi框架

 

 

 

目标:

1.针对home/default进行解析.2.提供简单的httpcontext处理.3.对mvc的框架有个最简单的了解.4.一步步通过好的框架风格,建立自己的框架风格

关键还有一点就是很多东西比你想的还简单,难得是里面可扩展可维护的实现方式以及面面俱到的安全验证.但是前人栽树后人乘凉,我们借鉴就好.

一:创建controller对象

目标:通过工厂模式,获取所有controller,并创建其对象

1.1.定义接口,并定义所有controller继承的基类apibasecontroller

    public interface iapicontroller
    {
    }
    public abstract class apibasecontroller : iapicontroller
    {
    }

  模拟创建一个controller,这就是我们自己定义的控制器,其实就是你自己实现的控制器

    public class homecontroller : apibasecontroller
    {
    }

1.2.通过工厂模式,通过名称调用对应的控制对象,因为控制器基本上不会改变,然后通过name找到对应的控制器

   public interface icontrollerfactory
    {
        iapicontroller createcontroller(string name);
    }
    public class defaultcontrollerfactory : icontrollerfactory
    {
        private static list<type> controllertypes = new list<type>();
        static defaultcontrollerfactory()
        {
            assembly assembly = assembly.getexecutingassembly();
            foreach (type type in assembly.gettypes().where(type => typeof(iapicontroller).isassignablefrom(type)))
            {
                controllertypes.add(type);
            }
        }
        public iapicontroller createcontroller(string name)
        {
            if (name.indexof("controller") == -1) name += "controller";
            type controllertype = controllertypes.firstordefault(c => string.compare(name, c.name, true) == 0);
            if (controllertype == null)
            {
                return null;
            }
            return (iapicontroller)activator.createinstance(controllertype);
        }
    }

ok,这样就可以取个简单的控制器工厂模式.

二:既然控制器已经创建,那么同样的情况调用里面的方法,目前home/default,会直接解析成default方法

1.先简单的实现出调用方法

 

    public interface actioninvoker
    {
        void invokeaction(object type, string actionname);
    }
    public class defaultactioninvoker : actioninvoker
    {
        public void invokeaction(object controller, string actionname)
        {
            methodinfo methodinfo = controller.gettype().getmethods().first(m => string.compare(actionname, m.name, true) == 0);
            methodinfo.invoke(controller, null);
        }
    }

 

此处非常简单,就直接调用对应的方法名即可,这就是webapi在解析路由中出现的最简单的实现方式,

其实理论就是如此简单,没其他人想的那么困难,接下来开始会做修饰,一步步来构建一个也是简单,但是稍微有点健全的api

三:优化代码,针对控制器及方法做缓存

/// <summary>
    /// 全局所有iapicontroller类型的操作都是由此处进行缓存
    /// 其他地方只做类型处理,比如 a/b,那么是对应的是acontroller 还是a,都是其他地方做处理
    /// 注意此处,只当做类型及方法的缓存,不做任何对执行返回结果及传递对象的处理,保持功能单一
    /// 保持路径单一,即a/b中a控制器只有1个,b方法也只有1个,即使有重载,也必须得通过 路由名 进行区分
    /// </summary>
    internal static class apicontrolleractioncache
    {
        private static dictionary<string, type> s_controllertypes = new dictionary<string, type>();
        private static dictionary<type, actioncache> s_actioncache = new dictionary<type, actioncache>();
        static apicontrolleractioncache()
        {
            assembly assembly = assembly.getexecutingassembly();
            foreach (type type in assembly.gettypes().where(type => typeof(iapicontroller).isassignablefrom(type)))
            {
                string name = type.name;
                if(type.getcustomattribute<prerouteattribute>() != null)
                {
                    name = type.getcustomattribute<prerouteattribute>().preroutename;
                }
                if (s_controllertypes.containskey(name)) throw new exception($"{name}存在相同的路由名,请保持路由唯一");
                s_controllertypes.add(name, type);
                s_actioncache.add(type, new actioncache(type));
            }
        }
        public static type getcontroller(string controllername)
        {
            if (!s_controllertypes.containskey(controllername)) throw new exception("没有此路由对应的类型");
            return s_controllertypes[controllername];
        }
        /// <summary>
        /// 通过路由值获取对应的委托方法
        /// </summary>
        /// <param name="controllername"></param>
        /// <param name="actionname"></param>
        /// <returns></returns>
        public static func<iapicontroller, object[], object> getmethod(type controller, string actionname)
        {
            if(!s_actioncache.containskey(controller)) throw new exception("没有此路由对应的类型");
            actioncache cache = s_actioncache[controller];
            if (!cache.containskey(actionname)) throw new exception("没有此路由对应的方法");
            return cache.getmethodinfo(actionname);
        }
    }
    public class actioncache
    {
        private dictionary<string, methodinfo> m_methodinfo;
        private dictionary<methodinfo, func<iapicontroller, object[], object>> m_funccache ;
        public methodinfo this[string name]
        {
            get
            {
                return m_methodinfo[name];
            }
        }
        public boolean containskey(string name)
        {
            return m_methodinfo.containskey(name);
        }
        private object m_lock = new object();
        /// <summary>
        /// 可以考虑延迟加载
        /// </summary>
        /// <param name="type"></param>
        public actioncache(type type)
        {
            m_methodinfo = new dictionary<string, methodinfo>();
            m_funccache = new dictionary<methodinfo, func<iapicontroller, object[], object>>();
            foreach(methodinfo info in type.getmethods())
            {
                string name = info.name;
                if(info.getcustomattribute<routeattribute>() != null)
                {
                    name = info.getcustomattribute<routeattribute>().routename;
                }
                if (m_methodinfo.containskey(name)) throw new exception($"{type.name}中{name}重复,请保持路径唯一");
                m_methodinfo.add(name, info);
            }
        }
        /// <summary>
        /// 通过名称获取对应的委托
        /// </summary>
        /// <param name="methodinfo"></param>
        /// <returns>iapicontroller:传递的执行方法, object[]:方法参数, object 返回值,void为空</returns>
        public func<iapicontroller, object[], object> getmethodinfo(string methodname)
        {
            methodinfo methodinfo = m_methodinfo[methodname];
            if (!m_funccache.containskey(methodinfo))
            {
                lock (m_lock)
                {
                    if (!m_funccache.containskey(methodinfo))
                    {
                        m_funccache.add(methodinfo, createexecutor(methodinfo));
                    }
                }
            }
            return m_funccache[methodinfo];
        }
        private func<object, object[], object> createexecutor(methodinfo methodinfo)
        {
            parameterexpression target = expression.parameter(typeof(object), "target");
            parameterexpression arguments = expression.parameter(typeof(object[]), "arguments");
            list<expression> parameters = new list<expression>();
            parameterinfo[] paraminfos = methodinfo.getparameters();
            for (int32 i = 0; i < paraminfos.length; i++)
            {
                parameterinfo paraminfo = paraminfos[i];
                binaryexpression getelementbyindex = expression.arrayindex(arguments, expression.constant(i));
                unaryexpression convertoparamtertype = expression.convert(getelementbyindex, paraminfo.parametertype);
                parameters.add(convertoparamtertype);
            }
            unaryexpression instancecast = expression.convert(target, methodinfo.reflectedtype);
            methodcallexpression methodcall = expression.call(instancecast, methodinfo, parameters);
            unaryexpression convertoobjecttype = expression.convert(methodcall, typeof(object));
            return expression.lambda<func<object, object[], object>>(convertoobjecttype, target, arguments).compile();
        }
    }

 注意:此处对全局controller做了一次缓存,限制为不会通过传的参数进行判定使用哪个方法,只允许单个接口存在,即

1.如果在不同空间下具有相同类型名的,必须具有不同的preroute特性限定,

2.如果一个类方法重载,得通过route特性限定唯一标识

3.表达式树是通过创建一个委托,传递当前的controller对象调用对应的方法

以上并不算框架,只属于单一调用方法功能实现,并做优化,接下来属于api框架实现

四:api实现首先得确定传输的值及协议标准,

4.1.确定传输中的必须信息,去掉其他所有的额外信息后有如下几点,为求简单,先全部保存成字符串形式:

从哪里来(urlreferrer),到哪里去 url(请求的接口),

url请求的参数(a/b?query=1中的query值解析),body中保存的值(frombody),包含的请求头参数(headers)

所有请求处理完成后,返回的值信息

 

public class httprequest
    {
        /// <summary>
        /// 从哪里来的
        /// </summary>
        public string urlreferrer { get; }
        /// <summary>
        /// 到哪里去
        /// </summary>
        public string uri { get; set; }
        /// <summary>
        /// uri请求参数处理
        /// </summary>
        public string queryparams { get; set; }
        /// <summary>
        /// 请求的内容
        /// </summary>
        public string requestcontent { get; set; }
        /// <summary>
        /// 请求头参数
        /// </summary>
        public string headers { get; set; }
    }
    public class httpresponse
    {
        /// <summary>
        /// 返回的内容
        /// </summary>
        public string responsecontent { get; set; }
    }
    public class httpcontext
    {
        public httprequest request { get; }
        public httpresponse response { get; }
    }

 

此处只是单纯做展示使用,在后期会写一个mq中间件时候,在做扩展.只展示httpcontext

4.2.对http请求做一个统一接口处理

    public class urlroutingmodule : iroutingmodule
    {
        public void init(httpbasecontext context)
        {
            
        }
    }

五:通过路由模板收集

5.1.在写api的时候,会添加一个defaultapi匹配路由,注意:这里以mvc的解析规则实现

    
 routeconfig.registerroutes(routetable.routes);
public static class routeconfig { public static void registerroutes(routecollection routes) { routes.ignoreroute("{resource}.axd/{*pathinfo}"); routes.maproute( name: "defaultapi", routetemplate: "api/{controller}/{action}/{id}", defaults: new { controller = "home", action = "index", id = urlparameter.optional } ); } }

5.2.考虑此模板的功能及实现方式

1.因为可能有多个默认匹配,并且执行的是顺序加载过程,所以有个routetable静态类收集全局模板路由

2.路由有对应的默认路由及路由值(route routedata)及收集collection

3.route实现路由解析,并提供routedata值,并提供实现

4.在解析route的时候,通过生成的路由句柄defaultrouterhandler去routedata进行后续处理

5.通过defaultapihandler对路由进行解析,生成对应的控制器

6.通过controller,对当前的action进行解析,并绑定路由

7.调取当前执行的方法后,获取返回的值对象,并对返回值进行处理

大体的模型调用过程如图

白话系列之实现自己简单的mvc式webapi框架

 

 6:实现代码:请查看对应的github地址

https://github.com/besthyc/webapisolution.git

7.例子:(已经准备好对应的json序列化解析,赋值粘贴即可,看上图)