Share via


푸시 알림 보내기 빠른 시작(XAML)

클라우드 서버는 WNS(Windows 푸시 알림 서비스)를 통해 앱에 푸시 알림을 보낼 수 있습니다. 이 절차는 타일, 알림, 배지 및 원시 푸시 알림에 적용됩니다.

목표: 타일, 알림, 배지 또는 원시 푸시 알림을 만들고 보냅니다.

필수 조건

이 항목을 이해하거나 제공하는 코드를 사용하려면 다음이 필요합니다.

지침

1. 필요한 네임스페이스 참조 포함

이 항목에 제공된 예는 있는 그대로 사용할 수 있지만 코드에 다음 네임스페이스 참조가 포함되어야 합니다.

using System.Net;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Web;
using System.Text;

2. HTTP POST 요청 만들기

uri 매개 변수는 앱에서 요청하고 클라우드 서버에 전달되는 채널 URI(Uniform Resource Identifier)입니다. 자세한 내용은 알림 채널을 요청, 만들기 및 저장하는 방법을 참조하세요.

HttpWebRequest request = HttpWebRequest.Create(uri) as HttpWebRequest;
request.Method = "POST";

3. 필요한 헤더 추가

모든 푸시 알림에 포함되어야 하는 4개의 필수 헤더(X-WNS-Type, Content-Type, Content-Length 및 Authorization)가 있습니다.

  • X-WNS-Type 헤더는 이것이 타일, 알림, 배지 또는 푸시 알림인지 지정합니다.
  • Content-Type은 X-WNS-Type 값에 따라 설정됩니다.
  • Content-Length는 포함된 알림 페이로드의 크기를 제공합니다.
  • Authorization 헤더는 이 채널을 통해 이 사용자에게 푸시 알림을 보낼 수 있는 인증 자격 증명을 지정합니다.

Authorization 헤더의 accessToken 매개 변수는 클라우드 서버가 인증을 요청할 때 WNS에서 수신한 서버에 저장된 액세스 토큰을 지정합니다. 액세스 토큰이 없으면 알림이 거부됩니다.

가능한 헤더의 전체 목록은 푸시 알림 서비스 요청 및 응답 헤더를 참조하세요.

request.Headers.Add("X-WNS-Type", notificationType);
request.ContentType = contentType;
request.Headers.Add("Authorization", String.Format("Bearer {0}", accessToken.AccessToken));

4. 준비된 콘텐츠 추가

HTTP 요청에 관한 한 알림의 XML 콘텐츠는 요청 본문의 데이터 BLOB입니다. 예를 들어 XML이 X-WNS-Type 사양과 일치하는지 확인하지 않습니다. 콘텐츠는 XML 페이로드로 지정되며 여기에 바이트 스트림으로 요청에 추가됩니다.

byte[] contentInBytes = Encoding.UTF8.GetBytes(xml);
                        
using (Stream requestStream = request.GetRequestStream())
    requestStream.Write(contentInBytes, 0, contentInBytes.Length);

5. 알림 수신을 확인하는 WNS의 응답을 듣습니다.

참고 항목

알림에 대한 배달 확인은 절대 받지 않으며 WNS에서 수신했다는 확인만 받습니다.

using (HttpWebResponse webResponse = (HttpWebResponse)request.GetResponse())
    return webResponse.StatusCode.ToString();

6. WNS 응답 코드 처리

App Service가 알림을 보낼 때 받을 수 있는 응답 코드는 많이 있습니다. 이러한 응답 코드 중 일부는 다른 것보다 더 일반적이며 catch 블록에서 쉽게 처리할 수 있습니다.

