[转]ASP.NET 上傳檔案進度顯示

 

ASP.NET 上傳檔案進度顯示

 

 

/ 黃忠成

 

上傳檔案所需面對的問題

 

  運用ASP.NETFileUpload 控件來讓使用者上傳大檔案,一直以來都困擾著ASP.NET 的程式設計師,雖然透過修改web.confighttpRuntime 區段的maxRequestLength 設定值可以讓上傳檔案的大小放大到4MB 以上,但是隨之而來的問題也不少,第一個是上傳大檔案所需要花費的時間,每個ASP.NET 頁面都有一個最大執行時間,一旦超過這個時間,那麼網頁就會拋出Timeout 的例外,導致使用者會於瀏覽器上見到連線錯誤的網頁,這個最大執行時間預設是90 秒,也就是1 分鐘半,要修改這個時間,可以透過修改web.confighttpRuntime 區段的executeTimeout 設定來達到。第二是當使用者上傳了一個檔案大小超過限制的檔案時,ASP.NET 一樣  會回應一個連線錯誤的網頁,這個網頁中根本沒有任何資訊告知使用者,錯誤是發生在使用者上傳了一個過大的檔案,這讓使用者完全弄不清楚問題出在那裡。第三個問題是上傳期間,瀏覽器會處於送出資料的狀態,使用者完全無法得知上傳的進度,此問題可透過IFrame 來解決。

1-1 ASP.NET 處理大檔案上傳所需解決的問題

1 、上傳大檔案所需花費的時間大於預設的1 分鐘30 秒。

2 、上傳大於限制的檔案時,瀏覽器會以『連線錯誤』的網頁回應。

3 、上傳檔案期間,網頁處於停滯狀態,使用者無從得知上傳進度。

4 、使用者必須手動,一個個選擇要上傳的檔案。

 

檔案過大時的錯誤處理

 

  在一月份於我的 BLOG 中有詳細的解法,透過 IFRAME 的動態顯示及隱藏功能,將連線錯誤的訊息藏起來,而後透過 AJAX 將易懂的訊息回報給使用者。

 

http://blog.csdn.net/Code6421/archive/2008/01/28/2070566.aspx

 

進度顯示,有可能嗎?

 

  上傳檔案過大的錯誤顯示只是解決表 1 中的第二個問題,對使用者來說意義並不大,如果能解決問題 3 ,那麼對於 ASP.NET 網頁上傳檔案將會有極大的改進,但有可能嗎?其實這個問題很早就有解決方案了,透過 ActiveX 的技巧,在上傳檔案時顯示進度並不是件難事,問題就在於,對用戶來說,安裝 ActiveX 控件是一個不安全的動作,更別談非 IE 平台上根本就沒有這東西可用了。那除了 ActiveX 控件外,是否還有別的解法呢?有的,你可以使用 Flash 類型的 Upload 控件,這是一勞永逸的解法,可以解決表 1 上所列出的 4 個問題。倘若不使用 ActiveX Flash ,那麼這裡我將提供一個純 ASP.NET AJAX 的解法給各位。

  要顯示檔案上傳進度,我們得先了解 ASP.NET Runtime 是如何處理檔案上傳的,當使用者於 FileUpload 控件上選擇要上傳檔案,並按下確認 (Submit) 按紐時,瀏覽器會送出 Form 上的欄位值,由於 Form 上有 FileUpload 控件,所以送出的形式會是 Multipart ASP.NET Runtime 在收到這類型資料時,會依據 Mutlipart 中的資訊來循序讀取瀏覽器送上來的資料。也就是說,瀏覽器於送出 multipart header 後,就會開始送出上傳檔案的內容,而 ASP.NET Runtime 則於一個迴圈中不停的讀取收到的資料並解譯。

  因此,如果要顯示上傳進度,我們必須要能夠插手這個收取資料迴圈,於內將進度放置 Cache 中,最後由 AJAX Timer 控件來取得資訊並使用 UpdatePanel 或其它機制來顯示。

  問題在,這個迴圈是封閉的,一般的手法是無法對其做任何改變的,最簡單的方式是由 HttpHandler 開始,自行掌控關於 FileUpload 的所有動作,這意味著,你得自行解析 multipart 的資訊,而這是相當繁複的過程,至少你得讀懂 RFC1341 ,也就是 MIME 中的 mutlipart content type

  基於懶惰不想寫太多程式碼及除錯,我選擇了一個相當取巧的途徑, ASP.NET Runtime 中本來就存在完整的 multipart 解譯機制,缺的只是進度回報的部份,因此我利用了 Reflection 機制來取用 ASP.NET Runtime 中的 mutlipart 解譯機制,並使用 ASP.NET AJAX 及簡易的 Http Handler 來完成進度回報的工作。

 

