Net6 速建控制台应用程序,支持DI,Nlog,DbContext,Options选项模式、windows服务

十年河东,十年河西,莫欺少年穷

学无止境,精益求精

每次搭建NetCore控制台应用程序都得费个十几分钟时间,甚至更多

索性搞个简易版的,供以后复用

1、新建Net6控制台应用程序

1.1、项目文件

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.6" />
    <PackageReference Include="Microsoft.Extensions.Configuration" Version="6.0.0" />
    <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="6.0.0" />
    <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" />
    <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
    <PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.0" />
         <PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.0" />               
    <PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
    <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="6.0.0" />
    <PackageReference Include="NLog.Extensions.Logging" Version="5.0.4" />
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\CoreDbContext\CoreDbContext.csproj" />
    <ProjectReference Include="..\swapCommon\swapCommon.csproj" />
    <ProjectReference Include="..\swapModels\swapModels.csproj" />
  </ItemGroup>

  <ItemGroup>
    <None Update="appsettings.json">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </None>
    <None Update="nlog.config">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </None>
  </ItemGroup>

</Project>

项目文件中引入的包,可以原封不动直接复制到你的项目文件中

该控制台项目引用了三个外部项目,关于这三个外部项目最后说明

项目中创建了两个配置文件,appsettings.json 和 nlog.config  ,分别属于系统配置文件和Nlog日志配置文件

内容分别如下:

appsettings.json  属性需要设置为始终复制

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "virtualPath": "", //调试时为空,swap环境:/dev/swap  正式环境:/swap
  "ConnectionStrings": {
    "swapDbContext": "Data Source=10.11.12.13;Initial Catalog=swap;Password=xxx;User ID=sa;"
  },
  "MongoDatabaseName": "swapApiLogsDb",
  "MongoDbConnect": "mongodb://127.0.0.1:27017",
  "RedisConnectionString": "127.0.0.1:6379,allowadmin=true",
  "RedisDbNum": 0,
  "IotRedisDbNum": 1,
  "UploadFileOptions": {
    "FileTypes": ".gif,.jpg,.jpeg,.png,.bmp,.GIF,.JPG,.JPEG,.PNG,.BMP,.xls,.xlsx",
    "MaxSize": 104857600,
    "BaseUrl": "D:\\dotnet\\fileServer\\WuanManagerFiles",
    "BasePath": "Uploads",
    "ServerUrl": "https://portal.one5a.com/Uploads"
  }
}
View Code

 nlog.config    属性需要设置为始终复制

<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      autoReload="true"
      internalLogLevel="Info"
      internalLogFile="internal-nlog-AspNetCore.txt">

    <!-- enable asp.net core layout renderers -->
    <extensions>
        <add assembly="NLog.Web.AspNetCore"/>
    </extensions>

    <!-- the targets to write to -->
    <targets>
        <!-- File Target for all log messages with basic details -->
        <target xsi:type="File" name="allfile" fileName="logs/log-${shortdate}.log"
                layout="${longdate}|${event-properties:item=EventId_Id:whenEmpty=0}|${level:uppercase=true}|${logger}|${message} ${exception:format=tostring}" archiveAboveSize="10000" maxArchiveFiles="3"/>

        <!-- File Target for own log messages with extra web details using some ASP.NET core renderers -->
        <target xsi:type="File" name="ownFile-web" fileName="nlog-AspNetCore-own-${shortdate}.log"
                layout="${longdate}|${event-properties:item=EventId_Id:whenEmpty=0}|${level:uppercase=true}|${logger}|${message} ${exception:format=tostring}|url: ${aspnet-request-url}|action: ${aspnet-mvc-action}|${callsite}" archiveAboveSize="10000" maxArchiveFiles="3" />

        <!--Console Target for hosting lifetime messages to improve Docker / Visual Studio startup detection -->
        <target xsi:type="Console" name="lifetimeConsole" layout="${MicrosoftConsoleLayout}" />
    </targets>

    <!-- rules to map from logger name to target -->
    <rules>
        <!--All logs, including from Microsoft-->
        <logger name="*" minlevel="Trace" writeTo="allfile" />

        <!--Output hosting lifetime messages to console target for faster startup detection -->
        <logger name="Microsoft.Hosting.Lifetime" minlevel="Info" writeTo="lifetimeConsole, ownFile-web" final="true" />

        <!--Skip non-critical Microsoft logs and so log only own logs (BlackHole) -->
        <logger name="Microsoft.*" maxlevel="Info" final="true" />
        <logger name="System.Net.Http.*" maxlevel="Info" final="true" />

        <logger name="*" minlevel="Trace" writeTo="ownFile-web" />
    </rules>
</nlog>
View Code

2、Program 代码

using CoreDbContext;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using NLog.Extensions.Logging;
using swapModels;

namespace CabinetService
{
    static class Program
    {
        static void Main(string[] args)
        { 
            ConfigurationBuilder builder = new ConfigurationBuilder();
            builder.AddJsonFile("appsettings.json", true, true);
            var ConfigRoot = builder.Build();//根节点
            IServiceCollection Services = new ServiceCollection();
            Services.AddDbContext<swapDbContext>(options => options.UseSqlServer(ConfigRoot.GetConnectionString("swapDbContext")), ServiceLifetime.Scoped);
            //
            Services.AddLogging(log => { log.AddConsole(); log.AddNLog(); log.SetMinimumLevel(LogLevel.Information); });
            Services.AddScoped<LogService>();
            Services.AddScoped<DbTest>();
            Services.AddScoped<coreOptions>(); 
            Services.AddOptions().Configure<UploadFileOptions>(A => ConfigRoot.GetSection("UploadFileOptions").Bind(A));
            //  
            using (ServiceProvider provider = Services.BuildServiceProvider())
            {
                //测试数据库
                var dbTest = provider.GetService<DbTest>();
                dbTest.Test();
                //选项模式测试
                var options = provider.GetService<coreOptions>();
                options.Test();
                //测试日志
                var logservice = provider.GetService<LogService>();
                logservice.test();
                // 
            }
            CreateHostBuilder(args).Run();
        }

        public static IHost CreateHostBuilder(string[] args)
        {
            var builder = Host.CreateDefaultBuilder(args)
                 .ConfigureServices((hostContext, services) =>
                 { 
                     services.AddHostedService<IotService>();
                 }).UseWindowsService();
            var host = builder.Build();
           
            return host;
        }
    }

    /// <summary>
    /// efCore first 测试
    /// </summary>
    public class DbTest
    {
        private readonly swapDbContext context;

        public DbTest(swapDbContext context)
        {
            this.context = context;
        }

        public void Test()
        {
            var linq = context.Users.Where(A => A.uid != "");
            var sql = linq.ToQueryString();
            Console.WriteLine("linq转化为的SQL语句为:" + sql);
            var data = linq.ToList();
            foreach (var item in data)
            {
                Console.WriteLine(item.userName);
            }
        }
    }

    public class coreOptions
    {
        private readonly IOptionsSnapshot<UploadFileOptions> options; 
        public coreOptions(IOptionsSnapshot<UploadFileOptions> options)
        {
            this.options = options; 
        }
        public void Test()
        {
            Console.WriteLine("========================================");
            Console.WriteLine(options.Value.BaseUrl);
            Console.WriteLine(options.Value.MaxSize);
            Console.WriteLine(options.Value.FileTypes);
        }
    }

    public class LogService
    {
        private readonly ILogger<LogService> logger;
        public LogService(ILogger<LogService> logger)
        {
            this.logger = logger;
        }

        public void test()
        {
            for (int i = 0; i < 10; i++)
            {
                logger.LogTrace("跟踪");
                logger.LogDebug("调试");
                logger.LogInformation("信息");
                logger.LogError("错误");
                logger.LogCritical("严重错误");
                try
                {
                    int a = 0;
                    int b = 1;
                    var c = b / a;
                }
                catch (Exception ex)
                {
                    logger.LogError(ex, "异常");
                }
            }
        }

        /// <summary>
        /// windows 日志  部署在linux 下无效
        /// </summary>
        public void EventLog()
        {
            //Microsoft.Extensions.Logging.EventLog
        }
    }
}
View Code

2.1、选项模式中用到 swapModels 中定义的实体类,如下

    public class UploadFileOptions
    {
        /// <summary>
        /// 允许的文件类型
        /// </summary>
        public string FileTypes { get; set; }
        /// <summary>
        /// 最大文件大小
        /// </summary>
        public int MaxSize { get; set; }
        /// <summary>
        /// 文件的基地址
        /// </summary>
        public string BaseUrl { get; set; }
        /// <summary>
        /// IIS 虚拟路径 解析到 BaseUrl
        /// </summary>
        public string BasePath { get; set; }


        public string ServerUrl { get; set; } 

    }
View Code

2.2、DbContext 数据库上下文

根据项目文件可知,该控制台应用程序引用项目CoreDbContext ,CoreDbContext 采用EFCORE+CodeFirst的模式进行搭建,下面简单贴出部分代码

swapDbContext

using CoreDbContext.DbDtos; 
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design; 

namespace CoreDbContext
{
    /// <summary>
    /// add-Migration -Context  可以通过-Context 指定上下文,因此,项目中可以有多个DbCOntext,这样就支持多个数据库
    /// </summary>
    public class swapDbContext : DbContext
    {
        public DbSet<T_Group> Groups { get; set; }
        public DbSet<T_AppInfo> AppInfos { get; set; }
        public DbSet<T_User> Users { get; set; }
        public DbSet<T_Role> Roles { get; set; }
        public DbSet<T_UserRole> UserRoles { get; set; }
        public DbSet<T_Station> Stations { get; set; }
        public DbSet<T_SysDictionary> SysDictionaries { get; set; }
        public DbSet<T_CabinetProduce> CabinetProduces { get; set; }
        public DbSet<T_Cabinet> Cabinets { get; set; }
        public DbSet<T_BatteryProduce> BatteryProduces { get; set; }
        public DbSet<T_Battery> Batteries { get; set; }
        public DbSet<C_Customer> Customers { get; set; }
        public DbSet<C_CustomerBattery> CustomerBatteries { get; set; }
        public DbSet<C_ChargeGoods>  ChargeGoods { get; set; }
        public DbSet<C_ChargeGoodOrder> ChargeGoodOrders { get; set; }
        public DbSet<C_ChargeGoodOrderDtl> ChargeGoodOrderDtls { get; set; }
        public DbSet<C_Deposit> Deposits { get; set; }
        public DbSet<C_DepositOrder>  DepositOrders { get; set; }
        public DbSet<C_DepositApply> DepositApplies { get; set; }
        public DbSet<T_Document> Documents { get; set; }
        public DbSet<T_SysParam> SysParams { get; set; }


        public swapDbContext() : base()
        {
        }
        public swapDbContext(DbContextOptions<swapDbContext> options) : base(options)
        {
        }


        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            //关闭级联删除
            var foreignKeys = modelBuilder.Model.GetEntityTypes().SelectMany(e => e.GetForeignKeys()).Where(fk => fk.DeleteBehavior == DeleteBehavior.Cascade);
            foreach (var fk in foreignKeys)
            {
                fk.DeleteBehavior = DeleteBehavior.Restrict;
            }
            //建立索引 并 增加唯一性约束
            modelBuilder.Entity<T_CabinetProduce>().HasIndex(u => u.batchNo).IsUnique();

            base.OnModelCreating(modelBuilder);
            //从当前程序集命名空间加载所有的IEntityTypeConfiguration
            modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly);
        }
    }

    /// <summary>
    /// 开发环境专用  用于add-Migration 时使用
    /// </summary>
    public class swapDbContextFactory : IDesignTimeDbContextFactory<swapDbContext>
    {
        public swapDbContext CreateDbContext(string[] args)
        {
            var optionsBuilder = new DbContextOptionsBuilder<swapDbContext>();
            optionsBuilder.UseSqlServer("Data Source=1.2.3.4;Initial Catalog=swap;Password=xxx;User ID=sa;");

            return new swapDbContext(optionsBuilder.Options);
        }
    }
}
View Code

