.NET용 Azure Mobile Apps v4.2.0 클라이언트 라이브러리를 사용하는 방법
메모
이 제품은 사용 중지되었습니다. .NET 8 이상을 사용하는 프로젝트를 대체하려면 Community Toolkit Datasync 라이브러리참조하세요.
이 가이드에서는 Azure Mobile Apps용 .NET 클라이언트 라이브러리를 사용하여 일반적인 시나리오를 수행하는 방법을 보여 줍니다. Windows(WPF, UWP) 또는 Xamarin(네이티브 또는 양식) 애플리케이션에서 .NET 클라이언트 라이브러리를 사용합니다. Azure Mobile Apps를 처음 접하는 경우 먼저 Xamarin.Forms 자습서에 대한
경고
이 문서에서는 v5.0.0 라이브러리로 대체되는 v4.2.0 라이브러리 버전에 대한 정보를 다룹니다. 최신 정보는 최신 버전 문서를 참조하세요.
지원되는 플랫폼
.NET 클라이언트 라이브러리는 .NET Standard 2.0 및 다음 플랫폼을 지원합니다.
- API 수준 19에서 API 수준 30까지의 Xamarin.Android
- Xamarin.iOS 버전 8.0~ 14.3.
- 유니버설 Windows 플랫폼은 16299 이상을 빌드합니다.
- 모든 .NET Standard 2.0 애플리케이션.
"서버 흐름" 인증은 제공된 UI에 WebView를 사용하며 모든 플랫폼에서 사용할 수 없습니다. 사용할 수 없는 경우 "클라이언트 흐름" 인증을 제공해야 합니다. 이 클라이언트 라이브러리는 인증을 사용하는 경우 조사식 또는 IoT 폼 팩터에 적합하지 않습니다.
설정 및 필수 구성 요소
하나 이상의 테이블을 포함하는 Azure Mobile Apps 백 엔드 프로젝트를 이미 만들고 게시했다고 가정합니다. 이 항목에 사용된 코드에서 테이블의 이름은 TodoItem
문자열 Id
Text
필드와 부울 Complete
열이 있습니다. 이 테이블은 빠른 시작완료할 때 만든 테이블과 동일합니다.
C#의 해당 형식화된 클라이언트 쪽 형식은 다음 클래스입니다.
public class TodoItem
{
public string Id { get; set; }
[JsonProperty(PropertyName = "text")]
public string Text { get; set; }
[JsonProperty(PropertyName = "complete")]
public bool Complete { get; set; }
}
JsonPropertyAttribute 클라이언트 필드와 테이블 필드 간의 PropertyName 매핑을 정의하는 데 사용됩니다.
Mobile Apps 백 엔드에서 테이블을 만드는 방법을 알아보려면
관리되는 클라이언트 SDK 패키지 설치
프로젝트를 마우스 오른쪽 단추로 클릭하고 Microsoft.Azure.Mobile.Client.SQLiteStore
패키지도 설치합니다.
Azure Mobile Apps 클라이언트 만들기
다음 코드는 Mobile App 백 엔드에 액세스하는 데 사용되는 MobileServiceClient 개체를 만듭니다.
var client = new MobileServiceClient("MOBILE_APP_URL");
앞의 코드에서 MOBILE_APP_URL
App Service 백 엔드의 URL로 바꿉니다.
MobileServiceClient
개체는 싱글톤이어야 합니다.
테이블 작업
다음 섹션에서는 레코드를 검색 및 검색하고 테이블 내의 데이터를 수정하는 방법을 자세히 설명합니다. 다음 항목에 대해 설명합니다.
- 테이블 참조 만들기
- 데이터 쿼리
- 필터가 반환된 데이터
- 반환된 데이터 정렬
- 페이지 데이터 반환
- 특정 열 선택
- ID 레코드 조회
- 형식화되지 않은 쿼리 실행
- 데이터 삽입
- 데이터 업데이트
- 데이터 삭제
- 충돌 해결 및 낙관적 동시성
- Windows 사용자 인터페이스 데이터 바인딩
- 페이지 크기 변경
테이블 참조 만들기
백 엔드 테이블의 데이터에 액세스하거나 수정하는 모든 코드는 MobileServiceTable
개체의 함수를 호출합니다. 다음과 같이 GetTable 메서드를 호출하여 테이블에 대한 참조를 가져옵니다.
IMobileServiceTable<TodoItem> todoTable = client.GetTable<TodoItem>();
반환된 개체는 형식화된 serialization 모델을 사용합니다. 형식화되지 않은 serialization 모델도 지원됩니다. 다음 예제 형식화되지 않은 테이블대한 참조를 만듭니다.
// Get an untyped table reference
IMobileServiceTable untypedTodoTable = client.GetTable("TodoItem");
형식화되지 않은 쿼리에서는 기본 OData 쿼리 문자열을 지정해야 합니다.
모바일 앱에서 데이터 쿼리
이 섹션에서는 다음 기능을 포함하는 모바일 앱 백 엔드에 대한 쿼리를 실행하는 방법을 설명합니다.
- 필터가 반환된 데이터
- 반환된 데이터 정렬
- 페이지 데이터 반환
- 특정 열 선택
- ID 레코드 조회
메모
모든 행이 반환되지 않도록 서버 기반 페이지 크기가 적용됩니다. 페이징은 큰 데이터 집합에 대한 기본 요청이 서비스에 부정적인 영향을 주지 않도록 유지합니다. 50개 이상의 행을 반환하려면 페이지
반환된 데이터 필터링
다음 코드에서는 쿼리에 Where
절을 포함하여 데이터를 필터링하는 방법을 보여 줍니다.
Complete
속성이 false
같은 todoTable
모든 항목을 반환합니다.
Where 함수는 테이블에 대한 쿼리에 행 필터링 조건자를 적용합니다.
// This query filters out completed TodoItems and items without a timestamp.
List<TodoItem> items = await todoTable
.Where(todoItem => todoItem.Complete == false)
.ToListAsync();
브라우저 개발자 도구 또는 Fiddler같은 메시지 검사 소프트웨어를 사용하여 백 엔드로 전송된 요청의 URI를 볼 수 있습니다. 요청 URI를 보면 쿼리 문자열이 수정됩니다.
GET /tables/todoitem?$filter=(complete+eq+false) HTTP/1.1
이 OData 요청은 서버 SDK에서 SQL 쿼리로 변환됩니다.
SELECT *
FROM TodoItem
WHERE ISNULL(complete, 0) = 0
Where
메서드에 전달되는 함수는 임의의 수의 조건을 가질 수 있습니다.
// This query filters out completed TodoItems where Text isn't null
List<TodoItem> items = await todoTable
.Where(todoItem => todoItem.Complete == false && todoItem.Text != null)
.ToListAsync();
이 예제는 서버 SDK에서 SQL 쿼리로 변환됩니다.
SELECT *
FROM TodoItem
WHERE ISNULL(complete, 0) = 0
AND ISNULL(text, 0) = 0
이 쿼리는 여러 절로 분할할 수도 있습니다.
List<TodoItem> items = await todoTable
.Where(todoItem => todoItem.Complete == false)
.Where(todoItem => todoItem.Text != null)
.ToListAsync();
두 메서드는 동일하며 서로 바꿔 사용할 수 있습니다. 한 쿼리에서 여러 조건자를 연결하는 이전 옵션은 더 간결하고 권장됩니다.
Where
절은 OData 하위 집합으로 변환되는 작업을 지원합니다. 작업에는 다음이 포함됩니다.
- 관계형 연산자(
==
,!=
,<
,<=
,>
,>=
), - 산술 연산자(
+
,-
,/
,*
,%
), - 숫자 정밀도(
Math.Floor
,Math.Ceiling
), - 문자열 함수(
Length
,Substring
,Replace
,IndexOf
,StartsWith
,EndsWith
), - 날짜 속성(
Year
,Month
,Day
,Hour
,Minute
,Second
), - 개체의 액세스 속성 및
- 이러한 작업을 결합하는 식입니다.
서버 SDK에서 지원하는 기능을 고려할 때 OData v3 설명서고려할 수 있습니다.
반환된 데이터 정렬
다음 코드에서는 쿼리에 OrderBy 또는 OrderByDescending 함수를 포함하여 데이터를 정렬하는 방법을 보여 줍니다.
Text
필드를 기준으로 정렬된 todoTable
항목을 반환합니다.
// Sort items in ascending order by Text field
MobileServiceTableQuery<TodoItem> query = todoTable
.OrderBy(todoItem => todoItem.Text)
List<TodoItem> items = await query.ToListAsync();
// Sort items in descending order by Text field
MobileServiceTableQuery<TodoItem> query = todoTable
.OrderByDescending(todoItem => todoItem.Text)
List<TodoItem> items = await query.ToListAsync();
페이지에서 데이터 반환
기본적으로 백 엔드는 처음 50개 행만 반환합니다.
Take 메서드를 호출하여 반환된 행 수를 늘릴 수 있습니다.
Take
Skip 메서드와 함께 사용하여 쿼리에서 반환된 총 데이터 세트의 특정 "페이지"를 요청합니다. 다음 쿼리는 실행될 때 테이블의 상위 3개 항목을 반환합니다.
// Define a filtered query that returns the top 3 items.
MobileServiceTableQuery<TodoItem> query = todoTable.Take(3);
List<TodoItem> items = await query.ToListAsync();
다음 수정된 쿼리는 처음 세 개의 결과를 건너뛰고 다음 세 개의 결과를 반환합니다. 이 쿼리는 데이터의 두 번째 "페이지"를 생성합니다. 여기서 페이지 크기는 세 개의 항목입니다.
// Define a filtered query that skips the top 3 items and returns the next 3 items.
MobileServiceTableQuery<TodoItem> query = todoTable.Skip(3).Take(3);
List<TodoItem> items = await query.ToListAsync();
query = query.IncludeTotalCount();
실제 앱에서는 이전 예제와 유사한 쿼리를 페이저 컨트롤 또는 비교 가능한 UI와 함께 사용하여 페이지 간을 탐색할 수 있습니다.
메모
모바일 앱 백 엔드에서 50행 제한을 재정의하려면 EnableQueryAttribute 공용 GET 메서드에 적용하고 페이징 동작을 지정해야 합니다. 메서드에 적용하면 반환되는 최대 행을 1000으로 설정합니다.
[EnableQuery(MaxTop=1000)]
특정 열 선택
쿼리에 Select 절을 추가하여 결과에 포함할 속성 집합을 지정할 수 있습니다. 예를 들어 다음 코드는 하나의 필드만 선택하는 방법과 여러 필드를 선택하고 서식을 지정하는 방법을 보여줍니다.
// Select one field -- just the Text
MobileServiceTableQuery<TodoItem> query = todoTable
.Select(todoItem => todoItem.Text);
List<string> items = await query.ToListAsync();
// Select multiple fields -- both Complete and Text info
MobileServiceTableQuery<TodoItem> query = todoTable
.Select(todoItem => string.Format("{0} -- {1}",
todoItem.Text.PadRight(30), todoItem.Complete ?
"Now complete!" : "Incomplete!"));
List<string> items = await query.ToListAsync();
지금까지 설명한 모든 함수는 가산적이므로 계속 연결할 수 있습니다. 연결된 각 호출은 더 많은 쿼리에 영향을 줍니다. 한 가지 더 예제:
MobileServiceTableQuery<TodoItem> query = todoTable
.Where(todoItem => todoItem.Complete == false)
.Select(todoItem => todoItem.Text)
.Skip(3).
.Take(3);
List<string> items = await query.ToListAsync();
ID별로 데이터 조회
LookupAsync 함수를 사용하여 특정 ID를 사용하여 데이터베이스에서 개체를 조회할 수 있습니다.
// This query filters out the item with the ID of 37BBF396-11F0-4B39-85C8-B319C729AF6D
TodoItem item = await todoTable.LookupAsync("37BBF396-11F0-4B39-85C8-B319C729AF6D");
형식화되지 않은 쿼리 실행
형식화되지 않은 테이블 개체를 사용하여 쿼리를 실행할 때 다음 예제와 같이 ReadAsync호출하여 OData 쿼리 문자열을 명시적으로 지정해야 합니다.
// Lookup untyped data using OData
JToken untypedItems = await untypedTodoTable.ReadAsync("$filter=complete eq 0&$orderby=text");
속성 모음처럼 사용할 수 있는 JSON 값을 다시 가져옵니다.
JToken
및 Newtonsoft Json에 대한 자세한 내용은 Newtonsoft JSON 사이트를 참조하세요.
데이터 삽입
모든 클라이언트 형식은 기본적으로 문자열인 id멤버를 포함해야 합니다. 이 ID CRUD 작업을 수행하고 오프라인 동기화에 필요합니다. 다음 코드에서는 InsertAsync 메서드를 사용하여 테이블에 새 행을 삽입하는 방법을 보여 줍니다. 매개 변수에는 .NET 개체로 삽입할 데이터가 포함됩니다.
await todoTable.InsertAsync(todoItem);
삽입하는 동안 고유한 사용자 지정 ID 값이 todoItem
포함되지 않으면 서버에서 GUID를 생성합니다. 호출이 반환된 후 개체를 검사하여 생성된 ID를 검색할 수 있습니다.
형식화되지 않은 데이터를 삽입하려면 다음 Json.NET 활용할 수 있습니다.
JObject jo = new JObject();
jo.Add("Text", "Hello World");
jo.Add("Complete", false);
var inserted = await table.InsertAsync(jo);
다음은 이메일 주소를 고유한 문자열 ID로 사용하는 예제입니다.
JObject jo = new JObject();
jo.Add("id", "myemail@emaildomain.com");
jo.Add("Text", "Hello World");
jo.Add("Complete", false);
var inserted = await table.InsertAsync(jo);
ID 값 작업
Mobile Apps는 테이블의 id 열에 고유한 사용자 지정 문자열 값을 지원합니다. 문자열 값을 사용하면 애플리케이션에서 전자 메일 주소 또는 ID에 대한 사용자 이름과 같은 사용자 지정 값을 사용할 수 있습니다. 문자열 ID는 다음과 같은 이점을 제공합니다.
- ID는 데이터베이스를 왕복하지 않고 생성됩니다.
- 레코드는 다른 테이블 또는 데이터베이스에서 병합하는 것이 더 쉽습니다.
- ID 값은 애플리케이션의 논리와 더 잘 통합될 수 있습니다.
문자열 ID 값이 삽입된 레코드에 설정되지 않은 경우 모바일 앱 백 엔드는 ID에 대한 고유 값을 생성합니다. Guid.NewGuid 메서드를 사용하여 클라이언트 또는 백 엔드에서 고유한 ID 값을 생성할 수 있습니다.
JObject jo = new JObject();
jo.Add("id", Guid.NewGuid().ToString("N"));
데이터 업데이트
다음 코드에서는 UpdateAsync 메서드를 사용하여 새 정보로 동일한 ID로 기존 레코드를 업데이트하는 방법을 보여 줍니다. 매개 변수에는 .NET 개체로 업데이트할 데이터가 포함됩니다.
await todoTable.UpdateAsync(todoItem);
형식화되지 않은 데이터를 업데이트하려면 다음과 같이 Newtonsoft JSON 활용할 수 있습니다.
JObject jo = new JObject();
jo.Add("id", "37BBF396-11F0-4B39-85C8-B319C729AF6D");
jo.Add("Text", "Hello World");
jo.Add("Complete", false);
var inserted = await table.UpdateAsync(jo);
업데이트할 때 id
필드를 지정해야 합니다. 백 엔드는 id
필드를 사용하여 업데이트할 행을 식별합니다.
id
필드는 InsertAsync
호출의 결과에서 가져올 수 있습니다.
id
값을 제공하지 않고 항목을 업데이트하려고 하면 ArgumentException
발생합니다.
데이터 삭제
다음 코드에서는 DeleteAsync 메서드를 사용하여 기존 인스턴스를 삭제하는 방법을 보여 줍니다. 인스턴스는 todoItem
설정된 id
필드로 식별됩니다.
await todoTable.DeleteAsync(todoItem);
형식화되지 않은 데이터를 삭제하려면 다음과 같이 Json.NET 활용할 수 있습니다.
JObject jo = new JObject();
jo.Add("id", "37BBF396-11F0-4B39-85C8-B319C729AF6D");
await table.DeleteAsync(jo);
삭제 요청을 수행할 때 ID를 지정해야 합니다. 다른 속성은 서비스에 전달되지 않거나 서비스에서 무시됩니다.
DeleteAsync
호출의 결과는 일반적으로 null
. 전달할 ID는 InsertAsync
호출의 결과에서 가져올 수 있습니다.
id
필드를 지정하지 않고 항목을 삭제하려고 하면 MobileServiceInvalidOperationException
throw됩니다.
충돌 해결 및 낙관적 동시성
둘 이상의 클라이언트가 동시에 동일한 항목에 변경 내용을 작성할 수 있습니다. 충돌 검색이 없으면 마지막 쓰기가 이전 업데이트를 덮어씁니다. 낙관적 동시성 제어 각 트랜잭션이 커밋될 수 있으므로 리소스 잠금을 사용하지 않는다고 가정합니다. 트랜잭션을 커밋하기 전에 낙관적 동시성 제어는 다른 트랜잭션이 데이터를 수정하지 않은 것을 확인합니다. 데이터가 수정된 경우 커밋 트랜잭션이 롤백됩니다.
Mobile Apps는 Mobile App 백 엔드의 각 테이블에 대해 정의된 version
시스템 속성 열을 사용하여 각 항목의 변경 내용을 추적하여 낙관적 동시성 제어를 지원합니다. 레코드가 업데이트 될 때마다 Mobile Apps는 해당 레코드의 version
속성을 새 값으로 설정합니다. 각 업데이트 요청 중에 요청에 포함된 레코드의 version
속성이 서버의 레코드에 대해 동일한 속성과 비교됩니다. 요청과 함께 전달된 버전이 백 엔드와 일치하지 않으면 클라이언트 라이브러리에서 MobileServicePreconditionFailedException<T>
예외가 발생합니다. 예외에 포함된 형식은 레코드의 서버 버전을 포함하는 백 엔드의 레코드입니다. 그런 다음 애플리케이션은 이 정보를 사용하여 변경 내용을 커밋하기 위해 백 엔드의 올바른 version
값으로 업데이트 요청을 다시 실행할지 여부를 결정할 수 있습니다.
낙관적 동시성을 사용하도록 설정하기 위해 version
시스템 속성에 대한 테이블 클래스의 열을 정의합니다. 예를 들어:
public class TodoItem
{
public string Id { get; set; }
[JsonProperty(PropertyName = "text")]
public string Text { get; set; }
[JsonProperty(PropertyName = "complete")]
public bool Complete { get; set; }
// *** Enable Optimistic Concurrency *** //
[JsonProperty(PropertyName = "version")]
public string Version { set; get; }
}
형식화되지 않은 테이블을 사용하는 애플리케이션은 다음과 같이 테이블 SystemProperties
Version
플래그를 설정하여 낙관적 동시성을 가능하게 합니다.
//Enable optimistic concurrency by retrieving version
todoTable.SystemProperties |= MobileServiceSystemProperties.Version;
낙관적 동시성을 사용하도록 설정하는 것 외에도 UpdateAsync호출할 때 코드에서 MobileServicePreconditionFailedException<T>
예외를 catch해야 합니다. 업데이트된 레코드에 올바른
private async void UpdateToDoItem(TodoItem item)
{
MobileServicePreconditionFailedException<TodoItem> exception = null;
try
{
//update at the remote table
await todoTable.UpdateAsync(item);
}
catch (MobileServicePreconditionFailedException<TodoItem> writeException)
{
exception = writeException;
}
if (exception != null)
{
// Conflict detected, the item has changed since the last query
// Resolve the conflict between the local and server item
await ResolveConflict(item, exception.Item);
}
}
private async Task ResolveConflict(TodoItem localItem, TodoItem serverItem)
{
//Ask user to choose the resolution between versions
MessageDialog msgDialog = new MessageDialog(
String.Format("Server Text: \"{0}\" \nLocal Text: \"{1}\"\n",
serverItem.Text, localItem.Text),
"CONFLICT DETECTED - Select a resolution:");
UICommand localBtn = new UICommand("Commit Local Text");
UICommand ServerBtn = new UICommand("Leave Server Text");
msgDialog.Commands.Add(localBtn);
msgDialog.Commands.Add(ServerBtn);
localBtn.Invoked = async (IUICommand command) =>
{
// To resolve the conflict, update the version of the item being committed. Otherwise, you will keep
// catching a MobileServicePreConditionFailedException.
localItem.Version = serverItem.Version;
// Updating recursively here just in case another change happened while the user was making a decision
UpdateToDoItem(localItem);
};
ServerBtn.Invoked = async (IUICommand command) =>
{
RefreshTodoItems();
};
await msgDialog.ShowAsync();
}
자세한 내용은 Azure Mobile Apps 항목에서
Windows 사용자 인터페이스에 데이터 바인딩
이 섹션에서는 Windows 앱에서 UI 요소를 사용하여 반환된 데이터 개체를 표시하는 방법을 보여 줍니다. 다음 예제 코드는 불완전한 항목에 대한 쿼리를 사용하여 목록의 원본에 바인딩합니다. MobileServiceCollection Mobile Apps 인식 바인딩 컬렉션을 만듭니다.
// This query filters out completed TodoItems.
MobileServiceCollection<TodoItem, TodoItem> items = await todoTable
.Where(todoItem => todoItem.Complete == false)
.ToCollectionAsync();
// itemsControl is an IEnumerable that could be bound to a UI list control
IEnumerable itemsControl = items;
// Bind this to a ListBox
ListBox lb = new ListBox();
lb.ItemsSource = items;
관리되는 런타임의 일부 컨트롤은 ISupportIncrementalLoading라는 인터페이스를 지원합니다. 이 인터페이스를 사용하면 사용자가 스크롤할 때 컨트롤에서 추가 데이터를 요청할 수 있습니다. 컨트롤의 호출을 자동으로 처리하는 MobileServiceIncrementalLoadingCollection통해 유니버설 Windows 앱에 대한 이 인터페이스를 기본적으로 지원합니다. 다음과 같이 Windows 앱에서 MobileServiceIncrementalLoadingCollection
사용합니다.
MobileServiceIncrementalLoadingCollection<TodoItem,TodoItem> items;
items = todoTable.Where(todoItem => todoItem.Complete == false).ToIncrementalLoadingCollection();
ListBox lb = new ListBox();
lb.ItemsSource = items;
Windows Phone 8 및 "Silverlight" 앱에서 새 컬렉션을 사용하려면 IMobileServiceTableQuery<T>
및 IMobileServiceTable<T>
ToCollection
확장 메서드를 사용합니다. 데이터를 로드하려면 LoadMoreItemsAsync()
호출합니다.
MobileServiceCollection<TodoItem, TodoItem> items = todoTable.Where(todoItem => todoItem.Complete==false).ToCollection();
await items.LoadMoreItemsAsync();
ToCollectionAsync
또는 ToCollection
호출하여 만든 컬렉션을 사용하면 UI 컨트롤에 바인딩할 수 있는 컬렉션을 가져옵니다. 이 컬렉션은 페이징을 인식합니다. 컬렉션이 네트워크에서 데이터를 로드하므로 로드에 실패하는 경우가 있습니다. 이러한 오류를 처리하려면 MobileServiceIncrementalLoadingCollection
OnException
메서드를 재정의하여 LoadMoreItemsAsync
호출로 인한 예외를 처리합니다.
테이블에 많은 필드가 있지만 일부 필드만 컨트롤에 표시하려는 경우를 고려합니다. 이전 섹션 "특정 열 선택"의 지침을 사용하여 UI에 표시할 특정 열을 선택할 수 있습니다.
페이지 크기 변경
Azure Mobile Apps는 기본적으로 요청당 최대 50개 항목을 반환합니다. 클라이언트와 서버 모두에서 최대 페이지 크기를 늘려 페이징 크기를 변경할 수 있습니다. 요청된 페이지 크기를 늘리려면 PullAsync()
사용할 때 PullOptions
지정합니다.
PullOptions pullOptions = new PullOptions
{
MaxPageSize = 100
};
서버 내에서 PageSize
100보다 크거나 같은 경우 요청은 최대 100개의 항목을 반환합니다.
오프라인 테이블 작업
오프라인 테이블은 로컬 SQLite 저장소를 사용하여 오프라인일 때 사용할 데이터를 저장합니다. 모든 테이블 작업은 원격 서버 저장소 대신 로컬 SQLite 저장소에 대해 수행됩니다. 오프라인 테이블을 만들려면 먼저 프로젝트를 준비합니다.
- Visual Studio에서 솔루션 >솔루션 관리...솔루션을 마우스 오른쪽 단추로 클릭한 다음 솔루션의 모든 프로젝트에 대해 Microsoft.Azure.Mobile.Client.SQLiteStore NuGet 패키지를 검색하고 설치합니다.
- Windows 디바이스의 경우 참조 추가
참조를 누르고, Windows 폴더확장 확장한 다음, Windows SDK용Visual C++ 2013 런타임과 함께 Windows SDK용 적절한 SQLite를 사용하도록 설정합니다. SQLite SDK 이름은 각 Windows 플랫폼에 따라 약간 다릅니다.
테이블 참조를 만들려면 먼저 로컬 저장소를 준비해야 합니다.
var store = new MobileServiceSQLiteStore(Constants.OfflineDbPath);
store.DefineTable<TodoItem>();
//Initializes the SyncContext using the default IMobileServiceSyncHandler.
await this.client.SyncContext.InitializeAsync(store);
저장소 초기화는 일반적으로 클라이언트를 만든 직후에 수행됩니다. OfflineDbPath 지원하는 모든 플랫폼에서 사용하기에 적합한 파일 이름이어야 합니다. 경로가 정규화된 경로인 경우(즉, 슬래시로 시작) 해당 경로가 사용됩니다. 경로가 정규화되지 않은 경우 파일은 플랫폼별 위치에 배치됩니다.
- iOS 및 Android 디바이스의 경우 기본 경로는 "개인 파일" 폴더입니다.
- Windows 디바이스의 경우 기본 경로는 애플리케이션별 "AppData" 폴더입니다.
GetSyncTable<>
메서드를 사용하여 테이블 참조를 가져올 수 있습니다.
var table = client.GetSyncTable<TodoItem>();
오프라인 테이블을 사용하기 위해 인증할 필요가 없습니다. 백 엔드 서비스와 통신할 때만 인증해야 합니다.
오프라인 테이블 동기화
오프라인 테이블은 기본적으로 백 엔드와 동기화되지 않습니다. 동기화는 두 조각으로 분할됩니다. 변경 내용을 새 항목 다운로드와 별도로 푸시할 수 있습니다. 일반적인 동기화 방법은 다음과 같습니다.
public async Task SyncAsync()
{
ReadOnlyCollection<MobileServiceTableOperationError> syncErrors = null;
try
{
await this.client.SyncContext.PushAsync();
await this.todoTable.PullAsync(
//The first parameter is a query name that is used internally by the client SDK to implement incremental sync.
//Use a different query name for each unique query in your program
"allTodoItems",
this.todoTable.CreateQuery());
}
catch (MobileServicePushFailedException exc)
{
if (exc.PushResult != null)
{
syncErrors = exc.PushResult.Errors;
}
}
// Simple error/conflict handling. A real application would handle the various errors like network conditions,
// server conflicts and others via the IMobileServiceSyncHandler.
if (syncErrors != null)
{
foreach (var error in syncErrors)
{
if (error.OperationKind == MobileServiceTableOperationKind.Update && error.Result != null)
{
//Update failed, reverting to server's copy.
await error.CancelAndUpdateItemAsync(error.Result);
}
else
{
// Discard local change.
await error.CancelAndDiscardItemAsync();
}
Debug.WriteLine(@"Error executing sync operation. Item: {0} ({1}). Operation discarded.", error.TableName, error.Item["id"]);
}
}
}
PullAsync
첫 번째 인수가 null이면 증분 동기화가 사용되지 않습니다. 각 동기화 작업은 모든 레코드를 검색합니다.
SDK는 레코드를 끌어 오기 전에 암시적 PushAsync()
수행합니다.
충돌 처리는 PullAsync()
메서드에서 발생합니다. 온라인 테이블과 동일한 방식으로 충돌을 처리할 수 있습니다. 삽입, 업데이트 또는 삭제하는 동안 대신 PullAsync()
호출될 때 충돌이 발생합니다. 여러 충돌이 발생하면 단일 MobileServicePushFailedException으로 번들로 제공됩니다. 각 오류를 개별적으로 처리합니다.
사용자 지정 API 작업
사용자 지정 API를 사용하면 삽입, 업데이트, 삭제 또는 읽기 작업에 매핑되지 않는 서버 기능을 노출하는 사용자 지정 엔드포인트를 정의할 수 있습니다. 사용자 지정 API를 사용하면 HTTP 메시지 헤더 읽기 및 설정, JSON 이외의 메시지 본문 형식 정의 등 메시징을 더 잘 제어할 수 있습니다.
클라이언트에서 InvokeApiAsync 메서드 중 하나를 호출하여 사용자 지정 API를 호출합니다. 예를 들어 다음 코드 줄은 백 엔드의 completeAll API에 POST 요청을 보냅니다.
var result = await client.InvokeApiAsync<MarkAllResult>("completeAll", System.Net.Http.HttpMethod.Post, null);
이 양식은 형식화된 메서드 호출이며 MarkAllResult 반환 형식을 정의해야 합니다. 형식화된 메서드와 형식화되지 않은 메서드가 모두 지원됩니다.
InvokeApiAsync() 메서드는 API가 '/'로 시작되지 않는 한 호출하려는 API 앞에 '/api/'를 추가합니다. 예를 들어:
-
InvokeApiAsync("completeAll",...)
호출 /api/complete백 엔드의All -
InvokeApiAsync("/.auth/me",...)
백 엔드에서 /.auth/me를 호출합니다.
InvokeApiAsync를 사용하여 Azure Mobile Apps로 정의되지 않은 WebAPIs를 포함하여 모든 WebAPI를 호출할 수 있습니다. InvokeApiAsync()를 사용하면 인증 헤더를 비롯한 적절한 헤더가 요청과 함께 전송됩니다.
사용자 인증
Mobile Apps는 Facebook, Google, Microsoft 계정, Twitter 및 Microsoft Entra ID와 같은 다양한 외부 ID 공급자를 사용하여 앱 사용자를 인증하고 권한을 부여할 수 있도록 지원합니다. 테이블에 대한 사용 권한을 설정하여 특정 작업에 대한 액세스를 인증된 사용자로만 제한할 수 있습니다. 인증된 사용자의 ID를 사용하여 서버 스크립트에서 권한 부여 규칙을 구현할 수도 있습니다.
클라이언트 관리
메모
프로덕션 앱에서 클라이언트 관리 흐름을 사용하는 것이 좋습니다.
인증을 설정하려면 하나 이상의 ID 공급자에 앱을 등록해야 합니다. ID 공급자는 앱에 대한 클라이언트 ID 및 클라이언트 비밀을 생성합니다. 그런 다음, 이러한 값은 Azure App Service 인증/권한 부여를 사용하도록 백 엔드에서 설정됩니다.
이 섹션에서는 다음 항목을 다룹니다.
- 클라이언트 관리 인증
- 서버 관리 인증
- 인증 토큰 캐싱
클라이언트 관리 인증
앱은 ID 공급자에 독립적으로 연락한 다음 백 엔드에 로그인하는 동안 반환된 토큰을 제공할 수 있습니다. 이 클라이언트 흐름을 사용하면 사용자에게 Single Sign-On 환경을 제공하거나 ID 공급자에서 추가 사용자 데이터를 검색할 수 있습니다. ID 공급자 SDK가 좀 더 네이티브 UX 느낌을 제공하고 더 많은 사용자 지정을 허용하기 때문에 클라이언트 흐름 인증은 서버 흐름을 사용하는 것이 좋습니다.
다음 클라이언트 흐름 인증 패턴에 대한 예제가 제공됩니다.
Active Directory 인증 라이브러리를 사용하여 사용자 인증
ADAL(Active Directory 인증 라이브러리)을 사용하여 Microsoft Entra 인증을 사용하여 클라이언트에서 사용자 인증을 시작할 수 있습니다.
경고
ADAL(Active Directory 인증 라이브러리)에 대한 지원은 2022년 12월에 종료됩니다. 기존 OS 버전에서 ADAL을 사용하는 앱은 계속 작동하지만 기술 지원 및 보안 업데이트는 종료됩니다. 자세한 내용은 앱을 MSAL마이그레이션을 참조하세요.
How to Configure App Service for Active Directory 로그인 자습서에 따라 Microsoft Entra 로그온에 대한 모바일 앱 백 엔드를 구성합니다. 네이티브 클라이언트 애플리케이션을 등록하는 선택적 단계를 완료해야 합니다.
Visual Studio에서 프로젝트를 열고
Microsoft.IdentityModel.Clients.ActiveDirectory
NuGet 패키지에 대한 참조를 추가합니다. 검색할 때 시험판 버전을 포함합니다.사용 중인 플랫폼에 따라 애플리케이션에 다음 코드를 추가합니다. 각각에서 다음을 대체합니다.
INSERT-AUTHORITY-HERE 애플리케이션을 프로비전한 테넌트 이름으로 바꿉다. 형식은
https://login.microsoftonline.com/contoso.onmicrosoft.com
합니다. 이 값은 [Azure Portal]의 Microsoft Entra ID에 있는 도메인 탭에서 복사할 수 있습니다.INSERT-RESOURCE-ID-HERE 모바일 앱 백 엔드의 클라이언트 ID로 바꿉니다. 포털의 microsoft Entra 설정
고급 탭에서 클라이언트 ID를 가져올 수 있습니다.INSERT-CLIENT-ID-HERE 네이티브 클라이언트 애플리케이션에서 복사한 클라이언트 ID로 바꿉니다.
INSERT-REDIRECT-URI-HERE HTTPS 체계를 사용하여 사이트의
/.auth/login/done
엔드포인트로 바꿉니다. 이 값은https://contoso.azurewebsites.net/.auth/login/done
유사해야 합니다.각 플랫폼에 필요한 코드는 다음과 같습니다.
Windows:
private MobileServiceUser user; private async Task AuthenticateAsync() { string authority = "INSERT-AUTHORITY-HERE"; string resourceId = "INSERT-RESOURCE-ID-HERE"; string clientId = "INSERT-CLIENT-ID-HERE"; string redirectUri = "INSERT-REDIRECT-URI-HERE"; while (user == null) { string message; try { AuthenticationContext ac = new AuthenticationContext(authority); AuthenticationResult ar = await ac.AcquireTokenAsync(resourceId, clientId, new Uri(redirectUri), new PlatformParameters(PromptBehavior.Auto, false) ); JObject payload = new JObject(); payload["access_token"] = ar.AccessToken; user = await App.MobileService.LoginAsync( MobileServiceAuthenticationProvider.WindowsAzureActiveDirectory, payload); message = string.Format("You are now logged in - {0}", user.UserId); } catch (InvalidOperationException) { message = "You must log in. Login Required"; } var dialog = new MessageDialog(message); dialog.Commands.Add(new UICommand("OK")); await dialog.ShowAsync(); } }
Xamarin.iOS
private MobileServiceUser user; private async Task AuthenticateAsync(UIViewController view) { string authority = "INSERT-AUTHORITY-HERE"; string resourceId = "INSERT-RESOURCE-ID-HERE"; string clientId = "INSERT-CLIENT-ID-HERE"; string redirectUri = "INSERT-REDIRECT-URI-HERE"; try { AuthenticationContext ac = new AuthenticationContext(authority); AuthenticationResult ar = await ac.AcquireTokenAsync(resourceId, clientId, new Uri(redirectUri), new PlatformParameters(view)); JObject payload = new JObject(); payload["access_token"] = ar.AccessToken; user = await client.LoginAsync( MobileServiceAuthenticationProvider.WindowsAzureActiveDirectory, payload); } catch (Exception ex) { Console.Error.WriteLine(@"ERROR - AUTHENTICATION FAILED {0}", ex.Message); } }
Xamarin.Android
private MobileServiceUser user; private async Task AuthenticateAsync() { string authority = "INSERT-AUTHORITY-HERE"; string resourceId = "INSERT-RESOURCE-ID-HERE"; string clientId = "INSERT-CLIENT-ID-HERE"; string redirectUri = "INSERT-REDIRECT-URI-HERE"; try { AuthenticationContext ac = new AuthenticationContext(authority); AuthenticationResult ar = await ac.AcquireTokenAsync(resourceId, clientId, new Uri(redirectUri), new PlatformParameters(this)); JObject payload = new JObject(); payload["access_token"] = ar.AccessToken; user = await client.LoginAsync( MobileServiceAuthenticationProvider.WindowsAzureActiveDirectory, payload); } catch (Exception ex) { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.SetMessage(ex.Message); builder.SetTitle("You must log in. Login Required"); builder.Create().Show(); } } protected override void OnActivityResult(int requestCode, Result resultCode, Intent data) { base.OnActivityResult(requestCode, resultCode, data); AuthenticationAgentContinuationHelper.SetAuthenticationAgentContinuationEventArgs(requestCode, resultCode, data); }
Facebook 또는 Google에서 토큰을 사용하는 Single Sign-On
Facebook 또는 Google에 대한 이 코드 조각에 표시된 대로 클라이언트 흐름을 사용할 수 있습니다.
var token = new JObject();
// Replace access_token_value with actual value of your access token obtained
// using the Facebook or Google SDK.
token.Add("access_token", "access_token_value");
private MobileServiceUser user;
private async Task AuthenticateAsync()
{
while (user == null)
{
string message;
try
{
// Change MobileServiceAuthenticationProvider.Facebook
// to MobileServiceAuthenticationProvider.Google if using Google auth.
user = await client.LoginAsync(MobileServiceAuthenticationProvider.Facebook, token);
message = string.Format("You are now logged in - {0}", user.UserId);
}
catch (InvalidOperationException)
{
message = "You must log in. Login Required";
}
var dialog = new MessageDialog(message);
dialog.Commands.Add(new UICommand("OK"));
await dialog.ShowAsync();
}
}
서버 관리 인증
ID 공급자를 등록한 후에는 공급자의 MobileServiceAuthenticationProvider 값을 사용하여 MobileServiceClient에서 LoginAsync 메서드를 호출합니다. 예를 들어 다음 코드는 Facebook을 사용하여 서버 흐름 로그인을 시작합니다.
private MobileServiceUser user;
private async System.Threading.Tasks.Task Authenticate()
{
while (user == null)
{
string message;
try
{
user = await client
.LoginAsync(MobileServiceAuthenticationProvider.Facebook);
message =
string.Format("You are now logged in - {0}", user.UserId);
}
catch (InvalidOperationException)
{
message = "You must log in. Login Required";
}
var dialog = new MessageDialog(message);
dialog.Commands.Add(new UICommand("OK"));
await dialog.ShowAsync();
}
}
Facebook 이외의 ID 공급자를 사용하는 경우 MobileServiceAuthenticationProvider 값을 공급자의 값으로 변경합니다.
서버 흐름에서 Azure App Service는 선택한 공급자의 로그인 페이지를 표시하여 OAuth 인증 흐름을 관리합니다. ID 공급자가 반환되면 Azure App Service는 App Service 인증 토큰을 생성합니다.
LoginAsync 메서드는 인증된 사용자의 UserId와 MobileServiceAuthenticationToken을 JWT(웹 토큰)로 제공하는 MobileServiceUser반환합니다. 이 토큰은 만료될 때까지 캐시하고 다시 사용할 수 있습니다. 자세한 내용은 인증 토큰
메모
Azure Mobile Apps는 Xamarin.Essentials WebAuthenticator를 사용하여 작업을 수행합니다. Xamarin.Essentials로 다시 호출하여 서비스의 응답을 처리해야 합니다. 자세한 내용은 WebAuthenticator참조하세요.
인증 토큰 캐싱
경우에 따라 공급자의 인증 토큰을 저장하여 첫 번째 인증 후에 로그인 메서드 호출을 방지할 수 있습니다. Microsoft Store 및 UWP 앱은 다음과 같이 PasswordVault 사용하여 로그인에 성공한 후 현재 인증 토큰을 캐시할 수 있습니다.
await client.LoginAsync(MobileServiceAuthenticationProvider.Facebook);
PasswordVault vault = new PasswordVault();
vault.Add(new PasswordCredential("Facebook", client.currentUser.UserId,
client.currentUser.MobileServiceAuthenticationToken));
UserId 값은 자격 증명의 UserName으로 저장되고 토큰은 암호로 저장됩니다. 후속 시작 시 PasswordVault 캐시된 자격 증명을 확인할 수 있습니다. 다음 예제에서는 캐시된 자격 증명을 찾았을 때 사용하고, 그렇지 않으면 백 엔드를 사용하여 다시 인증을 시도합니다.
// Try to retrieve stored credentials.
var creds = vault.FindAllByResource("Facebook").FirstOrDefault();
if (creds != null)
{
// Create the current user from the stored credentials.
client.currentUser = new MobileServiceUser(creds.UserName);
client.currentUser.MobileServiceAuthenticationToken =
vault.Retrieve("Facebook", creds.UserName).Password;
}
else
{
// Regular login flow and cache the token as shown above.
}
사용자를 로그아웃할 때 다음과 같이 저장된 자격 증명도 제거해야 합니다.
client.Logout();
vault.Remove(vault.Retrieve("Facebook", client.currentUser.UserId));
클라이언트 관리 인증을 사용하는 경우 Facebook 또는 Twitter와 같은 공급자에서 가져온 액세스 토큰을 캐시할 수도 있습니다. 이 토큰은 다음과 같이 백 엔드에서 새 인증 토큰을 요청하도록 제공할 수 있습니다.
var token = new JObject();
// Replace <your_access_token_value> with actual value of your access token
token.Add("access_token", "<your_access_token_value>");
// Authenticate using the access token.
await client.LoginAsync(MobileServiceAuthenticationProvider.Facebook, token);
기타 항목
오류 처리
백 엔드에서 오류가 발생하면 클라이언트 SDK에서 MobileServiceInvalidOperationException
발생합니다. 다음 예제에서는 백 엔드에서 반환되는 예외를 처리하는 방법을 보여 줍니다.
private async void InsertTodoItem(TodoItem todoItem)
{
// This code inserts a new TodoItem into the database. When the operation completes
// and App Service has assigned an ID, the item is added to the CollectionView
try
{
await todoTable.InsertAsync(todoItem);
items.Add(todoItem);
}
catch (MobileServiceInvalidOperationException e)
{
// Handle error
}
}
요청 헤더 사용자 지정
특정 앱 시나리오를 지원하려면 모바일 앱 백 엔드와의 통신을 사용자 지정해야 할 수 있습니다. 예를 들어 보내는 모든 요청에 사용자 지정 헤더를 추가하거나 응답 상태 코드를 변경할 수 있습니다. 다음 예제와 같이 사용자 지정 DelegatingHandler사용할 수 있습니다.
public async Task CallClientWithHandler()
{
MobileServiceClient client = new MobileServiceClient("AppUrl", new MyHandler());
IMobileServiceTable<TodoItem> todoTable = client.GetTable<TodoItem>();
var newItem = new TodoItem { Text = "Hello world", Complete = false };
await todoTable.InsertAsync(newItem);
}
public class MyHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage>
SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
// Change the request-side here based on the HttpRequestMessage
request.Headers.Add("x-my-header", "my value");
// Do the request
var response = await base.SendAsync(request, cancellationToken);
// Change the response-side here based on the HttpResponseMessage
// Return the modified response
return response;
}
}
요청 로깅 사용
DelegatingHandler를 사용하여 요청 로깅을 추가할 수도 있습니다.
public class LoggingHandler : DelegatingHandler
{
public LoggingHandler() : base() { }
public LoggingHandler(HttpMessageHandler innerHandler) : base(innerHandler) { }
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken token)
{
Debug.WriteLine($"[HTTP] >>> {request.Method} {request.RequestUri}");
if (request.Content != null)
{
Debug.WriteLine($"[HTTP] >>> {await request.Content.ReadAsStringAsync().ConfigureAwait(false)}");
}
HttpResponseMessage response = await base.SendAsync(request, token).ConfigureAwait(false);
Debug.WriteLine($"[HTTP] <<< {response.StatusCode} {response.ReasonPhrase}");
if (response.Content != null)
{
Debug.WriteLine($"[HTTP] <<< {await response.Content.ReadAsStringAsync().ConfigureAwait(false)}");
}
return response;
}
}