目录
介绍
先决条件
创建项目架构
创建数据库
实现后端
一)创建DataAccess
1)创建实体和关系
2)设置数据库
3)创建存储库
II)实现应用逻辑
III)实现Web服务
IV)测试Web API
实现前端
运行应用程序
参考
通过本文,我们将使用.NET MVC Core项目从头开始构建一个漂亮的仪表板网页。在此处概述的步骤中,您将学习干净的体系结构,实体框架代码优先方法,开发Web服务以及使用chartjs。
- 下载源代码(RAR)-854.9 KB
在专业领域,公司的CEO或合格的经理希望快速访问所有关键数据点,以帮助分析、比较和做出相关决定。
仪表板是一种对公司的基本数据具有全局视角的方法,这些仪表板的一些用例是比较特定两年的净销售额、网站每年每月的订阅者数量和Azure租户的订阅者数量。 仪表板通常由图表和表格表示。
有很多JavaScript库可帮助构建可用于的漂亮图形视觉,其中最好的是ChartJs。
通过本文,我们将构建一个漂亮的仪表板Web应用程序,其中显示有关已订阅用户的一些指标。该应用程序将使用C#,ASP.NET MVC Core,JavaScript和ChartJs库构建。
在阅读本文之后,您将了解更多有关:
- 干净的架构
- 使用代码优先方法的实体框架核心
- 依赖注入
- PostgreSQL数据库
- CharJs
- 使用PostMan测试API端点
要理解本文,您应该具有有关ASP.NET MVC Core和JavaScript的基本知识。
创建项目架构在本教程中,我们将采用简洁的架构原理从头开始开发我们的应用程序。
采用纯净的架构,由于关注点的分离,我们的应用程序将在可维护性和可测试性方面获得很多收益,并且它不会专注于特定的框架或技术,而是专注于领域逻辑。建议您访问此链接和此链接,以获取完整的定义并深入了解此类最佳实践。
我们的项目将包括四个部分:
- UI:组成如下:
- 交互器:它拦截表示层发送的请求,执行关联的方案并返回正确的结果以供视图显示。对于我们的示例,它是一个API控制器。
- 表示层:它构成GUI的一部分,可以通过Angular,AngularJs,ASP.NET MVC Core等任何框架进行开发。对于我们的示例,它将是ASP.NET MVC Core项目。
- Application logic:用于实现我们的业务规则的一组工作流程(用例)。它们的主要目的是从控制器接收请求模型,并将其转换为结果,然后传递回视图。在我们的例子中,它将是一个.NET Core库项目。
- Domain:引用我们的业务逻辑的一组模型或实体。它应该独立于框架。在我们的例子中,它将是一个.NET Core库项目。
- Infrastructure:它包含从外部数据源(例如数据库、服务、库或文件)管理和收集数据的方式。基础结构使用领域类与外部数据源进行交互并收集响应数据。对于我们的应用程序,它将包含要与PostgreSql数据库交换的存储库和配置。在我们的例子中,它将是一个.NET Core库项目。
下图显示了最终项目结构的快照:
由于使用了EF Framework,我们的应用程序将独立于数据库,我们可以插入任何类型的数据库,例如PostgreSql,Oracle或SqlServer数据库,我们只需要更改提供程序即可。
对于此应用程序,我们将使用PostgreSql,首先我们需要将其安装在本地计算机上并创建一个新的空数据库,以执行以下操作:
- 从此链接下载并安装pgadmin4 。
- 从此链接下载并安装pgAgent 。
- 创建一个空数据库:我们需要启动pgadmin应用程序并创建一个新数据库,如下所示:
DataAccess是确保应用程序和数据库之间对话的一组类和配置。它的主要职责是定义业务实体,操作CRUD操作并将应用程序数据请求转换为数据库服务器已知的某些指令,反之亦然。
通过使用以下技术或框架之一来确保通信:ADO.NET,EF,NHibernate等,它们都具有相同的主要目标,这使应用程序和数据库之间的对话过程更加容易且透明。
对于我们的应用程序,我们将使用EF(实体框架),它是.NET Core项目中使用最流行的ORM,它具有多种优点,例如:
- 领域类和关系数据之间的映射。
- 通过使用对实体的Linq引入更多抽象来管理和收集数据库中的数据。
- 可以支持不同的关系数据库系统,例如PostgreSql、Oracle和SqlServer。
- 提供多种方法,例如代码优先、数据库优先、模型优先。
- 借助延迟加载机制,可以按需加载数据。
- 与诸如ADO.NET之类的旧数据访问技术相比,借助映射过程,EF ORM使从数据库中读取和写入数据变得更加容易,用户将更多地关注如何开发业务逻辑,而不是查询的构建。它节省了可观的开发时间。
- 在本节中,我们将重点介绍如何在模型与数据库实体之间创建链接,如何使用EF代码优先方法创建数据库架构以及为演示准备数据集。
我们的数据库的架构将由以下实体组成:
- User:用名字、年龄、工作和性别表示。
- Profession:是用户可以从事的工作,例如牙医、软件开发人员、教师等。
下面的类图将清楚地描述这些实体与每个表的属性列表之间的关系:
这些主要类将在DashBoardWebApp.Domain项目的Entities文件夹中创建:
- 创建User类:
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; }
}
- 创建Profession类:
public class Profession
{
public int Id { get; set; }
public string Name { get; set; }
public List Users { get; set; }
}
2)设置数据库
为了确保应用程序模型和数据库实体之间的映射,我们必须遵循以下步骤:
- 首先,使用Nuget Package Manager安装EF Core软件包和EF的PostegreSql提供程序:
- 配置实体和关系数据之间的映射:
在DashBoardWebApp.Infrastructure/Data/Config内部,我们定义要映射的每个实体的约束和关系的列表:
- 创建UserEntityConfiguration类:
public class UserEntityConfiguration : IEntityTypeConfiguration
{
public void Configure(EntityTypeBuilder 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
{
public void Configure(EntityTypeBuilder 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);
}
}
- 创建种子数据:
我们需要通过添加使用数据初始化数据库的种子方法来创建扩展ModelBuilder函数的ModelBuilderExtensions类,该类将在DashBoardWebApp.Infrastructure/Data/Config内部创建:
public static class ModelBuilderExtensions
{
public static void Seed(this ModelBuilder modelBuilder)
{
List professions = new List()
{
new Profession() { Id = 1, Name = "Software Developer"},
new Profession() { Id = 2, Name = "Dentist"},
new Profession() { Id = 3, Name = "Physician" }
};
modelBuilder.Entity().HasData(
professions
);
List users = new List()
{
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().HasData(
users
);
}
}
- 使用dbContext创建映射:
public class BDDContext : DbContext
{
public BDDContext([NotNullAttribute] DbContextOptions options) : base(options)
{
}
public DbSet Users { get; set; }
public DbSet Professions { get; set; }
#region Required
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.UseSerialColumns();
modelBuilder.ApplyConfiguration(new UserEntityConfiguration());
modelBuilder.ApplyConfiguration
(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();
db.Database.Migrate();
}
host.Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup();
});
}
通过使用dotnet迁移工具,我们可以在Infrastructure项目中运行以下命令:
dotnet ef migrations add InitialCreate --output-dir Migrations
最后,当我们启动Visual Studio项目时,我们的模型和数据将在所选数据库中创建。
现在,在此级别上,我们可以使用Linq to Entities来执行对数据库的查询并检索数据。
3)创建存储库这种模式引入了更多有关从数据库查询和管理数据的抽象。它被视为dataAccess部分的主要入口点,并且包含使CRUD或更复杂的数据源操作变得不同的方法。
每个存储库只能管理一个数据库实体,它将使用dbcontext和linqToEntity查询映射对象(我们的业务实体)。
该实现将进入基础结构项目,因为它依赖于外部资源,并将使用接口和依赖项注入(DI)公开给其他项目。
- 创建通用存储库类:
在DashBoardWebApp.Infrastructure/Data文件夹中,我们创建一个Repository类。此类将包含所有特定存储库的所有通用访问数据方法,我们可以列出:
-
- Delete(TEntity entity):从数据库上下文中删除实体,此操作将在调用context.savechanges()后应用于数据库。
- GetAll(Expression filter = null, List propertiesToInclude = null):返回与filter参数传递的条件匹配的所有实体。由于实体框架及时加载,返回的结果可以包含'propertiesToInclude'参数指定的所有指定关系。
- Insert(TEntity entity):将新的实体数据添加到数据库上下文中,当我们调用context.savechanges()方法时,该对象将被创建到数据库中。
- Update(TEntity entity):更新现有的实体数据,当我们调用context.savechanges()方法时,对象将被更新到数据库中。
public class Repository where TEntity : class
{
internal BDDContext context;
internal DbSet dbSet;
public Repository(BDDContext context)
{
this.context = context;
this.dbSet = context.Set();
}
///
/// remove entity if exists.
///
///
public virtual void Delete(TEntity entity)
{
this.dbSet.Remove(entity);
}
///
/// return all entities that match with condition passed by filter argument.
/// The result will include all specified relations specified by the
/// propertiesToInclude argument.
///
/// where condition
/// list of relation can be eager loaded
///
public virtual List GetAll(Expression filter = null,
List 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();
}
}
///
/// create a new entity
///
///
public virtual void Insert(TEntity entity)
{
this.dbSet.Add(entity);
}
///
/// update an existing entity.
///
///
public virtual void Update(TEntity entity)
{
this.dbSet.Update(entity);
}
}
- 实现UserRepository:
在DashBoardWebApp.Domain/Repositories文件夹中,创建IUserRepository接口以定义所需访问数据方法的列表,这些方法对于在应用程序逻辑项目内实现业务规则非常有用。这些方法是:
-
- GetUsersByYear(int year):返回在特定年份创建的所有用户。
- GetAllCreatedUsersYears():返回所有创建的用户年份。该信息对于构建年份过滤器很有用,该过滤器可获取特定年份的创建用户数据。
public interface IUserRepository
{
List GetUsersByYear(int year);
List GetAllCreatedUsersYears();
}
之后,我们在DashBoardWebApp.Infrastructure/Data/Repositories文件夹中创建UserRepository.cs。此类应重用该Repository类的通用方法并实现IUserRepository接口声明的方法:
public class UserRepository : Repository, IUserRepository
{
private readonly BDDContext _context;
public UserRepository(BDDContext context) : base(context)
{
this._context = context;
}
public List GetUsersByYear(int year)
{
Expression filterByYear = (u) => u.CreatedAt.Year == year;
List propertiesToInclude = new List() { "Profession" };
return base.GetAll(filterByYear,
propertiesToInclude)?.OrderBy(u => u.CreatedAt).ToList();
}
public List GetAllCreatedUsersYears()
{
return this.dbSet?.Select
(u => u.CreatedAt.Year).Distinct().OrderBy(y => y).ToList();
}
}
一旦完成了存储库的实现,就可以使用它们来构建我们的应用程序逻辑吗?但是,在此之前,我们需要通过修改Startup class以下ConfigureServices方法将其声明为依赖注入(DI)系统:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddDbContext(
options => options.UseNpgsql("Host=localhost; user id=postgres;
password=YOUR_PASSWORD; database=DashboardBDD"));
services.AddScoped();
}
II)实现应用逻辑
我们要实现的用例是检索三种数据:
- 第一个是检索按月分组的特定年份的已订阅用户,并且可以在折线图组件中投影此数据。
- 第二个是按专业分组检索特定年份的订阅用户,此数据将显示在饼图中。
- 第三个是检索按年龄分组的特定年份的订阅用户,此数据将投影在饼图中。
要执行该实现,我们需要创建我们的视图模型类:
- 在DashBoardWebApp.Application/common/DTO中创建LineChartDataDTO模型:
它保存折线图中的点的x,y坐标。
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模型:
它在饼图中保存切片的标签和百分比值。
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中创建DashBoardDTO模型:
该模型包含客户端所需的所有数据,它包含:
-
- 所有用户创建年份列表
- 特定年份中按月分组的已订阅用户列表
- 特定年份中按性别分组的已订阅用户列表
- 特定年份中按专业分组的已订阅用户列表
public class DashBoardDTO
{
public List Years { get; set; }
public List SubscribedUsersForYearGroupedByMonth { get; set; }
public List SubscribedUsersForYearGroupedByGender { get; set; }
public List
SubscribedUsersForYearGroupedByProfession { get; set; }
}
- 在DashBoardWebApp.Application/UseCases/DashBoard/services中创建IDashboardService接口:
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 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类将根据每个请求在我们的控制器内自动实例化。我们需要将以下指令添加到Startup类的ConfigureServices方法。
services.AddScoped();
III)实现Web服务
后端的最后一步是为开发的用例创建一个入口点,为此,我们应该创建一个Dashboard Web API类,为我们的开发用例公开一个端点。它仅包含一个称为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));
}
}
IV)测试Web API
为了测试我们的Web API服务,我们可以使用Postman创建和执行http REST请求:
- 从Visual Studio启动项目。
- 使用Postman创建一个新的REST请求。
- 执行请求。
- 首先,我们需要导入chartJs和moment.js库到布局页面中(路径:Views/Home/_Layout.chtml)。
- 接下来,修改主页(路径:Views/Home/Index.chtml)以显示主要过滤器,折线图和两个饼图:
@{
ViewData["Title"] = "Home Page";
}
Developing a dashboard web application with ASP.NET MVC Core,
WEB Api, JavaScript, PostegreSql and ChartJs
- 接下来,通过创建dashboard.css文件(路径:wwwroot/css/dashboard.css)向主页添加某种样式,然后将其包含在布局页面中。
.fullWidth {
width: 100%
}
.flex-d-column {
display: flex;
flex-direction: column;
}
.flex-d-row {
display: flex;
flex-direction: row;
}
.chart-container {
flex: 1;
}
- 然后,使用JavaScript语言对主页执行操作:我们需要创建一个dashboard.js(路径:wwwroot/js/dashboard.js),该文件将由上述功能列表组成:
- drawLineChart:此函数创建一个饼图配置,用于在特定画布上绘制或更新图表。
- drawPieChart:此函数创建一个饼图配置,用于在特定画布上绘制或更新图表。
- drawChart:使用上述功能创建的设置,它将更新图表的现有实例(如果存在),或者在特定画布上创建新图形,并返回将用于将来更新的新实例。
- makeRandomColor:将返回随机的十六进制颜色。此函数在每次更新操作中将随机颜色分配给不同的图表。
- filterDashboardDataByYear:这是我们的主要功能,它将在全局年份过滤器上检测到每次更改后将触发,它将向仪表板API发送请求并获得响应data(),该响应将显示在专用图表中。
$(document).ready(function () {
let lineChart1 = null;
let pieChart1 = null;
let pieChart2 = null;
function drawChart(chartInstance, canvasId, chartSettings) {
if (chartInstance != null) {
//update chart with new configuration
chartInstance.options = { ...chartSettings.options };
chartInstance.data = { ...chartSettings.data };
chartInstance.update();
return chartInstance;
} else {
//create new chart.
var ctx = document.getElementById(canvasId).getContext('2d');
return new Chart(ctx, chartSettings);
}
}
function buildSelectFilter(years, currentYear) {
//clear all options.
$("#filterByYear").empty();
var selectOptionsFilterHtml = "";
if (years) {
years.forEach((year) => {
selectOptionsFilterHtml += `${year}`
});
}
$("#filterByYear").append(selectOptionsFilterHtml);
}
function makeRandomColor() {
return "#" + Math.floor(Math.random() * 16777215).toString(16);
}
function drawLineChart(chartInstance, canvasId, data, titleText) {
let settings = {
// The type of chart we want to create
type: 'line',
// The data for our dataset
data: {
datasets: [{
backgroundColor: 'rgba(255,0,0,0)',
borderColor: makeRandomColor(),
data: data
}]
},
// Configuration options go here
options: {
legend: {
display: false
},
title: {
display: true,
text: titleText,
fontSize: 16
},
scales: {
xAxes: [{
type: 'time',
time: {
unit: 'month',
displayFormats: {
month: 'MM YYYY'
}
}
}]
}
}
};
return drawChart(chartInstance, canvasId, settings);
}
function drawPieChart(chartInstance, canvasId, data, labels, titleText) {
//generate random color for each label.
let bgColors = [];
if (labels) {
bgColors = labels.map(() => {
return makeRandomColor();
});
}
var settings = {
// The type of chart we want to create
type: 'pie',
// The data for our dataset
data: {
labels: labels,
datasets: [{
backgroundColor: bgColors,
borderColor: bgColors,
data: data
}],
},
// Configuration options go here
options: {
tooltips: {
callbacks: {
label: function (tooltipItem, data) {
//create custom display.
var label = data.labels[tooltipItem.index] || '';
var currentData = data.datasets[0].data[tooltipItem.index];
if (label) {
label = `${label} ${Number(currentData)} %`;
}
return label;
}
}
},
title: {
display: true,
text: titleText,
fontSize: 16
},
}
};
return drawChart(chartInstance, canvasId, settings);
}
function filterDashboardDataByYear(currentYear) {
currentYear = currentYear || '';
let url = `http://localhost:65105/api/dashboard/${currentYear}`;
$.get(url, function (data) {
if (!currentYear && data.years.length > 0) {
//pick the last year.
currentYear = data.years.reverse()[0];
}
buildSelectFilter(data.years, currentYear);
let data1 = [];
if (data.subscribedUsersForYearGroupedByMonth) {
data1 = data.subscribedUsersForYearGroupedByMonth.map
(u => { return { "x": moment(u.x, "YYYY-MM-DD"), "y": u.y } });
}
let data2 = [];
let labels2 = [];
if (data.subscribedUsersForYearGroupedByGender) {
data2 = data.subscribedUsersForYearGroupedByGender.map(u => u.value);
labels2 = data.subscribedUsersForYearGroupedByGender.map(u => u.label);
}
let data3 = [];
let labels3 = [];
if (data.subscribedUsersForYearGroupedByProfession) {
data3 = data.subscribedUsersForYearGroupedByProfession.map(u => u.value);
labels3 =
data.subscribedUsersForYearGroupedByProfession.map(u => u.label);
}
lineChart1 = drawLineChart(lineChart1, "mylineChart1", data1,
`Number of subscribed users per month in ${currentYear}`);
pieChart1 = drawPieChart(pieChart1, "mypieChart1", data2, labels2,
`Number of subscribed users in ${currentYear}
grouped by gender`);
pieChart2 = drawPieChart(pieChart2, "mypieChart2", data3, labels3,
`Number of subscribed users in
${currentYear} grouped by profession`);
});
}
filterDashboardDataByYear();
$(document).on("change", "#filterByYear", function () {
filterDashboardDataByYear(parseInt($(this).val()));
});
});
该JS文件应导入到布局页面中。
当我们第一次运行该应用程序时,显示的数据将根据创建用户的最后一年进行过滤。
我们可以通过combobox选择其他年份,以显示与所选年份相关的其他用户统计信息。
- 干净的架构
- ChartJs
- PostegreSql
- 实体框架
- Linq to Entities
- Dotnet迁移工具
https://www.codeproject.com/Articles/5292975/Dynamic-Dashboard-Web-Application-using-ASP-NET-Co