首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> C# >> 關於C#

C# 結構化存儲在 Web 開發中的應用實戰

     編輯:關於C#

背景動機

網站地名資訊系統含 70 萬檔案,直接存檔案系統面臨:

  • 即時查詢壓力大(資料庫/WEB 伺服器負載高)。
  • 快取檔案維護困難,FTP 刪除麻煩,單目錄檔案過多影響效能。
  • 主機商限制檔案數 5 萬,空間 5GB 無法充分利用。

結構化存儲可將大量檔案封裝為單一大檔案,繞過檔案數限制,並整合 GZIP 壓縮輸出。

技術選型

  • 參考:JianHua Zhang《C# COM+ 結構化存儲》、BlueDog《結構化存儲 C# 類庫》。
  • 採用 BlueDog 庫(ExpertLib.IO.Storage),修正其 EnumElements 未釋放 COM 物件導致控制代碼洩漏的 Bug。
  • 未使用 VirtualPathProvider:架構較複雜、彈性不足、難整合 GZIP。
  • 採用繼承架構:基類 FileSystem、衍生 FileSystemFFS(結構化存儲)、FileSystemOS(實體檔案)。

核心架構程式碼

基類 FileSystem

using System;
using System.IO;
using System.Web;
using System.IO.Compression;

namespace Step1.WebFileSystem
{
    public class FileSystem
    {
        protected string path, physicsPath;
        protected Handler handle;
        protected HttpContext httpContext = null;
        protected Stream stream = null;

        public FileSystem(string path, Handler handle)
        {
            httpContext = HttpContext.Current;
            this.path = path;
            this.physicsPath = httpContext.Server.MapPath(path);
            this.handle = handle;
        }

        public virtual bool Exists() => false;
        public virtual DateTime GetLastWriteTime() => DateTime.Now;
        
        public virtual Stream GetWriter()
        {
            stream = httpContext.Response.OutputStream;
            return handle.UseGzip ? (Stream)new GZipStream(stream, CompressionMode.Compress) : stream;
        }
        
        public virtual Stream GetReader() => null;
        public virtual void TransmitFile() { return; }
        
        public virtual void Close()
        {
            if (stream != null) { stream.Close(); stream = null; }
        }
        
        public virtual void Dispose() { Close(); }
        
        public void TransmitStream(bool unZip)
        {
            Stream reader = unZip ? new GZipStream(GetReader(), CompressionMode.Decompress) : GetReader();
            byte[] buffer = new byte[1024];
            int p;
            while ((p = reader.Read(buffer, 0, 1024)) > 0)
            {
                httpContext.Response.OutputStream.Write(buffer, 0, p);
                httpContext.Response.Flush();
            }
            Close();
        }
        
        public void ResponseFile()
        {
            string acceptEncoding = httpContext.Request.Headers["Accept-Encoding"];
            if (handle.UseGzip && acceptEncoding != null && acceptEncoding.IndexOf("gzip") >= 0)
            {
                httpContext.Response.AddHeader("Content-Encoding", "gzip");
                TransmitFile();
            }
            else
            {
                if (handle.UseGzip) TransmitStream(true);
                else TransmitFile();
            }
        }
        
        protected void CreateFolder(string path, bool createCurrent)
        {
            string folder = Path.GetDirectoryName(path);
            if (!Directory.Exists(folder)) CreateFolder(folder, true);
            if (createCurrent) Directory.CreateDirectory(path);
        }
        
        public static FileSystem Create(string path)
        {
            Configuration config = Configuration.Instance();
            if (config != null)
            {
                for (int i = 0; i < config.handlers.Length; i++)
                {
                    if (config.handlers[i].UrlRegex.IsMatch(path))
                    {
                        switch (config.handlers[i].HandlerType)
                        {
                            case "FFS": return new FileSystemFFS(path, config.handlers[i]);
                            case "OS": return new FileSystemOS(path, config.handlers[i]);
                            default: return new FileSystem(path, config.handlers[i]);
                        }
                    }
                }
            }
            return new FileSystem(path, new Handler());
        }
    }
}

結構化存儲實作 FileSystemFFS

using System;
using System.IO;
using System.Web;
using ExpertLib;
using ExpertLib.IO;
using ExpertLib.IO.Storage;
using System.Text.RegularExpressions;

namespace Step1.WebFileSystem
{
    public class FileSystemFFS : FileSystem
    {
        private Storage ffsFile = null;
        private string filePath, ffsPath;
        private bool writeError = false;

        public FileSystemFFS(string path, Handler handle) : base(path, handle)
        {
            this.ffsPath = httpContext.Server.MapPath(Regex.Replace(path, handle.Pattern, handle.SavePath));
            this.filePath = Regex.Replace(path, handle.Pattern, handle.SaveName).Replace('/', '_');
        }

        private void Open()
        {
            ffsFile = StorageFile.OpenStorageFile(this.ffsPath);
        }

