One ASP.NET及完美的開發體驗 - ASP.NET MVC 5
One ASP.NET及完美的開發體驗 - ASP.NET MVC 5
作者:陳傳興 ( Bruce, 微軟最有價值專家 )
註 – 本篇說明了 ASP.NET改進及 Visual Studio 2013的應用,使用Web Forms或是 MVC 開發技術者皆可閱讀,部份畫面及內容已更新為 VS 2013 RC版本
ASP.NET MVC從2007年CTP方式釋出,2009年正式發行ASP.NET MVC 1.0版,2010年發行ASP.NET MVC 2.0版,2011年發行ASP.NET MVC 3.0版,2012年發行ASP.NET MVC 4.0版,ASP.NET MVC已經發展為一個成熟的Model-View-Controller設計模式的開發框架。
ASP.NET MVC 5帶來全新的開發者體驗,One ASP.NET的整合新範本系統,經由單一入口即可完成所有Web範本的選擇,各種Web範本之間也可以靈活組合出新範本,例如,Web Forms + Web API等。MVC 5提供更有彈性的會員認證系統,透過Code First的方式開發人員不在受限於固定的Schema,可自由定義所需的資料Schema。採用Bootstrap設計的全新Web範本介面,Bootstrap提供高度彈性,讓開發人員也能快速擁有架構頁面介面及開發出支援響應式網頁 (Responsive Web)的能力。
One ASP.NET
隨著Visual Studio 2013的發行,開發工具又向前邁出大一步,它正在統一一致的體驗,現在,你能夠實現相同功能集,無論你如何開始建置ASP.NET應用程式。例如,開發人員可以在Web Forms專案中透過支架(Scaffold)加入Web API,開發人員可以自由混合選擇你所想要的技術。One ASP.NET是為了讓開發人員在所愛的ASP.NET裡更容易做想做的事,One ASP.NET應該給開發人員信心,無論他們選擇什麼,他們仍然是在一個可信任基礎框架-ASP.NET-下進行開發。
MVC專案範本無縫地整合成一個全新的One ASP.NET體驗。透過One ASP.NET的專案建立精靈,開發人員可以客製化MVC專案和組態認證。MVC專案現在是標準Web應用程式的一部分且不再使用它們自己的專案GUID。
圖一
圖一為Visual Studio 2013預設的【Web範本】,其中只有一個範本【ASP.NET Web應用程式】。當我們選擇【ASP.NET Web應用程式】專案範本可見到圖二:
圖二
圖二是一個統一介面,所有的【ASP.NET Web應用程式】都集中於此介面之中,新增ASP.NET專案介面之中提供了以下範本:
« Empty
« Web Forms
« MVC
« Web API
« SPA
如果在我們選擇的範本裡想要增加其他框加的參考,可以圖三之處勾選想加入的框加參考,Visual Studio 2013會加入最小所需的額外參考與NuGet套件。
圖三
支架(Scaffolding)
« Visual Studio 2013新的支架UI介面。
« 完整重寫MVC與WebForms支架。
n 現在,從任何Web應用程式使用ASP.NET支架可以建立MVC的Views與Controller。
« MVC支架支援已存在的非MVC或Web API專案。
n 使一個MVC或Web API的專案安裝所有必要的NuGet套件,加入所有MVC相依的檔案與資料夾,更新Web.Config組態,更新Global.asax。
n 允許單獨使用支架僅僅加入最少或完整的MVC 5依賴關係組態。
u 在極少選項,僅為MVC安裝最少量的NuGet套件。
u 在完整選項,也僅安裝最少量NuGet套件+Layout頁面+錯誤頁面+App_Start組態檔案+Scripts。
ASP.NET Identity
ASP.NET Identity是一套全新的會員身分系統(membership system)用於建置ASP.NET應用程式。ASP.NET Identity便於整合使用者特定設定檔(profile)資料與應用程式資料。ASP.NET Identity還允許開發人員控制應用程式的保存模型(persistence model),開發人員可選擇將資料儲存在SQL Server資料庫或是其住保存儲存區。
Visual Studio 2013更新MVC 5專案範本使用ASP.NET Identity進行身份認證與身份管理。透過圖二的【設定驗證】可進行驗證相關設定:
不驗證
圖四
如果你的應用程式不需要有任何驗證設定,請選取此選項。
個別使用者帳戶
圖五
你可以對應用程式中的使用者帳戶有完整控制能力。使用者將能夠向你的應用程式登錄帳戶,方法有在網站上建立使用者名稱和密碼,以及透過Facebook、Google、Microsoft Account、Twitter等社交網站的帳戶登入。
Windows驗證
圖六
如果你計劃在本身的區域網路執行你的應用程式,可以使用Windows驗證。使用者將以自己的本機或網域帳戶進行驗證。
個別使用者帳戶
如果選擇使用「個別使用者帳戶」,應用程式將會使用ASP.NET會員身分系統(membership system)進行使用者驗證。ASP.NET會員身分系統能夠讓使用者在網站上使用名稱與密碼來註冊帳戶,或者,使用社交網站如Facebook、Google、Microsoft Account或Twitter來進行登入。ASP.NET會員身分系統將使用者資訊(profile information)儲存於SQL Server LocalDB資料庫,在正式網站,也選擇發行至SQL Server或Windows Azure SQL Database。
就功能而言,Visual Studio 2013與Visual Studio 2012都是一樣的,但底層的ASP.NET會員身分系統的程式碼已經完全重新改寫,新程式碼的優點有:
« 新會員身分系統是基於OWIN,而不是ASP.NET表單驗證模組。這意味著開發人員可以使用相同的身分驗證機制,無論是在IIS裡的MVC或Web Forms,或是Self-Host的Web API或SignalR。
« 新會員身分系統資料庫是透過Entity Framework Code First來管理,所有的表格都是實體類別來呈現,開發人員可以修改Code First類別內容。這意味著開發人員可以輕鬆地自訂資料庫結構和設定檔相關的Web使用者介面以適應自己的需要,開發人員可以輕鬆地透過Code First遷移(migration)發行更新。
新會員身分系統已在Visual Studio 2013新範本中實作,它也可以在任何.NET Framework 4.5或更高的專案中手動實作。新ASP.NET會員身分系統是建置Internet網站且有外部使用者驗證需求的一個好選擇。
IdentityConfig與IdentityModels
如果新增一個預設的MVC專案範本(預設使用「個別使用者帳戶」驗證),在App_Start資料夾中可發現一個新的IdentityConfig.[cs | vb]組態檔。
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Security.Claims;
using System.Security.Principal;
using System.Threading.Tasks;
using System.Web;
using System.Web.Helpers;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
using WebApplication2;
using WebApplication2.Models;
namespace WebApplication2
{
public static class IdentityConfig
{
public const string LocalLoginProvider = "Local";
public static IUserSecretStore Secrets { get; set; }
public static IUserLoginStore Logins { get; set; }
public static IUserStore Users { get; set; }
public static IRoleStore Roles { get; set; }
public static string RoleClaimType { get; set; }
public static string UserNameClaimType { get; set; }
public static string UserIdClaimType { get; set; }
public static string ClaimsIssuer { get; set; }
public static void ConfigureIdentity()
{
var dbContextCreator = new DbContextFactory<IdentityDbContext>();
Secrets = new EFUserSecretStore<UserSecret>(dbContextCreator);
Logins = new EFUserLoginStore<UserLogin>(dbContextCreator);
Users = new EFUserStore<User>(dbContextCreator);
Roles = new EFRoleStore<Role, UserRole>(dbContextCreator);
RoleClaimType = ClaimsIdentity.DefaultRoleClaimType;
UserIdClaimType = "https://schemas.microsoft.com/aspnet/userid";
UserNameClaimType = "https://schemas.microsoft.com/aspnet/username";
ClaimsIssuer = ClaimsIdentity.DefaultIssuer;
AntiForgeryConfig.UniqueClaimTypeIdentifier = IdentityConfig.UserIdClaimType;
}
public static IList<Claim> RemoveUserIdentityClaims(IEnumerable<Claim> claims)
{
List<Claim> filteredClaims = new List<Claim>();
foreach (var c in claims)
{
if (c.Type != ClaimTypes.Name &&
c.Type != ClaimTypes.NameIdentifier)
{
filteredClaims.Add(c);
}
}
return filteredClaims;
}
public static void AddRoleClaims(IEnumerable<string> roles, IList<Claim> claims)
{
foreach (string role in roles)
{
claims.Add(new Claim(RoleClaimType, role, ClaimsIssuer));
}
}
public static void AddUserIdentityClaims(string userId, string displayName, IList<Claim> claims)
{
claims.Add(new Claim(ClaimTypes.Name, displayName, ClaimsIssuer));
claims.Add(new Claim(UserIdClaimType, userId, ClaimsIssuer));
claims.Add(new Claim(UserNameClaimType, displayName, ClaimsIssuer));
}
public static void SignIn(HttpContextBase context, IEnumerable<Claim> userClaims, bool isPersistent)
{
context.SignIn(userClaims, ClaimTypes.Name, RoleClaimType, isPersistent);
}
}
}
namespace Microsoft.AspNet.Identity
{
public static class IdentityExtensions
{
public static string GetUserName(this IIdentity identity)
{
return identity.Name;
}
public static string GetUserId(this IIdentity identity)
{
ClaimsIdentity ci = identity as ClaimsIdentity;
if (ci != null)
{
return ci.FindFirstValue(WebApplication2.IdentityConfig.UserIdClaimType);
}
return String.Empty;
}
public static string FindFirstValue(this ClaimsIdentity identity, string claimType)
{
Claim claim = identity.FindFirst(claimType);
if (claim != null)
{
return claim.Value;
}
return null;
}
}
}
Models資料夾中也有一個新的IdentityModels.[cs | vb]。
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Data.Entity.Validation;
using System.Linq;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
namespace WebApplication2.Models
{
public class User : IUser
{
public User()
: this(String.Empty)
{
}
public User(string userName)
{
UserName = userName;
Id = Guid.NewGuid().ToString();
}
[Key]
public string Id { get; set; }
public string UserName { get; set; }
}
public class UserLogin : IUserLogin
{
[Key, Column(Order = 0)]
public string LoginProvider { get; set; }
[Key, Column(Order = 1)]
public string ProviderKey { get; set; }
public string UserId { get; set; }
public UserLogin() { }
public UserLogin(string userId, string loginProvider, string providerKey)
{
LoginProvider = loginProvider;
ProviderKey = providerKey;
UserId = userId;
}
}
public class UserSecret : IUserSecret
{
public UserSecret()
{
}
public UserSecret(string userName, string secret)
{
UserName = userName;
Secret = secret;
}
[Key]
public string UserName { get; set; }
public string Secret { get; set; }
}
public class UserRole : IUserRole
{
[Key, Column(Order = 0)]
public string RoleId { get; set; }
[Key, Column(Order = 1)]
public string UserId { get; set; }
}
public class Role : IRole
{
public Role()
: this(String.Empty)
{
}
public Role(string roleName)
{
Id = roleName;
}
[Key]
public string Id { get; set; }
}
public class IdentityDbContext : DbContext
{
public IdentityDbContext()
: base("DefaultConnection")
{
}
public IdentityDbContext(string nameOrConnectionString)
: base(nameOrConnectionString)
{
}
protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry, IDictionary<object, object> items)
{
if (entityEntry.State == EntityState.Added)
{
User user = entityEntry.Entity as User;
if (user != null && Users.Where(u => u.UserName.ToUpper() == user.UserName.ToUpper()).Count() > 0)
{
var result = new DbEntityValidationResult(entityEntry, new List<DbValidationError>());
result.ValidationErrors.Add(new DbValidationError("User", "User name must be unique."));
return result;
}
}
return base.ValidateEntity(entityEntry, items);
}
public DbSet<User> Users { get; set; }
public DbSet<UserSecret> Secrets { get; set; }
public DbSet<UserLogin> UserLogins { get; set; }
public DbSet<Role> Roles { get; set; }
public DbSet<UserRole> UserRoles { get; set; }
}
}
更詳細的ASP.NET Identity可參考blogs.msdn.com的《Introducing ASP.NET Identity – A membership system for ASP.NET applications》[1]介紹。
認證過濾器
認證過濾器(Authentication filters)是ASP.NET MVC一個新類型的過濾器,在ASP.NET MVC管線中會優先處理認證過濾器,並且可以在每一Action方法或每一Controller或全域設置至全部Controller的認證邏輯。認證過濾器會在請求中處理使用者憑證(credentials)並提供相對應的主體(principal)。認證過濾器還可以在未經授權的請求中加入驗證挑戰(authentication challenge)至回應裡。
過濾器覆寫
現在,透過指定覆寫過濾器(override filter),你可以把覆寫過濾器用於給定的Action方法或Controller,覆寫過濾器應該指定一組不應執行的範圍(Action或Controller)的過濾器型別。這樣允許開發人員組態過濾器並套用至全域配置,然後排除特定的全域過濾器套用至特定的Action或Controller上。
Bootstrap
Visual Studio 2013更新MVC 5專案範本使用Bootstrap[2]框架重新設計_Layout及Views的版面與規劃,以提供時尚和反應迅速的外觀和感覺。
Note
Bootstrap在2013/8/19推出Version 3.0.0[3]正式版。目前Visual Studio 2013 Preview使用的是Bootstrap Version 2.3.1,兩個版本上使用上有差異性。未來Visual Studio 2013 RTM會採用Bootstrap 3.0.0版。
圖七
MVC範本預設引用Bootstrap相關CSS。
圖八
BundleConfig預設加入Bootstrap相關CSS與JavaScript組態。
圖九
_Layout頁面利用Bootstrap的重新改寫。
Tip
Bootstrap的透過設定CSS的class屬性與data-*屬性來產生效果。
圖十
MVC 5預設Layout畫面。
圖十一
利用Bootstrap的頁面擁有響應式能力 (Responsive Web)。
結語
相較於MVC的前幾個版本,此次MVC 5就MVC本身而言並無太大改變,主要是在安全性方面的提升。支架的使用與前幾版差異較大。另外,首次採用Bootstrap框架,Bootstrap可有效解決開發人員在頁面資料流安排上的弱點,Bootstrap在國外是非常火紅的技術,也有大量技術資源可以取得,例如,基於Bootstrap技術的套面資源、拖拉動態產生Bootstrap版面等。
參考資料
- https://www.asp.net/vnext/overview/latest/release-notes
- https://blogs.msdn.com/b/webdev/archive/2013/06/27/introducing-asp-net-identity-membership-system-for-asp-net-applications.aspx
- https://getbootstrap.com/2.3.2/ - Bootstrap 2.3.2文件
- https://twbs.github.io/bootstrap/ - Bootstrap 3.0.0文件
- https://kkbruce.tw - Bootstrap正體中文文件。
[1] https://blogs.msdn.com/b/webdev/archive/2013/06/27/introducing-asp-net-identity-membership-system-for-asp-net-applications.aspx
[2] https://getbootstrap.com/2.3.2/
[3] https://twbs.github.io/bootstrap/
延伸閱讀
整合社交驗證及開發Facebook App – 使用ASP.NET及 Visual Studio 2013
進擊的 ASP.NET Web API 2 巨人 – 打造支援各種裝置及平台的服務
Responsive Web 響應式設計及ASP.NET版面魔法師 – Bootstrap
Comments
Anonymous
September 25, 2013
你写的这个是关于 预览版的,现在已经推出了 rc 版,而很多技术和关于 IdentityConfig 与 IdentityModels 的架构已经完全被替换,能否对于 rc 版的 aspnet.identity 的功能进行一下详细阐述。谢谢Anonymous
December 30, 2013
关于RC版本的Identity功能我写了一篇博文,欢迎大家参考。 www.cnblogs.com/.../3497478.html