通过一个用户的增删改查来讲一下标题所说的架构,对于相关原理及细节不做深入讲解。
先看下最终界面,虽然只有一个页面。
图1-1 用户管理界面
图1-2 用户新增
图1-3 用户修改
再来看下项目代码目录结构的截图。
图1-4 项目代码目录结构
其中引用到的类库截图如下,红色框内为新引入的类库。
图1-5 项目引用
图2-1-1 建立MVC项目
图2-1-2 MVC项目
本项目采用的是ExtJS,所以Scripts下面的都可以删掉,然后引入ExtJS包,这里只需引入ExtJS中的resource文件夹和ext-all.js,当然引入压缩版的ExtJS更好,如图2-1-3所示。
图2-2-1 引入ExtJS
先添加引用,添加的包括开始所讲的哪些dll文件,最后结果如图2-3-1所示。
接下来就得引入Ibatis的一些配置文件并做修改。复制Ibatis.DataMapper.1.6.2.bin下的providers.config文件到项目中,你会发现里面有很多类型的数据库的配置,这个配置主要是给Ibatis访问数据库提供驱动信息。因为这里用的是MySQL数据库,因此只需要保留MySQL配置的节点就可以了,然后将enable属性值修改为true,这里要注意下MySQL类库的版本,版本可以通过查看MySQL类库的属性获得。然后修改description,assemblyName中版本号。最终修改完后的providers.config如图2-3-3所示。
图2-3-2MySQL.Data版本查看
图2-3-3 providers.config配置最终结果
复制Ibatis.DataMapper.1.6.2.bin下的sample.SqlMap.config到项目中,将文件名称修改为SqlMap.config,删掉properties节点,修改providers节点中的resource属性,属性值就是上面providers.config文件的路径,接着修改database节点中的provider节点name属性值改为providers.config中的name属性值,connectionString改为本地的数据库连接信息,最终修改后的结果如图2-3-4所示。
图2-3-4 SqlMap.config配置最终结果
修改Web.config文件,将以下内容添加到Web.config的configuration节点下。内容主要是log4net的配置信息。
<configSections> <sectionGroup name="iBATIS"> <section name="logging" type="IBatisNet.Common.Logging.ConfigurationSectionHandler, IBatisNet.Common"/> </sectionGroup> <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/> </configSections> <iBATIS> <logging> <logFactoryAdapter type="IBatisNet.Common.Logging.Impl.Log4NetLoggerFA, IBatisNet.Common.Logging.Log4Net"> <arg key="configType" value="inline"/> <arg key="showLogName" value="true"/> <arg key="showDataTime" value="true"/> <arg key="level" value="ALL"/> <arg key="dateTimeFormat" value="yyyy/MM/dd HH:mm:ss:SSS"/> </logFactoryAdapter> </logging> </iBATIS> <log4net> <!-- Define some output appenders --> <appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender"> <param name="File" value="mybatis.log"/> <param name="AppendToFile" value="true"/> <param name="MaxSizeRollBackups" value="2"/> <param name="MaximumFileSize" value="100KB"/> <param name="RollingStyle" value="Size"/> <param name="StaticLogFileName" value="true"/> <layout type="log4net.Layout.PatternLayout"> <param name="Header" value="[Header]\r\n"/> <param name="Footer" value="[Footer]\r\n"/> <param name="ConversionPattern" value="%d [%t] %-5p %c [%x] - %m%n"/> </layout> </appender> <appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender"> <layout type="log4net.Layout.PatternLayout"> <param name="ConversionPattern" value="%d [%t] %-5p %c [%x] <%X{auth}> - %m%n"/> </layout> </appender> <!-- Set root logger level to ERROR and its appenders --> <root> <level value="DEBUG"/> <appender-ref ref="RollingLogFileAppender"/> <appender-ref ref="ConsoleAppender"/> </root> <!-- Print only messages of level DEBUG or above in the packages --> <logger name="IBatisNet.DataMapper.Configuration.Cache.CacheModel"> <level value="DEBUG"/> </logger> <logger name="IBatisNet.DataMapper.Configuration.Statements.PreparedStatementFactory"> <level value="DEBUG"/> </logger> <logger name="IBatisNet.DataMapper.LazyLoadList"> <level value="DEBUG"/> </logger> <logger name="IBatisNet.DataAccess.DaoSession"> <level value="DEBUG"/> </logger> <logger name="IBatisNet.DataMapper.SqlMapSession"> <level value="DEBUG"/> </logger> <logger name="IBatisNet.Common.Transaction.TransactionScope"> <level value="DEBUG"/> </logger> <logger name="IBatisNet.DataAccess.Configuration.DaoProxy"> <level value="DEBUG"/> </logger> </log4net>
文章开头指出了这里主要是做一个用户的增删改查,那么只需建一个用户表即可,数据库名称为donet,MySQL建表语句如下。
CREATE TABLE `t_user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(32) DEFAULT NULL, `age` int(11) DEFAULT NULL, `address` varchar(256) DEFAULT NULL, `email` varchar(100) DEFAULT NULL, `phone` varchar(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
在Views下新建一个名为System的文件夹,然后增加一个名为User的视图(不要选择母版),如图3-1-1所示。
图3-1-1 新增视图
在页面中引入ExtJS库及CSS样式。最终页面代码如下,对于ExtJS的开发这里就不细讲,以下代码主要完成了一个支持查询、新增、删除、修改的用户信息列表,前后端交互主要用json格式数据完成。
<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<dynamic>" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head id="Head1" runat="server"> <title>用户管理</title> <script src="../../Scripts/ext/ext-all.js" type="text/javascript"></script> <link href="../../Scripts/ext/resources/ext-theme-neptune/ext-theme-neptune-all.css" rel="stylesheet" type="text/css" /> <script type="text/javascript"> Ext.onReady(function () { var store = Ext.create('Ext.data.Store', { autoLoad: true, pageSize: 10, fields: ['id', 'name', 'age', 'address', 'email', 'phone'], proxy: { type: 'ajax', url: '/user/list', reader: { root: 'dataList', totalProperty: 'totalCount' } } }); var gridPanel = Ext.create('Ext.grid.Panel', { title: '用户管理', margin: 20, id: 'usergrid', renderTo: Ext.getBody(), selType: 'checkboxmodel', store: store, dockedItems: [{ xtype: 'toolbar', layout: 'fit', items: [{ xtype: 'form', border: 0, layout: { type: 'column' }, defaults: { xtype: 'textfield', labelAlign: 'right', labelWidth: 'auto', margin: 2 }, items: [{ fieldLabel: '姓名', name: 'name' }, { xtype: 'container', items: [{ xtype: 'button', text: '查询', handler: function () { Ext.apply(gridPanel.getStore().proxy.extraParams, this.up('form').getForm().getValues()); gridPanel.getStore().loadPage(1); } }, { xtype: 'button', margin: '0 0 0 4', text: '清除', handler: function () { this.up('form').getForm().reset(); Ext.apply(gridPanel.getStore().proxy.extraParams, this.up('form').getForm().getValues()); gridPanel.getStore().loadPage(1); } }] }] }] }, { xtype: 'toolbar', items: [{ text: '新增', iconCls: 'icon-add', handler: addUser }, { text: '删除', iconCls: 'icon-delete', handler: deleteUser }] }, { xtype: 'pagingtoolbar', store: store, displayInfo: true, dock: 'bottom' }], columns: [ { text: '姓名', dataIndex: 'name', flex: 1, align: 'center' }, { text: '年龄', dataIndex: 'age', flex: 1, align: 'center' }, { text: '地址', dataIndex: 'address', flex: 1, align: 'center' }, { text: '邮箱', dataIndex: 'email', flex: 1, align: 'center' }, { text: '电话', dataIndex: 'phone', flex: 1, align: 'center' }, { text: '操作', flex: 1, align: 'center', renderer: operationRenderer } ] }); function operationRenderer(value, metaData, record, rowIndex) { return '<a href="javascript:void(0);" onclick="modifyUser(' + rowIndex + ')">修改</a>'; } Ext.get(window).on('resize', function () { gridPanel.doLayout(); }); }); function addUser() { var win = getWindow(); win.setTitle('新增用户'); win.show(); } function modifyUser(rowIndex) { var win = getWindow(); win.setTitle('修改用户'); var record = Ext.getCmp('usergrid').getStore().getAt(rowIndex).getData(); win.down('form').getForm().setValues(record); win.show(); } function getWindow() { return Ext.create('Ext.window.Window', { modal: true, width: 500, id: 'win', items: [{ xtype: 'form', layout: { type: 'vbox', align: 'center' }, defaults: { labelAlign: 'right', margin: '5 0 5 0', labelWidth: 50 }, defaultType: 'textfield', items: [{ xtype: 'hiddenfield', name: 'id', value: 0 }, { fieldLabel: '姓名', name: 'name' }, { xtype: 'numberfield', fieldLabel: '年龄', name: 'age' }, { fieldLabel: '地址', name: 'address' }, { fieldLabel: '邮箱', name: 'email' }, { fieldLabel: '电话', name: 'phone' }] }], buttonAlign: 'center', buttons: [{ text: '提交', handler: function () { var params = this.up('window').down('form').getForm().getValues(); Ext.Ajax.request({ url: '/user/save', headers: { Accept: 'application/json', 'Content-Type': 'application/json' }, params: Ext.encode(params), success: function (response) { Ext.getCmp('usergrid').getStore().load(); Ext.getCmp('win').close(); } }); } }, { text: '取消', handler: function () { this.up('window').close(); } }] }); } function deleteUser() { var rows = Ext.getCmp('usergrid').getSelectionModel().getSelection(); if (rows.length) { var ids = []; for (var i = rows.length - 1; i >= 0; i--) { ids.push(rows[i].get('id')); } Ext.Ajax.request({ url: '/user/delete', headers: { Accept: 'application/json', 'Content-Type': 'application/json' }, params: Ext.encode(ids), success: function (response) { Ext.getCmp('usergrid').getStore().load(); } }); } } </script> </head> <body style="overflow: hidden"> </body> </html>
对于MVC开发模式的话,新增视图后还需要新增对应的Controller类来返回视图。在Controllers下新增名为SystemController的控制器。代码如下。
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; namespace MyServer.Controllers { public class SystemController : Controller { // // GET: /System/User public ActionResult User() { return View(); } } }
基于Ibatis.net开发最方便的一点是可以自己写SQL,其中增删改查功能都通过自己写的SQL文件来完成。下面讲如何配置相应的SQL,先在项目中增加个Maps的文件夹,然后增加一个User.xml文件,这里要相应的在SqlMap.config的sqlMaps节点下添加这个文件路径。每加一个xml文件都需要在SqlMap中添加映射。
SqlMap.config的sqlMaps节点内容如下。
<sqlMaps> <sqlMap resource="Maps/User.xml" /> </sqlMaps>
User.xml文件如下,包含了增删改查的SQL,具体Ibatis的语法可以在网上学习下。语法很简单,这里就不赘述了。
<?xml version="1.0" encoding="utf-8" ?> <sqlMap namespace="EntityModel" xmlns="http://ibatis.apache.org/mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <alias> <typeAlias alias="User" type="MyServer.User,MyServer"/> </alias> <resultMaps> <resultMap id="SelectAllResult" class="User"> <result property="id" column="id"/> <result property="name" column="name"/> <result property="age" column="age" /> <result property="address" column="address" /> <result property="email" column="email" /> <result property="phone" column="phone" /> </resultMap> </resultMaps> <statements> <select id="SelectAllUser" resultMap="SelectAllResult" parameterClass="User"> select id,name,age,email,address,phone from t_user <dynamic prepend="where"> <isParameterPresent> <isNotEmpty property="name" > name like CONCAT('%',#name#,'%') </isNotEmpty> </isParameterPresent> </dynamic> order by id desc <dynamic prepend="limit"> <isParameterPresent> <isNotEmpty property="start" > #start#,#limit# </isNotEmpty> </isParameterPresent> </dynamic> </select> <select id="SelectUserCount" resultClass="int"> select count(id) from t_user <dynamic prepend="where"> <isParameterPresent> <isNotEmpty property="name" > name like CONCAT('%',#name#,'%') </isNotEmpty> </isParameterPresent> </dynamic> </select> <insert id="InsertUser" parameterClass="User"> insert into t_user(name,age,address,email,phone) values(#name#, #age#, #address#, #email#, #phone# ) <selectKey property="id" type="pre" resultClass="int"> SELECT LAST_INSERT_ID() as Id </selectKey> </insert> <delete id="DeleteUserByIdList" parameterClass="list"> delete from t_user <dynamic prepend="where"> <isParameterPresent> <iterate open="(" close=")" conjunction="OR"> id = #[]# </iterate> </isParameterPresent> </dynamic> </delete> <update id="UpdateUser" parameterClass="User"> update t_user set <dynamic> <isParameterPresent> <isNotEmpty property="name" > name = #name# </isNotEmpty> <isNotEmpty prepend="," property="age" > age = #age# </isNotEmpty> <isNotEmpty prepend="," property="address" > address = #address# </isNotEmpty> <isNotEmpty prepend="," property="email" > email = #email# </isNotEmpty> <isNotEmpty prepend="," property="phone" > phone = #phone# </isNotEmpty> </isParameterPresent> </dynamic> Where id=#id# </update> </statements> </sqlMap>
那么如何去调用上面的这些sql了,通过上面的sql也可以知道每个语句都有一个id,要调用哪个只需要找到对应的id就行了,具体的调用方法,ibatis.net提供了方法。这里可以在项目中新增一个DAL文件夹,增加一个BaseDA.cs 的类,这个类提供了通用的一些调用sql语句的方法,一般参数就是上面的sql的id以及要传入到sql的参数,参数可以是实体类的实例或单个参数等。代码如下。
using System; using System.Collections.Generic; using System.Linq; using System.Web; using IBatisNet.DataMapper; namespace MyServer { public class BaseDA { public static int Insert<T>(string statementName, T t) { ISqlMapper iSqlMapper = Mapper.Instance(); if (iSqlMapper != null) { return (int)iSqlMapper.Insert(statementName, t); } return 0; } public static int Update<T>(string statementName, T t) { ISqlMapper iSqlMapper = Mapper.Instance(); if (iSqlMapper != null) { return iSqlMapper.Update(statementName, t); } return 0; } public static int Delete(string statementName, int primaryKeyId) { ISqlMapper iSqlMapper = Mapper.Instance(); if (iSqlMapper != null) { return iSqlMapper.Delete(statementName, primaryKeyId); } return 0; } public static int Delete(string statementName, int[] primaryKeyIdList) { ISqlMapper iSqlMapper = Mapper.Instance(); if (iSqlMapper != null) { return iSqlMapper.Delete(statementName, primaryKeyIdList); } return 0; } public static T Get<T>(string statementName, int primaryKeyId) where T : class { ISqlMapper iSqlMapper = Mapper.Instance(); if (iSqlMapper != null) { return iSqlMapper.QueryForObject<T>(statementName, primaryKeyId); } return null; } public static IList<T> QueryForList<T>(string statementName, T t) { ISqlMapper iSqlMapper = Mapper.Instance(); if (iSqlMapper != null) { return iSqlMapper.QueryForList<T>(statementName, t); } return null; } public static object QueryForObject<T>(string statementName, T t) { ISqlMapper iSqlMapper = Mapper.Instance(); if (iSqlMapper != null) { return iSqlMapper.QueryForObject(statementName, t); } return null; } } }
上面相当于完成了数据库层面的增删改查功能,接着就是完成前端要调用的增删改查接口。先新增一个User的实体类(去掉命名空间中的Models),放在Models文件夹下,然后新增一个名为UserController的控制器,用来提供User的增删改查接口,代码如下。
using System.Web.Mvc; using Newtonsoft.Json; using System.IO; using System.Collections; using log4net; namespace MyServer.Controllers { [HandleError] public class UserController : Controller { ILog logger = LogManager.GetLogger(typeof(UserController)); /// <summary> /// 获取用户列表 /// </summary> /// <returns></returns> public string List() { string[] allKeys = Request.QueryString.AllKeys; Hashtable ht = new Hashtable(); foreach (string key in allKeys) { ht[key] = Request.QueryString[key]; } User user = JsonConvert.DeserializeObject<User>(JsonConvert.SerializeObject(ht)); object obj = BaseDA.QueryForList<User>("SelectAllUser", user); object count = BaseDA.QueryForObject<User>("SelectUserCount", user); DataList dataList = new DataList() { success = true, msg = "", totalCount = (int)count, dataList = obj }; return JsonConvert.SerializeObject(dataList); } /// <summary> /// 新增及修改用户 /// </summary> /// <returns></returns> public string Save() { StreamReader sr = new StreamReader(Request.InputStream); string userStr = sr.ReadToEnd(); User user = JsonConvert.DeserializeObject<User>(userStr); if (user.id == 0) { BaseDA.Insert<User>("InsertUser", user); logger.Info("new user " + user.ToString()); } else { BaseDA.Update<User>("UpdateUser", user); logger.Info("update user " + user.ToString()); } Data data = new Data() { success = true, msg = "", data = null }; return JsonConvert.SerializeObject(data); } /// <summary> /// 删除用户 /// </summary> /// <returns></returns> public string Delete() { StreamReader sr = new StreamReader(Request.InputStream); string userStr = sr.ReadToEnd(); int[] ids = JsonConvert.DeserializeObject<int[]>(userStr); BaseDA.Delete("DeleteUserByIdList", ids); Data data = new Data() { success = true, msg = "", data = null }; return JsonConvert.SerializeObject(data); } } }
修改Global.asax文件,主要改一些路由匹配的规则,修改路由参数默认值为上面添加的User视图,controller改为System,action改为User,代码如图。
public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( "Default", // 路由名称 "{controller}/{action}/{id}", // 带有参数的 URL new { controller = "System", action = "User", id = UrlParameter.Optional } // 参数默认值 ); }
接着就是直接按ctrl+F5运行了。在用户管理面可以添加用户,删除用户,修改用户,根据姓名查询用户。效果图在文章开头就已给出,这里就不在重复了。