一篇关于动态仪表盘Web应用程序的介绍

开课吧开课吧锤锤2021-04-01 18:02

介绍

    在专业领域,公司的首席执行官或合格的经理更希望能够快速访问所有关键数据点,以帮助分析、比较并做出相关的决定。

    仪表盘是一种对公司的重要数据有一个全局视野的方式,这些仪表盘的一些使用案例是比较两个特定年份的净销售额,一个网站上每个月的用户数量,以及一个Azure租户的订阅销售数量。仪表盘一般由图表和表格来表示。

    有很多JavaScript库可以帮助构建一个漂亮的图形视觉效果,可以用于,其中最好的是ChartJs。

    通过本文,我们将构建一个漂亮的仪表盘Web应用程序,显示一些关于订阅用户的指标。这个应用程序将使用C#、ASP.NETMVCCore、JavaScript和ChartJs库来构建。

    跟随这篇文章,你将会了解到更多的信息。

    -清晰的架构

    -实体框架核心采用代码优先的方法

    -依赖性注入

    -PostgreSql数据库

    -图形Js

    -使用PostMan测试API端点

    先决条件

    要理解本文,你应该具备ASP.NETMVCCore和JavaScript的基本知识。

    创建项目架构

    在本教程中,我们将采用简洁的架构原则,从头开始开发我们的应用。

    采用清洁架构,我们的应用在可维护性和可测试性方面会有很大的收获,这要归功于关注点的分离,它不会关注特定的框架或技术,而是关注领域逻辑。我建议你访问这个链接和这个链接,以有一个完整的定义,并深入了解这样的最佳实践。

    我们的项目将由四个部分组成。

    -UI:是由:

    交互器。它拦截展示层发送的请求,执行相关的场景,并返回正确的结果,由视图显示。在我们的例子中,它是一个API控制器。

    呈现层。它构成了GUI部分,它可以由任何框架开发,如Angular,AngularJs,ASP.NETMVCCore等。对于我们的例子,它将是一个ASP.NETMVCCore项目。

    -应用逻辑:实现我们业务规则的一组工作流(用例)。它们的主要目的是接收来自控制器的请求模型,并将其转换为结果,再传回给视图。在我们的案例中,它将是一个.NETCore库项目。

    -域:引用我们业务逻辑的模型或实体的集合。它应该是独立于框架的。在我们的例子中,它将是一个.NETCore库项目。

    -基础设施。它包含了管理和收集来自外部数据源(如数据库、服务、库或文件)的数据的方式。基础设施使用域类与外部数据源交互,并收集响应数据。对于我们的应用,它将包含与PostgreSqlDataBase交换的存储库和配置。在我们的案例中,它将是一个.NETCore库项目。

    下图显示了最终项目结构的快照。

web

    创建数据库

    由于EF框架的使用,我们的应用程序将独立于数据库,我们可以插入任何类型的数据库,如PostgreSql,Oracle或SqlServerDataBase,我们只需要改变供应商。

    在这个应用程序中,我们将使用PostgreSql,首先我们需要在本地机器上安装它,并创建一个新的和空的数据库,做到这一点。

    -从这个链接下载并安装pgadmin4。

    -从这个链接下载并安装pgAgent。

    -创建一个空数据库。我们需要启动pgadmin应用程序并创建一个新的数据库,如下所示。

web

    实施后端

    一)创建DataAccess

    DataAccess是一组确保应用程序和数据库之间对话的类和配置。它的主要职责是定义业务实体,操作CRUD操作,并将应用程序的数据请求翻译成数据库服务器已知的一些指令,反之亦然。通信是通过使用以下技术或框架之一来保证的:ADO.NET、EF、NHibernate等,它们的主要目标都是使应用程序和数据库之间的对话过程变得更加简单和透明。

    对于我们的应用,我们将使用EF(EntityFramework),它是.NETCore项目中最流行的ORM,它提供了多种优势,如:1.在域类和关系类之间建立映射。

    -域类和关系数据之间的映射。

    -通过使用Linq到实体,引入更多的抽象来管理和收集数据库中的数据。

    -可以支持不同的关系型数据库系统,如PostgreSql、Oracle和SqlServer。

    -提供多种方法,如代码优先、数据库优先、模型优先。

    -由于采用了懒惰加载机制,数据可以按需加载。

    -与ADO.NET等旧的数据访问技术相比,EFORM由于有了映射过程,使得从数据库中读写数据变得更加容易,用户将更多的精力放在如何开发业务逻辑上,而不是查询的构建上。它节省了相当多的开发时间。

    -在本节中,我们将重点介绍如何在我们的模型和数据库实体之间建立联系,使用EF代码先行的方法创建数据库模式,并为我们的演示准备一个数据集。

    1)创建实体和关系

    我们数据库的模式将由以下实体组成。

    -用户:用名字,年龄,工作和性别来表示。

    -职业:是指用户可以拥有的工作,如牙医、软件开发人员、教师等。

    下面的类图将清楚地描述这些实体之间的关系和每个表的属性列表。

web

    这些主要类将在DashBoardWebApp.Domain项目的Entities文件夹中创建。

   创建用户类。

public class User
  {
      public int? Id { get; set; }
      public string FirstName { get; set; }
      public int Age { get; set; }
      public string Gender { get; set; }
      public DateTime CreatedAt { get; set; }
      public int ProfessionId { get; set; }
      public Profession Profession { get; set; }
  }

    创建职业类。

public class Profession
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public List<User> Users { get; set; }
    }

    2)设置数据库

    为了保证应用模型和数据库实体之间的映射,我们要按照下面的步骤进行。

    -首先,使用Nuget包管理器安装EF核心包和EF的PostegreSql提供者。

web

    -配置实体和关系数据之间的映射。

    在DashBoardWebApp.Infrastructure/Data/Config中,我们定义了我们想要映射的每个实体的约束和关系列表。

    -创建UserEntityConfiguration类:

public class UserEntityConfiguration : IEntityTypeConfiguration<User>
   {
       public void Configure(EntityTypeBuilder<User> builder)
       {
           builder.ToTable("User");

           builder.Property(u => u.Id)
           .ValueGeneratedOnAdd()
           .HasColumnType("serial")
           .IsRequired();

           builder.HasKey(u => u.Id)
           .HasName("pk_user");

           builder.Property(u => u.FirstName).IsRequired();
           builder.Property(u => u.Age).IsRequired().HasDefaultValue(0);
           builder.Property(u => u.Gender).IsRequired().HasDefaultValue("Male");
           builder.Property(u => u.CreatedAt).IsRequired().HasDefaultValueSql
                           ("CURRENT_TIMESTAMP");

           builder.Property(u => u.ProfessionId).HasColumnType("int");
           builder.HasOne(u => u.Profession).WithMany(p => p.Users).HasForeignKey
                   (u => u.ProfessionId).HasConstraintName("fk_user_profession");
       }
   }

    创建ProfessionEntityConfiguration类。

public class ProfessionEntityConfiguration : IEntityTypeConfiguration<Profession>
{
    public void Configure(EntityTypeBuilder<Profession> builder)
    {
        builder.ToTable("Profession");

        builder.HasKey(p => p.Id)
          .HasName("pk_profession");

        builder.HasIndex(p => p.Name).IsUnique(true).HasDatabaseName
                        ("uc_profession_name");

        builder.HasMany(p => p.Users).WithOne(u => u.Profession);
    }
}

    创建种子数据。

    我们需要创建ModelBuilderExtensions类,通过添加种子方法来扩展ModelBuilder的功能,该类将在DashBoardWebApp.Infrastructure/Data/Config中创建。

public static class ModelBuilderExtensions
    {
        public static void Seed(this ModelBuilder modelBuilder)
        {
            List<Profession> professions = new List<Profession>()
            {
                 new Profession() { Id = 1, Name = "Software Developer"},
                 new Profession() { Id = 2, Name = "Dentist"},
                 new Profession() { Id = 3, Name = "Physician" }
            };
            
            modelBuilder.Entity<Profession>().HasData(
              professions
            );

            List<User> users = new List<User>()
            {
                 new User() { Id=1, FirstName = "O.Nasri 1", Age = 30, Gender = "Male", 
                              ProfessionId = 1, CreatedAt = new DateTime(2019, 01, 01) },
                 new User() { Id=2, FirstName = "O.Nasri 2 ", Age = 31, Gender = "Male", 
                              ProfessionId = 1, CreatedAt = new DateTime(2019, 01, 02) },
                 new User() { Id=3, FirstName = "O.Nasri 3", Age = 32, Gender = "Male", 
                              ProfessionId = 1, CreatedAt = new DateTime(2019, 01, 02) },
                 new User() { Id=4, FirstName = "O.Nasri 4", Age = 33, Gender = "Male", 
                              ProfessionId = 1, CreatedAt = new DateTime(2019, 01, 04) },
                 new User() { Id=5, FirstName = "O.Nasri 4", Age = 33, Gender = "Male", 
                              ProfessionId = 1, CreatedAt = new DateTime(2019, 02, 05) },

                 new User() { Id=6, FirstName = "Sonia 1", Age = 20, Gender = "Female", 
                              ProfessionId = 2, CreatedAt = new DateTime(2019, 04, 01) } ,
                 new User() { Id=7, FirstName = "Sonia 2", Age = 20, Gender = "Female", 
                              ProfessionId = 2, CreatedAt = new DateTime(2019, 04, 02) } ,
                 new User() { Id=8, FirstName = "Sonia 3", Age = 20, Gender = "Female", 
                              ProfessionId = 2, CreatedAt = new DateTime(2019, 05, 03) } ,
                 new User() { Id=9, FirstName = "Sonia 4", Age = 20, Gender = "Female", 
                              ProfessionId = 2, CreatedAt = new DateTime(2019, 05, 04) } ,
           
                 new User() { Id=10, FirstName = "O.Nasri 1", Age = 30, Gender = "Male", 
                              ProfessionId = 1, CreatedAt = new DateTime(2020, 01, 01) },
                 new User() { Id=11, FirstName = "O.Nasri 2 ", Age = 31, Gender = "Male", 
                              ProfessionId = 1, CreatedAt = new DateTime(2020, 01, 02) },
                 new User() { Id=12, FirstName = "O.Nasri 3", Age = 32, Gender = "Male", 
                              ProfessionId = 1, CreatedAt = new DateTime(2020, 01, 02) },
                 new User() { Id=13, FirstName = "O.Nasri 4", Age = 33, Gender = "Male", 
                              ProfessionId = 1, CreatedAt = new DateTime(2020, 01, 04) },
                 new User() { Id=14, FirstName = "O.Nasri 4", Age = 33, Gender = "Male", 
                              ProfessionId = 1, CreatedAt = new DateTime(2020, 01, 05) },

                 new User() { Id=15, FirstName = "Thomas 1", Age = 41, Gender = "Male", 
                              ProfessionId = 2, CreatedAt = new DateTime(2020, 03, 01) } ,
                 new User() { Id=16, FirstName = "Thomas 2", Age = 42, Gender = "Male", 
                              ProfessionId = 2, CreatedAt = new DateTime(2020, 03, 02) } ,
                 new User() { Id=17, FirstName = "Thomas 3", Age = 43, Gender = "Male", 
                              ProfessionId = 2, CreatedAt = new DateTime(2020, 03, 03) } ,
                 new User() { Id=18, FirstName = "Thomas 4", Age = 44, Gender = "Male", 
                              ProfessionId = 2, CreatedAt = new DateTime(2020, 03, 04) } ,

                 new User() { Id=19, FirstName = "Christophe 1", Age = 25, Gender = "Male", 
                              ProfessionId = 3, CreatedAt = new DateTime(2020, 05, 01) },
                 new User() { Id=20, FirstName = "Christophe 2", Age = 26, Gender = "Male", 
                              ProfessionId = 3, CreatedAt = new DateTime(2020, 05, 02) },
                 new User() { Id=21, FirstName = "Christophe 3", Age = 27, Gender = "Male", 
                              ProfessionId = 3, CreatedAt = new DateTime(2020, 05, 03)},

                 new User() { Id=22,  FirstName = "Linda 1", Age = 18, Gender = "Female", 
                              ProfessionId = 1, CreatedAt = new DateTime(2020, 06, 01) },
                 new User() { Id=23,  FirstName = "Linda 2 ", Age = 19, Gender = "Female", 
                              ProfessionId = 1, CreatedAt = new DateTime(2020, 06, 02) },
                 new User() { Id=24, FirstName = "Linda 3", Age = 20, Gender = "Female", 
                              ProfessionId = 1, CreatedAt = new DateTime(2020, 06, 02) },
                 new User() { Id=25, FirstName = "Linda 4", Age = 21, Gender = "Female", 
                              ProfessionId = 1, CreatedAt = new DateTime(2020, 06, 04) },
                 new User() { Id=26, FirstName = "Linda 4", Age = 22, Gender = "Female", 
                              ProfessionId = 1, CreatedAt = new DateTime(2020, 06, 05) },

                 new User() { Id=27, FirstName = "Dalida 1", Age = 40, Gender = "Female", 
                              ProfessionId = 2, CreatedAt = new DateTime(2020, 09, 06) } ,
                 new User() { Id=28, FirstName = "Dalida 2", Age = 41, Gender = "Female", 
                              ProfessionId = 2, CreatedAt = new DateTime(2020, 09, 07) } ,
                 new User() { Id=29, FirstName = "Dalida 3", Age = 42, Gender = "Female", 
                              ProfessionId = 2, CreatedAt = new DateTime(2020, 09, 08) } ,
                 new User() { Id=30, FirstName = "Dalida 4", Age = 43, Gender = "Female", 
                              ProfessionId = 2, CreatedAt = new DateTime(2020, 09, 09) } ,
            };

            modelBuilder.Entity<User>().HasData(
                users
           );
        }
    }

    使用dbContext创建映射。

