一个遵循CleanArchitecture原则的Asp.net core轻量级开源项目
这是一个基于最新的ASP.net core 5.0创建Razor Page应用程序解决方案模板。遵循Clean Architecture的原则,以最求简洁的代码风格和实现快速开发小型的web业务系统的目标,并且从没停止过更新。该项目从最早的asp.net web form,asp.net mvc5 到 asp.net core 3.1再到现在最新的asp.net core 5.0 Razor Page,从简单三层结构到N层结构再到现在流行的CQRS模式,一遍一遍的再重构,在这过程中体会到系统架构的重要性和在优秀的框架下开发系统是一件多么愉快的事情。做这样的项目纯粹是为了兴趣和能和很多Github上优秀的程序员一起交流和学习。
介绍
- Github: neozhu/RazorPageCleanArchitecture
- Demo: http://razor.i247365.net/
- For Asp.net Core MVC neozhu/smartadmin.core.urf
Technologies
- ASP.NET Core 5
- Entity Framework Core 5
- SmartAdmin - Responsive WebApp
- Razor Pages
- Jquery EasyUI
- MediatR
- AutoMapper
- FluentValidation
- NUnit, FluentAssertions, Moq & Respawn
- Docker
特点
- 遵循Clean Architecture原则什么是Clean Architecture
- 非常漂亮的用户界面SmartAdmin - Responsive WebApp
- 遵循CQRS模式极简的代码风格什么是CQRS
- 实现了基本的CRUD功能
- 实现了基本的认证和授权功能
- 支持多语言切换
项目结构
基本功能预览
- 新增
- 修改
- 删除
- 查询
- 导入Excel
- 下载模板
- 导出Excel
用户管理
- 新增
- 修改
- 删除
- 查询
- 导入Excel
- 下载模板
- 导出Excel
- 重置密码
- 角色管理
角色管理
- 新增
- 修改
- 删除
- 查询
- 导入Excel
- 下载模板
- 导出Excel
- 授权管理
如何开始
- 在Domain project中新增一个Entity,比如Customer客户信息
public partial class Customer : AuditableEntity, IHasDomainEvent
{
public int Id { get; set; }
public string Name { get; set; }
public string NameOfEnglish { get; set; }
public string GroupName { get; set; }
public PartnerType PartnerType { get; set; }
public string Region { get; set; }
public string Sales { get; set; }
public string RegionSalesDirector { get; set; }
public string Address { get; set; }
public string AddressOfEnglish { get; set; }
public string Contract { get; set; }
public string Email { get; set; }
public string PhoneNumber { get; set; }
public string Fax { get; set; }
public string Comments { get; set; }
public List<DomainEvent> DomainEvents { get; set; } = new();
}
- 在Application project中实现具体的功能请遵循CQRS模式
- Command
- AddEdit
- Delete
- Import
- DTOs
- Eventhandlers
- Queries
- Export
- PaginationQuery
- 在SmartAdmin.WebUI中添加UI页面
@page
@using CleanArchitecture.Razor.Domain.Enums
@using CleanArchitecture.Razor.Infrastructure.Constants.Permission
@model SmartAdmin.WebUI.Pages.Customers.IndexModel
@inject Microsoft.Extensions.Localization.IStringLocalizer<IndexModel> _localizer
@inject Microsoft.AspNetCore.Authorization.IAuthorizationService _authorizationService
@{
ViewData["Title"] = _localizer["Customers"].Value;
ViewData["PageName"] = "customers_index";
ViewData["Category1"] = _localizer["Customers"].Value;
ViewData["Heading"] = _localizer["Customers"].Value;
ViewData["PageDescription"] = _localizer["See all available options"].Value;
ViewData["PreemptiveClass"] = "Default";
var _canCreate = await _authorizationService.AuthorizeAsync(User, null, Permissions.Customers.Create);
var _canEdit = await _authorizationService.AuthorizeAsync(User, null, Permissions.Customers.Edit);
var _canDelete = await _authorizationService.AuthorizeAsync(User, null, Permissions.Customers.Delete);
var _canSearch = await _authorizationService.AuthorizeAsync(User, null, Permissions.Customers.Search);
var _canImport = await _authorizationService.AuthorizeAsync(User, null, Permissions.Customers.Import);
var _canExport = await _authorizationService.AuthorizeAsync(User, null, Permissions.Customers.Export);
}
@section HeadBlock {
<link rel="stylesheet" media="screen, print" href="~/css/formplugins/bootstrap-daterangepicker/bootstrap-daterangepicker.css">
<link rel="stylesheet" media="screen, print" href="~/css/fa-solid.css">
<link rel="stylesheet" media="screen, print" href="~/css/theme-demo.css">
<link rel="stylesheet" media="screen,print" href="~/lib/easyui/themes/insdep/easyui.css">
<style>
.customer_dg_datagrid-cell-c1-_action {
overflow: visible !important
}
</style>
}
<div id="js-page-content-demopanels" class="card mb-g">
<div class="card-header bg-white d-flex align-items-center">
<h4 class="m-0">
@_localizer["Customers"]
<small>@_localizer["See all available options"]</small>
</h4>
<div class="ml-auto">
@if (_canCreate.Succeeded)
{
<button class="btn btn-sm btn-outline-primary " id="addbutton">
<span class="@(Settings.Theme.IconPrefix) fa-plus mr-1"></span>
@_localizer["Add"]
</button>
}
@if (_canDelete.Succeeded)
{
<button class="btn btn-sm btn-outline-danger" disabled id="deletebutton">
<span class="@(Settings.Theme.IconPrefix) fa-trash-alt mr-1"></span>
@_localizer["Delete"]
</button>
}
@if (_canSearch.Succeeded)
{
<button class="btn btn-sm btn-outline-primary " id="searchbutton">
<span class="@(Settings.Theme.IconPrefix) fa-search mr-1"></span>
@_localizer["Search"]
</button>
}
@if (_canImport.Succeeded)
{
<div class="btn-group" role="group">
<button id="importbutton" type="button" class="btn btn-sm btn-outline-primary waves-effect waves-themed">
<span class="@(Settings.Theme.IconPrefix) fa-upload mr-1"></span> @_localizer["Import Excel"]
</button>
<button type="button" class="btn btn-sm btn-outline-primary dropdown-toggle dropdown-toggle-split waves-effect waves-themed" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="sr-only">Toggle Dropdown</span>
</button>
<div class="dropdown-menu" aria-labelledby="importbutton">
<button id="gettemplatebutton" class="dropdown-item">@_localizer["Download Template"]</button>
</div>
</div>
}
@if (_canExport.Succeeded)
{
<button class="btn btn-sm btn-outline-primary " id="exportbutton">
<span class="@(Settings.Theme.IconPrefix) fa-download mr-1"></span>
@_localizer["Export Excel"]
</button>
}
</div>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-12">
<table id="customer_dg">
</table>
</div>
</div>
</div>
</div>
<div class="modal fade" id="customer_modal" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Modal title</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true"><i class="@(Settings.Theme.IconPrefix) fa-times"></i></span>
</button>
</div>
<form id="customer_form" class="needs-validation" novalidate="novalidate">
...
</form>
</div>
</div>
</div>
@await Component.InvokeAsync("ImportExcel", new { importUri = Url.Page("/Customers/Index") + "?handler=Import",
getTemplateUri = @Url.Page("/Customers/Index") + "?handler=CreateTemplate",
onImportedSucceeded = "reload()" })
@section ScriptsBlock {
<partial name="_ValidationScriptsPartial" />
<script type="text/javascript" src="~/lib/easyui/jquery.easyui.min.js" asp-append-version="true"></script>
<script type="text/javascript" src="~/lib/easyui/jquery.easyui.component.js" asp-append-version="true"></script>
<script type="text/javascript" src="~/lib/easyui/plugins/datagrid-filter.js" asp-append-version="true"></script>
<script>jQuery.fn.tooltip = bootstrapTooltip;</script>
<script src="~/lib/axios/dist/axios.js"></script>
<script src="~/lib/jquery-form/jquery.jsonToForm.js"></script>
<script type="text/javascript">
$('#searchbutton').click(function () {
reload();
});
$('#addbutton').click(function () {
popupmodal(null);
});
$('#deletebutton').click(function () {
onDeleteChecked();
});
$('#exportbutton').click(function () {
onExport();
});
$('#importbutton').click(function () {
showImportModal();
});
$('#gettemplatebutton').click(function () {
onGetTemplate();
});
$('#customer_form :submit').click(function (e) {
...
event.preventDefault();
event.stopPropagation();
})
var $dg={};
var initdatagrid = () => {
$dg = $('#customer_dg').datagrid({
...
}
var reload = () => {
$dg.datagrid('load', '@Url.Page("/Customers/Index")?handler=Data');
}
$(() => {
initdatagrid();
})
var popupmodal = (customer) => {
...
}
var onEdit = (index) => {
var customer = $dg.datagrid('getRows')[index];
popupmodal(customer);
}
var onDelete = (id) => {
...
}
var onDeleteChecked = () => {
...
}
var onExport = () => {
...
}
</script>
}
我的项目成果
| 网站 | 账号/密码 | 截图 |
|---|---|---|
| http://tms.i247365.net/ TMS 运输管理系统 | demo@email.com/123456 | |
| http://manage.i247365.net/ 委托业务内控管理系统 | demo/123456 | |
| http://check.i247365.net/ 人脸考勤管理系统 | demo/123456 | |
| http://supplier.i247365.net/ 供应商询价系统 | demo/123456 | |
| http://iot.i247365.net/ 智能水务综合管理平台 | demo/123456 | |
| http://book.i247365.net/ 小型图书管理工具 | demo/123456 |
最后
keep coding, enjoy coding.
如果你喜欢这个项目,请记得在Github上点赞,谢谢
https://github.com/neozhu/RazorPageCleanArchitecture
一个遵循CleanArchitecture原则的Asp.net core轻量级开源项目的更多相关文章
- Asp.Net Core 2.0 项目实战(2)NCMVC一个基于Net Core2.0搭建的角色权限管理开发框架
Asp.Net Core 2.0 项目实战(1) NCMVC开源下载了 Asp.Net Core 2.0 项目实战(2)NCMVC一个基于Net Core2.0搭建的角色权限管理开发框架 Asp.Ne ...
- 从零开始一个个人博客 by asp.net core and angular(一)
这是一个个人叙述自己建设博客的帖子,既然是第一篇那肯定是不牵扯代码了,主要讲一下大体的东西,微软最新的web框架应该就数asp.net core 3.1了这是一个长期支持版,而且是跨平台又开源版本,所 ...
- [Asp.Net Core轻量级Aop解决方案]AspectCore Project 介绍
AspectCore Project 介绍 什么是AspectCore Project ? AspectCore Project 是适用于Asp.Net Core 平台的轻量级 Aop(Aspect- ...
- Asp.Net Core轻量级Aop解决方案:AspectCore
什么是AspectCore Project ? AspectCore Project 是适用于Asp.Net Core 平台的轻量级 Aop(Aspect-oriented programming) ...
- Asp.Net Core 2.0 项目实战(10) 基于cookie登录授权认证并实现前台会员、后台管理员同时登录
1.登录的实现 登录功能实现起来有哪些常用的方式,大家首先想到的肯定是cookie或session或cookie+session,当然还有其他模式,今天主要探讨一下在Asp.net core 2.0下 ...
- Asp.Net Core 2.0 项目实战(11) 基于OnActionExecuting全局过滤器,页面操作权限过滤控制到按钮级
1.权限管理 权限管理的基本定义:百度百科. 基于<Asp.Net Core 2.0 项目实战(10) 基于cookie登录授权认证并实现前台会员.后台管理员同时登录>我们做过了登录认证, ...
- Asp.Net Core 2.0 项目实战(9) 日志记录,基于Nlog或Microsoft.Extensions.Logging的实现及调用实例
本文目录 1. Net下日志记录 2. NLog的使用 2.1 添加nuget引用NLog.Web.AspNetCore 2.2 配置文件设置 2.3 依赖配置及调用 ...
- Asp.Net Core 2.0 项目实战(8)Core下缓存操作、序列化操作、JSON操作等Helper集合类
本文目录 1. 前沿 2.CacheHelper基于Microsoft.Extensions.Caching.Memory封装 3.XmlHelper快速操作xml文档 4.Serializatio ...
- Asp.Net Core 2.0 项目实战(7)MD5加密、AES&DES对称加解密
本文目录 1. 摘要 2. MD5加密封装 3. AES的加密.解密 4. DES加密/解密 5. 总结 1. 摘要 C#中常用的一些加密和解密方案,如:md5加密.RSA加密与解密和DES加密等, ...
随机推荐
- 面试官:spring中定义bean的方法有哪些?我一口气说出了12种,把面试官整懵了。
前言 在庞大的java体系中,spring有着举足轻重的地位,它给每位开发者带来了极大的便利和惊喜.我们都知道spring是创建和管理bean的工厂,它提供了多种定义bean的方式,能够满足我们日常工 ...
- 阿里P7大佬带你解密Sentinel
概述 在接连写了两篇关于限流的文章(<面试补习>- 你来说说什么是限流?, 限流神器Sentinel,不了解一下吗?)后,总感觉还差最后一点内容来闭环整个限流相关的内容,这两天在翻查相关文 ...
- ACM金牌选手算法讲解《线性表》
哈喽,大家好,我是编程熊,双非逆袭选手,字节跳动.旷视科技前员工,ACM亚洲区域赛金牌,保研985研究生,分享算法与数据结构.计算机学习经验,帮助大家进大厂~ 公众号:『编程熊』 文章首发于: ACM ...
- Motion Planning 是什么
前言与引用 这一个呢,主要是自己突然看一篇论文的时候不知道 为什么他提出的方法对于规划来说就是好的,规划又应该分为哪几个部分,解决的是哪几个部分的问题?带着这个问题,我就去搜:Motion Plann ...
- Spring总结之AOP
一.Spring AOP简介(百度百科) 面向切面编程(也叫面向方面编程):Aspect Oriented Programming(AOP),是软件开发中的一个热点,也是 Spring 框架中的一个重 ...
- pagehelper插件使用时查询不到数据
刚用mybatis 的分页插件时,老项目中分页封装的分页类起始为( pageno-1)* pagesize 于是直直接在pagehelper.start(start,pagesize)来进行分页.结 ...
- 微信小程序云开发-云存储-下载并打开文件文件(word/excel/ppt/pdf)
一.wxml文件 1.写文本框,用来获取文件链接. 2.按钮,点击下载文件 <!-- 下载文件(word/excel/ppt/pdf等) --> <view class=" ...
- 随机数种子(random seed)
在科学技术和机器学习等其他算法相关任务中,我们经常需要用到随机数,为了把握随机数的生成特性,从随机数的随机无序中获得确定和秩序.我们可以利用随机数种子(random seed)来实现这一目标,随机数种 ...
- tomcat禁用PUT,DELETE等一些不必要的HTTP方法
一.背景 公司进行安全整改, 技术要求:系统软件所需支撑的WEB容器环境应禁止除GET和POST外其他HTTP(S)方法. 提供凭证:建议在不影响业务的前提下,禁用PUT.DELETE.HEAD.OP ...
- 基于小熊派Hi3861鸿蒙开发的IoT物联网学习【三】
软件定时器:是基于系统Tick时钟中断且由软件来模拟的定时器,当经过设定的Tick时钟计数值后会触发用户定义的回调函数.定时精度与系统Tick时钟的周期有关. 定时器运行机制: cmsis_os2的A ...