今天在@张善友和@田园里的蟋蟀的博客看到微软“.Net社区虚拟大会”dotnetConf2015的信息,感谢他们的真诚付出!真希望自已也能为中国的.NET社区贡献绵薄之力。
上周星期天开通了博客并发布了第一篇文章《新思想、新技术、新架构——更好更快的开发现代ASP.NET应用程序》,汇集了一些比较流行的技术和开源项目,也把自己的程序架构、部分代码风格、前端表现简单做了一些展示,引起了近100位朋友的评论。特别感谢@田园里的蟋蟀、@深蓝医生、@郭明锋、@疯狂的提子、@jimcsharp、@以吾之名等给我建议和指导的朋友,也感谢那些给我支持和鼓励的朋友。还有对我提出批评的朋友,说我的面试题的内容不当,也很感谢他们让我更注意言辞,但并不会影响我对面试者基础知识的重视程度。
上周发布那篇文章主要是因为这段时间在招聘过程中发现几乎所有面试者对基础知识和新技术都知之甚少,有过几年工作经验的程序员也几乎只会单一模式的CURD,没有明显的技术特长,所以我想分享一些自己认为比较好的思想、技术、架构模式,引起更多ASP.NET程序员的思考和讨论。
其实,上周星期天是花了大半天写一篇博客,在发出来之前删掉了一大半内容(一些讲述我自己心路历程的内容),因为我在博客园是一个新人,在没有对别人提供价值帮助之前也许没人关心我是谁。那天由于时间太晚了,很多想写的内容都没有写出来,发布的时候仅贴了一些图片,后来在评论中写了很多内容,并修改了原文正文,补充分享了一些非常好的开源项目。希望之前看过的朋友可以再回去看看,给个链接:http://www.cnblogs.com/mienreal/p/4340864.html
之前的一个项目是做的微信公众平台的第三方平台,提供微网站自主建站、会员卡、微商城、外卖预订等几十项功能。在项目初期,我仅担任产品总监负责产品设计,后来因为没有强大的前端团队,不得不亲自实现微官网的可视化设计器的前端。再后来公司让我接管了开发部(全是JAVA开发人员),跟开发团队有了更直接的配合。我发现他们普遍代码质量不高,几乎不懂得运用设计模式和最佳实践。每新增或修改一点功能,都要将全部代码进行编译和发布,会影响正在登录使用的用户,而且有时候一个经验不足的程序员修改的一点东西会让整个平台不能正常启动。跟几个高级工程师多次沟通,希望他们学习新技术新思想,运用成熟的最佳实践来提高代码质量;希望他们了解领域驱动设计用于会员卡等业务较复杂的模块;希望他们能了解OSGI实现模块化开发和部署,但因为经验能力和积极性等原因,这些愿望都没有实现。后来在新项目(开发代号Fami)中,我选择了.NET技术平台,并组建新的开发团队来进行这个项目。现在项目才刚完成基础框架和项目规范。
下面把这个项目的架构思想和功能特性再分享一下。希望对正在设计架构的朋友有一个参考作用。本项目是Saas模式的在线产品,需实现多租户模式;有多个功能模块,且上线时间有先有后,需实现模块化开发。
本项目总体分为两个部分:一个基础框架组件,一个Fami解决方案。
基础框架组件的功能:
1、基础框架组件独立、通用,可用于多个不同项目。类似于daxnet的Apworks框架。
2、对项目实现模块化开发提供了支持,每个模块有独立的EF DbContext,可单独指定数据库。
3、对DDD的技术实现进行了封装,让项目以极精简的代码,专注于业务领域。
4、多租户支持,每个租户的数据自动隔离,业务模块开发者不需要手动操作TenantId。
5、集成ASP.NET Identity,实现登录认证、功能权限授权&验证、角色和用户管理。
6、集成Log4Net,实现日志记录。
7、集成AutoMapper,实现Dto类与实体类的双向自动转换。
8、实现UnitOfWork模式,为应用层和仓储层的(会写数据库的)方法自动实现数据库事务。
9、可通过ApplicationService的方法自动建立相应的WebApi方法,ajax可直接调用,不需要写ApiController和Action。
10、调用ApplicationService的方法时,自动验证权限和参数有效性(用相应的Attribute标注)。
11、实现一系列扩展方法,简化编码。
Fami项目解决方案结构图:
模块化结构图 | WEB项目结构图 |
每个模块是一个独立的类库项目,有独立的DbContext(如上面左图中的WechatMpDbContext.cs),可单独指定不同的数据库链接,以实现按功能模块分库。
每个模块有自己权限提供类(WechatMpAuthorizationProvider.cs)、设置提供类(WechatMpSettingProvider.cs)、仓储基类(WechatMpRepository.cs)。
模块的展现层代码(MVC文件)放在WEB项目的Areas下,有自己单独的路由注册类文件(如上面右图中的WechatMpAreaRegistration.cs)。
MVC的Controller只有极少的代码,用于返回列表页的View、表单页面的View和Model,新建、编辑、删除等操作无需写Action方法,直接由前端的ajax调用Application层的相应Service方法(运行时,动态代理自动生成ApiController及相应方法)。
拿一个最最简单的图文素材功能举例说明:
Domain层的Article实体类:
2 {
3 public class Article : AuditedEntityAndTenant
4 {
5 [MaxLength(50)]
6 public string Title { get; set; }
7
8 [MaxLength(512)]
9 public string PicUrl { get; set; }
10
11 [MaxLength(1000)]
12 public string Interoduction { get; set; }
13
14 [MaxLength(512)]
15 public string LinkUrl { get; set; }
16
17 [MaxLength(512)]
18 public string OriginalUrl { get; set; }
19
20 public string Content { get; set; }
21
22 [ForeignKey("ArticleCategoryId百度地图、ECharts整合HT for Web网络拓扑图应用 - xhload3d 阅读原文»
前一篇谈及到了ECharts整合HT for Web的网络拓扑图应用,后来在ECharts的Demo中看到了有关空气质量的相关报表应用,就想将百度地图、ECharts和HT for Web三者结合起来也做一个类似空气质量报告的报表+拓扑图应用,于是有了下面的Demo:
在这个Demo中,将GraphView拓扑图组件添加到百度地图组件中,覆盖在百度地图组件之上,并且在百度地图组件上和GraphView拓扑图组件上分别添加事件监听,相互同步经纬度和屏幕位置信息,从而来控制拓扑图上的组件位置固定在地图上,并在节点和节点之间的连线上加上了流动属性。右下角的图标框是采用HT for Web的Panel面板组件结合ECharts图表组件完成的。
接下来我们来看看具体的代码实现:
1.百度地图是如何与HT for Web组件结合的;
var view = graphView.getView();
view.className = 'graphView';
var mapDiv = document.getElementById('map');
mapDiv.firstChild.firstChild.appendChild(view);
首先需要在body中存在id为map的div,再通过百度地图的api来创建一个map地图对象,然后创建GraphView拓扑图组件,并获取GraphView组件中的view,最后将view添加到id为map的div的第二代孩子节点中。这时候问题就来了,为什么要将view添加到map的第二代孩子节点中呢,当你审查元素时你会发现这个div是百度地图的遮罩层,将view添加到上面,会使view会是在地图的顶层可见,不会被地图所遮挡。
2.百度地图和GraphView的事件监听;
resetPosition();
});
map.addEventListener('dragend', function(e){
resetPosition();
});
map.addEventListener('zoomend', function(e){
resetPosition();
});
graphView.handleScroll = function(){};
graphView.handlePinch = function(){};
function resetPosition(e){
graphView.tx(0);
graphView.ty(0);
dataModel.each(function(data){
var lonLat, position;
if(data instanceof ht.HtmlNode){
if(data.getId() != 'chartTotal') {
position = data.getHost().getPosition();
position = {x: position.x + 168, y: position.y + 158};
data.setPosition(position.x, position.y);
}
} else if(data instanceof ht.Node){
lonLat = data.lonLat;
position = map.pointToPixel(lonLat);
data.setPosition(position.x,position.y);
}
});
}
首先监听map的三个事件:moveend、 dragend、 zoomend,这三个事件做了同一件事--修改DataModel中所有data的position属性,让其在屏幕上的坐标与地图同步,然后将GraphView的Scroll和Pinch两个事件的执行函数设置为空函数,就是当监听到Scroll或者Pinch事件时不做任何的处理,将这两个事件交给map来处理。
在resetPosition函数中,做的事情很简单:遍历DataModel中的data,根据它们各自在地图上的经纬度来换算成屏幕坐标,并将坐标设置到相应的data中,从而达到GraphView中的节点能够固定在地图上的效果。
3.创建右下角的图表组件:
var self = this,
view = self._view = document.createElement('div');
view.style.position = 'absolute';
view.style.setProperty('box-sizing', 'border-box', null);
self._option = option;
self._chart = echarts.init(self.getView());
if(option)
self._chart.setOption(option);
self._FIRST = true;
};
ht.Default.def('ht.Chart', Object, {
ms_v: 1,
ms_fire: 1,
ms_ac: ['chart', 'option', 'isFirst', 'view'],
validateImpl: function(){
var self = this,
chart = self._chart;
chart.resize();
if(self._FIRST){
self._FIRST = false;
chart.restore();
}
},
setSize: function(w, h){
var view = this._view;
view.style.width = w + 'px';
view.style.height = h + 'px';
}
});
function createPanel(title, width, height){
chart = new ht.Chart(option);
var c = chart.getChart();
c.on(echarts.config.EVENT.LEGEND_SELECTED, legendSelectedFun);
var chartPanel = new ht.widget.Panel({
title: title,
restoreToolTip: "Overview",
width: width,
contentHeight: height,
narrowWhenCollapse: true,
content: chart,
expanded: true
});
chartPanel.setPositionRelativeTo("rightBottom");
chartPanel.setPosition(0, 0);
chartPanel.getView().style.margin = '10px';
document.body.appendChild(chartPanel.getView());
}
首先定义了ht.Chart类,并实现了validateImpl方法,方法中处理的逻辑也很简单:在每次方法执行的时候调用图表的reset方法重新设定图标的展示大小,如果该方法是第一次执行的话,就调用图表的restore方法将图表还原为最原始的状态。会有这样的设计是因为ht.Chart类中的view是动态创建的,在没有添加到dom之前将一直存在于内存中,在内存中因为并没有浏览器宽高信息,所以div的实际宽高均为0,因此chart将option内容绘制在宽高为0的div中,即使你resize了chart,如果没用重置图表状态的话,图表状态将无法在图表上正常显示。
接下来就是创建panel图表组件了,这是HT for Web的Panel组件的基本用法,其中content属性的值可以是HT for Web的任何组件或div元素,如果是HT fro Web组件的话,该组件必须实现了validateImpl方法,因为在panel阅读更多内容
没有评论:
发表评论