티스토리 뷰

반응형

2022.03.03 - [WPF .NET] - Microsoft.Toolkit.Mvvm을 이용한 간단한 프레임워크 part4 - LayerPopup 추가

2022.03.02 - [WPF] - Microsoft.Toolkit.Mvvm을 이용한 간단한 프레임워크 part3 - Busy 화면 구현

2022.02.24 - [WPF] - Microsoft.Toolkit.Mvvm을 이용한 간단한 프레임워크 part2 - Frame Navigation

2022.02.21 - [WPF] - Microsoft.Toolkit.Mvvm을 이용한 간단한 프레임워크 part1

 

프레임워크에서 사용하는 서비스를 하나 추가하고, 사용하는 방법에 대해서 알아보겠습니다.

이 예제를 하기 위해서는 LocalDB에 Northwind database가 있어야 합니다. 이 과정은 아래 링크를 참고하시기 바랍니다.

2022.03.30 - [Visual Studio] - Visual Studio를 이용해서 Northwind database 생성하기

 

1. 서비스 추가

추가할 서비스는 데이터베이스를 관리하는 것으로, 단일 static 헬퍼 클래스로 만들어서 사용할 수도 있지만,  Dependency Injection 기능을 이용하기 위해, Interface를 이용하는 형태로 만들겠습니다.

Interface에 대한 더 자세한 사항은 여기를 참고하시기 바랍니다.

Dependency Injection에 대한 더 자세한 사항은 여기를 참고하시기 바랍니다.

 

IDatabaseService.cs

기본 인터페이스로 외부에 노출할 부분만 정리

using System.Collections.Generic;
using System.Threading.Tasks;

namespace WpfFramework.Services
{
    /// <summary>
    /// IDatabaseService
    /// </summary>
    public interface IDatabaseService
    {
        /// <summary>
        /// ConnectionString
        /// </summary>
        string ConnectionString { get; }
        /// <summary>
        /// GetDatasAsync
        /// </summary>
        /// <remarks>
        /// query를 실행해서 IList&lt;<typeparamref name="T"/>&gt;를 반환한다.
        /// </remarks>
        Task<IList<T>> GetDatasAsync<T>(string query) where T : class;
    }
}

DatabaseService.cs

IDatabaseService의 구현 추상 클래스로, 단독으로 인스턴스화 시킬 수 없고, 다른 클래스의 Base 클래스로만 사용할 수 있습니다.

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;

namespace WpfFramework.Services
{
    /// <summary>
    /// DatabaseService 
    /// </summary>
    public abstract class DatabaseService : IDatabaseService
    {
        private string _connectionString;
        /// <summary>
        /// ConnectionString
        /// </summary>
        public string ConnectionString => _connectionString;
        /// <summary>
        /// 기본 connection
        /// </summary>
        protected DbConnection Connection { get; set; }
        /// <summary>
        /// 기본 command
        /// </summary>
        protected DbCommand Command { get; set; }

        /// <summary>
        /// 생성자
        /// </summary>
        /// <param name="connectionString"></param>
        public DatabaseService(string connectionString)
        {
            _connectionString = connectionString;
        }
        /// <summary>
        /// 쿼리 실행 후 결과 반환
        /// </summary>
        public virtual async Task<IList<T>> GetDatasAsync<T>(string query) where T : class
        {
            if (Connection == null || Command == null || string.IsNullOrEmpty(query))
            {
                return null;
            }
            //컨넥션 열기
            await Connection.OpenAsync();
            //쿼리 입력
            Command.CommandText = query;
            Command.Connection = Connection;
            var returnDatas = new List<T>();
            //커맨드 실행하고 리더 반환
            using var reader = await Command.ExecuteReaderAsync();
            //리터를 이용해서 한줄 읽음
            while (await reader.ReadAsync())
            {
                var row = (IDataRecord)reader;
                //T를 이용해서 인스턴스 생성
                var model = Activator.CreateInstance(typeof(T));
                //결과 목록에 추가
                returnDatas.Add(model as T);
                //이 아래 부분은 프로퍼티 한개씩 하드코딩 하지 않고, 값을 입력하기 위해서 사용하는 부분입니다.
                //모델에서 프로퍼티 추출
                var propertys = model.GetType().GetProperties();
                //프로퍼티 중 HasErrors라는 이름의 프로퍼티 빼고 나머지 데이터 입력
                foreach (var prop in propertys.Where(p => p.Name != "HasErrors"))
                {
                    try
                    {
                        var value = row[prop.Name];
                        if (value is DBNull == false)
                        {
                            prop.SetValue(model, value);
                        }
                    }
                    catch (Exception ex)
                    {
                        Debug.WriteLine(ex.Message);
                    }
                }
            }
            //컨넥션 닫기
            await Connection.CloseAsync();
            //결과 반환
            return returnDatas;
        }
    }
}

SqlService.cs

MS Sql 전용 서비스 클래스로, DatabaseService를 상속받아 사용합니다. 이 클래스는 생성자에서 Connection 프로퍼티와 Command 프로퍼티를 MS Sql을 이용할 수 있도록 생성하는 일만 합니다.

using System.Data.SqlClient;

namespace WpfFramework.Services
{
    /// <summary>
    /// MS SQL 전용 서비스
    /// </summary>
    public class SqlService : DatabaseService
    {
        public SqlService(string connectionString)
            : base(connectionString)
        {
            //기본 컨넥션을 MS SqlConnection으로 생성합니다.
            Connection = new SqlConnection(ConnectionString);
            //기본 커맨드를 MS SqlCommand로 생성합니다.
            Command = new SqlCommand();
        }
    }
}

지금까지 인터페이스, 추상 클래스, 서비스 클래스까지 만들어 보았습니다. 

이번에는 이 클래스를 실제 사용하기 위한 등록 방법을 알아 보겠습니다.

2. 서비스 등록

생성된 서비스를 애플리케이션 전체에서 사용하기 위해서는 등록을 먼저 진행해야합니다. 

App.xaml.cs

connectionString 문자열은 Visual Studio에서 쉽게 확인하실 수 있으며, 아래와 다를 수 있습니다.

SQL Server Object Explorer -> (localdb)\MSSQLLocalDB 선택 -> Property 창에 Connection string 항목에서 찾을 수 있습니다.

 

var connectionString = @"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=Northwind;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False";

일반적으로 서비스는 여러개의 인스턴스가 필요없기 때문에, 싱글톤으로 등록을 합니다.

IDatabaseService 인터페이스와 SqlService를 연결해서, 애플리케이션 전체에서 IDatabaseService를 Injection하면, SqlService가 반환됩니다. 이 때 IoC Container에 생성된 인스턴스가 없다면, new SqlService(connectionString)을 이용해서 만들게 됩니다.

//IDatabaseService 싱글톤 등록
services.AddSingleton<IDatabaseService, SqlService>(obj => new SqlService(connectionString));

ConfigureServices의 전체 내용입니다.

        /// <summary>
        /// Configures the services for the application.
        /// </summary>
        private static IServiceProvider ConfigureServices()
        {
            var services = new ServiceCollection();

            var connectionString = @"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=Northwind;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False";
            
            //ViewModel 등록
            services.AddTransient(typeof(MainViewModel));
            services.AddTransient(typeof(HomeViewModel));
            services.AddTransient(typeof(CustomerViewModel));

            //Control 등록
            services.AddTransient(typeof(AboutControl));

            //IDatabaseService 싱글톤 등록
            services.AddSingleton<IDatabaseService, SqlService>(obj => new SqlService(connectionString));

            return services.BuildServiceProvider();
        }

3. 서비스 사용

IoC Container에 등록된 서비스를 사용하는 가장 보편적인 방법은 생성자 주입입니다.

CustomerViewModel이 생성될 때 IDatabaseService를 injection해서 뷰모델 private readonly field에 넣어주고, 사용합니다.

        private readonly IDatabaseService _dbService;
        
        public CustomerViewModel(IDatabaseService databaseService)
        {
            _dbService = databaseService;
            Init();
        }

        public override async void OnNavigated(object sender, object navigatedEventArgs)
        {
            Message = "Navigated";

            var datas = await _dbService.GetDatasAsync<Customer>("Select * from [Customers]");
            Customers = datas;
        }

그외, 사용법은 직접 호출하는 방법으로 아래와 같은 방법으로도 사용이 가능합니다.

 

        public MainWindow()
        {
            InitializeComponent();
            DataContext = App.Current.Services.GetService(typeof(MainViewModel));
        }

 

Customer 모델에 대한 이야기와 서비스를 인터페이스로 구현한 이유에 대해서는 (2/2)에서 설명하도록 하겠습니다.

 

반응형
댓글