public class BDDContext : DbContext
    {
        public BDDContext([NotNullAttribute] DbContextOptions options) : base(options)
        {
        }

        public DbSet<User> Users { get; set; }
        public DbSet<Profession> Professions { get; set; }

        #region Required
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.UseSerialColumns();

            modelBuilder.ApplyConfiguration<User>(new UserEntityConfiguration());
            modelBuilder.ApplyConfiguration<Profession>
                         (new ProfessionEntityConfiguration());

            modelBuilder.Seed();
        }
        #endregion
    }

    执行迁移过程。

    在这一步,我们要在每次修改模型后创建或更新数据库结构,如果数据库是空的,则用一组数据填充。这些都可以通过EF迁移工具来完成。

    要做到这一点,我们需要修改Program类的内容,具体如下。

public class Program
 {
     public static void Main(string[] args)
     {
         var host = CreateHostBuilder(args).Build();

         using (var scope = host.Services.CreateScope())
         {
             var db = scope.ServiceProvider.GetRequiredService<BDDContext>();
             db.Database.Migrate();
         }

         host.Run();
     }

     public static IHostBuilder CreateHostBuilder(string[] args) =>
         Host.CreateDefaultBuilder(args)
             .ConfigureWebHostDefaults(webBuilder =>
             {
                 webBuilder.UseStartup<Startup>();
             });
 }

    并通过使用dotnet迁移工具,我们可以在Infrastructureproject内运行以下命令。

dotnet ef migrations add InitialCreate --output-dir Migrations

    最后,当我们启动VisualStudio项目时,我们的模型和数据将在选定的数据库中创建。

    现在,在这个层面,我们可以使用LinqtoEntities来执行对数据库的查询和检索数据。

    3)创建存储库(Repositories)

    这个模式引入了更多关于查询和管理数据库数据的抽象。它被认为是dataAccess部分的主要入口点,包含了不同的方法,可以对数据源进行CRUD或更复杂的操作。

    每个存储库只能管理一个数据库实体,它将使用dbcontext和linqToEntity来查询映射对象(我们的业务实体)。

    因为它依赖于外部资源,所以将在基础设施项目中实现,并将使用接口和依赖注入(DI)暴露给其他项目。

    -创建通用的存储库类。

    在DashBoardWebApp.Infrastructure/Data文件夹中,我们创建一个Repository类。这个类将包含所有特定存储库的所有通用访问数据方法,我们可以列出。

    Delete(TEntityentity)。从数据库上下文中删除一个实体,这个操作将在调用context.savechanges()后应用到数据库中。

    GetAll(Expression<Func<TEntity,bool>>filter=null,List<string>propertiesToInclude=null):返回所有符合filter参数传递条件的实体。返回的结果可以包括所有由'propertiesToInclude'参数指定的关系,这要归功于EntityFramework的急切加载。

    Insert(TEntityentity):将一个新的实体数据添加到数据库上下文中,当我们调用context.savechanges()方法时,该对象将被创建到数据库中。

    Update(TEntityentity):更新一个现有的实体数据,当我们调用context.savechanges()方法时,对象将被更新到数据库中。

public class Repository<TEntity> where TEntity : class
    {
        internal BDDContext context;
        internal DbSet<TEntity> dbSet;

        public Repository(BDDContext context)
        {
            this.context = context;
            this.dbSet = context.Set<TEntity>();
        }

        /// <summary>
        /// remove entity if exists.
        /// </summary>
        /// <param name="entity"></param>
        public virtual void Delete(TEntity entity)
        {
            this.dbSet.Remove(entity);
        }

        /// <summary>
        /// return all entities that match with condition passed by filter argument. 
        /// The result will include all specified relations specified by the 
        /// propertiesToInclude argument.
        /// </summary>
        /// <param name="filter">where condition</param>
        /// <param name="propertiesToInclude">list of relation can be eager loaded</param>
        /// <returns></returns>
        public virtual List<TEntity> GetAll(Expression<Func<TEntity, bool>> filter = null, 
               List<string> propertiesToInclude = null)
        {
            var query = this.dbSet.AsQueryable();

            if (propertiesToInclude != null && propertiesToInclude.Count > 0)
            {
                propertiesToInclude.ForEach(p =>
                {
                    query = query.Include(p);
                });
            }

            if (filter != null)
            {
                return query.Where(filter).ToList();
            }
            else
            {
                return query.ToList();
            }
        }

        /// <summary>
        /// create a new entity
        /// </summary>
        /// <param name="entity"></param>
        public virtual void Insert(TEntity entity)
        {
            this.dbSet.Add(entity);
        }

        /// <summary>
        /// update an existing entity. 
        /// </summary>
        /// <param name="entity"></param>
        public virtual void Update(TEntity entity)
        {
            this.dbSet.Update(entity);
        }
    }

    -实现UserRepository。

    在DashBoardWebApp.Domain/Repositories文件夹中,创建IUserRepository接口来定义所需的访问数据方法列表,这些方法对于在应用逻辑项目中实现业务规则非常有用。这些方法是:oGetUsersByYear()。

    GetUsersByYear(intyear):返回在特定年份创建的所有用户。

    GetAllCreatedUsersYears():返回所有创建用户的年份。这些信息对于建立一个年份的过滤器,获取特定年份的创建用户数据很有用。

