介紹
ASP.NET MVC 5 SmartCode Scaffolding是內建在Visual Studio.Net開發工具中一個ASP.NET MVC Web應用程式代碼生成架構,使用SmartCode Scaffolding可以快速添加一整套View,Controller,Model,Service可以運作的互動式代碼。減少程式員在系統開發過程中編寫重複的代碼行數(估計可以減少80%代碼Coding),同時有助于團隊成員遵循統一的架構和規範進行開發,就算沒有接觸過MVC的程式員也能快速的進行團隊開發。大大減少對基礎功能的debug的時間,提高軟體項目的開發效率。
SmartCode Scaffolding是自定義擴充Visual Studio.Net ASP.NET Scaffolding并且實作了更多功能和生成更多的标準代碼。非常适合快速原型法的開發過程。
該項目從2014年一直默默的在做版本更新和持續完善,從最早Visual Sutdio.Net 2013到最新2017。并且完全開源 GITHUB SmartCode Scaffolding
我的聯系方式 QQ:28440117,email:[email protected],微信:neostwitter
我的首頁:https://neozhu.github.io/WebSite/index.html
安裝&使用
需要要配合Demo中WebApp 項目來生成代碼,因為其中引用了大量的css和html模闆
代碼生成的過程
定義實體對象(Entity class)和屬性
參考EntityFramewrok Code-First規範定義,定義的越規範,資訊越多對後面的生成的代碼就越完善。
下面代碼定義一個Order,OrderDetail,一對多的關系,在建立Order類的Controller時會在controller,View,會根據關聯的實體生成相應的代碼,
比如EditView,會同時生成對表頭Order form表單的操作和明細表OrderDetail的datagrid操作。
定義OrderDetail中引用了Product,多對一的關系。會在View部分生成Combox控件或DropdownList的控件和Controller層的查詢方法。
//定義字段描述資訊,字段長度,基本驗證規則
public partial class Order:Entity
{
public Order() {
OrderDetails = new HashSet<OrderDetail>();
}
[Key]
public int Id { get; set; }
[Required]
[Display(Name ="客戶名稱",Description ="訂單所屬的客戶",Order =1)]
[MaxLength(30)]
public string Customer { get; set; }
[Required]
[Display(Name = "發貨位址", Description = "發貨位址", Order = 2)]
[MaxLength(200)]
public string ShippingAddress { get; set; }
[Display(Name = "訂單日期", Description = "訂單日期預設當天", Order = 3)]
public DateTime OrderDate { get; set; }
//關聯訂單明細 1-*
public virtual ICollection<OrderDetail> OrderDetails { get; set; }
}
public partial class OrderDetail:Entity
{
[Key]
public int Id { get; set; }
[Required(ErrorMessage = "必選")]
[Display(Name ="商品", Description ="商品",Order =2)]
public int ProductId { get; set; }
[ForeignKey("ProductId")]
[Display(Name = "商品", Description = "商品", Order = 3)]
public Product Product { get; set; }
[Required(ErrorMessage="必填")]
[Range(1,9999)]
[Display(Name = "數量", Description = "需求數量", Order = 4)]
public int Qty { get; set; }
[Required(ErrorMessage = "必填")]
[Range(1, 9999)]
[Display(Name = "單價", Description = "單價", Order = 5)]
public decimal Price { get; set; }
[Required(ErrorMessage = "必填")]
[Range(1, 9999)]
[Display(Name = "金額", Description = "金額(數量x單價)", Order = 6)]
public decimal Amount { get; set; }
[Display(Name = "訂單号", Description = "訂單号", Order = 1)]
public int OrderId { get; set; }
//關聯訂單表頭
[ForeignKey("OrderId")]
[Display(Name = "訂單号", Description = "訂單号", Order = 1)]
public Order Order { get; set; }
}
生成代碼
- 添加controller
- 生成以下代碼
Controllers\OrdersController.cs /* MVC控制類 */
Repositories\Orders\OrderQuery.cs /* 定義與業務邏輯相關查詢比如分頁帥選,外鍵/主鍵查詢 */
Repositories\Orders\OrderRepository.cs /* Repository模式 */
Services\Orders\IOrderService.cs /* 具體的業務邏輯接口 */
Services\Orders\OrderService.cs /* 具體的業務邏輯實作 */
Views\Orders\Index.cshtml /* 訂單資訊DataGrid包括查詢/新增/删除/修改/導入/導出等功能 */
Views\Orders\_PopupDetailFormView.cshtml /* 訂單資訊彈出編輯框 */
Views\Orders\Create.cshtml /* 訂單資訊新增操作頁面 */
Views\Orders\Edit.cshtml /* 訂單資訊編輯操作頁面 */
Views\Orders\EditForm.cshtml /* 訂單資訊編輯表單 */
index.html javascript代碼片段
var entityname = "Order";
//下載下傳Excel導入模闆
function downloadtemplate() {
//TODO: 修改下載下傳模闆的路徑
var url = "/ExcelTemplate/Order.xlsx";
$.fileDownload(url)
.fail(function() {
$.messager.alert("錯誤", "沒有找到模闆檔案! {" + url + "}");
});
}
//打開Excel上傳導入
function importexcel() {
$("#importwindow").window("open");
}
//執行Excel到處下載下傳
function exportexcel() {
var filterRules = JSON.stringify($dg.datagrid("options").filterRules);
//console.log(filterRules);
$.messager.progress({
title: "正在執行導出!"
});
var formData = new FormData();
formData.append("filterRules", filterRules);
formData.append("sort", "Id");
formData.append("order", "asc");
$.postDownload("/Orders/ExportExcel", formData, function(fileName) {
$.messager.progress("close");
console.log(fileName);
})
}
//顯示幫助資訊
function dohelp() {
}
//easyui datagrid 增删改查操作
var $dg = $("#orders_datagrid").datagrid({
rownumbers: true,
checkOnSelect: true,
selectOnCheck: true,
idField: 'Id',
sortName: 'Id',
sortOrder: 'desc',
remoteFilter: true,
singleSelect: true,
toolbar: '#orders_toolbar',
url: '/Orders/GetData',
method: 'get',
onClickCell: onClickCell,
pagination: true,
striped: true,
columns: [
[
/*{ field: 'ck', checkbox: true },*/
{
field: '_operate1',
title: '操作',
width: 120,
sortable: false,
resizable: true,
formatter: showdetailsformatter
},
/*{field:'Id',width:80 ,sortable:true,resizable:true }*/
{
field: 'Customer',
title: '@Html.DisplayNameFor(model => model.Customer)',
width: 140,
editor: {
type: 'textbox',
options: {
prompt: '客戶名稱',
required: true,
validType: 'length[0,30]'
}
},
sortable: true,
resizable: true
},
{
field: 'ShippingAddress',
title: '@Html.DisplayNameFor(model => model.ShippingAddress)',
width: 140,
editor: {
type: 'textbox',
options: {
prompt: '發貨位址',
required: true,
validType: 'length[0,200]'
}
},
sortable: true,
resizable: true
},
{
field: 'OrderDate',
title: '@Html.DisplayNameFor(model => model.OrderDate)',
width: 160,
align: 'right',
editor: {
type: 'datebox',
options: {
prompt: '訂單日期',
required: true
}
},
sortable: true,
resizable: true,
formatter: dateformatter
},
]
]
});
var editIndex = undefined;
function reload() {
if (endEditing()) {
$dg.datagrid("reload");
}
}
function endEditing() {
if (editIndex == undefined) {
return true
}
if ($dg.datagrid("validateRow", editIndex)) {
$dg.datagrid("endEdit", editIndex);
editIndex = undefined;
return true;
} else {
return false;
}
}
function onClickCell(index, field) {
var _operates = ["_operate1", "_operate2", "_operate3", "ck"]
if ($.inArray(field, _operates) >= 0) {
return;
}
if (editIndex != index) {
if (endEditing()) {
$dg.datagrid("selectRow", index)
.datagrid("beginEdit", index);
editIndex = index;
var ed = $dg.datagrid("getEditor", {
index: index,
field: field
});
if (ed) {
($(ed.target).data("textbox") ? $(ed.target).textbox("textbox") : $(ed.target)).focus();
}
} else {
$dg.datagrid("selectRow", editIndex);
}
}
}
function append() {
if (endEditing()) {
//$dg.datagrid("appendRow", { Status: 0 });
//editIndex = $dg.datagrid("getRows").length - 1;
$dg.datagrid("insertRow", {
index: 0,
row: {}
});
editIndex = 0;
$dg.datagrid("selectRow", editIndex)
.datagrid("beginEdit", editIndex);
}
}
function removeit() {
if (editIndex == undefined) {
return
}
$dg.datagrid("cancelEdit", editIndex)
.datagrid("deleteRow", editIndex);
editIndex = undefined;
}
function accept() {
if (endEditing()) {
if ($dg.datagrid("getChanges").length) {
var inserted = $dg.datagrid("getChanges", "inserted");
var deleted = $dg.datagrid("getChanges", "deleted");
var updated = $dg.datagrid("getChanges", "updated");
var effectRow = new Object();
if (inserted.length) {
effectRow.inserted = inserted;
}
if (deleted.length) {
effectRow.deleted = deleted;
}
if (updated.length) {
effectRow.updated = updated;
}
//console.log(JSON.stringify(effectRow));
$.post("/Orders/SaveData", effectRow, function(response) {
//console.log(response);
if (response.Success) {
$.messager.alert("提示", "送出成功!");
$dg.datagrid("acceptChanges");
$dg.datagrid("reload");
}
}, "json").fail(function(response) {
//console.log(response);
$.messager.alert("錯誤", "送出錯誤了!", "error");
//$dg.datagrid("reload");
});
}
//$dg.datagrid("acceptChanges");
}
}
function reject() {
$dg.datagrid("rejectChanges");
editIndex = undefined;
}
function getChanges() {
var rows = $dg.datagrid("getChanges");
alert(rows.length + " rows are changed!");
}
//datagrid 開啟篩選功能
$(function() {
$dg.datagrid("enableFilter", [
{
field: "Id",
type: "numberbox",
op: ['equal', 'notequal', 'less', 'lessorequal', 'greater', 'greaterorequal']
},
{
field: "OrderDate",
type: "dateRange",
options: {
onChange: function(value) {
$dg.datagrid("addFilterRule", {
field: "OrderDate",
op: "between",
value: value
});
$dg.datagrid("doFilter");
}
}
},
]);
})
//-----------------------------------------------------
//datagrid onSelect
//-----------------------------------------------------
function showdetailsformatter(value, row, index) {
return '<a onclick="showDetailsWindow(' + row.Id + ')" class="easyui-linkbutton" href="javascript:void(0)">檢視明細</a>';
}
//彈出明細資訊
function showDetailsWindow(id) {
//console.log(index, row);
$.getJSON('/Orders/PopupEdit/' + id, function(data, status, xhr) {
//console.log(data);
$('#detailswindow').window('open');
loadData(id, data);
});
}
OrderController.cs 代碼片段
public class OrdersController : Controller
{
//private StoreContext db = new StoreContext();
private readonly IOrderService _orderService;
private readonly IUnitOfWorkAsync _unitOfWork;
public OrdersController(IOrderService orderService, IUnitOfWorkAsync unitOfWork)
{
_orderService = orderService;
_unitOfWork = unitOfWork;
}
// GET: Orders/Index
[OutputCache(Duration = 360, VaryByParam = "none")]
public ActionResult Index()
{
return View();
}
// Get :Orders/PageList
// For Index View Boostrap-Table load data
[HttpGet]
public async Task<ActionResult> GetData(int page = 1, int rows = 10, string sort = "Id", string order = "asc", string filterRules = "")
{
var filters = JsonConvert.DeserializeObject<IEnumerable<filterRule>>(filterRules);
var totalCount = 0;
//int pagenum = offset / limit +1;
var orders = await _orderService
.Query(new OrderQuery().Withfilter(filters))
.OrderBy(n => n.OrderBy(sort, order))
.SelectPageAsync(page, rows, out totalCount);
var datarows = orders.Select(n => new { Id = n.Id, Customer = n.Customer, ShippingAddress = n.ShippingAddress, OrderDate = n.OrderDate }).ToList();
var pagelist = new { total = totalCount, rows = datarows };
return Json(pagelist, JsonRequestBehavior.AllowGet);
}
[HttpPost]
public async Task<ActionResult> SaveData(OrderChangeViewModel orders)
{
if (orders.updated != null)
{
foreach (var item in orders.updated)
{
_orderService.Update(item);
}
}
if (orders.deleted != null)
{
foreach (var item in orders.deleted)
{
_orderService.Delete(item);
}
}
if (orders.inserted != null)
{
foreach (var item in orders.inserted)
{
_orderService.Insert(item);
}
}
await _unitOfWork.SaveChangesAsync();
return Json(new { Success = true }, JsonRequestBehavior.AllowGet);
}
//[OutputCache(Duration = 360, VaryByParam = "none")]
public async Task<ActionResult> GetOrders(string q = "")
{
var orderRepository = _unitOfWork.RepositoryAsync<Order>();
var data = await orderRepository.Queryable().Where(n => n.Customer.Contains(q)).ToListAsync();
var rows = data.Select(n => new { Id = n.Id, Customer = n.Customer });
return Json(rows, JsonRequestBehavior.AllowGet);
}
//[OutputCache(Duration = 360, VaryByParam = "none")]
public async Task<ActionResult> GetProducts(string q = "")
{
var productRepository = _unitOfWork.RepositoryAsync<Product>();
var data = await productRepository.Queryable().Where(n => n.Name.Contains(q)).ToListAsync();
var rows = data.Select(n => new { Id = n.Id, Name = n.Name });
return Json(rows, JsonRequestBehavior.AllowGet);
}
// GET: Orders/Details/5
public async Task<ActionResult> Details(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
var order = await _orderService.FindAsync(id);
if (order == null)
{
return HttpNotFound();
}
return View(order);
}
// GET: Orders/Create
public ActionResult Create()
{
var order = new Order();
//set default value
return View(order);
}
// POST: Orders/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Create([Bind(Include = "OrderDetails,Id,Customer,ShippingAddress,OrderDate,CreatedDate,CreatedBy,LastModifiedDate,LastModifiedBy")] Order order)
{
if (ModelState.IsValid)
{
order.ObjectState = ObjectState.Added;
foreach (var item in order.OrderDetails)
{
item.OrderId = order.Id;
item.ObjectState = ObjectState.Added;
}
_orderService.InsertOrUpdateGraph(order);
await _unitOfWork.SaveChangesAsync();
if (Request.IsAjaxRequest())
{
return Json(new { success = true }, JsonRequestBehavior.AllowGet);
}
DisplaySuccessMessage("Has append a Order record");
return RedirectToAction("Index");
}
else
{
var modelStateErrors = String.Join("", this.ModelState.Keys.SelectMany(key => this.ModelState[key].Errors.Select(n => n.ErrorMessage)));
if (Request.IsAjaxRequest())
{
return Json(new { success = false, err = modelStateErrors }, JsonRequestBehavior.AllowGet);
}
DisplayErrorMessage(modelStateErrors);
}
return View(order);
}
// GET: Orders/PopupEdit/5
[OutputCache(Duration = 360, VaryByParam = "id")]
public async Task<ActionResult> PopupEdit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
var order = await _orderService.FindAsync(id);
return Json(order, JsonRequestBehavior.AllowGet);
}
// GET: Orders/Edit/5
public async Task<ActionResult> Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
var order = await _orderService.FindAsync(id);
if (order == null)
{
return HttpNotFound();
}
return View(order);
}
// POST: Orders/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Edit([Bind(Include = "OrderDetails,Id,Customer,ShippingAddress,OrderDate,CreatedDate,CreatedBy,LastModifiedDate,LastModifiedBy")] Order order)
{
if (ModelState.IsValid)
{
order.ObjectState = ObjectState.Modified;
foreach (var item in order.OrderDetails)
{
item.OrderId = order.Id;
//set ObjectState with conditions
if (item.Id <= 0)
item.ObjectState = ObjectState.Added;
else
item.ObjectState = ObjectState.Modified;
}
_orderService.InsertOrUpdateGraph(order);
await _unitOfWork.SaveChangesAsync();
if (Request.IsAjaxRequest())
{
return Json(new { success = true }, JsonRequestBehavior.AllowGet);
}
DisplaySuccessMessage("Has update a Order record");
return RedirectToAction("Index");
}
else
{
var modelStateErrors = String.Join("", this.ModelState.Keys.SelectMany(key => this.ModelState[key].Errors.Select(n => n.ErrorMessage)));
if (Request.IsAjaxRequest())
{
return Json(new { success = false, err = modelStateErrors }, JsonRequestBehavior.AllowGet);
}
DisplayErrorMessage(modelStateErrors);
}
return View(order);
}
// GET: Orders/Delete/5
public async Task<ActionResult> Delete(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
var order = await _orderService.FindAsync(id);
if (order == null)
{
return HttpNotFound();
}
return View(order);
}
// POST: Orders/Delete/5
[HttpPost, ActionName("Delete")]
//[ValidateAntiForgeryToken]
public async Task<ActionResult> DeleteConfirmed(int id)
{
var order = await _orderService.FindAsync(id);
_orderService.Delete(order);
await _unitOfWork.SaveChangesAsync();
if (Request.IsAjaxRequest())
{
return Json(new { success = true }, JsonRequestBehavior.AllowGet);
}
DisplaySuccessMessage("Has delete a Order record");
return RedirectToAction("Index");
}
// Get Detail Row By Id For Edit
// Get : Orders/EditOrderDetail/:id
[HttpGet]
public async Task<ActionResult> EditOrderDetail(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
var orderdetailRepository = _unitOfWork.RepositoryAsync<OrderDetail>();
var orderdetail = await orderdetailRepository.FindAsync(id);
var orderRepository = _unitOfWork.RepositoryAsync<Order>();
var productRepository = _unitOfWork.RepositoryAsync<Product>();
if (orderdetail == null)
{
ViewBag.OrderId = new SelectList(await orderRepository.Queryable().ToListAsync(), "Id", "Customer");
ViewBag.ProductId = new SelectList(await productRepository.Queryable().ToListAsync(), "Id", "Name");
//return HttpNotFound();
return PartialView("_OrderDetailEditForm", new OrderDetail());
}
else
{
ViewBag.OrderId = new SelectList(await orderRepository.Queryable().ToListAsync(), "Id", "Customer", orderdetail.OrderId);
ViewBag.ProductId = new SelectList(await productRepository.Queryable().ToListAsync(), "Id", "Name", orderdetail.ProductId);
}
return PartialView("_OrderDetailEditForm", orderdetail);
}
// Get Create Row By Id For Edit
// Get : Orders/CreateOrderDetail
[HttpGet]
public async Task<ActionResult> CreateOrderDetail()
{
var orderRepository = _unitOfWork.RepositoryAsync<Order>();
ViewBag.OrderId = new SelectList(await orderRepository.Queryable().ToListAsync(), "Id", "Customer");
var productRepository = _unitOfWork.RepositoryAsync<Product>();
ViewBag.ProductId = new SelectList(await productRepository.Queryable().ToListAsync(), "Id", "Name");
return PartialView("_OrderDetailEditForm");
}
// Post Delete Detail Row By Id
// Get : Orders/DeleteOrderDetail/:id
[HttpPost, ActionName("DeleteOrderDetail")]
public async Task<ActionResult> DeleteOrderDetailConfirmed(int id)
{
var orderdetailRepository = _unitOfWork.RepositoryAsync<OrderDetail>();
orderdetailRepository.Delete(id);
await _unitOfWork.SaveChangesAsync();
if (Request.IsAjaxRequest())
{
return Json(new { success = true }, JsonRequestBehavior.AllowGet);
}
DisplaySuccessMessage("Has delete a Order record");
return RedirectToAction("Index");
}
// Get : Orders/GetOrderDetailsByOrderId/:id
[HttpGet]
public async Task<ActionResult> GetOrderDetailsByOrderId(int id)
{
var orderdetails = _orderService.GetOrderDetailsByOrderId(id);
if (Request.IsAjaxRequest())
{
var data = await orderdetails.AsQueryable().ToListAsync();
var rows = data.Select(n => new { OrderCustomer = (n.Order == null ? "" : n.Order.Customer), ProductName = (n.Product == null ? "" : n.Product.Name), Id = n.Id, ProductId = n.ProductId, Qty = n.Qty, Price = n.Price, Amount = n.Amount, OrderId = n.OrderId });
return Json(rows, JsonRequestBehavior.AllowGet);
}
return View(orderdetails);
}
//導出Excel
[HttpPost]
public ActionResult ExportExcel(string filterRules = "", string sort = "Id", string order = "asc")
{
var fileName = "orders_" + DateTime.Now.ToString("yyyyMMddHHmmss") + ".xlsx";
var stream = _orderService.ExportExcel(filterRules, sort, order);
return File(stream, "application/vnd.ms-excel", fileName);
}
private void DisplaySuccessMessage(string msgText)
{
TempData["SuccessMessage"] = msgText;
}
private void DisplayErrorMessage(string msgText)
{
TempData["ErrorMessage"] = msgText;
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
_unitOfWork.Dispose();
}
base.Dispose(disposing);
}
}
注冊UnityConfig.cs
/// <summary>Registers the type mappings with the Unity container.</summary>
/// <param name="container">The unity container to configure.</param>
/// <remarks>There is no need to register concrete types such as controllers or API controllers (unless you want to
/// change the defaults), as Unity allows resolving a concrete type even if it was not previously registered.</remarks>
public static void RegisterTypes(IUnityContainer container)
{
container.RegisterType<IRepositoryAsync<Order>, Repository<Order>>();
container.RegisterType<IOrderService, OrderService>();
container.RegisterType<IRepositoryAsync<OrderDetail>, Repository<OrderDetail>>();
container.RegisterType<IOrderDetailService, OrderDetailService>();
}
運作生成的代碼功能
Animation2-1.gif
以上功能一鍵生成帶帶主從表關聯的多表同時操作的頁面功能和背景代碼,包括必填,長度等輸入校驗規則
基礎功能
即時聊天功能
系統賬号管理
菜單導航授權
Excel導入導出配置
枚舉值維護
系統日志
消息通知
Animation8.gif
整個項目的系統架構和功能
主要元件
- ”Microsoft.AspNet.Mvc” version="5.2.4"
- “Microsoft.AspNet.Razor“ version="3.2.4"
- "EasyUI" version="1.4.5"
- "Hangfire" version="1.6.17"
- "Unity.Mvc" version="5.0.13"
- "Z.EntityFramework.Plus.EF6" version="1.7.15"
- SmartAdmin - Responsive WebApp v1.9.1
-
"EntityFramework" version="6.2.0" 支援Oracle,MySql,Sql Server,PostgreSQL,SQLite,Sybase等
image.png
實戰項目
x-TMS
Animation4.gif
供應鍊協同平台
Animation5.gif
MES系統
Animation6.gif
我們還能做
承接企業内部業務系統開發,組建企業私有雲,虛拟化叢集伺服器部署。
承接BizTalk B2B/EAI/EDI/AS/RosettaNet 開發工作
聯系方式
捐助
如果這個項目對您有用,我們歡迎各方任何形式的捐助,也包括參與到項目代碼更新或意見回報中來。謝謝!
資金捐助:
License
我的部落格即将搬運同步至騰訊雲+社群,邀請大家一同入駐:https://cloud.tencent.com/developer/support-plan