You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ASP.NET Core Razor Pages文件查看后通知徽章未正确更新问题

问题:ASP.NET Core Razor Pages中文件查看后通知徽章重新出现的问题

我正在开发一个ASP.NET Core Razor Pages项目,用户可查看和管理请求。每个请求对应一个通知徽章,用户查看请求关联文件后该徽章应消失,且已查看过文件的请求在用户重新访问页面时徽章不应重现。

但目前遇到问题:用户查看文件并更新数据库后,重新访问页面时通知徽章仍会再次出现。


表结构

CREATE TABLE [dbo].[VisualizacaoArquivo](
    [IdVisualizacaoArquivo] [int] IDENTITY(1,1) NOT NULL,
    [IdRequisicao] [int] NOT NULL,
    [IdUsuario] [int] NOT NULL,
    [DataVisualizacao] [datetime] NOT NULL,
    CONSTRAINT [PK_VisualizacaoArquivo] PRIMARY KEY CLUSTERED ([IdVisualizacaoArquivo] ASC)
);

Requisicao.cshtml.cs

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Linq;
using System.Threading.Tasks;

namespace ProcurementSystem.Pages.Request
{
    [Authorize]
    public class RequisicaoModel : PageModel
    {
        public List<RequisicaoInfo> ListRequests = new List<RequisicaoInfo>();

