MVC开发模式下的WebApp架构尝试
第一次尝试搭建项目架构,当时遇到的情况是这样,公司搞了一个webapp的项目,这个项目会随着版本更新多出很多新的模块,但是项目代码本身初期设计的时候没有考虑到版本更新的问题,于是就碰到了一旦发布的版本遇到了重大的BUG,开发机上的项目不能直接更新Bin目录下的DLL,因为开发机上的Bin包含了下个版本需要发布的内容,会与现在版本的冲突,其实就是UI和BO层的耦合度太高了。于是接到的第二个webapp项目就要求能够切断这种耦合,任务就交到了我的手上。
任务开始,头脑风暴时刻
当初设想的方案有以下几个
- 在站点下面根据版本建立虚拟目录,用VSS控制版本的代码。乍一听之下,这个办法挺好的,完全可以满足需求,但是站在开发者角度一想,如果发布了10个版本,发现第一个版本里的一个DAO或者BO或者随便哪里的代码有巨大漏洞,尼玛我这得改10个地方,虽然在小项目中几率比较小,但是作为攻城狮我得避免这个情况
- 用类似于webpart的方式进行配件式的开发。这个已经可以满足需求了,但是想想,我们做的这个是展示类的网站,如果要划分成配件式的,得有20来个,每个配件的BO和DAO其实都是一样的架构方式,但是要搞20多个,工作量有点大啊,只能再想想
- 搭一套全新的架构,自己想办法完成这个需求。。。哎~~缺点很明显,没人搭过架构,谁知道搭出来的好不好用啊,但是项目经理下了决心要有一套自己的架构,那就动工吧
需求分析
- 架构要能做到根据版本号的不同返回不同的View,调用不同的Bo
- 每个调用的Bo要做到可配置,如果新上的Bo出现问题,可以不用发布Bin修改配置改用老的Bo方法
架构设想
其实也没什么设想,听到需求已经能肯定了,简单的依赖注入就可以解决,但是依赖注入我也没怎么玩过,网上查查看到的大多不能完全满足需求,算了自己写,我决定用反射去实现注入,虽然效率可能低了点,但是无所谓啊~如果效率低,我再发反射实现的方法改成工厂返回的那种模式就行了嘛
下面是我实现的代码
首先是分层,既然要切断BO与UI的耦合,肯定要定义一个BO的接口层,然后是BO
然后,我先写了一个BaseController
- 重写了View()和View(Object Model)这2个方法,每次retrun View的时候自动获取到配置中具体View的Name当然可能将来还得多重写几个
public new ActionResult View()
{
return base.View(GetViewName(RouteData.Values["Controller"].ToString(), RouteData.Values["Action"].ToString()));
}
public new ActionResult View(Object Model)
{
return base.View(GetViewName(RouteData.Values["Controller"].ToString(), RouteData.Values["Action"].ToString()),Model);
}
2.写了一个方法将Request中的参数序列化为对象,因为我的UI层中不准备写任何的逻辑,否则版本没法控制了
/// <summary>
/// 获取所有参数集合
/// </summary>
/// <returns></returns>
public RequestParameter GetRequestParameter()
{
RequestParameter rtn=new RequestParameter();
Dictionary<string, string> PostDic = new Dictionary<string, string>();
if (Request.Form != null)
{
foreach (string key in Request.Form)
{
PostDic.Add(key, Request.QueryString[key]);
}
}
rtn.PostParameter = PostDic;
Dictionary<string, string> GetDic = new Dictionary<string, string>();
if (Request.QueryString != null)
{
foreach (string key in Request.QueryString)
{
GetDic.Add(key, Request.QueryString[key]);
}
}
rtn.GetParameter = GetDic;
Dictionary<string, RequestFile> FileDic = new Dictionary<string, RequestFile>();
for (int i = 0; i < this.Request.Files.Count; i++)
{
RequestFile _file = new RequestFile();
_file.FileName = this.Request.Files[i].FileName;
_file.ContentType = this.Request.Files[i].ContentType;
_file.ContentLength = this.Request.Files[i].ContentLength;
_file.FileStream = this.Request.Files[i].InputStream;
}
rtn.FileParameter = FileDic;
return rtn;
}
3.反射的方法用于根据版本号获取对应的BO
/// <summary>
/// 获取指定名称的BLL对象
/// </summary>
/// <param name="ControllerName"></param>
/// <returns></returns>
public Object GetBll()
{
string ControllerName = RouteData.Values["Controller"].ToString();
string Path = ConfigurationManager.AppSettings["BllXmlConfigPath"] + BllConfigXmlName;
var query = from a in XElement.Load(Path).Elements()
where a.Attribute("Controller").Value == ControllerName
select new BoConfig
{
Controller = a.Attribute("Controller").Value,
BllName = a.Attribute("BllName").Value
};
Assembly assembly = Assembly.Load("BLL");
return assembly.CreateInstance(query.First().BllName);
}
/// <summary>
/// 获取指定名称的BLL对象
/// </summary>
/// <param name="ControllerName"></param>
/// <returns></returns>
public Object GetBll(string ControllerName)
{
string Path = ConfigurationManager.AppSettings["BllXmlConfigPath"] + BllConfigXmlName;
var query = from a in XElement.Load(Path).Elements()
where a.Attribute("Controller").Value == ControllerName
select new BoConfig
{
Controller = a.Attribute("Controller").Value,
BllName = a.Attribute("BllName").Value
};
Assembly assembly = Assembly.Load("BLL");
return assembly.CreateInstance(query.First().BllName);
}
下面是我2个配置的XML格式
<?xml version="1.0" encoding="utf-8" ?> <Root> <Config Controller="Home" BllName="BLL.HomeBll10"/> </Root> <?xml version="1.0" encoding="utf-8" ?> <Root> <Config Controller="Home" BllName="BLL.HomeBll10"/> </Root>
最后就是Controller里如何使用了
public class HomeController : BaseController
{
//
// GET: /Home/
[RequestLog(ViewName = "公共自行车", Action = SysEnum.ActionType.Retrieve, Module = "交通出行")]//访问日志记录
[UserLoginRole]//登录拦截
public ActionResult Index()
{
//传入Controller的名称获取对应的BLL对象
IBLL.IHome BLL = (IBLL.IHome)this.GetBll();
ViewBag.Ttt= BLL.Test(this.GetRequestParameter())+BLL.Test2();
return View("111");
}
UI层介绍完毕之后是我BO曾的实现
以HomeController调用的BO为例,首先是一个BaseHomeBo,这个类实现IBO的接口所有方法,但是全部都以抛出异常为结尾
using IBLL;
namespace BLL
{
public class BaseHomeBll:IHome
{
public virtual string Test(COMMON.RequestParameter parameter)
{
throw new System.NotImplementedException();
}
public virtual string Test2()
{
throw new System.NotImplementedException();
}
}
}
接着根据版本,每个版本一个类,继承上一个版本的类,这里我写了10和20两个版本的类
namespace BLL
{
public class HomeBll10:BaseHomeBll
{
public override string Test(COMMON.RequestParameter parameter)
{
return "111";
}
public override string Test2()
{
return "222";
}
}
}
namespace BLL
{
public class HomeBll20:HomeBll10
{
public override string Test(COMMON.RequestParameter parameter)
{
return "333";
}
}
}
很简单,高版本重写低版本内容,没有重写就是以前版本的内容,就是一个多态的策略模式
好了结束了,请各位大牛指点下,这个模式还能优化吗?
听了大神的建议,修改了一些内容
对BO的反射进行了缓存
/// <summary>
/// 获取指定名称的BLL对象
/// </summary>
/// <param name="ControllerName"></param>
/// <returns></returns>
public Object GetBll(string ControllerName)
{
Cache cache = HttpRuntime.Cache;
if (cache.Get("System_BoCache_" + ControllerName + "_" + CurrentUserVersion) == null && !IsBoCache)
{
string Path = BllXmlConfigPath + BllConfigXmlName;
var query = from a in XElement.Load(Path).Elements()
where a.Attribute("Controller").Value == ControllerName
select new BoConfig
{
Controller = a.Attribute("Controller").Value,
BllName = a.Attribute("BllName").Value
};
Assembly assembly = Assembly.LoadFile(BoPath);
object obj = assembly.CreateInstance(query.First().BllName);
cache.Insert("System_BoCache_" + ControllerName + "_" + CurrentUserVersion, obj, null, DateTime.Now.AddHours(1),
TimeSpan.Zero);
return obj;
}
else
{
return cache["System_BoCache_" + ControllerName + "_" + CurrentUserVersion];
}
}
posted on 2015-03-04 10:35 !!!!!!!!!!!!!!! 阅读(595) 评论(0) 收藏 举报
浙公网安备 33010602011771号