A Hacking

 

   由於涉及 ASP.NET Runtime 中未公開的機制,我並不打算將程式碼一一列出並解釋,因為這對讀者們並沒有太大的益處 ( 其實是連我自己都不太記得裡面的流程 ) ,取而代之的是一個簡單的範例,此例子的結構如圖 1 所示。

1

 

這個網站中有四個檔案, Default.aspx 是顯示給使用者的上傳檔案網頁,請注意,其內內嵌了 IFrame ,連結至 UploadHandler.aspx ,而 UploadHandler.aspx 中的確認 (Submit) 按紐則是運用了 Cross-Page Postback 機制,將動作引導至 Handler.ashx ,最後由 Handler.ashx 呼叫 HackUpload.cs 中定義的 Helper class 來處理檔案上傳動作。

  我想,其中最令人好奇的應該是 HackUpload.cs 的內容,在裡面處理上傳檔案的主要函式如程式 1 所示。

public bool Load()

{

        if (_context.Request.ContentLength < GetMaxRequestSize())

        {

            DateTime startTime = DateTime .Now;

            if (_hGetMultipartBoundary.Invoke(_context.Request, null ) != null )

            {

                object ruc = CreateRawUploadContent();

                HttpWorkerRequest wr =

                  (HttpWorkerRequest )_hWorkReqeust.GetValue(_context.Request);

                byte [] preloadedEntityBody = wr.GetPreloadedEntityBody();

                if (preloadedEntityBody != null )

                    _hAddBytes.Invoke(ruc, new object [] { preloadedEntityBody, 0,

                                 preloadedEntityBody.Length });

                if (!wr.IsEntireEntityBodyIsPreloaded())

                {

                    int num3 = (_context.Request.ContentLength > 0) ?

                               (_context.Request.ContentLength -

                              (int )_hLength.GetValue(ruc, null )) : 0x7fffffff;

                    byte [] buffer = new byte [8192];

                    int length = (int )_hLength.GetValue(ruc, null );

                    while (num3 > 0)

                    {

                        int size = buffer.Length;

                        if (size > num3)

                            size = num3;

                        int num6 = wr.ReadEntityBody(buffer, size);

                        if (num6 <= 0)

                             break ;

                        _hreadEntityBody.SetValue(_context.Request, true );

                        _hAddBytes.Invoke(ruc, new object [] { buffer, 0, num6 });

                        num3 -= num6;

                        length += num6;

                         OnReadProgressReport(

                           new ReadProgressReportEventArgs (

                            _context.Request.ContentLength, length, startTime));

                    }

                }

                _hdoneBytes.Invoke(ruc, null );

                _hrawContent.SetValue(_context.Request, ruc);

            }

            return true ;

        }

        return false ;

    }

如你所見,這並不是一段易讀的程式碼,尤其內部牽涉到了許多 ASP.NET Runtime 的內部機制,這也是我決定不詳細解說此程式碼的原因。

  不過用法上仍然是必須解說的,在 Default.aspx 中有著下列的程式碼。

<% @ Page Language ="C#" AutoEventWireup ="true"   CodeFile ="Default.aspx.cs" Inherits ="_Default" %>

 

<! DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

 

< html xmlns ="http://www.w3.org/1999/xhtml">

< head runat ="server">

    < title > Untitled Page</ title >

</ head >

