Есть простая база данных клиентов. В ней 2 таблицы, в одной из которых клиент с информацией и 2 таблица с его почтовыми ящиками. Внешний ключ связывает эти таблицы по id
клиента. Есть такой код добавления на бекенде в базу данных:
public async Task<Unit> Handle(CreateCommand request, CancellationToken cancellationToken)
{
var entity = new Customer
{
CompanyName = request.CompanyName,
Country = request.Country,
City = request.City,
Address = request.Address,
ZipCode = request.ZipCode,
};
_context.Customers.Add(entity);
await _context.SaveChangesAsync(cancellationToken);
foreach (var createCustEmail in request.Emails)
{
_context.CustomerEmails.Add(new CustomerEmail()
{
CustomerId = entity.CustomerId,
Email = createCustEmail.Email,
});
}
await _context.SaveChangesAsync(cancellationToken);
return Unit.Value;
}
Вопрос заключается в том,как сделать добавление емейлов на стороне клиента в Razor Pages?
У меня есть несколько разных солюшенов, которые отрабатывают разные аспекты мультиформ, вопрос реально достаточно объёмный, поэтому вам рекомендую сделать так же: прежде, чем делать реальное приложение сделать несколько тестовых, чтобы поиграться и посмотреть отдельные аспекты.
Во-первых, вы можете начать с приложения в котором у вас модель емейла будет представлять простую строку:
public class Customer
{
public int Id { get; set; }
public string Title { get; set; }
public string[] Phones { get; set; }
}
Это иногда очень удобно, в этом случае рекомендую посмотреть как хранить подобные структуры в базе используя Entity Framework.
Мы же будем начинать с двух полноценных моделей (т.е. телефону нужно хранить не только сам номер, но и допустим тип - рабочий/домашний/факс)
Модель контакта (мысленно можете заменять на Customer):
public class Contact
{
public Contact()
{
this.Phones = new List<Phone>();
}
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public virtual ICollection<Phone> Phones { get; set; }
}
Модель телефона (показаны только ключевые поля):
public class Phone
{
public int Id { get; set; }
public int ContactId { get; set; }
public virtual Contact Contact { get; set; }
public string Number { get; set; }
}
Далее тупо создаём CRUD-контроллер, используя scaffolding. Вы наверное многократно видели подобные заготовки, в них нет ничего сложного, но увы, они не умеют в мультиэлементы.
Что нужно сделать. Во-первых, добавить в форму блок для отображения div'а с телефонными номерами:
<div id="phoneList">
@Html.EditorFor(x => x.Phones)
<p>
<a href="javascript:void(0);" class="addRowPhone"><span class="glyphicon glyphicon-plus"></span>Добавить номер</a>
</p>
</div>
Целиком файл формы Create:
@model WebApplication1.Models.Contact
@{
ViewBag.Title = "Create";
}
<h2>Create</h2>
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Contact</h4>
<hr />
@Html.ValidationSummary(true, "", new { @class = "text-danger" })
<div class="form-group">
@Html.LabelFor(model => model.FirstName, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.FirstName, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.FirstName, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.LastName, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.LastName, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.LastName, "", new { @class = "text-danger" })
</div>
</div>
<div id="phoneNumbers">
@Html.EditorFor(x => x.Phones)
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
@Html.ActionLink("Back to List", "Index")
</div>
@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
}
Чтобы эта конструкция заработала - нужно добавить папку Views\Contacts\EditorTemplates и в ней расположить файл Phone.cshtml (который и будет определять как отобразить наш элемент редактирования:
@model WebApplication1.Models.Phone
<div class="phoneRow">
@Html.HiddenFor(x => x.Id)
@Html.HiddenFor(x => x.ContactId)
<p>
<label>Phone Number</label>
@Html.TextBoxFor(x => x.Number)
</p>
<a href="javascript:void(0);" class="remRowPhone"><span class="glyphicon glyphicon glyphicon-trash"></span>Удалить</a>
</div>
Итого, получается что-то типа:
И выглядит как-то так (стилями css не украшал, так как проект учебный, поэтому выглядит страшненько):
Почитайте букварь по этой теме, чтобы понимать, как это работает: Переопределение шаблонов отображения и редактирования
В asp.net core принято создавать формы используя тэг-хелперы и аналога для Html.EditorFor я не знаю, но когда я смешал два стиля и прописал Html.EditorFor - оно сработало. Ну это просто некрасиво, слишком разнородный стиль.
Но основная проблема связана с тем, что шаблоны DisplayTemplates/EditorTemplates для razor pages не работают, даже в 2.1 - по крайней мере когда я пробовал и в /Pages и в /Views/Shared разные варианты не сработал ни один, увы ;(
Поэтому я и показывать стал на классическом asp'е, тем более что и проекты есть готовые ;)
Какие подводные камни вас ещё ждут. Когда вы получаете данные с формы - всё у вас хорошо, но когда вы начинаете сохранять телефоны при редактировании - вам нужно определять какие из телефонов были удалены, какие добавлены и какие изменились -- и соответственно, удалённые - удалить из базы, новые добавить, а остальные изменить.
Это отдельная большая тема, которая выпивает множество крови. Я вас отправлю к вот этому и этому вопросу, но будьте готовы потратить несколько дней на эту коварную тему, в этой ветке я просто обозначаю проблему.
При помощи GraphDiff у вас получится сократить код до примерно такого:
// POST: Contacts/Edit/5
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(Contact contact)
{
if (!this.ModelState.IsValid)
return this.View(contact);
var entity = db.Contacts.Include(x=>x.Phones).SingleOrDefault(x => x.Id == contact.Id);
if (entity == null)
return this.HttpNotFound();
this.db.UpdateGraph(contact, map => map.OwnedCollection(p => p.Phones));
this.db.SaveChanges();
return this.RedirectToAction("Index");
}
А на ванильном c# код будет раза в четыре больше.
Другой момент будет как смапить ваши классы на MediatR, это смотрите сами по конкретному проекту.
И ещё один момент я упустил - javascript на кнопочках добавления/удаления записей. Я его в тестовом проекте не делал, да и он различается для ванильного js/jQuery/vue/etc - поэтому пишите свой по месту.
Айфон мало держит заряд, разбираемся с проблемой вместе с AppLab
Перевод документов на английский язык: Важность и ключевые аспекты
Подскажите пожалуйста какие библиотеки существуют для чтения/записи данных в excel документ?
Нужно сделать, чтобы пользователь мог выполнять C#-код на моем сервере (что-то вроде dotnetfiddlenet)