上回合中,我们不痛不痒的把小泥鳅的数据库从只能供在Windows下运行的Access数据库改为支持跨平台的MYSQL数据库,毫无营养的修改,本回合中,我们将把我们修改后得来的项目往Linux中部署、调试,让它适应Linux.NET的运行环境。

在本回合中,我们将讨论研究:

  1、由一个谎言引出另一个谎言

  2、遭遇大量大小写问题怎么办

  3、requestValidationMode?

  4、同一个房顶,却是不同的房间


1、由一个谎言引出另外一个谎言

当我们把小泥鳅部署上Linux之后,首页一般是没有问题的(首页能够打开,并且能够阅读里面的文章),但是当我们点击后台管理时,页面就开始变得奇怪起来,它没有像我们想象那样,出现一个填写用户名密码的界面,而是如下图所示的“问题”页面:

通过阅读堆栈跟踪,我们大概知道程序用一个“AdminPage.CheckLoginAndPermission”的地方进去之后就开始报错,为此,我们需要先确定这个叫做“CheckLoginAndPermission”的东西到底是Mono或其他三方类库里的东西还是我们代码里的。

判断的方法也挺简单,对着Visual Studio 按“Ctrl+Shirt+F”,没错,就是查找功能,只要我们能够在项目中找到相关的代码,那就证明改方法是我们项目中自己的东西。

通过搜索,结果还真让我们找到症结的所在,于是,我们就顺藤摸瓜的进入到该方法里面,该方法的代码如下:

/// <summary>
/// 检查登录和权限
/// </summary>
protected void CheckLoginAndPermission()
{
if (!PageUtils.IsLogin)
{
HttpContext.Current.Response.Redirect("login.aspx?returnurl=" + HttpContext.Current.Server.UrlEncode(RequestHelper.CurrentUrl));
}
UserInfo user = UserManager.GetUser(PageUtils.CurrentUserId); if (user == null) //删除已登陆用户时有效
{
PageUtils.RemoveUserCookie();
HttpContext.Current.Response.Redirect("login.aspx?returnurl=" + HttpContext.Current.Server.UrlEncode(RequestHelper.CurrentUrl)); } if (StringHelper.GetMD5(user.UserId + HttpContext.Current.Server.UrlEncode(user.UserName) + user.Password) != PageUtils.CurrentKey)
{
PageUtils.RemoveUserCookie();
HttpContext.Current.Response.Redirect("login.aspx?returnurl=" + HttpContext.Current.Server.UrlEncode(RequestHelper.CurrentUrl));
} if (PageUtils.CurrentUser.Status == )
{
ResponseError("您的用户名已停用", "您的用户名已停用,请与管理员联系!");
} string[] plist = new string[] { "themelist.aspx", "themeedit.aspx", "linklist.aspx", "userlist.aspx", "setting.aspx" ,"categorylist.aspx","taglist.aspx","commentlist.aspx"};
if (PageUtils.CurrentUser.Type == (int)UserType.Author)
{
string pageName = System.IO.Path.GetFileName(HttpContext.Current.Request.Url.ToString()).ToLower(); foreach (string p in plist)
{
if (pageName == p)
{
ResponseError("没有权限", "您没有权限使用此功能,请与管理员联系!");
}
}
}
}

CheckLoginAndPermission

咋眼一看,一个验证账户登录与权限的方法,如果不满足则自动的跳转到各自的页面,没有什么特别的,也没有什么问题。但是,程序的异常就是出现在这里,因此我们需要把他找出来。

各位读者第一时间想到的可能是马上按“F5”或者“附加到进程”,依赖Visual Studio 这个强大的IDE来定位哪一步出了问题。但是,别忘了,我们的程序在Windows下是没有问题的,并且当前的操作系统也不是Windows,因此Visual Studio的功能我们是无法使用的。或许,有些读者还知道有“Mono Develop”这个IDE,该IDE可以在Linux中使用,可惜,我们的Linux中并没有安装这个工具,甚至连Xwindows也没有安装,Linux的运行级别也只是“init-3”级别,要弄“Mono Develop”太麻烦了,我们需要一些有趣的手段来定位我们的问题。

先回想一下,既然成功发布,那就证明项目是成功的编译,而在运行时出现却报错,则表示,这个是一个运行时异常。运行时异常,其实我们也会经常遇到,譬如让程序读一个不存在的文件、数据库连接字串写错之类的,这些都属于运行时异常,只有程序运行到这一步出现错误的时候,程序才终止继续运行并提示错误。

根据这一原理,我们可以自己定义一些“谎言”(手动的添加一些运行时错误),让程序运行到此处终止并提示错误,通过比较程序提示的错误,我们就可以定位到项目中发生错误的哪一行代码了,通过一个谎言来引出另外一个谎言。