< body >

    < form id ="form1" runat =server >

    < div >

        < asp : ScriptManager ID ="ScriptManager1" runat ="server">

        </ asp : ScriptManager >

        < div >

        < iframe id ="fileframe" name ="fileframe" frameborder ="0" scrolling ="no"

                      src ="UploadHandler.aspx?UID= <% = UploadFrameHelper.GetUID() %> "

                       style =" height:60px;"></ iframe >

        < span id ="statusLabel"></ span >

        </ div >

            < asp : UpdatePanel ID ="UpdatePanel1" UpdateMode =Conditional runat ="server">

            < ContentTemplate >

                < asp : Timer ID ="Timer1" Interval =500 runat ="server" ontick ="Timer1_Tick">

                </ asp : Timer >

                < asp : Label ID ="Label1" runat ="server" Text ="" Visible =true></ asp : Label >

            </ ContentTemplate >

            </ asp : UpdatePanel >

    </ div >

    </ form >

</ body >

</ html >

請注意 IFRAME 這段,這連結到了 UploadHandelr.aspx ,特別的是此處呼叫了一個 GetUID 函式,下面是此函式的原始碼。

public static string GetUID()

    {

        if (HttpContext .Current.Session["$UPLOAD$_UID" ] != null )

            return (string )HttpContext .Current.Session["$UPLOAD$_UID" ];

        HttpContext .Current.Session["$UPLOAD$_UID" ] = Guid .NewGuid().ToString();

        return (string )HttpContext .Current.Session["$UPLOAD$_UID" ];

    }

GetUID 主要的用途是在 Session 中產生一個識別碼,稍後我們將以此識別碼做為鍵值,在 AJAX Async-postback 期間,利用 Cache 來儲存及取得上傳進度資訊。

下面是 UploadHandler.aspx 的程式碼。

<% @ Page Language ="C#" AutoEventWireup ="true" CodeFile ="UploadHandler.aspx.cs" Inherits ="UploadHandler" %>

 

<! DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

 

< html xmlns ="http://www.w3.org/1999/xhtml">

< head runat ="server">

    < title > Untitled Page</ title >

</ head >

< body >

    < form id ="form1" runat ="server">

    < div >    

        < script language =javascript>

         function delayDisable()

         {

            window.setTimeout("document.getElementById('Btn1').disabled = true;" ,0);

            window.top.setFrameVisible(false );

            window.top.document.getElementById("statusLabel" ).innerHTML =

                             " 上傳準備中, 請稍後" ;

         }

        </ script >

        < asp : FileUpload ID ="FileUpload1" runat ="server" />

        < asp : Button ID ="Btn1" Text ="Submit" OnClientClick ="delayDisable();"   runat =server />

    </ div >

    </ form >

</ body >

</ html >

文章內附的範例僅允許上傳一個檔案,如果需要上傳多個檔案,可以自行添加 FileUpload 控件至 FileUpload.aspx 內。

下面是 UploadHandelr.aspx.cx 的程式碼。

using System;

using System.Collections;

using System.Configuration;

using System.Data;

using System.Web;

using System.Web.Security;

using System.Web.UI;

using System.Web.UI.HtmlControls;

using System.Web.UI.WebControls;

using System.Web.UI.WebControls.WebParts;

 

public partial class UploadHandler : System.Web.UI.Page

{

    protected void Page_Load(object sender, EventArgs e)

    {

        if (Request.QueryString["UID" ] == null )

        {

            Response.Write("invalid UID" );

            Response.Flush();

        }

        else

            Btn1.PostBackUrl = "Handler.ashx?UID=" + Request.QueryString["UID" ];

    }

}

於此,我利用了 Cross-Page Postback 機制,將 Submit 動作導向 Handler.ashx 中,下面是 .ashx 的程式碼。

<% @ WebHandler Language ="C#" Class ="Handler" %>

 

using System;

using System.Web;

using System.Reflection;

using System.Security.Permissions;

using System.IO;

using System.Web.UI;

 

public class Handler : IHttpHandler {

   

    public void ProcessRequest (HttpContext context) {

        if (UploadFrameHelper .HandleUpload())

        {

             // 於此儲存上傳的檔案.

            // ie:

            //    context.Request.Files[0].SaveAs(@"c:/temp1/upload.xxx");

            //Page p = UploadFrameHelper.GetPreviousPage();

            context.Response.Write(

                       context.Request.Files[0].FileName);           

        }

    }

 

    public bool IsReusable {

        get {

            return false ;

        }

    }

}

請注意粗體字的部份,本文內附的範例只是於上傳檔案後顯示檔案名稱,並沒有將檔案存到硬碟中,在實際應用上,你可以呼叫 context.Request.Files[0].SaveAs 來儲存第一個上傳檔案至指定目錄及檔名,呼叫 context.Request.Files[1].SaveAs 來儲存第二個上傳檔案,以此類推。

下圖是此範例的執行畫面。

 

另外,此範例也整合了前篇文章所提及的檔案上傳過大的處理,讀者們可於 web.config 中的 HttpRuntime 區段修改 maxRequestLength 的值來限制上傳檔案的最大容量。

(PS: 嫌進度列太醜嗎?呵,我的 ASP.NET AJAX/Silverlight 聖典一書中有漂亮點的哦。 )

 

關於測試

 

  這個範例及技巧,已於自身的 Web Development Server IIS 及實際網路上的 ASP.NET 網路空間測試過,在 256K 上傳的頻寬,目前最大測試過上傳過 300 MB ,未發生任何錯誤。

 

為何 delay.....

 

  這個範例的完成時間是 2008/1/30 號,遲遲未公佈的主要原因是那時我正忙於【極意之道 -.NET Framework 3.5 資料庫開發聖典 ASP.NET 篇】的撰寫工作,隨著書即將於 4/18 號左右上市,此篇文章也沒有再拖延下去的理由了。

  在公佈這篇文章時,我內心其實有些許的掙扎,原由是曾和出版社討論過另一本新書的企劃,書中將會列舉出許多有用、鮮為人知的 ASP.NET 手法及技巧,而此篇文章正巧可做為賣點之一,於此將其公佈,對我並沒有實質的好處,不過由於早已答應各位讀者,索性就不管了,日後若要製作該書,我再尋其它手法來取代此手法於書中的地位便是。

 

^_^

 

本文範例下載:

http://www.dreams.idv.tw/~code6421/files/UploadWithProgress2.zip

 

-------------------------------------------------------------------------------------------------

ASP.NET 上傳進度顯示 - 補遺 (Update 04/16)

 

下列是ASP.NET上傳機制的補遺.

 

1. UploadFrameHelper 提供了GetPreviousPage函式,可以取得Cross-Page PostBack時
   的Page,如下所示:
  

<%@ WebHandler Language="C#" Class="Handler" %>

using System;
using System.Web;
using System.Reflection;
using System.Security.Permissions;
using System.IO;
using System.Web.UI;

public class Handler : IHttpHandler {
   
    public void ProcessRequest (HttpContext context) {
        if (UploadFrameHelper.HandleUpload())
        {
            // 於此儲存上傳的檔案.
            // ie:
            //    context.Request.Files[0].SaveAs(@"c:/temp1/upload.xxx");
            Page p = UploadFrameHelper.GetPreviousPage();
            System.Web.UI.WebControls.FileUpload fl =   (System.Web.UI.WebControls.FileUpload)p.FindControl("FileUpload1");
            fl.SaveAs(@"c:/uploadfile"+fl.FileName);
            context.Response.Write(context.Request.Files[0].FileName);           
        }
    }
 
    public bool IsReusable {
        get {
            return false;
        }
    }

}


