コンテンツにスキップ
ASP.NET Core でロールベースの Web API を作成する方法

ASP.NET Core でロールベースの Web API を作成する方法

もう一つの優れたハウツー ブログ投稿をご紹介します。ASP.NET Core を使用してロールベースの Web API と Swagger UI を構築し、エンドポイントを視覚化して操作する方法を説明します。

20分で読める

ASP.NET Core でロールベースの Web API を構築する場合、コード ファースト アプローチは強力で効率的な方法です。これを使用すると、データ モデルと関係をコードで定義し、対応するデータベース スキーマを自動的に生成できます。これは何をもたらすのでしょうか。開発サイクルが速くなり、柔軟性が確実に高まります。なぜでしょうか。データベース スキーマを直接変更しなくても、データ モデルをすばやく簡単に変更できるためです。デザイン ファースト アプローチとコード ファースト アプローチの詳細については、swagger.io を参照してください。

このチュートリアルでは、ASP.NET Core 6 を使用してロールベースの Web API を作成する手順について説明します。エンドポイントを視覚化して操作するために Swagger UI を使用し、データベースとして MS SQL Server を使用します。アプリケーションには、認証モジュールとイベント モジュールが含まれます。ログインしたユーザーは自分のアカウントに関連付けられたイベントを表示でき、管理者ロールを持つユーザーはイベントを作成、更新、削除できます。

始めましょう!

プロジェクトのセットアップ

まず、プロジェクトをセットアップする必要があります。そのためには、Visual Studio を開き、新しいプロジェクトを作成して、ASP.NET Core Web API を選択します。

Visual Studioを開き、新しいプロジェクトを作成して、ASP.NET Core Web APIを選択します。

アプリケーションの名前を選択し、「次へ」をクリックします。

Visual Studioでアプリケーションの名前を選択します

APIデータベースの設定

アプリケーションを初期化したら、データベースを構成する必要があります。データベースの管理に役立つように、ORMとしてEntityFrameworkCoreを使用します。このため、いくつかのパッケージをインストールする必要があります。

Visual Studio で API データベースを設定する

パッケージを正常にインストールした後、次に行うことは、DbContextを作成することです。DataContext.csファイルを作成し、DBContextクラスを継承します。 ここで、テーブルを定義します。

public class DataContext: DbContext
{
  public DataContext(DbContextOptions options): base(options)
  {
  }

  //Define our tables
}

次に、Program.csファイルを開いてdbContextを追加します。appsettings.json ファイルから取得されるdbProviderと接続文字列を指定する必要があります。dbProvider、SqlServer、MySql、またはInMemoryにすることができます。

// Add Db context

var dbProvider = builder.Configuration.GetConnectionString("Provider");

builder.Services.AddDbContext < DataContext > (options =>
  {

    if (dbProvider == "SqlServer")
    {
      options.UseSqlServer(builder.Configuration.GetConnectionString("SqlServerConnectionString"));
    }
  });

次のように、appsettings.jsonファイルにConnectionStringProviderが追加されていることを確認してください。

…
"ConnectionStrings": {
  "Provider": "SqlServer",
  "SqlServerConnectionString": "Data Source=(localdb)\\MSSQLLocalDB;Database=HRApplication2;Integrated Security=True;Connect Timeout=30; "
},
…

DbContextを構成した後、データベース モデルを生成する必要があります。この場合、User と Event という 2 つのエンティティと、それらの間の多対多の関係を確立するための 3 番目のテーブル UserEvent が必要です。これを実現するには、Models フォルダーを作成し、その中に DbModels サブフォルダーを作成して、データベース エンティティを作成することをお勧めします。

まず、Userモデルから始めましょう。各ユーザーには、ハッシュ形式で保存される一意の ID、電子メール、名、姓、パスワード、デモ用のユーザーと管理者になるロール、およびUserEventテーブルに関連付けられるUserEvents が必要です。

