使用 Nancy 开发 selfhost 和 IIS host
早几年用 C# 开发一个小型站点或者一个简单的服务模块,还得开 Visual Studio 写一个 WebForm 或者 MVC 站点,想写个服务还得用 WCF,太麻烦了,最近,从前年开始,我就开始将一些 APP 的服务端从 WCF 改成 用 Nancyfx 作为框架开发的独立运行的 “微服务” 模式了。
Nancy 其实很早就出来了,无论在 .NET 还是 Mono 还是目前的 .NET Core 下都可以跑起来,我目前的做法是写好业务层,然后就可以写一个 selfhost 的独立运行程序,将它打包为一个 EXE 就可以运行起来,或者丢到 IIS 里也可以运行起来,不管在 Windows 下还是 Linux 还是 MacOS 下,程序永远都可以运行,不需要修改一行代码,我觉得这 TM 才是真正的跨平台吧。
不过我还在使用 1.x 的版本,如要使用旧版本,可以切换 1.x 的分支去看对应的示例,不过要使用的话还是使用最新的版本吧。
这是官方的一个 Hello World 栗子。
public class Module : NancyModule
{
public Module()
{
Get("/greet/{name}", x => {
return string.Concat("Hello ", x.name);
});
}
}
我的项目中,将所有的业务通过 Module 区分,由于只有少量的 JS / CSS /Html 资源,以及不打算使用 Razor 引擎,所有所有的资源也全部封装到一个资源 DLL 里了,内嵌资源的加载方式很简单:
var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Hello.html");
我觉得在不太需要复杂的页面渲染逻辑的项目中,使用 DotLiquid 要简便得多,怎么引用,可以上 Nuget.org 搜索,然后通过 PM 导入即可,我把模板的渲染作为一个 String 类型的扩展方法,更加方便使用。
public static string AsHtmlFromTemplate(this string tmpl, object model)
{
return Template.Parse(tmpl).Render(Hash.FromAnonymousObject(model));
}
想要调用的话可以一行代码就可以:
htmlTemplate.AsHtmlFromTemplate(new { Title = "Hello" });
通常 Response 输出或响应的类型有很多,文本,二进制文件,流,其他响应,以及会在 Header 或 Cookie 写入什么值,这些用的都很普遍,看看 Nancy 是怎么处理这些类型的输出。
文本类型最简单,通过在 Response 对象上调用 AsText 即可。
Response.AsText("内容","MIME")
JSON 类型,可以使用 AsJson 方法,传递匿名对象就可以达到目的。
对于流输出,需要构造 Nancy 的 Stream ,比如输出字体文件,可以这么做:
Nancy.Responses.StreamResponse streamResponse = new Nancy.Responses.StreamResponse(() => { return fontstream; }, mime);
要设置 Header,只需要在 StreamResponse 的调用 WithHeader 即可。
不过在 IIS Host 的 ASP.NET 中使用路径和 SelfHost 模式有些不同,在 ASP.NET 通常我们获取路径的方式是 ~/YourPath ,而在 SelfHost 中是直接访问文件系统的绝对路径或相对路径。我这里做法是,在业务模块中,创建一个接口,比如 IUnc,然后 SelfHost 或 ASP.NET 中分别实现他们,告诉根路径的物理上的绝对路径。
public interface IUnc
{
string GetRoot();
}
以及他们分别在 ASP.NET 或 Console Client 中的实现:
public string GetRoot()
{
return HttpContext.Current.Server.MapPath("~/");
}
public string GetRoot()
{
return Environment.CurrentDirectory;
}
然后通过反射,获取 IUNC 的实现类,确定 Root 路径的地址:
private void DetectRoot()
{
string root = Environment.CurrentDirectory;
try
{
var types = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(a => a.GetTypes().Where(t => t.GetInterfaces().Contains(typeof(IUnc))))
.ToArray();
foreach (var v in types)
{
var msds = v.GetMethods();
foreach (var msd in msds)
{
root = (Activator.CreateInstance(v) as IUnc).GetRoot();
break;
}
break;
}
}
catch(Exception ex)
{
}
_root = root;
}
在 Nancy 中,如果需要对 "模块" 进行授权,例如 Basic 认证,Form 认证,可以在每个 Module 的构造方法中调用 RequiresAuthentication。
this.RequiresAuthentication();
以上就是在我使用的过程中,遇到的一些简单的使用的方法,其实写到最后,项目结构都比较复杂了,比如光 Nancy 的 Bootstraper 类都达到了数百行,随着业务增多,复杂化,逻辑也变得很不清晰,但是已经上线的项目中,我用它也完成了跨 Linux 和 Windows 的两个平台部署,以及在 Windows 上也实现了 Nginx+SelfHost 模式承载服务和 IIS 承载模式。
如果你需要一个轻量的微服务架构,像 Springboot 那样,不妨考虑试试 Nancy :)