譬如我在检查是否登陆这里添加一个“谎言”。

/// <summary>
/// 检查登录和权限
/// </summary>
protected void CheckLoginAndPermission()
{
if (!PageUtils.IsLogin)
{
HttpContext.Current.Response.Redirect("login.aspx?returnurl=" + HttpContext.Current.Server.UrlEncode(RequestHelper.CurrentUrl));
}
UserInfo user = UserManager.GetUser(PageUtils.CurrentUserId); //在这里添加谎言
var a = decimal.Parse("小蝶惊鸿"); if (user == null) //删除已登陆用户时有效
{
PageUtils.RemoveUserCookie();
HttpContext.Current.Response.Redirect("login.aspx?returnurl=" + HttpContext.Current.Server.UrlEncode(RequestHelper.CurrentUrl)); } if (StringHelper.GetMD5(user.UserId + HttpContext.Current.Server.UrlEncode(user.UserName) + user.Password) != PageUtils.CurrentKey)
{
PageUtils.RemoveUserCookie();
HttpContext.Current.Response.Redirect("login.aspx?returnurl=" + HttpContext.Current.Server.UrlEncode(RequestHelper.CurrentUrl));
} if (PageUtils.CurrentUser.Status == )
{
ResponseError("您的用户名已停用", "您的用户名已停用,请与管理员联系!");
} string[] plist = new string[] { "themelist.aspx", "themeedit.aspx", "linklist.aspx", "userlist.aspx", "setting.aspx", "categorylist.aspx", "taglist.aspx", "commentlist.aspx" };
if (PageUtils.CurrentUser.Type == (int)UserType.Author)
{
string pageName = System.IO.Path.GetFileName(HttpContext.Current.Request.Url.ToString()).ToLower(); foreach (string p in plist)
{
if (pageName == p)
{
ResponseError("没有权限", "您没有权限使用此功能,请与管理员联系!");
}
}
}
}

谎言的CheckLoginAndPermission

编译发布后再刷新页面

我们得到了这个运行时异常,图明显的跟之前的不同,那就证明,刚才的异常在此“谎言”的下方。

我们不断的把我们的“谎言”(手动添加的运行时错误)往下挪,直到它把真正的“谎言”(原本的运行时错误)引出。

通过这种方法的迭代,我们大概定位到这里:

再结合它报给我们的错误“Object reference not set to an instance of an object”(未将对象实例化),我们可以推演出,这里有东西为null。在这里,只有user为需要实例化的类(PageUtils.CurrentKey是一个static的属性),我们可以猜或许是user为null。

为了验证,我们可以做如下动作:

通过验证,我们发现我们的推理是对的,就是user为null造成了此处的失败。

但是,为什么user为空呢?或者说,难道说小泥鳅中原程序中没有对user作出checknull的判断吗?我们先追述一下user的来源,user来自于本方法中:

通过传入一个CurrentUserId,来获得user类,而在GetUser方法中,代码如下:

        /// <summary>
/// 获取用户
/// </summary>
/// <param name="userId"></param>
/// <returns></returns>
public static UserInfo GetUser(int userId)
{
foreach (UserInfo user in _users)
{
if (user.UserId == userId)
{
return user;
}
}
return null;
}

GetUser

CurrentId实际上还是一个userid,通过比较两个userid的值来获得user实例。还在想为什么没有得到null吗?这里是一个陷阱,我们根本就没有登陆,所以根本就没有CurrentUserId(或者说userid的值为null),因此,GetUser方法的输出东西应该也是为null。

难道小泥鳅没有对输出为null的情况作出处理吗?答案是否定的,小泥鳅中已经有判断,不然在Windows中就已经报错了,如果用户没有登陆(没有登陆就必定没有CurrentUserId了),页面就跳转到“login.aspx”页面(登陆页面)。

逻辑上当然是这样的,但是现实却并非这样,页面没有发生跳转,或者更确切的说,程序没有到达Redirect方法之后进行重定向并终止“CurrentUserId”方法中接下来的代码。看清楚幕后的“元凶”之后,想要对付它也变得简单起来,我们只需手动的让它终止运行方法内接下来的代码就可以了。

在Redirect下方加上Return(方法内所有的Redirect都加上):

然后再次编译发布,我们就可以看到我们的登陆页面了。

2、如果遇到大量大小写问题怎么办?

我们进入后台管理页面之后,尝试添加一篇文章:

为了添加图片,我们需要点击“插入图片/文件”:

然后就……,上面说“'UserControls/upfilemanager.ascx”不存在。

典型的大小写问题,"UserControls"文件夹的大小写。解决办法很简单,把文件夹名按照大小写改好就ok。