public class User
{
    public string UserId { get; set; }
    public string Email { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Password { get; set; }
    public string Role { get; set; }
    public IList <UserEvent> UserEvents { get; set; }
}

イベント モデルには、一意の ID、タイトル、カテゴリ、日付、およびUserEventsテーブルとの関係も必要です。

public class Event
{
    public string Id { get; set; }
    public string Title { get; set; }
    public string Category { get; set; }
    public DateTime Date { get; set; }
    public IList <UserEvent> UserEvents { get; set; }
}

ユーザーは複数のイベントに参加でき、イベントには複数のユーザーが参加できるため、これらのエンティティ間に多対多の関係を確立する必要があります。これを行うには、UserEventsという追加のテーブルを作成します。このテーブルには、User エンティティEventエンティティ間の関係を確立するUserId 列EventId列が含まれます。

public class UserEvent
{
    public string UserId { get; set; }
    public User User { get; set; }
    public string EventId { get; set; }
    public Event Event { get; set; }
}

データベース モデルを作成したら、次のステップはそれをDbContextに登録することです。これを実現するには、DataContext.csファイルに移動し、すべてのエンティティをDbSetsとして追加し、リレーションシップと主キーを宣言します。これは、OnModelCreatingメソッドをオーバーライドし、Fluent API を使用してリレーションシップとキーを構成することで実現できます。完了すると、結果は次のようになります。

public class DataContext: DbContext
{
  public DataContext(DbContextOptions options): base(options)
  {
  }
  
  public DbSet <User> Users { get; set; }
  public DbSet < Event > Events { get; set; }
  public DbSet < UserEvent > UserEvents { get; set; }

  protected override void OnModelCreating(ModelBuilder builder)
  {
    base.OnModelCreating(builder);

    builder.Entity<User>()
      .HasKey(u => new {
        u.UserId
      });

    builder.Entity<Event>()
      .HasKey(e => new {
        e.Id
      });

    builder.Entity<UserEvent>()
      .HasKey(ue => new {
        ue.UserId, ue.EventId
      });

    builder.Entity<UserEvent>()
      .HasOne(ue => ue.User)
      .WithMany(user => user.UserEvents)
      .HasForeignKey(u => u.UserId);

    builder.Entity<UserEvent>()
      .HasOne(uc => uc.Event)
      .WithMany(ev => ev.UserEvents)
      .HasForeignKey(ev => ev.EventId);
  }
}

データベース設計の準備ができたら、データベースを作成する初期移行を生成する必要があります。

パッケージ マネージャー コンソールを開き、次のコマンドを入力します。

移行の追加初期作成

Add-Migration InitialCreate in Project Manager Console

正常に実行されたら、次のようにデータベースを更新する必要があります。

データベースを更新する

次に、Microsoft SQL Management Studio で、新しく作成されたデータベースが表示されます。

AutoMapper の設定

AutoMapper は、あるモデルを別のモデルに変換するのに役立ちます。これは、入力モデルをdbModels に変換します。これを行う理由は、一方のモデルのすべてのプロパティをもう一方のモデルに含める必要がない場合があるからです。チュートリアルの後半で、これをどのように使用するかを正確に説明します。その前に、まずこれを構成する必要があります。AutoMapperの詳細な説明は、公式ドキュメントに記載されています。

まず、AutoMaper NuGet パッケージをインストールする必要があります。その後、すべてのマッピングを定義するMappingProfiles.csファイルを生成できます。整理のために、このファイルを Helpers フォルダーの下に作成することをお勧めします。

マッピングを宣言するには、MappingProfiles がProfile クラスを継承し、CreateMap<from, to>()メソッドを使用してマッピングを宣言する必要があります。モデルを逆方向 にマッピングする機能が必要な場合は、.ReverseMap()メソッドを含めることができます。

マッピングが完了したら、Program.csファイルに移動し、AutoMapperMappingProfilesに登録する必要があります。

…
var config = new MapperConfiguration(cfg =>
  {
    cfg.AddProfile(new MappingProfiles());
  });

var mapper = config.CreateMapper();

builder.Services.AddSingleton(mapper);
…

認証の設定

認証にはJWTトークンを使用します。これにより、関係者間で情報を JSON オブジェクトとして安全に転送できるようになります。JWTトークンの詳細については、こちらを参照てください。JWT トークンを使用するには、まず必要な NuGet パッケージをインストールする必要があります。Microsoft.IdentityModel.Tokens とMicrosoft.AspNetCore.Authentication.JwtBearerの両方が必要です。

次に、appsettings.json ファイルでいくつかのトークン構成を定義する必要があります。これらの構成には、Issuer、Audience、および SecretKey が含まれます。

"Jwt": {
  "Issuer": "https://localhost:7244/",
  "Audience": "https://localhost:7244/",
  "Key": "S1u*p7e_r+S2e/c4r6e7t*0K/e7y"
}

トークン構成が定義されたら、Program.csファイルで JWT サービスを構成できます。これには、必要な検証パラメータとともに使用されるスキーマの指定が含まれます。

…
builder.Services.AddAuthentication(options =>
    {
        options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
        options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
    }).AddJwtBearer(o =>
    {
        o.TokenValidationParameters = new TokenValidationParameters
    {
        ValidIssuer = builder.Configuration["Jwt:Issuer"],
            ValidAudience = builder.Configuration["Jwt:Audience"],
            IssuerSigningKey = new SymmetricSecurityKey
        (Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])),
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = false,
            ValidateIssuerSigningKey = true
    };
  });
