目录
FormLeadCardEdit_ItemChangedRule
FormLeadCardEdit_ItemDeletingRule
本系列中的所有BlazorForms文章:
- BlazorForms低代码开源框架——第1部分:介绍和种子项目
- BlazorForms低代码开源框架——第2部分:CrmLight项目
- BlazorForms低代码开源框架——第3部分:CrmLight Lead Board
我们在这里部署了一个工作解决方案:
请记住,它没有数据库,它将所有数据保存在内存中,供所有用户共享,并且在一段时间不活动后,容器会关闭丢失所有数据。
我们部署到具有最低CPU和内存要求的Linux docker容器,但它运行速度非常快。
介绍
这篇文章延续了由PRO CODERS PTY LTD开发的关于BlazorForms框架的系列文章,并作为带有MIT许可证的开源共享。
在上一篇文章中,我介绍了CrmLight种子项目,该项目展示了如何使用BlazorForms实现、编辑和列出form,BlazorForms是一个简化UI开发并允许你构建简单且可维护的C#解决方案的框架。
BlazorForms Paradigm
我们在此框架中放置的主要范例是将解决方案的逻辑部分与物理UI呈现分离。BlazorForms鼓励开发人员首先规划解决方案,并考虑实体、关系、数据访问、模型和业务逻辑,而不是UI控件和事件编码。
从GitHub下载此博客文章代码:
GitHub上的BlazorForms项目:
CrmLight Lead Board
我们将继续扩展CrmLight项目,以展示框架的不同领域和组件。在这篇文章中,我们将展示如何实施CRM领导板。
如果运行结果解决方案,则可以在导航菜单中看到“Lead Board”。单击它将导航到屏幕:
图1
在这里,您可以看到系统中所有打开的潜在客户。
您可以垂直对卡进行排序,并将它们向左或向右移动到表示Lead Board状态的存储桶中。
当您单击卡片时,将显示编辑对话框。在这里,用户可以更新卡片详细信息,添加评论并跟踪评论历史记录:
图2
历史记录注释也可以为注释所有者编辑:
图3
当卡移动到Won存储桶时,系统会要求用户提供更多详细信息以创建客户端记录(将显示在Clients页面中):
图4
当用户在下拉搜索栏中选择一个值(例如客户端管理器)时,还可以添加新记录或编辑现有项目之一:
图5
图6
如何实现
如果您查看Visual Studio解决方案,您将看到新的子文件夹已添加到Flow文件夹:
图7
它包含Lead Board的业务逻辑(flow、form和规则)。
UI部分将由稍后将讨论的FlowBoard控件呈现。现在,我应该提到FlowBoard对它使用的flow和model有一些要求。
Flow
让我们从 LeadBoardStateFlow.cs 开始它控制 Lead Board 状态和转换。
using BlazorForms.Flows;
using BlazorForms.Flows.Definitions;
using CrmLightDemoApp.Onion.Domain.Repositories;
using CrmLightDemoApp.Onion.Services.Model;
namespace CrmLightDemoApp.Onion.Services.Flow.LeadBoard
{
public class LeadBoardStateFlow : StateFlowBase<LeadBoardCardModel>
{
// Board Columns
public state Lead;
public state Contacted;
public state MeetingScheduled = new state("Meeting Scheduled");
public state ProposalDelivered = new state("Proposal Delivered");
public state Won;
// Board Card Transitions
public override void Define()
{
this
.SetEditForm<FormLeadCardEdit>()
.State(Lead)
.TransitionForm<FormContactedCardEdit>
(new UserActionTransitionTrigger(), Contacted)
.State(Contacted)
.Transition<UserActionTransitionTrigger>(Lead)
.Transition(new UserActionTransitionTrigger(), MeetingScheduled)
.State(MeetingScheduled)
.Transition<UserActionTransitionTrigger>(Contacted)
.Transition<UserActionTransitionTrigger>(ProposalDelivered)
.State(ProposalDelivered)
.Transition<UserActionTransitionTrigger>(MeetingScheduled)
.TransitionForm<FormCardCommit>
(new UserActionTransitionTrigger(), Won)
.State(Won)
.Transition<UserActionTransitionTrigger>(Lead)
.Transition<UserActionTransitionTrigger>(Contacted)
.Transition<UserActionTransitionTrigger>(MeetingScheduled)
.Transition<UserActionTransitionTrigger>(ProposalDelivered)
.End();
}
}
}
您可以在顶部看到声明的所有五个状态,当我们需要与状态名称不同的状态描述时,我们使用public state MeetingScheduled = new state("Meeting Scheduled");
接下来,我们定义状态之间所有可能的转换,这行代码:
.State(Lead)
.TransitionForm<FormContactedCardEdit>(new UserActionTransitionTrigger(), Contacted)
表示Lead和Contacted状态之间存在过渡。在过渡期间,当过渡发生时显示formFormContactedCardEdit,只能通过用户操作触UserActionTransitionTrigger发器(通过拖放或上下文菜单)进行过渡。
如果未定义两种状态之间的转换,则Lead Board将不允许移动卡。
Model
flow指定一个特定的model类,您可以将其视为StateFlowBase flow基类——LeadBoardCardModel的模板参数。如果单击此模型类并按 F12,Visual Studio将导航到 LeadBoardCardModel.cs其中包含类代码:
public class LeadBoardCardModel : BoardCard, IFlowBoardCard
{
public virtual string? Comments { get; set; }
// for dropdowns
public virtual List<PersonModel> AllPersons { get; set; } = new();
public virtual List<CompanyModel> AllCompanies { get; set; } = new();
public virtual List<LeadSourceType> AllLeadSources { get; set; } = new();
// for ClientCompany
public virtual ClientCompany ClientCompany { get; set; } = new();
public virtual List<CardHistoryModel>? CardHistory { get; set; } = new();
// properties
public string SalesPersonFullName
{
get
{
var sp = AllPersons.FirstOrDefault(p => p.Id == SalesPersonId);
if (sp != null)
{
return sp.FullName;
}
return null;
}
}
}
模型类继承BoardCard实体的所有属性并实现IFlowBoardCard接口,这对于FlowBoard UI使用的模型是必需的。
LeadBoardCardModel类也由flow中定义的所有form使用。
Form
flow线.SetEditForm<FormLeadCardEdit>()定义当用户想要编辑卡片时显示的默认编辑form。
如果指定处于特定状态的SetEditForm<>()函数,则指定的form将仅用于编辑处于此状态的卡片。
public class FormLeadCardEdit : FormEditBase<LeadBoardCardModel>
{
protected override void Define(FormEntityTypeBuilder<LeadBoardCardModel> f)
{
f.DisplayName = "Lead Card";
f.Rule(typeof(FormLeadCard_RefreshSources), FormRuleTriggers.Loaded);
f.Confirm(ConfirmType.ChangesWillBeLost,
"If you leave before saving, your changes will be lost.",
ConfirmButtons.OkCancel);
f.Layout = FormLayout.TwoColumns;
f.Group("left");
f.Property(p => p.State).IsReadOnly();
f.Property(p => p.Title).IsRequired();
f.Property(p => p.Description);
f.Property(p => p.SalesPersonId).DropdownSearch
(p => p.AllPersons, m => m.Id, m => m.FullName).Label
("Sales person").IsRequired()
.ItemDialog(typeof(PersonDialogFlow));
f.Property(p => p.RelatedCompanyId).DropdownSearch
(p => p.AllCompanies, m => m.Id, m => m.Name).Label("Lead company")
.ItemDialog(typeof(CompanyDialogFlow));
f.Property(p => p.RelatedPersonId).DropdownSearch
(p => p.AllPersons, m => m.Id, m => m.FullName).Label("Lead contact")
.ItemDialog(typeof(PersonDialogFlow));
f.Property(p => p.LeadSourceTypeId).Dropdown
(p => p.AllLeadSources, m => m.Id, m => m.Name).Label("Lead source");
f.Property(p => p.Phone);
f.Property(p => p.Email);
f.Property(p => p.ContactDetails).Label("Other contact info");
f.Group("right");
f.Property(p => p.Comments).Control(ControlType.TextArea);
f.CardList(p => p.CardHistory, e =>
{
e.DisplayName = "Comment history";
e.Card(p => p.TitleMarkup, p => p.Text, p => p.AvatarMarkup);
e.Rule(typeof(FormLeadCardEdit_ItemChangedRule));
e.Rule(typeof(FormLeadCardEdit_ItemDeletingRule),
FormRuleTriggers.ItemDeleting);
e.Confirm(ConfirmType.DeleteItem,
"Delete this comment?", ConfirmButtons.YesNo);
e.Button(ButtonActionTypes.Edit);
e.Button(ButtonActionTypes.Delete);
});
f.Button(ButtonActionTypes.Submit, "Save");
f.Button(ButtonActionTypes.Cancel, "Cancel");
}
}
加载form时将执行FormLeadCard_RefreshSources规则。
form FormLayout.TwoColumns用于将form内容拆分为两列,将所有编辑控件保留在左列,注释和历史记录保留在右列。
对于DropdownSearch控件,我们使用ItemDialog函数来指定用于添加或查看项详细信息的对话框。
为了实现评论历史记录,我们使用了CardList控件,其中包含
- 绑定到用于呈现每个历史项的模型属性,
- 编辑和删除按钮以编辑项目,
- 以及在编辑或删除历史记录项时执行的规则,并确认DeleteItem操作。
规则
FormLeadCard_RefreshSources
每次显示(加载)form时都会执行此规则:
public class FormLeadCard_RefreshSources : FlowRuleAsyncBase<LeadBoardCardModel>
{
private readonly ICompanyRepository _companyRepository;
private readonly IPersonRepository _personRepository;
private readonly IBoardCardHistoryRepository _boardCardHistoryRepository;
private readonly IAppAuthState _appAuthState;
public override string RuleCode => "BRD-4";
public FormLeadCard_RefreshSources(ICompanyRepository companyRepository,
IPersonRepository personRepository,
IBoardCardHistoryRepository boardCardHistoryRepository,
IAppAuthState appAuthState)
{
_companyRepository = companyRepository;
_personRepository = personRepository;
_boardCardHistoryRepository = boardCardHistoryRepository;
_appAuthState = appAuthState;
}
public override async Task Execute(LeadBoardCardModel model)
{
// refresh drop down sources
model.AllPersons = (await _personRepository.GetAllAsync())
.Select(x =>
{
var item = new PersonModel();
x.ReflectionCopyTo(item);
item.FullName = $"{x.FirstName} {x.LastName}";
return item;
}).OrderBy(x => x.FullName).ToList();
model.AllCompanies = (await _companyRepository.GetAllAsync())
.Select(x =>
{
var item = new CompanyModel();
x.ReflectionCopyTo(item);
return item;
}).OrderBy(x => x.Name).ToList();
// refresh comments
if (model.Id > 0)
{
model.CardHistory =
(await _boardCardHistoryRepository.GetListByCardIdAsync(model.Id))
.Select(x =>
{
var item = new CardHistoryModel();
x.ReflectionCopyTo(item);
return item;
}).ToList();
}
// refresh card buttons - display buttons only for comment owners
for (int i = 0; i < model.CardHistory.Count; i++)
{
var isCurrentUser = _appAuthState.GetCurrentUser().Id ==
model.CardHistory[i].PersonId;
Result.Fields[FindField(m => m.CardHistory,
ModelBinding.EditButtonBinding, i)].Visible = isCurrentUser;
Result.Fields[FindField(m => m.CardHistory,
ModelBinding.DeleteButtonBinding, i)].Visible = isCurrentUser;
}
}
}
它使用依赖注入来接收所需的存储库,以及读取登录到系统的当前用户所需的IAppAuthState。
在该Execute方法中,它填充用于DropdownSearch控件的集合,并填充读取此卡的所有历史记录项的CardHistory集合。
然后,它会更新“编辑”和“删除”按钮的Visible属性,仅允许修改注释历史记录项所有者。
FormLeadCardEdit_ItemChangedRule
当用户修改注释卡时,将执行此规则。它设置EditedDate并使用_boardCardHistoryRepository保存评论。
public class FormLeadCardEdit_ItemChangedRule : FlowRuleAsyncBase<LeadBoardCardModel>
{
private readonly IBoardCardHistoryRepository _boardCardHistoryRepository;
public override string RuleCode => "BRD-5";
public FormLeadCardEdit_ItemChangedRule
(IBoardCardHistoryRepository boardCardHistoryRepository)
{
_boardCardHistoryRepository = boardCardHistoryRepository;
}
public override async Task Execute(LeadBoardCardModel model)
{
var changedCard = model.CardHistory[RunParams.RowIndex];
changedCard.EditedDate = DateTime.Now;
await _boardCardHistoryRepository.UpdateAsync(changedCard);
Result.SkipThisChange = true;
}
}
FormLeadCardEdit_ItemDeletingRule
此规则只是在用户单击“删除”按钮时删除项目。
public class FormLeadCardEdit_ItemDeletingRule : FlowRuleAsyncBase<LeadBoardCardModel>
{
private readonly IBoardCardHistoryRepository _boardCardHistoryRepository;
private readonly IAppAuthState _appAuthState;
public override string RuleCode => "BRD-6";
public FormLeadCardEdit_ItemDeletingRule
(IBoardCardHistoryRepository boardCardHistoryRepository, IAppAuthState appAuthState)
{
_boardCardHistoryRepository = boardCardHistoryRepository;
_appAuthState = appAuthState;
}
public override async Task Execute(LeadBoardCardModel model)
{
await _boardCardHistoryRepository.SoftDeleteAsync
(model.CardHistory[RunParams.RowIndex]);
Result.SkipThisChange = true;
}
}
其他Form和Flow
FormContactedCardEdit
此窗体用于从Lead到Contacted状态的转换,每次用户将卡从Lead存储桶移动到Contacted存储桶时都会显示此窗体。
它显示用户可以在转换期间更新的几个字段,以及用户在提交转换之前必须输入的一个必填字段“Lead contact”。
public class FormContactedCardEdit : FormEditBase<LeadBoardCardModel>
{
protected override void Define(FormEntityTypeBuilder<LeadBoardCardModel> f)
{
f.DisplayName = "Lead Contacted Card";
f.Property(p => p.RelatedCompanyId).DropdownSearch
(p => p.AllCompanies, m => m.Id, m => m.Name).Label("Lead company")
.ItemDialog(typeof(CompanyDialogFlow));
f.Property(p => p.RelatedPersonId).DropdownSearch
(p => p.AllPersons, m => m.Id, m => m.FullName).Label("Lead contact")
.ItemDialog(typeof(PersonDialogFlow)).IsRequired();
f.Property(p => p.Phone);
f.Property(p => p.Email);
f.Property(p => p.ContactDetails).Label("Other contact info");
f.Button(ButtonActionTypes.Submit, "Save");
f.Button(ButtonActionTypes.Cancel, "Cancel");
}
}
FormCardCommit
此form用于向Won状态的最终过渡。它只有一个必填字段“Client company”,用户可以在其中使用对话框CompanyDialogFlow选择或添加新公司:
public class FormCardCommit : FormEditBase<LeadBoardCardModel>
{
protected override void Define(FormEntityTypeBuilder<LeadBoardCardModel> f)
{
f.DisplayName = "Congrats with another win!
Click 'Save' to create client record.";
f.Property(p => p.Title).IsReadOnly();
f.Property(p => p.ClientCompany.StartContractDate).Label("Start contract date");
f.Property(p => p.ClientCompany.ClientManagerId).DropdownSearch
(p => p.AllPersons, m => m.Id, m => m.FullName)
.Label("Client manager").ItemDialog(typeof(PersonDialogFlow));
f.Property(p => p.ClientCompany.AlternativeClientManagerId).DropdownSearch
(p => p.AllPersons, m => m.Id, m => m.FullName)
.Label("Alternative client manager").ItemDialog(typeof(PersonDialogFlow));
f.Property(p => p.RelatedCompanyId).DropdownSearch
(p => p.AllCompanies, m => m.Id, m => m.Name)
.Label("Client company").ItemDialog
(typeof(CompanyDialogFlow)).IsRequired();
f.Button(ButtonActionTypes.Submit, "Save");
f.Button(ButtonActionTypes.Cancel, "Cancel");
}
}
您可能已经注意到,我们在定义控件时使用了几次。CompanyDialogFlowPersonDialogFlowDropdownSearch
这些是从基类引用模型和窗体继承的简化流,这些流具有在显示对话框时加载数据和在提交对话框时保存数据的方法。DialogFlowBase<>
例如,在以下代码行中使用特定使用的集合中项的模型类型非常重要:DropdownSearch
f.Property(p => p.RelatedCompanyId).DropdownSearch(p => p.AllCompanies,
m => m.Id, m => m.Name)
.Label("Client company").ItemDialog
(typeof(CompanyDialogFlow)).IsRequired();
DropdownSearch引用定义为List<CompanyModel>的AllCompanies集合,这意味着CompanyDialogFlow应该引用CompanyModel。
CompanyDialogFlow
此flow使用依赖关系注入来接收ICompanyRepository,该ICompanyRepository用于通过LoadDataAsync中的Id读取模型数据,并将其保存在SaveDataAsync中。
form FormCompanyDialogEdit定义字段控件和按钮。
public class CompanyDialogFlow : DialogFlowBase<CompanyModel, FormCompanyDialogEdit>
{
private readonly ICompanyRepository _companyRepository;
public CompanyDialogFlow(ICompanyRepository companyRepository)
{
_companyRepository = companyRepository;
}
public override async Task LoadDataAsync()
{
if (GetId() > 0)
{
var record = await _companyRepository.GetByIdAsync(GetId());
record.ReflectionCopyTo(Model);
}
else
{
Model.Name = Params["Name"];
}
}
public override async Task SaveDataAsync()
{
if (GetId() > 0)
{
await _companyRepository.UpdateAsync(Model);
}
else
{
Model.Id = await _companyRepository.CreateAsync(Model);
}
}
}
public class FormCompanyDialogEdit : FormEditBase<CompanyModel>
{
protected override void Define(FormEntityTypeBuilder<CompanyModel> f)
{
f.DisplayName = "Add new company";
f.Property(p => p.Name).Label("Name").IsRequired();
f.Property(p => p.RegistrationNumber).Label("Reg. No.");
f.Property(p => p.EstablishedDate).Label("Established date");
f.Button(ButtonActionTypes.Cancel, "Cancel");
f.Button(ButtonActionTypes.Submit, "Save");
}
}
PersonDialogFlow
此form与前一个form非常相似,但使用FormPersonEdit之前在第 2部分:CrmLight项目中创建的form。
public class PersonDialogFlow : DialogFlowBase<PersonModel, FormPersonEdit>
{
private readonly IPersonRepository _personRepository;
public PersonDialogFlow(IPersonRepository personRepository)
{
_personRepository = personRepository;
}
public override async Task LoadDataAsync()
{
if (GetId() > 0)
{
var record = await _personRepository.GetByIdAsync(GetId());
record.ReflectionCopyTo(Model);
}
var fullName = Params["Name"];
if (fullName != null)
{
var split = fullName.Split(' ');
Model.FirstName = split[0];
if (split.Count() > 1)
{
Model.LastName = split[1];
}
}
}
public override async Task SaveDataAsync()
{
// we need full name for drop down option
Model.FullName = $"{Model.FirstName} {Model.LastName}";
if (GetId() > 0)
{
await _personRepository.UpdateAsync(Model);
}
else
{
Model.Id = await _personRepository.CreateAsync(Model);
}
}
}
现在我们完成了所有抽象的定义,可以专注于如何在UI中呈现它们。
用户界面渲染
现在我们需要看看解决方案页面文件夹中的 LeadBoard.razor。
我们使用FlowBoard控制并为其提供一些参数。
<FlowBoard TFlow=LeadBoardStateFlow TItem=LeadBoardCardModel Caption="Lead Board"
Items=@_items ItemsChanged=@ItemsChanged
CardTitleBackColor="lightblue" Options="GlobalSettings.BoardFormOptions"
EditFormOptions="GlobalSettings.EditFormOptions">
<CardAvatar>
<MudIcon Icon="@Icons.Material.TwoTone.Savings" />
</CardAvatar>
<CardTitle>
<MudText Typo="Typo.body1" Color="Color.Info">@context.Title</MudText>
</CardTitle>
<CardBody>
<MudText Typo="Typo.body2">@context.Description</MudText>
<MudText Typo="Typo.caption" Color="Color.Primary">
@context.SalesPersonFullName</MudText>
</CardBody>
</FlowBoard>
首先,我们需要指定TFlow和TItem参数,将控件指向我们上面已经讨论过的LeadBoardStateFlow及其模型。
第二件事是定义板卡的外观,我们可以定义CardAvatar、CardTitle和CardBody。
最后一件事是提供Items——要在板上显示的卡片列表,以及每次更改卡片状态或卡片详细信息时将执行的ItemsChanged事件,以便我们可以将这些更改保存到数据库中。
@code {
@inject IBoardService _boardService
private List<LeadBoardCardModel> _items = new();
protected override async Task OnParametersSetAsync()
{
await LoadItems();
}
private async Task LoadItems()
{
_items = await _boardService.GetBoardCardsAsync();
}
private async Task ItemsChanged
(List<BoardCardChangedArgs<LeadBoardCardModel>> list)
{
// you can save in transaction to make sure that
// changes are saved all or nothing
//_boardService.BeginUnitOfWork();
var creating = list.Where(x => x.Type == ItemChangedType.Creating).ToList();
creating.ForEach(async a => await _boardService.CreatingBoardCardAsync(a.Item));
var deleted = list.Where(x => x.Type == ItemChangedType.Deleted).ToList();
deleted.ForEach(async a => await _boardService.DeleteBoardCardAsync(a.Item));
var added = list.Where(x => x.Type == ItemChangedType.Added).ToList();
added.ForEach(async a => await _boardService.CreateBoardCardAsync(a.Item));
// if card moved to Won state - create ClientCompany record
var closing = list.FirstOrDefault(x => x.ChangedToTargetState("Won"));
if (closing != null)
{
await CreateClientRecordAsync(closing.Item);
}
// save all changed board cards
var changed = list.Where(x => x.Type == ItemChangedType.Changed
|| x.Type == ItemChangedType.State
|| x.Type == ItemChangedType.Order).ToList();
changed.ForEach(async a => await _boardService.UpdateBoardCardAsync(a.Item));
//_boardService.CommitUnitOfWork();
await LoadItems();
StateHasChanged();
}
private async Task CreateClientRecordAsync(LeadBoardCardModel item)
{
// save Client Company
item.ClientCompany.Id = item.ClientCompanyId ?? 0;
item.ClientCompany.CompanyId = item.RelatedCompanyId.Value;
var existing = await _boardService.FindClientCompanyAsync
(item.ClientCompany.CompanyId);
if (existing != null)
{
// use existing ClientCompany, don't create duplicate
item.ClientCompany.Id = existing.Id;
}
if (item.ClientCompany.Id > 0)
{
await _boardService.UpdateClientCompanyAsync(item.ClientCompany);
}
else
{
item.ClientCompanyId =
await _boardService.CreateClientCompanyAsync(item.ClientCompany);
}
}
}
您可以看到我们通过依赖注入注入IBoardService,并将其用于在LoadItems方法中读取卡片,并在ItemsChanged事件处理程序中保存卡片。
事件处理程序接收已更改项的列表,并且每条记录都具有通知我们发生的更改类型的ItemChangedType属性。例如,如果是ItemChangedType.Creating,我们需要执行_boardService.CreatingBoardCardAsync。
我们为什么将加载/保存逻辑保留在razor页面中而不是将其保留在LeadBoardStateFlow中。这是因为一次只使用一张卡进行LeadBoardStateFlow操作,并且flow用于检查转换是否可能、触发器是什么以及在转换期间要做什么。但是,在board上,我们使用一系列卡片进行操作,我们必须从外部提供卡片,而最好的方法是提供卡片项目作为FlowBoard控件参数。
对于Save操作,最好通过一组卡来操作,例如,当用户重新订购卡并且同时更改多个卡订单时,我们可能希望将它们全部保存在一个数据库事务中。
BoardService
我想在这篇文章中考虑的最后一件事是BoardService实现接口IBoardService。
我们需要它,因为当UI直接使用存储库和实体时,这不是好的做法——最好有一个服务层,它与业务对象一起操作。
因此,BoardService通过依赖注入接收一些存储库,并将翻译存储库实体的逻辑封装到UI中使用的更专业的业务对象中。
它还提供高级业务对象操作,而不是存储库更精细和低级的操作。
using BlazorForms.Shared;
using CrmLightDemoApp.Onion.Domain;
using CrmLightDemoApp.Onion.Domain.Repositories;
using CrmLightDemoApp.Onion.Infrastructure;
using CrmLightDemoApp.Onion.Services.Abstractions;
using CrmLightDemoApp.Onion.Services.Model;
namespace CrmLightDemoApp.Onion.Services
{
public class BoardService : IBoardService
{
private readonly IBoardCardRepository _repo;
private readonly IPersonRepository _personRepository;
private readonly ICompanyRepository _companyRepository;
private readonly IClientCompanyRepository _clientCompanyRepository;
private readonly IRepository<LeadSourceType> _leadSourceTypeRepository;
private readonly IBoardCardHistoryRepository _boardCardHistoryRepository;
private readonly IAppAuthState _appAuthState;
public BoardService(IBoardCardRepository repo,
IPersonRepository personRepository,
IClientCompanyRepository clientCompanyRepository,
ICompanyRepository companyRepository,
IRepository<LeadSourceType> leadSourceTypeRepository,
IBoardCardHistoryRepository boardCardHistoryRepository,
IAppAuthState appAuthState)
{
_repo = repo;
_personRepository = personRepository;
_clientCompanyRepository = clientCompanyRepository;
_companyRepository = companyRepository;
_leadSourceTypeRepository = leadSourceTypeRepository;
_boardCardHistoryRepository = boardCardHistoryRepository;
_appAuthState = appAuthState;
}
public async Task<int> CreateBoardCardAsync(LeadBoardCardModel card)
{
var item = new BoardCard();
card.ReflectionCopyTo(item);
card.Id = await _repo.CreateAsync(item);
return card.Id;
}
public async Task CreatingBoardCardAsync(LeadBoardCardModel card)
{
card.AllPersons = await GetAllPersons();
card.AllCompanies = await GetAllCompanies();
card.AllLeadSources = await GetAllLeadTypes();
}
public async Task DeleteBoardCardAsync(LeadBoardCardModel card)
{
await _repo.SoftDeleteAsync(card);
}
private async Task<List<LeadSourceType>> GetAllLeadTypes()
{
return await _leadSourceTypeRepository.GetAllAsync();
}
private async Task<List<CompanyModel>> GetAllCompanies()
{
return (await _companyRepository.GetAllAsync())
.Select(x =>
{
var item = new CompanyModel();
x.ReflectionCopyTo(item);
return item;
}).OrderBy(x => x.Name).ToList();
}
private async Task<List<PersonModel>> GetAllPersons()
{
return (await _personRepository.GetAllAsync())
.Select(x =>
{
var item = new PersonModel();
x.ReflectionCopyTo(item);
item.FullName = $"{x.FirstName} {x.LastName}";
return item;
}).OrderBy(x => x.FullName).ToList();
}
public async Task<List<LeadBoardCardModel>> GetBoardCardsAsync()
{
var persons = await GetAllPersons();
var companies = await GetAllCompanies();
var leadTypes = await GetAllLeadTypes();
var items = (await _repo.GetAllAsync()).Select(x =>
{
var item = new LeadBoardCardModel();
x.ReflectionCopyTo(item);
item.AllPersons = persons;
item.AllCompanies = companies;
item.AllLeadSources = leadTypes;
return item;
}).OrderBy(x => x.Order).ToList();
return items;
}
public async Task UpdateBoardCardAsync(LeadBoardCardModel card)
{
var item = new BoardCard();
card.ReflectionCopyTo(item);
await _repo.UpdateAsync(item);
if (!string.IsNullOrWhiteSpace(card.Comments))
{
var comment = new BoardCardHistory
{
BoardCardId = card.Id,
Title = "Comment",
Text = card.Comments,
PersonId = _appAuthState.GetCurrentUser().Id,
Date = DateTime.Now,
};
await _boardCardHistoryRepository.CreateAsync(comment);
}
}
public async Task<int> CreateCompanyAsync(Company company)
{
return await _companyRepository.CreateAsync(company);
}
public async Task<int> CreateClientCompanyAsync(ClientCompany clientCompany)
{
return await _clientCompanyRepository.CreateAsync(clientCompany);
}
public async Task UpdateClientCompanyAsync(ClientCompany clientCompany)
{
await _clientCompanyRepository.UpdateAsync(clientCompany);
}
public async Task<ClientCompany> FindClientCompanyAsync(int companyId)
{
return await _clientCompanyRepository.FindByCompanyIdAsync(companyId);
}
}
}
如您所见,某些操作(例如GetBoardCardsAsync使用多个存储库),并且需要了解如何使用每个存储库以及数据库实体是什么。
因此,这种封装使我们能够简化UI代码,使其对数据访问层的依赖性降低,从而提高代码的可维护性和可扩展性。
考虑可维护性/可扩展性很重要,因为许多项目根本无法完成,因为在添加新功能或更改现有功能时会产生越来越多的错误。
总结
在这篇文章中,我介绍了使用BlazorForms开源框架实现的CrmLight种子项目领导板功能。它显示了有关如何连接数据库存储库、应用程序业务逻辑和用户界面的简单方法。
BlazorForms的主要范例是将解决方案的逻辑部分与物理UI呈现分离。BlazorForms鼓励开发人员首先规划解决方案,并考虑实体、关系、数据访问、模型和业务逻辑,而不是UI控件和事件编码。
当前版本BlazorForms 0.8.2包含这些更改,你可以将CrmLight种子项目用作项目的基础或代码示例。
Contact Us - Pro Coders
https://www.codeproject.com/Articles/5353131/BlazorForms-Low-Code-Open-Source-Framework-Part-3