dto_User

    /// <summary>
    /// 用于平台登录
    /// </summary>
    public class T_User
    {
        public string? uid { get; set; }
        public string? userName { get; set; }
        public string? passWord { get; set; }
        public bool isEnable { get; set; } = true;
        public DateTime? lastLoginTime { get; set; }
        public DateTime createTime { get; set; } 
        public string? groupId { get; set; } 
        public bool isManager { get; set; }
        /// <summary>
        /// 一个用户只能属于一个商户
        /// </summary>
        public virtual T_Group? Group { get; set; }
        
    }
View Code

User_Config

using CoreDbContext.DbDtos;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using System;
using System.Collections.Generic;
using System.Text;

namespace CoreDbContext.DbConfigs
{
    internal class T_UserConfig : IEntityTypeConfiguration<T_User>
    {
        public void Configure(EntityTypeBuilder<T_User> builder)
        {
            builder.ToTable("T_Users");
            builder.HasKey(A => A.uid);
            builder.Property(A => A.uid).HasColumnType("varchar(64)").HasComment("主键"); 
            builder.Property(A => A.userName).HasColumnType("varchar(64)").HasComment("登录账户");  
            builder.Property(A => A.passWord).HasColumnType("varchar(512)").HasComment("密码,密文");
            builder.Property(A => A.groupId).HasColumnType("varchar(64)").HasComment("所属商户");
            builder.Property(A => A.isManager) .HasComment("是否是平台管理员,否为商家端登录用户");
            //builder.HasQueryFilter(A => A.isEnable == true); 
            //一对多主外键配置
            builder.HasOne<T_Group>(A => A.Group).WithMany().HasForeignKey(A => A.groupId);  
        }
    }
}
View Code

项目CoreDbContext 的项目文件为:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.6" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.6" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.6">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
  </ItemGroup>

</Project>

2.3、windows服务部分

    public class IotService : BackgroundService
    {
        System.Timers.Timer timer1;
        public override Task StartAsync(CancellationToken cancellationToken)
        {
            if (!cancellationToken.IsCancellationRequested)
            {
                timer1 = new System.Timers.Timer();
                timer1.Interval = 1000 * 1;  //设置计时器事件间隔执行时间  每隔1秒执行一次
                timer1.Elapsed += new System.Timers.ElapsedEventHandler(timer1_Elapsed);
                timer1.Enabled = true;
            }
            return base.StartAsync(cancellationToken);
        }
        /// <summary>
        ///  每一秒执行一次
        /// </summary>
        /// <param name="stoppingToken"></param>
        /// <returns></returns>
        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            while (!stoppingToken.IsCancellationRequested)
            {
                var now = DateTime.Now;
                Console.WriteLine("ExecuteAsync执行结果:" + now);
                await Task.Delay(1000, stoppingToken);
            }
            //
            if (!stoppingToken.IsCancellationRequested)
            {
                //只执行一次
                var now = DateTime.Now;
                Console.WriteLine("ExecuteAsync执行结果:" + now);
                await Task.Delay(1000, stoppingToken);
            }
            if (stoppingToken.IsCancellationRequested)
            {
                //不使用ExecuteAsync ,使用自定义的timer1
                var now = DateTime.Now;
                Console.WriteLine("ExecuteAsync执行结果:" + now);
                await Task.Delay(1000, stoppingToken);
            }
        }

        public override Task StopAsync(CancellationToken cancellationToken)
        {
            timer1.Enabled = false;
            return base.StopAsync(cancellationToken);
        }

        private void timer1_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            var now = DateTime.Now;
            Console.WriteLine("timer1_Elapsed执行结果:" + now);
        }
    }
View Code

关于windows服务NetCore部分,可参考:

NetCore3.1 制作windows服务

关于code first 搭建可参考鄙人的博客

Net6/NetCore3.1搭建codeFirst 【支持多dbcontext】并接合TransactionScope 完成事务操作

Net5 控制台应用程序引入EFCore+CodeFirst

Net5 EFCore CodeFirst FluentApi关系配置

Net5 EfCore CodeFirst 乐观锁 RowRersion

3、swapCommon 公共方法类库

这个类库没用到哦

@天才卧龙的博客

posted @ 2022-10-14 16:05  天才卧龙  阅读(684)  评论(0编辑  收藏  举报