展现层实现增删改查

原文作者:圣杰

原文地址:ABP入门系列(5)——展现层实现增删改查

在原文作者上进行改正,适配ABP新版本。内容相同(本章未做任何更改)

这一章节将通过完善Controller、View、ViewModel,来实现展现层的增删改查。最终实现效果如下图:

一、定义Controller

ABP对ASP.NET MVC Controllers进行了集成,通过引入Abp.Web.Mvc命名空间,创建Controller继承自AbpController, 我们即可使用ABP附加给我们的以下强大功能:

  • 本地化
  • 异常处理
  • 对返回的JsonResult进行包装
  • 审计日志
  • 权限认证([AbpMvcAuthorize]特性)
  • 工作单元(默认未开启,通过添加[UnitOfWork]开启)

1,创建TasksController继承自AbpController

通过构造函数注入对应用服务的依赖。

 1     [AbpMvcAuthorize]
 2     public class TasksController : AbpController
 3     {
 4         private readonly ITaskAppService _taskAppService;
 5         private readonly IUserAppService _userAppService;
 6 
 7         public TasksController(ITaskAppService taskAppService, IUserAppService userAppService)
 8         {
 9             _taskAppService = taskAppService;
10             _userAppService = userAppService;
11         }
12     }

二、创建列表展示分部视图(_List.cshtml)

在分部视图中,我们通过循环遍历,输出任务清单。

 1 @model IEnumerable<LearningMpaAbp.Tasks.Dtos.TaskDto>
 2 <div>
 3     <ul class="list-group">
 4         @foreach (var task in Model)
 5         {
 6             <li class="list-group-item">
 7                 <div class="btn-group pull-right">
 8                     <button type="button" class="btn btn-info" onclick="editTask(@task.Id);">Edit</button>
 9                     <button type="button" class="btn btn-success" onclick="deleteTask(@task.Id);">Delete</button>
10                 </div>
11 
12                 <div class="media">
13                     <a class="media-left" href="#">
14                         <i class="fa @task.GetTaskLable() fa-3x"></i>
15                     </a>
16                     <div class="media-body">
17                         <h4 class="media-heading">@task.Title</h4>
18                         <p class="text-info">@task.AssignedPersonName</p>
19                         <span class="text-muted">@task.CreationTime.ToString("yyyy-MM-dd HH:mm:ss")</span>
20                     </div>
21                 </div>
22 
23             </li>
24         }
25     </ul>
26 </div>

三,创建新增分部视图(_CreateTask.cshtml)

为了好的用户体验,我们采用异步加载的方式来实现任务的创建。

1,引入js文件

使用异步提交需要引入jquery.validate.unobtrusive.min.jsjquery.unobtrusive-ajax.min.js,其中jquery.unobtrusive-ajax.min.js,需要通过Nuget安装微软的Microsoft.jQuery.Unobtrusive.Ajax包获取。
然后通过捆绑一同引入到视图中。打开App_Start文件夹下的BundleConfig.cs,添加以下代码:

1  bundles.Add(
2      new ScriptBundle("~/Bundles/unobtrusive/js")
3          .Include(
4              "~/Scripts/jquery.validate.unobtrusive.min.js",
5              "~/Scripts/jquery.unobtrusive-ajax.min.js"
6              )
7      );

找到Views/Shared/_Layout.cshtml,添加对捆绑的js引用。

1 @Scripts.Render("~/Bundles/vendor/js/bottom")
2 @Scripts.Render("~/Bundles/js")
3 //在此处添加下面一行代码
4 @Scripts.Render("~/Bundles/unobtrusive/js")

2,创建分部视图

其中用到了Bootstrap-Modal,Ajax.BeginForm,对此不了解的可以参考
Ajax.BeginForm()知多少
Bootstrap-Modal的用法介绍

该Partial View绑定CreateTaskInput模型。最终_CreateTask.cshtml代码如下:

 1 @model LearningMpaAbp.Tasks.Dtos.CreateTaskInput
 2 
 3 @{
 4     ViewBag.Title = "Create";
 5 }
 6 <div class="modal fade" id="add" tabindex="-1" role="dialog" aria-labelledby="createTask" data-backdrop="static">
 7     <div class="modal-dialog" role="document">
 8         <div class="modal-content">
 9             <div class="modal-header">