        public void OnGet()
        {
            string userId = User.Claims.FirstOrDefault(c => c.Type == "IdUsuario")?.Value;

            try
            {
                string connectionString = "Data Source=...; Initial Catalog=...; User id=...; Password=...!";
                using (SqlConnection connection = new SqlConnection(connectionString))
                {
                    connection.Open();
                    string sql = @"
                    SELECT 
                        a.IdRequisicao, 
                        a.NomeRequisicao, 
                        b.TipoRequisicao,  
                        a.DataRequisicao, 
                        ISNULL(a.DataConclusao, '') AS DataConclusao, 
                        ISNULL(a.IdUsuarioAtendente, 0) AS IdUsuarioAtendente, 
                        ISNULL(c.Nome, '') AS NomeAtendente, 
                        a.IdUsuarioRequisitante, 
                        d.Nome AS NomeRequisitante, 
                        e.DescricaoStatus,
                        f.Operacao,
                        g.Comentario AS UltimoComentario,
                        h.DescricaoStatusComentario AS StatusComentario,
                        i.DataVisualizacao AS UltimaVisualizacaoArquivos
                    FROM 
                        [dbo].[Requisicao] a
                    LEFT JOIN 
                        [dbo].[TipoRequisicao] b ON a.IdTipoRequisicao = b.IdTipoRequisicao  
                    LEFT JOIN 
                        [dbo].[Usuario] c ON a.IdUsuarioAtendente = c.IdUsuario
                    LEFT JOIN 
                        [dbo].[Usuario] d ON a.IdUsuarioRequisitante = d.IdUsuario
                    LEFT JOIN 
                        [dbo].[StatusRequisicao] e ON a.IdStatusRequisicao = e.IdStatus
                    LEFT JOIN 
                        [dbo].[Operacao] f ON a.IdOperacao = f.IdOperacao
                    LEFT JOIN 
                        [dbo].[HistoricoRequisicao] g ON a.IdRequisicao = g.IdRequisicao AND g.IdRequisicaoHist = (
                            SELECT TOP 1 IdRequisicaoHist 
                            FROM [dbo].[HistoricoRequisicao] 
                            WHERE IdRequisicao = a.IdRequisicao 
                            ORDER BY DataForecast DESC
                        )
                    LEFT JOIN 
                        [dbo].[StatusRequisicaoComentario] h ON g.IdStatusComentario = h.IdStatusComentario
                    LEFT JOIN
                        [dbo].[VisualizacaoArquivo] i ON a.IdRequisicao = i.IdRequisicao AND i.IdUsuario = @UserId
                    WHERE 
                        a.FlagAtiva = 1 and FlagAssumiu = 1";

                    using (SqlCommand command = new SqlCommand(sql, connection))
                    {
                        command.Parameters.AddWithValue("@UserId", userId);

                        using (SqlDataReader reader = command.ExecuteReader())
                        {
                            while (reader.Read())
                            {
                                RequisicaoInfo requInfo = new RequisicaoInfo
                                {
                                    IdRequisicao = reader.GetInt32(0).ToString(),
                                    NomeRequisicao = reader.GetString(1),
                                    TipoRequisicao = reader.GetString(2),
                                    DataRequisacao = reader.GetDateTime(3).ToString("yyyy-MM-dd"),
                                    DataConclusao = reader.IsDBNull(4) ? string.Empty : reader.GetDateTime(4).ToString("yyyy-MM-dd"),
                                    IdUsuarioAtendente = reader.IsDBNull(5) ? string.Empty : reader.GetInt32(5).ToString(),
                                    NomeAtendente = reader.GetString(6),
                                    IdUsuarioRequisitante = reader.GetInt32(7).ToString(),
                                    NomeRequisitante = reader.GetString(8),
                                    DescricaoStatus = reader.GetString(9),
                                    Operacao = reader.GetString(10),
                                    UltimoComentario = reader.GetString(11),
                                    StatusComentario = reader.GetString(12),
                                    UltimaVisualizacaoArquivos = reader.IsDBNull(13) ? string.Empty : reader.GetDateTime(13).ToString("yyyy-MM-dd HH:mm:ss")
                                };
                                ListRequests.Add(requInfo);
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                // Handle exception
            }
        }

        [HttpPost]
        public async Task<IActionResult> OnPostVisualizarArquivosAsync([FromForm] int idRequisicao)
        {
            if (idRequisicao == 0)
            {
                return BadRequest(new { message = "IdRequisicao is missing or invalid." });
            }

            string userId = User.Claims.FirstOrDefault(c => c.Type == "IdUsuario")?.Value;
            if (string.IsNullOrEmpty(userId))
            {
                return BadRequest(new { message = "User ID is missing." });
            }

            string connectionString = "Data Source=...; Initial Catalog=...; User id=...; Password=...!";
            try
            {
                using (SqlConnection connection = new SqlConnection(connectionString))
                {
                    await connection.OpenAsync();
                    string sql = @"
                IF EXISTS (SELECT 1 FROM [dbo].[VisualizacaoArquivo] WHERE IdRequisicao = @IdRequisicao AND IdUsuario = @UserId)
                BEGIN
                    UPDATE [dbo].[VisualizacaoArquivo]
                    SET DataVisualizacao = @DataVisualizacao
                    WHERE IdRequisicao = @IdRequisicao AND IdUsuario = @UserId
                END
                ELSE
                BEGIN
                    INSERT INTO [dbo].[VisualizacaoArquivo] (IdRequisicao, IdUsuario, DataVisualizacao)
                    VALUES (@IdRequisicao, @UserId, @DataVisualizacao)
                END";

                    using (SqlCommand command = new SqlCommand(sql, connection))
                    {
                        command.Parameters.AddWithValue("@DataVisualizacao", DateTime.Now);
                        command.Parameters.AddWithValue("@IdRequisicao", idRequisicao);
                        command.Parameters.AddWithValue("@UserId", userId);

                        await command.ExecuteNonQueryAsync();
                    }
                }
            }
            catch (Exception ex)
            {
                return StatusCode(500, new { message = $"An error occurred: {ex.Message}" });
            }

            return new JsonResult(new { success = true });
        }
    }
}

Requisicao.cshtml

<div class="modal fade" id="andamentosModal" tabindex="-1" aria-labelledby="andamentosModalLabel" aria-hidden="true">
    <div class="modal-dialog modal-lg">
        <div class="modal-content">
            <div class="modal-header">
                <h5 class="modal-title" id="andamentosModalLabel">Andamentos da Requisição</h5>
                <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
            </div>
            <div class="modal-body">
                <ul class="nav nav-tabs" id="andamentoTab" role="tablist">
                    <li class="nav-item" role="presentation">
                        <a class="nav-link active" id="comentarios-tab" data-bs-toggle="tab" href="#comentarios" role="tab" aria-controls="comentarios" aria-selected="true">Comentários</a>
                    </li>
                    <li class="nav-item" role="presentation">
                        <a class="nav-link" id="arquivos-tab" data-bs-toggle="tab" href="#arquivos" role="tab" aria-controls="arquivos" aria-selected="false">
                            Arquivos <span id="arquivoBadge" class="badge bg-danger" style="display: none;">Novo arquivo</span>
                        </a>
                    </li>
                </ul>
                <div class="tab-content" id="andamentoTabContent">
                    <div class="tab-pane fade show active" id="comentarios" role="tabpanel" aria-labelledby="comentarios-tab">
                        <div class="scrollable-content" style="height: 300px; overflow-y: auto;">
                            <div id="message-container">
                                <!-- Mensagens dinâmicas serão carregadas aqui -->
                            </div>
                        </div>
                    </div>
                    <div class="tab-pane fade" id="arquivos" role="tabpanel" aria-labelledby="arquivos-tab">
                        <div class="scrollable-content" style="height: 300px; overflow-y: auto;">
                            <div id="file-container">
                                <!-- Lista de arquivos será carregada aqui -->
                            </div>
                        </div>
                    </div>
                </div>
            </div>
            <div class="modal-footer">
                <div class="fixed-content w-100">
                    <div class="mb-3">
                        <label for="formFile" class="form-label">Carregar arquivo (opcional)</label>
                        <input class="form-control" type="file" id="formFile">
                    </div>
                    <div class="form-floating mb-3">
                        <textarea class="form-control" placeholder="Deixe um comentário aqui" id="floatingTextarea2" style="height: 100px"></textarea>
                        <label for="floatingTextarea2">Adicionar um comentário</label>
                    </div>
                    <div class="mb-3">
                        <label for="listaStatus">Selecione o status:</label>
                        <select id="statusSelect" class="form-control"></select>
                    </div>
                    <button type="button" id="btnInserirComentario" class="btn btn-primary">Inserir Comentário</button>
                    <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Fechar</button>
                </div>
            </div>
        </div>
    </div>
</div>

<script>
    $('#arquivos-tab').on('shown.bs.tab', function () {
        $('#arquivoBadge').hide();

        var selectedData = $('#tabelaRequisicoes').DataTable().row('.selected').data();

        if (selectedData && selectedData.IdRequisicao) {
            var idRequisicao = selectedData.IdRequisicao;

            var postData = {
                idRequisicao: idRequisicao
            };

            console.log("Dados a serem enviados:", postData); // Adicionando log
            var token = $('input[name="__RequestVerificationToken"]').val();

            $.ajax({
                type: 'POST',
                url: '/Request/Requisicao?handler=VisualizarArquivos',
                data: postData,
                headers: {
                    "RequestVerificationToken": token
                },
                success: function (data) {
                    console.log("Arquivos marcados como visualizados:", data);
                },
                error: function (jqXHR, textStatus, errorThrown) {
                    console.error("Erro ao marcar arquivos como visualizados:", textStatus, errorThrown);
                    console.error("Response:", jqXHR.responseText); // Adicionando log para a resposta do erro
                }
            });
        } else {
            console.warn("Nenhuma linha selecionada ao tentar marcar arquivos como visualizados.");
        }
    });
</script>

排查方向与改进建议

1. 补全页面加载时的徽章显示逻辑

当前前端仅在切换到文件标签时隐藏徽章,但页面初始化时没有根据数据库返回的UltimaVisualizacaoArquivos值判断是否显示徽章。需要在DataTable渲染完成后,针对选中的请求动态控制徽章状态:

$('#tabelaRequisicoes').DataTable({
    // 其他配置项
    initComplete: function() {
        var table = this.api();
        // 监听行选中事件
        table.on('select', function(e, dt, type, indexes) {
            if (type === 'row') {
                var data = table.rows(indexes).data()[0];
                // 根据是否有查看记录控制徽章显示
                $('#arquivoBadge').toggle(data.UltimaVisualizacaoArquivos === '');
            }
        });
    }
});

2. 验证用户ID与参数绑定的正确性

  • 确认User.Claims中IdUsuario的类型是否为整数,若为字符串类型,在SQL查询中可能导致关联失败,需转换为整数后传入参数:
    if (!int.TryParse(userId, out int userIdInt))
    {
        // 处理用户ID无效的情况
        return;
    }
    command.Parameters.AddWithValue("@UserId", userIdInt);
    
  • 前端AJAX请求中,将selectedData.IdRequisicao转换为整数后传递,避免字符串类型导致后端参数绑定失败:
    var idRequisicao = parseInt(selectedData.IdRequisicao);
    

3. 优化数据库操作的原子性

将原有的IF EXISTS+UPDATE/INSERT逻辑替换为MERGE语句,避免并发场景下的更新异常:

MERGE INTO [dbo].[VisualizacaoArquivo] AS target
USING (VALUES (@IdRequisicao, @UserId, @DataVisualizacao)) AS source (IdRequisicao, IdUsuario, DataVisualizacao)
ON target.IdRequisicao = source.IdRequisicao AND target.IdUsuario = source.IdUsuario
WHEN MATCHED THEN
    UPDATE SET DataVisualizacao = source.DataVisualizacao
WHEN NOT MATCHED THEN
    INSERT (IdRequisicao, IdUsuario, DataVisualizacao)
    VALUES (source.IdRequisicao, source.IdUsuario, source.DataVisualizacao);

4. 添加调试日志确认数据库更新

在OnPostVisualizarArquivosAsync方法中添加日志,记录操作结果,确认数据库是否正确更新:

// 注入ILogger
private readonly ILogger<RequisicaoModel> _logger;
public RequisicaoModel(ILogger<RequisicaoModel> logger)
{
    _logger = logger;
}

// 在ExecuteNonQueryAsync后添加日志
int rowsAffected = await command.ExecuteNonQueryAsync();
_logger.LogInformation("Visualização de arquivo registrada: Usuário {UserId}, Requisição {IdRequisicao}, Linhas afetadas: {RowsAffected}", userId, idRequisicao, rowsAffected);

5. 确保AntiForgeryToken正确传递

若页面中没有自动生成的__RequestVerificationToken隐藏字段,需在Razor页面顶部添加:

@Html.AntiForgeryToken()

内容的提问来源于stack exchange,提问作者Lucas Abrantes

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 01:43:09