…

app.UseAuthentication();も追加されていることを確認してください。

Swagger の設定

Swagger UIを使用してアプリケーション エンドポイントをテストするには、Program.csファイルにapp.UseSwaggerUI()を含める必要があります。

その後、JWT トークンを使用して認証されたエンドポイントをテストするのに役立つAuthResponseフィルターを生成する必要があります。これを実現するには、IOperationFilterインターフェイスを実装するAuthResponsesOperationFilterクラスを作成します。Apply メソッドには、AuthResponseフィルターを Swagger に追加するために必要なロジックを含める必要があります。

public class AuthResponsesOperationFilter: IOperationFilter
{
    public void Apply(OpenApiOperation operation, OperationFilterContext context)
    {
        var authAttributes = context.MethodInfo.DeclaringType.GetCustomAttributes(true)
            .Union(context.MethodInfo.GetCustomAttributes(true))
            .OfType<AuthorizeAttribute>();

        if (authAttributes.Any())
        {
            var securityRequirement = new OpenApiSecurityRequirement()
            {
                {
                    new OpenApiSecurityScheme
                    {
                        Reference = new OpenApiReference
                        {
                            Type = ReferenceType.SecurityScheme,
                            Id = "Bearer"
                        }
                    },
                    new List<string>()
                }
            };

            operation.Security = new List<OpenApiSecurityRequirement> {
                securityRequirement
            };

            operation.Responses.Add("401", new OpenApiResponse {
                Description = "Unauthorized"
            });
        }
    }
}

その後、Program.cs .AddSwaggerGenメソッドにオプションとしてフィルターを追加したことを確認します。

builder.Services.AddSwaggerGen(option =>
    {
        option.SwaggerDoc("v1", new OpenApiInfo {
            Title = "Northwind CRUD", Version = "v1"
        });

        option.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
        {
            In = ParameterLocation.Header,
            Description = "Please enter a valid token",
            Name = "Authorization",
            Type = SecuritySchemeType.Http,
            BearerFormat = "JWT",
            Scheme = "bearer"
        });

        option.OperationFilter < AuthResponsesOperationFilter > ();
    }
  );

「Swagger とは何か?」のより詳しい説明は公式ドキュメントで読むことができます。

エンドポイントを登録する

構成が完了したら、レジスタ エンドポイントの作成に進むことができます。最初の手順は、Models/InputModelsフォルダーの下にあるRegisterInputModel.csファイルを生成することです。

登録プロセスには、Email、FirstName、LastName、Password、ConfirmedPassword の各フィールドが必要です。これらのフィールドはすべて必須なので、[Required]属性を含めます。また、Email フィールドに[EmailAddress]属性を含めます。必要に応じて、最小長や最大長などの属性を追加できます。ただし、このデモでは、これらの属性を使用します。

public class RegisterInputModel
{
    [EmailAddress]
    public string Email { get; set; }

    [Required]
    public string FirstName { get; set; }

    [Required]
    public string LastName { get; set; }

    [Required]
    public string Password { get; set; }

    [Required]
    public string ConfirmedPassword { get; set; }
}

次に、RegisterInputModel モデルUserモデル間の双方向の変換を可能にするマッピングをMappingProfiles.csファイルに追加する必要があります。

CreateMap<RegisterInputModel, User>().ReverseMap();

関心の分離を維持するために、Services フォルダーを作成します。各モジュールには、データベースと対話するための独自のサービスがあります。まず、AuthService.csファイルを生成し、DataContextConfigurationを挿入します。

AuthService.csの最初のメソッドはGenerateJwtTokenです。これは、電子メールとロールをパラメーターとして受け取り、ユーザー情報を含む JWT トークンを返します。