10                 <button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span><span class="sr-only">Close</span></button>
11                 <h4 class="modal-title" id="myModalLabel">Create Task</h4>
12             </div>
13             <div class="modal-body" id="modalContent">
14 
15                 @using (Ajax.BeginForm("Create", "Tasks", new AjaxOptions()
16                 {
17                     UpdateTargetId = "taskList",
18                     InsertionMode = InsertionMode.Replace,
19                     OnBegin = "beginPost('#add')",
20                     OnSuccess = "hideForm('#add')",
21                     OnFailure = "errorPost(xhr, status, error,'#add')"
22                 }))
23                 {
24                     @Html.AntiForgeryToken()
25                     <div class="form-horizontal">
26                         <h4>Task</h4>
27                         <hr />
28                         @Html.ValidationSummary(true, "", new { @class = "text-danger" })
29                         <div class="form-group">
30                             @Html.LabelFor(model => model.AssignedPersonId, "AssignedPersonId", htmlAttributes: new { @class = "control-label col-md-2" })
31                             <div class="col-md-10">
32                                 @Html.DropDownList("AssignedPersonId", null, htmlAttributes: new { @class = "form-control" })
33                                 @Html.ValidationMessageFor(model => model.AssignedPersonId, "", new { @class = "text-danger" })
34                             </div>
35                         </div>
36 
37                         <div class="form-group">
38                             @Html.LabelFor(model => model.Title, htmlAttributes: new { @class = "control-label col-md-2" })
39                             <div class="col-md-10">
40                                 @Html.EditorFor(model => model.Title, new { htmlAttributes = new { @class = "form-control" } })
41                                 @Html.ValidationMessageFor(model => model.Title, "", new { @class = "text-danger" })
42                             </div>
43                         </div>
44 
45                         <div class="form-group">
46                             @Html.LabelFor(model => model.Description, htmlAttributes: new { @class = "control-label col-md-2" })
47                             <div class="col-md-10">
48                                 @Html.EditorFor(model => model.Description, new { htmlAttributes = new { @class = "form-control" } })
49                                 @Html.ValidationMessageFor(model => model.Description, "", new { @class = "text-danger" })
50                             </div>
51                         </div>
52 
53                         <div class="form-group">
54                             @Html.LabelFor(model => model.State, htmlAttributes: new { @class = "control-label col-md-2" })
55                             <div class="col-md-10">
56                                 @Html.EnumDropDownListFor(model => model.State, htmlAttributes: new { @class = "form-control" })
57                                 @Html.ValidationMessageFor(model => model.State, "", new { @class = "text-danger" })
58                             </div>
59                         </div>
60 
61                         <div class="form-group">
62                             <div class="col-md-offset-2 col-md-10">
63                                 <button type="submit" class="btn btn-default">Create</button>
64                             </div>
65                         </div>
66                     </div>
67                 }
68             </div>
69         </div>
70     </div>
71 </div>

对应Controller代码:

 1 [ChildActionOnly]
 2 public PartialViewResult Create()
 3 {
 4     var userList = _userAppService.GetUsers();
 5     ViewBag.AssignedPersonId = new SelectList(userList.Items, "Id", "Name");
 6     return PartialView("_CreateTask");
 7 }
 8 
 9 [HttpPost]
10 [ValidateAntiForgeryToken]
11 public ActionResult Create(CreateTaskInput task)
12 {
13     var id = _taskAppService.CreateTask(task);
14 
15     var input = new GetTasksInput();
16     var output = _taskAppService.GetTasks(input);
17 
18     return PartialView("_List", output.Tasks);
19 }

四、创建更新分部视图(_EditTask.cshtml)

同样,该视图也采用异步更新方式,也采用Bootstrap-Modal,Ajax.BeginForm()技术。该Partial View绑定UpdateTaskInput模型。

 1 @model LearningMpaAbp.Tasks.Dtos.UpdateTaskInput
 2 @{
 3     ViewBag.Title = "Edit";
 4 }
 5 
 6 <div class="modal fade" id="editTask" tabindex="-1" role="dialog" aria-labelledby="editTask" data-backdrop="static">
 7 
 8     <div class="modal-dialog" role="document">
 9         <div class="modal-content">
