目錄
【第一篇】ASP.NET MVC快速入門之數據庫操作(MVC5+EF6)
【第二篇】ASP.NET MVC快速入門之數據注解(MVC5+EF6)
【第三篇】ASP.NET MVC快速入門之安全策略(MVC5+EF6)
【第四篇】ASP.NET MVC快速入門之完整示例(MVC5+EF6)
【番外篇】ASP.NET MVC快速入門之免費jQuery控件庫(MVC5+EF6)
請關注三石的博客:http://cnblogs.com/sanshi
數據庫連接字符串
上一篇文章中,我們使用MVC的模板自動生成了CRUD的全部操作,但是沒有配置數據庫連接字符串,那么數據存到什么地方了?
打開項目的App_Data目錄,你可以發現數據庫原來在這里:
我們通過VS自帶的數據庫訪問工具,來看下表結構和其中的數據,首先找到[服務器資源管理器]面板,新增數據庫連接:
在添加連接向導對話框中,輸入服務器名:(LocalDb)\MSSQLLocalDB,這個是VS2015自帶的LocalDb的服務器實例名稱(如果你使用VS2013,這個名稱可能是:(LocalDB)\v11.0)。數據庫選擇我們剛剛創建的StudentDbContext。
原來如果沒有顯式的指定數據庫連接字符串,VS會使用默認的LocalDb實例,這個對應關系在Web.config中有定義:
<entityFramework> <defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework"> <parameters> <parameter value="mssqllocaldb" /> </parameters> </defaultConnectionFactory> <providers> <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" /> </providers> </entityFramework>
當然我們也可以明確指定數據庫連接字符串:
<connectionStrings> <add name="DefaultConnection" connectionString="Data Source=(LocalDb)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\AspNetMvc.QuickStart.Models.StudentDbContext.mdf;Initial Catalog=AspNetMvc.QuickStart.Models.StudentDbContext;Integrated Security=True" providerName="System.Data.SqlClient" /> </connectionStrings>
然后在代碼中引用這個數據庫連接字符串:
public class StudentDbContext : DbContext { public StudentDbContext() : base("DefaultConnection") { } public DbSet<Student> Students { get; set; } }
注意:如果使用的VS2013,Data Source應該是(LocalDb)\v11.0,而VS2015對應的是(LocalDb)\MSSQLLocalDB。
經過這個改變,在真正部署到MSSQL服務器時,簡單修改數據庫連接字符串就可以了。
數據庫表結構
打開Students的表定義:
可以看下EF是怎么將Student模型映射到數據庫表結構的:
1. 模型中ID屬性的數據映射為表的主鍵。
2. 模型中的string類型映射為表的nvarchar(MAX)。
3. 模型中的int和DateTime分別映射為表的int和datetime類型。
再來看下上一篇文章中添加到表中的數據:
如果你之前有數據庫設計的經驗,會很容易發現這個表結構的問題:
1. Name和Major存儲字符串,一般需要限制最大長度,比如nvarchar(200)。
2. Name和Major列應該不允許為空。
那么怎么來實現這兩個需求呢?直接修改數據庫肯定是不行的!
數據注解
我們應該從模型入手,還記得我們在上一篇文章結尾說的那句話嗎,數據模型不僅會影響數據庫的表結構,還會控制MVC視圖層的客戶端驗證和控制器層的服務器端驗證。
修改Student模型類,添加適當的數據注解:
public class Student { public int ID { get; set; } [Required] [StringLength(200)] public string Name { get; set; } public int Gender { get; set; } [Required] [StringLength(200)] public string Major { get; set; } public DateTime EntranceDate { get; set; } }
如果輸入[Required]時沒有智能感知,很可能是沒有引用相應的命名空間,VS可以很方便的協助我們添加:
這樣就會在文件頭部添加如下引用:
using System.ComponentModel.DataAnnotations;
直接運行項目(Ctrl+F5),此時我們會看到如下的錯誤頁面:
相信使用EF的同學都會遇到這個頁面,上面的提示也很明確,包含兩個層次的信息:
1. 數據庫創建之后模型改變了。
2. 可以使用數據遷移來更新數據庫。
數據遷移(Migrations)
從VS的[工具]菜單中,找到Nuget包管理器控制臺:
啟用數據遷移
在控制臺中輸入如下命令:Enable-Migrations
這時會在項目目錄中增加一個Migrations文件夾,里面放置了兩個文件:
EF會通過C#代碼的方式將每一次對模型的修改保存到這個文件夾中,現在來看下生成的文件內容:
每個遷移文件,都包含Up和Down兩個重寫函數,分別對應于更新和回退。上面的代碼也很直白,Up函數中創建一個Students表,定義表結構并指定ID主鍵(PrimaryKey),Down函數用來回退操作,里面簡單的刪除了Students表。
可以看到,這里的創建表操作并沒有使用最新的模型(Name列沒有nullable的設置),因為這是初始模型對應的表結構,EF會在數據庫中自動生成一個名為__MigrationHistory表來跟蹤數據庫的狀態。
增加遷移項
增加遷移項需要我們手工來進行,在程序包管理器控制臺中,輸入如下命令:
Add-Migration Add_Annotation_Name_Major
這時會在Migrations目錄下生成遷移文件,文件是以[時間+遷移名]命名的,方便查找:
201612160406415_Add_Annotation_Name_Major.cs
更新到數據庫
此時,數據庫尚未改變,我們還需要手工命令來更新數據庫:
Update-Database
此時,再來查看數據庫中Students的表結構:
Name列的數據類型和是否允許Null都已經改變了。
在真實的項目中,數據庫可能部署在遠程服務器中,這時我們就不能直接在VS中通過Update-Database來更新數據庫了。
不過我們可以生成更新SQL腳本,然后拿到數據庫服務器上執行。生成這個SQL腳本的方法:
Update-Database -Script
-SourceMigration: InitialCreate
-TargetMigration: Add_Annotation_Name_Major
來看下生成的SQL更新腳本:
有了這個SQL更新腳本,我們就可以方便的更新遠程數據庫了。
視圖的客戶端驗證
現在運行項目,轉到創建頁面:
可以看到,如果Name為空則會有錯誤提示信息,而Major輸入字符串過多,也會有提示信息,而這些設置是來自模型的數據注解。
這個客戶端驗證是有jQuery的validation插件提供的:
http://bassistance.de/jquery-plugins/jquery-plugin-validation/
如果你查看頁面源代碼,會發現Major輸入框的input標簽上有相應的自定義屬性data-val-length-max=200和data-val-length,而這些屬性值正是來自于模型的數據注解。
控制器的服務器端驗證
在啟用JavaScript的情況下,由于所有的錯誤輸入在客戶端就會被攔截,所以根本到達不了服務器,不過這并不表示惡意用戶無法提交錯誤的輸入,有很多種方法可以做到:
禁用JavaScript
不同瀏覽器禁用JavaScript的方法不同,在Chrome中,F12打開開發工具,然后找到設置對話框:
此時提交頁面,你會看到和前面完全相同的頁面,由于本地運行速度很快,你甚至可能沒意識已經發起了一次HTTP POST請求,而顯示錯誤提示的頁面來自服務器,而不是客戶端:
響應正文包含如下內容,其中錯誤信息是服務器端生成的:
<div class="form-group"> <label class="control-label col-md-2" for="Name">Name</label> <div class="col-md-10"> <input class="input-validation-error form-control text-box single-line" data-val="true" data-val-length="字段 Name 必須是最大長度為 200 的字符串。" data-val-length-max="200" data-val-required="Name 字段是必需的。" id="Name" name="Name" type="text" value="" /> <span class="field-validation-error text-danger" data-valmsg-for="Name" data-valmsg-replace="true">Name 字段是必需的。</span> </div> </div>
此時再回過頭來看下Students控制器中Create操作方法的定義:
[HttpPost] [ValidateAntiForgeryToken] public ActionResult Create([Bind(Include = "ID,Name,Gender,Major,EntranceDate")] Student student) { if (ModelState.IsValid) { db.Students.Add(student); db.SaveChanges(); return RedirectToAction("Index"); } return View(student); }
如果驗證失敗,則不更新數據庫,并返回帶Student模型的視圖。
模擬POST請求
有很多工具可以模擬POST,這里我們講解如果使用Fiddler來模擬項服務器提交POST請求。
打開Fiddler,就開始自動監測所有的HTTP請求,這時我們刷新Create頁面,并在JavaScript禁用的情況下,提交表單,這時會有兩個請求:
首先選中左側的第二個請求,右側面板中選擇Inspectors->WebForms,下面會顯示三個窗格:
1. QueryString:當前請求的URL查詢字符串。
2. Body:POST請求的表單參數。
3. 第三部分:響應正文,我們可以看到服務器端返回的錯誤信息。
現在切換到Composer選項卡,我們可以在這里面模擬POST請求:
上面有一段提示信息:使用這個頁面創建一個請求。你可以通過拖拽的方式從左側會話列表中拷貝一個之前的請求。
這就方便多了,我們從左側選中第二個請求并拖拽到本頁面:
這時頁面背景變成明顯的綠色以作提示,拖拽結束:
這時,Fiddler自動幫我們設置了模擬POST請求的參數,拷貝自之前的某個請求,這時的[Request Body]是經過URL編碼的,我們可以方便的進行解碼:
我們把這段代碼修改成:
__RequestVerificationToken=wwoxICDootbixw8YMiFIOU1WW95QSCicREsWLeewlSAE28sdyEA0ZChlY0nfuOlxu2WDIjcrx086GYkaBOAtewyARWbeRZp0kD6tRt-hyAs1&Name=張三石&Gender=1&Major=&EntranceDate=2000-09-01
把這段字符串拷貝到Fiddler中的[Request Body],并點擊[Execute]按鈕,這時會發起一個新的模擬請求:
注意這個請求并不是從頁面發出的,而是通過工具模擬的HTTP POST請求,并且我們還修改了其中的表單參數(Name=張三石,Major=空字符串),這樣當然也就會躲開瀏覽器端的JavaScript驗證規則,但是還是無法穿透服務器端的驗證。
這也正是MVC中數據注解帶來的便利,一個地方定義,三個地方使用(數據庫表結構、客戶端驗證,服務器端驗證)。
小結
本章我們首先查看了EF自動生成的數據庫結構,然后為數據模型添加數據注解,繼而介紹了數據遷移的工作過程。數據注解不僅對數據庫表結構產生影響,而且會應用到前臺的客戶端驗證和服務器端驗證,接下來我們詳細講解了兩則躲避客戶端驗證的手段,分別為禁用JavaScript和模擬POST請求。從而更加深刻的認識到數據注解給我們提供的便利:一個地方定義,三個地方使用(數據庫表結構、客戶端驗證,服務器端驗證)
在創建新用戶頁面,我們可以看到兩個安全相關的代碼(ValidateAntiForgeryToken和Bind),它們分別用于阻止CSRF跨站請求偽造和Over-Posting過多提交攻擊,下一篇文章我們會詳細介紹。
文章列表