블로그 이미지
* Microsoft MVP - Windows Development 2014 ~ 2019 5ring * LINE : kaki104 * facebook : https://www.facebook.com/kaki104 https://www.facebook.com/groups/w10app/ kaki104

카테고리

List All (551)
Xamarin Forms (4)
Bot Framework (19)
Azure (9)
Windows 10 (35)
Facebook News & Tips (158)
Windows App(Universa.. (83)
Windows 8&8.1 (113)
Windows Phone 8 (42)
Silverlight (37)
HTML5 & MVC4 (16)
WPF (1)
Portable Class Library (2)
Uncategorised Tips a.. (3)
Kinect for Windows (2)
ETC (12)
kaki104 Scrap (4)
App News (11)
Total521,318
Today37
Yesterday108

에저 모바일 서비스 관련 링크 모음 입니다.


Logging with the .NET backend for Azure Mobile Services

http://blogs.msdn.com/b/azuremobile/archive/2014/04/24/logging-with-the-net-backend-for-azure-mobile-services.aspx


Quickstart: Sending a toast notification (XAML)

https://msdn.microsoft.com/en-us/library/windows/apps/xaml/Hh868254%28v=win.10%29.aspx?f=255&MSPPError=-2147217396


빠른 시작: 알림 메시지 보내기(XAML)

https://msdn.microsoft.com/ko-kr/library/windows/apps/xaml/Hh868254%28v=win.10%29.aspx?f=255&MSPPError=-2147217396


알림 메시지에서 활성화를 처리하는 방법(XAML)

https://msdn.microsoft.com/ko-kr/library/windows/apps/xaml/hh868212.aspx


Posted by MVP kaki104

[Azure Mobile Service] Raw Push Notification, Local Database Sync

 

이 방대한 작업 작은 이유에서 시작되었다.

즐겨 찾기 구현과와 최근 항목 구현하기.. 어느날 이 두가지를 구현해야지~하는 생각으로 작업을 시작했는데.. 처음에는 단순하게 Roaming Storage를 이용해서 공유를 하면 되겠구나..라고 생각했다. 그래서 이레 저레 작업이 대충 마무리가 되어서 테스트를 했는데..윈폰과 윈8 사이의 싱크가 바로 바로 되지 않는다..

 

그래서, 그 때가지 작업 했던 내용을 주석처리하고, AMS에 즐겨찾기, 최근항목을 저장하고, 추가/삭제시 Raw Push Notification을 발생 시켜서 윈폰과 윈8간에 동기화를 하도록 계획을 변경했다.

 

음 그래서 이런 저런 내용을 작업하다보니..거의 일주일? 정도 걸린듯하다..쿨럭

 

 

1. 참고자료

 

종인님 코맨트

https://www.facebook.com/groups/vapps/

 

알림 허브 개요

http://msdn.microsoft.com/ko-kr/library/jj927170.aspx

 

Getting Started with Notification Hubs

http://azure.microsoft.com/en-us/documentation/articles/notification-hubs-windows-store-dotnet-get-started/

 

 

 

2. .Net Backend 에서 Raw Push Notification 보내기

 

이미 내가 만들어 놓은 서비스에서 간단하게 Push Notification을 사용하고 있었다.

 

 

        // POST tables/HstCommentDTO
        public async Task<IHttpActionResult> PostHstCommentDTO(HstCommentDTO item)
        {
            HstCommentDTO current = await InsertAsync(item);
            //프로시저 실행
            ExecUpdate_UserAndFolderTotalInformation(item.FolderId);

            //Push 대상, 폴더 등록자, 코맨트에 부모가 있을 경우 부모 등록자, 두경우 모두 등록자 자신이면 제외

            // Create a WNS native toast.
            var message = new WindowsPushMessage
            {
                XmlPayload = @"<?xml version=""1.0"" encoding=""utf-8""?>" +
                             @"<toast><visual><binding template=""ToastText01"">" +
                             @"<text id=""1"">" + item.ValueDesc + @"</text>" +
                             @"</binding></visual></toast>"
            };

            // Define the XML paylod for a WNS native toast notification
            // that contains the text of the inserted item.
            try
            {
                var currentUser = User as ServiceUser;
                if (currentUser != null)
                {
                    var result = await Services.Push.SendAsync(message, string.Format("!{0}", currentUser.Id));
                    Services.Log.Info(result.State.ToString());
                }
            }
            catch (System.Exception ex)
            {
                Services.Log.Error(ex.Message, null, "Push.SendAsync Error");
            }

            return CreatedAtRoute("Tables", new {id = current.Id}, current);
        }

 

 

기본 예제를 이용해서 만들다 보면 작성하는 코드로 특별한 내용은 없다. 그런데, 알림 허브 개요(Notification Hubs Overview) 페이지를 보니 이렇게 보내는 Push는 아래의 그림과 같은 구조인 듯하다.

 

 

 

.Net Backend service에서 직접 PNS 서버에 Push를 요청하는 구조이며, Raw Push Notification을 요청하는 소스는 아래와 같다.

 

 

        private async void RawPushNotification(string tags, string type, string action, string data)

        {
            if (string.IsNullOrEmpty(tags) || string.IsNullOrEmpty(type) || string.IsNullOrEmpty(action) || string.IsNullOrEmpty(data)) return;

 

            //Raw push notification data create
            var datas = new Dictionary<string, string> { { "type", type }, { "action", action }, { "data", data } };
            var message = new WindowsPushMessage
            {
                XmlPayload = JsonConvert.SerializeObject(datas)
            };
            message.Headers.Add("X-WNS-Type", "wns/raw");
            try
            {
                var result = await Services.Push.SendAsync(message, tags);
                Services.Log.Info(result.State.ToString());
            }
            catch (System.Exception ex)
            {
                // ReSharper disable once ExplicitCallerInfoArgument
                Services.Log.Error(ex.Message, null, string.Format("tags:{0},type:{1},action:{2},data:{3}", tags, type, action, data));
            }
        }

 

 

대충 이런 메소드를 각 컨트롤러에 넣어 놓고 보내는 연습을 했는데 잘 동작하는 하는 듯해서..넘어가려고 했는데, 종인님이 알려준 내용을 다시 찾아보니,

 

 

 

이런 구조를 가지도록 만드는 것이 더 좋다는 것을 알게되었다. 으흠.. 그래서, 다시 아래 내용 처럼 PushHelper를 만들게 되었다.

 

 

    public class PushHelper
    {
        private static readonly NotificationHubClient HubClient = NotificationHubClient.CreateClientFromConnectionString
            (
                "<full-access endpoint>",
                "<hub name>");

        /// <summary>
        /// RawPushNotification
        /// </summary>
        /// <returns></returns>
        public static async Task RawPushNotification(string tags, string type, string action, string data)
        {
            if (string.IsNullOrEmpty(tags) || string.IsNullOrEmpty(type) || string.IsNullOrEmpty(action) ||
                string.IsNullOrEmpty(data)) return;

            //Raw push notification data
            var datas = new Dictionary<string, string> {{"type", type}, {"action", action}, {"data", data}};
            string payload = JsonConvert.SerializeObject(datas);
            var message = new WindowsNotification(payload, new Dictionary<string, string>
            {
                {"X-WNS-Type", "wns/raw"}
            });
            try
            {
                NotificationOutcome result = await HubClient.SendNotificationAsync(message, tags);
                if (result.Results != null && result.Results.Any())
                {
                }
                //Services.Log.Info(result.State.ToString());
            }
            catch (Exception ex)
            {
                // ReSharper disable once ExplicitCallerInfoArgument
                //Services.Log.Error(ex.Message, null, string.Format("tags:{0},type:{1},action:{2},data:{3}", tags, type, action, data));
                Debug.WriteLine(ex.Message);
            }
        }
    }

 

대략 Push만 가능한 상태의 Push 전용 메소드이다. 약간 아쉬운것은 Log를 남기는 방법을 아직 몰라서 로그를 남길 수 없다는 것이다.

 

이렇게 전용 메소드를 만들고, 각 컨트롤러도 수정을 했다.

 

 

        // POST tables/HstUserFavoriteDTO
        public async Task<IHttpActionResult> PostHstUserFavoriteDTO(HstUserFavoriteDTO item)
        {
            HstUserFavoriteDTO current = await InsertAsync(item);
            var currentUser = User as ServiceUser;
            await PushHelper.RawPushNotification(currentUser.Id, "HstUserFavoriteDTO", "POST", item.Id);
            return CreatedAtRoute("Tables", new {id = current.Id}, current);
        }

        // DELETE tables/HstUserFavoriteDTO/48D68C86-6EA6-4C25-AA33-223FC9A27959
// ReSharper disable once InconsistentNaming
        public async Task DeleteHstUserFavoriteDTO(string id)
        {
            //삭제할 아이템의 존재 여부 확인, 존재하지 않으면 삭제하지 않음, 노티도 발생시키지 않음
            SingleResult<HstUserFavoriteDTO> deleteItem = Lookup(id);
            if (!deleteItem.Queryable.Any()) return;

            await DeleteAsync(id);
            var currentUser = User as ServiceUser;
            await PushHelper.RawPushNotification(currentUser.Id, "HstUserFavoriteDTO", "DELETE", id);
            //return DeleteAsync(id);
        }

 

 

PushHelper.RawPushNotification 메소드 호출은 반드시 await를 붙여 주어야한다. 그러다 보니, Delete 웹 메소드 부분이 여러가지 변화가 있는데, async로 변경을 하고, DeleteAsync(id)와 PushHelper.RawPushNotification 모두 await를 붙여 주는 것이다.

 

 

앞으로도 PushHelper는 계속 이런 저런 기능을 추가해 나가야 할 것 같다.

 

Posted by MVP kaki104

이번 포스트는 나 빼고 다른 모든 사람들에게 노티를 보내기!를 구현하기 위한 가장 마지막 단계인것 같다.

이 내용에 대한 작업을 하기 위해 Notification Hubs에 대한 문서를 간단하게 읽어 보아야 한다.

 

 

1. 참고

알림 허브 기능(한글)

http://msdn.microsoft.com/ko-kr/library/dn530752.aspx

 

Notification Hubs Features

http://msdn.microsoft.com/en-us/library/dn530752.aspx

 

위의 링크로 이동해서 일단 어떤 개념인지에 대한 파악을 한다.

 

 

2. 특히 중요한 부분은

 

Routing and Tag Expressions

http://msdn.microsoft.com/en-us/library/dn530749.aspx

 

이 부분이다.

 

 

앱이 실행이 되면 PNS 서버로 부터 자신을 가르키는 주소를 받아 온다. 받아온 주소를 Notification Hubs에 등록을 하게 되는데, 이때, 사용자 인증이 되어 있다면, 그 정보를 Tag로 사용 한다. 이 내용은 조금전에 소스를 통해서 알게 되었는데. 다음과 같이 확인 하면 된다.

 

 

 

 

RegisterNativeAsync를 실행한 후에 자신이 등록되어 있는 서비스를 조회하면 1개의 WnsRegistration이 나오는데, 그 안에 Tags에 자신의 인증에 사용된 ID가 나오는 것을 알 수 있다.

 

**********

서버단에 PushRegistrationHandler 코드에 사용자 ID를 Tag로 등록하는 곳이 있다.

ValidateTags 메소드에 custom:을 추가해서 사용자 정의 인증된 사용자도 확인이 가능하도록 한다.

 

    public class PushRegistrationHandler : INotificationHandler
    {
        public Task Register(ApiServices services, HttpRequestContext context, NotificationRegistration registration)
        {
            try
            {
                // Perform a check here for user ID tags, which are not allowed.
                if (!ValidateTags(registration))
                {
                    throw new InvalidOperationException(
                        "You cannot supply a tag that is a user ID.");
                }

                // Get the logged-in user.
                var currentUser = context.Principal as ServiceUser;

 

                // Add a new tag that is the user ID.
                if (currentUser != null)
                {
                    registration.Tags.Add(currentUser.Id);
                    services.Log.Info("Registered tag for userId: " + currentUser.Id);
                }
            }
            catch (Exception ex)
            {
                services.Log.Error(ex.ToString());
            }
            return Task.FromResult(true);
        }

        public Task Unregister(ApiServices services, HttpRequestContext context, string deviceId)
        {
            // This is where you can hook into registration deletion.
            return Task.FromResult(true);
        }

 

        private bool ValidateTags(NotificationRegistration registration)
        {
            // Create a regex to search for disallowed tags.
            var searchTerm =
            new System.Text.RegularExpressions.Regex(@"facebook:|google:|twitter:|microsoftaccount:|custom:",
                System.Text.RegularExpressions.RegexOptions.IgnoreCase);

            foreach (string tag in registration.Tags)
            {
                if (searchTerm.IsMatch(tag))
                {
                    return false;
                }
            }
            return true;
        }
    }

 

 

그럼, 이 이야기는 사용자 인증이 완료된 클라이언트가 Notification Hubs에 등록이 되면 기본적으로 자신의 id를 Tag로 가지고 있다는 것이니, Push를 날릴때 자기 자신을 지칭하는 Tag만 제외 시키면 되는 것이다.

 

 

즉,

 

* 서버단에서 Push.SendAsync를 하는 곳

 

            try
            {
                var currentUser = User as ServiceUser;
                if (currentUser != null)
                {
                    var result = await Services.Push.SendAsync(message, string.Format("!{0}", currentUser.Id));
                    Services.Log.Info(result.State.ToString());
                }
            }
            catch (System.Exception ex)
            {
                Services.Log.Error(ex.Message, null, "Push.SendAsync Error");
            }

 

서버단의 Azure Mobile Service에서 서비스를 호출한 사람이 currentUser이니, Tag Expression

string.Format("!{0}", currentUser.Id) 이렇게 붙인다면, !custom:f2f0fc0cxxxxxxxxx가 되는 것이다.

즉, 자신의 테그를 제외한 나머지에게 발송을 하라는 뜻이 된다.

 

 

이렇게 해서 나 제외한 다른 모든 사람에게 Push를 보내는 것을 완료했다.

 

Posted by MVP kaki104

에저 삽질 3탄!! 이번엔 푸쉬 노티피케이션을 구현하다가, 시작된 인증 서비스 구현 편이다. 지난번 포스트까지만 해도 괜찬았는데..후후후

 

 

1. 문제의 발생

기본 인증 방법 중에 Microsoft Account(Live ID)를 이용한 인증을 구현하고 테스트 까지 완료.

음 이정도면 할만하네..라고 생각하며 다음 과정까지 작업

 

여기서 내 앱은 원드라이브를 이용하기 때문에 기본적으로 Microsoft Account 인증을 한번 받음, 그리고, 매번 앱이 시작될 때마다 인증과정을 거친 후에 서비스를 인증하도록 변경함

 

서비스 인증을 할 때

 

 

_user = await App.MobileService.LoginAsync(MobileServiceAuthenticationProvider.MicrosoftAccount, true);

 

 

이렇게 입력해서, SSO 사용을 하도록 변경하고, Windows 8.1 store app을 실행 했을 때는 바로 인증이 완료됨, 그런데 Windows Phone 8.1 rt에서 문제 발생

 

 

이런 로그인 화면이 앱을 시작 할 때 마다 떠서 로그인을 해주어야 한다는 것!!

 

이런걸 어떻게 쓰란 말이냐.. 나 같아도 매번 로그인 해야하는 앱은 쓰기 싫은데..쿨럭..으흠

 

 

2. 사용자 정의 인증 구현하기

 

우선 이런 내용을 종인님에게 물어보니,

 

"아 모바일 서비스 인증 때문에 그런거에요? 전 저런것 때문에 그냥 자체인증 써서 잘 모르는 듯요 ㅋ"

 

라는 친절한 답변을 날려주었다. 그래서 이때부터 사용자 정의 인증 구현하기 시작

 

 

일단 매우 친절하게 되어 있는 서버단 서비스 구현에 대한 포스트이다.

 

Get started with custom authentication

http://azure.microsoft.com/en-us/documentation/articles/mobile-services-dotnet-backend-get-started-custom-authentication/

 

내용 중

 

config.SetIsHosted(true);

 

를 입력해서 로컬 호스트에서 테스트 할 때 사용하라고 하는 옵션은 추가하지 않아도, 로컬에서 테스트 할 수 있다.

이 글은 꼭 읽어야한다. 그런데, 서버단에서 하는 내용만 있고, 클라이언트 단에서 어떻게 하라는 이야기는 없다는 것이 함정이다;;

 

 

클라이언트 구현에 대해서 검색을 해봤는데 좀 지난 내용만 존재하고 최신 내용은 찾지를 못했다. 검색 기술이 떨어지는 가보다;;

 

그럼 어떻게 구성해야하는지 하나하나 살펴 보자

 

 

3. API Settings -> Redirect URLs : Clear

 

이전 Microsoft Account 인증 때문에 입력해 놓았단, Redirect URLs를 지워준다. 설정하지 않은 사람은 여기 들어올 필요도 없다. 기본이 빈값이니;;

 

 

 

4. Client AuthenticateAsync

 

** HttpMethod : System.Net.Http.HttpMethod

 

        public async Task AuthenticateAsync()
        {
            int retryCount = 0;

            while (_user == null)
            {
                string message;
                try
                {
                    var login = new JObject
                    {
                        {"username", "*login user name input*"},
                        {"password", "*login user password input*"}
                    };

                    JToken result =
                        await App.MobileService.InvokeApiAsync("CustomLogin", login, HttpMethod.Post, null);

                    _user = new MobileServiceUser(result["user"]["userId"].ToString())
                    {
                        MobileServiceAuthenticationToken = result["authenticationToken"].ToString()
                    };

                    App.MobileService.CurrentUser = _user;
                    message = string.Format("You are now signed in - {0}", _user.UserId);
                }
                catch (InvalidOperationException ioe)
                {
                    switch (ioe.Message)
                    {
                        case "Invalid username or password":
                            message = ioe.Message;
                            break;
                        default:
                            message = "You must log in. Login Required";
                            break;
                    }
                    retryCount++;
                }
                catch (Exception ex)
                {
                    message = ex.Message;
                }

                Debug.WriteLine(message);

 

                if (retryCount > 1)
                {
                    await EtcHelper.Instance.MsgBoxAsync("Service login fail. exit app");
                    Application.Current.Exit();
                }
            }
        }

 

 

5. Client CustomRegistration

 

public async Task RegistrationAsync()

{

                    //사용자 등록
                    var newUser = new Dictionary<string, string>
                    {
                        {"username", "*registration user name input*"},
                        {"password", "*registration user password input*"},
                    };


                    string json = JsonConvert.SerializeObject(newUser);
                    var content = new StringContent(json);
                    content.Headers.ContentLength = json.Length;
                    content.Headers.ContentType = new MediaTypeHeaderValue("application/json");

                    try
                    {
                        HttpResponseMessage result = await App.MobileService.InvokeApiAsync("CustomRegistration", content, HttpMethod.Post, null, null);
                        if (!result.IsSuccessStatusCode)
                        {
                            //error
                        }
                    }
                    catch (InvalidOperationException ex)
                    {
                        Debug.WriteLine(ex.Message);
                    }

}

 

 

짜잔!!!!! 기본 골격은 이렇다. 물론 내 서비스에서 사용하는 사용자 정보는 좀 복잡한 내용을 가지고 있어서, 기본 셈플에서 제공하는 모델 형태를 사용해서, 사용자 등록을 하는 코드로 살짝 변경했다.

 

사용자 인증 후에 서비스 호출까지 정상적으로 잘 동작한다.. 문제는 이제 다시 푸쉬 노티피케이션을 해야한다는 것이다;;

푸쉬에서 삽질하면 다시 포스팅 하도록 하겠다.

 

Posted by MVP kaki104

Push Notification을 달고나니 문제가 내가 등록한 코맨트에 대해서 나한테 노티를 날리는 상황이 발생!

그래서, 좀더 파고 들어 가려고 하니, 인증이 튀어 나온다..쿠궁..

 

다행인것은 여러가지 인증가능한 서비스들을 제공하고 있다는 것인데..음..

이것이 너무 자주 바뀌니까 뭐가 뭔지 찾기가 쉽지 않다. 쿨럭

 

 

1. 인증 추가하기 기본 문서

http://azure.microsoft.com/en-us/documentation/articles/mobile-services-dotnet-backend-windows-store-dotnet-get-started-users/

이 문서대로 작업을 진행하다가, 마이크로소프트 계정 인증인지, 페이스북 인증인지에 따라 Redirect URL이 다르니 찾아서 작업 해야한다.

 

 

2. 마이크로소프트 계정 인증을 위한 기본 작업

Register your apps to use a Microsoft Account login

http://azure.microsoft.com/en-us/documentation/articles/mobile-services-how-to-register-microsoft-authentication/

 

 

3. 인증관련 오류 발생시 대처법

Troubleshooting authentication issues in Azure Mobile Services

http://blogs.msdn.com/b/carlosfigueira/archive/2012/10/23/troubleshooting-authentication-issues-in-azure-mobile-services.aspx

 

Authentication - Only HTTPS scheme is allowed

http://stackoverflow.com/questions/27141441/authentication-only-https-scheme-is-allowed/27527915#27527915

 

 

4. 기타 여러가지 정보 확인 및 입력해야하는 곳

 

에저 포탈

https://manage.windowsazure.com

 

앱 개발자 센터

https://appdev.microsoft.com

 

마이크로소프트 계정 정보

https://account.live.com

 

 

 

5. 마이크로소프트 계정으로 인증하기

 

앱이 이미 Live Id로 로그인을 하기 때문에 재인증을 하지 않기 위해 true를 붙여줌

 

        public async Task AuthenticateAsync()
        {
            int retryCount = 0;
            while (_user == null)
            {
                string message;
                try
                {
                    _user = await App.MobileService.LoginAsync(MobileServiceAuthenticationProvider.MicrosoftAccount, true);
                    message = string.Format("You are now signed in - {0}", _user.UserId);
                }
                catch (InvalidOperationException ex)
                {
                    message = "You must log in. Login Required";
                    retryCount++;
                }
                Debug.WriteLine(message);

                if (retryCount > 2)
                {
                    await EtcHelper.Instance.MsgBoxAsync("Service login fail. exit app");
                    Application.Current.Exit();
                }
            }
        }

 

 

이 화면 나오다가 인증 완료되면 자동으로 꺼지내용..음..이화면 자체도 않나올 수 없을려나..쿨럭

 

큭..인증 붙이다가 중간에 오류 처리한다고 저녁 시간 다 보내고.;;;

내일은 1번 문서의 중간부터 계속 해야겠당..음냐

 

 

아래 내용은 서비스;;

노티피케이션 등록할 때 시물레이터로 돌리면 오류가 발생하는데 방지할 수 있는 코드 추가

 

        /// <summary>
        /// 노티피케이션 등록
        /// </summary>
        private async void InitNotificationsAsync()
        {
            if (Windows.System.RemoteDesktop.InteractiveSession.IsRemote) return;

            // Request a push notification channel.
            var channel = await PushNotificationChannelManager
                .CreatePushNotificationChannelForApplicationAsync();

            // Register for notifications using the new channel
            Exception exception = null;
            try
            {
                await MobileService.GetPush().RegisterNativeAsync(channel.Uri);
            }
            catch (Exception ex)
            {
                exception = ex;
            }
            if (exception != null)
            {
                var dialog = new MessageDialog(exception.Message, "Registering Channel URI");
                dialog.Commands.Add(new UICommand("OK"));
                await dialog.ShowAsync();
            }
        }

 

 

 

6. 사용자 정의 인증 추가하기

으흐흐;; 어제 추가한 사용자 인증이 윈도 앱에서는 자동으로 인증이 처리가 되는데, 윈폰에서는 인증 페이지를 매번 호출을 하고 있다. 그래서, 사용자 정의 인증으로 가닥을 잡았다.

 

Get started with custom authentication

http://azure.microsoft.com/en-us/documentation/articles/mobile-services-dotnet-backend-get-started-custom-authentication/

Posted by MVP kaki104

에저 모바일 서비스에서는 기본적으로 한번 조회시에 50의 레코드만을 조회하도록 설정되어 있다.

하지만, 한번에 내가 원하는 모든 데이터를 조회하는 것이 필요해서, 찾아보다가 아래 포스트를 발견해서 사용해 보았는데 매우 만족스러운 결과를 보여준다.

 

참고 포스트

Retrieving more data from Azure Mobile Services using paging and LoadAllAsync extension

http://blogs.msdn.com/b/kevinash/archive/2013/02/13/retrieving_2d00_more_2d00_data_2d00_from_2d00_azure_2d00_mobile_2d00_services_2d00_using_2d00_paging.aspx?CommentPosted=true#commentmessage

 

자세한 설명은 하지 않도록 하겠다. 다만, 약간의 수정이 필요하기 때문에 포스트를 하게 되었다. 그리고, 이 내용으로 확장 메소드를 만들면 사용하기가 매우 편리하다.

 

 

        public static async Task<List<T>> LoadAllAsync<T>(this IMobileServiceTableQuery<T> table, int bufferSize = 1000)
        {
           
            IMobileServiceTableQuery<T> query = table.IncludeTotalCount();
            IEnumerable<T> results = await query.ToEnumerableAsync();
            long count = ((ITotalCountProvider) results).TotalCount;
            if (results != null && count > 0)
            {
                var updates = new List<T>();
                while (updates.Count < count)
                {
                    List<T> next = await query.Skip(updates.Count).Take(bufferSize).ToListAsync();
                    updates.AddRange(next);
                }
                return updates;
            }

            return null;
        }

 

위의 소스 코드 중 굵은 글씨 부분이 포스트와 다른 부분이다. 이전 버전 SDK에는 존재 했던 것 같은지 현재는 위의 이름으로 사용해야 한다.

 

 

실제 사용

                var allCodes = await PublicHelper.Instance.CodeTable
                    .Where(p => p.ClassName == "MainCategory" || p.ClassName == "SubCategory")
                    .LoadAllAsync();

 

 

테이블 쿼리를 만든 후 마지막에 LoadAllAsync()를 붙여 주기만 하면 쿼리 조건에 해당하는 모든 데이터를 불러 올 수 있다.

 

Posted by MVP kaki104

에저 모바일 서비스를 사용하면 여러가지 에러를 만나게 되는데..

이 에러가 간단하게 한줄로 나오는 것이 아니라 장문의 에러메시지를 일일이 찾아서 봐야한다.

 

에러1)

Multiple object sets per type are not supported. The object sets 'HstComments' and 'HstCommentDTOes' can both contain instances of type 'OneSearch.Service.DataObjects.HstCommentDTO'.

 

->

 

Controler를 추가하는 과정에서 Context에 1개가 더 추가가 되어서 발생

http://stackoverflow.com/questions/3560091/entity-framework-4-code-only-error-multiple-objects-sets-per-type-are-not-suppo

 

 

에러2)

The conversion of a datetime2 data type to a datetime data type resulted in an out-of-range value.\r\nThe statement has been terminated.

 

-> datetime 필드에 적절한 데이터를 넣지 않아서 발생하는 오류

Posted by MVP kaki104

애제를 이용해서 테스트를 진행하는데, 또 다른 문제가 발생했다.

서비스 프로젝트에서 사용한 테이블 클래스 명은 TodoItem

클라이언트 프로젝트에서 사용한 모델 클래스 명은 TodoItemM

이렇게 이름이 다른 경우 아래 셈플 코드대로 작업이 불가능하다.

 

 

 

todoTable = App.MobileService.GetTable<TodoItemM>();
TodoItemMs = await todoTable.ToCollectionAsync();

 

 

그래서 찾아본 것이 string으로 가지고 오는 방법인데..이 경우에는 사용 방법이 복잡해 질 것 같다.

 

 

var table = App.MobileService.GetTable("TodoItem");
var result = await table.ReadAsync("");

 

 

우훔..그래서 다시 구글링을 해서 아래 내용을 찾아냈다.

 

http://stackoverflow.com/questions/17280228/azure-mobile-json-net-map-class-to-table-with-different-name

 

클래스 이름 위에 아래와 같은 attribute를 추가하면 클래스가 사용하는 테이블 이름을 변경할 수 있다고 한다.

[DataTable("TodoItem")]

public class TodoItemM : BindableBase

{...}

 

여기서 간단히 끝났으면 참 좋은데, 난 여기서 한가지 더 고민이 있었다.

DataTable은 Microsoft.WindowsAzure.MobileServices에 속한 attribute이다. 그런데, 내가 만들고 있는 프로젝트는 기본적으로 모든 모델을 PCL에 넣어 놓고 있는데, PCL에는 Azure Mobile Service용 Nuget package를 추가할 수 없다는 것이다.

 

처음에는 partial 클래스로 만들어서 사용해 볼려고 했지만, 실패했다. 그래서, Wrap클래스를 만들어서 사용하기로 했다.

 

최종 결과는 아래와 같다.

 

 

1. PCL project Model

 

namespace CrossPlatform.Universal.PCL.Models
{
    public class TodoItemM : BindableBase
    {
        private string _text;
        private bool _complete;
        public string Id { get; set; }

        [JsonProperty(PropertyName = "text")]
        public string Text
        {
            get { return _text; }
            set { SetProperty(ref _text, value); }
        }

        [JsonProperty(PropertyName = "complete")]
        public bool Complete
        {
            get { return _complete; }
            set { SetProperty(ref _complete, value); }
        }
    }
}

 

 

2. Universal shared project Model

 

namespace OneSearch.Universal.Models
{
    [DataTable("TodoItem")]
    public partial class TodoItemMWrap : TodoItemM
    {
    }
}

 

 

3. Universal shared project ViewModel

 

private IMobileServiceTable<TodoItemMWrap> _todoTable = App.MobileService.GetTable<TodoItemMWrap>();

 

private MobileServiceCollection<TodoItemMWrap, TodoItemMWrap> _todoItemMs;

public MobileServiceCollection<TodoItemMWrap, TodoItemMWrap> TodoItemMs
{
    get { return _todoItemMs; }
    set { this.RaiseAndSetIfChanged(ref _todoItemMs, value); }
}

 

 

private async void RefreshTodoItems()
{
    MobileServiceInvalidOperationException exception = null;
    try
    {
        TodoItemMs = await _todoTable.ToCollectionAsync();
    }
    catch (MobileServiceInvalidOperationException e)
    {
        exception = e;
    }

    if (exception != null)
    {
        await new MessageDialog(exception.Message, "Error loading items").ShowAsync();
    }
}

 

 

 

 

 