public string GenerateJwtToken(string email, string role)
{
    var issuer = this.configuration["Jwt:Issuer"];
    var audience = this.configuration["Jwt:Audience"];
    var key = Encoding.ASCII.GetBytes(this.configuration["Jwt:Key"]);
    var tokenDescriptor = new SecurityTokenDescriptor
    {
        Subject = new ClaimsIdentity(new []
                {
                    new Claim("Id", Guid.NewGuid().ToString()),
                        new Claim(JwtRegisteredClaimNames.Sub, email),
                        new Claim(JwtRegisteredClaimNames.Email, email),
                        new Claim(ClaimTypes.Role, role),
                        new Claim(JwtRegisteredClaimNames.Jti,
                            Guid.NewGuid().ToString())
                }),
            Expires = DateTime.UtcNow.AddMinutes(5),
            Issuer = issuer,
            Audience = audience,
            SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha512Signature)
    };

    var tokenHandler = new JwtSecurityTokenHandler();
    var token = tokenHandler.CreateToken(tokenDescriptor);

    return tokenHandler.WriteToken(token);
}

パスワードをハッシュするには、BCrypt.Net.BCryptを使用します。まず、パッケージをインストールし、ファイルの先頭に using ステートメントとして追加する必要があります。

BC = BCrypt.Net.BCrypt を使用します。

その後、いくつかのヘルパー メソッドを作成します。1 つは指定されたメール アドレスを持つユーザーが存在するかどうかを確認し、もう 1 つはユーザーを認証し、さらに 2 つはメール アドレスと ID でユーザーを取得します。

public bool IsAuthenticated(string email, string password)
{
    var user = this.GetByEmail(email);
    return this.DoesUserExists(email) && BC.Verify(password, user.Password);
}

public bool DoesUserExists(string email)
{
    var user = this.dataContext.Users.FirstOrDefault(x => x.Email == email);
    return user != null;
}

public User GetById(string id)
{
    return this.dataContext.Users.FirstOrDefault(c => c.UserId == id);
}

public User GetByEmail(string email)
{
    return this.dataContext.Users.FirstOrDefault(c => c.Email == email);
}

登録メソッドを作成する前に、まず一意の ID を生成するメソッドを作成する必要があります。このメソッドは次のように定義できます。

public class IdGenerator
{
    public static string CreateLetterId(int length)
    {
        var random = new Random();
        const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

        return new string(Enumerable.Repeat(chars, length)
            .Select(s => s[random.Next(s.Length)]).ToArray());
    }
}

これで、Register メソッドの実装に進むことができます。このメソッド内で、一意の ID を生成し、それがすでに存在するかどうかを確認し、存在する場合は新しい ID を生成します。次に、ユーザーのパスワードをハッシュし、新しいユーザーをデータベースに追加します。

public User RegisterUser(User model)
{
    var id = IdGenerator.CreateLetterId(10);
    var existWithId = this.GetById(id);

    while (existWithId != null)
    {
        id = IdGenerator.CreateLetterId(10);
        existWithId = this.GetById(id);
    }

    model.UserId = id;
    model.Password = BC.HashPassword(model.Password);
    var userEntity = this.dataContext.Users.Add(model);
    this.dataContext.SaveChanges();

    return userEntity.Entity;
}

AuthControllerをまだ作成していない場合は、今がそのタイミングです。Controllersフォルダーに移動し、Controller クラスを継承するAuthControllerを追加します。また、Swagger に認識されるように[ApiController]属性と[Route(“[controller]”)]も追加する必要があります。

その後、マッパー、authService、およびロガー(使用する場合は)を注入し、RegisterMethod を作成する必要があります。これは、認証されていないユーザーのみがリクエスト後にアクセスできる必要があります。引数としてRegisterInputModelを受け入れ、ModelStateが有効かどうかを確認します。有効であれば、そのユーザーの jwt トークンを生成します。メソッド全体は次のようになります。

[AllowAnonymous]
[HttpPost("Register")]
public ActionResult<string> Register(RegisterInputModel userModel)
{
    try
    {
        if (ModelState.IsValid)
        {
            if (userModel.Password != userModel.ConfirmedPassword)
            {
                return BadRequest("Passwords does not match!");
            }

            if (this.authService.DoesUserExists(userModel.Email))
            {
                return BadRequest("User already exists!");
            }

            var mappedModel = this.mapper.Map<RegisterInputModel, User>(userModel);
            mappedModel.Role = "User";
            
            var user = this.authService.RegisterUser(mappedModel);
            if (user != null)
            {
                var token = this.authService.GenerateJwtToken(user.Email, mappedModel.Role);
                return Ok(token);
            }
            return BadRequest("Email or password are not correct!");
        }

        return BadRequest(ModelState);
    } catch (Exception error)
    {
        logger.LogError(error.Message);
        return StatusCode(500);
    }
}

ログイン機能

ログイン機能は、データベースでユーザーを検索する必要がある点を除いて同様です。まず、今回は Emailフィールドと Password フィールドのみを持つLoginInputModel.csを作成する必要があります。MappingProfiles.cs にも追加することを忘れないでください。そうしないと機能しません。

public class LoginInputModel
{
    [EmailAddress]
    [Required]
    public string Email { get; set; }

    [Required]
    public string Password { get; set; }
}

次に、AuthController.csで、LoginInputModelをパラメーターとして受け取り、ユーザーが認証されているかどうかを確認する Login メソッドを作成します。認証されている場合は、トークンを生成します。認証されていない場合は、エラーを返します。

[AllowAnonymous]
[HttpPost("Login")]
public ActionResult <string> Login(LoginInputModel userModel)
{
    try
    {
        if (ModelState.IsValid)
        {
            if (this.authService.IsAuthenticated(userModel.Email, userModel.Password))
            {
                var user = this.authService.GetByEmail(userModel.Email);
                var token = this.authService.GenerateJwtToken(userModel.Email, user.Role);

                return Ok(token);
            }

            return BadRequest("Email or password are not correct!");
        }

        return BadRequest(ModelState);

    } 
    catch (Exception error)
    {
        logger.LogError(error.Message);
        return StatusCode(500);
    }
}

イベントのCRUDの追加

認証が完了したら、イベントのエンドポイントを開発できます。完全な CRUD 操作を作成します。ユーザーの場合と同様に、EventService.csファイルを作成する必要があります。これには、ID によるイベントの取得、特定のユーザーのすべてのイベントの取得、新しいイベントの作成、既存のイベントの更新、イベントの削除を行うメソッドが含まれます。ファイル全体は次のようになります。

public class EventService
{
    private readonly DataContext dataContext;

    public EventService(DataContext dataContext)
    {
        this.dataContext = dataContext;
    }

    public Event[] GetAllForUser(string email)
    {
        var user = this.dataContext.Users
            .FirstOrDefault(user => user.Email == email);

        return this.dataContext.Events
            .Include(ev => ev.UserEvents)
            .Where(e => e.UserEvents.FirstOrDefault(ue => ue.UserId == user.UserId) != null)
            .ToArray();
    }

    public Event GetById(string id)
    {
        return this.dataContext.Events
            .Include(ev => ev.UserEvents)
            .FirstOrDefault(c => c.Id == id);
    }

    public Event Create(Event model)
    {
        var id = IdGenerator.CreateLetterId(6);
        var existWithId = this.GetById(id);

        while (existWithId != null)
        {
            id = IdGenerator.CreateLetterId(6);
            existWithId = this.GetById(id);
        }

        model.Id = id;
        var eventEntity = this.dataContext.Events.Add(model);
        this.dataContext.SaveChanges();

        return eventEntity.Entity;
    }

    public Event Update(Event model)
    {
        var eventEntity = this.dataContext.Events
            .Include(ev => ev.UserEvents)
            .FirstOrDefault(c => c.Id == model.Id);

        if (eventEntity != null)
        {
            eventEntity.Title = model.Title != null ? model.Title : eventEntity.Title;

            eventEntity.Date = model.Date != null ? model.Date : eventEntity.Date;

            eventEntity.Category = model.Category != null ? model.Category : eventEntity.Category;

            eventEntity.UserEvents = model.UserEvents.Count! > 0 ? model.UserEvents : eventEntity.UserEvents;

            this.dataContext.SaveChanges();
        }

        return eventEntity;
    }

    public Event Delete(string id)
    {
        var eventEntity = this.GetById(id);

        if (eventEntity != null)
        {
            this.dataContext.Events.Remove(eventEntity);
            this.dataContext.SaveChanges();
        }

        return eventEntity;
    }
}

次に、コントローラーに移動して、各リクエストのメソッドを設定します。

イベント モデルから必要なすべてのデータを格納するために使用されるEventBindingModelを作成します。

