Commit ea32df3f authored by TTrueBenji's avatar TTrueBenji

Добавить валидацию.

***
- Добавил валидацию полей формы создания и редактирования телефона.
- Пофиксил ошибку сравнения строк при проверке названия телефона.
- Изменил форму добавления телефона.
- Вынес данные о производителе телефонов в отдельную таблицу.
- Вынес форму добавления телефона в частичное представление.
- Добавил клиентскую валидацию полей в формах создания и редактирования данных телефона.
***
parent d2c688e2
using Microsoft.AspNetCore.Mvc;
using PhoneStore.Models;
using PhoneStore.ViewModels;
namespace PhoneStore.Controllers
{
public class BrandsController : Controller
{
private readonly MobileContext _db;
public BrandsController(MobileContext db)
{
_db = db;
}
[HttpGet]
public IActionResult Create()
{
return View();
}
[HttpPost]
public IActionResult Create(CreateBrandViewModel model)
{
_db.Brands.Add(new Brand
{
Name = model.Name
});
_db.SaveChanges();
return RedirectToAction("Index", "Phones");
}
}
}
\ No newline at end of file
using System;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using PhoneStore.Models;
namespace PhoneStore.Controllers
{
public class PhoneValidatorController : Controller
{
private readonly MobileContext _db;
public PhoneValidatorController(MobileContext db)
{
_db = db;
}
[AcceptVerbs("GET", "POST")]
public bool CheckName(string name)
{
return !_db.Phones
.AsEnumerable()
.Any(p => p.Name.Equals(name, StringComparison.CurrentCulture));
}
}
}
\ No newline at end of file
using System; using System;
using System.Collections;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using PhoneStore.Models; using PhoneStore.Models;
using PhoneStore.Services; using PhoneStore.Services;
...@@ -29,35 +32,54 @@ namespace PhoneStore.Controllers ...@@ -29,35 +32,54 @@ namespace PhoneStore.Controllers
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
[HttpGet] [HttpGet]
public IActionResult Index(int? id, string? name) public IActionResult Index(int? brandId, string name)
{ {
var phones = _db.Phones.AsQueryable();
if (id.HasValue)
{
phones = _db.Phones.Where(p => p.Id == id);
}
if (!string.IsNullOrEmpty(name))
{
phones = phones.Where(p => string.Equals(p.Name, name, StringComparison.CurrentCultureIgnoreCase));
}
return View(phones.ToList()); IQueryable<Phone> phones = _db.Phones
.Include(p => p.Brand)
.AsQueryable();
IEnumerable<Brand> brands = _db.Brands;
if (brandId is > 0)
phones = _db.Phones.Where(p => p.BrandId == brandId);
return View(new IndexViewModel
{
Brands = brands,
Phones = phones.ToList()
});
} }
[HttpGet] [HttpGet]
public IActionResult Create() public IActionResult Create()
{ {
return View(); PhoneCreateViewModel model = new PhoneCreateViewModel
{
Brands = _db.Brands.ToList()
};
return View(model);
} }
[HttpPost] [HttpPost]
public IActionResult Create(Phone phone) public IActionResult Create(PhoneCreateViewModel model)
{
if (ModelState.IsValid)
{ {
_db.Phones.Add(phone); _db.Phones.Add(new Phone
{
Name = model.Name,
Price = (decimal)model.Price!,
BrandId = model.BrandId
});
_db.SaveChanges(); _db.SaveChanges();
return RedirectToAction("Index"); return RedirectToAction("Index");
} }
model.Brands = _db.Brands.ToList();
return View("Create", model);
}
[HttpGet] [HttpGet]
public IActionResult Delete(int phoneId) public IActionResult Delete(int phoneId)
{ {
...@@ -89,7 +111,15 @@ namespace PhoneStore.Controllers ...@@ -89,7 +111,15 @@ namespace PhoneStore.Controllers
{ {
return BadRequest(); return BadRequest();
} }
return View(phone); PhoneCreateViewModel model = new PhoneCreateViewModel
{
Id = phone.Id,
Name = phone.Name,
Price = phone.Price,
BrandId = (int)phone.BrandId,
Brands = _db.Brands.ToList()
};
return View(model);
} }
[HttpPost] [HttpPost]
......
// <auto-generated />
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using PhoneStore.Models;
namespace PhoneStore.Migrations
{
[DbContext(typeof(MobileContext))]
[Migration("20220630102229_BrandEntity")]
partial class BrandEntity
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "5.0.17");
modelBuilder.Entity("PhoneStore.Models.Basket", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("PhoneId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("PhoneId");
b.ToTable("Baskets");
});
modelBuilder.Entity("PhoneStore.Models.Brand", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Name")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Brands");
});
modelBuilder.Entity("PhoneStore.Models.Order", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Address")
.HasColumnType("TEXT");
b.Property<string>("ContactPhone")
.HasColumnType("TEXT");
b.Property<int>("PhoneId")
.HasColumnType("INTEGER");
b.Property<string>("UserName")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("PhoneId");
b.ToTable("Orders");
});
modelBuilder.Entity("PhoneStore.Models.Phone", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("BrandId")
.HasColumnType("INTEGER");
b.Property<string>("Name")
.HasColumnType("TEXT");
b.Property<decimal>("Price")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("BrandId");
b.ToTable("Phones");
});
modelBuilder.Entity("PhoneStore.Models.Basket", b =>
{
b.HasOne("PhoneStore.Models.Phone", "Phone")
.WithMany()
.HasForeignKey("PhoneId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Phone");
});
modelBuilder.Entity("PhoneStore.Models.Order", b =>
{
b.HasOne("PhoneStore.Models.Phone", "Phone")
.WithMany()
.HasForeignKey("PhoneId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Phone");
});
modelBuilder.Entity("PhoneStore.Models.Phone", b =>
{
b.HasOne("PhoneStore.Models.Brand", "Brand")
.WithMany()
.HasForeignKey("BrandId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Brand");
});
#pragma warning restore 612, 618
}
}
}
using Microsoft.EntityFrameworkCore.Migrations;
namespace PhoneStore.Migrations
{
public partial class BrandEntity : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Company",
table: "Phones");
migrationBuilder.AddColumn<int>(
name: "BrandId",
table: "Phones",
type: "INTEGER",
nullable: false,
defaultValue: 0);
migrationBuilder.CreateTable(
name: "Brands",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
Name = table.Column<string>(type: "TEXT", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Brands", x => x.Id);
});
migrationBuilder.CreateIndex(
name: "IX_Phones_BrandId",
table: "Phones",
column: "BrandId");
migrationBuilder.AddForeignKey(
name: "FK_Phones_Brands_BrandId",
table: "Phones",
column: "BrandId",
principalTable: "Brands",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Phones_Brands_BrandId",
table: "Phones");
migrationBuilder.DropTable(
name: "Brands");
migrationBuilder.DropIndex(
name: "IX_Phones_BrandId",
table: "Phones");
migrationBuilder.DropColumn(
name: "BrandId",
table: "Phones");
migrationBuilder.AddColumn<string>(
name: "Company",
table: "Phones",
type: "TEXT",
nullable: true);
}
}
}
...@@ -31,6 +31,20 @@ namespace PhoneStore.Migrations ...@@ -31,6 +31,20 @@ namespace PhoneStore.Migrations
b.ToTable("Baskets"); b.ToTable("Baskets");
}); });
modelBuilder.Entity("PhoneStore.Models.Brand", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Name")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Brands");
});
modelBuilder.Entity("PhoneStore.Models.Order", b => modelBuilder.Entity("PhoneStore.Models.Order", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")
...@@ -62,8 +76,8 @@ namespace PhoneStore.Migrations ...@@ -62,8 +76,8 @@ namespace PhoneStore.Migrations
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<string>("Company") b.Property<int>("BrandId")
.HasColumnType("TEXT"); .HasColumnType("INTEGER");
b.Property<string>("Name") b.Property<string>("Name")
.HasColumnType("TEXT"); .HasColumnType("TEXT");
...@@ -73,6 +87,8 @@ namespace PhoneStore.Migrations ...@@ -73,6 +87,8 @@ namespace PhoneStore.Migrations
b.HasKey("Id"); b.HasKey("Id");
b.HasIndex("BrandId");
b.ToTable("Phones"); b.ToTable("Phones");
}); });
...@@ -97,6 +113,17 @@ namespace PhoneStore.Migrations ...@@ -97,6 +113,17 @@ namespace PhoneStore.Migrations
b.Navigation("Phone"); b.Navigation("Phone");
}); });
modelBuilder.Entity("PhoneStore.Models.Phone", b =>
{
b.HasOne("PhoneStore.Models.Brand", "Brand")
.WithMany()
.HasForeignKey("BrandId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Brand");
});
#pragma warning restore 612, 618 #pragma warning restore 612, 618
} }
} }
......
namespace PhoneStore.Models
{
public class Brand
{
public int Id { get; set; }
public string Name { get; set; }
}
}
\ No newline at end of file
...@@ -7,6 +7,7 @@ namespace PhoneStore.Models ...@@ -7,6 +7,7 @@ namespace PhoneStore.Models
public DbSet<Phone> Phones { get; set; } public DbSet<Phone> Phones { get; set; }
public DbSet<Order> Orders { get; set; } public DbSet<Order> Orders { get; set; }
public DbSet<Basket> Baskets { get; set; } public DbSet<Basket> Baskets { get; set; }
public DbSet<Brand> Brands { get; set; }
public MobileContext(DbContextOptions<MobileContext> options) : base(options) public MobileContext(DbContextOptions<MobileContext> options) : base(options)
{ {
......
...@@ -4,7 +4,8 @@ namespace PhoneStore.Models ...@@ -4,7 +4,8 @@ namespace PhoneStore.Models
{ {
public int Id { get; set; } public int Id { get; set; }
public string Name { get; set; } public string Name { get; set; }
public string Company { get; set; }
public decimal Price { get; set; } public decimal Price { get; set; }
public int? BrandId { get; set; }
public Brand Brand { get; set; }
} }
} }
\ No newline at end of file
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
<ItemGroup> <ItemGroup>
<Folder Include="Migrations" /> <Folder Include="Migrations" />
<Folder Include="Pages" />
</ItemGroup> </ItemGroup>
</Project> </Project>
namespace PhoneStore.ViewModels
{
public class CreateBrandViewModel
{
public string Name { get; set; }
}
}
\ No newline at end of file
using System.Collections;
using System.Collections.Generic;
using PhoneStore.Models;
namespace PhoneStore.ViewModels
{
public class IndexViewModel
{
public IEnumerable<Phone> Phones { get; set; }
public IEnumerable<Brand> Brands { get; set; }
}
}
\ No newline at end of file
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc;
using PhoneStore.Models;
namespace PhoneStore.ViewModels
{
public class PhoneCreateViewModel
{
public int Id { get; set; }
[Required(ErrorMessage = "Поле обязательно для заполнения")]
[Remote("CheckName", "PhoneValidator", ErrorMessage = "Имя занято")]
[StringLength(50, MinimumLength = 3, ErrorMessage = "Минимальная длина 3 символа, максимальная - 50")]
public string Name { get; set; }
[Required(ErrorMessage = "Поле обязательно для заполнения")]
[Range(1000, 50000, ErrorMessage = "Значение не валидно, вводить можно знаение от 1000 до 5000")]
public decimal? Price { get; set; }
public int BrandId { get; set; }
public List<Brand> Brands { get; set; }
}
}
\ No newline at end of file
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
<tr> <tr>
<td>@basket.PhoneId</td> <td>@basket.PhoneId</td>
<td>@basket.Phone.Name</td> <td>@basket.Phone.Name</td>
<td>@basket.Phone.Company</td> <td>@basket.Phone.Brand.Name</td>
<td>@basket.Phone.Price</td> <td>@basket.Phone.Price</td>
<td><a asp-route-id="@basket.PhoneId" asp-action="Remove" asp-controller="Baskets" class="btn btn-outline-danger">Удалить из корзины</a></td> <td><a asp-route-id="@basket.PhoneId" asp-action="Remove" asp-controller="Baskets" class="btn btn-outline-danger">Удалить из корзины</a></td>
</tr> </tr>
......
@model CreateBrandViewModel
@{
ViewBag.Title = "Добавление бренда";
Layout = "_Layout";
}
<h2>Заполните форму</h2>
<div class="row">
<div class="phone_add_form col-md-6">
<form asp-action="Create" asp-controller="Brands" method="post">
<div class="form_row">
<label for="name">
Наименование
<input id="name" type="text" asp-for="Name">
</label>
<button class="btn btn-outline-warning">Отправить</button>
</div>
</form>
</div>
</div>
\ No newline at end of file
@model Phone @model PhoneCreateViewModel
@{ @{
ViewBag.Title = "title"; ViewBag.Title = "title";
...@@ -9,21 +9,18 @@ ...@@ -9,21 +9,18 @@
<div class="row"> <div class="row">
<div class="phone_add_form col-md-6"> <div class="phone_add_form col-md-6">
<form asp-action="Create" asp-controller="Phones" method="post"> <form asp-action="Create" asp-controller="Phones" method="post">
<div class="form_row"> @{
<label for="name"> await Html.RenderPartialAsync("PartialViews/PhoneFormPartialView", Model);
Наименование }
<input id="name" type="text" asp-for="Name">
</label>
<label for="company">
Производитель
<input id="company" type="text" asp-for="Company">
</label>
<label for="price">
Стоимость
<input id="price" type="text" asp-for="Price">
</label>
<button class="btn btn-outline-warning">Отправить</button> <button class="btn btn-outline-warning">Отправить</button>
</div>
</form> </form>
</div> </div>
</div> </div>
@section Scripts
{
@{
await Html.RenderPartialAsync("_ValidationScriptsPartial");
}
}
@model Phone @model PhoneCreateViewModel
@{ @{
ViewBag.Title = "Редактирование"; ViewBag.Title = "Редактирование";
...@@ -10,9 +10,15 @@ ...@@ -10,9 +10,15 @@
@{ @{
await Html.RenderPartialAsync("PartialViews/PhoneFormPartialView"); await Html.RenderPartialAsync("PartialViews/PhoneFormPartialView");
} }
<input type="text" hidden asp-for="Id"> <input type="text" hidden asp-for="@Model.Id">
<button type="submit" <button type="submit"
class="btn btn-outline-warning">Изменить</button> class="btn btn-outline-warning">Изменить</button>
</form> </form>
</div> </div>
</div> </div>
@section Scripts
{
@{
await Html.RenderPartialAsync("_ValidationScriptsPartial");
}
}
\ No newline at end of file
@model List<Phone> @model IndexViewModel
@{ @{
ViewBag.Title = "Смартфоны"; ViewBag.Title = "Смартфоны";
Layout = "_Layout"; Layout = "_Layout";
} }
@if (Model.Count == 0) @if (Model.Phones.Count() == 0)
{ {
<h2>Список пуст.</h2> <h2>Список пуст.</h2>
} }
else else
{ {
<form asp-action="Index" asp-controller="Phones" method="get">
<select name="brandId" class="form-control">
<option>Выберите бренд</option>
@foreach (var brand in Model.Brands)
{
<option value="@brand.Id">@brand.Name</option>
}
</select>
<button type="submit" class="btn btn-outline-warning">Отправить</button>
</form>
<h2>Список устройств</h2> <h2>Список устройств</h2>
<table style="width: 100%"> <table style="width: 100%">
<tr> <tr>
...@@ -20,12 +30,12 @@ else ...@@ -20,12 +30,12 @@ else
<th></th> <th></th>
<th></th> <th></th>
</tr> </tr>
@foreach (var phone in Model) @foreach (var phone in Model.Phones)
{ {
<tr> <tr>
<td><img class="phone_img" src="" alt="картинка паламатая"/></td> <td><img class="phone_img" src="" alt="картинка паламатая"/></td>
<td>@phone.Name</td> <td>@phone.Name</td>
<td>@phone.Company</td> <td>@phone.Brand.Name</td>
<td>@phone.Price</td> <td>@phone.Price</td>
<td> <td>
<div class="dropdown"> <div class="dropdown">
...@@ -70,3 +80,4 @@ else ...@@ -70,3 +80,4 @@ else
<div class="d-flex justify-content-end my-5"> <div class="d-flex justify-content-end my-5">
<a class="btn btn-outline-info" asp-action="Create" asp-controller="Phones">Добавить устройство</a> <a class="btn btn-outline-info" asp-action="Create" asp-controller="Phones">Добавить устройство</a>
</div> </div>
@model Phone @model PhoneCreateViewModel
<label for="name"> <div class="mb-3">
Наименование <label asp-for="Name" class="form-label">Наименование</label>
<input id="name" type="text" asp-for="Name"> <input type="text" class="form-control" asp-for="Name">
</label> <div class="form-text">
<label for="company"> <span asp-validation-for="Name" class="text-danger"></span>
Производитель </div>
<input id="company" type="text" asp-for="Company"> </div>
</label> <div class="mb-3">
<label for="price"> <label asp-for="Price" class="form-label">Стоимость</label>
Стоимость <input type="text" class="form-control" asp-for="Price">
<input id="price" type="text" asp-for="Price"> <div class="form-text">
</label> <span asp-validation-for="Price" class="text-danger"></span>
\ No newline at end of file </div>
</div>
<div class="mb-3">
<label asp-for="BrandId" class="form-label">Производитель</label>
<select asp-for="BrandId" class="form-select">
@foreach (var brand in Model.Brands)
{
<option value="@brand.Id">@brand.Name</option>
}
</select>
</div>
...@@ -24,6 +24,9 @@ ...@@ -24,6 +24,9 @@
<li class="nav-item"> <li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Orders" asp-action="Index">Заказы</a> <a class="nav-link text-dark" asp-area="" asp-controller="Orders" asp-action="Index">Заказы</a>
</li> </li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Brands" asp-action="Create">Добавить бренд</a>
</li>
</ul> </ul>
</div> </div>
</div> </div>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment