快速入门:发送推送通知 (XAML)
云服务器可以通过 Windows 推送通知服务 (WNS) 向应用发送推送通知。 此过程适用于磁贴、toast、锁屏提醒和原始推送通知。
目标:创建和发送磁贴、toast、锁屏提醒或原始推送通知。
先决条件
若要了解本主题或使用所提供的代码,需要:
- 熟悉 HTTP 通信。
- 经过身份验证的云服务器。 有关详细信息,请参阅如何使用 Windows 推送通知服务 (WNS) 进行身份验证。
- 云服务器可与应用进行通信的已注册通道。 有关详细信息,请参阅如何请求、创建和保存通知通道。
- 应用的现有磁贴(在应用的清单中定义),用于接收通知(除非正在发送原始通知)。 有关详细信息,请参阅快速入门:使用 Microsoft Visual Studio 清单编辑器创建默认磁贴。
- 熟悉 XML 及其操作(通过文档对象模型 (DOM) API)。
- 如果发送原始通知,则必须将应用配置为接收原始通知。 有关详细信息,请参阅快速入门:为正在运行的应用截获推送通知和快速入门:创建和注册原始通知后台任务。
Instructions
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)。 有关详细信息,请参阅如何请求、创建和保存通知通道。
HttpWebRequest request = HttpWebRequest.Create(uri) as HttpWebRequest;
request.Method = "POST";
3. 添加必需的标头
所有推送通知中必须包含四个必需的标头:X-WNS-Type、Content-Type、Content-Length 和 Authorization。
- X-WNS-Type 标头指定是磁贴、toast、锁屏提醒还是原始通知。
- 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 响应代码
应用服务发送通知时可以接收许多响应代码。 其中一些响应代码相比于其他代码更常见,可以在 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. 将代码封装到单个函数中
以下示例将上述步骤中提供的代码打包到单个函数中。 此函数将撰写一个 HTTP POST 请求,该请求包含要发送到 WNS 的通知。 通过更改 type 参数的值并调整其他标头,此代码可用于 toast、磁贴、锁屏提醒或原始推送通知。 可以将此函数用作云服务器代码的一部分。
请注意,此函数也处理访问令牌过期的错误。 在这种情况下,它会调用另一个云服务器函数,该函数使用 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);
}
下面显示了 toast 推送通知的 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 响应,该响应由 WNS 发送到云服务器以响应 HTTP POST 请求。
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 请求。 随后 WNS 将通知传送到应用。 此时,你已注册应用、使用 WNS 对云服务器进行身份验证、创建 XML 内容来定义通知,并将该通知从服务器发送到应用。