您的位置:首页 > 数据库

MVC初学笔记(3):MVC访问数据库实现登录

2014-10-31 10:57 381 查看
原来一直不明白MVC项目是怎样访问数据库的,项目中也没有看到与ADO.NET相关的代码,后来在这个简单的登录小功能了解了,原来是EF直接访问的。以下是一个MVC与三层架构结合做的一个简单登录功能小项目。

首先创建一个空的MVC项目,然后把三层给搭起来。添加了一个Common类库用于放工具类,添加一个Entity类库放实体类用于传递数据。



1.编写Entity层。它的作用就是把数据库的每一张表变成一个对象,在各层中传递数据。以下是User表实例UserEntity

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace UserReservation.Entitys
{
public class UserEntity
{
public string Id { get; set; }

public string Name { get; set; }

public string Account { get; set; }

public string Password { get; set; }

public string Mobile { get; set; }

public string Credentials_Number { get; set; }

public DateTime CreateTime { get; set; }

public int UserType { get; set; }

public string Company_Id { get; set; }

public string Email { get; set; }
}
}


2.实现DAL层。DAL层主要作用就是与数据库打交道。该项目是用的Mysql(估计使用其他数据库实现过程也是大同小异)。结构:



这其中的Model是数据库中每一张表对应的模型,在Entity Framework中是必不可少的,如果使用的是Code First,那么数据表就是根据这些模型来生成的——但是此项目是DB First...它与上面的Entity是有本质区别的。我就白痴地把Model看做是一张张的表好了。Entity和Model的区别在具体使用的时候应该就可以意会了。以下是User 的Model

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.DataAnnotations;

namespace UserReservation.DAL.Models
{
public class User
{

[Key]
[MaxLength(36)]
public string Id { get; set; }
[MaxLength(200)]
public string Name { get; set; }
[MaxLength(200)]
public string Account { get; set; }

[MaxLength(50)]
public string Password { get; set; }

[MaxLength(40)]
public string Mobile { get; set; }

[MaxLength(100)]
public string Credentials_Number { get; set; }

public int Credentials_Type { get; set; }

public DateTime CreateTime { get; set; }

public int UserType { get; set; }

[MaxLength(36)]
public string Company_Id { get; set; }
[MaxLength(200)]
public string Email { get; set; }
}
}


中括号那些代码都是一些约束。他们需要引入using System.ComponentModel.DataAnnotations;命名空间。

下面创建上下文Context类。至于什么是上下文,到目前为止我还很迷糊。在这里我简单的把它理解为程序与数据库进行沟通的一个桥梁吧,在这里的作用就是请求和响应程序对数据库的操作命令。它是MVC访问数据库的核心。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Entity;
using UserReservation.DAL.Models;

namespace UserReservation.DAL.Context
{
public class RDBaoContext: DbContext
{
public RDBaoContext()
: base("name=RDBaoContext")
{
}
public DbSet<User> Users { get; set; }

public DbSet<Company> Companies { get; set; }

private const string context_name = "RDBaoContext";
public static RDBaoContext CurrentContext
{
get
{
var http = System.Web.HttpContext.Current;
RDBaoContext context = http == null ? null : (RDBaoContext)http.Items["MySql_" + context_name];
if (null == context)
{
context = new RDBaoContext();
if (null != http)
{
http.Items["MySql_" + context_name] = context;
}
}
return context;
}
}
}
}


上下文类它继承自DbContext类。DbContext类为Entity Framework的基础,包括初始化数据库连接,Database类,DbSet类,实体验证DbEntityValidationResult, 实体类DbEntityEntry等。这个类里面创建了一个构造函数并指定执行父类构造函数:public DbContext(string nameOrConnectionString);(就是: base("name=RDBaoContext")),此处的name就是项目Web.config文件中的数据库连接字符串。所以配置文件里面的链接字符串可别忘了写

<connectionStrings>
<add name="RDBaoContext" connectionString="Data Source=192.168.0.123;port=1234;Initial Catalog=DBtest;user id=admin;password=1234;charset=gb2312;" providerName="MySql.Data.MySqlClient" />
</connectionStrings>

这样,利用context,就能对数据库进行CRUD操作了。至于CurrentContext()这个成员方法,还尚不了解。意为当前使用的上下文对象?

