[ASP.NET Core 2 Identity] APIでユーザー登録とアクセスを管理(JWT) 2. 基本メソッド編 (アカウント作成)

# 前回からのあらすじ
前回：[ASP.NET Core 2 API x Identity でユーザー登録とアクセスを管理(JWT) 1.準備編](https://qiita.com/syantien/items/e0f9de06382052f02bbf)

前回はASP.NET Core 2 API x Identityを使うための準備をしました。
今回は、Identity ライブラリーを使って実際に利用するメソッドを書いていきます。今日は一旦ユーザーを作成するところから。

# メソッド一覧

## ユーザーを管理するためのメソッド

|名前|利用用途| 
|:--|:--|
|CreateUser|ユーザーを作成（登録）|
|UpdateUser|ユーザー情報、名前などのプロフィール系データの更新|
|ChangePassword|パスワードの更新|
|EmailConfirmToken|メールアドレス確認時に利用するトーケンを生成|
|DeleteUser|ユーザー情報の削除|
|AddToRole|ロールに追加|
|RemoveFromRole|ロールを削除|
|FindUsers|ユーザーを検索|
|GetUser|ユーザーデータを取得|

## ロールを管理するためのメソッド

|名前|利用用途| 
|:--|:--|
|CreateRole|ユーザーを作成（登録）|
|DeleteRole|ユーザー情報、名前などのプロフィール系データの更新|
|InRole|ロール内のユーザー一覧を返す|


# モデルの準備

ユーザーを新しく登録するために必要なモデルを準備します。　API経由でのリクエストモデル、レスポンスモデルを用意します。

## データモデル

前章のモデルに加え、アカウント作成時間を追加しました。DBの更新も忘れずに！

```csharp:/Models/ApplicationUsers/DataModels/ApplicationUserDataModel.cs
using System;
using Microsoft.AspNetCore.Identity;

namespace SampleApi.Models.ApplicationUsers.DataModels
{
    public class ApplicationUserDataModel : IdentityUser
    {
        public DateTime Created { get; set; }
    }
}
```

## リクエストモデル

```csharp:/Models/ApplicationUsers/RequestModels/ApplicationUserRequestModel.cs
using SampleApi.Models.ApplicationUsers.DataModels;

namespace SampleApi.Models.ApplicationUsers.RequestModels
{
    public class ApplicationUserRequestModel
    {
        public ApplicationUserDataModel CreateUserRequestModelToApplicationUserDateModel(
            CreateUserRequestModel user)
        {
            return new ApplicationUserDataModel()
            {
                UserName = user.UserName,
                Email =  user.Email
            };
        }
    }

    public class CreateUserRequestModel
    {
        public string UserName { get; set; }
        public string Password { get; set; }
        public string Email { get; set; }
    }
}
```

## レスポンスモデル

```csharp:/Models/ApplicationUsers/ResponseModels/ApplicationUserResponseModel.cs
using System;
using System.Collections.Generic;
using SampleApi.Models.ApplicationUsers.DataModels;

namespace SampleApi.Models.ApplicationUsers.ResponseModels
{
    public class ApplicationUserResponseModel
    {

        /// <summary>
        /// From ApplicationUserDataModel to CreateUserResponseModel
        /// </summary>
        /// <param name="user"></param>
        /// <returns></returns>
        public CreateUserResponseModel ApplicationUserDataModelToCreateUserRequestMode(
            ApplicationUserDataModel user)
        {
            var response = new CreateUserResponseModel()
            {
                UserName = user.UserName,
                Email = user.Email,
                Created = user.Created
            };
            
            return response;
        }
    }

    
    public class CreateUserResponseModel
    {
        public string UserName { get; set; }
      
        public string Email { get; set; }
        public DateTime Created { get; set; }

        public List<CreateUserErrorResponseModel> Errors { get; set; }
    }

    public class CreateUserErrorResponseModel
    {
        public string ErrorCode { get; set; }
        public string ErrorMessage { get; set; }
    }
}
```

# Handlerの作成
上記で定義したメソッドの詳細を次のクラスでまとめます。コントローラーから呼ばれて使われる想定です。

```csharp:/Handlers/Account/AccountHandler.cs
using System;
using System.Collections.Generic;
using SampleApi.Handlers.Accounts.Interfaces;
using SampleApi.Models.ApplicationUsers.DataModels;
using SampleApi.Models.ApplicationUsers.RequestModels;
using SampleApi.Models.ApplicationUsers.ResponseModels;
using Microsoft.AspNetCore.Identity;
using System.Threading.Tasks;

namespace SampleApi.Handlers.Accounts
{
    /// <summary>
    /// Handles all account related operations
    /// </summary>
    public class AccountHandler : IAccountHandler
    {

        private readonly UserManager<ApplicationUserDataModel> _userManager;

        public AccountHandler(UserManager<ApplicationUserDataModel> userManager)
        {
            _userManager = userManager;
        }

        /// <summary>
        /// Create new user
        /// </summary>
        /// <param name="user"></param>
        /// <returns></returns>
        public async Task<CreateUserResponseModel> Create(CreateUserRequestModel user)
        {
            // Request model
            ApplicationUserRequestModel request = new ApplicationUserRequestModel();

            // Request model to data model
            ApplicationUserDataModel applicationUser = request.CreateUserRequestModelToApplicationUserDateModel(user);

            // Timestamping
            applicationUser.Created = DateTime.UtcNow;

            // Create new user
            IdentityResult newUser = await _userManager.CreateAsync(
                applicationUser,
                user.Password);

            if (newUser.Succeeded)
            {
                // Response model
                ApplicationUserResponseModel response = new ApplicationUserResponseModel();

                // Return created user
                return response.ApplicationUserDataModelToCreateUserRequestMode(applicationUser);

            }

            // Error
            CreateUserResponseModel errorResponse = new CreateUserResponseModel();

            if (newUser.Errors != null)
            {
                errorResponse.Errors = new List<CreateUserErrorResponseModel>();

                foreach (var error in newUser.Errors)
                {
                    errorResponse.Errors.Add(
                        new CreateUserErrorResponseModel()
                        {
                            ErrorCode =  error.Code,
                            ErrorMessage = error.Description
                        });
                }
            }

            return errorResponse;
        }
    }
}
```

# DI

Startup.cs にて今回新しく追加したHandlerが使えるようにDIします。また、ユーザー作成時のオプションプロパティーを設定しています。

```csharp:/Startup.cs
using SampleApi.Filters;
using SampleApi.Handlers;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Swashbuckle.AspNetCore.Swagger;
using System;
using System.IO;
using System.Reflection;
using SampleApi.Handlers.Accounts;
using SampleApi.Handlers.Accounts.Interfaces;
using SampleApi.Models.ApplicationUsers.DataModels;
using Microsoft.AspNetCore.Identity;

namespace SampleApi
{
    public class Startup
    {
        public Startup(IConfiguration configuration, IOptions<AppSettings> optionsAccessor)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            //Sql
            services.AddDbContext<JetonModelContext>(options =>
                options.UseSqlServer(
                    Configuration.GetConnectionString("DefaultConnection")));

            services.AddDbContext<ApplicationDbContext>(options =>
                options.UseSqlServer(
                    Configuration.GetConnectionString("UserDabtaseConnection")));

            services.AddIdentity<ApplicationUserDataModel, IdentityRole>()
                .AddEntityFrameworkStores<ApplicationDbContext>()
                .AddUserManager<UserManager<ApplicationUserDataModel>>()
                .AddDefaultTokenProviders();

            services.Configure<IdentityOptions>(
                options =>
                {
                    // Password settings
                    options.Password.RequireDigit = false;
                    options.Password.RequiredLength = 6;
                    options.Password.RequireNonAlphanumeric = false;
                    options.Password.RequireUppercase = false;
                    options.Password.RequireLowercase = false;
                    options.Password.RequiredUniqueChars = 3;

                    // Lockout settings
                    options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(30);
                    options.Lockout.MaxFailedAccessAttempts = 5;
                    options.Lockout.AllowedForNewUsers = true;

                    // User settings
                    options.User.RequireUniqueEmail = true;
                });

            services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
            services.AddTransient<IAccountHandler, AccountHandler>();
            
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
            
            // Register the Swagger generator, defining 1 or more Swagger documents
            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new Info
                {
                    Title = "{title}",
                    Version = "1",
                    Description = "{description}",
                    Contact = new Contact
                    {
                        Name = "{name}",
                        Email = "{email}",
                        Url = "{url}"
                    }
                });

                c.OperationFilter<AddAuthorizationHeaderParameterOperationFilter>();

                // Set the comments path for the Swagger JSON and UI.
                string xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
                string xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
                c.IncludeXmlComments(xmlPath);
            });

        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseHsts();
            }

            // Enable middleware to serve generated Swagger as a JSON endpoint.
            app.UseSwagger();

            // Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.), 
            // specifying the Swagger JSON endpoint.
            app.UseSwaggerUI(c =>
            {
                c.SwaggerEndpoint("/swagger/v1/swagger.json", "Jeton API V1");
              
                c.RoutePrefix = string.Empty;
            });

            app.UseAuthentication();
            app.UseHttpsRedirection();
            app.UseMvc();
        }
    }
}
```

# Controllerの作成

では・・やっとControllerを追加します。

```csharp:/Controllers/AccountController.cs
using System.Threading.Tasks;
using SampleApi.Handlers.Accounts.Interfaces;
using SampleApi.Models.ApplicationUsers.RequestModels;
using SampleApi.Models.ApplicationUsers.ResponseModels;
using Microsoft.AspNetCore.Mvc;

namespace SampleApi.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class AccountController : ControllerBase
    {
        private readonly IAccountHandler _accountHandler;

        public AccountController(IAccountHandler accountHandler)
        {
            _accountHandler = accountHandler;
        }
        
        [HttpPost, Route("/Account/CreateUser")]
        public async Task<ActionResult<CreateUserResponseModel>> Create([FromBody] CreateUserRequestModel user)
        {
            var response = await _accountHandler.Create(user);

            return response ;
        }
    }
}
```

# Swaggerからアカウント作成

Swaggerから新しいアカウントを作成してみましょう。

まずは正常系のリクエスト・レスポンスを試してみます。

## 正常リクエスト

![image.png](https://qiita-image-store.s3.amazonaws.com/0/241909/f861a0af-b546-e36b-aa93-f9a710b27742.png)

## 正常レスポンス
![image.png](https://qiita-image-store.s3.amazonaws.com/0/241909/d2e1b6d0-5da3-7268-ab31-4a4e19d3e1f1.png)

## DBの中身確認
DBの中身を確認すると新しいユーザーが生成されていることを確認しました。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/241909/6a31a8d9-d4a9-c409-84f8-85e366ee0ed5.png)

## 異常系リクエスト

では異常系リクエストを試してみます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/241909/f8264a38-fd4c-8073-5203-65784466bee4.png)


## 異常系レスポンス

パスワードは少なくても6文字、3文字ユニークでないといけないとのことです。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/241909/02c1f442-a142-9d3d-40fa-ca366f5b2f93.png)


# 次

アカウントが作成できたので、次はログインとトークンを発行してみましょう。

次回：[ASP.NET Core 2 API x Identity でユーザー登録とアクセスを管理(JWT) 2. 基本メソッド編 (ログイン)](https://qiita.com/syantien/items/8bf537669c7f9591538b)