이 장에서는 Controller, View, ViewModel을 개선하여 프리젠테이션 레이어의 추가, 삭제, 수정 및 확인을 구현해 보겠습니다. 최종 구현 효과는 다음과 같습니다.
1. 컨트롤러 정의
ABP는 ASP.NET MVC 컨트롤러를 통합하고 Abp.Web.Mvc를 도입하여 이름을 지정합니다. Space, AbpController에서 상속되는 컨트롤러를 생성하면 ABP가 우리에게 추가하는 다음과 같은 강력한 기능을 사용할 수 있습니다:
현지화
예외 처리
반환된 JsonResult 래핑
감사 로그
권한 인증([AbpMvcAuthorize] 기능)
작업 단위(기본적으로 활성화되어 있지 않으며, [UnitOfWork]를 추가하여 활성화)
1, TasksController를 생성하면 AbpController
에서 상속되고 생성자를 통해 애플리케이션 서비스에 대한 종속성을 주입합니다.
[AbpMvcAuthorize] public class TasksController : AbpController { private readonly ITaskAppService _taskAppService; private readonly IUserAppService _userAppService; public TasksController(ITaskAppService taskAppService, IUserAppService userAppService) { _taskAppService = taskAppService; _userAppService = userAppService; } }
2. 목록 표시 부분 보기(_List.cshtml)를 만듭니다.
부분 보기에서는 루프 순회를 통해 작업 목록을 출력합니다.
@model IEnumerable<LearningMpaAbp.Tasks.Dtos.TaskDto> <div> <ul class="list-group"> @foreach (var task in Model) { <li class="list-group-item"> <div class="btn-group pull-right"> <button type="button" class="btn btn-info" onclick="editTask(@task.Id);">Edit</button> <button type="button" class="btn btn-success" onclick="deleteTask(@task.Id);">Delete</button> </div> <div class="media"> <a class="media-left" href="#"> <i class="fa @task.GetTaskLable() fa-3x"></i> </a> <div class="media-body"> <h4 class="media-heading">@task.Title</h4> <p class="text-info">@task.AssignedPersonName</p> <span class="text-muted">@task.CreationTime.ToString("yyyy-MM-dd HH:mm:ss")</span> </div> </div> </li> } </ul> </div>
3. 새로운 부분 뷰 생성(_CreateTask.cshtml)
좋은 사용자 경험을 위해 비동기 로딩을 사용하여 생성 작업을 구현합니다.
1, js 파일 소개
비동기 제출을 사용하려면 jquery.validate.unobtrusive.min.js 및 jquery.unobtrusive-ajax.min.js가 도입되어야 하며, 그중 jquery.unobtrusive- ajax.min .js를 사용하려면 Nuget을 통해 Microsoft의 Microsoft.jQuery.Unobtrusive.Ajax 패키지를 설치해야 합니다.
그런 다음 번들링을 통해 뷰에 도입됩니다. App_Start 폴더에서 BundleConfig.cs를 열고 다음 코드를 추가합니다.
bundles.Add( new ScriptBundle("~/Bundles/unobtrusive/js") .Include( "~/Scripts/jquery.validate.unobtrusive.min.js", "~/Scripts/jquery.unobtrusive-ajax.min.js" ) );
Views/Shared/_Layout.cshtml을 찾아 번들에 js 참조를 추가합니다.
@Scripts.Render("~/Bundles/vendor/js/bottom") @Scripts.Render("~/Bundles/js")//在此处添加下面一行代码 @Scripts.Render("~/Bundles/unobtrusive/js")
2, Bootstrap-Modal과 Ajax.BeginForm을 사용하는 부분 뷰
를 만듭니다. 이에 대해 모르면
얼마나 알고 있나요? About Ajax.BeginForm()
Bootstrap-Modal 사용법 소개
Partial View는 CreateTaskInput 모델을 바인딩합니다. 최종 _CreateTask.cshtml 코드는 다음과 같습니다.
@model LearningMpaAbp.Tasks.Dtos.CreateTaskInput@{ ViewBag.Title = "Create"; } <div class="modal fade" id="add" tabindex="-1" role="dialog" aria-labelledby="createTask" data-backdrop="static"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span><span class="sr-only">Close</span></button> <h4 class="modal-title" id="myModalLabel">Create Task</h4> </div> <div class="modal-body" id="modalContent"> @using (Ajax.BeginForm("Create", "Tasks", new AjaxOptions() { UpdateTargetId = "taskList", InsertionMode = InsertionMode.Replace, OnBegin = "beginPost('#add')", OnSuccess = "hideForm('#add')", OnFailure = "errorPost(xhr, status, error,'#add')" })) { @Html.AntiForgeryToken() <div class="form-horizontal"> <h4>Task</h4> <hr /> @Html.ValidationSummary(true, "", new { @class = "text-danger" }) <div class="form-group"> @Html.LabelFor(model => model.AssignedPersonId, "AssignedPersonId", htmlAttributes: new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.DropDownList("AssignedPersonId", null, htmlAttributes: new { @class = "form-control" }) @Html.ValidationMessageFor(model => model.AssignedPersonId, "", new { @class = "text-danger" }) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.Title, htmlAttributes: new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.EditorFor(model => model.Title, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.Title, "", new { @class = "text-danger" }) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.Description, htmlAttributes: new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.EditorFor(model => model.Description, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.Description, "", new { @class = "text-danger" }) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.State, htmlAttributes: new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.EnumDropDownListFor(model => model.State, htmlAttributes: new { @class = "form-control" }) @Html.ValidationMessageFor(model => model.State, "", new { @class = "text-danger" }) </div> </div> <div class="form-group"> <div class="col-md-offset-2 col-md-10"> <button type="submit" class="btn btn-default">Create</button> </div> </div> </div> } </div> </div> </div> </div>
해당 컨트롤러 코드:
[ChildActionOnly]public PartialViewResult Create(){ var userList = _userAppService.GetUsers(); ViewBag.AssignedPersonId = new SelectList(userList.Items, "Id", "Name"); return PartialView("_CreateTask"); } [HttpPost] [ValidateAntiForgeryToken]public ActionResult Create(CreateTaskInput task){ var id = _taskAppService.CreateTask(task); var input = new GetTasksInput(); var output = _taskAppService.GetTasks(input); return PartialView("_List", output.Tasks); }
넷째, 부분 뷰(_EditTask.cshtml)를 생성하고 업데이트합니다.
이와 마찬가지로 view는 또한 비동기 업데이트 방법을 사용하며 Bootstrap-Modal 및 Ajax.BeginForm() 기술을 사용합니다. 이 부분 보기는 UpdateTaskInput 모델에 바인딩됩니다.
@model LearningMpaAbp.Tasks.Dtos.UpdateTaskInput@{ ViewBag.Title = "Edit"; } <div class="modal fade" id="editTask" tabindex="-1" role="dialog" aria-labelledby="editTask" data-backdrop="static"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span><span class="sr-only">Close</span></button> <h4 class="modal-title" id="myModalLabel">Edit Task</h4> </div> <div class="modal-body" id="modalContent"> @using (Ajax.BeginForm("Edit", "Tasks", new AjaxOptions() { UpdateTargetId = "taskList", InsertionMode = InsertionMode.Replace, OnBegin = "beginPost('#editTask')", OnSuccess = "hideForm('#editTask')" })) { @Html.AntiForgeryToken() <div class="form-horizontal"> <h4>Task</h4> <hr /> @Html.ValidationSummary(true, "", new { @class = "text-danger" }) @Html.HiddenFor(model => model.Id) <div class="form-group"> @Html.LabelFor(model => model.AssignedPersonId, "AssignedPersonId", htmlAttributes: new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.DropDownList("AssignedPersonId", null, htmlAttributes: new { @class = "form-control" }) @Html.ValidationMessageFor(model => model.AssignedPersonId, "", new { @class = "text-danger" }) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.Title, htmlAttributes: new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.EditorFor(model => model.Title, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.Title, "", new { @class = "text-danger" }) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.Description, htmlAttributes: new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.EditorFor(model => model.Description, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.Description, "", new { @class = "text-danger" }) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.State, htmlAttributes: new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.EnumDropDownListFor(model => model.State, htmlAttributes: new { @class = "form-control" }) @Html.ValidationMessageFor(model => model.State, "", new { @class = "text-danger" }) </div> </div> <div class="form-group"> <div class="col-md-offset-2 col-md-10"> <input type="submit" value="Save" class="btn btn-default" /> </div> </div> </div> } </div> </div> </div> </div> <script type="text/javascript"> //该段代码十分重要,确保异步调用后jquery能正确执行验证逻辑 $(function () { //allow validation framework to parse DOM $.validator.unobtrusive.parse('form'); }); </script>
백엔드 코드:
public PartialViewResult Edit(int id){ var task = _taskAppService.GetTaskById(id); var updateTaskDto = AutoMapper.Mapper.Map<UpdateTaskInput>(task); var userList = _userAppService.GetUsers(); ViewBag.AssignedPersonId = new SelectList(userList.Items, "Id", "Name", updateTaskDto.AssignedPersonId); return PartialView("_EditTask", updateTaskDto); } [HttpPost] [ValidateAntiForgeryToken]public ActionResult Edit(UpdateTaskInput updateTaskDto){ _taskAppService.UpdateTask(updateTaskDto); var input = new GetTasksInput(); var output = _taskAppService.GetTasks(input); return PartialView("_List", output.Tasks); }
5. 인덱스 뷰 생성
在首页中,我们一般会用来展示列表,并通过弹出模态框的方式来进行新增更新删除。为了使用ASP.NET MVC强视图带给我们的好处(模型绑定、输入校验等等),我们需要创建一个ViewModel来进行模型绑定。因为Abp提倡为每个不同的应用服务提供不同的Dto进行数据交互,新增对应CreateTaskInput,更新对应UpdateTaskInput,展示对应TaskDto。那我们创建的ViewModel就需要包含这几个模型,方可在一个视图中完成多个模型的绑定。
1,创建视图模型(IndexViewModel)
namespace LearningMpaAbp.Web.Models.Tasks{ public class IndexViewModel { /// <summary> /// 用来进行绑定列表过滤状态 /// </summary> public TaskState? SelectedTaskState { get; set; } /// <summary> /// 列表展示 /// </summary> public IReadOnlyList<TaskDto> Tasks { get; } /// <summary> /// 创建任务模型 /// </summary> public CreateTaskInput CreateTaskInput { get; set; } /// <summary> /// 更新任务模型 /// </summary> public UpdateTaskInput UpdateTaskInput { get; set; } public IndexViewModel(IReadOnlyList<TaskDto> items) { Tasks = items; } /// <summary> /// 用于过滤下拉框的绑定 /// </summary> /// <returns></returns> public List<SelectListItem> GetTaskStateSelectListItems() { var list=new List<SelectListItem>() { new SelectListItem() { Text = "AllTasks", Value = "", Selected = SelectedTaskState==null } }; list.AddRange(Enum.GetValues(typeof(TaskState)) .Cast<TaskState>() .Select(state=>new SelectListItem() { Text = $"TaskState_{state}", Value = state.ToString(), Selected = state==SelectedTaskState }) ); return list; } } }
2,创建视图
Index视图,通过加载Partial View的形式,将列表、新增视图一次性加载进来。
@using Abp.Web.Mvc.Extensions @model LearningMpaAbp.Web.Models.Tasks.IndexViewModel @{ ViewBag.Title = L("TaskList"); ViewBag.ActiveMenu = "TaskList"; //Matches with the menu name in SimpleTaskAppNavigationProvider to highlight the menu item } @section scripts{ @Html.IncludeScript("~/Views/Tasks/index.js"); }<h2> @L("TaskList") <button type="button" class="btn btn-primary" data-toggle="modal" data-target="#add">Create Task</button> <a class="btn btn-primary" data-toggle="modal" href="@Url.Action("RemoteCreate")" data-target="#modal" role="button">(Create Task)使用Remote方式调用Modal进行展现</a> <!--任务清单按照状态过滤的下拉框--> <span class="pull-right"> @Html.DropDownListFor( model => model.SelectedTaskState, Model.GetTaskStateSelectListItems(), new { @class = "form-control select2", id = "TaskStateCombobox" }) </span></h2><!--任务清单展示--><div class="row" id="taskList"> @{ Html.RenderPartial("_List", Model.Tasks); }</div><!--通过初始加载页面的时候提前将创建任务模态框加载进来--> @Html.Action("Create")<!--编辑任务模态框通过ajax动态填充到此div中--><div id="edit"></div><!--Remote方式弹出创建任务模态框--> <div class="modal fade" id="modal" tabindex="-1" role="dialog" aria-labelledby="createTask" data-backdrop="static"> <div class="modal-dialog" role="document"> <div class="modal-content"> </div> </div></div>
3,Remote方式创建任务讲解
Remote方式就是,点击按钮的时候去加载创建任务的PartialView到指定的div中。而我们代码中另一种方式是通过@Html.Action("Create")的方式,在加载Index的视图的作为子视图同步加载了进来。
感兴趣的同学自行查看源码,不再讲解。
<a class="btn btn-primary" data-toggle="modal" href="@Url.Action("RemoteCreate")" data-target="#modal" role="button">(Create Task)使用Remote方式调用Modal进行展现</a> <!--Remote方式弹出创建任务模态框--> <div class="modal fade" id="modal" tabindex="-1" role="dialog" aria-labelledby="createTask" data-backdrop="static"> <div class="modal-dialog" role="document"> <div class="modal-content"> </div> </div></div>
4,后台代码
public ActionResult Index(GetTasksInput input) { var output = _taskAppService.GetTasks(input); var model = new IndexViewModel(output.Tasks) { SelectedTaskState = input.State }; return View(model); }
5,js代码(index.js)
var taskService = abp.services.app.task; (function ($) { $(function () { var $taskStateCombobox = $('#TaskStateCombobox'); $taskStateCombobox.change(function () { getTaskList(); }); var $modal = $(".modal"); //显示modal时,光标显示在第一个输入框 $modal.on('shown.bs.modal', function () { $modal.find('input:not([type=hidden]):first').focus(); }); }); })(jQuery);//异步开始提交时,显示遮罩层function beginPost(modalId) { var $modal = $(modalId); abp.ui.setBusy($modal); }//异步开始提交结束后,隐藏遮罩层并清空Formfunction hideForm(modalId) { var $modal = $(modalId); var $form = $modal.find("form"); abp.ui.clearBusy($modal); $modal.modal("hide"); //创建成功后,要清空form表单 $form[0].reset(); }//处理异步提交异常function errorPost(xhr, status, error, modalId) { if (error.length>0) { abp.notify.error('Something is going wrong, please retry again later!'); var $modal = $(modalId); abp.ui.clearBusy($modal); } }function editTask(id) { abp.ajax({ url: "/tasks/edit", data: { "id": id }, type: "GET", dataType: "html" }) .done(function (data) { $("#edit").html(data); $("#editTask").modal("show"); }) .fail(function (data) { abp.notify.error('Something is wrong!'); }); }function deleteTask(id) { abp.message.confirm( "是否删除Id为" + id + "的任务信息", function (isConfirmed) { if (isConfirmed) { taskService.deleteTask(id) .done(function () { abp.notify.info("删除任务成功!"); getTaskList(); }); } } ); }function getTaskList() { var $taskStateCombobox = $('#TaskStateCombobox'); var url = '/Tasks/GetList?state=' + $taskStateCombobox.val(); abp.ajax({ url: url, type: "GET", dataType: "html" }) .done(function (data) { $("#taskList").html(data); });
js代码中处理了Ajax回调函数,以及任务状态过滤下拉框更新事件,编辑、删除任务代码。其中getTaskList()函数是用来异步属性列表,对应调用的GetList()Action的后台代码如下:
public PartialViewResult GetList(GetTasksInput input){ var output = _taskAppService.GetTasks(input); return PartialView("_List", output.Tasks); }
六、总结
至此,完成了任务的增删改查。展现层主要用到了Asp.net mvc的强类型视图、Bootstrap-Modal、Ajax异步提交技术。
其中需要注意的是,在异步加载表单时,需要添加以下js代码,jquery方能进行前端验证。
<script type="text/javascript"> $(function () { //allow validation framework to parse DOM $.validator.unobtrusive.parse('form'); });
以上就是ABP入门系列(6)——展现层实现增删改查的内容,更多相关内容请关注PHP中文网(www.php.cn)!