Modal Pages¶
- Do reference only application contracts package at razor ui layer.
- Do inherit from the
AbpPageModel
page model. - Do always group
modals
under two certain development convention:- Create
- Update
Creating A New Entity¶
- Do use related input dto which is available at application contracts and always use Input as property name as a binding property at modal razor ui models.
- Do create
ErrorModelInfo
withError
name to direct business exceptions from app services to error page. - Do always initialize sub collections for dropdown menu items in the constructor.
- Do initialize input dto in the OnGet method.
- Do validate model in the OnPost method.
- Do return No content in the OnPost method.
- Do inject application services and localizer as private readonly.
Example create modal model:
public class CreateModalModel : ProjectPlanningPageModel
{
[BindProperty]
public CreateProjectInput Input { get; set; } // From application contracts
public ErrorModelInfo Error { get; set; }
public List<SelectListItem> BaseModelList { get; set; } // Dropdown menu items
private readonly IProjectAppService _projectAppService; // Application services for use cases
private readonly IBaseModelAppService _baseModelAppService; // Application services for use cases
public CreateModalModel(IProjectAppService projectAppService, IBaseModelAppService baseModelAppService, IStringLocalizer<ProjectPlanningResource> localizer)
{
_projectAppService = projectAppService;
_baseModelAppService = baseModelAppService;
_localizer = localizer;
BaseModelList = new List<SelectListItem>
{
new SelectListItem { Value = "", Text = _localizer["Select"].Value }
};
}
public async Task<IActionResult> OnGetAsync()
{
Input = new CreateProjectInput();
Error = new ErrorModelInfo();
try
{
var baseModels = await _baseModelAppService.GetListAsync(new GetBaseModelInput { MaxResultCount = LimitedResultRequestDto.MaxMaxResultCount });
BaseModelList.AddRange(baseModels.Items.Select(b => new SelectListItem(b.Name, b.Id.ToString())).ToList());
}
catch (Exception ex)
{
Logger.LogError(ex.Message);
Error.HasError = true;
Error.Message = _localizer["Oopst!"].Value;
return Page();
}
return Page();
}
public async Task<IActionResult> OnPostAsync()
{
ValidateModel();
await _projectAppService.CreateAsync(Input);
return NoContent();
}
public class ErrorModelInfo
{
public bool HasError { get; set; }
public string Message { get; set; }
}
}
The related Input DTO:
public class CreateProjectInput : ExtensibleObject
{
[Required]
public string Name { get; set; }
public string Description { get; set; } = string.Empty;
public CreateProjectInput()
: base(setDefaultsForExtraProperties: false)
{
}
}
The related modal cshtml:
@page
@using Microsoft.AspNetCore.Mvc.Localization
@using GridLab.PSSX.ProjectPlanning.Localization
@using GridLab.PSSX.ProjectPlanning.Pages.Shared.ModalErrorComponent
@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal
@using GridLab.PSSX.ProjectPlanning.Web.Pages.ProjectPlanning.Projects
@using System.Globalization
@inject IHtmlLocalizer<ProjectPlanningResource> L
@model CreateModalModel
@{
Layout = null;
}
<form data-ajaxForm="true" asp-page="/ProjectPlanning/Projects/CreateModal" autocomplete="off">
<abp-modal id="ProjectCreateModal" size="Large">
<abp-modal-header title="@L["NewProject"].Value"></abp-modal-header>
<abp-modal-body>
@if (Model.Error.HasError)
{
@(await Component.InvokeAsync<ModalErrorViewComponent>(new { model = new ModalErrorPageModel { ErrorMessage = Model.Error.Message } }))
}
else
{
<abp-tabs name="CreateProjectTab">
<abp-tab title="@L["GeneralData"].Value">
<abp-input asp-for="Input.Name" label="@L["Name"].Value" />
<abp-input asp-for="Input.Description" label="@L["Description"].Value" />
<abp-input asp-for="Input.Version" label="@L["Version"].Value" />
<abp-select asp-items="Model.PriorityList" asp-for="Input.Priority" label="@L["Priority"].Value" />
<abp-select asp-items="Model.DefinitionList" asp-for="Input.DefinitionId" label="@L["Definition"].Value" />
</abp-tab>
<abp-tab title="@L["BaseModel"].Value">
<abp-select asp-items="Model.BaseModelList" asp-for="Input.BaseModelId" label="@L["BaseModel"].Value" />
</abp-tab>
<abp-tab title="@L["Dates"].Value">
<abp-input asp-for="Input.StartDate" value="@DateTime.Now.ToString("yyyy-MM-dd")" type="date" label="@L["StartDate"].Value" />
<abp-input asp-for="Input.EndDate" value="" type="date" label="@L["EndDate"].Value" />
<abp-input asp-for="Input.CommissionDate" value="" type="date" label="@L["CommissionDate"].Value" />
<abp-input asp-for="Input.InServiceDate" value="" type="date" label="@L["InServiceDate"].Value" />
</abp-tab>
<abp-tab title="@L["Projects"].Value">
<div class="mb-3">
<label class="form-label">@L["Successors"]</label>
<select id="successors" asp-for="Input.SuccessorIds" data-placeholder="@L["Select"].Value" multiple></select>
</div>
<div class="mb-3">
<label class="form-label">@L["Predecessors"]</label>
<select id="predecessors" asp-for="Input.PredecessorIds" data-placeholder="@L["Select"].Value" multiple></select>
</div>
@* <select asp-for="Input.SuccessorIds" abp multiple select example
label="@L["Successors"].Value"
multiple="multiple"
class="auto-complete-select"
data-autocomplete-api-url="/api/ProjectPlanning/project-management/projects"
data-autocomplete-display-property="name"
data-autocomplete-value-property="id"
data-autocomplete-items-property="items"
data-autocomplete-filter-param-name="name"
data-autocomplete-allow-clear="true">
</select>*@
</abp-tab>
</abp-tabs>
}
</abp-modal-body>
<abp-modal-footer>
<abp-button id="cancel" text="@L["Cancel"].Value" button-type="Secondary" />
@if (!Model.Error.HasError)
{
<abp-button text="@L["Save"].Value" type="submit" icon="check" button-type="Primary" />
}
</abp-modal-footer>
</abp-modal>
</form>
Updating An Existing Entity¶
- Do use Guid datatype and Id as property name as a binding property at modal razor ui models.
- Do use the
ModelInfo
postfix for the existing entity object name (ex:ProjectModelInfo
). - Do inherit entity model info from the
IHasConcurrencyStamp
interface. Property must be decorated with[HiddenInput]
attribute. - Do define parameters to be changed in the created model info.
- Do create
ErrorModelInfo
withError
name to direct business exceptions from app services to error page. - Do always initialize sub collections for dropdown menu items in the constructor.
- Do define OnGet method with parameter name Id and data type Guid.
- Do get entity from persistence layer via application services with id parameter in the OnGet method.
- Do map input dto to the model info in the OnGet method.
- Do validate model in the OnPost method.
- Do return No content in the OnPost method.
- Do inject application services and localizer as private readonly.
Example edit modal model:
public class EditModalModel : ProjectPlanningPageModel
{
[BindProperty]
public Guid Id { get; set; }
[BindProperty]
public ProjectModelInfo Project { get; set; }
public ErrorModelInfo Error { get; set; }
public List<SelectListItem> PriorityList { get; set; }
public List<SelectListItem> BaseModelList { get; set; }
private readonly IProjectAppService _projectAppService;
private readonly IStringLocalizer<ProjectPlanningResource> _localizer;
public EditModalModel(IProjectAppService projectAppService, IStringLocalizer<ProjectPlanningResource> localizer)
{
_projectAppService = projectAppService;
_localizer = localizer;
PriorityList = new List<SelectListItem>
{
new SelectListItem { Value = "", Text = _localizer["Select"].Value},
new SelectListItem { Value = Convert.ToInt32(Priority.High).ToString(), Text = _localizer["Project:Priority:High"].Value},
new SelectListItem { Value = Convert.ToInt32(Priority.Low).ToString(), Text = _localizer["Project:Priority:Low"].Value},
};
}
public async Task<IActionResult> OnGetAsync(Guid id)
{
Error = new ErrorModelInfo();
try
{
var project = await _projectAppService.GetAsync(id);
Project = ObjectMapper.Map<ProjectDto, ProjectModelInfo>(project);
Id = id;
}
catch (AbpRemoteCallException ex) when (ex.Code == ProjectPlanningErrorCodes.ProjectDoesNotExistException | ex.Code == ProjectPlanningErrorCodes.BaseModelDoesNotExistException)
{
Logger.LogWarning(ex.Message);
Error.HasError = true;
Error.Message = ex.Message;
return Page();
}
catch (Exception ex)
{
Logger.LogError(ex.Message);
Error.HasError = true;
Error.Message = _localizer["Oopst!"].Value;
return Page();
}
return Page();
}
public async Task<IActionResult> OnPostAsync()
{
ValidateModel();
var input = ObjectMapper.Map<ProjectModelInfo, UpdateProjectInput>(Project);
await _projectAppService.UpdateAsync(Id, input);
return NoContent();
}
public class ErrorModelInfo
{
public bool HasError { get; set; }
public string Message { get; set; }
}
}
ProjectModelInfo
will be changed on the modal screen. The related Entity ModelInfo:
public class ProjectModelInfo : IHasConcurrencyStamp
{
[Required]
public string Name { get; set; }
public string Description { get; set; }
[Required]
public Priority Priority { get; set; }
public int Version { get; set; }
public DateTime StartDate { get; set; }
public DateTime? EndDate { get; set; }
public DateTime? CommissionDate { get; set; }
public DateTime? InServiceDate { get; set; }
[HiddenInput]
public string ConcurrencyStamp { get; set; }
}
The related modal cshtml:
@page
@using Microsoft.AspNetCore.Mvc.Localization
@using GridLab.PSSX.ProjectPlanning.Localization
@using GridLab.PSSX.ProjectPlanning.Pages.Shared.ModalErrorComponent
@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal
@using GridLab.PSSX.ProjectPlanning.Web.Pages.ProjectPlanning.Projects
@using System.Globalization
@inject IHtmlLocalizer<ProjectPlanningResource> L
@model GridLab.PSSX.ProjectPlanning.Pages.ProjectPlanning.Projects.EditModalModel
@{
Layout = null;
}
<form data-ajaxForm="true" asp-page="/ProjectPlanning/Projects/EditModal" autocomplete="off">
<abp-modal id="ProjectEditModal" size="Large">
<abp-modal-header title="@L["EditProject"].Value"></abp-modal-header>
<abp-modal-body>
@if (Model.Error.HasError)
{
@(await Component.InvokeAsync<ModalErrorViewComponent>(new { model = new ModalErrorPageModel { ErrorMessage = Model.Error.Message } }))
}
else
{
<abp-tabs name="EditProjectTab">
<abp-tab title="@L["GeneralData"].Value">
<abp-input asp-for="Project.Name" label="@L["Name"].Value" />
<abp-input asp-for="Project.Description" label="@L["Description"].Value" />
<abp-input asp-for="Project.Version" label="@L["Version"].Value" />
<abp-select asp-items="Model.PriorityList" asp-for="Project.Priority" label="@L["Priority"].Value" />
</abp-tab>
<abp-tab title="@L["Dates"].Value">
<abp-input asp-for="Project.StartDate" value="@Model.Project.StartDate.ToString("yyyy-MM-dd")" type="date" label="@L["StartDate"].Value" />
<abp-input asp-for="Project.EndDate" value="@Model.Project.EndDate?.ToString("yyyy-MM-dd")" type="date" label="@L["EndDate"].Value" />
<abp-input asp-for="Project.CommissionDate" value="@Model.Project.CommissionDate?.ToString("yyyy-MM-dd")" type="date" label="@L["CommissionDate"].Value" />
<abp-input asp-for="Project.InServiceDate" value="@Model.Project.InServiceDate?.ToString("yyyy-MM-dd")" type="date" label="@L["InServiceDate"].Value" />
</abp-tab>
</abp-tabs>
<abp-input type="hidden" asp-for="@Model.Id" />
<abp-input type="hidden" asp-for="Project.ConcurrencyStamp" />
}
</abp-modal-body>
<abp-modal-footer>
<abp-button id="cancel" text="@L["Cancel"].Value" button-type="Secondary" />
@if (!Model.Error.HasError)
{
<abp-button text="@L["Update"].Value" type="submit" icon="plus" button-type="Primary" />
}
</abp-modal-footer>
</abp-modal>
</form>