catch (WebException webException)
{
    HttpStatusCode status = ((HttpWebResponse)webException.Response).StatusCode;

HttpStatusCode.Unauthorized: 제출한 액세스 토큰이 만료되었습니다. 새 알림을 받은 다음 알림을 다시 보냅니다. 캐시된 액세스 토큰은 24시간 후에 만료되므로 WNS에서 이 응답을 하루에 한 번 이상 받을 수 있습니다. 최대 다시 시도 정책을 구현하는 것이 좋습니다.

    if (status == HttpStatusCode.Unauthorized)
    {
        GetAccessToken(secret, sid);
        return PostToWns(uri, xml, secret, sid, notificationType, contentType);
    }

HttpStatusCode.Gone/HttpStatusCode.NotFound: 채널 URI가 더 이상 유효하지 않습니다. 더 이상 알림을 보내려는 시도를 방지하려면 데이터베이스에서 이 채널을 제거합니다. 다음에 이 사용자가 앱을 시작할 때 새 WNS 채널을 요청합니다. 앱은 채널이 변경되었음을 검색해야 하며, 이는 앱이 새 채널 URI를 앱 서버로 보내도록 트리거해야 합니다. 자세한 내용은 알림 채널을 요청, 만들기 및 저장하는 방법을 참조하세요.

    else if (status == HttpStatusCode.Gone || status == HttpStatusCode.NotFound)
    {
        return "";
    }

HttpStatusCode.NotAcceptable: 이 채널은 WNS에 의해 제한되고 있습니다. 다시 제한되는 것을 방지하기 위해 전송되는 알림의 양을 기하급수적으로 줄이는 다시 시도 전략을 구현합니다. 또한 알림이 제한되는 시나리오를 다시 고려합니다. 진정한 가치를 더하는 알림으로 보내는 알림을 제한하여 더 풍부한 사용자 환경을 제공할 것입니다.

    else if (status == HttpStatusCode.NotAcceptable)
    {
        return "";
    }

기타 응답 코드: WNS가 덜 일반적인 응답 코드로 응답했습니다. 디버깅에 도움이 되도록 이 코드를 기록합니다. WNS 응답 코드의 전체 목록은 푸시 알림 서비스 요청 및 응답 헤더를 참조하세요.

    else
    {
        string[] debugOutput = {
                                   status.ToString(),
                                   webException.Response.Headers["X-WNS-Debug-Trace"],
                                   webException.Response.Headers["X-WNS-Error-Description"],
                                   webException.Response.Headers["X-WNS-Msg-ID"],
                                   webException.Response.Headers["X-WNS-Status"]
                               };
        return string.Join(" | ", debugOutput);            
    }

7. 코드를 단일 함수로 캡슐화

다음 예에서는 이전 단계에서 제공된 코드를 단일 함수로 패키징합니다. 이 함수는 WNS로 보낼 알림이 포함된 HTTP POST 요청을 구성합니다. type 매개 변수의 값을 변경하고 추가 헤더를 조정하여 이 코드를 알림, 타일, 배지 또는 원시 푸시 알림에 사용할 수 있습니다. 이 기능을 클라우드 서버 코드의 일부로 사용할 수 있습니다.

이 함수의 오류 처리에는 액세스 토큰이 만료된 상황이 포함됩니다. 이 경우 WNS로 재인증하는 다른 클라우드 서버 함수를 호출하여 새 액세스 토큰을 얻습니다. 그런 다음 원래 함수를 새로 호출합니다.

// Post to WNS
public string PostToWns(string secret, string sid, string uri, string xml, string notificationType, string contentType)
{
    try
    {
        // You should cache this access token.
        var accessToken = GetAccessToken(secret, sid);

        byte[] contentInBytes = Encoding.UTF8.GetBytes(xml);

        HttpWebRequest request = HttpWebRequest.Create(uri) as HttpWebRequest;
        request.Method = "POST";
        request.Headers.Add("X-WNS-Type", notificationType);
        request.ContentType = contentType;
        request.Headers.Add("Authorization", String.Format("Bearer {0}", accessToken.AccessToken));

        using (Stream requestStream = request.GetRequestStream())
            requestStream.Write(contentInBytes, 0, contentInBytes.Length);

        using (HttpWebResponse webResponse = (HttpWebResponse)request.GetResponse())
            return webResponse.StatusCode.ToString();
    }
    
    catch (WebException webException)
    {
        HttpStatusCode status = ((HttpWebResponse)webException.Response).StatusCode;

        if (status == HttpStatusCode.Unauthorized)
        {
            // The access token you presented has expired. Get a new one and then try sending
            // your notification again.
              
            // Because your cached access token expires after 24 hours, you can expect to get 
            // this response from WNS at least once a day.

            GetAccessToken(secret, sid);

            // We recommend that you implement a maximum retry policy.
            return PostToWns(uri, xml, secret, sid, notificationType, contentType);
        }
        else if (status == HttpStatusCode.Gone || status == HttpStatusCode.NotFound)
        {
            // The channel URI is no longer valid.

            // Remove this channel from your database to prevent further attempts
            // to send notifications to it.

            // The next time that this user launches your app, request a new WNS channel.
            // Your app should detect that its channel has changed, which should trigger
            // the app to send the new channel URI to your app server.

            return "";
        }
        else if (status == HttpStatusCode.NotAcceptable)
        {
            // This channel is being throttled by WNS.

            // Implement a retry strategy that exponentially reduces the amount of
            // notifications being sent in order to prevent being throttled again.

            // Also, consider the scenarios that are causing your notifications to be throttled. 
            // You will provide a richer user experience by limiting the notifications you send 
            // to those that add true value.

            return "";
        }
        else
        {
            // WNS responded with a less common error. Log this error to assist in debugging.

            // You can see a full list of WNS response codes here:
            // https://msdn.microsoft.com/library/windows/apps/hh868245.aspx#wnsresponsecodes

            string[] debugOutput = {
                                       status.ToString(),
                                       webException.Response.Headers["X-WNS-Debug-Trace"],
                                       webException.Response.Headers["X-WNS-Error-Description"],
                                       webException.Response.Headers["X-WNS-Msg-ID"],
                                       webException.Response.Headers["X-WNS-Status"]
                                   };
            return string.Join(" | ", debugOutput);            
        }
    }

    catch (Exception ex)
    {
        return "EXCEPTION: " + ex.Message;
    }
}

// Authorization
[DataContract]
public class OAuthToken
{
    [DataMember(Name = "access_token")]
    public string AccessToken { get; set; }
    [DataMember(Name = "token_type")]
    public string TokenType { get; set; }
}

private OAuthToken GetOAuthTokenFromJson(string jsonString)
{
    using (var ms = new MemoryStream(Encoding.Unicode.GetBytes(jsonString)))
    {
        var ser = new DataContractJsonSerializer(typeof(OAuthToken));
        var oAuthToken = (OAuthToken)ser.ReadObject(ms);
        return oAuthToken;
    }
}

protected OAuthToken GetAccessToken(string secret, string sid)
{
    var urlEncodedSecret = HttpUtility.UrlEncode(secret);
    var urlEncodedSid = HttpUtility.UrlEncode(sid);

    var body = String.Format("grant_type=client_credentials&client_id={0}&client_secret={1}&scope=notify.windows.com", 
                             urlEncodedSid, 
                             urlEncodedSecret);

    string response;
    using (var client = new WebClient())
    {
        client.Headers.Add("Content-Type", "application/x-www-form-urlencoded");
        response = client.UploadString("https://login.live.com/accesstoken.srf", body);
    }
    return GetOAuthTokenFromJson(response);
}

다음은 알림 푸시 알림에 대한 HTTP POST 요청에 대한 예시 콘텐츠를 보여 줍니다.

POST https://db3.notify.windows.com/?token=AgUAAADCQmTg7OMlCg%2fK0K8rBPcBqHuy%2b1rTSNPMuIzF6BtvpRdT7DM4j%2fs%2bNNm8z5l1QKZMtyjByKW5uXqb9V7hIAeA3i8FoKR%2f49ZnGgyUkAhzix%2fuSuasL3jalk7562F4Bpw%3d HTTP/1.1
Authorization: Bearer EgAaAQMAAAAEgAAACoAAPzCGedIbQb9vRfPF2Lxy3K//QZB79mLTgK
X-WNS-RequestForStatus: true
X-WNS-Type: wns/toast
Content-Type: text/xml
Host: db3.notify.windows.com
Content-Length: 196

<toast launch="">
  <visual lang="en-US">
    <binding template="ToastImageAndText01">
      <image id="1" src="World" />
      <text id="1">Hello</text>
    </binding>
  </visual>
</toast>

다음은 HTTP POST 요청에 대한 응답으로 WNS에서 클라우드 서버로 보낸 HTTP 응답의 예를 보여 줍니다.

HTTP/1.1 200 OK
Content-Length: 0
X-WNS-DEVICECONNECTIONSTATUS: connected
X-WNS-STATUS: received
X-WNS-MSG-ID: 3CE38FF109E03A74
X-WNS-DEBUG-TRACE: DB3WNS4011534

요약

이 빠른 시작에서는 WNS에 HTTP POST Request to Send를 작성했습니다. WNS는 차례로 앱에 알림을 전달합니다. 이 시점에서 앱을 등록하고, WNS로 클라우드 서버를 인증하고, 알림을 정의하는 XML 콘텐츠를 만들고, 서버에서 앱으로 해당 알림을 보냈습니다.