10             <div class="modal-header">
11                 <button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span><span class="sr-only">Close</span></button>
12                 <h4 class="modal-title" id="myModalLabel">Edit Task</h4>
13             </div>
14             <div class="modal-body" id="modalContent">
15 
16                 @using (Ajax.BeginForm("Edit", "Tasks", new AjaxOptions()
17                 {
18                     UpdateTargetId = "taskList",
19                     InsertionMode = InsertionMode.Replace,
20                     OnBegin = "beginPost('#editTask')",
21                     OnSuccess = "hideForm('#editTask')"
22                 }))
23                 {
24                     @Html.AntiForgeryToken()
25 
26                     <div class="form-horizontal">
27                         <h4>Task</h4>
28                         <hr />
29                         @Html.ValidationSummary(true, "", new { @class = "text-danger" })
30                         @Html.HiddenFor(model => model.Id)
31 
32                         <div class="form-group">
33                                 @Html.LabelFor(model => model.AssignedPersonId, "AssignedPersonId", htmlAttributes: new { @class = "control-label col-md-2" })
34                                 <div class="col-md-10">
35                                     @Html.DropDownList("AssignedPersonId", null, htmlAttributes: new { @class = "form-control" })
36                                     @Html.ValidationMessageFor(model => model.AssignedPersonId, "", new { @class = "text-danger" })
37                                 </div>
38                             </div>
39 
40                         <div class="form-group">
41                             @Html.LabelFor(model => model.Title, htmlAttributes: new { @class = "control-label col-md-2" })
42                             <div class="col-md-10">
43                                 @Html.EditorFor(model => model.Title, new { htmlAttributes = new { @class = "form-control" } })
44                                 @Html.ValidationMessageFor(model => model.Title, "", new { @class = "text-danger" })
45                             </div>
46                         </div>
47 
48                         <div class="form-group">
49                             @Html.LabelFor(model => model.Description, htmlAttributes: new { @class = "control-label col-md-2" })
50                             <div class="col-md-10">
51                                 @Html.EditorFor(model => model.Description, new { htmlAttributes = new { @class = "form-control" } })
52                                 @Html.ValidationMessageFor(model => model.Description, "", new { @class = "text-danger" })
53                             </div>
54                         </div>
55 
56                         <div class="form-group">
57                             @Html.LabelFor(model => model.State, htmlAttributes: new { @class = "control-label col-md-2" })
58                             <div class="col-md-10">
59                                 @Html.EnumDropDownListFor(model => model.State, htmlAttributes: new { @class = "form-control" })
60                                 @Html.ValidationMessageFor(model => model.State, "", new { @class = "text-danger" })
61                             </div>
62                         </div>
63 
64                         <div class="form-group">
65                             <div class="col-md-offset-2 col-md-10">
66                                 <input type="submit" value="Save" class="btn btn-default" />
67                             </div>
68                         </div>
69                     </div>
70                 }
71 
72             </div>
73         </div>
74     </div>
75 </div>
76 <script type="text/javascript">
77     //该段代码十分重要,确保异步调用后jquery能正确执行验证逻辑
78     $(function () {
79         //allow validation framework to parse DOM
80         $.validator.unobtrusive.parse('form');
81     });
82 </script>

后台代码:

 1 public PartialViewResult Edit(int id)
 2 {
 3     var task = _taskAppService.GetTaskById(id);
 4 
 5     var updateTaskDto = AutoMapper.Mapper.Map<UpdateTaskInput>(task);
 6 
 7     var userList = _userAppService.GetUsers();
 8     ViewBag.AssignedPersonId = new SelectList(userList.Items, "Id", "Name", updateTaskDto.AssignedPersonId);
 9 
10     return PartialView("_EditTask", updateTaskDto);
11 }
12 
13 [HttpPost]
14 [ValidateAntiForgeryToken]
15 public ActionResult Edit(UpdateTaskInput updateTaskDto)
16 {
17     _taskAppService.UpdateTask(updateTaskDto);
18 
19     var input = new GetTasksInput();
20     var output = _taskAppService.GetTasks(input);
21 
22     return PartialView("_List", output.Tasks);
23 }

五,创建Index视图

在首页中,我们一般会用来展示列表,并通过弹出模态框的方式来进行新增更新删除。为了使用ASP.NET MVC强视图带给我们的好处(模型绑定、输入校验等等),我们需要创建一个ViewModel来进行模型绑定。因为Abp提倡为每个不同的应用服务提供不同的Dto进行数据交互,新增对应CreateTaskInput,更新对应UpdateTaskInput,展示对应TaskDto。那我们创建的ViewModel就需要包含这几个模型,方可在一个视图中完成多个模型的绑定。