public interface IUserRepository
  {
      List<User> GetUsersByYear(int year);
      List<int> GetAllCreatedUsersYears();
  }

    之后,我们在DashBoardWebApp.Infrastructure/Data/Repositories文件夹中创建UserRepository.cs。这个类应该重用Repository类的常用方法,并实现IUserRepository接口声明的方法。

public class UserRepository : Repository<User>, IUserRepository
    {
        private readonly BDDContext _context;

        public UserRepository(BDDContext context) : base(context)
        {
            this._context = context;
        }

        public List<User> GetUsersByYear(int year)
        {
            Expression<Func<User, bool>> filterByYear = (u) => u.CreatedAt.Year == year;

            List<String> propertiesToInclude = new List<string>() { "Profession" };
            return base.GetAll(filterByYear, 
                   propertiesToInclude)?.OrderBy(u => u.CreatedAt).ToList();
        }

        public List<int> GetAllCreatedUsersYears()
        {
            return this.dbSet?.Select
                   (u => u.CreatedAt.Year).Distinct().OrderBy(y => y).ToList();
        }
    }

    一旦我们完成了资源库的实现,我们就可以使用它们来构建我们的应用逻辑?但是,在此之前,我们需要通过修改Startup类的ConfigureServices方法,将其声明到依赖注入(DI)系统中。