现在就差具体操作了。在UserDAL中:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UserReservation.DAL.Context;
using UserReservation.Entitys;

namespace UserReservation.DAL
{
public class UserDAL
{
UserReservation.DAL.Context.RDBaoContext context = UserReservation.DAL.Context.RDBaoContext.CurrentContext;
//实例化上下文对象
public UserEntity Get(string account, string pwd, string companyName)
{
var query = from a in context.Users
from b in context.Companies
where a.Company_Id == b.Id
&& a.Account == account
&& a.Password == pwd
&& b.Code == companyName
select new UserEntity
{
Account = a.Account,
Company_Id = a.Company_Id,
CreateTime = a.CreateTime,
Credentials_Number = a.Credentials_Number,
Email = a.Email,
Id = a.Id,
Mobile = a.Mobile,
Name = a.Name,
Password = a.Password,
UserType = a.UserType
};
//利用上下文对象联表获取数据库中的所需数据装载到一个实体中并返回该实体
return query.FirstOrDefault();
}

}
}


这里应该就会看到Entity和Model各有什么作用了。

3. BLL层.

这个层里面处理业务逻辑,不过结合MVC的话,大量业务逻辑都会在Controller中给处理了。这里只是一个传递的作用了

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UserReservation.DAL;
using UserReservation.Entitys;

namespace UserReservation.BLL
{
public class UserBLL
{
UserDAL _ud = new UserDAL();
public UserEntity Get(string account, string pwd, string companyName)
{
return _ud.Get(account, pwd, companyName);
}
}
}


4.Common层.

由于本项目的登录是需要有验证码的,公共层类库就放了一个验证码生成类。关于验证码怎么生成,这其中的原理也不用去研究,下次要使用验证码直接调用就好了。

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Web;

namespace UserReservation.Common
{
public class ValidateCode
{
public ValidateCode()
{
}
/// <summary>
/// 验证码的最大长度
/// </summary>
public int MaxLength
{
get { return 10; }
}
/// <summary>
/// 验证码的最小长度
/// </summary>
public int MinLength
{
get { return 1; }
}

/// <summary>
/// 生成验证码
/// </summary>
/// <param name="length">指定验证码的长度</param>
/// <returns></returns>
public string CreateValidateCode(int length)
{
int[] randMembers = new int[length];
int[] validateNums = new int[length];
string validateNumberStr = "";
//生成起始序列值
int seekSeek = unchecked((int)DateTime.Now.Ticks);
Random seekRand = new Random(seekSeek);
int beginSeek = (int)seekRand.Next(0, Int32.MaxValue - length * 10000);
int[] seeks = new int[length];
for (int i = 0; i < length; i++)
{
beginSeek += 10000;
seeks[i] = beginSeek;
}
//生成随机数字
for (int i = 0; i < length; i++)
{
Random rand = new Random(seeks[i]);
int pownum = 1 * (int)Math.Pow(10, length);
randMembers[i] = rand.Next(pownum, Int32.MaxValue);
}
//抽取随机数字
for (int i = 0; i < length; i++)
{
string numStr = randMembers[i].ToString();
int numLength = numStr.Length;
Random rand = new Random();
int numPosition = rand.Next(0, numLength - 1);
validateNums[i] = Int32.Parse(numStr.Substring(numPosition, 1));
}
//生成验证码
for (int i = 0; i < length; i++)
{
validateNumberStr += validateNums[i].ToString();
}
return validateNumberStr;
}

/// <summary>
/// 创建验证码的图片
/// </summary>
/// <param name="validateCode">验证码</param>
public byte[] CreateValidateGraphic(string validateCode)
{
Bitmap image = new Bitmap((int)Math.Ceiling(validateCode.Length * 12.0), 22);
Graphics g = Graphics.FromImage(image);
try
{
//生成随机生成器
Random random = new Random();
//清空图片背景色
g.Clear(Color.White);
//画图片的干扰线
for (int i = 0; i < 25; i++)
{
int x1 = random.Next(image.Width);
int x2 = random.Next(image.Width);
int y1 = random.Next(image.Height);
int y2 = random.Next(image.Height);
g.DrawLine(new Pen(Color.Silver), x1, y1, x2, y2);
}
//Font font = new Font("Arial", 12, (FontStyle.Bold | FontStyle.Italic));
string[] fontName = { "华文新魏", "宋体", "圆体", "黑体", "隶书" };
Font font = new Font(fontName[new Random().Next(0, validateCode.Length)], 12, (FontStyle.Bold | FontStyle.Italic));
LinearGradientBrush brush = new LinearGradientBrush(new Rectangle(0, 0, image.Width, image.Height),
Color.Blue, Color.DarkRed, 1.2f, true);
g.DrawString(validateCode, font, brush, 3, 2);
//画图片的前景干扰点
for (int i = 0; i < 100; i++)
{
int x = random.Next(image.Width);
int y = random.Next(image.Height);
image.SetPixel(x, y, Color.FromArgb(random.Next()));
}
//画图片的边框线
g.DrawRectangle(new Pen(Color.Silver), 0, 0, image.Width - 1, image.Height - 1);
//保存图片数据
MemoryStream stream = new MemoryStream();
image.Save(stream, ImageFormat.Jpeg);
//输出图片流
return stream.ToArray();
}
finally
{
g.Dispose();
image.Dispose();
}
}

/// <summary>
/// 得到验证码图片的长度
/// </summary>
/// <param name="validateNumLength">验证码的长度</param>
/// <returns></returns>
public static int GetImageWidth(int validateNumLength)
{
return (int)(validateNumLength * 12.0);
}

/// <summary>
/// 得到验证码的高度
/// </summary>
/// <returns></returns>
public static double GetImageHeight()
{
return 23;
}
}
}


