1
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

.NET MAUI (ローカルDBとしてEF.Coreの利用、サービスの依存性注入)

Posted at

はじめに

前回、.NET MAUIのデータストアについて調査しました。

ローカルDBとしてsqlite-net-pclを利用することができましたが、EntityFramework.Coreについては未調査だったので、試してみました。

また、サービスのDI(Dependency Injection = 依存性注入)が使えることが分かったので、それについても記載しておきます。

開発環境

  • Win11
  • VS2022 Preview Version 17.3.0 Preview 2.0

環境構築&プロジェクト作成の流れはこちらの記事でどうぞ。

サンプルソース

以前作った、データ検証(Validation)のソースの疑似ログイン処理を拡張していきます。

一式GitHubに公開してます。

NuGet

Microsoft.EntityFrameworkCore.Sqlite
EF Core のSQLite実装をNuGetします。

Table

ユーザーテーブルを作成します。

User.cs

using MauiAppDemo.Pages;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MauiAppDemo.Models
{
    [Table(nameof(User))]
    public class User : ValidationPropertyModel
    {
        private int _No;
        private string _Name;
        private string _Password;

        [Display(Order = 1, Name = nameof(Messages.User_No), ResourceType = typeof(Messages))]
        [Key]
        [Required(ErrorMessageResourceName = nameof(Messages.Error_Required), ErrorMessageResourceType = typeof(Messages))]
        [Range(1, 9999, ErrorMessageResourceName = nameof(Messages.Error_Range), ErrorMessageResourceType = typeof(Messages))]
        public int No
        {
            get => _No;
            set => SetProperty(ref _No, value);
        }

        [Column(nameof(Name))]
        [Display(Order = 2, Name = nameof(Messages.User_Name), ResourceType = typeof(Messages))]
        [Required(ErrorMessageResourceName = nameof(Messages.Error_Required), ErrorMessageResourceType = typeof(Messages))]
        [MinLength(1, ErrorMessageResourceName = nameof(Messages.Error_Required), ErrorMessageResourceType = typeof(Messages))]
        public string Name
        {
            get => _Name;
            set => SetProperty(ref _Name, value);
        }

        [Column(nameof(Password))]
        [Display(Order = 3, Name = nameof(Messages.User_Password), ResourceType = typeof(Messages))]
        [Required(ErrorMessageResourceName = nameof(Messages.Error_Required), ErrorMessageResourceType = typeof(Messages))]
        [MinLength(1, ErrorMessageResourceName = nameof(Messages.Error_Required), ErrorMessageResourceType = typeof(Messages))]
        public string Password
        {
            get => _Password;
            set => SetProperty(ref _Password, value);
        }
    }
}

Database

User テーブルのみのデータベースを作成します。

MauiAppDemoDbContext.cs

using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MauiAppDemo.Models
{
    public class MauiAppDemoDbContext : DbContext
    {
        public DbSet<User> Users { get; set; }

        private string connStr;

        public MauiAppDemoDbContext()
        {
            connStr = Path.Combine(FileSystem.AppDataDirectory, "MauiAppDemo.sq3");
        }

        protected override void OnConfiguring(DbContextOptionsBuilder options)
            => options.UseSqlite($"Data Source={connStr}");
    }
}

DI(Dependency Injection) in .NET MAUI

アプリケーション起動時にDbContextを作ったら、いろんなページから利用したいところです。
そこで、.NET MAUIのサービスのDIの仕組みを利用します。

サービスを作成

DbContext取得サービスのインターフェース

IMauiAppDemoService.cs

using MauiAppDemo.Models;

namespace MauiAppDemo.Services;

public interface IMauiAppDemoService
{
    public MauiAppDemoDbContext DBContext { get; }
}

DbContext取得サービスの実装
※DBを作成、テーブルがなければ作成、初期ユーザーがいなければ追加

MauiAppDemoService.cs

using MauiAppDemo.Models;
using MauiAppDemo.Utils;

namespace MauiAppDemo.Services;

public class MauiAppDemoService : IMauiAppDemoService
{
	private MauiAppDemoDbContext ctx;

	public MauiAppDemoDbContext DBContext
	{
		get
		{
			if (ctx == null)
			{
				ctx = new MauiAppDemoDbContext();

				ctx.Database.EnsureCreated();

				using (var t = ctx.Database.BeginTransaction())
				{
					var count = ctx.Users.Count();
					if (count == 0)
					{
						ctx.Users.Add(new User() { No = 1234, Name = "Kashin777", Password = Hashs.Sha256("1234", "1234") });
						ctx.Users.Add(new User() { No = 9999, Name = "User9999", Password = Hashs.Sha256("9999", "Password") });
					}

					t.Commit();
				}
			}

			return ctx;
		}
	}
}

DIの設定

DbContext取得サービス、それを利用したいクラス(ViewModel、ContentPage)をサービスに追加します。
※ソースの★の部分

MauiProgram.cs

using Microsoft.Maui.Platform;
using MauiAppDemo.Pages;
using MauiAppDemo.Services;

namespace MauiAppDemo;