public void ConfigureServices(IServiceCollection services)
   {
       services.AddControllersWithViews();
       services.AddDbContext<BDDContext>(
         options => options.UseNpgsql("Host=localhost; user id=postgres;
                    password=YOUR_PASSWORD; database=DashboardBDD"));

       services.AddScoped<IUserRepository, UserRepository>();
   }

    二)实现应用逻辑

    我们要实现的用例是检索三种数据。

    -第一种是检索按月分组的特定年份的订阅用户,这些数据可以投射到线图组件中。

    -第二种是按职业来检索特定年份的订阅用户,这些数据将显示在饼图里面。

    -第三个是按年龄分组检索特定年份的订阅用户,这个数据会投射在饼图里面。

    要进行实现,我们需要创建我们的视图模型类。

    -在DashBoardWebApp.Application/common/DTO里面创建LineChartDataDTO模型。

    它拥有一个点的x,y坐标在一个Line图表里面。

public class LineChartDataDTO
 {
     public DateTime X { get; set; }
     public decimal Y { get; set; }

     public LineChartDataDTO()
     {

     }

     public LineChartDataDTO(DateTime x, int y)
     {
         this.X = x;
         this.Y = y;
     }
 }

    在DashBoardWebApp.Application/common/DTO中创建PieChartDataDTO模型。

    它保存了Pie图中一个切片的标签和百分比值。

public class PieChartDataDTO
    {
        public string Label { get; set; }
        public decimal Value { get; set; }

        public PieChartDataDTO()
        {

        }

        public PieChartDataDTO(string label, decimal value)
        {
            Label = label;

            Value = Math.Round(value, 2);
        }
    }

    在DashBoardWebApp.Application/UseCases/DashBoard/DTO.Application/UseCases/DashBoard/DTO中创建DashBoardDTO模型。

    该模型包含了客户端所需的所有数据,它拥有:所有用户的创建年限列表。

    所有用户创建年份的列表

    特定年份内按月份分类的订阅用户列表。

    按性别分列的某一年的注册用户名单。

    按专业分类的特定年份的注册用户名单。

public class DashBoardDTO
   {
       public List<int> Years { get; set; }
       public List<LineChartDataDTO> SubscribedUsersForYearGroupedByMonth { get; set; }
       public List<PieChartDataDTO> SubscribedUsersForYearGroupedByGender { get; set; }
       public List<PieChartDataDTO>
              SubscribedUsersForYearGroupedByProfession { get; set; }
   }
public interface IDashboardService
   {
       DashBoardDTO GetSubscribedUsersStatsByYear(int? year);
   }

    在DashBoardWebApp.Application/UseCases/DashBoard/services里面创建DashboardService。

    这个类包含了合约所暴露的不同方法的实现。

public class DashboardService : IDashboardService
   {
       private IUserRepository _userRepository;
       public DashboardService(IUserRepository userRepository)
       {
           this._userRepository = userRepository;
       }

       public DashBoardDTO GetSubscribedUsersStatsByYear(int? year)
       {
           DashBoardDTO dashBoard = new DashBoardDTO();

           dashBoard.Years = this._userRepository.GetAllCreatedUsersYears();

           if (dashBoard.Years == null || dashBoard.Years.Count == 0)
           {
               return dashBoard;
           }

           if (!year.HasValue)
           {
               //if year not exists then set it with the last year from years list.
               year = dashBoard.Years.LastOrDefault();
           }

           List<User> subsribedUsers = this._userRepository.GetUsersByYear(year.Value);

           if (subsribedUsers?.Count == 0)
           {
               return dashBoard;
           }

           dashBoard.SubscribedUsersForYearGroupedByMonth =
                     subsribedUsers.GroupBy(g => g.CreatedAt.Month).Select
                     (g => new LineChartDataDTO(g.First().CreatedAt, g.Count())).ToList();

           var totalCount = subsribedUsers.Count;

           dashBoard.SubscribedUsersForYearGroupedByGender = subsribedUsers.GroupBy
                     (g => g.Gender).Select(g => new PieChartDataDTO(g.Key, g.Count()*
                     100/(decimal)totalCount )).ToList();
           dashBoard.SubscribedUsersForYearGroupedByProfession =
                     subsribedUsers.GroupBy(g => g.Profession.Name).Select
                     (g => new PieChartDataDTO(g.Key, g.Count() *
                     100 / (decimal)totalCount )).ToList();

           dashBoard.SubscribedUsersForYearGroupedByGender.Last().Value =
           100 - dashBoard.SubscribedUsersForYearGroupedByGender.Where(d => d !=
           dashBoard.SubscribedUsersForYearGroupedByGender.Last()).Sum(d => d.Value);
           dashBoard.SubscribedUsersForYearGroupedByProfession.Last().Value =
           100 - dashBoard.SubscribedUsersForYearGroupedByProfession.Where
           (d => d != dashBoard.SubscribedUsersForYearGroupedByProfession.Last()).Sum
           (d => d.Value);

           return dashBoard;
       }
   }

    最后,我们在DI系统里面声明DashboardService类,在每次请求时自动在我们的控制器里面实例化。我们需要在ConfigureServicesofStartup类中添加以下指令。

services.AddScoped<IDashboardService, DashboardService>();

    三)实现Web服务

    后端的最后一步是为开发的用例创建一个入口点,为此,我们应该创建一个DashboardWebAPI类,为我们开发的用例暴露一个端点。它将只包含一个名为FilterByYear的方法,它将返回特定年份的订阅用户的所有所需信息。

[Route("api/dashboard")]
    [ApiController]
    public class DashboardApi : ControllerBase
    {
        private readonly IDashboardService _dashboardService;

        public DashboardApi(IDashboardService dashboardService)
        {
            this._dashboardService = dashboardService;
        }

        [HttpGet("{year:int?}")]
        public IActionResult FilterByYear([FromRoute] int? year)
        {
            return Ok(this._dashboardService.GetSubscribedUsersStatsByYear(year));
        }
    }

    四)测试WebAPI

    为了测试我们的WebAPI服务,我们可以使用Postman来创建和执行httpREST请求。

    -在VisualStudio中启动项目

    -使用Postman创建一个新的REST请求。

web

    -执行请求。

web

    更多Web教程尽在开课吧Web教程频道

有用
分享