• 展现层实现增删改查


    原文作者:圣杰

    原文地址: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>
  • 相关阅读:
    郁闷,母版页为什么会这样?怎么在使用了母版页的情况下使用js?大家帮忙
    .NET中实现无刷新客户端联动下拉菜单 (无刷新)(一)
    ADO.NET(二)
    HasRows的返回值问题
    动态生成DataTable绑定至DataList一例
    关于FastReport4.3的使用心得1
    资源文件的编译
    加密当前数据库的所有存储过程。
    使用拼音首字母序列实现检索功能
    关于错误Access Violation和too many consecutive exceptions,解决方法
  • 原文地址:https://www.cnblogs.com/fanqisoft/p/11047860.html
Copyright © 2020-2023  润新知