asp.net HC框架
- 框架简介
- .net core和.net framework框架配置
- 如何添加新页面
- 页面变量(开发篇)
- [Get] [Post] 特性 (开发篇)
- 请求参数 Request和Response(开发篇)
- [Cache]页面缓存特性(开发篇)
- 追踪器(开发篇)
- Url重写&短域名(配置篇)
- 自定义页面后缀名&自定义非驱动页面(配置篇)
- 自定义页面位置(配置篇)
- 自定义过滤符合后缀名的请求(配置篇)
- 源码传送门(核心代码)
框架简介
asp.net HC框架是区别于mvc和webform .net跨平台开发的web框架
框架有以下特点
1.跨平台 支持运行在linux和windows上
2.开发跨平台 在windows. netFramework环境上开发,发布在linux上
3.超轻量级框架
4.支持vue和各类前段框架
5.配置简单
.net core和.net framework框架配置
正常情况下直接F5运行 访问 localhost:端口/__info 即可得到页面 如果访问不了 请查看浏览器运行的端口是多少 改一下HCMiddleware.cs(.net core)或Gloabal.asax.cs(.net framework)中的
然后F5运行该项目 访问 localhost:端口/__info 即可得到HC默认系统页面
如果你要手动配置那么
第一步:引入程序集HC.dll
第二步(.net core):加入一个中间件然后
更改中间件如下面代码。然后app.UseHC();
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
using HCFrameWork;
namespace HC.Core
{
// You may need to install the Microsoft.AspNetCore.Http.Abstractions package into your project
public class HCMiddleware
{
private readonly RequestDelegate _next;
public HCMiddleware(RequestDelegate next)
{
GlobalController.InitHCFrameWork<Program>(
new Dictionary<string, string>() {
{ "View", "Controller" }//MVC
,{ "Webform", "Webform" }//WebForm
}//路由
//, "C:\\publish\\TestHCFrameWork\\"//分盘符部署 页面路径 最后要加\\代表根文件夹 用于分布式 磁盘映射 发布一个文件夹即可 linux\\变/即可
, diyExt: "|.html|.jsp|"//系统支持.html和.jsp来驱动控制器
,isLinux:true//是否是Linux
);
_next = next;
}
public Task Invoke(HttpContext httpContext)
{
string url = httpContext.Request.GetDisplayUrl().ToString().Replace("www", string.Empty);
GlobalController.ControlRoute(httpContext,
new string[] {".ico",".jpg",".png",".htm",".js",".css",".jpeg",".bmp",".gif"},//过滤后缀名文件
new string[] {//配置测试的域名和发布的域名
"localhost:51330",
"netcore.dichan.com",
},
new string[]{
"/Default.html"//过滤指定路径的文件 不驱动Controller一般用于静态页面
},
new Dictionary<string, string>(){//Rewriter配置<访问的短局域名,映射View路径(根文件夹为准)>
{"/sdt.html","/Views/Test/FrameWorkTest/ShortDomainTest.html"},
{"/getindex.html","/Views/Test/GetIndex.html"}
}, url);
return Task.CompletedTask;
}
}
public static class HCMiddlewareExtensions
{
public static IApplicationBuilder UseHC(this IApplicationBuilder builder)
{
return builder.UseMiddleware<HCMiddleware>();
}
}
}
第二步(.net Framework):更改Global.asax.cs文件如下代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.SessionState;
using HCFrameWork;
namespace TestHCFrameWork
{
public class Global : System.Web.HttpApplication
{
//public static GlobalController gc;
void Application_Start(object sender, EventArgs e)
{
GlobalController.InitHCFrameWork<Global>(new Dictionary<string, string>() {
{"View","Controller"}//路由
}
//, "C:\\publish\\TestHCFrameWork\\"//分盘符部署 页面路径 最后要加\\代表根文件夹 用于分布式 磁盘映射 发布一个文件夹即可
, diyExt: "|.html|.jsp|"//系统支持.html和.jsp来驱动控制器
);
}
protected void Application_BeginRequest(object sender, EventArgs e)
{
string url = Request.Url.ToString().Replace("www", string.Empty);
GlobalController.ControlRoute(
new string[] { ".ico", ".jpg", ".png", ".htm", ".js", ".css", ".jpeg",".bmp" },//过滤后缀名文件
new string[] {//配置测试的域名和发布的域名
"localhost:7292",
"housevote2019.dichan.com"
},
new string[]{//过滤指定路径的文件
//"/Views/Index.jsp",
"/Default.html"
},
new Dictionary<string, string>(){//Rewriter配置<访问的短局域名,映射View路径(根文件夹为准)>
{"/Index.html","/Views/Web/Index.html"},
{"/ShareBanner.html","/Views/Web/ShareBanner.html"}
},url);
}
}
}
第三步:然后更改下面的开发环境和发布环境的域名 这样就配置成功了。
如何添加新页面
方式1.
在View文件夹下添加一个xxx.html (xxx例如Index)
在Controller里添加一个对应名称和路径的xxx.cs的类并且让该类继承HCController并且给一个入口函数(函数名随意)并添加无参数的[Get]特性(如下图)编译后 访问localhost:端口/Views/xxx.html即可
注:如果你是Webform的开发者 你可以设置View和Controller是一个文件夹 入口函数写为Page_Load如下图
using HCFrameWork;
namespace HC.Core.Controller
{
public class Index:HCController
{
[Get]
public void Main()
{
}
}
}
方式2.
添加HC模板(参考地址:)
然后在Controller里鼠标右键添加新项选择Visual C#
然后在View中添加一个html 运行访问地址即可进入到入口函数中
看到这你完全可以进行开发了。
页面变量(开发篇)
HC架构为我们提供了嵌入式代码块
变量式代码块 @[XXX] 它是用来绑定对应Controller中定义为public的变量(固定格式)
自定义代码块 @:XXX 它是自行定义的代码块 它可以自行定义 __XXX *XXX $XXX都可以
下面我们来实现一个HelloWorld 如下图
这样title的值就绑定上了自定义代码块 $__title
后台public string info绑定到在html中 @[info] 中
还支持各种嵌套变量 例如一个类里面包含字段,属性,类类型,枚举等如何绑定
<!DOCTYPE html>
<html>
<head>
<title>页面public变量</title>
</head>
<body>
<p>这是一个页面public变量测试页面</p>
<p>string类型后台字段@ [data]:@[data]</p>
<p>string类型后台属性@ [userName]:@[userName]</p>
<p> 实体类型后台字段内嵌字段@ [model.aa]:@[model.aa]</p>
<p>实体类型后台字段内嵌属性@ [model.Name]:@[model.Name]</p>
<p>多层级类套类字段内嵌对象下的字段@ [model.d.geb]:@[model.d.geb]</p>
<p>多层级类套类字段内嵌对象下的属性@ [model.d.dz]:@[model.d.dz]</p>
<p>实体类型后台属性@ [good.GoodName]:@[good.GoodName]</p>
<p>多层级类套类枚举@ [good.work]:@[good.work]</p>
<p>@[zxx]</p>
</body>
</html>
using HCFrameWork;
using System;
using System.Collections.Generic;
namespace HC.Core.Controller.Test.FrameWorkTest
{
public class ViewVarTest : HCController
{
public string data;
public string zxx = "赵晓霞";
public string userName { get; set; }
public TestPerson model;
public Good good { get; set; }
[Get]
public void Main()
{
data = "后台传入参数:=>param";
userName = "可以";
model = new TestPerson();
model.Name = "tianshuyang";
model.d = new D();
model.aa = -17;
model.d.dz = "1";
model.d.geb = "ok!";
good = new Good();
//good.work = (Work)2;
//good.GoodName = "冰激凌";
}
}
public class Good
{
public string GoodName { get; set; }
public Work work;
public D d { get; set; }
}
public class TestPerson : Person
{
public D d { get; set; }
public Work work { get; set; }
public int aa;
}
public class D
{
public string MJ { get; set; }
public string dz { get; set; }
public string geb;
public List<string> list;
}
public enum Work
{
Programmer = 1,
Cook = 2
}
public class Person : Condition
{
public int Age { get; set; }
public string Header { get; set; }
public long TotalCount { get; set; }
public DateTime? BirthDay { get; set; }
}
public class Condition
{
public string Name { get; set; }
public int? Sex { get; set; }
public DateTime? BirthStart { get; set; }
public DateTime? BirthEnd { get; set; }
}
}
注:如果传值对象到前台可以把对象转成json然后前台转化回来
变量绑定是如何实现的呢?点击源码传送门.
[Get] [Post] 特性 (开发篇)
HC框架支持[Get] [Post] 两种特性来支持方法接收请求
如何驱动指定的方法 举个例子 在Controller LL.cs定义下面的方法
[Get("ctl")]
public void Test1()
{
}
访问url:http://localhost:端口/LL?__function=ctl 就可以进入断点 如下图
post 如何获得呢?
表单提交方式:html中加入代码
<form function="sub" method="post">
<input name="age" value="22"/>
<input type="submit" value="提交">
</form>
function=“sub”代表驱动方法上[post(“sub”)]修饰的方法
[Post("sub")]
public void Test2()
{
}
如下图
发送 Ajax 发送post 需要在data参数里拼接一个 __hcform="sub" 就可以驱动特性是Post["sub"] 方法了
HC架构支持同一个方法又接收Post又接收Get双修饰
有人问过我为什么不写put delete 因为我用不到 如果你想用可以在源码里加
那特性修饰驱动方法是如何实现的呢?点击源码传送门.
请求参数 Request和Response(开发篇)
url参数和表单提交的参数如何取值
http://netcore.dichan.com/Views/Test/Data.html <- 取值赋值的demo
假设发送过来有两个参数 id name 下面就是获取参数的方式
[Get]
public void Test1()
{
var id = Request.QueryString["id"];//取url单个字段
var name = Request.QueryString["name"];
var model = Url<UrlFormParam>();//自定义实体直接取url所有字段
var urlp = Request.QueryStringFormat;//url参数拼接格式 ?id=1&name=tsy
var id1 = Request.Form["id"];//取form里的id值
var name1 = Request.Form["name"];
var model1 = Form<UrlFormParam>();//自定义实体直接取form里所有字段
var formp = Request.FormStringFormat; //form参数拼接格式 id=1&name=tsy
}
public class UrlFormParam
{
public string id { get; set; }
public string name { get; set; }
}
反思:
我们想一下MVC里面的Controller的方法上的参数 是怎么实现的?
其实就用了单个字段和实体取值的结合 然后再调用(反射或委托执行)Controller里的对应方法连同参数一起带过去
有人会疑问.net core里面不能这么Request.Form[""] HC架构就可以 因为方便那些.net framework的开发人员
Reponse中包含的方法
/// <summary>
///
/// </summary>
/// <param name="text">输出内容</param>
/// <param name="isEnd">是否终止后面输出(谨慎使用 会自动触发异常一次)</param>
public void Write(string text, bool isEnd = false, Encoding en = null)
{
if (en == null)
{
Context.Response.WriteAsync(text);
}
else
{
Context.Response.WriteAsync(text, en);
}
if (isEnd)//性能瓶颈点
{
End();
}
}
/// <summary>
/// 报出输出异常(谨慎使用 会自动触发异常一次)
/// </summary>
public void End()
{
throw new ResponseEndException();
}
public void ResponseType(string typeString = "text/html;charset=utf-8")
{
Context.Response.ContentType = typeString;
}
反思:
看到Response的源码你是不是有点疑问 Response.End() 怎么抛出一个异常?啥都没干
答案:你可以不用HC去try一下.net mvc或者webform里的Response.End()也会抛出异常
这下你知道为什么他可以阻止后面的代码运行了吧。因为直接抛出去异常调到catch里了
那么为什么我们可以直接在.net core上Request和Response呢?源码传送门
另外HC提供了和Response.Write()一样输出字符串的方法 Console()
/// <summary>
/// 输出控制流
/// </summary>
/// <param name="str">输出的字符串</param>
/// <param name="OnlyConsoleResponseWriteText">是否只输出Response.Write的内容</param>
/// <param name="IsEnd">是否结束输出</param>
public virtual void Console(string str = null, bool OnlyConsoleResponseWriteText = false, bool IsEnd = false)
{
if (!string.IsNullOrWhiteSpace(str))
{
Response.Write(str);
}
if (OnlyConsoleResponseWriteText)
{
Html = string.Empty;
}
if (IsEnd)
{
Response.End();
}
}
OnlyConsoleResponseWriteText 这个参数如果是true那么页面只输出Response.Write()的值,不会输出html。如果一个方法内Response.Write()5次 那么这5次都会输出 而且仅仅输出这5条.如果在这个方法后面加一个return;这样就可以"代替"(单个方法中)Response.End();
[Cache]页面缓存特性(开发篇)
Cache特性用于方法上面修饰,主要作用是页面缓存
[Get]
[Cache(1000,"id,type")]
public void Main()
{
}
参数1 uint类型:代表缓存1000秒,1000内该方法结果缓存在内存中.访问请求不会进入Main函数内直接返回结果
参数2 string类型:代表缓存的参数,只有参数是id和type的才缓存是其他参数的还会进入这个方法里
例如
localhost:xxx/Views/LL?id=1&type=2 会被缓存
localhost:xxx/Views/LL?id=2&type=3 再次缓存
上面两次的结果都会被缓存 如果别人访问也是?id=1&type=2 不会进入Main方法里直接返回?id=1&type=2的缓存结果
localhost:xxx/Views/LL?id=2&type=3&name=tsy 符合缓存参数id和type 返回?id=2&type=3的缓存结果
也就是说 HC只管你要缓存的参数 其他参数是什么它不管
localhost:xxx/Views/LL?id=2&name=tsy 不符合缓存参数 根本不会缓存因为没有type 所以每次请求都会进入Main函数
哪里可以查看到缓存?在系统维护页面可以查看 地址:域名/__info
cache如何工作的?
当第一次提交会提交到后台[Post("PostData")]的aa()方法中并缓存结果
第二次点击就不会提交了 直接返回结果 也就是说该方法被缓存
查看缓存 域名/__info 可以看到缓存的详细信息包含 地址 调用的方法 参数 缓存的开启结束时间 还可以查看被缓存的html结果
如果想去除缓存 点击系统维护页面的清空所有页面缓存按钮
反思:webform和MVC的页面缓存OutputCache是如何处理的?
HC页面缓存源码传送门
追踪器(开发篇)
Trace这个东西大家应该不陌生,好多webform和mvc的开发者也许会用过
HC架构中也有这个东西 就是追踪器
它是用来监测自己写的代码耗时多长时间以便调优
调用方式Trace("描述"); 如果不想使用Trace那么把所有Trace删除 webform是设立一个开关 调试完还留着这个代码干嘛?我觉得没必要设立开关
http://netcore.dichan.com/Views/Test/FrameWorkTest/TraceTest.html <-追踪器Demo传送门
using HCFrameWork;
using System.Collections.Generic;
namespace HC.Core.Controller.Test.FrameWorkTest
{
public class TraceTest : HCController
{
[Get]
[Cache(1000000, "id,type")]//根据id存储 type存储 id和type存储 三种存储方式
public void Main()
{
Trace("init");
List<string> s = new List<string>();
for (var m = 0; m < 20000; m++)
{
s.Add(m.ToString());
}
Trace("1 finished");
s = new List<string>();
for (var m = 0; m < 100000; m++)
{
s.Add(m.ToString());
}
Trace("2 finished");
s = new List<string>();
for (var m = 0; m < 200000; m++)
{
s.Add(m.ToString());
}
Trace("3 finished");
s = new List<string>();
for (var m = 0; m < 200000; m++)
{
s.Add(m.ToString());
}
Trace("4 finished");
s = new List<string>();
for (var m = 0; m < 200000; m++)
{
s.Add(m.ToString());
}
Trace("5 finished");
}
}
}
结果
跟踪器信息:
标注 时间 耗时
init 12:46:23 886. +0毫秒
1 finished 12:46:23 893. +6毫秒
2 finished 12:46:23 905. +12毫秒
3 finished 12:46:23 928. +23毫秒
4 finished 12:46:23 951. +22毫秒
5 finished 12:46:23 971. +20毫秒
总耗时:85毫秒
追踪器实现源码传送门
Url重写&短域名(配置篇)
在HC架构中可以配置Url重写 GlobalController.ControlRoute 方法中 参数Dictionary<string, string> rewriter 具体配置如下
http://netcore.dichan.com/sdt.html <-短域名Demo传送门
如果你想让你的某个页面自定义url访问可以在这里设置
当访问 http://localhost:xxx/sdt.html 就会转向 http://localhost:xxx/Views/Test/FrameWorkTest/ShortDomainTest.html
而且原页面也不受影响(访问http://localhost:xxx/Views/Test/FrameWorkTest/ShortDomainTest.html也能访问)
Url重写功能源码传送门
自定义页面后缀名&自定义非驱动页面(配置篇)
HC框架中允许你自己定义页面后缀名
如果你不想让你的页面都是 .html 如果我想自己加一个 .jsp或着其他后缀来驱动Controller 需要下面配置
试试访问地址改成jsp 就可以进入页面入口函数了
注意:一个目录下不允许有相同名称的Controller驱动文件出现 例如 LL.jsp LL.html 不能出现在同一目录下.因为HC不知道你以哪个页面来驱动Controller
自定义页面后缀名传送门
自定义非驱动页面
顾名思义就是不想让这个页面驱动Controller.
假如别人的项目也有.html文件现在需要合并到你的HC项目中 那么合并进来的.html页面也会驱动Controller而造成找不到对应的Controller异常
HC允许你定义哪些页面不被驱动如下
非常简单 没什么好解释的
如果你有大批量html需要导入 你可以建立一个txt保存html路径动态的读取。后期HC会设置静态目录,
想想.netCore为毛要搞一个app.UseStaticFiles();
现在明白了吧!后期我们也会升级加一个纯静态文件 将这里的配置支持目录形式 就实现了staticFiles()的思想
自定义页面位置(配置篇)
HC架构允许你的页面发布和IIS目录分离开
当你有N个站点来负载均衡 那么当你更新页面的时候 你会发现 要么你就得装一个软件来同步(Rasync或者ssh等), 要么你就手动发布到IIS,那么多负载的站点一个个去更新不会吐吗?
那么HC架构的自定义页面位置就很好的解决了这个问题 如下图
你可以自己定义页面在什么位置 C:\publish\xxx 然后设置这个目录为共享目录 在其他的负载均衡站点添加一个虚拟目录指向其他服务器的共享目录
这样以后就发布这一个目录 其他负载均衡的服务器都会去这个位置读View页面
注:linux 路径 以home为主目录 home/xx/a/bb
自定义页面位置源码传送门
自定义过滤符合后缀名的请求(配置篇)
IIS Filter和.NET中间件Filter里面配置了太多的模块和MIME
说实在我当初也是那么做的 最后发现没有太多时间去实现
windows服务器直接可以在IIS里做 kestrel和其他服务器又可以在core中配置
我就放弃弄个操作简单的过滤如下图
静态资源直接不参与(页面-驱动)模式直接return 让web服务器处理就完事了
过滤符合后缀名的请求源码传送门
源码传送门(核心代码)
变量绑定的实现源码
后台public变量绑定实现
string reg = "@\\[.*?\\]";//取出@[_name]中的_name
var paramList = RegexSth.GetRegex(objClass.Html, reg);
if (paramList != null && paramList.Count() > 0)
{
var varList = paramList.Distinct();//去重
foreach (var m in varList)//对前台定义的变量进行循环(在后台的字段和属性里找)
{
var varName = m.Substring(2, m.Length - 3);//public变量名
var sm = varName.Split('.');//判断是否是类类型嵌套
objClass.Html = objClass.Html.Replace(m, RecursionPageFieldsProperties(pageObj.GetFields(), pageObj.GetProperties(), sm, obj)); //页面变量替换
}
}
后台自定义变量绑定实现
if (objClass.Replacers != null && objClass.Replacers.Count > 0)//替换器有内容
{
objClass.Replacer(objClass.Replacers);//进行替换
}
/// <summary>
/// 替换器
/// </summary>
/// <param name="dic"></param>
public void Replacer(Dictionary<string, string> dic)
{
foreach (var a in dic)
{
Html = Html.Replace(a.Key, a.Value);
}
}
特性和方法驱动的实现
首先定义一个特性类并继承HCHttpAttribute 这个类又继承Attribute
/// <summary>
/// Get请求特性 只有请求是Get才可以进入此方法
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public sealed class GetAttribute : HCHttpAttribute
{
public GetAttribute() { }
public GetAttribute(string actionName)
{
_actionName = actionName;
}
}
/// <summary>
/// Post请求特性 只有请求是Post才可以进入此方法
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public sealed class PostAttribute : HCHttpAttribute
{
public PostAttribute() { }
public PostAttribute(string actionName)
{
_actionName = actionName;
}
}
在HCController中有一个方法
/// <summary>
/// Form正则
/// </summary>
public void FormRegex()
{
//for循环
string form = "<form[^>]*?function=\"([^\"]+)\"[^>]*?>";//获取包含有function="xxx"的form标签 结果为:<form action="Index" function="PostTableInfo" method="post">
string function = "function=\"([^\"]+)\"";//获取function="xxx"
var formList = RegexSth.GetRegex(Html, form);
foreach (var a in formList)//支持一个页面多form
{
var functionList = RegexSth.GetRegex(a, function);//取出该段的 function="xxx"
var functionName = functionList[0].Replace("function=\"", string.Empty).Replace("\"", string.Empty);//function名字
Html = Html.Replace(a, a.Replace(functionList[0], string.Empty) + "<input name=\"__hcForm\" type=\"hidden\" value=\"" + functionName + "\">");//去除这段funtion="xxx"
}
}
先解析html中所有form标签 然后找到function=“xxx” 然后把他替换成string.empty然后在后面拼接一个input name为__hcForm这里你就明白为啥要在ajax里拼接这个参数了吧 当页面点击按钮提交后HC架构是这样处理的
if (request.MethodType.ToLower() == "get")
{
targetMethod = GetMethodByMemberInfo<GetAttribute>(targetMember, request.QueryString == null ? null : request.QueryString["__function"], "get");
}
else
{
targetMethod = GetMethodByMemberInfo<PostAttribute>(targetMember, request.Form == null ? null : request.Form["__hcForm"], "post");
}
/// <summary>
/// 根据form action映射Controller里的方法成员(关联http请求类型)
/// </summary>
/// <typeparam name="T">页面类类型</typeparam>
/// <param name="targetMember">成员</param>
/// <param name="action">action</param>
/// <param name="HttpMedthod">方法名</param>
/// <returns>成员</returns>
static MethodInfo GetMethodByMemberInfo<T>(MemberInfo[] targetMember, string action, string HttpMedthod) where T : HCHttpAttribute
{
var targetMemberList = targetMember.Where(u => u.GetCustomAttributes(typeof(T), false).Count() == 1).ToList();
if (targetMemberList.Count() == 0)
{
throw new HCException(503, "can not find " + HttpMedthod + " method -> 在Controller所有方法中找不到相应的方法 -> code:503.11");
}
foreach (var target in targetMemberList)
{
var customAttribute = ((T)Attribute.GetCustomAttribute(target, typeof(T)));
if (customAttribute.ActionName == action)
{
var method = target.ReflectedType.GetMethod(target.Name);
return method;
}
}
string attributeName = "[" + HttpMedthod.Substring(0, 1).ToUpper() + HttpMedthod.Substring(1, HttpMedthod.Length - 1) + (string.IsNullOrWhiteSpace(action) ? string.Empty : "(\"" + action + "\")") + "]";
string ex = "code:503.12";
ex += " <br/> 在" + targetMemberList[0].DeclaringType.FullName + "中,找不到找不到特性中含有";
ex += attributeName + "特性的方法";
ex += " <br/> can not find a method contain attribution which attribute:" + attributeName;
throw new HCException(503, ex);
}
找到该Controller中被Post修饰的特性然后foreach找到里面的修饰内容的那个方法之后实现一个委托执行那个方法
这下明白了吧!!!
Response和Request的源码
namespace HCFrameWork
{
public class HCRequest : HCContext
{
public HCRequest(HttpContext context)
{
Context = context;
}
/// <summary>
/// Url参数集合
/// </summary>
public NameValueCollection QueryString;
/// <summary>
/// Form参数集合
/// </summary>
public NameValueCollection Form;
/// <summary>
/// Url地址
/// </summary>
public string Url;
/// <summary>
/// Url参数集合字符串(格式?id=1&name=2)
/// </summary>
public string QueryStringFormat;
/// <summary>
/// Form参数集合字符串(格式id=1&name=2)
/// </summary>
public string FormStringFormat;
/// <summary>
/// 请求方式
/// </summary>
public string MethodType { get; set; }
}
public class HCContext
{
public HttpContext Context;
}
public class HCResponse : HCContext
{
public HCResponse(HttpContext context)
{
Context = context;
ResponseType();
}
/// <summary>
///
/// </summary>
/// <param name="text">输出内容</param>
/// <param name="isEnd">是否终止后面输出(谨慎使用 会自动触发异常一次)</param>
public void Write(string text, bool isEnd = false, Encoding en = null)
{
if (en == null)
{
Context.Response.WriteAsync(text);
}
else
{
Context.Response.WriteAsync(text, en);
}
if (isEnd)//性能瓶颈点
{
End();
}
}
/// <summary>
/// 报出输出异常(谨慎使用 会自动触发异常一次)
/// </summary>
public void End()
{
//Context.Response.StatusCode = 200;
throw new ResponseEndException();
}
public void ResponseType(string typeString = "text/html;charset=utf-8")
{
Context.Response.ContentType = typeString;
}
}
在HC接收到一个请求后 会判断是linux运行环境还是windows
如果是linux会选择填装HCRequest和HCResponse
/// <summary>
/// 装填Request
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
static HCRequest InitRequest(HttpContext context1)
{
var request = new HCRequest(context1);//实例化HCRequest
var context = request.Context;
request.MethodType = context.Request.Method.ToLower();//请求方式转化
request.Url = context.Request.GetDisplayUrl();//url地址转化
request.QueryStringFormat = context.Request.QueryString.Value;//url参数转化
var urlParam = request.QueryStringFormat;
var nv = new NameValueCollection();
if (!string.IsNullOrWhiteSpace(urlParam))//如果Url有值
{
var kv = urlParam.Split('?')[1].Split('&');//?s=1&c=2 分割
foreach (var n in kv)//遍历分割后 s=1 c=2
{
string[] kvArr = n.Split('=');
if (kvArr.Length > 1)
{ nv.Add(kvArr[0], kvArr[1]); }//添加到HCRequest的Url对象中
}
}
request.QueryString = nv;
nv = new NameValueCollection();
if (context.Request.ContentLength != null)//如果form有值
{
if (context.Request.Form != null)
{
var form = context.Request.Form;
string formString = string.Empty;
foreach (var i in form)
{
if (!string.IsNullOrWhiteSpace(i.Key))
{
formString += "&" + i.Key + "=" + i.Value;
nv.Add(i.Key, i.Value);
}
}
if (!string.IsNullOrWhiteSpace(formString))
{
request.FormStringFormat = formString.Remove(0, 1);
}
}
}
request.Form = nv;
return request;
}
static HCResponse InitResponse(HttpContext context)
{
var response = new HCResponse(context);//实例化HCResponse
return response;
}
填装完毕后会在后面实例化一个HCController类然后赋值到这个类里面 当进入你的方法里后 你就可以直接用Request对象和Response对象 因为该继承了HCController 如果没继承会在进入你的方法前报出异常
object obj = Activator.CreateInstance(targetClass[0]);//创建该类的实例(页面类对象)
HCController objClass;
try
{
objClass = (HCController)obj;
}
catch
{
throw new HCException(503, "code:503.1 <br/> 目标类需要继承HCController类 <br/> target class needs inherit HCController class <br/> 需要继承的类为(which class need inherit):" + obj.ToString());
}
Cache页面缓存源码和实现原理
当HC接收到请求后判断要执行的方法地址是不是已经缓存,如果缓存中已经缓存了这个地址和参数 那么直接返回缓存中的数据
public static List<__CacheInfo> CacheList;//页面缓存
#region 判断页面缓存
if (CacheList != null)
{
CacheList = CacheList.Where(u => u.StartTime.AddSeconds(u.CacheTime) > DateTime.Now).ToList();//自动删除过期时间的页面缓存
var cacheList = CacheList.Where(u => u.CacheMethod == targetMethod).ToList();//根据方法找到缓存
if (cacheList != null && cacheList.Count() != 0)//已经排除缓存到期的集合
{
string compareStringWithParam = null;
if (cacheList[0].Param != null)//Cache特性参数不为空 参数为空不进行参数验证
{
string[] urlParam = url.Split('?');
var nv = request.MethodType == "get" ? HttpUtility.ParseQueryString(urlParam.Length > 1 ? urlParam[1] : string.Empty) : request.Form;//如果是get请求那么将参数转化成NameValue否则副院副院赋值Form
var paramCacheArr = cacheList[0].Param.Split(',');
var index = 0;
foreach (var a in paramCacheArr)
{
if (nv[a] == null)
{
break;
}
if (index != 0)
{
compareStringWithParam += ",";
}
compareStringWithParam += (a + "=" + nv[a]);
index++;
}
}
if (cacheList.Select(u => u.ParamValue).Contains(compareStringWithParam))//如果参数相同
{
var outputList = cacheList.Where(u => u.ParamValue == compareStringWithParam);
response.Write(outputList.FirstOrDefault().CacheHtml);//加入script标签 如果[Get]请求 data有数据script标签 post之后如果还在本页面 还是需要一次Get 如果跳转走 那么consoleEnd
return;
}
}
}
#endregion
当然第一次访问肯定是没被缓存的 那么判断该方法是否有缓存特性[Cache]如果有添加到缓存集合中 随着下次访问就会判断是否缓存
/// <summary>
/// 设置页面缓存
/// </summary>
/// <param name="method">执行的方法</param>
/// <param name="html">执行页面后的Html</param>
/// <param name="url">url</param>
static void SetCache(MethodInfo method, HCRequest request, string html, string url)
{
var customAttributeCache = ((Cache)Attribute.GetCustomAttribute(method, typeof(Cache)));//找到该方法的自定属性Cache
if (customAttributeCache != null)//如果不是空的 说明有设置Cache参数
{
if (CacheList == null)
{
CacheList = new List<__CacheInfo>();
}
var cacheMethod = CacheList.Where(u => u.CacheMethod == method);//根据方法在缓存变量中找到该变量
var cacheInfo = new __CacheInfo() { HttpMehtod = request.MethodType, CacheHtml = html, CacheMethod = method, CacheTime = customAttributeCache._cacheTime, StartTime = DateTime.Now, Url = url, Param = customAttributeCache._paramName };
if (!string.IsNullOrWhiteSpace(customAttributeCache._paramName))
{
string[] urlParam = url.Split('?');
var nv = request.MethodType == "get" ? HttpUtility.ParseQueryString(urlParam.Length > 1 ? urlParam[1] : string.Empty) : request.Form;
var paramCacheArr = customAttributeCache._paramName.Split(',');
var index = 0;
foreach (var a in paramCacheArr)
{
if (nv[a] == null)
{
return;
}
if (index != 0)
{
cacheInfo.ParamValue += ",";
}
cacheInfo.ParamValue += (a + "=" + nv[a]);
index++;
}
}
if (!CacheList.Select(u => u.ParamValue).Contains(cacheInfo.ParamValue))
{
CacheList.Add(cacheInfo);//添加该页面参数 上面有自动删除该页面参数的配置
}
}
}
追踪器实现源码
/// <summary>
/// 追踪器
/// </summary>
public Dictionary<string, DateTime> TraceList;
string trace = null;//追踪器字符串
if (objClass.TraceList != null && objClass.TraceList.Count() > 0)
{
trace += "<br><br><br><div style=\"border:1px solid red;background-color:black;color:#f9f9f9\">跟踪器信息:<br/><table style=\"color:#f9f9f9\"><tr><td style=\"width:150px;\"> 标注</td><td style=\"width:150px;\"> 时间</td><td> 耗时</td></tr>";
DateTime lastTime = objClass.TraceList.FirstOrDefault().Value;//默认给第一次时间 第一次相减为0
DateTime firstTime = lastTime;//记录第一次时间 后面做减法
foreach (var g in objClass.TraceList)
{
var time = g.Value - lastTime;
trace += "<tr><td>" + g.Key + "</td><td>" + g.Value.ToString("HH:mm:ss fff.") + "</td><td>+" + (time.Minutes == 0 ? "" : time.Minutes + "分") + (time.Seconds == 0 ? "" : time.Seconds + "秒") + time.Milliseconds + "毫秒</td></tr>";
lastTime = g.Value;//赋值本次的时间给lastTime
}
var useTime = lastTime - firstTime;
trace += "<tr><td></td><td></td><td>总耗时:" + (useTime.Minutes == 0 ? "" : useTime.Minutes + "分") + (useTime.Seconds == 0 ? "" : useTime.Seconds + "秒") + useTime.Milliseconds + "毫秒</td><td></td></tr></table></div>";
}
追踪器加入数据是也页面的方法里
显示就是在调用完页面的方法后面把追踪器遍历并输出到页面上如此而已
后期会用watchstop来实现这个功能
Url重写及短域名源码
#region 支持Rewriter
if (rewriter != null)//rewrite的支持
{
foreach (var r in rewriter)
{
foreach (var n in allowDomain)
{
if (url.Contains((n + r.Key).ToLower()))
{//如果url包涵 域名+页面目录地址
url = url.Replace((n + r.Key).ToLower(), (n + r.Value).ToLower());//直接替换映射地址 支持后面带参数
}
}
}
}
#endregion
当请求过来发现他是重写的url(Dictionary的Key)替换成实际的url((Dictionary的Value) 然后重新赋值给Url就可以找到对应的Controller和里面的方法了
自定义页面后缀名
#region 支持自定义后缀名
var diyExtArr = DiyExt.Split('|');//以|分割自定义页面名后缀
if (diyExtArr != null)
{
foreach (var b in diyExtArr)
{
if (!string.IsNullOrWhiteSpace(b))
{
url = url.Replace(b, string.Empty);
}
}
}
#endregion
因为HC架构支持不带后缀名访问 假如你请求访问的页面是LL.jsp 那么把你定义驱动的后缀名给删掉 依然可以驱动对应的Controller 如果你没配置的 他不会替换所以就找不到页面404
自定义页面位置源码
/// <summary>
/// 获取路径下所有的文件列表
/// </summary>
/// <param name="rootDirectory">路径</param>
/// <param name="allowFile">查找的后缀(例如 |.html| 要加|)</param>
/// <returns>文件列表</returns>
public static List<FileDetailInfo> ListDirectory(string rootDirectory, string allowFile,bool isLinux=false)
{
List<FileDetailInfo> list = new List<FileDetailInfo>();
if (Directory.Exists(rootDirectory))
{
FileSystemInfo[] fileSystemInfos = new DirectoryInfo(rootDirectory).GetFileSystemInfos();
foreach (FileSystemInfo info2 in fileSystemInfos)
{
if (info2 is FileInfo)
{
if (string.IsNullOrWhiteSpace(allowFile) || (allowFile.ToLower().IndexOf("|" + info2.Extension.ToString().ToLower() + "|") > -1))
{
var file = (FileInfo)info2;
var aFile = file.OpenRead();
var fileInfo = new FileDetailInfo();
var data = new byte[file.Length];
fileInfo.ExtensionName = file.Extension;
fileInfo.Name = file.Name.Replace(file.Extension, string.Empty);
var dn = file.DirectoryName;
if (isLinux)
{
dn = dn.Replace("\\", "/");
}
fileInfo.Root = dn;
list.Add(fileInfo);
}
}
if (info2 is DirectoryInfo)
{
list.AddRange(ListDirectory(rootDirectory + "/" + info2.Name, allowFile, isLinux));
}
}
}
GC.Collect();
return list;
}
}
当HC框架启动时会执行GlobalController.InitHCFrameWork() 他会去调用上面方法去把根目录下的所有能驱动Controller的自定义后缀名文件全部读出来,然后存在静态集合当中(只读这一次) 读取位置是配置文件里给的路径 如果没有默认根目录 所以它能让发布的页面和程序分离 也就是说可以不必非发布在IIS中
过滤请求符合配置的后缀名源码
#region 支持后缀名过滤
if (pageNameExecptArr.Count() > 0)//?分割后有值 说明url不是空的
{
pageNameExe = pageNameExecptArr[pageNameExecptArr.Length - 1].ToLower();//取最后一项 带页面后缀
if (pageNameExe.Contains("."))//如果Url不是mvc方式的访问的(带后缀名方式)
{
var extArr = pageNameExe.Split('.');//包含多后缀 .xx.cc.vv
pageNameExe = "." + extArr[extArr.Length - 1]; //拼合成.后缀名 例如.html
}
}
foreach (var a in exceptExt ?? new string[] { })//不被检测的后缀名文件
{
if (a.ToLower() == pageNameExe) { return; }//比对页面后缀是否在过滤后缀名中
}
#endregion
如果是配置的后缀名直接return;不参与映射.