GetAllメソッドの場合は、GETリクエストを使用してユーザーのトークンを取得し、それをデコードして、そのユーザーのイベントを取得するようにしてください。

[HttpGet]
[Authorize]
public ActionResult<EventBindingModel[]> GetAll()
{
    try
    {
        var userEmail = this.authService.DecodeEmailFromToken(this.Request.Headers["Authorization"]);
        var events = this.eventService.GetAllForUser(userEmail);
        return Ok(this.mapper.Map<Event[], EventBindingModel[]> (events));

    } catch (Exception error)
    {
        logger.LogError(error.Message);
        return StatusCode(500);
    }
}

…
public string DecodeEmailFromToken(string token)
{
    var decodedToken = new JwtSecurityTokenHandler();
    var indexOfTokenValue = 7;
    var t = decodedToken.ReadJwtToken(token.Substring(indexOfTokenValue));

    return t.Payload.FirstOrDefault(x => x.Key == "email").Value.ToString();
}
…

ID による取得も、ID をパラメータとして指定したGETリクエストである必要があります。

[HttpGet("{id}")]
[Authorize]
public ActionResult<Event> GetById(string id)
{
    try
    {
        var eventEntity = this.eventService.GetById(id);

        if (eventEntity != null)
        {
            return Ok(eventEntity);
        }

        return NotFound();
    } 
    catch (Exception error)
    {
        logger.LogError(error.Message);
        return StatusCode(500);
    }
}

削除エンドポイントはDELETEリクエストとなり、引数として ID も受け取ります。

[HttpDelete("{id}")]
[Authorize(Roles = "Administrator")]
public ActionResult<Event> Delete(string id)
{
    try
    {
        var eventEntity = this.eventService.Delete(id);

        if (eventEntity != null)
        {
            return Ok(eventEntity);
        }

        return NotFound();
    } 
    catch (Exception error)
    {
        logger.LogError(error.Message);
        return StatusCode(500);
    }
}

イベント レコードの追加または更新のプロセスを簡素化するために、作成および更新操作専用のEventInputModelを作成しましょう。このモデルでは、タイトル、カテゴリ、日付、userId、eventId など、userEvents の必須プロパティのみを指定する必要があります。このモデルを使用すると、各操作でイベントモデルのすべてのプロパティを指定する必要がなくなります。

[HttpPost]
public ActionResult<Event> Create(EventInputModel model)
{
    try
    {
        if (ModelState.IsValid)
        {
            var mappedModel = this.mapper.Map < EventInputModel,
                Event > (model);
            var eventEntity = this.eventService.Create(mappedModel);

            return Ok(eventEntity);
        }

        return BadRequest(ModelState);
    } 
    catch (Exception error)
    {
        logger.LogError(error.Message);
        return StatusCode(500);
    }
}

更新はPUTリクエストとなり、EventInputModelもパラメータとして受け取ります。

[HttpPut]
public ActionResult<Event> Update(EventInputModel model)
{
    try
    {
        if (ModelState.IsValid)
        {
            var mappedModel = this.mapper.Map<EventInputModel, Event>(model);
            var eventEntity = this.eventService.Update(mappedModel);

            if (eventEntity != null)
            {
                return Ok(eventEntity);
            }

            return NotFound();
        }

        return BadRequest(ModelState);
    } 
    catch (Exception error)
    {
        logger.LogError(error.Message);
        return StatusCode(500);
    }
}

ロールベースの承認の追加

特定のアクションを特定のユーザー ロールに制限するには、ロールベースの承認を使用できます。たとえば、このシナリオでは、イベントの作成、更新、削除エンドポイントへのアクセスを管理者ロールを持つユーザーに制限します。

これを設定するには、Program.csファイルにapp.UseAuthorization();を追加する必要があります。次に、制限付きアクセスを必要とするエンドポイントごとに、[Authorize] 属性を追加して、許可されるロールを指定します。たとえば、管理者のみが削除エンドポイントにアクセスできるようにすることができます。

…
[Authorize(Roles = "Administrator")]
public ActionResult<Event> Delete(string id)
…

DbSeeder の作成

アプリケーションを実行するときに、テスト目的やその他の理由で、データベースに何らかのデータを事前に入力しておく必要があることがよくあります。ここでシーディングが役立ちます。まず、使用するデータを定義する必要があります。

これを行うには、Resources フォルダーを作成し、ユーザー用とイベント用の 2 つの JSON ファイルを追加します。これらのファイルには、データベースに入力するデータが含まれている必要があります。たとえば、ファイルは次のようになります。