        public override bool Exists()
        {
            if (ffsFile == null)
            {
                FileInfo fi = new FileInfo(this.ffsPath);
                if (!fi.Exists) return false;
                Open();
            }
            return ffsFile.IsElementExist(this.filePath);
        }

        public override DateTime GetLastWriteTime()
        {
            if (ffsFile == null)
            {
                FileInfo fi = new FileInfo(this.ffsPath);
                if (!fi.Exists) return DateTime.Now;
                Open();
            }
            var elements = ffsFile.GetChildElementsInfo();
            for (int i = elements.Count - 1; i >= 0; i--)
                if (elements[i].Name == filePath) return elements[i].LastModifyTime;
            return DateTime.Now;
        }

        public override Stream GetWriter()
        {
            Close();
            if (ffsFile == null)
            {
                if (!File.Exists(this.ffsPath))
                {
                    CreateFolder(this.ffsPath, false);
                    ffsFile = StorageFile.CreateStorageFile(this.ffsPath, StorageCreateMode.Create, 
                        StorageReadWriteMode.ReadWrite, StorageShareMode.ShareDenyWrite, StorageTransactedMode.Direct);
                }
                else Open();
            }
            stream = (Stream)ffsFile.CreateStream(filePath);
            if (stream == null) { writeError = true; return base.GetWriter(); }
            return handle.UseGzip ? (Stream)new GZipStream(stream, CompressionMode.Compress) : stream;
        }

        public override Stream GetReader()
        {
            Close();
            if (ffsFile == null)
            {
                FileInfo fi = new FileInfo(this.ffsPath);
                if (!fi.Exists) return null;
                Open();
            }
            return stream = (Stream)ffsFile.OpenStream(filePath);
        }

        public override void TransmitFile()
        {
            if (writeError) base.TransmitFile();
            else TransmitStream(false);
        }

        public override void Dispose()
        {
            Close();
            if (ffsFile != null) { ffsFile.Dispose(); ffsFile = null; }
        }
    }
}

頁面調用範例

DateTime lastWriteTime = DateTime.Now;
bool isExists = wfs.Exists() && DateTime.Now.Subtract(lastWriteTime = wfs.GetLastWriteTime()).TotalDays <= fileKeepDays;
lastWriteTime = isExists ? lastWriteTime : DateTime.Now;

Response.Cache.SetExpires(lastWriteTime.AddDays(fileKeepDays));
Response.Cache.SetLastModified(lastWriteTime.AddMinutes(-1));
Response.ContentType = "text/html";

if (!isExists)
{
    Stream writer = null;
    try
    {
        writer = wfs.GetWriter();
        // 這裡寫入頁面內容,通常用 XSLT 輸出
    }
    finally
    {
        if (writer != null) writer.Close();
        wfs.Dispose();
    }
}
wfs.ResponseFile();
wfs.Dispose();

Web.config 設定

<Handler pattern="^/place/cn/((?:[^$/]+/){3})([^$]+).aspx" 
         handlerType="FFS" useGzip="true" 
         savePath="/place/cn/$1FFS.resx" saveName="$2" />
<Handler pattern="^/place/cn/([^$]+).aspx" 
         handlerType="OS" useGzip="true" 
         savePath="/place/cn/$1.aspx" />

前三級行政區(省市縣)用實體檔案快取,後續層級用結構化存儲(按縣分檔)。

BlueDog 庫 Bug 修正

EnumElements 取得的 IEnumSTATSTG 需呼叫 Marshal.ReleaseComObject 釋放,否則檔案控制代碼洩漏,下次開啟失敗。

修正前後對比

// IsElementExist 修正
public bool IsElementExist(string elementName)
{
    // ... 略 ...
    IEnumSTATSTG statstg;
    ComTypes.STATSTG stat;
    uint k;
    this.storage.EnumElements(0, IntPtr.Zero, 0, out statstg);
    statstg.Reset();
    bool found = false;
    while (statstg.Next(1, out stat, out k) == HRESULT.S_OK)
    {
        if (string.Compare(stat.pwcsName, elementName, true) == 0) { found = true; break; }
    }
    Marshal.ReleaseComObject(statstg); // 新增:釋放 COM 列舉器
    return found;
}

// GetChildElementsInfo 修正
public List<StgElementInfo> GetChildElementsInfo()
{
    // ... 略 ...
    IEnumSTATSTG statstg;
    ComTypes.STATSTG stat;
    uint k;
    List<StgElementInfo> list = new List<StgElementInfo>();
    this.storage.EnumElements(0, IntPtr.Zero, 0, out statstg);
    statstg.Reset();
    while (statstg.Next(1, out stat, out k) == HRESULT.S_OK)
        list.Add(new StgElementInfo(stat));
    Marshal.ReleaseComObject(statstg); // 新增:釋放 COM 列舉器
    return list;
}
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved