标签: asp.net core

  • ASP.NET Core 记录请求日志

    在 ASP.NET Core 应用程序中,记录请求日志可以帮助开发者分析请求情况、调试错误以及监控应用的运行状态。本文介绍如何通过配置 Logging 记录请求日志。

    配置 appsettings.json 记录请求日志

    appsettings.json 文件的 Logging 配置中,添加或修改以下内容:

    "Logging": {
      "LogLevel": {
        "Default": "Information",
        "Microsoft": "Warning",
        "Microsoft.Hosting.Lifetime": "Information",
        // 下面两行用于记录请求日志
        "Microsoft.AspNetCore.Hosting.Diagnostics": "Information",
        "Microsoft.AspNetCore.Server.Kestrel": "Information"
      }
    }

    如果你的 appsettings.json 文件中已经有 Logging 配置,只需添加以下两行:

    "Microsoft.AspNetCore.Hosting.Diagnostics": "Information",
    "Microsoft.AspNetCore.Server.Kestrel": "Information"

    说明

    Microsoft.AspNetCore.Server.Kestrel:记录 Kestrel 服务器的详细请求信息,例如请求地址、请求头、响应状态等。

    Microsoft.AspNetCore.Hosting.Diagnostics:记录 ASP.NET Core 请求处理的相关日志,包括中间件执行等。

    运行应用并查看日志

    如果你的应用使用 控制台日志文件日志,可以在终端或日志文件中查看请求日志。例如,运行应用后,在控制台中可能会看到类似的日志输出:

    info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
          Request starting HTTP/1.1 GET http://localhost:5000/  
    info: Microsoft.AspNetCore.Hosting.Diagnostics[2]
          Request finished in 25ms 200 application/json  

    如果你希望将日志输出到文件,可以在 appsettings.json 中进一步配置日志提供程序,例如使用 SerilogNLog 进行日志存储和分析。

  • Debian使用服务单元部署asp.net core

    创建 /etc/systemd/system/your_app.service 文件,替换为适合你的应用的名称:

    [Unit]
    Description=ASP.NET Core Application
    
    [Service]
    WorkingDirectory=/path/to/your_app/publish
    ExecStart=/usr/bin/dotnet /path/to/your_app/publish/your_app.dll
    Restart=always
    RestartSec=10
    SyslogIdentifier=dotnet-your_app
    User=www-data
    Environment=ASPNETCORE_ENVIRONMENT=Production
    
    [Install]
    WantedBy=multi-user.target
    • WorkingDirectory:应用程序的工作目录(发布目录)。
    • ExecStart:启动应用的命令,这里指定 dotnet 和应用的 DLL 文件路径。
    • Restart:配置应用异常退出时自动重启。
    • SyslogIdentifier:指定日志标识符,可以方便地过滤日志。
    • User:指定运行服务的用户(例如 www-data,避免使用 root 用户)。
    • Environment:指定环境变量(如 Production)。

    启动并启用服务

    sudo systemctl enable your_app.service
    sudo systemctl start your_app.service

    查看服务状态:

    sudo systemctl status your_app.service

    查看服务日志:

    sudo journalctl -u your_app.service

    nginx 反代模板:

    server {
        listen 80;
        server_name your_domain.com;
    
        location / {
            proxy_pass https://lwbj.cn:5000;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection keep-alive;
            proxy_set_header Host $host;
            proxy_cache_bypass $http_upgrade;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }
    }

    查看实时日志输出

    要实时查看应用的日志输出,可以使用以下命令:

    sudo journalctl -u your_app.service -f
    • -u your_app.service 指定服务名称,替换为你的服务文件名称。
    • -f 表示实时跟踪日志输出(类似 tail -f)。

    查看全部日志

    查看应用自启动以来的全部日志:

    sudo journalctl -u your_app.service

    这将显示该服务的全部日志记录。

    按时间范围查看日志

    可以按时间范围筛选日志,例如查看过去一小时的日志:

    sudo journalctl -u your_app.service --since "1 hour ago"

    或者查看特定日期之后的日志:

    sudo journalctl -u your_app.service --since "2024-11-01"

    按关键字过滤日志

    如果你只想查看特定关键字的日志,例如包含错误信息的日志,可以使用 grep 命令进行过滤:

    sudo journalctl -u your_app.service | grep "error"

     清除指定服务的日志

    要清除特定服务(如 ASP.NET Core 应用)的日志,可以使用 journalctl 的 –vacuum 选项。虽然 journalctl 没有直接提供按服务清除的功能,但可以通过指定服务名来筛选日志,再结合清除命令:

    sudo journalctl --vacuum-size=100M -u your_app.service

    这将限制日志文件的总大小为 100MB,多余的日志会被删除。将 100M 替换为你期望的日志限制大小。

    清除日志到指定时间点

    你可以通过 –vacuum-time 参数清除指定时间之前的所有日志数据:

    sudo journalctl --vacuum-time=7d

    这将清除 7 天之前的所有日志数据,包括所有服务的日志(不是仅 ASP.NET Core 应用)。

    清除所有 journald 日志

    如果你希望彻底清除所有 journald 日志数据(适用于开发或测试环境,生产环境需谨慎),可以使用以下命令:

    sudo journalctl --vacuum-size=1M

    这会将日志数据限制为 1MB,基本相当于清空所有日志数据。

    如果你希望彻底清除所有 journald 日志数据(适用于开发或测试环境,生产环境需谨慎),可以使用以下命令:

    sudo journalctl –vacuum-size=1M

    删除 journal 日志文件

    可以手动删除存储在 /var/log/journal/ 目录下的日志文件。这样会清空所有服务的日志,建议谨慎使用。

    sudo rm -rf /var/log/journal/*
    sudo systemctl restart systemd-journald

    定期清理

    可以在 journald 配置文件 /etc/systemd/journald.conf 中设置日志的最大保留大小或时间,定期清理旧日志。

  • ASP.NET Core 中如何获取 Unix 时间戳

    在 ASP.NET Core 中,我们可以通过 DateTimeOffset 类型来获取当前时间的 Unix 时间戳。DateTimeOffset 代表一个带有时区偏移量的日期和时间,它提供了比 DateTime 更强大的时区支持。要获取当前时间的 Unix 时间戳,可以使用 ToUnixTimeSeconds 方法。

    需要注意的是,此方法从 .NET Core 2.0 开始提供。

    1. 获取当前时间的 Unix 时间戳

    // language: csharp
    using System;
    
    public class Program
    {
        public static void Main()
        {
            // 获取当前时间的 Unix 时间戳(秒级)
            long timestamp = DateTimeOffset.Now.ToUnixTimeSeconds();
            Console.WriteLine("当前时间的 Unix 时间戳为: " + timestamp);
        }
    }

    2. 获取 UTC 时间的 Unix 时间戳

    如果你需要获取 UTC 时间的 Unix 时间戳,可以使用 DateTimeOffset.UtcNow,它返回的是 UTC(协调世界时)时间。

    // language: csharp
    using System;
    
    public class Program
    {
        public static void Main()
        {
            // 获取当前 UTC 时间的 Unix 时间戳(秒级)
            long timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
            Console.WriteLine("当前 UTC 时间的 Unix 时间戳为: " + timestamp);
        }
    }

  • 解决 ASP.NET Core 连接 SQL Server 时“证书链由不受信任的颁发机构颁发”错误

    使用基于.NET 8的ASP.NET Core连接sql server数据库时,遇到报错:sql server 证书链是由不受信任 的颁发机构颁发的

    解决办法是,在数据库连接串上增加TrustServerCertificate=False;,即改为下面的形式就可以了:

    // language: csharp
    Data Source=.;Initial Catalog=WorkSummary;User Id=sa;Password=123456;Encrypt=False;TrustServerCertificate=False;
  • asp.net core 解析 easyui的tree的json

    easyui的tree组件支持从JSON加载数据,在asp.net中,我们可以从数据库读取相关数据,然后转换成json格式后返回给easyui的tree组件。下面是具体过程:

    1、定义相关的节点类

    public class PermissionTreeNode
    {
        public int id { get; set; }
        public string? text { get; set; }
        public string? state { get; set; }
        public bool @checked { get; set; }
        public PermissionTreeNode[]? children { get; set; }
    }
    
    public class Permission
    {
        public int Id { get; set; }
        public string Name { get; set; } = string.Empty;
        public Role? Role { get;set; }
        public DateTime? CreatedAt { get; set; } = DateTime.Now;
        public DateTime? UpdatedAt { get; set; } = DateTime.Now;
    }
    
    public class Role 
    {
        public int Id { get; set; }
        public string Name { get; set; } = string.Empty;
        public ICollection<Permission>? Permissions { get; set; }
        public ICollection<Account>? Accounts { get; set; }
        public DateTime? CreatedAt { get; set; } = DateTime.Now;
        public DateTime? UpdatedAt { get; set; } = DateTime.Now;
    }

    2、编写生成节点的函数 ParseJson,在这个函数里面加了权限处理,只添加当前用户有权限的节点:

    public PermissionTreeNode ParseJson(PermissionTreeNode permissionTreeNode, List<Permission> rolePermissions, Role role)
    {
        if (permissionTreeNode.children != null)
        {
            foreach (var menu in permissionTreeNode.children)
            {
                if (rolePermissions.Any(x => x.Name == menu.text) || role.Name == "超级管理员")
                {
                    menu.@checked = true;
                }
    
                if (menu.children != null)
                {
                    ParseJson(menu, rolePermissions, role);
                }
            }
        }
    
        return permissionTreeNode;
    }

    3、最后,我们在初始化tree的数据的时候,调用InitPermissionTree函数生成节点数据,再通过JsonSerializer.Serialize函数转换成json格式,不要忘记在转换后的json字符串两边分别加上[],不然easyui的tree组件加载不了。

    public ContentResult InitPermissionTree(int roleId)
    {
        var rolePermissions = _appDbContext.Permissions.Where(x => x.Role != null && x.Role.Id == roleId).ToList();
    
        var role = _appDbContext.Roles.Where(x => x.Id == roleId).FirstOrDefault();
    
        var jsonFile = Path.Combine(_env.WebRootPath, $"PermissionTree.json");
        var json = System.IO.File.ReadAllText(jsonFile);
    
    
        JsonSerializerOptions options = new JsonSerializerOptions();
        options.ReferenceHandler = default;
        var permissionTreeNode = JsonSerializer.Deserialize<PermissionTreeNode>(json, options);
    
        if (permissionTreeNode != null && role != null)
        {
            permissionTreeNode = ParseJson(permissionTreeNode, rolePermissions, role);
        }
    
        var result = "[" + JsonSerializer.Serialize(permissionTreeNode) + "]";
    
        return Content(result, "application/json");
    }

    我们可以使用F12浏览器开发者工具查看接口返回的JSON,如下:

      {
        "id": 1,
        "text": "工作总结生成管理系统",
        "checked": false,
        "children": [
          {
            "id": 100,
            "text": "内容管理",
            "state": "open",
            "checked": false,
            "children": [
              {
                "id": 101,
                "text": "文章管理",
                "checked": false,
                "children": [
                  {
                    "id": 102,
                    "text": "文章生成",
                    "checked": false
                  },
                  {
                    "id": 103,
                    "text": "编辑文章",
                    "checked": false
                  },
                  {
                    "id": 104,
                    "text": "删除文章",
                    "checked": false
                  },
                  {
                    "id": 105,
                    "text": "状态设置",
                    "checked": false
                  },
                  {
                    "id": 106,
                    "text": "导出Word文档(文章管理)",
                    "checked": false
                  }
                ]
              },
              {
                "id": 200,
                "text": "素材库",
                "state": "opened",
                "checked": false,
                "children": [
                  {
                    "id": 201,
                    "text": "新增素材",
                    "checked": false
                  },
                  {
                    "id": 202,
                    "text": "编辑素材",
                    "checked": false
                  },
                  {
                    "id": 203,
                    "text": "删除素材",
                    "checked": false
                  },
                  {
                    "id": 204,
                    "text": "导入导出",
                    "checked": false
                  },
                  {
                    "id": 205,
                    "text": "素材审核",
                    "checked": false
                  }
                ]
              },
              {
                "id": 300,
                "text": "标签库",
                "state": "opened",
                "checked": false,
                "children": [
                  {
                    "id": 301,
                    "text": "创建标签",
                    "checked": false
                  },
                  {
                    "id": 302,
                    "text": "编辑标签",
                    "checked": false
                  },
                  {
                    "id": 303,
                    "text": "删除标签",
                    "checked": false
                  }
                ]
              },
    
              {
                "id": 400,
                "text": "模板库",
                "state": "opened",
                "checked": false,
                "children": [
                  {
                    "id": 401,
                    "text": "新增模板",
                    "checked": false
                  },
                  {
                    "id": 402,
                    "text": "编辑模板",
                    "checked": false
                  },
                  {
                    "id": 403,
                    "text": "删除模板",
                    "checked": false
                  }
                ]
              },
    
              {
                "id": 500,
                "text": "档案库",
                "state": "opened",
                "checked": false,
                "children": [
                  {
                    "id": 501,
                    "text": "删除档案",
                    "checked": false
                  },
                  {
                    "id": 502,
                    "text": "查看档案",
                    "checked": false
                  },
                  {
                    "id": 503,
                    "text": "导入Word文档",
                    "checked": false
                  },
                  {
                    "id": 504,
                    "text": "导出Word文档(档案库)",
                    "checked": false
                  },
                  {
                    "id": 505,
                    "text": "移到文章管理",
                    "checked": false
                  }
                ]
              }
            ]
          },
          {
            "id": 600,
            "text": "系统设置",
            "state": "open",
            "checked": false,
            "children": [
              {
                "id": 601,
                "text": "帐号管理",
                "state": "open",
                "checked": false,
                "children": [
                  {
                    "id": 602,
                    "text": "新增帐号",
                    "checked": false
                  },
                  {
                    "id": 603,
                    "text": "编辑帐号",
                    "checked": false
                  },
                  {
                    "id": 604,
                    "text": "删除帐号",
                    "checked": false
                  },
                  {
                    "id": 605,
                    "text": "重置密码",
                    "checked": false
                  }
                ]
              },
              {
                "id": 700,
                "text": "部门管理",
                "state": "open",
                "checked": false,
                "children": [
                  {
                    "id": 701,
                    "text": "新增部门",
                    "checked": false
                  },
                  {
                    "id": 702,
                    "text": "编辑部门",
                    "checked": false
                  },
                  {
                    "id": 703,
                    "text": "删除部门",
                    "checked": false
                  }
                ]
              },
              {
                "id": 800,
                "text": "角色管理",
                "state": "open",
                "checked": false,
                "children": [
                  {
                    "id": 801,
                    "text": "新增角色",
                    "checked": false
                  },
                  {
                    "id": 802,
                    "text": "编辑角色",
                    "checked": false
                  },
                  {
                    "id": 803,
                    "text": "删除角色",
                    "checked": false
                  }
                ]
              }
    
            ]
          }
        ]
      }
    

    这样,asp.net core 给 easyui 的 tree 组件动态生成数据就完成了。

  • IIS部署ASP.NET Core报错:HTTP Error 500.38 – Failed to locate ASP.NET Core app

    在使用IIS部署ASP.NET Core程序时,遇到报错,错误信息如下 :HTTP Error 500.38 – Failed to locate ASP.NET Core app

    经过一番搜索,在微软官方网站中找到了错误原因,网站如下:https://learn.microsoft.com/zh-cn/aspnet/core/test/troubleshoot-azure-iis?view=aspnetcore-7.0

    在这个排查页面上可以看到,这个错误是未禁用单文件发布导致的,那么,我们只要在项目文件中禁用单文件发布就行了。

    解决:在IIS部署时,禁用单文件发布。编辑xxx.csproj项目文件(xxx是你的项目名字),把PublishSingleFile这一项改为false,如果没有,直接添加下面的代码即可。

    <PublishSingleFile>false</PublishSingleFile>	

    如果使用VS的发布,要在发布选项中去掉单文件发布选项的钩:

  • Supervisor部署ASP.NET core配置文件

    在部署asp.net core的应用程序时,为防止进程退出,可以使用supervisor来守护asp.net core应用的进程,对于一个asp.net core应用supervisor配置如下,可以作为模板修改使用。

    [program:pcsweb]
    directory=/www/pcsweb
    command=dotnet /www/pcsweb/YiSha.Admin.Web.dll
    autostart=true
    autorestart=true
    stdout_logfile=/var/log/pcsweb_stdout.log
    stderr_logfile=/var/log/pcsweb_stderr.log
    startsecs=5
    startretries=3
    stopasgroup=true
    killasgroup=true
  • Hello, Laravel

    经过一周的紧张开发,又一个面向银行的内部系统总算完成了。这个系统本来打算是用 .NET 来做,但这段时间一直心心念念 Laravel 框架,就像所有趁手好用的工具一样,只要用过一次,就忘不掉她的好。

    经过简单分析,发现这个系统其实并没有复杂的业务和计算,由于是内部系统,也不存在高并发的可能,所以,对于 Laravel 是适用的。本来 .NET 开发的系统已经完成了 70% ,花了一周不到的时间就用 Laravel 重写完成了,不得不感慨开发速度之快,同时开发体验拉满。

    这么多年来,虽说 PHP 也在断断续续的用,但从没怎么把它当回事,一般的项目主要还是使用 .NET 来做。对于自由开发者来说,.NET 可谓是相当的万金油,既可以做桌面开发,也可以做 Web 开发,关键时刻还可以拿来写一写安卓移动应用。由于是静态编译型语言,性能基本够用,配合 SQL Server 数据库,基本没有做不了的项目。

    但是,自从知道 Laravel 框架后,就在慢慢地在不断了解,越了解就越被她的优雅所打动,这里我也用了优雅这个词,因为实在没有想到更好的词能描述那种感觉。直到去年,尝试用她开发了一个小项目,由于当时对 Eloquent 了解还不够深入,甚至多对多、一对多这种关系的用法还不太清楚,就强行上了,结果也还行,顺顺利利的开发完成。

    这次的项目开发,在彻底弄清了 Eloquent 之后,就果断切换到 Laravel 来开发,真的是太好用了,再也不用定义 ASP.NET 里面的那一堆堆的实体类,再也不再需要 AutoMapper,再也不用那蹩脚的表单验证,再也不用等待编译生成,等等好处无法一一言说。总之就是开发轻量了,保存即生效,刷新即可看效果,整个过程轻松加愉快,身心愉悦!

    当然,Laravel 并不是完美的,并不适用于所有场景,比如高并发场景,计算密集、IO密集型应用,对于这些场景和应用,Laravel 不是不能做,而是不够好,性能不够高!当然,这不能怪 Laravel,这是 PHP 的问题,什么时候等 PHP 进化出高性能的运行模式,那 Laravel 就是完美的。

    最后,程序语言、程序框架始终是工具,只有根据需求和场景选择合适的工具才是真理。

    后记:该项目最终使用ASP.NET Core重新开发,重业务的项目还是要使用强类型语言,懂得都懂,不解释。

  • ASP.NET CORE中的IOC注册类的三种方式

    AddSingleton():

    When we use the AddSingleton() method to register a service, then it will create a singleton service. It means a single instance of that service is created and that singleton instance is shared among all the components of the application that require it. That singleton service is created when we requested for the first time.

    AddScoped():

    Scoped means instance per request. When we use the AddScoped() method to register a service, then it will create a Scoped service. It means, an instance of the service is created once per each HTTP request and uses that instance in other calls of the same request.

    AddTransient():

    When we use the AddTransient() method to register a service, then it will create a Transient service. It means a new instance of the specified service is created each time when it is requested and they are never shared.

    Note: In the real-time applications, you need to register the components such as application-wide configuration as Singleton. The Database access classes like Entity Framework contexts are recommended to be registered as Scoped so that the connection can be re-used. If you want to run anything in parallel then it is better to register the component as Transient.


    Singleton:每次获取的时候,都是同一个实例,相当于静态类

    生命周期:项目启动——项目关闭

    AddSingleton可以直接用圆括号添加一个实例

    Transient: 每次从容器中获取的时候,都是一个新的实例

    生命周期:GC回收,主动释放

    Scoped: 对象在请求是才开始创建,在这次请求中获取的对象都是同一个

    生命周期:请求开始——请求结束

    scoped在同一次请求中,获取多次对象得到的是同一个对象

    对于同一个接口的多个实现或静态类,最后注册的实现可替换之前的

  • asp.net core 发布脚本

    @echo off
    
    del /f/q  .\bin\release\netcoreapp2.1\publish\*
    dotnet publish -c release
    
    start .\bin\release\netcoreapp2.1\publish\
    
    echo 发布完成
    
    pause