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

ASP.NET 即時訊息發送功能改寫:多使用者選擇、數量限制、重導至訊息編輯頁

     編輯:關於ASP.NET

原始程式碼展示一個 Web Forms 頁面,使用者從 `CheckBoxList` 選取多位收件者,點擊按鈕後驗證選取人數 (最多 5 人),將收件者 ID 串聯成字串重導至 `info.aspx` 進行訊息編輯發送。以下重構為現代、可維護、安全的實作。

原始邏輯問題分析

  • 硬式編碼限制 (5 人) 散落在事件處理器中
  • 字串手工拼接 (`tostu_id += ... + "@"`),易產生尾端分隔符、注入風險
  • 直接 `Response.Redirect` 攜帶 QueryString,長度受限、敏感資料外露
  • 無輸入驗證 (收件者是否存在、是否為有效使用者)
  • UI 與業務邏輯強耦合,難以單元測試

重構設計

1. 定義領域模型與驗證


public class MessageRecipient
{
    public string UserId { get; set; }
    public string DisplayName { get; set; }
}

public class SendMessageRequest
{
    public List Recipients { get; set; } = new();
    public string Subject { get; set; }
    public string Body { get; set; }
}

public static class MessagePolicy
{
    public const int MaxRecipients = 5;
    public const int MaxSubjectLength = 200;
    public const int MaxBodyLength = 5000;
}

2. 服務層:收件者驗證與暫存


public interface IMessageService
{
    // 驗證收件者是否存在、有效,回傳合法清單
    Task> ValidateRecipientsAsync(IEnumerable userIds);
    
    // 暫存訊息草稿,回傳 Token (避免 QueryString 傳大量資料)
    Task SaveDraftAsync(SendMessageRequest request, string senderId);
    
    // 發送訊息
    Task SendAsync(string draftToken, string senderId);
}

public class MessageService : IMessageService
{
    private readonly IDistributedCache _cache; // Redis / MemoryCache
    private readonly IUserRepository _users;

    public async Task> ValidateRecipientsAsync(IEnumerable userIds)
    {
        var distinctIds = userIds.Distinct().ToList();
        if (distinctIds.Count > MessagePolicy.MaxRecipients)
            throw new ArgumentException($"收件者不可超過 {MessagePolicy.MaxRecipients} 人");
        
        var users = await _users.GetByIdsAsync(distinctIds);
        return users.Select(u => new MessageRecipient { UserId = u.Id, DisplayName = u.Name }).ToList();
    }

    public async Task SaveDraftAsync(SendMessageRequest request, string senderId)
    {
        var token = Guid.NewGuid().ToString("N");
        var draft = new MessageDraft 
        { 
            Token = token, 
            SenderId = senderId, 
            Recipients = request.Recipients,
            Subject = request.Subject,
            Body = request.Body,
            CreatedAt = DateTime.UtcNow
        };
        await _cache.SetStringAsync($"msg:draft:{token}", JsonSerializer.Serialize(draft), 
            new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1) });
        return token;
    }

    public async Task SendAsync(string draftToken, string senderId)
    {
        var json = await _cache.GetStringAsync($"msg:draft:{draftToken}");
        if (json == null) throw new InvalidOperationException("草稿不存在或已過期");
        
        var draft = JsonSerializer.Deserialize(json);
        if (draft.SenderId != senderId) throw new UnauthorizedAccessException();
        
        // 實際發送邏輯 (寫入資料庫、推播通知、Email 等)
        await _messageRepository.CreateAsync(draft);
        await _cache.RemoveAsync($"msg:draft:{draftToken}");
    }
}

3. Controller / 頁面邏輯 (MVC 模式展示,Web Forms 同理)


public class MessageController : Controller
{
    private readonly IMessageService _msgService;

    // 步驟 1:選擇收件者 → POST 驗證 → 重導編輯頁
    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task SelectRecipients(string[] recipientIds)
    {
        try
        {
            var recipients = await _msgService.ValidateRecipientsAsync(recipientIds);
            // 暫存收件者至 TempData (跨重導保留一請求)
            TempData["Recipients"] = JsonSerializer.Serialize(recipients);
            return RedirectToAction("Compose");
        }
        catch (ArgumentException ex)
        {
            ModelState.AddModelError("", ex.Message);
            return View("Index", GetUserList());
        }
    }

    // 步驟 2:編輯頁面
    public IActionResult Compose()
    {
        var recipients = TempData.Peek("Recipients") as string; // 不消費,供表單再次使用
        if (recipients == null) return RedirectToAction("Index");
        return View(new ComposeViewModel { RecipientsJson = recipients });
    }

    // 步驟 3:送出發送
    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task Send(ComposeViewModel model)
    {
        if (!ModelState.IsValid) return View("Compose", model);
        
        var recipients = JsonSerializer.Deserialize>(model.RecipientsJson);
        var request = new SendMessageRequest 
        { 
            Recipients = recipients, 
            Subject = model.Subject, 
            Body = model.Body 
        };
        
        string draftToken = await _msgService.SaveDraftAsync(request, User.Identity.Name);
        await _msgService.SendAsync(draftToken, User.Identity.Name);
        
        TempData.Remove("Recipients");
        TempData["SuccessMessage"] = "訊息已發送";
        return RedirectToAction("Index");
    }
}

4. 前端 View (Razor)


@model ComposeViewModel
收件者: @Html.Raw(JsonSerializer.Deserialize>(Model.RecipientsJson) .Select(r => r.DisplayName).Join(", "))
取消

關鍵改進點總結

原始做法重構做法效益
字串拼接 ID 串強型別 List + JSON 序列化型別安全、可擴充屬性、無分隔符問題
QueryString 傳遞收件者TempData / 分散式快取 (Redis) 暫存草稿 Token無長度限制、敏感資料不外露、支援大量收件者
硬式編碼 5 人限制MessagePolicy.MaxRecipients 集中管理易維護、可配置、單元測試友善
無收件者驗證服務層 ValidateRecipientsAsync 驗證存在性、有效性資料完整性、安全性
UI 事件處理器包含業務邏輯Controller 薄、Service 富、Model 富關注點分離、可測試、可復用

Web Forms 版本快速修正 (若無法重構架構)


protected void btnSend_Click(object sender, EventArgs e)
{
    var selectedIds = cblRecipients.Items.Cast()
        .Where(i => i.Selected)
        .Select(i => i.Value)
        .Distinct()
        .ToList();

    if (selectedIds.Count > 5)
    {
        lblError.Text = "最多選擇 5 位收件者";
        return;
    }

    // 驗證收件者有效性 (略)

    // 使用 TempData 等價機制:Session + 頁面標記
    string token = Guid.NewGuid().ToString("N");
    Session[$"MsgDraft_{token}"] = new { Recipients = selectedIds, Time = DateTime.Now };
    Response.Redirect($"ComposeMessage.aspx?token={token}");
}

核心原則:**資料結構化、驗證服務化、狀態伺服器端暫存、敏感資訊不進 URL、策略集中管理**。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved