2013年6月29日 星期六

設定User與Roles的第一筆種子(seed)資料, 客製化User的屬性 (在MVC4上使用SimpleRoleProvider, Entity Framework 5 CodeFirst)

只有網際網路應用程式(Internet Application)SimpleRoleProvider, 所以用這份版建立一個專案

即使是隱含存在的, 但是還是顯式的將providers加到<System.Web>裡面, 這樣當我們為了要migrations在套件管理器主控台(Package Manager Console)上執行update-database命令時, 我們可以使用本機的"Roles"API
    <roleManager enabled="true" defaultProvider="SimpleRoleProvider">
      <providers>
        <clear/>
        <add name="SimpleRoleProvider" type="WebMatrix.WebData.SimpleRoleProvider, WebMatrix.WebData"/>
      </providers>
    </roleManager>
    <membership defaultProvider="SimpleMembershipProvider">
      <providers>
        <clear/>
        <add name="SimpleMembershipProvider" type="WebMatrix.WebData.SimpleMembershipProvider, WebMatrix.WebData" />
      </providers>
    </membership>

現在來客製化 User資料表, 加入Mobile 欄位到UserProfile 實體類別

    [Table("UserProfile")]
    public class UserProfile
    {
        [Key]
        [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
        public int UserId { getset; }
        public string UserName { getset; }
        [Display(Name = "電話")]
        public string Mobile { getset; }
    }

套件管理器主控台(Package Manager Console)啟用EF5 CodeFirst Migrations
PM> enable-Migrations
執行完後跑出Migrations資料夾

Migrations/Configuration.cs修改部分資料, 可執行種入我預備想要的Roles和任何User

    internal sealed class Configuration : DbMigrationsConfiguration<MvcApplication4.Models.UsersContext>
    {
        public Configuration()
        {
            AutomaticMigrationsEnabled = true;
        }
 
        protected override void Seed(MvcApplication4.Models.UsersContext context)
        {
            WebSecurity.InitializeDatabaseConnection(
                "DefaultConnection",
                "UserProfile",
                "UserId",
                "UserName", autoCreateTables: true);
            if (!Roles.RoleExists("Administrator"))
                Roles.CreateRole("Administrator");
 
            if (!WebSecurity.UserExists("ian100"))
                WebSecurity.CreateUserAndAccount(
                    "ian100""pw0000",
                    new { Mobile = "07888999" });
 
            if (!Roles.GetRolesForUser("ian100").Contains("Administrator"))
                Roles.AddUsersToRoles(new[] { "ian100" }, new[] { "Administrator" });
        }
    }


注意到WebSecurity.InitializeDatabaseConnection()發法被引用, 這方法是告訴SimpleMembership 哪個資料表我們要使用於Users, 哪個欄位是用於UserId UserName, 接著要在註冊頁面加入新增的客製化欄位

套件管理器主控台(Package Manager Console)執行
PM> update-database –verbose
(使用-verbose 可以在Package Manager Console看到生成的SQL)
執行最後可以看到正在執行 Seed 方法。
(如果沒有在Web.Confing加入providers設定, 則會在套件管理器主控台顯示錯誤 : 尚未啟用角色管理員功能。
此時Users 被新增, Roles被提供

Ian100被加入到且關聯到Administrator角色

現在執行最後一件事, 修改註冊相關頁面與類別(加入新增的欄位Mobile)
Register View (Register.cshtml)
// … 省略
<li>
    @Html.LabelFor(m => m.Mobile)
    @Html.TextBoxFor(m => m.Mobile)
</li>

Register model (AccountModel.cs)
public class RegisterModel
{
    // … 省略
    [Required]
    [DataType(DataType.PhoneNumber)]
    [Display(Name = "Mobile")]
    public string Mobile { get; set; }
}

Register Action (AccountController.cs)
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Register(RegisterModel model)
{
    if (ModelState.IsValid)
    {
        // Attempt to register the user
        try
        {
            WebSecurity.CreateUserAndAccount(
                model.UserName,
                model.Password,
                new { Mobile = model.Mobile },
                false);

            WebSecurity.Login(model.UserName, model.Password);
            return RedirectToAction("Index", "Home");
        }
        catch (MembershipCreateUserException e)
        {
            ModelState.AddModelError("", ErrorCodeToString(e.StatusCode));
        }
    }

    // If we got this far, something failed, redisplay form
    return View(model);

}

在ASP.NET MVC4使用預設的User Roles

新增一個網際網路應用程式,打開 AccountController並看看註冊action
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Register(RegisterModel model)
{
    if (ModelState.IsValid)
    {
     // 嘗試註冊使用者
        try
        {
            WebSecurity.CreateUserAndAccount(model.UserName, model.Password);
            WebSecurity.Login(model.UserName, model.Password);
            return RedirectToAction("Index""Home");
        }
        catch (MembershipCreateUserException e)
        {
            ModelState.AddModelError("", ErrorCodeToString(e.StatusCode));
            }
        }

        // 如果執行到這裡,發生某項失敗,則重新顯示表單
        return View(model);
}

這裡少了對新的使用者指派腳色設定,在 WebSecurity.CreateUserAndAccount() 後面加入
if (!Roles.RoleExists("user"))
{
    Roles.CreateRole("user");
}
Roles.AddUserToRole(model.UserName, "user");

之後可以再Action上面加入
[Authorize(Roles = "admin")]

或是在程式或是View裡面判斷
if (Roles.IsUserInRole("admin")){}

---------------------------------------------------------

當在別的Controller裡面的Action加入[Authorize(Roles = "admin")] 會出現錯誤 :
建立連接至 SQL Server 時,發生網路相關或執行個體特定的錯誤。找不到或無法存取伺服器。確認執行個名稱是否正確,以及 SQL Server 是否設定為允許遠端連線。 (provider: SQL Network Interfaces, error: 26 - 搜尋指定的伺服器/執行個體時發生錯誤)

因為使用SimpleMembership的資料庫還未初始化,可以在Controller上加入 
[InitializeSimpleMembership] 即可(預設只有AccountController有加入該屬性)

或是想要在應用程式啟動時就初始化可以在Global.asax 加入
WebSecurity.InitializeDatabaseConnection("DefaultConnection""UserProfile""UserId""UserName", autoCreateTables: true);

如果指示發生錯誤在:
// 請確定一個應用程式啟動只起始一次 ASP.NET Simple Membership
LazyInitializer.EnsureInitialized(ref _initializer, ref _isInitialized, ref _initializerLock);

頁面顯示錯誤訊息為:
"WebSecurity.InitializeDatabaseConnection" 方法只能呼叫一次。

將該檔案(Filters/InitializeSimpleMembershipAttribute.cs)內的WebSecurity.InitializeDatabaseConnection()方法移除

---------------------------------------------------------
取的目前使用者的角色(陣列)
string[] roles = Roles.GetRolesForUser()

判斷角色
bool isAdmin = Roles.IsUserInRole("admin")