从DotNetFrameWork迁移到DotNetCore经历了各种坎坷。不是DotNetCore有多难。而且迁移既要保证继承老的代码遗产,又要保证新DotNetCore业务代码向老系统兼容(业务和UI代码层兼容性)。各种完成之后就剩下迁移前就是心病的一个地方。老的Framework的后台ashx是脚本性质的,不需要编译直接改代码就好使。新的DotNetCore是编译性质的,代码都编译dll了给asp.netcore站点使用。但是我们的业务不允许编译性质存在啊,一个项目6-7年不验收都是有的。而且频繁改业务代码,每改个后台都要编译那可了的,基本难以维护。
不过船到桥头自然直,没开始迁移之前发现的问题随着对DotNetCore的理解深入也浮现了解决方案。经此折腾后,借用一句话,一顶王冠落地,检验要变天了。
那么怎么实现DotNetCore后台脚本化呢?
1.抽象和ashx一样的IBaseHttpHandler接口,替代老的HttpHandler
2.所有后台服务类继承该接口,后面用程序把每个类代码和工程文件组装,调用dotnet build命令编译dll
3.通过反射为每个请求路径从编译生成dll目录查询有没有dll,没有就先组装编译。有了就反射得到接口实现类,然后执行接口方法。
抽象接口
using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace XXXLIS
{
/// <summary>
/// http执行请求接口
/// </summary>
public interface IBaseHttpHandler
{
/// <summary>
/// 执行请求
/// </summary>
/// <param name="context"></param>
public void ProcessRequest(HttpContext context);
}
}
自动编译封装
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.IO;
using System.Diagnostics;
using System.Text;
namespace XXXLIS
{
/// <summary>
/// 实现ashx转cs后自动编译
/// </summary>
public static class AutoBuild
{
/// <summary>
/// 编译
/// </summary>
/// <param name="codepath">类代码路径</param>
/// <param name="proname">工程名称,不带后缀的</param>
/// <param name="standprofilepath">标准工程文件全路径</param>
/// <returns>返回空成功,非空就是失败原因</returns>
public static string Bulid(string codepath,string proname,string standprofilepath)
{
try
{
//编译临时总目录
string buildPath = Path.Combine(AppContext.BaseDirectory, "AutoBuildTmp");
if (!Directory.Exists(buildPath))
{
Directory.CreateDirectory(buildPath);
}
//构建项目编译文件夹
string proBuildPath = Path.Combine(buildPath, proname);
if (!Directory.Exists(proBuildPath))
{
Directory.CreateDirectory(proBuildPath);
}
//编译目录的项目文件全路径
string proFullName = Path.Combine(proBuildPath, proname + ".csproj");
//拷贝标准项目文件
File.Copy(standprofilepath, proFullName, true);
FileInfo fi = new FileInfo(codepath);
//编译目录类全名
string clsFullName = Path.Combine(proBuildPath, fi.Name);
//复制类代码
File.Copy(codepath, clsFullName, true);
//创建一个ProcessStartInfo对象 使用系统shell指定命令和参数 设置标准输出
var psi = new ProcessStartInfo("dotnet", "build " + proBuildPath) { RedirectStandardOutput = true };
//启动
var proc = Process.Start(psi);
if (proc == null)
{
LIS.Core.Util.LogUtils.WriteSecurityLog("编译:" + codepath + "无法运行命令!");
return "编译:" + codepath + "无法运行命令!";
}
else
{
StringBuilder retsb = new StringBuilder();
//读取标准输出
using (var sr = proc.StandardOutput)
{
while (!sr.EndOfStream)
{
retsb.Append(sr.ReadLine());
LIS.Core.Util.LogUtils.WriteSecurityLog(sr.ReadLine());
}
//进程没结束就杀进程
if (!proc.HasExited)
{
proc.Kill();
}
//删除源文件
DeleteDirectory(proBuildPath);
}
if (proc.ExitCode == 0)
{
return "";
}
return retsb.ToString();
}
}
catch(Exception ex)
{
return ex.Message;
}
}
/// <summary>
/// 删除文件夹
/// </summary>
/// <param name="dir">路径</param>
private static void DeleteDirectory(string dir)
{
//递归结束
if (Directory.GetDirectories(dir).Length == 0 && Directory.GetFiles(dir).Length == 0)
{
Directory.Delete(dir);
return;
}
//删目录
foreach (string var in Directory.GetDirectories(dir))
{
DeleteDirectory(var);
}
//删文件
foreach (string var in Directory.GetFiles(dir))
{
File.Delete(var);
}
Directory.Delete(dir);
}
}
}
中间件处理调用请求片段
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.Cors.Infrastructure;
namespace XXXLIS
{
/// <summary>
/// 主中间件
/// </summary>
public class MianMiddleware
{
/// <summary>
/// 请求委托
/// </summary>
private readonly RequestDelegate _next;
/// <summary>
/// 静态资源目录
/// </summary>
public static string StaticFilesPath = "";
/// <summary>
/// 缓存路径和类型
/// </summary>
private static System.Collections.Hashtable hsType = new System.Collections.Hashtable();
/// <summary>
/// 类文件和dll时间最后检测时间
/// </summary>
private static System.Collections.Hashtable hsClassCheckTime = new System.Collections.Hashtable();
/// <summary>
/// 构造
/// </summary>
/// <param name="next"></param>
public MianMiddleware(RequestDelegate next)
{
_next = next;
}
/// <summary>
/// 执行方法
/// </summary>
/// <param name="httpContext"></param>
/// <returns></returns>
public Task Invoke(HttpContext httpContext)
{
//得到请求路径
string path = httpContext.Request.Path;
if (path.Contains(".asmx"))
{
return _next(httpContext);
}
if (!path.Contains(".ashx"))
{
httpContext.Response.WriteAsync("");
return Task.CompletedTask;
}
//按路径解析得到类名
string className = path.Split('.')[0];
if (className == "")
{
httpContext.Response.WriteAsync("");
return Task.CompletedTask;
}
//反射得到类型
object objDeal = GetObjectByConfString(className, httpContext);
IBaseHttpHandler baseDeal = (IBaseHttpHandler)objDeal;
if (baseDeal != null)
{
try
{
baseDeal.ProcessRequest(httpContext);
}
catch (Exception ex)
{
httpContext.Response.WriteAsync(ex.Message);
}
}
else
{
httpContext.Response.WriteAsync("系统提示:未找到全名为:" + className + "的处理类");
}
return Task.CompletedTask;
}
/// <summary>
/// 通过配置得当对象
/// </summary>
/// <param name="confStr">配置</param>
/// <returns></returns>
private object GetObjectByConfString(string confStr, HttpContext httpContext)
{
//最后检测时间
DateTime lastCheckTime = DateTime.Now.AddMinutes(-10);
if (hsClassCheckTime.ContainsKey(confStr))
{
lastCheckTime = (DateTime)hsClassCheckTime[confStr];
}
//使用路径和类型哈希,提升创建对象速度
if (hsType.ContainsKey(confStr))
{
if ((DateTime.Now - lastCheckTime).TotalSeconds < 60)
{
Type objType = hsType[confStr] as Type;
return Activator.CreateInstance(objType);
}
}
try
{
if (confStr[0] == '/')
{
confStr = confStr.Substring(1);
}
string[] strArr = confStr.Replace(" ", "").Split(',');
string[] nameArr = strArr[0].Split('/');
string classFullName = "UI";
//类代码全路径
string classCodePath = StaticFilesPath;
for (int i = 1; i < nameArr.Length; i++)
{
classCodePath = Path.Combine(classCodePath, nameArr[i]);
classFullName += "." + nameArr[i];
}
//类代码地址
classCodePath = classCodePath + ".cs";
//编译的dll名字
string clsDllPath = Path.Combine(AppContext.BaseDirectory, "BinAshx", classFullName + ".dll");
if ((DateTime.Now - lastCheckTime).TotalSeconds > 60)
{
//有代码没dll就执行编译
if (File.Exists(classCodePath) && (!File.Exists(clsDllPath)))
{
//标准工程文件
string standprofilepath = Path.Combine(AppContext.BaseDirectory, "LISStandAshxProj.csproj");
string buildRet = AutoBuild.Bulid(classCodePath, classFullName, standprofilepath);
if (buildRet != "")
{
httpContext.Response.WriteAsync("编译:" + classFullName + "发生错误:" + buildRet);
}
if (!hsClassCheckTime.ContainsKey(confStr))
{
hsClassCheckTime.Add(confStr, DateTime.Now);
}
else
{
hsClassCheckTime[confStr] = DateTime.Now;
}
}
else if (File.Exists(classCodePath) && (File.Exists(clsDllPath)))
{
FileInfo fiCode = new FileInfo(classCodePath);
FileInfo fiDll = new FileInfo(clsDllPath);
//更新检测时间
if (!hsClassCheckTime.ContainsKey(confStr))
{
hsClassCheckTime.Add(confStr, DateTime.Now);
}
else
{
hsClassCheckTime[confStr] = DateTime.Now;
}
//代码比dll新,执行编译
if ((fiCode.LastWriteTime - fiDll.LastWriteTime).TotalSeconds > 0)
{
//标准工程文件
string standprofilepath = Path.Combine(AppContext.BaseDirectory, "LISStandAshxProj.csproj");
string buildRet = AutoBuild.Bulid(classCodePath, classFullName, standprofilepath);
if (buildRet != "")
{
httpContext.Response.WriteAsync("编译:" + classFullName + "发生错误:" + buildRet);
}
}
else
{
if (hsType.ContainsKey(confStr))
{
Type objType = hsType[confStr] as Type;
return Activator.CreateInstance(objType);
}
}
}
}
Assembly assembly = null;
//类的程序集不在,且目录程序集不在
if ((!File.Exists(clsDllPath)) && strArr.Length < 2)
{
assembly = Assembly.GetExecutingAssembly();
}
else
{
string path = "";
//加载程序集
if (File.Exists(clsDllPath))
{
path = clsDllPath;
}
else
{
path = Path.Combine(AppContext.BaseDirectory, strArr[1]);
if (!path.Contains(".dll"))
{
path = path + ".dll";
}
}
assembly = Assembly.LoadFile(path);
}
object obj = assembly.CreateInstance(classFullName, false);
//更新类型
if (!hsType.ContainsKey(confStr) && obj != null)
{
hsType.Add(confStr, obj.GetType());
}
else
{
hsType[confStr] = obj.GetType();
}
return obj;
}
catch (Exception ex)
{
LIS.Core.Util.LogUtils.WriteExceptionLog("反射:" + confStr + "异常", ex);
throw ex;
}
}
}
/// <summary>
/// Extension method used to add the middleware to the HTTP request pipeline.
/// </summary>
public static class MianMiddlewareExtensions
{
public static IApplicationBuilder UseMianMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<MianMiddleware>();
}
}
}
这样请求的类代理改动的话就会在60秒内编译新dll到BinAshx下供中间件反射,自动编译在Linux和Window都一样,不需要其他额外设置,部署时候安装DotNetCore的SDK,而不是仅仅运行时额。
请求了哪些后台服务类就编译哪些
写在最后,不停探索,积累基础知识,达到一定量了可能就来质变的方案了,敢于尝试,敢想敢干。