[
    {
        "UserId": "USERABCDE",
        "FirstName": "Kate",
        "LastName": "Lorenz",
        "Password": "kate.lorenz",
        "Email": "klorenz@hrcorp.com",
        "Role": "Administrator"
    },
    {
        "UserId": "ASERABCDE",
        "FirstName": "Anthony",
        "LastName": "Murray",
        "Password": "anthony.murray",
        "Email": "amurray@hrcorp.com",
        "Role": "User"
    }
]

次に、Seed メソッドを含むDbSeederクラスを作成します。このメソッドは、前に定義したデータを読み取り、データベースに入力します。これを行うには、dbContextをパラメーターとして渡す必要があります。

public class DBSeeder
{
    public static void Seed(DataContext dbContext)
    {
        ArgumentNullException.ThrowIfNull(dbContext, nameof(dbContext));
        dbContext.Database.EnsureCreated();

        var executionStrategy = dbContext.Database.CreateExecutionStrategy();

        executionStrategy.Execute(
            () => {
                using(var transaction = dbContext.Database.BeginTransaction())
                {
                    try
                    {
                        // Seed Users
                        if (!dbContext.Users.Any())
                        {
                            var usersData = File.ReadAllText("./Resources/users.json");
                            var parsedUsers = JsonConvert.DeserializeObject <User[]>(usersData);
                            foreach(var user in parsedUsers)
                            {
                                user.Password = BC.HashPassword(user.Password);
                            }

                            dbContext.Users.AddRange(parsedUsers);
                            dbContext.SaveChanges();
                        }

                        // Seed Events
                        if (!dbContext.Events.Any())
                        {
                            var eventsData = File.ReadAllText("./Resources/events.json");
                            var parsedEvents = JsonConvert.DeserializeObject <Event[]>(eventsData);

                            dbContext.Events.AddRange(parsedEvents);
                            dbContext.SaveChanges();
                        }

                        transaction.Commit();
                    } 
                    catch (Exception ex)
                    {
                        transaction.Rollback();
                    }
                }
            });
    }
}

その後、Helpersフォルダーで、Seedメソッドを実行するデータベース初期化拡張機能を作成する必要があります。

public static class DBInitializerExtension
{
    public static IApplicationBuilder UseSeedDB(this IApplicationBuilder app)
    {
        ArgumentNullException.ThrowIfNull(app, nameof(app));
        using var scope = app.ApplicationServices.CreateScope();

        var services = scope.ServiceProvider;
        var context = services.GetRequiredService < DataContext > ();

        DBSeeder.Seed(context);

        return app;
    }
}

最後に、Program.csファイルを開いて、app.UseSeedDB()メソッドを追加する必要があります。これにより、アプリケーションの起動時に、データベースにデータがあるかどうかがチェックされます。データがない場合、先ほど作成した Seed メソッドによって、定義したデータが自動的に入力されます。

CORSの追加

エンドポイントでクロスオリジン リソース共有 (CORS) を有効にするには、Program.csファイルに cors サービスを追加する必要があります。この場合、どのリージョンからのアクセスも許可しますが、必要に応じて特定のドメインを指定することもできます。

builder.Services.AddCors(policyBuilder =>
    policyBuilder.AddDefaultPolicy(policy =>
        policy.WithOrigins("*")
        .AllowAnyHeader()
        .AllowAnyHeader())
);

その後、app.UseCors();メソッドを追加します。

これにより、フロントエンド アプリケーションから API にアクセスできるようになります。

クロスオリジン リソース共有 (CORS) の詳細については、こちらをご覧ください。

まとめると…

ASP .NET Core でロールベースの API を作成することは、安全でスケーラブルな Web アプリケーションを構築する上で重要な要素です。ロールベースの承認を使用すると、ユーザーに割り当てられたロールに基づいて API リソースへのアクセスを制御できます。これにより、承認されたユーザーのみが機密データにアクセスしたり、重要なアクションを実行したりできるため、アプリケーションのセキュリティと信頼性が向上します。このチュートリアルでは、シンプルなロールベースの API を 0 から 1 まで作成する方法を説明しました。デモのコード全体は GitHub で確認できます。

このチュートリアルのパート 2 では、その API をApp Builder TMに接続し、フロントエンド アプリケーションを作成する方法を説明します。

デモを予約