4.UI层

完成了上述几个层,接下来就是UI了。在Controller文件夹中添加LoginController控制器:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using UserReservation.BLL;
using UserReservation.Common;
using UserReservation.Web.Models;

namespace UserReservation.Controllers
{
public class LoginController : Controller
{
//
// GET: /Login/

[HttpGet]
public ActionResult Login()
{
LoginModel model = new LoginModel();
Response.AddHeader("P3P", "CP=CAO PSA OUR");
return View(model);
}

[HttpPost]
public ActionResult Login(LoginModel model)
{
if (null == Session["ValidateCode"] ||
(null != Session["ValidateCode"] && Session["ValidateCode"].ToString() != model.ValidateCode))
{
ViewBag.Message = "验证码不正确";
}
else
{
model.UserPwd = encryptPwd(model.UserPwd);//将密码加密
UserBLL bll = new UserBLL();
var user = bll.Get(model.UserAccount, model.UserPwd, model.CompanyName);
if (user != null)
{
ViewBag.Message = "登录成功!";
}
else
{
ViewBag.Message = "登录信息不正确!";
}
}
return View(model);
}

public ActionResult GetValidateCode(string timestamp)
{
Response.AddHeader("P3P", "CP=CAO PSA OUR");
ValidateCode vCode = new ValidateCode();
string code = vCode.CreateValidateCode(4);
Session["ValidateCode"] = code;
byte[] bytes = vCode.CreateValidateGraphic(code);
return File(bytes, "image/gif");
}
//密码加密方法
public string encryptPwd(string pwd)
{
return System.Web.Security.FormsAuthentication.HashPasswordForStoringInConfigFile(pwd, "MD5");
}
}
}


这个控制器中有两个Login的Action.一个是[HttpGet]方式,一个是[HttpPost]方式。以前我也搞不清楚这两个有什么区别,不过在老项目上面断点,然后百度一下资料,就有一个大致的了解。顾名思义,get是获取,Post是发送,一个是获取数据,一个是发送数据。对于LoginController对应的Login页面,什么时候需要获取数据,什么时候需要发送数据呢?获取数据我相信无非是页面加载或查询操作的时候,而发送数据一般都是提交表单的时候吧。这样就容易理解了,在Login.cshtml页面加载的时候会执行带有[HttpGet]前缀的action,而当我们提交表单(点击登录)的时候就会执行带有[HttpPost]前缀的action。至于有[HttpGet]前缀的action中的“Response.AddHeader("P3P",
"CP=CAO PSA OUR");”这一句话,查了资料是这么个意思:“一段解决IFRAME中SESSION无法保留的代码要理解原理.p3p是微软的隐私策略,通常情况下跨域iframe或者frameset默认采用的隐私策略为“中”,该级别的策略拒绝保留session。CAO PSA OUR则意味着你同意跨域保留session,但是也意味着你的网站不再安全。”

对了,登录时执行的action中都有LoginModel,这个Model是在Web项目里的,它又是用来干嘛的呢。



以下是LoginModel代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace UserReservation.Web.Models
{
public class LoginModel
{

public string UserAccount { get; set; }

public string UserPwd { get; set; }

public string CompanyName { get; set; }

public string ValidateCode { get; set; }

}
}

这个model也是用于传递数据的,相当于Entity类中的实体作用一样。既然是用来用户登录传递数据的,而Entity中又有一个UserEntity,那么为什么不直接用UserEntity而又单独去写一个LoginModel呢?仔细看UserEntity和LoginModel就可以知道,LoginModel除了简单符合登录的所有条件之外,他还有一个字段是UserEntity中没有的:CompanyName.

看登录界面就应该知道:



User表中只有CompanyID,并没有CompanyName。验证登录信息需要User表和Company表联合查询才能进行,因此创建LoginModel还是很有好处的,其他项目中出现很多这种情况,很多的Model只不过是为了配合一个简单查询。因此搞清楚这些Model到底有什么作用还是很关键的。

至于GetValidateCode()方法就是验证码了,这个就不多提了。

然后添加视图,以下是登录界面的前端代码:

@model UserReservation.Web.Models.LoginModel

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

<script src="~/Content/Scripts/jquery-1.8.3.min.js"></script>
<link href="~/Content/CSS/LoginCSS.css" rel="stylesheet" />

<title>实时保登录</title>

</head>
<script type="text/javascript">
var errorMessage = "@ViewBag.Message";
$(function () {

if (errorMessage != "") {
alert(errorMessage);
}
AddValidateCodeRefresh();

});
//添加验证码刷新事件
function AddValidateCodeRefresh() {
$("#imgV").click(function () {
var src = "@Url.Content("~/Login/GetValidateCode")" + '?timestamp' + (new Date()).toString();
$("#imgV").attr("src", src);
});
}
</script>
<body>
<div class="login">
<p><img src="../Content/images/a-logo.png" /><b>实时保登录</b></p>
@using (Html.BeginForm("Login", "Login", null, FormMethod.Post, new { id = "mainForm" }))
{
<ul>
<li><span>帐 号:</span>@Html.TextBoxFor(model => model.UserAccount, new { @name = "user", @id = "txtacc" })</li>
<li><span>密 码:</span>@Html.PasswordFor(model => model.UserPwd, new { @name = "pasword", @id = "txtpwd" })</li>
<li><span>企业名:</span>@Html.TextBoxFor(model => model.CompanyName, new { @name = "name", @id = "txtcom" })</li>
<li class="number">

<span>验证码:</span>@Html.TextBoxFor(model => model.ValidateCode, new { @name = "number", @id = "txtcod" })
<i><img id="imgV" src="@Url.Action("GetValidateCode", "Login", new { timestamp = DateTime.Now.Ticks })" width="74" height="30" alt="看不清请点击刷新验证码" title="看不清请点击刷新验证码" /></i>

</li>
<li><a href="javascript:check();">登 录</a></li>
</ul>
}
</div>

</body>

</html>

<script type="text/javascript">

function check() {

var acc = document.getElementById('txtacc').value;
var pwd = document.getElementById('txtpwd').value;
var com = document.getElementById('txtcom').value;

if ("" == acc) {
alert('账号不能为空!');
}
else if ("" == pwd) {
alert('密码不能为空!');
}else if(""==com){
alert('企业名称不能为空!');
}

else {
$('#mainForm').submit();
}

}

</script>


最后要指出的是:除了DAL,BLL,Web这三层之间的引用,最值得一提的就是需要添加Entity Framework、Mysql(不同的数据库有不同的程序集)等程序集的引用,可以在Nuget程序包中联机找到下载或者手动在项目根目录的packages文件夹中添加源文件,如果是手动添加的,则需要在配置文件中添加节点。版本问题一定要小心,不同的层里面引用程序集的时候版本一定要相同。





OK了。原来MVC就是这样访问数据库的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: