首页 文章详情

ASP.NET Core 轻量级开源项目(遵循CleanArchitecture原则)

dotNET全栈开发 | 10 2022-08-01 05:21 0 0 0
UniSMS (合一短信)


前言


这是一个基于最新的ASP.NET Core 5.0 创建Razor Page应用程序解决方案模板。遵循Clean Architecture的原则,以最求简洁的代码风格和实现快速开发小型的Web业务系统的目标,并且从没停止过更新。

该项目从最早的ASP.NET Web Form,ASP.NET MVC 5 到 ASP.NET Core 3.1再到现在最新的ASP.NET Core 5.0 Razor Page,从简单三层结构到N层结构再到现在流行的CQRS模式,一遍一遍的再重构,在这过程中体会到系统架构的重要性和在优秀的框架下开发系统是一件多么愉快的事情。

做这样的项目纯粹是为了兴趣和能和很多Github上优秀的程序员一起交流和学习。

介绍

1、Github https://github.com/neozhu/RazorPageCleanArchitecture

2、Demo http://razor.i247365.net/

3、For Asp.net Core MVC https://github.com/neozhu/smartadmin.core.urf

Technologies

  • ASP.NET Core 5:https://devblogs.microsoft.com/aspnet/announcing-asp-net-core-in-net-5/
  • Entity Framework Core 5:https://docs.microsoft.com/en-us/ef/core/
  • SmartAdmin - Responsive WebApp:https://wrapbootstrap.com/theme/smartadmin-responsive-webapp-WB0573SK0/
  • Razor Pages:https://docs.microsoft.com/en-us/aspnet/core/razor-pages/?view=aspnetcore-5.0&tabs=visual-studio
  • Jquery EasyUI:https://www.jeasyui.com/
  • MediatR:https://github.com/jbogard/MediatR
  • AutoMapper:https://automapper.org/
  • FluentValidation:https://fluentvalidation.net/
  • NUnit:https://nunit.org/
  • FluentAssertions:https://fluentassertions.com/
  • Moq:https://github.com/moq
  • Respawn:https://github.com/jbogard/Respawn
  • Docker:https://www.docker.com/

特点

  • 遵循Clean Architecture原则

    什么是Clean Architecture:https://www.youtube.com/watch?v=dK4Yb6-LxAk

  • 非常漂亮的用户界面

    SmartAdmin - Responsive WebApp:https://wrapbootstrap.com/theme/smartadmin-responsive-webapp-WB0573SK0/

  • 遵循CQRS模式极简的代码风格

    什么是CQRS:https://zhuanlan.zhihu.com/p/115685384

  • 实现了基本的CRUD功能

  • 实现了基本的认证和授权功能

  • 支持多语言切换

项目结构

  • 项目结构参考

jasontaylordev:https://github.com/jasontaylordev

CleanArchitecture:https://github.com/jasontaylordev/CleanArchitecture

基本功能预览

  • 新增
  • 修改
  • 删除
  • 查询
  • 导入Excel
  • 下载模板
  • 导出Excel

用户管理

  • 新增
  • 修改
  • 删除
  • 查询
  • 导入Excel
  • 下载模板
  • 导出Excel
  • 重置密码
  • 角色管理

角色管理

  • 新增
  • 修改
  • 删除
  • 查询
  • 导入Excel
  • 下载模板
  • 导出Excel
  • 授权管理

如何开始

在Domain project中新增一个Entity,比如Customer客户信息

 public partial class Customer : AuditableEntityIHasDomainEvent
    {
        public int Id { getset; }
        public string Name { getset; }
        public string NameOfEnglish { getset; }
        public string GroupName { getset; }
        public PartnerType PartnerType { getset; }
        public string Region { getset; }
        public string Sales { getset; }
        public string RegionSalesDirector { getset; }
        public string Address { getset; }
        public string AddressOfEnglish { getset; }
        public string Contract { getset; }
        public string Email { getset; }
        public string PhoneNumber { getset; }
        public string Fax { getset; }
        public string Comments { getset; }
        public List<DomainEvent> DomainEvents { getset; } = 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

转自:阿新

链接:cnblogs.com/neozhu/p/15149673.html

good-icon 0
favorite-icon 0
收藏
回复数量: 0
    暂无评论~~
    Ctrl+Enter