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

ASP.NET AJAX 實作評論「讚/踩」功能:jQuery + Generic Handler + NHibernate 完整實戰

     編輯:ASP.NET 基礎

系統架構概覽

採用經典 **jQuery AJAX + .ashx 處理常式 + NHibernate ORM** 三層架構,實現評論的非同步讚/踩計數更新。

功能示意圖

資料庫與實體設計 (PostgreSQL + NHibernate)

1. 資料表結構

CREATE TABLE "Info" (
    "Id" SERIAL PRIMARY KEY,
    "Content" TEXT NOT NULL
);

CREATE TABLE "Review" (
    "Id" SERIAL PRIMARY KEY,
    "InfoId" INT NOT NULL REFERENCES "Info"("Id"),
    "Content" TEXT NOT NULL,
    "Support" INT DEFAULT 0,
    "Opposition" INT DEFAULT 0
);

2. NHibernate 實體類別

public class Info
{
    public virtual int Id { get; set; }
    public virtual string Content { get; set; }
}

public class Review
{
    public virtual int Id { get; set; }
    public virtual int InfoId { get; set; }
    public virtual string Content { get; set; }
    public virtual int Support { get; set; }
    public virtual int Opposition { get; set; }
}

3. NHibernate 映射 (hbm.xml 或 Fluent NHibernate)

<class name="Review" table=""Review"">
    <id name="Id"><generator class="native"/></id>
    <property name="InfoId" column=""InfoId""/>
    <property name="Content" column=""Content""/>
    <property name="Support" column=""Support""/>
    <property name="Opposition" column=""Opposition""/>
</class>

業務邏輯層 (Service)

public class ReviewService
{
    public Review UpdateSupport(int reviewId)
    {
        using (var session = NHibernateHelper.OpenSession())
        using (var tx = session.BeginTransaction())
        {
            var review = session.Get(reviewId);
            if (review != null)
            {
                review.Support++;
                session.Update(review);
                tx.Commit();
            }
            return review;
        }
    }

    public Review UpdateOpposition(int reviewId)
    {
        using (var session = NHibernateHelper.OpenSession())
        using (var tx = session.BeginTransaction())
        {
            var review = session.Get(reviewId);
            if (review != null)
            {
                review.Opposition++;
                session.Update(review);
                tx.Commit();
            }
            return review;
        }
    }
}

AJAX 端點:Generic Handler (.ashx)

<%@ WebHandler Language="C#" Class="ChangeState" %>
using System;
using System.Web;
using AjaxDemo.BLL;
using AjaxDemo.Modal;

namespace AjaxDemo.Ajax
{
    public class ChangeState : IHttpHandler
    {
        public void ProcessRequest(HttpContext context)
        {
            // 參數驗證
            if (!int.TryParse(context.Request.QueryString["id"], out var id) ||
                !int.TryParse(context.Request.QueryString["state"], out var state))
            {
                context.Response.StatusCode = 400;
                context.Response.Write("參數錯誤");
                return;
            }

            var service = new ReviewService();
            Review review = state switch
            {
                0 => service.UpdateSupport(id),    // 0 = 讚
                1 => service.UpdateOpposition(id), // 1 = 踩
                _ => null
            };

            if (review == null)
            {
                context.Response.StatusCode = 404;
                context.Response.Write("找不到評論");
                return;
            }

            // 回傳純數字,前端直接顯示
            context.Response.ContentType = "text/plain";
            context.Response.Write(state == 0 ? review.Support : review.Opposition);
        }

        public bool IsReusable => false;
    }
}

前端頁面與 jQuery 邏輯

