728x90
반응형
728x170
[TestServer 프로젝트]
▶ Properties/launchSettings.json
{
"$schema" : "https://json.schemastore.org/launchsettings.json",
"iisSettings":
{
"windowsAuthentication" : false,
"anonymousAuthentication" : true,
"iisExpress" :
{
"applicationUrl" : "http://localhost:46375",
"sslPort" : 44369
}
},
"profiles":
{
"PartServer" :
{
"commandName" : "Project",
"dotnetRunMessages" : true,
"launchBrowser" : true,
"launchUrl" : "swagger",
"applicationUrl" : "https://localhost:7210;http://localhost:5210",
"environmentVariables" :
{
"ASPNETCORE_ENVIRONMENT" : "Development"
}
},
"IIS Express" :
{
"commandName" : "IISExpress",
"launchBrowser" : true,
"launchUrl" : "swagger",
"environmentVariables" :
{
"ASPNETCORE_ENVIRONMENT" : "Development"
}
}
}
}
728x90
▶ Models/Part.cs
namespace TestServer.Models
{
/// <summary>
/// 부품
/// </summary>
public class Part
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Property
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 부품 ID - PartID
/// <summary>
/// 부품 ID
/// </summary>
public string PartID { get; set; }
#endregion
#region 부품명 - PartName
/// <summary>
/// 부품명
/// </summary>
public string PartName { get; set; }
#endregion
#region 공급자 리스트 - SupplierList
/// <summary>
/// 공급자 리스트
/// </summary>
public List<string> SupplierList { get; set; }
#endregion
#region 부품 이용 가능일 - PartAvailableDate
/// <summary>
/// 부품 이용 가능일
/// </summary>
public DateTime PartAvailableDate { get; set; }
#endregion
#region 부품 타입 - PartType
/// <summary>
/// 부품 타입
/// </summary>
public string PartType { get; set; }
#endregion
}
}
300x250
▶ Models/PartFactory.cs
namespace TestServer.Models
{
/// <summary>
/// 부품 팩토리
/// </summary>
public static class PartFactory
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Field
////////////////////////////////////////////////////////////////////////////////////////// Static
//////////////////////////////////////////////////////////////////////////////// Public
#region Field
/// <summary>
/// 부품 딕셔너리
/// </summary>
public static Dictionary<string, Tuple<DateTime, List<Part>>> PartDictionary = new Dictionary<string, Tuple<DateTime, List<Part>>>();
#endregion
//////////////////////////////////////////////////////////////////////////////// Private
#region Field
/// <summary>
/// 잠금자
/// </summary>
private static object _locker = new object();
/// <summary>
/// 난수 발생기
/// </summary>
private static readonly Random _random = new Random();
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Property
////////////////////////////////////////////////////////////////////////////////////////// Static
//////////////////////////////////////////////////////////////////////////////// Private
#region 디폴트 부품 열거 가능형 - DefaultPartEnumerable
/// <summary>
/// 디폴트 부품 열거 가능형
/// </summary>
private static IEnumerable<Part> DefaultPartEnumerable
{
get
{
yield return new Part
{
PartID = "0545685192",
PartName = "Large motherboard",
SupplierList = new List<string> { "A. Datum Corporation", "Allure Bays Corp", "Awesome Computers" },
PartAvailableDate = new DateTime(2019, 10, 1),
PartType = "Circuit Board",
};
yield return new Part
{
PartID = "0553801473",
PartName = "RISC processor",
SupplierList = new List<string> { "Allure Bays Corp", "Contoso Ltd", "Parnell Aerospace" },
PartAvailableDate = new DateTime(2021, 07, 12),
PartType = "CPU",
};
yield return new Part
{
PartID = "0544272994",
PartName = "CISC processor",
SupplierList = new List<string> { "Fabrikam, Inc", "A. Datum Corporation", "Parnell Aerospace" },
PartAvailableDate = new DateTime(2020, 9, 4),
PartType = "CPU",
};
yield return new Part
{
PartID = "141971189X",
PartName = "High resolution card",
SupplierList = new List<string> { "Awesome Computers" },
PartAvailableDate = new DateTime(2019, 11, 10),
PartType = "Graphics Card",
};
yield return new Part
{
PartID = "1256324778",
PartName = "240V/110V switchable",
SupplierList = new List<string> { "Reskit" },
PartAvailableDate = new DateTime(2021, 10, 21),
PartType = "PSU",
};
}
}
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Method
////////////////////////////////////////////////////////////////////////////////////////// Static
//////////////////////////////////////////////////////////////////////////////// Public
#region 초기화하기 - Initialize(authorizationToken)
/// <summary>
/// 초기화하기
/// </summary>
/// <param name="authorizationToken">권한 토큰</param>
public static void Initialize(string authorizationToken)
{
lock(_locker)
{
PartDictionary.Add(authorizationToken, Tuple.Create(DateTime.UtcNow.AddHours(1), DefaultPartEnumerable.ToList()));
}
}
#endregion
#region 오래된 데이터 지우기 - ClearStaleData()
/// <summary>
/// 오래된 데이터 지우기
/// </summary>
public static void ClearStaleData()
{
lock(_locker)
{
List<string> keyList = PartDictionary.Keys.ToList();
foreach(string key in keyList)
{
if(PartDictionary.TryGetValue(key, out Tuple<DateTime, List<Part>> result) && result.Item1 < DateTime.UtcNow)
{
PartDictionary.Remove(key);
}
}
}
}
#endregion
#region 부품 ID 생성하기 - CreatePartID()
/// <summary>
/// 부품 ID 생성하기
/// </summary>
/// <returns>부품 ID</returns>
public static string CreatePartID()
{
char[] characterArray = new char[10];
for(int i = 0; i < 10; i++)
{
characterArray[i] = (char)('0' + _random.Next(0, 9));
}
return new string(characterArray);
}
#endregion
}
}
반응형
▶ BaseController.cs
using System.Net;
using Microsoft.AspNetCore.Mvc;
using TestServer.Models;
namespace TestServer.Controllers;
/// <summary>
/// 베이스 컨트롤러
/// </summary>
[Route("api/[controller]")]
[ApiController]
public class BaseController : ControllerBase
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Property
////////////////////////////////////////////////////////////////////////////////////////// Protected
#region 사용자 부품 리스트 - UserPartList
/// <summary>
/// 사용자 부품 리스트
/// </summary>
protected List<Part> UserPartList
{
get
{
if(string.IsNullOrWhiteSpace(AuthorizationToken))
{
return null;
}
if(!PartFactory.PartDictionary.ContainsKey(AuthorizationToken))
{
return null;
}
Tuple<DateTime, List<Part>> tuple = PartFactory.PartDictionary[AuthorizationToken];
return tuple.Item2;
}
}
#endregion
#region 권한 토큰 - AuthorizationToken
/// <summary>
/// 권한 토큰
/// </summary>
protected string AuthorizationToken
{
get
{
string authorizationToken = string.Empty;
HttpContext context = HttpContext;
if(context != null)
{
authorizationToken = context.Request.Headers["Authorization"].ToString();
}
return authorizationToken;
}
}
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Method
////////////////////////////////////////////////////////////////////////////////////////// Protected
#region 권한 체크하기 - CheckAuthorization()
/// <summary>
/// 권한 체크하기
/// </summary>
/// <returns>권한 체크 결과</returns>
protected bool CheckAuthorization()
{
PartFactory.ClearStaleData();
try
{
HttpContext context = HttpContext;
if(context != null)
{
if(string.IsNullOrWhiteSpace(AuthorizationToken))
{
context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return false;
}
}
else
{
return false;
}
if(!PartFactory.PartDictionary.ContainsKey(AuthorizationToken))
{
return false;
}
return true;
}
catch
{
}
return false;
}
#endregion
}
▶ LoginController.cs
using Microsoft.AspNetCore.Mvc;
using TestServer.Models;
namespace TestServer.Controllers;
/// <summary>
/// 로그인 컨트롤러
/// </summary>
[Route("api/[controller]")]
[ApiController]
public class LoginController : BaseController
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Method
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 구하기 - Get()
/// <summary>
/// 구하기
/// </summary>
/// <returns>액션 결과</returns>
[HttpGet]
public ActionResult Get()
{
try
{
string authorizationToken = Guid.NewGuid().ToString();
PartFactory.Initialize(authorizationToken);
return new JsonResult(authorizationToken);
}
catch(Exception exception)
{
return new JsonResult(exception.Message);
}
}
#endregion
}
▶ PartController.cs
using System.Net;
using System.Text.Json;
using Microsoft.AspNetCore.Mvc;
using TestServer.Models;
namespace TestServer.Controllers;
/// <summary>
/// 부품 컨트롤러
/// </summary>
[Route("api/[controller]")]
[ApiController]
public class PartController : BaseController
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Method
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 조회하기 - Get()
/// <summary>
/// 조회하기
/// </summary>
/// <returns>액션 결과</returns>
[HttpGet]
public ActionResult Get()
{
bool authorized = CheckAuthorization();
if(!authorized)
{
return Unauthorized();
}
Console.WriteLine("GET /api/part");
return new JsonResult(UserPartList);
}
#endregion
#region 조회하기 - Get(partID)
/// <summary>
/// 조회하기
/// </summary>
/// <param name="partID">부품 ID</param>
/// <returns>액션 결과</returns>
[HttpGet("{partid}")]
public ActionResult Get(string partID)
{
bool authorized = CheckAuthorization();
if(!authorized)
{
return Unauthorized();
}
if(string.IsNullOrEmpty(partID))
{
return BadRequest();
}
partID = partID.ToUpperInvariant();
Console.WriteLine($"GET /api/part/{partID}");
List<Part> userPartList = UserPartList;
Part part = userPartList.SingleOrDefault(x => x.PartID == partID);
if(part == null)
{
return NotFound();
}
else
{
return Ok(part);
}
}
#endregion
#region 추가하기 - Post(part)
/// <summary>
/// 추가하기
/// </summary>
/// <param name="part">부품</param>
/// <returns>액션 결과</returns>
[HttpPost]
public ActionResult Post([FromBody] Part part)
{
try
{
bool authorized = CheckAuthorization();
if(!authorized)
{
return Unauthorized();
}
if(!string.IsNullOrWhiteSpace(part.PartID))
{
return BadRequest();
}
Console.WriteLine($"POST /api/part");
Console.WriteLine(JsonSerializer.Serialize(part));
part.PartID = PartFactory.CreatePartID();
if(!ModelState.IsValid)
{
return BadRequest();
}
List<Part> userPartList = UserPartList;
if(userPartList.Any(x => x.PartID == part.PartID))
{
return this.Conflict();
}
userPartList.Add(part);
return this.Ok(part);
}
catch(Exception)
{
return this.Problem("Internal server error");
}
}
#endregion
#region 수정하기 - Put(partID, part)
/// <summary>
/// 수정하기
/// </summary>
/// <param name="partID">부품 ID</param>
/// <param name="part">부품</param>
/// <returns>응답 메시지</returns>
[HttpPut("{partid}")]
public HttpResponseMessage Put(string partID, [FromBody] Part part)
{
try
{
bool authorized = CheckAuthorization();
if(!authorized)
{
return new HttpResponseMessage(HttpStatusCode.Unauthorized);
}
if (!ModelState.IsValid)
{
return new HttpResponseMessage(HttpStatusCode.BadRequest);
}
if(string.IsNullOrEmpty(part.PartID))
{
return new HttpResponseMessage(HttpStatusCode.BadRequest);
}
Console.WriteLine($"PUT /api/part/{partID}");
Console.WriteLine(JsonSerializer.Serialize(part));
List<Part> userPartList = UserPartList;
Part existingPart = userPartList.SingleOrDefault(x => x.PartID == partID);
if(existingPart != null)
{
existingPart.SupplierList = part.SupplierList;
existingPart.PartType = part.PartType;
existingPart.PartAvailableDate = part.PartAvailableDate;
existingPart.PartName = part.PartName;
}
return new HttpResponseMessage(HttpStatusCode.OK);
}
catch(Exception)
{
return new HttpResponseMessage(HttpStatusCode.InternalServerError);
}
}
#endregion
#region 삭제하기 - Delete(partID)
/// <summary>
/// 삭제하기
/// </summary>
/// <param name="partID">부품 ID</param>
/// <returns>응답 메시지</returns>
[HttpDelete]
[Route("{partid}")]
public HttpResponseMessage Delete(string partID)
{
try
{
bool authorized = CheckAuthorization();
if(!authorized)
{
return new HttpResponseMessage(HttpStatusCode.Unauthorized);
}
List<Part> userPartList = UserPartList;
Part existingPart = userPartList.SingleOrDefault(x => x.PartID == partID);
if(existingPart == null)
{
return new HttpResponseMessage(HttpStatusCode.NotFound);
}
Console.WriteLine($"POST /api/part/{partID}");
userPartList.RemoveAll(x => x.PartID == partID);
return new HttpResponseMessage(HttpStatusCode.OK);
}
catch(Exception)
{
return new HttpResponseMessage(HttpStatusCode.InternalServerError);
}
}
#endregion
}
▶ Program.cs
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
builder.WebHost.UseUrls("https://*:7210", "http://*:5210"); // 외부에서 접속을 가능하게 해준다.
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
WebApplication application = builder.Build();
application.UseSwagger();
application.UseSwaggerUI();
//application.UseHttpsRedirection(); // http만 접속시 주석 처리한다.
application.UseAuthorization();
application.MapControllers();
application.Run();
[TestClient 프로젝트]
▶ Platforms/Android/Resources/xml/network_security_config.xml
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">125.33.218.112</domain>
<domain includeSubdomains="true">asuscomm.com</domain>
</domain-config>
</network-security-config>
※ 상기 파일을 추가하는 것은 테스트를 위해 http 접속을 허용하기 위한 것이다.
▶ Platforms/Android/AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:allowBackup="true"
android:icon="@mipmap/appicon"
android:roundIcon="@mipmap/appicon_round"
android:supportsRtl="true"
android:networkSecurityConfig="@xml/network_security_config" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
</manifest>
※ application 태그의 android:networkSecurityConfig 특성을 설정하는 것은 테스트를 위해 http 접속을 허용하기 위한 것이다.
▶ DATA/Part.cs
using System.ComponentModel;
using System.Text;
namespace TestClient;
/// <summary>
/// 부품
/// </summary>
[Serializable]
public class Part : INotifyPropertyChanged
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Event
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 속성 변경시 - PropertyChanged
/// <summary>
/// 속성 변경시
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Field
////////////////////////////////////////////////////////////////////////////////////////// Private
#region Field
/// <summary>
/// 부품 ID
/// </summary>
private string partID;
/// <summary>
/// 부품명
/// </summary>
private string partName;
/// <summary>
/// 부품 타입
/// </summary>
private string partType;
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Property
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 부품 ID - PartID
/// <summary>
/// 부품 ID
/// </summary>
public string PartID
{
get => this.partID;
set
{
if(this.partID == value)
{
return;
}
this.partID = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(PartID)));
}
}
#endregion
#region 부품명 - PartName
/// <summary>
/// 부품명
/// </summary>
public string PartName
{
get => this.partName;
set
{
if(this.partName == value)
{
return;
}
this.partName = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(PartName)));
}
}
#endregion
#region 부품 타입 - PartType
/// <summary>
/// 부품 타입
/// </summary>
public string PartType
{
get => this.partType;
set
{
if(this.partType == value)
{
return;
}
this.partType = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(PartType)));
}
}
#endregion
#region 공급자 리스트 - SupplierList
/// <summary>
/// 공급자 리스트
/// </summary>
public List<string> SupplierList { get; set; }
#endregion
#region 공급자 리스트 2 - SupplierList2
/// <summary>
/// 공급자 리스트 2
/// </summary>
public string SupplierList2
{
get
{
StringBuilder stringBuilder = new StringBuilder();
foreach(string supplier in SupplierList)
{
stringBuilder.Append($"{supplier}, ");
}
return stringBuilder.ToString().TrimEnd(',');
}
}
#endregion
#region 부품 이용 가능일 - PartAvailableDate
/// <summary>
/// 부품 이용 가능일
/// </summary>
public DateTime PartAvailableDate { get; set; }
#endregion
}
▶ DATA/PartManager.cs
using System.Net.Http.Json;
using Newtonsoft.Json;
namespace TestClient
{
/// <summary>
/// 부품 관리자
/// </summary>
public static class PartManager
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Field
////////////////////////////////////////////////////////////////////////////////////////// Static
//////////////////////////////////////////////////////////////////////////////// Private
#region Field
/// <summary>
/// HTTP 클라이언트
/// </summary>
private static HttpClient _client;
/// <summary>
/// 기본 주소
/// </summary>
private static readonly string _baseAddress = "http://icodebroker.asuscomm.com:5210";
/// <summary>
/// URL
/// </summary>
private static readonly string _url = $"{_baseAddress}/api/";
/// <summary>
/// 권한 키
/// </summary>
private static string _authorizationKey;
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Method
////////////////////////////////////////////////////////////////////////////////////////// Static
//////////////////////////////////////////////////////////////////////////////// Public
#region 부품 열거 가능형 구하기 (비동기) - GetPartEnumerableAsync()
/// <summary>
/// 부품 열거 가능형 구하기 (비동기)
/// </summary>
/// <returns>부품 열거 가능형 태스크</returns>
public static async Task<IEnumerable<Part>> GetPartEnumerableAsync()
{
if(Connectivity.Current.NetworkAccess != NetworkAccess.Internet)
{
return new List<Part>();
}
HttpClient client = await GetClient();
string result = await client.GetStringAsync($"{_url}part");
return JsonConvert.DeserializeObject<List<Part>>(result);
}
#endregion
#region 추가하기 (비동기) - AddAsync(partName, partType, supplier)
/// <summary>
/// 추가하기 (비동기)
/// </summary>
/// <param name="partName">부품명</param>
/// <param name="partType">부품 타입</param>
/// <param name="supplier">공급자</param>
/// <returns>부품 태스크</returns>
public static async Task<Part> AddAsync(string partName, string partType, string supplier)
{
if(Connectivity.Current.NetworkAccess != NetworkAccess.Internet)
{
return new Part();
}
Part part = new Part()
{
PartName = partName,
SupplierList = new List<string>(new[] { supplier }),
PartID = string.Empty,
PartType = partType,
PartAvailableDate = DateTime.Now.Date
};
HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Post, $"{_url}part");
requestMessage.Content = JsonContent.Create<Part>(part);
HttpResponseMessage responseMessage = await _client.SendAsync(requestMessage);
responseMessage.EnsureSuccessStatusCode();
string json = await responseMessage.Content.ReadAsStringAsync();
Part partInserted = JsonConvert.DeserializeObject<Part>(json);
return partInserted;
}
#endregion
#region 수정하기 비동기) - UpdateAsync(part)
/// <summary>
/// 수정하기 비동기)
/// </summary>
/// <param name="part">부품</param>
/// <returns>태스크</returns>
public static async Task UpdateAsync(Part part)
{
if(Connectivity.Current.NetworkAccess != NetworkAccess.Internet)
{
return;
}
HttpRequestMessage requestMessage = new(HttpMethod.Put, $"{_url}part/{part.PartID}");
requestMessage.Content = JsonContent.Create<Part>(part);
HttpClient client = await GetClient();
HttpResponseMessage responseMessage = await client.SendAsync(requestMessage);
responseMessage.EnsureSuccessStatusCode();
}
#endregion
#region 삭제하기 (비동기) - DeleteAsync(partID)
/// <summary>
/// 삭제하기 (비동기)
/// </summary>
/// <param name="partID">부품 ID</param>
/// <returns>태스크</returns>
public static async Task DeleteAsync(string partID)
{
if(Connectivity.Current.NetworkAccess != NetworkAccess.Internet)
{
return;
}
HttpRequestMessage msg = new(HttpMethod.Delete, $"{_url}part/{partID}");
HttpClient client = await GetClient();
var response = await client.SendAsync(msg);
response.EnsureSuccessStatusCode();
}
#endregion
//////////////////////////////////////////////////////////////////////////////// Private
#region HTTP 클라이언트 구하기 - GetClient()
/// <summary>
/// HTTP 클라이언트 구하기
/// </summary>
/// <returns>HTTP 클라이언트</returns>
private static async Task<HttpClient> GetClient()
{
if(_client != null)
{
return _client;
}
_client = new HttpClient();
if(string.IsNullOrEmpty(_authorizationKey))
{
_authorizationKey = await _client.GetStringAsync($"{_url}login");
_authorizationKey = JsonConvert.DeserializeObject<string>(_authorizationKey);
}
_client.DefaultRequestHeaders.Add("Authorization", _authorizationKey );
_client.DefaultRequestHeaders.Add("Accept" , "application/json");
return _client;
}
#endregion
}
}
▶ VIEWMODEL/PartViewModel.cs
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Input;
namespace TestClient;
/// <summary>
/// 부품 뷰 모델
/// </summary>
public class PartViewModel : INotifyPropertyChanged
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Event
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 속성 변경시 - PropertyChanged
/// <summary>
/// 속성 변경시
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Field
////////////////////////////////////////////////////////////////////////////////////////// Private
#region Field
/// <summary>
/// 갱신 여부
/// </summary>
private bool isRefreshing = false;
/// <summary>
/// 실행 여부
/// </summary>
private bool isBusy = false;
/// <summary>
/// 부품 컬렉션
/// </summary>
private ObservableCollection<Part> partCollection;
/// <summary>
/// 선택 부품
/// </summary>
private Part selectedPart;
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Property
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 갱신 여부 - IsRefreshing
/// <summary>
/// 갱신 여부
/// </summary>
public bool IsRefreshing
{
get => this.isRefreshing;
set
{
if(this.isRefreshing == value)
{
return;
}
this.isRefreshing = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsRefreshing)));
}
}
#endregion
#region 실행 여부 - IsBusy
/// <summary>
/// 실행 여부
/// </summary>
public bool IsBusy
{
get => this.isBusy;
set
{
if(this.isBusy == value)
{
return;
}
this.isBusy = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsBusy)));
}
}
#endregion
#region 부품 컬렉션 - PartCollection
/// <summary>
/// 부품 컬렉션
/// </summary>
public ObservableCollection<Part> PartCollection
{
get => this.partCollection;
set => this.partCollection = value;
}
#endregion
#region 선택 부품 - SelectedPart
/// <summary>
/// 선택 부품
/// </summary>
public Part SelectedPart
{
get => this.selectedPart;
set
{
if(this.selectedPart == value)
{
return;
}
this.selectedPart = value;
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedPart)));
}
}
#endregion
#region 데이터 로드 명령 - LoadDataCommand
/// <summary>
/// 데이터 로드 명령
/// </summary>
public ICommand LoadDataCommand { get; private set; }
#endregion
#region 부품 선택시 명령 - PartSelectedCommand
/// <summary>
/// 부품 선택시 명령
/// </summary>
public ICommand PartSelectedCommand { get; private set; }
#endregion
#region 신규 부품 추가 명령 - AddNewPartCommand
/// <summary>
/// 신규 부품 추가 명령
/// </summary>
public ICommand AddNewPartCommand { get; private set; }
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 생성자 - PartViewModel()
/// <summary>
/// 생성자
/// </summary>
public PartViewModel()
{
this.partCollection = new ObservableCollection<Part>();
LoadDataCommand = new Command(async () => await ProcessLoadData());
PartSelectedCommand = new Command(async () => await ProcessPartSelected());
AddNewPartCommand = new Command(async () => await Shell.Current.GoToAsync("addpart"));
MessagingCenter.Subscribe<AddPartViewModel>(this, "refresh", async (sender) => await ProcessLoadData());
Task.Run(ProcessLoadData);
}
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Method
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 데이터 로드 처리하기 - ProcessLoadData()
/// <summary>
/// 데이터 로드 처리하기
/// </summary>
/// <returns>태스크</returns>
private async Task ProcessLoadData()
{
if(IsBusy)
{
return;
}
try
{
IsRefreshing = true;
IsBusy = true;
IEnumerable<Part> partEnumerable = await PartManager.GetPartEnumerableAsync();
MainThread.BeginInvokeOnMainThread
(
() =>
{
PartCollection.Clear();
foreach(Part part in partEnumerable)
{
PartCollection.Add(part);
}
}
);
}
finally
{
IsRefreshing = false;
IsBusy = false;
}
}
#endregion
#region 부품 선택시 처리하기 - ProcessPartSelected()
/// <summary>
/// 부품 선택시 처리하기
/// </summary>
/// <returns>태스크</returns>
private async Task ProcessPartSelected()
{
if(SelectedPart == null)
{
return;
}
Dictionary<string, object> parameterDictionary = new Dictionary<string, object>()
{
{ "part", SelectedPart }
};
await Shell.Current.GoToAsync("addpart", parameterDictionary);
MainThread.BeginInvokeOnMainThread(() => SelectedPart = null);
}
#endregion
}
▶ VIEWMODEL/AddPartViewModel.cs
using System.ComponentModel;
using System.Windows.Input;
namespace TestClient;
/// <summary>
/// 부품 추가 뷰 모델
/// </summary>
public class AddPartViewModel : INotifyPropertyChanged
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Event
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 속성 변경시 - PropertyChanged
/// <summary>
/// 속성 변경시
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Field
////////////////////////////////////////////////////////////////////////////////////////// Private
#region Field
/// <summary>
/// 부품 ID
/// </summary>
private string partID;
/// <summary>
/// 부품명
/// </summary>
private string partName;
/// <summary>
/// 공급자 리스트
/// </summary>
private string supplierList;
/// <summary>
/// 부품 타입
/// </summary>
private string partType;
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Property
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 부품 ID - PartID
/// <summary>
/// 부품 ID
/// </summary>
public string PartID
{
get => this.partID;
set
{
if(this.partID == value)
{
return;
}
this.partID = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(PartID)));
}
}
#endregion
#region 부품명 - PartName
/// <summary>
/// 부품명
/// </summary>
public string PartName
{
get => this.partName;
set
{
if(this.partName == value)
{
return;
}
this.partName = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(PartName)));
}
}
#endregion
#region 공급자 리스트 - SupplierList
/// <summary>
/// 공급자 리스트
/// </summary>
public string SupplierList
{
get => this.supplierList;
set
{
if(this.supplierList == value)
{
return;
}
this.supplierList = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SupplierList)));
}
}
#endregion
#region 부품 타입 - PartType
/// <summary>
/// 부품 타입
/// </summary>
public string PartType
{
get => this.partType;
set
{
if(this.partType == value)
{
return;
}
this.partType = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(PartType)));
}
}
#endregion
#region 편집 완료시 명령 - DoneEditingCommand
/// <summary>
/// 편집 완료시 명령
/// </summary>
public ICommand DoneEditingCommand { get; private set; }
#endregion
#region 저장 명령 - SaveCommand
/// <summary>
/// 저장 명령
/// </summary>
public ICommand SaveCommand { get; private set; }
#endregion
#region 삭제 명령 - DeleteCommand
/// <summary>
/// 삭제 명령
/// </summary>
public ICommand DeleteCommand { get; private set; }
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 생성자 - AddPartViewModel()
/// <summary>
/// 생성자
/// </summary>
public AddPartViewModel()
{
DoneEditingCommand = new Command(async () => await ProcessEditingDone());
SaveCommand = new Command(async () => await ProcessSave());
DeleteCommand = new Command(async () => await ProcessDelete());
}
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Method
////////////////////////////////////////////////////////////////////////////////////////// Private
#region 부품 삽입하기 - InsertPart()
/// <summary>
/// 부품 삽입하기
/// </summary>
/// <returns>태스크</returns>
private async Task InsertPart()
{
await PartManager.AddAsync(PartName, PartType, SupplierList);
MessagingCenter.Send(this, "refresh");
await Shell.Current.GoToAsync("..");
}
#endregion
#region 부품 수정하기 - UpdatePart()
/// <summary>
/// 부품 수정하기
/// </summary>
/// <returns>태스크</returns>
private async Task UpdatePart()
{
Part partToSave = new()
{
PartID = PartID,
PartName = PartName,
PartType = PartType,
SupplierList = SupplierList.Split(",").ToList()
};
await PartManager.UpdateAsync(partToSave);
MessagingCenter.Send(this, "refresh");
await Shell.Current.GoToAsync("..");
}
#endregion
#region 편집 완료시 처리하기 - ProcessEditingDone()
/// <summary>
/// 편집 완료시 처리하기
/// </summary>
/// <returns>태스크</returns>
private async Task ProcessEditingDone()
{
await Shell.Current.GoToAsync("..");
}
#endregion
#region 저장 처리하기 - ProcessSave()
/// <summary>
/// 저장 처리하기
/// </summary>
/// <returns>태스크</returns>
private async Task ProcessSave()
{
if(string.IsNullOrWhiteSpace(PartID))
{
await InsertPart();
}
else
{
await UpdatePart();
}
}
#endregion
#region 삭제 처리하기 - ProcessDelete()
/// <summary>
/// 삭제 처리하기
/// </summary>
/// <returns>태스크</returns>
private async Task ProcessDelete()
{
if(string.IsNullOrWhiteSpace(PartID))
{
return;
}
await PartManager.DeleteAsync(PartID);
MessagingCenter.Send(this, "refresh");
await Shell.Current.GoToAsync("..");
}
#endregion
}
▶ PAGE/PartPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage x:Class="TestClient.PartPage"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
<VerticalStackLayout>
<Button
Margin="20,10,20,10"
Text="신규 부품 추가하기"
Command="{Binding AddNewPartCommand}" />
<RefreshView x:Name="refreshView"
IsRefreshing="{Binding IsRefreshing}"
Command="{Binding LoadDataCommand}">
<CollectionView
Margin="30,20,30,30"
ItemsSource="{Binding PartCollection}"
SelectedItem="{Binding SelectedPart, Mode=TwoWay}"
SelectionChangedCommand="{Binding PartSelectedCommand}"
SelectionMode="Single">
<CollectionView.ItemsLayout>
<LinearItemsLayout
Orientation="Vertical"
ItemSpacing="20" />
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate>
<VerticalStackLayout
Margin="10,5,10,5"
Padding="15,10">
<Label
Margin="0,0,0,20"
FontSize="Title"
Text="{Binding PartID, StringFormat='ID: {0}'}" />
<Label Text="{Binding PartName, StringFormat='부품명 : {0}'}"/>
<Label Text="{Binding PartType, StringFormat='부품 타입 : {0}'}"/>
</VerticalStackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</RefreshView>
</VerticalStackLayout>
</ContentPage>
▶ PAGE/PartPage.xaml.cs
namespace TestClient;
/// <summary>
/// 부품 페이지
/// </summary>
public partial class PartPage : ContentPage
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 생성자 - PartPage()
/// <summary>
/// 생성자
/// </summary>
public PartPage()
{
InitializeComponent();
BindingContext = new PartViewModel();
}
#endregion
}
▶ PAGE/AddPartPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage x:Class="TestClient.AddPartPage"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
Title="부품 편집">
<Grid
RowDefinitions="*,Auto"
ColumnDefinitions="*,*,*"
ColumnSpacing="5">
<TableView Grid.Row="0" Grid.ColumnSpan="3"
Intent="Data">
<TableRoot>
<TableSection Title="부품 정보">
<EntryCell
Label="부품 ID"
Text="{Binding PartID}"
IsEnabled="False" />
<EntryCell
Label="부품명"
Text="{Binding PartName}" />
<EntryCell
Label="부품 타입"
Text="{Binding PartType}" />
<EntryCell
Label="공급자"
Text="{Binding SupplierList}" />
</TableSection>
</TableRoot>
</TableView>
<Button Grid.Row="1" Grid.Column="0"
Margin="10"
Text="저장"
Command="{Binding SaveCommand}" />
<Button Grid.Row="1" Grid.Column="1"
Margin="10"
Text="삭제"
Command="{Binding DeleteCommand}"/>
<Button Grid.Row="1" Grid.Column="2"
Margin="10"
Text="취소"
Command="{Binding DoneEditingCommand}" />
</Grid>
</ContentPage>
▶ PAGE/AddPartPage.xaml.cs
namespace TestClient;
/// <summary>
/// 파트 추가 페이지
/// </summary>
[QueryProperty("PartToDisplay", "part")]
public partial class AddPartPage : ContentPage
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Field
////////////////////////////////////////////////////////////////////////////////////////// Private
#region Field
/// <summary>
/// 뷰 모델
/// </summary>
private AddPartViewModel viewModel;
/// <summary>
/// 표시할 부품
/// </summary>
private Part partToDisplay;
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Property
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 표시할 부품 - PartToDisplay
/// <summary>
/// 표시할 부품
/// </summary>
public Part PartToDisplay
{
get => this.partToDisplay;
set
{
if(this.partToDisplay == value)
{
return;
}
this.partToDisplay = value;
this.viewModel.PartID = this.partToDisplay.PartID;
this.viewModel.PartName = this.partToDisplay.PartName;
this.viewModel.SupplierList = this.partToDisplay.SupplierList2;
this.viewModel.PartType = this.partToDisplay.PartType;
}
}
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 생성자 - AddPartPage()
/// <summary>
/// 생성자
/// </summary>
public AddPartPage()
{
InitializeComponent();
this.viewModel = new AddPartViewModel();
BindingContext = this.viewModel;
}
#endregion
}
▶ AppShell.xaml
<?xml version="1.0" encoding="UTF-8" ?>
<Shell x:Class="TestClient.AppShell"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:TestClient"
Shell.FlyoutBehavior="Disabled">
<TabBar>
<Tab>
<ShellContent
Title="부품 리스트"
Route="listparts"
ContentTemplate="{DataTemplate local:PartPage}" />
</Tab>
</TabBar>
</Shell>
▶ AppShell.xaml.cs
namespace TestClient;
/// <summary>
/// 앱 셸
/// </summary>
public partial class AppShell : Shell
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 생성자 - AppShell()
/// <summary>
/// 생성자
/// </summary>
public AppShell()
{
InitializeComponent();
Routing.RegisterRoute("addpart", typeof(AddPartPage));
}
#endregion
}
▶ App.xaml
<?xml version = "1.0" encoding = "UTF-8" ?>
<Application x:Class="TestClient.App"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" />
▶ App.xaml.cs
namespace TestClient;
/// <summary>
/// 앱
/// </summary>
public partial class App : Application
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 생성자 - App()
/// <summary>
/// 생성자
/// </summary>
public App()
{
InitializeComponent();
MainPage = new AppShell();
}
#endregion
}
▶ MauiProgram.cs
namespace TestClient;
/// <summary>
/// MAUI 프로그램
/// </summary>
public static class MauiProgram
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Method
////////////////////////////////////////////////////////////////////////////////////////// Static
//////////////////////////////////////////////////////////////////////////////// Public
#region MAUI 앱 생성하기 - CreateMauiApp()
/// <summary>
/// MAUI 앱 생성하기
/// </summary>
/// <returns>MAUI 앱</returns>
public static MauiApp CreateMauiApp()
{
MauiAppBuilder builder = MauiApp.CreateBuilder();
builder.UseMauiApp<App>()
.ConfigureFonts
(
fontCollection =>
{
fontCollection.AddFont("OpenSans-Regular.ttf" , "OpenSansRegular" );
fontCollection.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
}
);
return builder.Build();
}
#endregion
}
※ 외부 접속을 위해 개인적으로 서버 방화벽 및 무선 AP 포트 포워딩을 설정했다.
728x90
반응형
그리드형(광고전용)
'C# > MAUI' 카테고리의 다른 글
[C#/MAUI/.NET6] 누겟 설치 : CommunityToolkit.Maui.Core (0) | 2022.07.13 |
---|---|
[C#/MAUI/.NET6] 누겟 설치 : CommunityToolkit.Maui (0) | 2022.07.13 |
[C#/MAUI/.NET6] SQLite 데이터베이스 사용하기 (ANDROID) (UWP) (0) | 2022.07.12 |
[C#/MAUI/.NET6] 누겟 설치 : SQLitePCLRaw.provider.dynamic_cdecl (0) | 2022.07.12 |
[C#/MAUI/.NET6] 누겟 설치 : sqlite-net-pcl (0) | 2022.07.12 |
[C#/MAUI/.NET6] Shell 엘리먼트 : 태양 일출/일몰 및 달 위상 정보 조회하기 (0) | 2022.07.09 |
[C#/MAUI/.NET6] Connectivity 클래스 : ConnectivityChanged 정적 이벤트를 사용해 네트워크 연결 변경시 처리하기 (0) | 2022.07.05 |
[C#/MAUI/.NET6] IImage 인터페이스 : Save 메소드를 사용해 이미지 저장하기 (0) | 2022.07.04 |
[C#/MAUI/.NET6] ICanvas 인터페이스 : FillCircle 메소드를 사용해 원 칠하기 (0) | 2022.07.03 |
[C#/MAUI/.NET6] ICanvas 인터페이스 : BlendMode 속성을 사용해 혼합 모드 설정하기 (프리뷰 버전 오류) (0) | 2022.07.03 |
댓글을 달아 주세요