WebForm与Mvc
我简单说一下WebForm与Mvc,WebForm是微软很早就推出的一种WEB开发架构,微软对其进行了大量的封装,使开发人员可以像开发桌面程序一样去开发WEB程序,虽然开发效率得到了提高,但可控性会一差。而MVC框架的出现,使WEB开发又回到了请求与响应的开发模式下,可控性增强了,但开发难度也增加了,所以说在架构的选择上没有好与坏,只有适合自己的才是最好的。
FineUIMvc与IFrame
虽然FineUIMvc基于MVC架构,但是也沿用了WebForm页面开发的方式,所以一开始刚接触FineUIMvc时还有一点疑惑。在FineUIMvc中的控制器与视图之间存在着一定的耦合,但这对FineUIMvc来说应该不是什么问题,因为FineUIMvc本身并不是一个通用型的架构,FineUIMvc更适合拥有大量控件操作的项目,耦合的存在可以降低控件的操作难度。在官网的示例中,我们经常能看到在一个控制器中有多个可以返回视图的Action,特别是AppBoxMvc更是达到极致,一个控制器几乎包含了整个项目的全部分代码,这是我所不推荐的做法,所以我在后面对这方面会做一些约束。
在FineUIMvc中有很多容器控件都可以启用IFrame,IFrame之间的交互,在官网示例(http://fineui.com/demo_mvc/)的内联框架中也给出了示例,在这些示例中,大家经常会看到在很多地方都在调用ActiveWindow中的方法,这个也是FineUIMvc对活动窗体的封装。那你在调用ActiveWindow中的方法时,你就已明确的知道,当前的页面是在一个窗体中显示的,并且还是一个活动窗体,如果我们想重用这个页面,并将其放到其它非窗体的容器中,我们要么需要在页面中判断,要么再重新创建一个新的页面,这样必然会对页面的重用带来一些麻烦,同时如果需要在页面间传递一些数据,页面还需要事先知道父页面或同级页面中的方法,这也无形中增加了页面与页面之间的耦合。
FineUIMvc组件开发的基本概念
如果基于组件的方式去开发FineUIMvc项目,这样就可以解决上面所遇到的问题。首先组件是封闭性的,你只需要暴露你想暴露的事件与方法,同时组件不需要知道调用者的信息,调用者只需要监控组件所提供的事件,调用组件所提供的方法,就可以完成组件的操作。我们这里所指的组件,主要是在FineUIMvc中通过IFrame显示的页面,IFrame本身可以起到上下文隔离的作用,再加上在IFrame中显示的页面以组件的方式提供服务,这样页面的开发可以独立出来,而不用去考虑调用者所处在的上下文。
FineUIMvc组件的构成
通过上面的介绍大家应该都知道,我们这里的组件其实就是页面,组件与组件之间的交互,其实就是页面与页面之间的交互。一个页面一般要包含HTML文件、CSS文件及JS文件,由于我们是基于TS开发,所以这里的JS文件就是TS文件,HTML文件对应到MVC中就是CSHTML视图文件。如果我们要创建一个UserInfoComponent组件,那它对应到MVC中应该包含哪些文件及是什么样的结构呢,请看下图
图中用箭头所指向的文件就是我们UserInfoComponent组件所需要的文件,有人可能会问,开发一个组件需要这么文件吗,那组件的开发是不是很复杂。为了使前后端的开发更顺畅,我们需要一些辅助文件,所以这些文件都是必须的,但大家也不用担心,因为这些文件是通过FineUIMvcTools插件自动生成的,并且会在你更改视图及控制器代码时,会自动更新相应的文件。下面是这些文件说明:
- UserInfoController.cs:这是MVC中的控制器文件,与普通控制器文件一样没有什么区别
- UserInfoController.g.cs:控件引用定义文件,由工具生成,方便直接操作视图中的控件
- UserInfoModels.cs:模型文件,这个文件中包含了与当前控制器相关的模型,其中有些模型是自动生成的
- Index.cshtml:视图文件,对应控制器中的Index方法
- Index.css:视图文件的样式文件
- Main.cs:组件引导文件,由工具生成,一般不用修改此文件
- UserInfoComponent.ts:组件的TS逻辑文件,组件在客户端的逻辑代码
- UserInfoComponent.g.ts:组件的TS辅助文件,由工具生成,包含了控件TS引用定义、模型TS引用定义 及TS的控制器中Action的调用方法
UserInfoComponent组件的开发过程
下面为UserInfoComponent组件的内容,4个文本框及2个按钮,4个文本框的数据来源于外部调用者的设置,“确定”及“取消”按钮触发事件通知调用者
当我们创建完视图时,工具会自动帮助我们生成代码
UserInfoController.g.cs中的代码:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 // 2 // 此代码由工具生成 3 // 对此文件的修改,将会在下次生成时丢失 4 // 5 6 using FineUIMvc; 7 using System.Collections.Generic; 8 9 namespace FineUIMvc.EmptyProject.Controllers 10 { 11 public partial class UserInfoController 12 { 13 private Dictionary<string, IControlBaseAjaxHelper<ControlBase>> _FINE_UI_MVC_CONTROLS_ = new Dictionary<string, IControlBaseAjaxHelper<ControlBase>>(); 14 15 private TextBoxAjaxHelper txtName 16 { 17 get 18 { 19 var controlId = "txtName"; 20 21 if (!this._FINE_UI_MVC_CONTROLS_.ContainsKey(controlId)) 22 { 23 this._FINE_UI_MVC_CONTROLS_[controlId] = UIHelper.TextBox(controlId); 24 } 25 26 return (TextBoxAjaxHelper)this._FINE_UI_MVC_CONTROLS_[controlId]; 27 } 28 } 29 30 private TextBoxAjaxHelper txtEmail 31 { 32 get 33 { 34 var controlId = "txtEmail"; 35 36 if (!this._FINE_UI_MVC_CONTROLS_.ContainsKey(controlId)) 37 { 38 this._FINE_UI_MVC_CONTROLS_[controlId] = UIHelper.TextBox(controlId); 39 } 40 41 return (TextBoxAjaxHelper)this._FINE_UI_MVC_CONTROLS_[controlId]; 42 } 43 } 44 45 private TextBoxAjaxHelper txtTelphone 46 { 47 get 48 { 49 var controlId = "txtTelphone"; 50 51 if (!this._FINE_UI_MVC_CONTROLS_.ContainsKey(controlId)) 52 { 53 this._FINE_UI_MVC_CONTROLS_[controlId] = UIHelper.TextBox(controlId); 54 } 55 56 return (TextBoxAjaxHelper)this._FINE_UI_MVC_CONTROLS_[controlId]; 57 } 58 } 59 60 private TextBoxAjaxHelper txtAddress 61 { 62 get 63 { 64 var controlId = "txtAddress"; 65 66 if (!this._FINE_UI_MVC_CONTROLS_.ContainsKey(controlId)) 67 { 68 this._FINE_UI_MVC_CONTROLS_[controlId] = UIHelper.TextBox(controlId); 69 } 70 71 return (TextBoxAjaxHelper)this._FINE_UI_MVC_CONTROLS_[controlId]; 72 } 73 } 74 75 private ButtonAjaxHelper btnOK 76 { 77 get 78 { 79 var controlId = "btnOK"; 80 81 if (!this._FINE_UI_MVC_CONTROLS_.ContainsKey(controlId)) 82 { 83 this._FINE_UI_MVC_CONTROLS_[controlId] = UIHelper.Button(controlId); 84 } 85 86 return (ButtonAjaxHelper)this._FINE_UI_MVC_CONTROLS_[controlId]; 87 } 88 } 89 90 private ButtonAjaxHelper btnCancel 91 { 92 get 93 { 94 var controlId = "btnCancel"; 95 96 if (!this._FINE_UI_MVC_CONTROLS_.ContainsKey(controlId)) 97 { 98 this._FINE_UI_MVC_CONTROLS_[controlId] = UIHelper.Button(controlId); 99 } 100 101 return (ButtonAjaxHelper)this._FINE_UI_MVC_CONTROLS_[controlId]; 102 } 103 } 104 } 105 }
UserInfoComponent.g.ts中的代码:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 // 2 // 此代码由工具生成 3 // 对此文件的修改,将会在下次生成时丢失 4 // 5 6 /* 7 * UI 8 */ 9 export module UI { 10 export let txtName = F<F.TextBox>('txtName'); 11 export let txtEmail = F<F.TextBox>('txtEmail'); 12 export let txtTelphone = F<F.TextBox>('txtTelphone'); 13 export let txtAddress = F<F.TextBox>('txtAddress'); 14 export let btnOK = F<F.Button>('btnOK'); 15 export let btnCancel = F<F.Button>('btnCancel'); 16 } 17 18 /* 19 * Action 20 */ 21 export module Action { 22 export function getActionUrl(actionName: string) { 23 return `${F.baseUrl}UserInfo/${actionName}`; 24 } 25 26 export function index(model?: Model.IndexModel, owner?: Window | GetIFrameWindow) { 27 let url = `${getActionUrl('Index')}${model != null ? '?' + QueryString.stringify(model) : ''}`; 28 let wnd : any = owner || window; 29 wnd = (wnd != null && wnd.getIFrameWindow && wnd.getIFrameWindow()) || wnd; 30 wnd.open(url, '_self'); 31 } 32 } 33 34 /* 35 * Model 36 */ 37 export module Model { 38 39 export class IndexModel { 40 41 constructor() { 42 } 43 } 44 45 }
外部调用者设置组件的信息,我们需要在UserInfoComponent.ts中去实现(23行到53行为自己添加的代码,其它的为创建时自动生成的代码),代码:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 import { Component } from '../../Scripts/Component'; 2 import { UI, Action, Model } from './UserInfoComponent.g'; 3 4 export class UserInfoComponent extends Component { 5 public static readonly TYPE = 'FINEUIMVC_EMPTYPROJECT_VIEWS_USERINFOCOMPONENT'; 6 7 constructor() { 8 super(UserInfoComponent.TYPE); 9 this.init(); 10 } 11 12 public static validType(component: Component) { 13 return component.isType(UserInfoComponent.TYPE); 14 } 15 16 public static open(model?: Model.IndexModel, owner?: Window | GetIFrameWindow) { 17 Action.index(model, owner); 18 } 19 20 private init() { 21 } 22 23 public set userName(name: string) { 24 UI.txtName.setText(name, false); 25 } 26 27 public get userName() { 28 return UI.txtName.getText(); 29 } 30 31 public set email(email: string) { 32 UI.txtEmail.setText(email, false); 33 } 34 35 public get email() { 36 return UI.txtEmail.getText(); 37 } 38 39 public set telphone(telphone: string) { 40 UI.txtTelphone.setText(telphone, false); 41 } 42 43 public get telphone() { 44 return UI.txtTelphone.getText(); 45 } 46 47 public set address(address: string) { 48 UI.txtAddress.setText(address, false); 49 } 50 51 public get address() { 52 return UI.txtAddress.getText(); 53 } 54 }
外部调用者监听组件事件,我们还是需要在UserInfoComponent.ts中去实现(20行到36行为自己添加的代码),代码:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 import { Component } from '../../Scripts/Component'; 2 import { UI, Action, Model } from './UserInfoComponent.g'; 3 4 export class UserInfoComponent extends Component { 5 public static readonly TYPE = 'FINEUIMVC_EMPTYPROJECT_VIEWS_USERINFOCOMPONENT'; 6 7 constructor() { 8 super(UserInfoComponent.TYPE); 9 this.init(); 10 } 11 12 public static validType(component: Component) { 13 return component.isType(UserInfoComponent.TYPE); 14 } 15 16 public static open(model?: Model.IndexModel, owner?: Window | GetIFrameWindow) { 17 Action.index(model, owner); 18 } 19 20 private init() { 21 UI.btnOK.onClick(() => { 22 $(document).trigger('onOKClick'); 23 }); 24 25 UI.btnCancel.onClick(() => { 26 $(document).trigger('onCancelClick'); 27 }); 28 } 29 30 public onOKClick(handler: (e, m) => any) { 31 $(document).on('onOKClick', handler); 32 } 33 34 public onCancelClick(handler: (e, m) => any) { 35 $(document).on('onCancelClick', handler); 36 } 37 38 public set userName(name: string) { 39 UI.txtName.setText(name, false); 40 } 41 42 public get userName() { 43 return UI.txtName.getText(); 44 } 45 46 public set email(email: string) { 47 UI.txtEmail.setText(email, false); 48 } 49 50 public get email() { 51 return UI.txtEmail.getText(); 52 } 53 54 public set telphone(telphone: string) { 55 UI.txtTelphone.setText(telphone, false); 56 } 57 58 public get telphone() { 59 return UI.txtTelphone.getText(); 60 } 61 62 public set address(address: string) { 63 UI.txtAddress.setText(address, false); 64 } 65 66 public get address() { 67 return UI.txtAddress.getText(); 68 } 69 }
调用者本身也是一个组件,组件中含有一个Window控件,用于加载UserInfoComponent组件(22行到45行为调用UserInfoComponent,第3行为导入UserInfoComponent),代码:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 import { Component } from '../../Scripts/Component'; 2 import { UI, Action, Model } from './DefaultComponent.g'; 3 import { UserInfoComponent } from '../UserInfo/UserInfoComponent'; 4 5 export class DefaultComponent extends Component { 6 public static readonly TYPE = 'FINEUIMVC_EMPTYPROJECT_VIEWS_DEFAULTCOMPONENT'; 7 8 constructor() { 9 super(DefaultComponent.TYPE); 10 this.init(); 11 } 12 13 public static validType(component: Component) { 14 return component.isType(DefaultComponent.TYPE); 15 } 16 17 public static open(model?: Model.IndexModel, owner?: Window | GetIFrameWindow) { 18 Action.index(model, owner); 19 } 20 21 private init() { 22 UserInfoComponent.open(null, UI.window1); 23 UI.window1.onReady(() => { 24 let userInfoComponent = UI.window1.getComponent<UserInfoComponent>(); 25 if (userInfoComponent && UserInfoComponent.validType(userInfoComponent)) { 26 userInfoComponent.userName = '张三'; 27 userInfoComponent.email = 'test@test.com'; 28 userInfoComponent.telphone = '123456789'; 29 userInfoComponent.address = 'xxxxxxxxxxx'; 30 31 userInfoComponent.onOKClick(() => { 32 F.notify({ 33 message: `姓名:${userInfoComponent.userName};邮箱:${userInfoComponent.email}`, 34 messageIcon: F.MessageBoxIcon.$Success 35 }); 36 }); 37 38 userInfoComponent.onCancelClick(() => { 39 F.notify({ 40 message: '单击了取消按钮', 41 messageIcon: F.MessageBoxIcon.$Warning 42 }); 43 }); 44 } 45 }); 46 } 47 }
运行后的效果(修改完姓名及邮箱后,单击“确定”按钮),如图:
以上就是组件开发的过程,基于组件方式的开发,可以最大程度的提高页面重复利用次数,降低页面与页面之间的耦合。
在后续的文章中,我会介绍组件开发的每一个细节,FineUIMvcTools工具还在完善中,我想等到这个系列文章写完后,再提供给大家。
示例代码:
链接: http://pan.baidu.com/s/1o7AgH2y 密码: 6b33