ASPX 頁面 (Repeater 綁定評論列表)

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="ViewDetail.aspx.cs" Inherits="AjaxDemo.ViewDetail" %>
<!DOCTYPE html>
<html xmlns="https://www.w3.org/1999/xhtml">
<head runat="server">
    <title>評論讚/踩示範</title>
    <script src="Scripts/jquery-3.7.1.min.js"></script>
    <style>
        .review-item { border-bottom: 1px solid #eee; padding: 1rem 0; }
        .vote-btn { cursor: pointer; color: #3498db; text-decoration: none; margin-right: 1rem; }
        .vote-btn.voted { color: #e74c3c; font-weight: bold; pointer-events: none; }
    </style>
</head>
<body>
    <form id="form1" runat="server">
        <div id="infoDetail">
            <h1><asp:Label ID="lblInfoContent" runat="server" /></h1>
        </div>

        <div id="reviews">
            <h3>網友評論</h3>
            <asp:Repeater ID="rptReviews" runat="server">
                <ItemTemplate>
                    <div class="review-item" data-id="<%# Eval("Id") %>">
                        <p><%# Eval("Content") %></p>
                        <a href="#" class="vote-btn btn-support" data-action="support">
                            讚 (<span class="count-support"><%# Eval("Support") %></span>)
                        </a>
                        <a href="#" class="vote-btn btn-oppose" data-action="oppose">
                            踩 (<span class="count-oppose"><%# Eval("Opposition") %></span>)
                        </a>
                    </div>
                </ItemTemplate>
            </asp:Repeater>
        </div>
    </form>

    <script>
        $(function () {
            // 事件代理:針對動態新增評論也有效
            $('#reviews').on('click', '.vote-btn', function (e) {
                e.preventDefault();
                var $btn = $(this);
                var $item = $btn.closest('.review-item');
                var reviewId = $item.data('id');
                var action = $btn.data('action'); // 'support' 或 'oppose'

                // 防重複點擊
                if ($btn.hasClass('voted')) return;

                var state = action === 'support' ? 0 : 1;
                var $countSpan = $btn.find('.count-support, .count-oppose');

                $.get('./Ajax/ChangeState.ashx', { id: reviewId, state: state })
                    .done(function (data) {
                        $countSpan.text(data); // 更新計數
                        $btn.addClass('voted'); // 標記已投票
                        // 可選:同步禁用另一個按鈕
                        $btn.siblings('.vote-btn').addClass('voted');
                    })
                    .fail(function (xhr) {
                        alert('操作失敗:' + xhr.responseText);
                    });
            });
        });
    </script>
</body>
</html>

關鍵設計決策與優化點

決策點方案理由
前端參數傳遞QueryString (?id=1&state=0)簡單、GET 請求可快取 (但此處為寫操作不可快取)、易除錯
回傳格式純文字數字輕量、無需 JSON 解析、直接 .text(data)
防重複投票前端 .voted 樣式類別 + pointer-events: none即時回饋、減少伺服器請求 (生產環境需結合 Cookie/資料庫記錄用戶投票狀態)
事件綁定jQuery 事件代理 .on('click', '.vote-btn')支援 AJAX 載入更多評論後自動生效、效能優於逐個綁定
資料庫鎖定NHibernate 隱性樂觀鎖 (Version 欄位) 或資料庫 Row Lock高併發下防止計數遺失更新,建議加 <version/> 映射

進階擴充:真實專案必備強化

  1. 用戶投票記錄:新增 UserVote 表 (UserId, ReviewId, VoteType, CreateTime),後端檢查是否已投過票。
  2. 防刷票:IP 限流、驗證碼、登入才能投票、滑塊驗證。
  3. 即時同步:引入 SignalR,投票後廣播新計數給所有觀看該頁面的用戶。
  4. 快取層:Redis 緩存熱門評論計數,降低資料庫壓力。
  5. 單元測試:Mock ISession 測試 ReviewService 邏輯,整合測試 ChangeState.ashx 端點。

部署檢查清單

  • IIS 處理常式映射:.ashx 對應 SimpleHandlerFactory (預設已設定)。
  • NHibernate 配置 hibernate.cfg.xml 正確複製至輸出目錄 (Copy if newer)。
  • PostgreSQL 驅動 Npgsql.dll 版本與 NHibernate 相容。
  • 連接字串加密 (生產環境建議用 aspnet_regiis -pef 加密 connectionStrings 區段)。

此範例展示了傳統 ASP.NET Web Forms 以最輕量方式實現 AJAX 互動的完整鏈路,適合快速原型與中小型專案;大型高併發系統建議遷移至 ASP.NET Core + EF Core + SignalR + Redis 技術棧。

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