65.9K
CodeProject 正在变化。 阅读更多。
Home

借助 FogBugz 将 Windows Phone 应用程序的异常记录到中央服务器

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0投票)

2012 年 6 月 28 日

CPOL

3分钟阅读

viewsIcon

20642

downloadIcon

72

本文介绍了如何使用 FogBugz 将 Windows Phone 应用程序的异常记录到中央服务器。

引言

我想与社区分享我们公司在 Windows Phone Marketplace 上获得的经验。

背景

所有 Windows Marketplace 应用程序都必须满足 Microsoft 施加的条件(技术认证要求)。其中一项是排除“未处理的异常”。为了让我们的应用程序进入市场,它必须避免因未处理的异常而关闭。在以下规定中指出了这一点

5.1.2. 应用程序关闭

应用程序必须处理由 .NET Framework 引发的异常,并且不能意外关闭。在认证过程中,将监视应用程序是否意外关闭。意外关闭的应用程序将无法通过认证。应用程序必须继续运行并对用户输入保持响应,在异常处理之后。

然而,当发生未处理的异常时,我们向用户展示“友好”的界面,实际上这仅仅是症状治疗。无论如何,如果在应用程序使用过程中发生类似情况,我们的应用程序将导致未处理的异常。真正的答案是,如果开发人员被告知这个问题,那么在应用程序的进一步开发中,他们将避免异常。为此,需要有关运行时未处理异常的信息。这有很多可能性

作为 FogBugz 用户,对于桌面应用程序,我们将获得一个易于集成的框架

问题所在

收到的代码将无法在 Windows Phone Silverlight 上运行。除了几个小问题外,还应解决以下主要问题

  • 基于 Web 的通信应以异步方式组织;为此,我们必须创建
    • 一个委托
    • 一个事件
  • 由于异步任务执行,应包含超时监视和控制代码。
  • 由于异步性,我们从 Web 接收的响应不是在 UI 执行线程中运行的。

解决方案

我下载了示例代码,我开发了框架项目用于演示示例,然后我做了必要的更改。其中最重要的是

我们应该借助委托将从 Web 服务器收到的异步响应转换为事件,这将指示界面。

  • 异步通信
    • 委托的定义和返回值
    • #region delegate for async webresponse
      
      public delegate void fbBugScoutCompletedEventHandler(object sender, fbBugScoutCompletedEventArgs e);
      
      public class fbBugScoutCompletedEventArgs
      {
          public fbBugScoutCompletedEventArgs(fbBugScoutCompletionCode value, string msg) { code = value; message = msg; }
          public fbBugScoutCompletionCode code { get; private set; }
          public string message { get; private set; }
      }
      
      public enum fbBugScoutCompletionCode
      {
          CompletedOK,
          CompletedError
      }
      
      #endregion
    • 事件处理程序的定义
    • #region event for async webresponse
      
      public static event fbBugScoutCompletedEventHandler fbBugScoutCompletedEvent;
      void RaiseFbBugScoutCompleted(fbBugScoutCompletionCode code, string message)
      {
          if (null != fbBugScoutCompletedEvent)
              fbBugScoutCompletedEvent(this, new fbBugScoutCompletedEventArgs(code, message));
      }
      
      #endregion
  • 处理超时
  • 我们使用计时器处理超时:当搜索开始时,我们设置它并启动它,当它完成时,停止请求我们指示超时。这需要定义

    #region TimeOut methods
    
    private bool bIsInTime = false;
    private System.Windows.Threading.DispatcherTimer _toTimer;
    
    public void StartTimer(TimeSpan tTimeOut)
    {
        _toTimer = new System.Windows.Threading.DispatcherTimer();
        _toTimer.Interval = tTimeOut;
        _toTimer.Tick += toTimer_Tick;
        bIsInTime = true;
        _toTimer.Start();
    }
    
    private void StopToTimer()
    {
        bIsInTime = false;
        if (null != _toTimer)
        {
            _toTimer.Stop();
            _toTimer.Tick -= toTimer_Tick;
            _toTimer = null;
        }
    }
    
    private void toTimer_Tick(object o, EventArgs sender)
    {
        StopToTimer();
        RaiseFbBugScoutCompleted(fbBugScoutCompletionCode.CompletedError, "Time out occurred in network communication");
    }
    
    #endregion
  • 最后,以下代码将使用之前的代码进行基于 Web 的通信
  • #region async webrequest/response methods
    
    private void CallWebRequest(string sURL, Dictionary<string, string> rgArgs, TimeSpan tTimeOut)
    {
        string urlText = String.Empty;
    
        foreach (System.Collections.Generic.KeyValuePair<string, string> i in rgArgs)
        {
            urlText += (0 == urlText.Length ? "" : "&") + String.Format("{0}={1}", i.Key, i.Value);
        }
    
        string url = sURL + urlText;
        HttpWebRequest request = (HttpWebRequest)WebRequest.Create(new Uri(url));
        request.BeginGetResponse(HandleWebResponse, request);
        StartTimer(tTimeOut);
        return;
    }
    
    private void HandleWebResponse(IAsyncResult result)
    {
        if (bIsInTime)
        {
            // response only if request while timeout frame
            // stop the timer
            ((App)App.Current).RootFrame.Dispatcher.BeginInvoke(this.StopToTimer);
    
            // Put this in a try block in case the Web request was unsuccessful.
            try
            {
                // Get the request from the IAsyncResult
                HttpWebRequest request = (HttpWebRequest)(result.AsyncState);
    
                // Read the response stream from the response.
                StreamReader sr = new StreamReader(request.EndGetResponse(result).GetResponseStream());
                string data = sr.ReadToEnd();
                ParseResult(data);
            }
            catch (Exception ex)
            {
                RaiseFbBugScoutCompleted(fbBugScoutCompletionCode.CompletedError, 
                  string.Format("Unable to get data from Web: {0}", ex.Message));
            }
        }
        //else { /* The time is out: nothing todo */ }
    }
    
    #endregion
  • 多线程任务执行
  • 正如我们在之前的代码中看到的,响应没有在与发送请求相同的线程中运行。因此,为了停止正在运行的超时监视计时器(我们已在发送请求线程中启动),我们必须返回到界面的执行线程 (UI 线程),这可以通过以下代码行获得

    ((App)App.Current).RootFrame.Dispatcher.BeginInvoke(this.StopToTimer);

当我们要显示从服务器收到的响应在界面上时,我们将遇到类似的问题

首先,我们应该创建一个委托,然后它将显示响应

delegate void callerfbBugScoutCompleted(fbBugScoutCompletedEventArgs e);
void dofbBugScoutCompleted(fbBugScoutCompletedEventArgs e)
{
    if (fbBugScoutCompletionCode.CompletedOK == e.code)
    {
        lblStatus.Text = e.message;
    }
    else
    {
        lblStatus.Text = string.Format("Error on submit: {0}", e.message);
    }
    progressHide();
}

然后我们应该像往常一样在界面的执行线程中执行

private void btnSendInfo_Click(object sender, RoutedEventArgs e)
{
    //todo

    try
    {
        //************************************************************************************
        //TO DO: SET THESE VALUES BEFORE CALLING THIS METHOD!
        
        //example: https:///fogbugz/scoutSubmit.asp
        string url = "https://smarterdb.fogbugz.com/scoutSubmit.asp?";
        string user = "Gábor Plesz"; //existing FogBugz User
        string project = "WPA.SmarterDB.CodeProject.FogBugz Errors"; //existing FogBugz project 
        string area = "CodeProject"; //existing FogBugz area
        string email = "gabor.plesz@gmail.com"; //email address of the customer who reports the bug
        //the message to return to the user if no Scout Message is found for an existing duplicate bug
        string defaultMessage = "Thank you for your message. It arrived safe " + 
           "and sound and we will resolve the issue as soon as possible."; 
        bool forceNewBug = false;
        //If set to true, this forces FogBugz to create a new case
        //for this bug, even if a bug with the same description already exists.
        //************************************************************************************

        //send the bug we created:
        progressShow();
        lblStatus.Text = "Sending debug info...";
        BugReport.fbBugScoutCompletedEvent += 
          new fbBugScoutCompletedEventHandler(BugReport_fbBugScoutCompletedEvent);
        BugReport.Submit(url, user, project, area, email, forceNewBug, 
           defaultMessage, Exception, true, "{0}.{1}.{2}.{3}", true);

        //lblStatus.ForeColor = Color.Green;
    }
    catch (Exception ex)
    {
        //lblStatus.ForeColor = Color.Red;
        lblStatus.Text = "Cannot send the debug info: " + ex.Message;
        progressHide();
    }

}

void BugReport_fbBugScoutCompletedEvent(object sender, fbBugScoutCompletedEventArgs e)
{
    BugReport.fbBugScoutCompletedEvent -= new fbBugScoutCompletedEventHandler(BugReport_fbBugScoutCompletedEvent);
    ((App)App.Current).RootFrame.Dispatcher.BeginInvoke(new callerfbBugScoutCompleted(dofbBugScoutCompleted), (e));
}

为了概述整个过程,我还绘制了一个插图

Sample Image

标有蓝色的元素在界面的执行线程(UI 线程)中运行,红色的元素显示了从 Web 服务器接收的异步响应线程,两个绿色的箭头表示返回线程在界面线程中启动一个过程的点。

© . All rights reserved.