最新修改文章请移步到这里查看
1. 简介
通过一个用户的增删改查来讲一下标题所说的架构,对于相关原理及细节不做深入讲解。
先看下最终界面,虽然只有一个页面。
图1-1 用户管理界面
图1-2 用户新增
图1-3 用户修改
再来看下项目代码目录结构的截图。
图1-4 项目代码目录结构
其中引用到的类库截图如下,红色框内为新引入的类库。
图1-5 项目引用
2. 搭建
2.1 MVC搭建
利用VS2010建立一个MVC项,这里的MVC版本是2.0,如图2-1-1所示。
图2-1-1 建立MVC项目
图2-1-2 MVC项目
2.2 ExtJS导入
本项目采用的是ExtJS,所以Scripts下面的都可以删掉,然后引入ExtJS包,这里只需引入ExtJS中的resource文件夹和ext-all.js,当然引入压缩版的ExtJS更好,如图2-1-3所示。
图2-2-1 引入ExtJS
2.3 Ibatis.net配置
先添加引用,添加的包括开始所讲的哪些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配置最终结果
2.4 log4net配置
修改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>
2.5 数据库建表
文章开头指出了这里主要是做一个用户的增删改查,那么只需建一个用户表即可,数据库名称为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;
3 前后台开发
3.1 新增页面
在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);" οnclick="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();
}
}
}
3.2 增删改查功能开发
基于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);
}
}
}
3.3 运行
修改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运行了。在用户管理面可以添加用户,删除用户,修改用户,根据姓名查询用户。效果图在文章开头就已给出,这里就不在重复了。