Posted by MVP kaki104

요즘 애저 모바일 서비스를 만지작 만지작 하고 있다. 과거에 여러개의 서비스를 구현해서 사용해 보았기 때문에 두려워 하지 않고 도전. 그러나, 결과는 처음부터 아래의 오류 발생으로 인해 몇시간을 헤매고 있었다.

 

처음에 구글에 에러 메시지 부분을 잘못 넣었는지 원하는 결과를 찾을 수 없어서, 페이스북 그룹에 도움을 요청해서 여러가지 이야기를 들었으나 해결이 않되어 좌절하고 있었다. 그러다가, 에러 메시지를 다시 보니 아래 굵은 글씨 부분이 눈에 들어 오는 것이다. 아...음..이름이 뭔가 잘못되었구나..

 

 

{"message":"An error has occurred.","exceptionMessage":"The database name 'TR_OneSearch_Service]_TodoItems_InsertUpdateDelete' is invalid. Database names must be of the form [<schema_name>.]<object_name>.","exceptionType":"System.ArgumentException","stackTrace":" at System.Data.Entity.SqlServer.Utilities.DatabaseName.Parse(String name)\r\n at System.Data.Entity.SqlServer.SqlServerMigrationSqlGenerator.Name(String name)\r\n at Microsoft.WindowsAzure.Mobile.Service.Tables.EntityTableSqlGenerator.GetTriggerName(String tableName)\r\n at Microsoft.WindowsAzure.Mobile.Service.Tables.EntityTableSqlGenerator.GetTrigger(String tableName, String idColumnName, String updatedAtColumnName)\r\n at

....

 

그래서 다시 구글링에 들어가니 아래의 질문이 눈에 들어온다.

 

http://stackoverflow.com/questions/25647447/developing-with-azure-mobile-services-in-a-local-computer-net-backend

 

내용을 대충 읽어 보면, SQL schema 이름에는 특수 문자가 들어갈 수 없다고 한다. 그런데 프로젝트 이름이 OneSearch.Service라고 중간에 .(periods)가 들어가 있는 것이 문제였다.

 

그래서 web.config에 가서 MS_MobileServiceName 옆에 이름을 OneService_Service라고 변경을 하니 잘 된다.

 

하하하

 

Posted by MVP kaki104

티스토리 툴바