1,创建视图模型(IndexViewModel)

 1 namespace LearningMpaAbp.Web.Models.Tasks
 2 {
 3     public class IndexViewModel
 4     {
 5         /// <summary>
 6         /// 用来进行绑定列表过滤状态
 7         /// </summary>
 8         public TaskState? SelectedTaskState { get; set; }
 9 
10         /// <summary>
11         /// 列表展示
12         /// </summary>
13         public IReadOnlyList<TaskDto> Tasks { get; }
14 
15         /// <summary>
16         /// 创建任务模型
17         /// </summary>
18         public CreateTaskInput CreateTaskInput { get; set; }
19 
20         /// <summary>
21         /// 更新任务模型
22         /// </summary>
23         public UpdateTaskInput UpdateTaskInput { get; set; }
24 
25         public IndexViewModel(IReadOnlyList<TaskDto> items)
26         {
27             Tasks = items;
28         }
29         
30         /// <summary>
31         /// 用于过滤下拉框的绑定
32         /// </summary>
33         /// <returns></returns>
34 
35         public List<SelectListItem> GetTaskStateSelectListItems()
36         {
37             var list=new List<SelectListItem>()
38             {
39                 new SelectListItem()
40                 {
41                     Text = "AllTasks",
42                     Value = "",
43                     Selected = SelectedTaskState==null
44                 }
45             };
46 
47             list.AddRange(Enum.GetValues(typeof(TaskState))
48                 .Cast<TaskState>()
49                 .Select(state=>new SelectListItem()
50                 {
51                     Text = $"TaskState_{state}",
52                     Value = state.ToString(),
53                     Selected = state==SelectedTaskState
54                 })
55             );
56             return list;
57         }
58     }
59 }

2,创建视图

Index视图,通过加载Partial View的形式,将列表、新增视图一次性加载进来。

 1 @using Abp.Web.Mvc.Extensions
 2 @model LearningMpaAbp.Web.Models.Tasks.IndexViewModel
 3 
 4 @{
 5     ViewBag.Title = L("TaskList");
 6     ViewBag.ActiveMenu = "TaskList"; //Matches with the menu name in SimpleTaskAppNavigationProvider to highlight the menu item
 7 }
 8 @section scripts{
 9     @Html.IncludeScript("~/Views/Tasks/index.js");
10 }
11 <h2>
12     @L("TaskList")
13 
14     <button type="button" class="btn btn-primary" data-toggle="modal" data-target="#add">Create Task</button>
15 
16     <a class="btn btn-primary" data-toggle="modal" href="@Url.Action("RemoteCreate")" data-target="#modal" role="button">(Create Task)使用Remote方式调用Modal进行展现</a>
17 
18     <!--任务清单按照状态过滤的下拉框-->
19     <span class="pull-right">
20         @Html.DropDownListFor(
21             model => model.SelectedTaskState,
22             Model.GetTaskStateSelectListItems(),
23             new
24             {
25                 @class = "form-control select2",
26                 id = "TaskStateCombobox"
27             })
28     </span>
29 </h2>
30 
31 <!--任务清单展示-->
32 <div class="row" id="taskList">
33     @{ Html.RenderPartial("_List", Model.Tasks); }
34 </div>
35 
36 <!--通过初始加载页面的时候提前将创建任务模态框加载进来-->
37 @Html.Action("Create")
38 
39 <!--编辑任务模态框通过ajax动态填充到此div中-->
40 <div id="edit">
41 
42 </div>
43 
44 <!--Remote方式弹出创建任务模态框-->
45 <div class="modal fade" id="modal" tabindex="-1" role="dialog" aria-labelledby="createTask" data-backdrop="static">
46     <div class="modal-dialog" role="document">
47         <div class="modal-content">
48 
49         </div>
50     </div>
51 </div>

3,Remote方式创建任务讲解

