這是一個基於最新的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 運輸管理系統 | [email protected]/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