public static partial class MauiProgram
{
	public static MauiApp CreateMauiApp()
	{
		var builder = MauiApp.CreateBuilder();
		builder
			.UseMauiApp<App>()
			.ConfigureFonts(fonts =>
			{
				fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
				fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
			});

		// ★ Depedency Injection
        builder.Services
			.AddTransient<LoginPage>()
			.AddTransient<LoginPageViewModel>()
			.AddTransient<MainPage>()
			.AddTransient<MainPageViewModel>()
			.AddSingleton<IMauiAppDemoService, MauiAppDemoService>();

		// プラットフォームに応じたカスタマイズ
        Microsoft.Maui.Handlers.EntryHandler.Mapper.ModifyMapping(nameof(IEntry.Background), (handler, entry, action) => 
		{
#if ANDROID
            handler.PlatformView.BackgroundTintList = Android.Content.Res.ColorStateList.ValueOf(Colors.Gray.ToPlatform());
#endif
		});

        return builder.Build();
	}
}

Add~によって、DIされるインスタンスのライフサイクルが変わります。

// 都度生成
builder.Services.AddTransient()
// スコープ間で1回生成
builder.Services.AddScoped()
// 常に1個
builder.Services.AddSingleton()

DI適用後のログイン処理

ContentPage -> ViewModel -> DbContextサービスの流れでDIします。
コントラクタ引数を追加することで、勝手にインスタンスが割り当てられます。
※ソースの★部分

LoginPage.xaml.cs

using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Windows.Input;
using MauiAppDemo.Models;
using MauiAppDemo.Utils;
using MauiAppDemo.Services;

namespace MauiAppDemo.Pages;

/// <summary>
/// ログインページ。
/// </summary>
public partial class LoginPage : ContentPage
{
    //★ LoginPageにViewModelをDI
    public LoginPage(LoginPageViewModel viewModel)
	{
        // 言語を変更
        var culture = new CultureInfo(Preferences.Get("Language", "ja"));
        CultureInfo.CurrentCulture = culture;
        CultureInfo.CurrentUICulture = culture;

        InitializeComponent();

        //★ DIされたViewModelをBindingContextに設定
        BindingContext = viewModel;
    }
}

/// <summary>
/// ログインページのビューモデル。
/// </summary>
public class LoginPageViewModel : ValidationPropertyViewModel
{
    private StringKeyValuPair _Language = new StringKeyValuPair() { Key = Preferences.Get("Language", "ja") };
    private int? _No;
    private string _Password;

    /// <summary>
    /// 社員番号
    /// </summary>
    [Display(Order = 1, Name = nameof(Messages.Login_No), ResourceType = typeof(Messages))]
    [Required(ErrorMessageResourceName = nameof(Messages.Error_Required), ErrorMessageResourceType = typeof(Messages))]
    [Range(1, 9999, ErrorMessageResourceName = nameof(Messages.Error_Range), ErrorMessageResourceType = typeof(Messages))]
    public int? No {
        set
        {
            SetProperty(ref _No, value);
            ChangeCanExecute(LoginCommand);
        }
        get => _No;	
    }

    /// <summary>
    /// パスワード
    /// </summary>
    [Display(Order = 2, Name = nameof(Messages.Login_Password), ResourceType = typeof(Messages))]
    [Required(ErrorMessageResourceName = nameof(Messages.Error_Required), ErrorMessageResourceType = typeof(Messages))]
    [MinLength(1, ErrorMessageResourceName = nameof(Messages.Error_Required), ErrorMessageResourceType = typeof(Messages))]
    public string Password
    {
        set
        {
            SetProperty(ref _Password, value);
            ChangeCanExecute(LoginCommand);
        }
        get => _Password;
    }

    /// <summary>
    /// 言語
    /// </summary>
    public StringKeyValuPair Language {
        set { 
            SetProperty(ref _Language, value);
            ChangeCanExecute(LanguageCommand);
        }
        get => _Language;
    }

    /// <summary>
    /// 言語変更コマンド。
    /// </summary>
    public ICommand LanguageCommand { protected set; get; }

    /// <summary>
    /// ログインコマンド。
    /// </summary>
    public ICommand LoginCommand { protected set; get; }

    //★ ViewModelにDbContext取得サービスをDI
    public LoginPageViewModel(IMauiAppDemoService service)
    {
        // 言語変更コマンドの実装
        LanguageCommand = new Command(() => 
        {
            // 言語を保存
            Preferences.Set("Language", Language.Key);

            // 画面を初期化
            App.Current.MainPage = new AppShell();
        }, 
        () => { 
            return !Language.Key.Equals(CultureInfo.CurrentUICulture.TwoLetterISOLanguageName); 
        });

        // ログインコマンドの実装
        LoginCommand = new Command(async() =>
        {
            // データ検証
            if (Validate())
            {

                //★ DbContextを取得
                var ctx = service.DBContext;

                // 社員番号とパスワードが一致するか?
                var user = await ctx.FindAsync<User>(No);
                if (user != null && user.Password.Equals(Hashs.Sha256(user.No.ToString(), Password)))
                {
                    // メインページへ移動
                    await Shell.Current.GoToAsync($"///MainPage");

                    // ログインユーザを通知
                    MessagingCenter.Send<User>(user, "Login");

                    // 初期化
                    No = null;
                    Password = null;
                    ClearErrors();
                }
                // 一致しない場合
                else
                {
                    AddModelError(Messages.Error_Comman_Login);
                }
            }

            ChangeCanExecute(LoginCommand);
        },
        () =>
        {
            // 画面にエラーが出ている場合は無効
            return !HasErrors;
        });

        // コマンドを登録
        AddCommands(LanguageCommand, LoginCommand);
    }
}

ご参考

1
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?