Remote方式就是,点击按钮的时候去加载创建任务的PartialView到指定的div中。而我们代码中另一种方式是通过@Html.Action("Create")的方式,在加载Index的视图的作为子视图同步加载了进来。
感兴趣的同学自行查看源码,不再讲解。

 1 <a class="btn btn-primary" data-toggle="modal" href="@Url.Action("RemoteCreate")" data-target="#modal" role="button">(Create Task)使用Remote方式调用Modal进行展现</a>
 2 
 3 <!--Remote方式弹出创建任务模态框-->
 4 <div class="modal fade" id="modal" tabindex="-1" role="dialog" aria-labelledby="createTask" data-backdrop="static">
 5     <div class="modal-dialog" role="document">
 6         <div class="modal-content">
 7 
 8         </div>
 9     </div>
10 </div>

4,后台代码

 1         public ActionResult Index(GetTasksInput input)
 2         {
 3             var output = _taskAppService.GetTasks(input);
 4 
 5             var model = new IndexViewModel(output.Tasks)
 6             {
 7                 SelectedTaskState = input.State
 8 
 9             };
10             return View(model);
11         }

5,js代码(index.js)

 1 var taskService = abp.services.app.task;
 2 
 3 (function ($) {
 4 
 5     $(function () {
 6 
 7         var $taskStateCombobox = $('#TaskStateCombobox');
 8 
 9         $taskStateCombobox.change(function () {
10             getTaskList();
11         });
12 
13         var $modal = $(".modal");
14         //显示modal时,光标显示在第一个输入框
15         $modal.on('shown.bs.modal',
16             function () {
17                 $modal.find('input:not([type=hidden]):first').focus();
18             });
19 
20     });
21 })(jQuery);
22 
23 //异步开始提交时,显示遮罩层
24 function beginPost(modalId) {
25     var $modal = $(modalId);
26 
27     abp.ui.setBusy($modal);
28 }
29 
30 //异步开始提交结束后,隐藏遮罩层并清空Form
31 function hideForm(modalId) {
32     var $modal = $(modalId);
33 
34     var $form = $modal.find("form");
35     abp.ui.clearBusy($modal);
36     $modal.modal("hide");
37     //创建成功后,要清空form表单
38     $form[0].reset();
39 }
40 
41 //处理异步提交异常
42 function errorPost(xhr, status, error, modalId) {
43     if (error.length>0) {
44         abp.notify.error('Something is going wrong, please retry again later!');
45         var $modal = $(modalId);
46         abp.ui.clearBusy($modal);
47     }
48 }
49 
50 function editTask(id) {
51     abp.ajax({
52         url: "/tasks/edit",
53         data: { "id": id },
54         type: "GET",
55         dataType: "html"
56     })
57         .done(function (data) {
58             $("#edit").html(data);
59             $("#editTask").modal("show");
60         })
61         .fail(function (data) {
62             abp.notify.error('Something is wrong!');
63         });
64 }
65 
66 function deleteTask(id) {
67     abp.message.confirm(
68         "是否删除Id为" + id + "的任务信息",
69         function (isConfirmed) {
70             if (isConfirmed) {
71                 taskService.deleteTask(id)
72                     .done(function () {
73                         abp.notify.info("删除任务成功!");
74                         getTaskList();
75                     });
76             }
77         }
78     );
79 
80 }
81 
82 function getTaskList() {
83     var $taskStateCombobox = $('#TaskStateCombobox');
84     var url = '/Tasks/GetList?state=' + $taskStateCombobox.val();
85     abp.ajax({
86         url: url,
87         type: "GET",
88         dataType: "html"
89     })
90         .done(function (data) {
91             $("#taskList").html(data);
92         });
93 }

js代码中处理了Ajax回调函数,以及任务状态过滤下拉框更新事件,编辑、删除任务代码。其中getTaskList()函数是用来异步刷新列表,对应调用的GetList()Action的后台代码如下:

1 public PartialViewResult GetList(GetTasksInput input)
2 {
3     var output = _taskAppService.GetTasks(input);
4     return PartialView("_List", output.Tasks);
5 }

六、总结

至此,完成了任务的增删改查。展现层主要用到了Asp.net mvc的强类型视图、Bootstrap-Modal、Ajax异步提交技术。
其中需要注意的是,在异步加载表单时,需要添加以下js代码,jquery方能进行前端验证。

1 <script type="text/javascript">
2     $(function () {
3         //allow validation framework to parse DOM
4         $.validator.unobtrusive.parse('form');
5     });
6 </script>

 

 

posted @ 2019-06-18 21:14  SpringCore  阅读(488)  评论(0编辑  收藏  举报