【发布时间】:2017-12-15 08:45:52
【问题描述】:
我正在开发 ASP.NET MVC 项目。
我需要一些时间来解释我的疯狂情况。
我正在尝试从 MVC 项目向 Android 和 Apple 设备发送推送通知。
两者的发送逻辑都是正确的,请不要浪费时间考虑这个
我面临的灾难是:静态类中负责发送通知的静态方法没有被调用。 (我不是新手,我有5年多的C#编程经验)但是我不能调用方法。
为了让您了解问题的上下文,当我在本地机器(开发机器)上执行代码时,调用并执行此方法并将通知到达设备。
当我发布 MVC 项目并将其部署到我们的服务器时,不会调用静态方法。
我怎么知道该方法没有被调用?
因为我正在记录到一个文本文件,并且我在第一行放了一条日志语句
方法和调用方法之前的日志语句。
调用方法之前的日志会被执行并充实到文本文件中,而静态方法开始的日志不会被执行!!!!!!。
这是一些代码,然后我会告诉你我试图解决这个问题的方法。
public interface IHandler<T> where T : IMessage
{
Task Handle(T args);
}
public class RequestAddedAppMonitorHandler : IHandler<RequestAdded>
{
public Task Handle(RequestAdded args)
{
return Task.Factory.StartNew(() =>
{
try
{
GoogleNotification notification = CreateAndroidPartnerAppNotification(deviceId);
// this statment is executed, and the text log file will contains this line
TracingSystem.TraceInformation("Before Send Google Notification");
SendersFacade.PartnerSender.Send(notification).Wait();
}
catch (Exception ex)
{
TracingSystem.TraceException(ex);
}
});
}
private GoogleNotification CreateAndroidPartnerAppNotification(string to)
{
return new GoogleNotification(); // some initialization and creating for the notification object.
}
}
门面类
public static class SendersFacade
{
public static GoogleNotificationSender ClientSender { get; private set; }
public static GoogleNotificationSender PartnerSender { get; private set; }
//public static AppleNotificationSender AppleSender { get; private set; }
static SendersFacade()
{
ClientSender = new GoogleNotificationSender("correct api key");
PartnerSender = new GoogleNotificationSender("correct api key");
//AppleSender = some intialization.
}
}
Google 通知发送逻辑
public class GoogleNotificationSender
{
private string _authorizationToken;
private string AuthorizationToken
{
get { return _authorizationToken; }
set
{
if (string.IsNullOrEmpty(value))
throw new InvalidOperationException("authorizationToken must not be null");
_authorizationToken = value;
}
}
public GoogleNotificationSender(string authorizationToken)
{
this.AuthorizationToken = authorizationToken;
}
public async Task Send(GoogleNotification notification)
{
// ATTENTION PLEASE
// This method is not called, and the following line is not fleshed to the log file
TracingSystem.TraceInformation("Inside Send Google notification");
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", "key=" + AuthorizationToken);
string json = notification.GetJson();
StringContent content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");
using (HttpResponseMessage message = await client.PostAsync("https://fcm.googleapis.com/fcm/send", content))
{
message.EnsureSuccessStatusCode();
string resultAsString = await message.Content.ReadAsStringAsync();
GoogleNotificationResult result = JsonConvert.DeserializeObject<GoogleNotificationResult>(resultAsString);
if (result.Failure > 0)
throw new Exception($"Sending Failed : {result.Results.FirstOrDefault().Error}");
}
}
}
}
Google 通知类
public class GoogleNotification
{
[JsonProperty("to")]
public string To { get; set; }
[JsonProperty("data")]
public JObject Data { get; set; }
[JsonProperty("notification")]
public JObject Notification { get; set; }
// some other property which is not used at all
internal string GetJson()
{
return JsonConvert.SerializeObject(this,
new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
}
}
前三天我尝试了什么?
1-将用于调试的DLL(不是已发布的DLL,使用Release模式)部署到服务器,这并没有解决问题。
2- 将SendersFacade 设为非静态类,并在其上应用单调设计模式,也不起作用。
public class SendersFacade
{
public static SendersFacade Instance { get; private set; }
public GoogleNotificationSender ClientSender { get; private set; }
public GoogleNotificationSender PartnerSender { get; private set; }
//public static AppleNotificationSender AppleSender { get; private set; }
static SendersFacade()
{
if (Instance != null)
Instance = new SendersFacade();
}
public SendersFacade()
{
ClientSender = new GoogleNotificationSender("correct api key");
PartnerSender = new GoogleNotificationSender("correct api key");
//AppleSender = some intialization.
}
}
3- 尝试将发送的逻辑放在它自己的 Handler 类中,这很有效,我能够从服务器发送通知,但是为什么,在地狱,下面的代码工作正常,但以前的代码不工作??????????
public interface IHandler<T> where T : IMessage
{
Task Handle(T args);
}
public class RequestAddedAppMonitorHandler : IHandler<RequestAdded>
{
public Task Handle(RequestAdded args)
{
return Task.Factory.StartNew(() =>
{
try
{
GoogleNotification notification = CreateAndroidPartnerAppNotification(deviceId);
// this statment is executed, and the text log file will contains this line
TracingSystem.TraceInformation("Before Send Google Notification");
SendersFacade.PartnerSender.Send(notification).Wait();
}
catch (Exception ex)
{
TracingSystem.TraceException(ex);
}
});
}
private GoogleNotification CreateAndroidPartnerAppNotification(string to)
{
return new GoogleNotification(); // some initialization and creating for the notification object.
}
private void Send(GoogleNotification notification, string authorizationToken)
{
TracingSystem.TraceInformation("Inside Send Google notification");
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", "key=" + authorizationToken);
string json = notification.GetJson();
StringContent content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");
using (HttpResponseMessage message = client.PostAsync("https://fcm.googleapis.com/fcm/send", content).Result)
{
message.EnsureSuccessStatusCode();
string resultAsString = message.Content.ReadAsStringAsync().Result;
GoogleNotificationResult result = JsonConvert.DeserializeObject<GoogleNotificationResult>(resultAsString);
if (result.Failure > 0)
throw new Exception($"Sending Failed : {result.Results.FirstOrDefault().Error}");
}
}
}
}
只需将 send 方法的逻辑添加到 RequestAddedAppMonitorHandler 类即可解决问题,但我不想这样做,为什么会发生这种情况?
只是调用一个方法。
3-尝试使发送方法串行方法(不使用async),它也没有工作
public void Send(GoogleNotification notification)
{
TracingSystem.TraceInformation("Inside Send Google notification");
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", "key=" + AuthorizationToken);
string json = notification.GetJson();
StringContent content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");
using (HttpResponseMessage message = client.PostAsync(BASE_URL, content).Result)
{
message.EnsureSuccessStatusCode();
string resultAsString = message.Content.ReadAsStringAsync().Result;
GoogleNotificationResult result = JsonConvert.DeserializeObject<GoogleNotificationResult>(resultAsString);
if (result.Failure > 0)
throw new Exception($"Sending Failed : {result.Results.FirstOrDefault().Error}");
}
}
}
注意1: 我注意到我在服务器上遇到问题(在我的本地机器上根本没有出现),这是该网站特定的应用程序池经常停止,这导致请求网站时 503 服务不可用。
注意 2: 我怀疑问题的最可能原因是线程。 但我无法得出明确的解决方案
注意 3:请不要认为这个问题有答案,它对我一点帮助都没有。
我从三天开始就在做这个,我真的很绝望,任何想法谢谢。
更新 @Nkosi 的回答真的很有帮助,至少我现在知道出了什么问题,我决定一路同步。并避免将
async/await 与阻塞调用混合。
所以这是我达到的结果
public class RequestAddedAppMonitorHandler : IHandler<RequestAdded>
{
public Task Handle(RequestAdded args)
{
return Task.Factory.StartNew(() =>
{
try
{
if (deviceOS.Value == DeviceOSEnum.Android.ToString())
{
GoogleNotification notification = CreateAndroidUpdateRequestMessage(args.CustomerRequest, deviceId.Value, notificationString.Title_RequestStared, message);
SendGoogleNotification(notification, "some id");
}
else if (deviceOS.Value == DeviceOSEnum.IOS.ToString())
{
AppleNotification notification = CreateAppleNotification(deviceId.Value, notificationString.Title_RequestStared, message);
AppleNotificationSender sender = new AppleNotificationSender();
sender.SendAppleNotification(notification);
}
}
catch (Exception ex)
{
TracingSystem.TraceException(ex);
}
});
}
和 AppleNotificationSender 类
public class AppleNotificationSender
{
private TcpClient client;
private string host = "gateway.push.apple.com";
private int port = 2195;
private X509Certificate2 certificate;
public AppleNotificationSender()
{
string path = HostingEnvironment.MapPath("~/Certificates.p12");
certificate = new X509Certificate2(path, "some correct password");
}
private void SetSocketKeepAliveValues(Socket socket, int KeepAliveTime, int KeepAliveInterval)
{
//KeepAliveTime: default value is 2hr
//KeepAliveInterval: default value is 1s and Detect 5 times
uint dummy = 0; //lenth = 4
byte[] inOptionValues = new byte[System.Runtime.InteropServices.Marshal.SizeOf(dummy) * 3]; //size = lenth * 3 = 12
BitConverter.GetBytes((uint)1).CopyTo(inOptionValues, 0);
BitConverter.GetBytes((uint)KeepAliveTime).CopyTo(inOptionValues, System.Runtime.InteropServices.Marshal.SizeOf(dummy));
BitConverter.GetBytes((uint)KeepAliveInterval).CopyTo(inOptionValues, System.Runtime.InteropServices.Marshal.SizeOf(dummy) * 2);
// of course there are other ways to marshal up this byte array, this is just one way
// call WSAIoctl via IOControl
// .net 3.5 type
socket.IOControl(IOControlCode.KeepAliveValues, inOptionValues, null);
}
private bool SocketCanWrite(SslStream stream)
{
if (client == null)
return false;
if (stream == null || !stream.CanWrite)
return false;
if (!client.Client.Connected)
return false;
return client.Client.Poll(1000, SelectMode.SelectWrite);
}
private void Connect()
{
try
{
if (client == null)
client = new TcpClient();
client.Connect(host, port);
//Set keep alive on the socket may help maintain our APNS connection
try { client.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true); }
catch { }
// Really not sure if this will work on MONO....
// This may help windows azure users
try
{
SetSocketKeepAliveValues(client.Client, (int)TimeSpan.FromMinutes(20).TotalMilliseconds, (int)TimeSpan.FromSeconds(30).TotalMilliseconds);
}
catch { }
}
catch (Exception ex)
{
throw new Exception("Failed to Connect, check your firewall settings!", ex);
}
}
public void SendAppleNotification(AppleNotification notification)
{
SslStream stream = null;
try
{
Connect();
stream = new SslStream(client.GetStream(),
false,
(sender, cert, chain, policyErrors) => true,
(sender, targetHost, localCerts, remoteCert, acceptableIssuers) => certificate);
try
{
X509CertificateCollection collection = new X509CertificateCollection();
collection.Add(certificate);
stream.AuthenticateAsClient(host, collection, System.Security.Authentication.SslProtocols.Tls, false);
}
catch (System.Security.Authentication.AuthenticationException ex)
{
throw new Exception("SSL Stream Failed to Authenticate as Client", ex);
}
if (!stream.IsMutuallyAuthenticated)
throw new Exception("SSL Stream Failed to Authenticate", null);
if (!stream.CanWrite)
throw new Exception("SSL Stream is not Writable", null);
if (!SocketCanWrite(stream))
Connect();
byte[] data = notification.ToBytes();
stream.Write(data, 0, data.Length);
//TracingSystem.TraceInformation("Write to stream ended.");
}
catch (Exception)
{
TracingSystem.TraceError("Error in sending Apple notification");
throw;
}
finally
{
try { stream?.Close(); } catch { }
try { stream?.Dispose(); } catch { }
try { client?.Client?.Shutdown(SocketShutdown.Both); } catch { }
try { client?.Client?.Dispose(); } catch { }
try { client?.Close(); } catch { }
client = null;
}
}
}
现在我解决了死锁问题,但我遇到了另一个问题。发送苹果通知时,触发这个Handle方法的MVC动作被调用了两次,这将导致业务规则异常(如果这个动作触发了两次,这是正常的事情)。并且根本没有收到 Apple 通知。
注意:当我在本地机器上调试发送Apple Notification的代码时,一切都很好,并且通知到达,并且仅调用了一次Action,在部署此代码后出现前面描述的问题到服务器。
注意:发送 Google 通知时根本不会出现此问题。
这里顺便说一下Handle方法的触发
public class MessageBus : ICommandSender
{
public static MessageBus Instance { get; private set; }
private MessageBus()
{
handlers = new List<Delegate>();
}
static MessageBus()
{
if (Instance == null)
Instance = new MessageBus();
}
private List<Delegate> handlers;
public void Send<T>(T command) where T : ICommand
{
List<Task> tasks = new List<Task>();
foreach (Func<T, Task> handle in handlers.OfType<Func<T, Task>>())
{
try { tasks.Add(handle(command)); }
catch (Exception ex) { TracingSystem.TraceException(ex); }
}
try { Task.WaitAll(tasks.ToArray()); }
catch (BusinessRuleException buzEx) { TracingSystem.TraceException(buzEx); throw buzEx; }
catch (Exception ex) { TracingSystem.TraceException(ex); }
}
}
【问题讨论】:
-
有没有一种简单的方法可以让我们重新创建代码并查看问题? stackoverflow.com/help/mcve
-
“我需要一些时间来解释我的疯狂情况。”紧随其后的是“很简单”......红灯闪烁!
-
疯狂的情况来自于根本不起作用的简单@InBetween
-
我觉得一点都不简单......
-
看起来你正在使用 ASP.NET 实现 fire-and-forgetty。如果是这种情况,这可能会给您一些见解:stackoverflow.com/questions/18502745/…。检查答案中链接的博客...
标签: c# asp.net-mvc