2. 這個機制事實上並不受限於maxRequestLength的設定,
   所謂的限制來自於下方的程式碼.
  
   HackUpload.cs
  
 ..........
   public bool Load()
    {
        if (_context.Request.ContentLength < GetMaxRequestSize())
        {
   ..........

     若移除此處程式碼,那麼上傳檔案將無限制,我猜大概只受限於executionTimeout及
   IO,大概是2GB 或 4GB吧.
  
3.ASP.NET Runtime 的記憶體耗費,記憶中, ASP.NET Runtime是將上傳的檔案先行存在記憶體中的,

 這意味著當上傳檔案很大,而且有很多人使用時,IIS將會耗掉許多記憶體.

 從追蹤ASP.NET Runtime的處理機制來看,我想會有辦法解決此問題,不過得待我有空時才能好好看看.

 

 

<think>好的,用户想知道OLED显示屏如何显示负数。首先,我需要结合提供的引用内容来分析可能的解决方案。引用13提到OLED驱动程序支持硬件软件I2C,这说明显示内容是由微控制器(如STM32)通过程序控制的。显示负数通常涉及数到字符串的转换,以及字库的支持。 首先,OLED本身只能显示像素点,没有内置处理负数的功能。所以负数显示需要软件处理。步骤可能包括:将负数转换为符号的字符串,比如将-123转换为"-123",然后调用显示函数渲染字符。 接着,参考引用3中的代码配置,可能需要检查字库是否包含负号。如果字库中没有,需要自定义符号或使用现有字符替代。比如,使用减号或者下划线组合成负号。 然后,考虑显示位置。负数需要预留符号位,避免数字溢出屏幕。例如,如果显示区域宽度有限,需要调整数的位置,确保负号显示正确。 最后,结合引用2提到的图像处理,虽然用户问题不涉及AI检测,但图像预处理中的步骤可能对字符显示优化有参考意义,比如像素排列或对比度调整,确保负号清晰可见。 总结:显示负数需软件处理,转换数为字符串,检查字库支持,调整显示位置,必要时自定义字符。</think>在OLED显示屏上显示负数需要通过软件编程实现数到字符串的转换,并结合显示屏的字库支持。以下是具体步骤: ### 1. **数符号判断与转换** 首先需判断数是否为负。若为负数,则在字符串首部添加负号`-`,并将数转换为绝对处理。例如: ```c int num = -123; char str[20]; if(num < 0) { str[0] = '-'; num = -num; } itoa(num, &str[1], 10); // 将数转换为字符串 ``` ### 2. **字库支持验证** OLED显示依赖预存字库(如ASCII码表): - 需确认字库是否包含负号`-`(ASCII码为`0x2D`) - 若字库无负号,需自定义字符点阵,例如: ```c // 自定义负号点阵(8x16像素) const uint8_t minus_char[16] = {0x00, 0x00, 0x00, 0x7F, 0x7F, 0x00, 0x00, 0x00, ...}; ``` ### 3. **显示位置调整** 负号占用1个字符宽度,需调整显示坐标。例如使用`OLED_ShowString(x, y, str)`函数时: ```c // 设置起始坐标偏移量 OLED_ShowString(x + (is_negative ? 8 : 0), y, str); // 假设每个字符宽度为8像素 ``` ### 4. **硬件驱动配置** 根据引用[3],需确保I2C通信正常: - 启用硬件I2C时取消注释`#define OLED_USE_HW_I2C` - 验证引脚定义`I2C3_SCL``I2C3_SDA`与实际电路匹配 ### 示例代码 ```c void ShowNumber(int16_t num, uint8_t x, uint8_t y) { char buffer[10]; uint8_t idx = 0; if(num < 0) { buffer[idx++] = '-'; num = -num; } // 转换为字符串 itoa(num, &buffer[idx], 10); // 调用显示函数 OLED_ShowString(x, y, buffer); } ``` ### 注意事项 - **动态刷新**:数变化时需先清空局部区域再写入新,避免残影 - **对齐方式**:负数可能导致右对齐错位,建议使用固定宽度格式(如`%4d`) ### 相关扩展问题 1. 如何实现OLED显示浮点数的小数点? 需将浮点拆分为整数小数部分,分别转换后插入小数点符号[^3]。 2. OLED显示汉字的实现方法? 需要扩展中文字库,并通过字模提取工具生成点阵数据[^1]。 3. 如何优化负数显示的刷新效率? 可使用局部刷新技术,仅更新数变化区域[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值