ASP.NET Identity ベースのフォーム認証を実装した ASP.NET MVC5 アプリケーションで、管理者がユーザー情報の追加、変更、削除を行う機能を実装する例を書きます。

CodeZine の記事「ASP.NET Identity でユーザーを管理する」に、ASP.NET Web Forms アプリ用のユーザー管理画面を作る手順が載っていますが、その MVC 版です。
ベースとなるのは、Visual Studio 2015 Community で MVC のテンプレートを利用し、認証に「個別のユーザーアカウント」を指定して自動生成させたプロジェクトです。設定は以下の画像を見てください。

上の設定で自動生成されたプロジェクトは ASP.NET Identity ベースのフォーム認証を使用する ASP.NET MVC5 アプリケーションの基本的機能を持ちます。
プロジェクトの作成後、Visual Studio で[デバッグの開始(S)]または[デバッグなしで開始(H)]で実行すると、Web サーバーとして IIS Express が立ち上がって web アプリが実行され、テンプレートに含まれている Home/Index がブラウザ(デフォルトで IE)に表示されます。
そこから[Register]をクリックしてユーザー登録を行うと、初回に EF Code First と LocalDB の機能を利用してユーザー情報のストア(.mdf ファイル)が App_Data フォルダに生成され、その後は登録した Email とパスワードでログイン可能になります。
その状態から、Web アプリケーションに管理者がユーザー情報の追加、変更、削除を行う機能を実装します。
ユーザー情報の追加、変更、削除を行うのに用いるクラスは以下の通りです。いずれもテンプレートから自動生成されたコードに完全な形で含まれているので、それらをそのまま利用します。
(1) Models/IdentityModels.cs
(2) App_Start/IdentityConfig.cs
また、App_Start/Startup.Auth.cs の Startup クラスで、ApplicationUserManager のインスタンスを OwinContext へ登録するコードも自動生成されます。
なので、Controller では HttpContext.GetOwinContext() で OwinContext を取得し、それから GetUserManager<ApplicationUserManager>() メソッドで ApplicationUserManager オブジェクトを取得できるようになっています。
それらを利用してユーザー情報の追加、変更、削除を行う Controller の実装例を以下に示します。説明はコード内のコメントに書きましたので、それを見てください。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
// 上は自動生成されたもの。それに以下の名前空間を追加
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.Owin;
using Mcv5App2.Models;
using System.Threading.Tasks;
using System.Net;
using System.ComponentModel.DataAnnotations;
namespace Mcv5App2.Controllers
{
// Edit 用に AccountViewModels.cs の既存の RegisterViewModel
// を流用しようとしたが、パスワードを入力しない場合は検証に
// 引っかかっるので、以下の EditViewModel を定義して利用。
// (Controller 内に定義したのは単に分けるのが面倒だから)
public class EditViewModel
{
[Required]
[EmailAddress]
[Display(Name = "Email")]
public string Email { get; set; }
// PasswordValidator を使えばここでの検証は不要
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[System.ComponentModel.DataAnnotations.Compare(
"Password",
ErrorMessage =
"Password と Confirm Password が一致しません")]
public string ConfirmPassword { get; set; }
}
// ここから Controller のコード
public class UsersController : Controller
{
private ApplicationUserManager _userManager;
public ApplicationUserManager UserManager
{
get
{
return _userManager ??
HttpContext.GetOwinContext().
GetUserManager<ApplicationUserManager>();
}
private set
{
_userManager = value;
}
}
// GET: Users(登録済みユーザーの一覧)
// Model は ApplicationUser
public ActionResult Index()
{
var users = UserManager.Users.
OrderBy(user => user.UserName);
return View(users);
}
// GET: Users/Details/Id(指定 Id のユーザー詳細)
// Model は ApplicationUser
public async Task<ActionResult> Details(string id)
{
if (id == null)
{
return new HttpStatusCodeResult(
HttpStatusCode.BadRequest);
}
var target = await UserManager.FindByIdAsync(id);
if (target == null)
{
return HttpNotFound();
}
return View(target);
}
// GET: Users/Create(新規ユーザー作成・登録)
// Model は AccountViewModels.cs の RegisterViewModel
public ActionResult Create()
{
return View();
}
// POST: Users/Create(新規ユーザー作成・登録)
// Model は AccountViewModels.cs の RegisterViewModel
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult>
Create(RegisterViewModel model)
{
// PasswordValidator による判定の結果は ModelState
// には反映されないので注意(下記 result で判定)
if (ModelState.IsValid)
{
// ユーザー入力のメールアドレスを UserName, Email
// プロパティに設定して ApplicationUser を生成
var user = new ApplicationUser {
UserName = model.Email,
Email = model.Email };
// 上の ApplicationUser とユーザー入力のパスワ
// ードで新規ユーザーを作成・登録
var result = await UserManager.
CreateAsync(user, model.Password);
// ユーザー作成・登録の成否を result.Succeeded で
// 判定。PasswordValidator の判定結果も result に
// 反映される
if (result.Succeeded)
{
// 登録に成功したら Users/Index にリダイレクト
return RedirectToAction("Index", "Users");
}
// result.Succeeded が false の場合 ModelSate にエ
// ラー情報を追加しないとエラーメッセージが出ない。
// AccountController と同様に AddErrors メソッドを
// 定義して利用(一番下に定義あり)
AddErrors(result);
}
// ユーザー登録に失敗した場合、登録画面を再描画
return View(model);
}
// GET: Users/Edit/Id(指定 Id のユーザー情報の更新)
// ここで更新できるのは UserName, Email, パスワード
// のみ。
// Model は上に定義した EditViewModel
public async Task<ActionResult> Edit(string id)
{
if (id == null)
{
return new HttpStatusCodeResult(
HttpStatusCode.BadRequest);
}
var target = await UserManager.FindByIdAsync(id);
if (target == null)
{
return HttpNotFound();
}
EditViewModel model =
new EditViewModel() { Email = target.Email };
return View(model);
}
// POST: Users/Edit/Id(指定 Id のユーザー情報の更新)
// ここで更新できるのは UserName, Email, パスワード
// のみ。
// UserName をソルトに使っていてパスワードだけもしくは
// UserName だけを更新するのは NG かと思っていたが問題
// なかった。(実際どのように対処しているかは不明)
// Model は上に定義した EditViewModel
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult>
Edit(string id, EditViewModel model)
{
if (id == null)
{
return new HttpStatusCodeResult(
HttpStatusCode.BadRequest);
}
if (ModelState.IsValid)
{
var target = await UserManager.FindByIdAsync(id);
target.Email = model.Email;
target.UserName = model.Email;
// ユーザーが新パスワードを入力した場合はパス
// ワードも更新する
if (!string.IsNullOrEmpty(model.Password))
{
// PasswordValidator による検証
var resultPassword =
await UserManager.PasswordValidator.
ValidateAsync(model.Password);
if (resultPassword.Succeeded)
{
// 検証 OK の場合、入力パスワードを hash
var hashedPassword =
UserManager.PasswordHasher.
HashPassword(model.Password);
target.PasswordHash = hashedPassword;
}
else
{
// 検証 NG の場合 ModelSate にエラー情報を
// 追加して編集画面を再描画
AddErrors(resultPassword);
return View(model);
}
}
var resultUpdate =
await UserManager.UpdateAsync(target);
if (resultUpdate.Succeeded)
{
// 更新に成功したら Users/Index にリダイレクト
return RedirectToAction("Index", "Users");
}
AddErrors(resultUpdate);
}
// 更新に失敗した場合、編集画面を再描画
return View(model);
}
// GET: Users/Delete/Id(指定 Id のユーザーを削除)
// 階層更新が行われているようで、ロールがアサインされて
// いるユーザーも削除可能。
// Model は ApplicationUser
public async Task<ActionResult> Delete(string id)
{
if (id == null)
{
return new HttpStatusCodeResult(
HttpStatusCode.BadRequest);
}
var target = await UserManager.FindByIdAsync(id);
if (target == null)
{
return HttpNotFound();
}
return View(target);
}
// POST: Users/Delete/Id(指定 Id のユーザーを削除)
// Model は ApplicationUser
// 上の Delete(string id) と同シグネチャのメソッド
// は定義できないので、メソッド名を変えて、下のよう
// に ActionName("Delete") を設定する
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<ActionResult>
DeleteConfirmed(string id)
{
if (id == null)
{
return new HttpStatusCodeResult(
HttpStatusCode.BadRequest);
}
var target = await UserManager.FindByIdAsync(id);
if (target == null)
{
return HttpNotFound();
}
// ロールがアサインされているユーザーも以下の一行で
// 削除可能。内部で階層更新が行われているらしい。
var result = await UserManager.DeleteAsync(target);
if (result.Succeeded)
{
// 削除に成功したら Users/Index にリダイレクト
return RedirectToAction("Index", "Users");
}
AddErrors(result);
return View(target);
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (_userManager != null)
{
_userManager.Dispose();
_userManager = null;
}
}
base.Dispose(disposing);
}
// ModelSate にエラー情報を追加するためのヘルパメソッド
private void AddErrors(IdentityResult result)
{
foreach (var error in result.Errors)
{
ModelState.AddModelError("", error);
}
}
}
}
上のコードは MSDN ライブラリに書いてある非同期版のメソッドを使っていますが、同期版も拡張メソッドとして定義されているようです。ケースバイケースでどちらが適切かを考えて使い分けた方が良いかもしれません。
View はコントローラをベースにスキャフォールディング機能を利用して自動生成できます。Visual Studio でアクションメソッドのコードを右クリックして[ビューを追加(D)...]を選ぶと、以下の画像のダイアログが表示されます。

ここで Template と Model class を設定して[Add]ボタンをクリックすれば View は自動生成されます。
Data context class は必ず空白にしてください。余計な設定をすると余計なコードが自動生成されて "Multiple object sets per type are not supported." というエラーになると思います。