正如我上回合中说的,小泥鳅的大小写还是比较严格的,基本上都是大小写敏感,但是,如果我们现在面对的不是小泥鳅,而是一个比较麻烦项目,里面充斥着大量的大小写问题,我们再对此进行地毯式的搜索并修正就显得可行性极低了(还不如推倒重写呢)。

面对这种情况,我们也是有“作弊码”可以行的,我们可以通过修改jws的脚本文件,让Mono对文件目录不区分大小写(注意,是文件目录,SQL语句还是区分的,因为解析SQL语句是数据库的事情,而不是Mono的事情)。

我们只需打开“jws”文件,并把Mono的IOMAP设置为all即可(jws中只需删掉“#”号)。

重启一下Jexus,再尝试添加:

ok,我们又搞定了一个问题。

3、requestValidationMode?

也不记得从什么时候开始,大概是.NET FrameWork 4.0 吧,我们使用富文本编辑器的时候,提交时会出现“Form表单有危险……”之类的提示,.NET也自动的帮我们验证从页面中Post回来的报文,有尖括号之类的敏感字串会自动的被.NET拒绝接收,解决的办法也很容易,网上是大把大把的,基本上就是把验证的模式从“4.0”(或更高)改为“2.0”就ok了。

但是,在我们这里,小泥鳅是基于.NET 2.0哦,所以应该就不会出现上述这种情况吧?!我们先试着添加一点东西:

然后再点击提交:

片刻的广告之后,我们只能说“嘿嘿~~”了,Mono把小泥鳅用ASP.NET 4.0 来运行了,既然它是使用.NET FrameWork 4.0 的模式来运行,那么我们也只需使用相应的解决办法即可。

我在“web.config”中的“httpruntime”节点中加上“requestValidationMode”(在这里,我直接就用VI加了,无需重新编译发布):

然后我们重新添加文章并保存,就可以在首页中找到了我们的文章了:

4、同一个房顶,却是不同的房间

整好了小泥鳅在Linux.NET中的运行异常之后,我们还需要增加一个对Sqlite数据库的支持,虽然平时经常听到这一款的数据库,但是却从来都没有真正的接触过,直到开始增加此扩展的时候还是第一次(初体验?!),多亏了通用性高的SQL语句和ADO.NET,使我在仅仅知道它有五种数据类型并从Nuget哪里获得驱动的情况下就做好了对Sqlite的扩展(于是悲剧来了)。

在Windows中测试通过之后,我们迫不及待的往Linux中发布,然后就:

在Linux.NET中,如果遇到DLL明明就在bin目录中,但是程序却报找不到或者无法加载之类的,一般要么就是DLL真的找不到(文件大小写问题)、还有其他依赖的DLL没有成功加载或者是直接不兼容造成的。排除了大小写问题之后,我们找找Mono中有没有带有对Sqlite作驱动的dll。

可以发现,Mono中已经自带了Sqlite的驱动,并且与在MS.NET中是处于两个不同的命名空间。此外,这里还有一个图片需要让读者们看看的:

不仅命名空间不同,大小写也是有点点差别,所以,千言万语都不说了,改吧~~!


至此,小泥鳅的改造基本上完成了,需要代码的读者可以在GitHub中找到(地址在上集中有说),能力有限,如果写得有不对的欢迎各位读者留言,有建议或者意见的也欢迎留言,我们下回见~~!

PS:今年是“码”年,在这里小蝶惊鸿给各位读者拜年,祝各位读者新年快乐,恭喜发财。

Linux.NET实战手记—自己动手改泥鳅(下)的更多相关文章

  1. Linux.NET实战手记—自己动手改泥鳅(上)

    各位读者大家好,不知各位读者有否阅读在下的前一个系列<Linux.NET 学习手记>,在前一个系列中,我们从Linux中Mono的编译安装开始,到Jexus服务器的介绍,以及如何在Linu ...

  2. Linux.NET学习手记(7)

    前一篇中,我们简单的讲述了下如何在Linux.NET中部署第一个ASP.NET MVC 5.0的程序.而目前微软已经提出OWIN并致力于发展VNext,接下来系列中,我们将会向OWIN方向转战. 早在 ...

  3. Linux.NET学习手记(8)

    上一回合中,我们讲解了Linux.NET面对OWIN需要做出的准备,以及介绍了如何将两个支持OWIN协议的框架:SignalR以及NancyFX以OwinHost的方式部署到Linux.NET当中.这 ...

  4. 关于《Linux.NET学习手记(8)》的补充说明

    早前的一两天<Linux.NET学习手记(8)>发布了,这一篇主要是讲述OWIN框架与OwinHost之间如何根据OWIN协议进行通信构成一套完整的系统.文中我们还直接学习如何直接操作OW ...

  5. 嵌入式Linux启动优化手记2&nbsp;U…

    参考一下 原文地址:U-boot优化">嵌入式Linux启动优化手记2 U-boot优化作者:ZhaoJunling 既然不能使用新的U-boot,那就优化一点是一点,慢慢干吧. 1. ...

  6. LR添加Windows和Linux压力机实战

    添加Windows和Linux压力机实战 既然Controller是LoadRunner的“心脏”,那么压力产生也必然是它发起的,通过压力机来对被测系统产生压力.一般压力机分为Windows和Linu ...

  7. Linux系统实战项目——sudo日志审计

    Linux系统实战项目——sudo日志审计   由于企业内部权限管理启用了sudo权限管理,但是还是有一定的风险因素,毕竟运维.开发等各个人员技术水平.操作习惯都不相同,也会因一时失误造成误操作,从而 ...

  8. 【Linux】将Oracle安装目录从根目录下迁移到逻辑卷

    [Linux]将Oracle安装目录从根目录下迁移到逻辑卷 1.1  BLOG文档结构图 1.2  前言部分 1.2.1  导读和注意事项 各位技术爱好者,看完本文后,你可以掌握如下的技能,也可以学到 ...

  9. Linux游(1): diff, patch和quilt (下一个)

    Linux游(1): diff, patch和quilt (下一个) 2 quilt 我们自己的项目可以用cvs或svn管理所有代码.但有时我们要使用其它开发人员维护的项目.我们须要改动一些文件.但又 ...

随机推荐

  1. 【小程序分享篇 一 】开发了个JAVA小程序, 用于清除内存卡或者U盘里的垃圾文件非常有用

    有一种场景, 手机内存卡空间被用光了,但又不知道哪个文件占用了太大,一个个文件夹去找又太麻烦,所以我开发了个小程序把手机所有文件(包括路径下所有层次子文件夹下的文件)进行一个排序,这样你就可以找出哪个 ...

  2. jQuery实践-网页版2048小游戏

    ▓▓▓▓▓▓ 大致介绍 看了一个实现网页版2048小游戏的视频,觉得能做出自己以前喜欢玩的小游戏很有意思便自己动手试了试,真正的验证了这句话-不要以为你以为的就是你以为的,看视频时觉得看懂了,会写了, ...

  3. 干货来袭-整套完整安全的API接口解决方案

    在各种手机APP泛滥的现在,背后都有同样泛滥的API接口在支撑,其中鱼龙混杂,直接裸奔的WEB API大量存在,安全性令人堪优 在以前WEB API概念没有很普及的时候,都采用自已定义的接口和结构,对 ...

  4. WebApi接口 - 响应输出xml和json

    格式化数据这东西,主要看需要的运用场景,今天和大家分享的是webapi格式化数据,这里面的例子主要是输出json和xml的格式数据,测试用例很接近实际常用情况:希望大家喜欢,也希望各位多多扫码支持和点 ...

  5. windows+nginx+iis+redis+Task.MainForm构建分布式架构 之 (nginx+iis构建服务集群)

    本次要分享的是利用windows+nginx+iis+redis+Task.MainForm组建分布式架构,由标题就能看出此内容不是一篇分享文章能说完的,所以我打算分几篇分享文章来讲解,一步一步实现分 ...

  6. 如何在网页中提取Email地址

    开博好久了,今天第一次发表技术文档,之前总是将一些好的事例保存在电脑,时间久了找起来也很麻烦,所以还是放在博客里进行归类比较方便,这样也能将自己在学习过程中的一些心得体会分享给大家,也能给需要的人一点 ...

  7. Git分布式版本控制教程

    Git分布式版本控制Git 安装配置Linux&Unix平台 Debian/Ubuntu $ apt-get install git Fedora $ ) $ dnf and later) G ...

  8. 封装集合(Encapsulate Collection)

    封装就是将相关的方法或者属性抽象成为一个对象. 封装的意义: 对外隐藏内部实现,接口不变,内部实现自由修改. 只返回需要的数据和方法. 提供一种方式防止数据被修改. 更好的代码复用. 当一个类的属性类 ...

  9. BPM嵌入式流程解决方案分享

    一.需求分析由于企业业务的独特性或者企业高层独特的管理思想,很多客户选择了自行开发业务系统的方式来实现独有的竞争力. 这类信息系统通常经过了多年的开发,伴随着企业的发展一直在不断优化,与企业的业务非常 ...

  10. Android开发学习—— Broadcast广播接收者

    现实中:电台要发布消息,通过广播把消息广播出去,使用收音机,就可以收听广播,得知这条消息.Android中:系统在运行过程中,会产生许多事件,那么某些事件产生时,比如:电量改变.收发短信.拨打电话.屏 ...