zoukankan      html  css  js  c++  java
  • 项目演化系列--路由解析

    前言

      搭建一个web项目不可能一蹴而就,会先从最基础的开始,不考虑数据库的支持,不考虑任何业务逻辑,使其以最简单的方式运行起来,然后慢慢的填充各个部分,使其从一个单机项目发展到集群分布式的大型项目,而其中最基础的部分便是路由了。

      之前已经写过一篇关于web form支持mvc的文章了,该文章为:《web form中自定义HttpHandler仿mvc》。

      此篇文章会在之前文章的基础上进行重构,抽取一些公用的代码,其实在开发过程当中,公用代码是很重要的一部分,它既包括了通用的代码,例如:利用表达式树实现反射、从Http请求中获取请求对象、加密解密等,也包括了一些业务公用代码,例如:根据路由访问view文件夹下相应的aspx或ascx、根据规则生成相应的前端验证组件、前端的复合组件等,在不断的开发、重构过程当中,就可以慢慢形成自己的核心代码库了,这对于开发是非常有利的,虽然其中会涉及到版本问题,会是很多开发人员萌生怯意,但是只要处理的当,好处还是大于坏处的。

    重构

      首先解析路由的大致代码如下:

    var match = URL_RULE.Match(context.Request.AppRelativeCurrentExecutionFilePath);
    if (!match.Success)
    {
    	//404
    	return;
    }
    
    var controller = Assembly.Load("业务层程序集").CreateInstance("Controller完整程序集" + match.Groups[1].Value, true);
    if (controller == null)
    {
    	//404
    	return;
    }
    
    var method = controller.GetType().GetMethod(match.Groups[2].Value, BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public);
    if (method == null)
    {
    	//404
    	return;
    }
    
    object[] methodArgs;
    if (method.GetParameters().Length > 0)
    {
    	//从HttpRequest中获取方法参数
    }
    else
    {
    	methodArgs = new object[] { };
    }
    
    try
    {
    	method.Invoke(controller, methodArgs);
    }
    catch
    {
    	//error
    }
    

      接下来要考虑的就是如何在其他的项目中重用以上的代码,通过观察,不同项目中有所差别的主要有以下几个部分:

      1、路由解析规则(正则)

      2、404处理

      3、Error处理

      那么可以开放类似如下的接口,提供给其他的项目实现,并且将MvcHandler转移到公用库中去,代码调整如下:

    //配置接口
    public interface IMvcSetting
    {
    	string ControllerAssemblyString { get; }
    
    	string ControllerNamespaceFormat { get; }
    
    	Regex UrlRule { get; }
    
    	ActionResult Page404 { get; }
    
    	ActionResult PageError { get; }
    }
    
    //mvcHandler
    private static IMvcSetting s_Setting;
    
    public static void Init(IMvcSetting setting)
    {
    	s_Setting = setting;
    }
    
    public void ProcessRequest(HttpContext context)
    {
    	var match = s_Setting.UrlRule.Match(context.Request.AppRelativeCurrentExecutionFilePath);
    	if (!match.Success)
    	{
    		s_Setting.PageError.ExecuteResult(context);
    		return;
    	}
    
    	var typeName = string.Format(s_Setting.ControllerNamespaceFormat, s_Setting.ControllerAssemblyString, match.Groups[1].Value);
    	var controller = Assembly.Load(s_Setting.ControllerAssemblyString).CreateInstance(typeName, true);
    	if (controller == null)
    	{
    		s_Setting.Page404.ExecuteResult(context);
    		return;
    	}
    
    	var method = controller.GetType().GetMethod(match.Groups[2].Value, BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public);
    	if (method == null)
    	{
    		s_Setting.Page404.ExecuteResult(context);
    		return;
    	}
    
    	object[] methodArgs;
    	if (method.GetParameters().Length > 0)
    	{
    		//从HttpRequest中获取方法参数
    	}
    	else
    	{
    		methodArgs = new object[] { };
    	}
    
    	try
    	{
    		(method.Invoke(controller, methodArgs) as ActionResult).ExecuteResult(context);
    	}
    	catch
    	{
    		s_Setting.PageError.ExecuteResult(context);
    	}
    }
    

      有了公用库后,其他项目就可以直接引用该MvcHandler,并实现相应的IMvcSetting,然后在Global.asax中调用MvcHandler.Init来进行设置。

      请求参数的话,不管是QueryString还是Params都是NameValueCollection,可以在公用库为NameValueCollection增加一个扩展方法,根据ParameterInfo[]获取请求数据,代码就不提供了。

      但是当方法的参数无法从QueryString或者Params中获取时,就比较麻烦了,因此如果将方法中的参数转换成一个对象,代码如下:

    public class LoingRequest
    {
    	public string Name { get; set; }
    
    	public string Password { get; set; }
    }
    
    public class UserController
    {
    	public ActionResult Login(LoginRequest req)
    	{
    		//coding
    	}
    }
    

      那么只要将扩展方法修改为转化成对象,代码大致如下:

    public static class NameValueCollectionExtension
    {
    	public static object ExtractFor(this NameValueCollection collection, string key, Type valueType)
    	{
    		try
    		{
    			if (valueType == typeof(Guid))
    			{
    				return new Guid(collection[key]);
    			}
    			else if (valueType.IsEnum)
    			{
    				return Enum.Parse(valueType, collection[key]);
    			}
    			else if (valueType.IsClass && valueType != typeof(string))
    			{
    				return collection.ExtractFor(valueType);
    			}
    			else if (valueType.IsValueType && valueType.IsGenericType)
    			{
    				string value = collection[key];
    				if (string.IsNullOrEmpty(value))
    					return null;
    
    				return collection.ExtractFor(key, valueType.GetGenericArguments()[0]);
    			}
    			return Convert.ChangeType(collection[key], valueType);
    		}
    		catch
    		{
    			return valueType != typeof(string) ? Activator.CreateInstance(valueType) : null;
    		}
    	}
    
    	public static object ExtractFor(this NameValueCollection collection, Type valueType)
    	{
    		try
    		{
    			var obj = Activator.CreateInstance(valueType);
    			foreach (var property in valueType.GetProperties(
    				BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty))
    			{
    				if (property.PropertyType.IsClass && property.PropertyType != typeof(string))
    					continue;
    
    				property.SetValue(obj, collection.ExtractFor(property.Name, property.PropertyType), null);
    			}
    			return obj;
    		}
    		catch
    		{
    			return Activator.CreateInstance(valueType);
    		}
    	}
    }
    
    //mvcHandler
    object[] methodArgs;
    if (method.GetParameters().Length > 0)
    {
    	var reqArg = context.Request.QueryString.ExtractFor(method.GetParameters()[0].ParameterType);
    	methodArgs = new object[]{ reqArg };
    }
    

      以上路由方法获取参数对象并不考虑是get或者post,真实环境中肯定不可能都是get方法的,因此路由方法需要一个标识来判断,这时候就轮到Attribute出场了。

      有了Attribute不仅可以标识Post、Get,也可以用来标识是否需要认证等,此处开发人员不要考虑路由方法既可以post访问,又可以get访问,这样只会增加判断的复杂度,当方法需要重用的时候,只要将内容重构成一个公用的方法,然后让post、get方法都调用它既可,代码调整如下:

    if (method.GetParameters().Length > 0)
    {
    	var isPost = method.GetCustomAttributes(typeof(HttpPostAttribute), false).Length > 0;
    	var nameValues = isPost ? context.Request.Params : context.Request.QueryString;
    	var reqArg = nameValues.ExtractFor(method.GetParameters()[0].ParameterType);
    	if (isPost)
    	{
    		//设置文件类型的属性
    	}
    }

    结语

      对于路由的重构就差不多结束了,由于本人的表达能力不好,因此代码占大部分,如果以上有错误,请各位给我留言,我会尽快修正,谢谢。

  • 相关阅读:
    数据库
    数据库
    数据库
    数据库
    数据库
    数据库
    windows
    LeetCode : Word Pattern
    LeetCode : Perfect Number
    LeetCode : Minimum Depth of Binary Tree
  • 原文地址:https://www.cnblogs.com/ahl5esoft/p/5069608.html
Copyright © 2011-2022 走看看