Добавил проект

parents
bin/
obj/
/packages/
riderModule.iml
/_ReSharper.Caches/
.idea
\ No newline at end of file

Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PhoneStore", "PhoneStore\PhoneStore.csproj", "{32DC0801-63D0-4261-9F07-8DD570B35335}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{32DC0801-63D0-4261-9F07-8DD570B35335}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{32DC0801-63D0-4261-9F07-8DD570B35335}.Debug|Any CPU.Build.0 = Debug|Any CPU
{32DC0801-63D0-4261-9F07-8DD570B35335}.Release|Any CPU.ActiveCfg = Release|Any CPU
{32DC0801-63D0-4261-9F07-8DD570B35335}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using PhoneStore.Enums;
using PhoneStore.Helpers;
using PhoneStore.Services.Abstractions;
using PhoneStore.ViewModels.Account;
namespace PhoneStore.Controllers
{
public class AccountController : Controller
{
private readonly IAccountService _accountService;
public AccountController(IAccountService accountService)
{
_accountService = accountService;
}
[HttpGet]
public IActionResult Register()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Register(RegisterViewModel model)
{
if (!ModelState.IsValid)
return View(model);
var result = await _accountService.Register(model);
if (result.StatusCodes == StatusCodes.Success)
return RedirectToAction("Index", "Phones");
if (result.ErrorMessages.Any())
ModelState.AddErrors(result.ErrorMessages);
return View(model);
}
[HttpGet]
public IActionResult Login(string returnUrl = null)
{
return View(new LoginViewModel { ReturnUrl = returnUrl });
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginViewModel model)
{
if (!ModelState.IsValid)
return View(model);
var result = await _accountService.LogIn(model);
if (result.StatusCodes == StatusCodes.Success)
{
if (!string.IsNullOrEmpty(model.ReturnUrl) && Url.IsLocalUrl(model.ReturnUrl))
return Redirect(model.ReturnUrl);
return RedirectToAction("Index", "Phones");
}
if (result.ErrorMessages.Any())
ModelState.AddErrors(result.ErrorMessages);
return View(model);
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> LogOff()
{
await _accountService.LogOf();
return RedirectToAction("Index", "Phones");
}
}
}
\ No newline at end of file
using Microsoft.AspNetCore.Mvc;
using PhoneStore.Enums;
using PhoneStore.Services.Abstractions;
using PhoneStore.ViewModels;
namespace PhoneStore.Controllers
{
public class BasketsController : Controller
{
private readonly IBasketService _basketService;
public BasketsController(IBasketService basketService)
{
_basketService = basketService;
}
[HttpGet]
public IActionResult Add(int id)
{
var result = _basketService.AddPhone(id);
ResponseMessageViewModel response = result switch
{
StatusCodes.Success => new ResponseMessageViewModel {Message = "Продукт успешно добавлен"},
StatusCodes.EntityNotFound => new ResponseMessageViewModel {Message = "Продукт не найден"},
_ => new ResponseMessageViewModel {Message = "Продукт не найден"}
};
response.Status = result;
return View(response);
}
public IActionResult Index()
{
var baskets = _basketService.GetAll();
return View(baskets);
}
[HttpGet]
public IActionResult Remove(int id)
{
_basketService.Remove(id);
return RedirectToAction("Index");
}
}
}
\ No newline at end of file
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.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using PhoneStore.ViewModels;
namespace PhoneStore.Controllers
{
public class ErrorsController : Controller
{
private const string OppsMessage = "Oops... Ошибка";
private readonly Dictionary<int, ErrorViewModel> _errorResolver;
public ErrorsController()
{
_errorResolver = new Dictionary<int, ErrorViewModel>();
_errorResolver.Add(404, new ErrorViewModel
{
StatusCode = 404,
Message = "Ресурс не найден",
Title = OppsMessage
});
_errorResolver.Add(400, new ErrorViewModel
{
StatusCode = 400,
Message = "Сервер не смог обработать запрос",
Title = OppsMessage
});
_errorResolver.Add(500, new ErrorViewModel
{
StatusCode = 500,
Message = "Сервер не смог обработать запрос",
Title = OppsMessage
});
_errorResolver.Add(777, new ErrorViewModel
{
StatusCode = 777,
Message = "Сущность не найдена",
Title = OppsMessage
});
_errorResolver.Add(666, new ErrorViewModel
{
StatusCode = 666,
Message = "Такой файл уже существует. Возможно вы добавляете смартфон, который уже есть в базе данных.",
Title = OppsMessage
});
}
[Route("Error/{statusCode}")]
[ActionName("Error")]
public IActionResult HttpStatusCodeHandler(int statusCode)
{
if (_errorResolver.ContainsKey(statusCode))
{
return View(_errorResolver[statusCode]);
}
return View(_errorResolver[404]);
}
}
}
\ No newline at end of file
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using PhoneStore.Models;
namespace PhoneStore.Controllers
{
public class OrdersController : Controller
{
private readonly MobileContext _db;
public OrdersController(MobileContext db)
{
_db = db;
}
// GET
public IActionResult Index()
{
var orders = _db.Orders
.Include(p => p.Phone)
.ToList();
return View(orders);
}
[HttpGet]
public IActionResult Create(int phoneId)
{
var phone = _db.Phones.FirstOrDefault(p => p.Id == phoneId);
if (phone is null)
return RedirectToAction("Error", "Errors", new {statusCode = 777});
Order order = new Order
{
Phone = phone
};
return View(order);
}
[HttpPost]
public IActionResult Create(Order order)
{
_db.Orders.Add(order);
_db.SaveChanges();
return RedirectToAction("Index");
}
}
}
\ 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.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using PhoneStore.Helpers;
using PhoneStore.Models;
using PhoneStore.Services.Abstractions;
using PhoneStore.ViewModels;
namespace PhoneStore.Controllers
{
public class PhonesController : Controller
{
private readonly MobileContext _db;
private readonly IPhoneService _phoneService;
public PhonesController(
MobileContext db,
IPhoneService phoneService)
{
_db = db;
_phoneService = phoneService;
}
[HttpGet]
public IActionResult Index(int? brandId)
{
IEnumerable<Brand> brands = _db.Brands;
IQueryable<Phone> phones = _db.Phones.AsQueryable();
if (brandId is > 0)
phones = _db.Phones.Where(p => p.BrandId == brandId);
return View(new IndexViewModel
{
Brands = brands,
Phones = phones.ToList()
});
}
[HttpGet]
public IActionResult Create()
{
PhoneCreateViewModel model = new PhoneCreateViewModel
{
Brands = _db.Brands.ToList()
};
return View(model);
}
[HttpPost]
public async Task<IActionResult> Create(PhoneCreateViewModel model)
{
try
{
if (ModelState.IsValid)
{
await _phoneService.CreateAsync(model);
return RedirectToAction("Index");
}
model.Brands = _db.Brands.ToList();
return View("Create", model);
}
//TODO: Добавить кастомный exception
catch (FileNotFoundException)
{
return RedirectToAction("Error", "Errors", new {statusCode = 666});
}
catch(Exception)
{
return RedirectToAction("Error", "Errors", new {statusCode = 777});
}
}
[HttpGet]
public IActionResult Delete(int phoneId)
{
var phone = _db.Phones.FirstOrDefault(p => p.Id == phoneId);
if (phone is null)
return RedirectToAction("Error", "Errors", new {statusCode = 777});
return View(phone);
}
[HttpPost]
[ActionName("Delete")]
public IActionResult Confirm(int phoneId)
{
//TODO: Добавить удаление файла изображения.
var phone = _db.Phones.FirstOrDefault(p => p.Id == phoneId);
if (phone is null)
return BadRequest();
_db.Phones.Remove(phone);
_db.SaveChanges();
return RedirectToAction("Index");
}
[HttpGet]
public IActionResult Edit(int phoneId)
{
var brands = _db.Brands.ToList();
var phone = _db.Phones.FirstOrDefault(p => p.Id == phoneId);
if (phone is null)
return RedirectToAction("Error", "Errors", new {statusCode = 777});
PhoneCreateViewModel model = new PhoneCreateViewModel
{
Id = phone.Id,
Name = phone.Name,
Price = phone.Price,
BrandId = (int)phone.BrandId,
Image = phone.Image,
Brands = brands
};
return View(model);
}
//TODO: Доделать
[HttpPost]
public IActionResult Edit(PhoneCreateViewModel model)
{
if (model is null)
return RedirectToAction("Error", "Errors", new {statusCode = 777});
string imagePath = string.Empty;
if (model.File is null)
imagePath = model.Image;
_db.Phones.Update(model.MapToPhone(imagePath));
_db.SaveChanges();
return RedirectToAction("Index");
}
[HttpGet]
public IActionResult About(int? phoneId)
{
if (!phoneId.HasValue) return RedirectToAction("Error", "Errors", new {statusCode = 777});
var phone = _db.Phones
.Include(p => p.Brand)
.Include(p => p.Feedbacks)
.ThenInclude(f => f.User)
.FirstOrDefault(p => p.Id == phoneId);
if (phone is null)
return RedirectToAction("Error", "Errors", new {statusCode = 777});
var phoneViewModel = new PhoneViewModel
{
Brand = phone.Brand,
Feedbacks = phone.Feedbacks.Select(f => new FeedbackViewModel
{
Id = f.Id,
Phone = f.Phone,
Text = f.Text,
User = f.User,
CreationDateTime = f.CreationDateTime,
UserId = f.UserId,
PhoneId = f.PhoneId
})
.OrderByDescending(f => f.CreationDateTime)
.ToList(),
Image = phone.Image,
Name = phone.Name,
Price = phone.Price,
Id = phone.Id
};
return View(phoneViewModel);
}
}
}
\ No newline at end of file
using System.Collections.Generic;
using Microsoft.AspNetCore.Identity;
using PhoneStore.Enums;
namespace PhoneStore.DataObjects
{
public class IdentityResult
{
public List<string> ErrorMessages { get; set; }
public StatusCodes StatusCodes { get; set; }
}
}
\ No newline at end of file
namespace PhoneStore.Enums
{
public enum Order
{
NameAsc,
NameDesc,
AgeAsc,
AgeDesc
}
}
\ No newline at end of file
namespace PhoneStore.Enums
{
public enum StatusCodes
{
Success = 200,
Error = 500,
EntityNotFound = 400
}
}
\ No newline at end of file
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace PhoneStore.Helpers
{
public static class ModelStateHelper
{
public static void AddErrors(this ModelStateDictionary self, List<string> errors)
{
errors.ForEach(e =>
{
self.AddModelError(string.Empty, e);
});
}
}
}
\ No newline at end of file
using PhoneStore.Models;
using PhoneStore.ViewModels;
namespace PhoneStore.Helpers
{
public static class PhoneMapper
{
public static Phone MapToPhone(this PhoneCreateViewModel self, string imagePath = null)
{
Phone phone = new Phone
{
Name = self.Name,
Price = (decimal) self.Price!,
BrandId = self.BrandId,
Image = imagePath,
Id = self.Id
};
return phone;
}
}
}
\ No newline at end of file
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using PhoneStore.Models;
using PhoneStore.Services;
using PhoneStore.Services.Abstractions;
namespace PhoneStore.Helpers
{
public static class ServiceConnector
{
public static void AddApplicationServices(this IServiceCollection services, IConfiguration configuration)
{
services.AddScoped<IBasketService, BasketService>();
services.AddTransient<UploadService>();
services.AddTransient<IDefaultPhoneImagePathProvider>(_ =>
new DefaultPhoneImagePathProvider(configuration["PathToDefaultAvatar:Path"]));
services.AddTransient<IPhoneService, PhoneService>();
// services.AddTransient<IUserService, UserService>();
services.AddTransient<IUsersSortService, UsersSortService>();
services.AddTransient<IUsersFilter, UsersFilter>();
services.AddTransient<IPaginationService<User>, PaginationService<User>>();
services.AddTransient<IAccountService, AccountService>();
}
}
}
\ No newline at end of file
using System.Linq;
using PhoneStore.Models;
using PhoneStore.ViewModels;
namespace PhoneStore.Helpers
{
public static class UserHelper
{
public static UsersViewModel MapToUsersViewModel(
this IQueryable<User> self,
string filter,
int page,
int count,
int pageSize)
{
return new UsersViewModel
{
Users = self.ToArray(),
Filter = filter,
PageViewModel = new PageViewModel(page, count, pageSize)
};
}
}
}
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
using PhoneStore.ViewModels;
namespace PhoneStore.Models
{
public class Basket : BaseEntity
{
public int PhoneId { get; set; }
public virtual Phone Phone { get; set; }
}
}
\ No newline at end of file
namespace PhoneStore.Models
{
public class Brand
{
public int Id { get; set; }
public string Name { get; set; }
}
}
\ No newline at end of file
using System;
namespace PhoneStore.Models
{
public class Feedback
{
public int Id { get; set; }
public int UserId { get; set; }
public int PhoneId { get; set; }
public string Text { get; set; }
public DateTime CreationDateTime { get; set; }
public User User { get; set; }
public Phone Phone { get; set; }
}
}
\ No newline at end of file
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
namespace PhoneStore.Models
{
public class MobileContext : IdentityDbContext<User, Role, int>
{
public DbSet<Phone> Phones { get; set; }
public DbSet<Order> Orders { get; set; }
public DbSet<Basket> Baskets { get; set; }
public DbSet<Brand> Brands { get; set; }
public DbSet<Feedback> Feedbacks { get; set; }
public MobileContext(DbContextOptions<MobileContext> options) : base(options)
{
}
}
}
\ No newline at end of file
namespace PhoneStore.Models
{
public class Order
{
public int Id { get; set; }
public string UserName { get; set; }
public string ContactPhone { get; set; }
public string Address { get; set; }
public int PhoneId { get; set; }
public Phone Phone { get; set; }
}
}
\ No newline at end of file
using System.Collections.Generic;
namespace PhoneStore.Models
{
public class Phone
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public int? BrandId { get; set; }
public Brand Brand { get; set; }
public string Image { get; set; }
public List<Feedback> Feedbacks { get; set; }
}
}
\ No newline at end of file
using Microsoft.AspNetCore.Identity;
namespace PhoneStore.Models
{
public class Role : IdentityRole<int>
{
public Role() : base()
{
}
public Role(string roleName) : base(roleName)
{
}
}
}
\ No newline at end of file
using Microsoft.AspNetCore.Identity;
namespace PhoneStore.Models
{
public class User : IdentityUser<int>
{
public string Name { get; set; }
public int Age { get; set; }
}
}
\ No newline at end of file
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="5.0.17" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.17">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="5.0.10" />
</ItemGroup>
<ItemGroup>
<_ContentIncludedByDefault Remove="wwwroot\Files\book.pdf" />
</ItemGroup>
<ItemGroup>
<Folder Include="Migrations" />
</ItemGroup>
</Project>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using PhoneStore.Models;
using PhoneStore.Services;
namespace PhoneStore
{
public class Program
{
public static async Task Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
using var scope = host.Services.CreateScope();
var services = scope.ServiceProvider;
try
{
var userManager = services.GetRequiredService<UserManager<User>>();
RoleManager<Role> rolesManager = services.GetRequiredService<RoleManager<Role>>();
await AdminInitializerService.SeedAdminUser(rolesManager, userManager);
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "Не удалось добавить начальные данные в БД");
}
await host.RunAsync();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); });
}
}
\ No newline at end of file
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:43724",
"sslPort": 44365
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"PhoneStore": {
"commandName": "Project",
"dotnetRunMessages": "true",
"launchBrowser": true,
"applicationUrl": "https://localhost:5001;http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
using System.Collections.Generic;
using System.Threading.Tasks;
using PhoneStore.DataObjects;
using PhoneStore.Models;
using PhoneStore.ViewModels.Account;
namespace PhoneStore.Services.Abstractions
{
public interface IAccountService
{
Task<IdentityResult> Register(RegisterViewModel model);
Task<IdentityResult> LogIn(LoginViewModel model);
Task LogOf();
}
}
\ No newline at end of file
using System.Collections.Generic;
using PhoneStore.Enums;
using PhoneStore.Models;
namespace PhoneStore.Services.Abstractions
{
public interface IBasketService
{
StatusCodes AddPhone(int id);
List<Basket> GetAll();
void Remove(int id);
}
}
\ No newline at end of file
using System.Threading.Tasks;
namespace PhoneStore.Services.Abstractions
{
public interface ICreatable<in T> where T : class
{
Task CreateAsync(T entity);
}
}
\ No newline at end of file
namespace PhoneStore.Services.Abstractions
{
public interface IDefaultPhoneImagePathProvider
{
string GetPathToDefaultImage();
}
}
\ No newline at end of file
using System.Linq;
using System.Threading.Tasks;
namespace PhoneStore.Services.Abstractions
{
public interface IPaginationService<T>
{
Task<(IQueryable<T>, int)> GetABatchOfData(IQueryable<T> elements, int page, int pageSize);
}
}
\ No newline at end of file
using PhoneStore.ViewModels;
namespace PhoneStore.Services.Abstractions
{
public interface IPhoneService : ICreatable<PhoneCreateViewModel>
{
}
}
\ No newline at end of file
using System.Linq;
using PhoneStore.Models;
namespace PhoneStore.Services.Abstractions
{
public interface IUsersFilter
{
IQueryable<User> FilterByName(IQueryable<User> users, string filterByName);
}
}
\ No newline at end of file
using System.Linq;
using PhoneStore.Models;
using Order = PhoneStore.Enums.Order;
namespace PhoneStore.Services.Abstractions
{
public interface IUsersSortService
{
IQueryable<User> Sort(IQueryable<User> users, Order order);
}
}
\ No newline at end of file
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using PhoneStore.Enums;
using PhoneStore.Models;
using PhoneStore.Services.Abstractions;
using PhoneStore.ViewModels.Account;
using IdentityResult = PhoneStore.DataObjects.IdentityResult;
using SignInResult = Microsoft.AspNetCore.Identity.SignInResult;
namespace PhoneStore.Services
{
public class AccountService : IAccountService
{
private readonly UserManager<User> _userManager;
private readonly SignInManager<User> _signInManager;
public AccountService(
UserManager<User> userManager,
SignInManager<User> signInManager)
{
_userManager = userManager;
_signInManager = signInManager;
}
public async Task<IdentityResult> Register(RegisterViewModel model)
{
if (model is null) return new IdentityResult
{
StatusCodes = StatusCodes.Error,
ErrorMessages = new List<string>{"Внутренняя ошибка"}
};
User user = new User
{
Email = model.Email,
UserName = model.Email,
Age = model.Age,
Name = model.Name
};
var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
await _userManager.AddToRoleAsync(user, "user");
await _signInManager.SignInAsync(user, false);
return new IdentityResult{StatusCodes = StatusCodes.Success};
}
var errors = result.Errors.Select(error => error.Description).ToList();
return new IdentityResult
{
ErrorMessages = errors,
StatusCodes = StatusCodes.Error
};
}
public async Task<IdentityResult> LogIn(LoginViewModel model)
{
User user = await _userManager.FindByEmailAsync(model.Email);
SignInResult result = await _signInManager.PasswordSignInAsync(
user,
model.Password,
model.RememberMe,
false
);
if (result.Succeeded)
return new IdentityResult {StatusCodes = StatusCodes.Success};
return new IdentityResult
{
StatusCodes = StatusCodes.Error,
ErrorMessages = new List<string>{"Неправильный логин и (или) пароль"}
};
}
public async Task LogOf()
=> await _signInManager.SignOutAsync();
}
}
\ No newline at end of file
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using PhoneStore.Models;
namespace PhoneStore.Services
{
public class AdminInitializerService
{
public static async Task SeedAdminUser(
RoleManager<Role> roleManager,
UserManager<User> userManager)
{
string adminEmail = "admin@admin.com";
string adminPassword = "Admin1234@";
var roles = new [] { "admin", "user" };
foreach (var role in roles)
{
if (await roleManager.FindByNameAsync(role) is null)
await roleManager.CreateAsync(new Role(role));
}
if (await userManager.FindByNameAsync(adminEmail) == null)
{
User admin = new User { Email = adminEmail, UserName = adminEmail };
IdentityResult result = await userManager.CreateAsync(admin, adminPassword);
if (result.Succeeded)
await userManager.AddToRoleAsync(admin, "admin");
}
}
}
}
\ No newline at end of file
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using PhoneStore.Enums;
using PhoneStore.Models;
using PhoneStore.Services.Abstractions;
namespace PhoneStore.Services
{
public class BasketService : IBasketService
{
private readonly MobileContext _context;
public BasketService(MobileContext context)
{
_context = context;
}
public StatusCodes AddPhone(int id)
{
var exists = _context.Phones.Any(p => p.Id == id);
if (!exists) return StatusCodes.EntityNotFound;
_context.Baskets.Add(new Basket{PhoneId = id});
_context.SaveChanges();
return StatusCodes.Success;
}
public List<Basket> GetAll()
{
return _context.Baskets
.Include(b => b.Phone)
.ToList();
}
public void Remove(int id)
{
var basket = _context.Baskets.FirstOrDefault(b => b.PhoneId == id);
if (basket is null)
return;
_context.Baskets.Remove(basket);
_context.SaveChanges();
}
}
}
\ No newline at end of file
using PhoneStore.Services.Abstractions;
namespace PhoneStore.Services
{
public class DefaultPhoneImagePathProvider : IDefaultPhoneImagePathProvider
{
private readonly string _path;
public DefaultPhoneImagePathProvider(string path)
{
_path = path;
}
public string GetPathToDefaultImage() => _path;
}
}
\ No newline at end of file
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using PhoneStore.Services.Abstractions;
namespace PhoneStore.Services
{
public class PaginationService<T> : IPaginationService<T>
{
public async Task<(IQueryable<T>, int)> GetABatchOfData(IQueryable<T> elements, int page, int pageSize)
{
int count = await elements.CountAsync();
return (elements.Skip((page - 1) * pageSize).Take(pageSize), count);
}
}
}
\ No newline at end of file
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using PhoneStore.Helpers;
using PhoneStore.Models;
using PhoneStore.Services.Abstractions;
using PhoneStore.ViewModels;
namespace PhoneStore.Services
{
public class PhoneService : IPhoneService
{
private readonly MobileContext _db;
private readonly IDefaultPhoneImagePathProvider _imagePathProvider;
private readonly UploadService _uploadService;
private readonly IHostEnvironment _environment;
public PhoneService(
MobileContext db,
IDefaultPhoneImagePathProvider imagePathProvider,
UploadService uploadService,
IHostEnvironment environment)
{
_db = db;
_imagePathProvider = imagePathProvider;
_uploadService = uploadService;
_environment = environment;
}
public async Task CreateAsync(PhoneCreateViewModel entity)
{
string imagePath;
if (entity.File is null)
imagePath = _imagePathProvider.GetPathToDefaultImage();
else
{
var brand = _db.Brands.FirstOrDefault(b => b.Id == entity.BrandId);
if (brand is null)
throw new Exception();
string dirPath = Path.Combine(_environment.ContentRootPath, $"wwwroot\\images\\phoneImages\\{brand.Name}");
string fileName = $"{entity.File.FileName}";
await _uploadService.UploadAsync(dirPath, fileName, entity.File);
imagePath = $"images\\phoneImages\\{brand!.Name}\\{fileName}";
}
_db.Phones.Add(entity.MapToPhone(imagePath));
await _db.SaveChangesAsync();
}
}
}
\ No newline at end of file
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
namespace PhoneStore.Services
{
public class UploadService
{
public async Task UploadAsync(string dirPath, string fileName, IFormFile file)
{
if (!Directory.Exists(dirPath))
Directory.CreateDirectory(dirPath);
var absolutePath = Path.Combine(dirPath, fileName);
if (File.Exists(absolutePath))
throw new FileNotFoundException();
await using var stream = new FileStream(Path.Combine(dirPath, fileName), FileMode.Create);
await file.CopyToAsync(stream);
}
}
}
\ No newline at end of file
using System.Linq;
using PhoneStore.Models;
using PhoneStore.Services.Abstractions;
namespace PhoneStore.Services
{
public class UsersFilter : IUsersFilter
{
public IQueryable<User> FilterByName(IQueryable<User> users, string filterByName)
{
return string.IsNullOrEmpty(filterByName)
? users
: users.Where(u => u.Name.Contains(filterByName));
}
}
}
\ No newline at end of file
using System.Linq;
using System.Threading.Tasks;
using PhoneStore.Models;
using PhoneStore.Services.Abstractions;
using Order = PhoneStore.Enums.Order;
namespace PhoneStore.Services
{
public class UsersSortService : IUsersSortService
{
public IQueryable<User> Sort(IQueryable<User> users, Order order)
{
switch (order)
{
case Order.AgeDesc:
return users.OrderByDescending(u => u.Age);
case Order.AgeAsc:
return users.OrderBy(u => u.Age);
case Order.NameDesc:
return users.OrderByDescending(u => u.Name);
default: return users.OrderBy(u => u.Name);
}
}
}
}
\ No newline at end of file
using System.Text;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using PhoneStore.Helpers;
using PhoneStore.Models;
using PhoneStore.Services;
using PhoneStore.Services.Abstractions;
namespace PhoneStore
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
string connection = Configuration.GetConnectionString("DefaultConnection");
services.AddDbContext<MobileContext>(options => options.UseNpgsql(connection))
.AddIdentity<User, Role>()
.AddEntityFrameworkStores<MobileContext>();
services.AddControllersWithViews();
services.AddApplicationServices(Configuration);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseStatusCodePagesWithRedirects("/Error/{0}");
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Phones}/{action=Index}/{id?}");
});
}
}
}
\ No newline at end of file
using System.ComponentModel.DataAnnotations;
namespace PhoneStore.ViewModels.Account
{
public class LoginViewModel
{
private const string RequiredErrorMessage = "Поле обязательно для заполнения";
[Required(ErrorMessage = RequiredErrorMessage)]
[Display(Name = "Email")]
public string Email { get; set; }
[Required(ErrorMessage = RequiredErrorMessage)]
[DataType(DataType.Password)]
[Display(Name = "Пароль")]
public string Password { get; set; }
[Display(Name = "Запомнить?")]
public bool RememberMe { get; set; }
public string ReturnUrl { get; set; }
}
}
\ No newline at end of file
using System.ComponentModel.DataAnnotations;
namespace PhoneStore.ViewModels.Account
{
public class RegisterViewModel
{
//TODO: Добавить ограничение по минимальной и максимальной длине строк и возраста.
private const string RequiredErrorMessage = "Поле обязательно для заполнения";
[Required(ErrorMessage = RequiredErrorMessage)]
[Display(Name = "Email")]
public string Email { get; set; }
[Required(ErrorMessage = RequiredErrorMessage)]
[Display(Name = "Возраст")]
public int Age { get; set; }
[Required(ErrorMessage = RequiredErrorMessage)]
[Display(Name = "Имя")]
public string Name { get; set; }
[Required(ErrorMessage = RequiredErrorMessage)]
[DataType(DataType.Password)]
[Display(Name = "Пароль")]
public string Password { get; set; }
[Required(ErrorMessage = RequiredErrorMessage)]
[Compare(nameof(Password), ErrorMessage = "Пароли не совпадают")]
[DataType(DataType.Password)]
[Display(Name = "Подтвердить пароль")]
public string PasswordConfirm { get; set; }
}
}
\ No newline at end of file
namespace PhoneStore.ViewModels
{
public class BaseEntity
{
public int Id { get; set; }
}
}
\ No newline at end of file
namespace PhoneStore.ViewModels
{
public class CreateBrandViewModel
{
public string Name { get; set; }
}
}
\ No newline at end of file
namespace PhoneStore.ViewModels
{
public class ErrorViewModel
{
public int StatusCode { get; set; }
public string Title { get; set; }
public string Message { get; set; }
}
}
\ No newline at end of file
namespace PhoneStore.ViewModels
{
public class FeedbackCreateViewModel
{
public int UserId { get; set; }
public int PhoneId { get; set; }
public string Text { get; set; }
}
}
\ No newline at end of file
using System;
using PhoneStore.Models;
namespace PhoneStore.ViewModels
{
public class FeedbackViewModel
{
public int Id { get; set; }
public int UserId { get; set; }
public int PhoneId { get; set; }
public string Text { get; set; }
public DateTime CreationDateTime { get; set; }
public User User { get; set; }
public Phone Phone { get; set; }
}
}
\ No newline at end of file
using Microsoft.AspNetCore.Http;
namespace PhoneStore.ViewModels
{
public class FileViewModel
{
public IFormFile File { 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;
namespace PhoneStore.ViewModels
{
public class PageViewModel
{
public int Page { get; set; }
public int TotalPages { get; set; }
public bool HasPrevious => Page > 1;
public bool HasNext => Page < TotalPages;
public int Count { get; set; }
public PageViewModel(int page, int count, int pageSize)
{
Page = page;
Count = count;
TotalPages = (int)Math.Ceiling(count / (double)pageSize);
}
}
}
\ No newline at end of file
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Http;
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; }
public IFormFile File { get; set; }
public string Image { get; set; }
}
}
\ No newline at end of file
using System.Collections.Generic;
using PhoneStore.Models;
namespace PhoneStore.ViewModels
{
public class PhoneViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public Brand Brand { get; set; }
public string Image { get; set; }
public List<FeedbackViewModel> Feedbacks { get; set; }
}
}
\ No newline at end of file
using PhoneStore.Enums;
namespace PhoneStore.ViewModels
{
public class ResponseMessageViewModel
{
public string Message { get; set; }
public StatusCodes Status { get; set; }
}
}
\ No newline at end of file
using System.Collections.Generic;
using PhoneStore.Models;
namespace PhoneStore.ViewModels
{
public class UsersViewModel
{
public IEnumerable<User> Users { get; set; }
public string Filter { get; set; }
public PageViewModel PageViewModel { get; set; }
}
}
\ No newline at end of file
@model LoginViewModel
@{
ViewBag.Title = "title";
Layout = "_Layout";
}
<h2>Вход в приложение</h2>
<form method="post" asp-controller="Account" asp-action="Login"
asp-route-returnUrl="@Model.ReturnUrl">
<div asp-validation-summary="ModelOnly"></div>
<div>
<label asp-for="Email"></label><br />
<input asp-for="Email" />
<span asp-validation-for="Email"></span>
</div>
<div>
<label asp-for="Password"></label><br />
<input asp-for="Password" />
<span asp-validation-for="Password"></span>
</div>
<div>
<label asp-for="RememberMe"></label><br />
<input asp-for="RememberMe" />
</div>
<div>
<button type="submit" class="btn btn-outline-warning">Войти</button>
</div>
</form>
@model RegisterViewModel
@{
ViewBag.Title = "title";
Layout = "_Layout";
}
<h2>Регистрация нового пользователя</h2>
<form method="post" asp-controller="Account" asp-action="Register">
<div asp-validation-summary="ModelOnly"></div>
<div>
<label asp-for="Email"></label><br />
<input asp-for="Email" />
<span asp-validation-for="Email"></span>
</div>
<div>
<label asp-for="Name"></label><br />
<input asp-for="Name" />
<span asp-validation-for="Name"></span>
</div>
<div>
<label asp-for="Age"></label><br />
<input asp-for="Age" />
<span asp-validation-for="Age"></span>
</div>
<div>
<label asp-for="Password"></label><br />
<input asp-for="Password" />
<span asp-validation-for="Password"></span>
</div>
<div>
<label asp-for="PasswordConfirm"></label><br />
<input asp-for="PasswordConfirm" />
<span asp-validation-for="PasswordConfirm"></span>
</div>
<div>
<button type="submit" class="btn btn-outline-warning">Регистрация</button>
</div>
</form>
@model ResponseMessageViewModel
@{
ViewBag.Title = "title";
Layout = "_Layout";
}
<h2>@Model.Status : @Model.Message</h2>
<a asp-action="Index" asp-controller="Baskets">Перейти в корзину</a>
@model List<Basket>
@{
ViewBag.Title = "title";
Layout = "_Layout";
}
<h2>Корзина</h2>
<table style="width: 100%">
<tr>
<th>Id</th>
<th>Наименование</th>
<th>Компания</th>
<th>Стоимость</th>
<th></th>
</tr>
@foreach (var basket in Model)
{
<tr>
<td>@basket.PhoneId</td>
<td>@basket.Phone.Name</td>
<td>@basket.Phone.Brand.Name</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>
</tr>
}
</table>
\ No newline at end of file
@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
This diff is collapsed.
@{
ViewData["Title"] = "Home Page";
}
<div class="text-center">
<h1 class="display-4">Welcome</h1>
<p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
</div>
\ No newline at end of file
@{
ViewData["Title"] = "Privacy Policy";
}
<h1>@ViewData["Title"]</h1>
<p>Use this page to detail your site's privacy policy.</p>
\ No newline at end of file
@model Order
@{
ViewBag.Title = "Заказать смартфон";
Layout = "_Layout";
}
<h2>Заполните форму для заказа</h2>
<div class="row">
<div class="col-md-6">
<form asp-action="Create" asp-controller="Orders" method="post">
<div class="form-row">
<label for="">
Имя покупателя
<input asp-for="UserName" type="text">
</label>
</div>
<div class="form-row">
<label for="">
Адрес доставки
<input asp-for="Address" type="text">
</label>
</div>
<div class="form-row">
<label for="">
Номер телефона
<input asp-for="ContactPhone" type="text">
</label>
</div>
<div class="form-row">
<label for="">
Смартфон :
<input disabled type="text" value="@Model.Phone.Name">
</label>
<input asp-for="PhoneId" type="hidden" value="@Model.Phone.Id">
</div>
<button type="submit">Заказать</button>
</form>
</div>
</div>
@model List<Order>
@{
ViewBag.Title = "Список заказов";
Layout = "_Layout";
}
@if (Model.Count == 0)
{
<h2>Список пуст</h2>
}
else
{
<table style="width: 100%">
<tr>
<th>Id</th>
<th>Адрес</th>
<th>Контактный телефон</th>
<th>Имя пользователя</th>
<th>Название телефона</th>
</tr>
@foreach (var order in Model)
{
<tr>
<td>@order.Id</td>
<td>@order.Address</td>
<td>@order.ContactPhone</td>
<td>@order.UserName</td>
<td>@order.Phone.Name</td>
</tr>
}
</table>
}
@model PhoneViewModel
@{
ViewBag.Title = "Подробная информация";
Layout = "_Layout";
}
<h2>@Model.Name</h2>
<div class="card mb-3">
<img class="card-img-top w-50" src="~/@Model.Image" alt="Card image cap">
<div class="card-body">
<h5 class="card-title">@Model.Brand.Name</h5>
<p class="card-text">@(Model.Price.ToString("C2"))</p>
</div>
</div>
\ No newline at end of file
@model PhoneCreateViewModel
@{
ViewBag.Title = "title";
Layout = "_Layout";
}
<h2>Заполните форму для добавления устройства</h2>
<div class="row">
<div class="phone_add_form col-md-6">
<form asp-action="Create" asp-controller="Phones" method="post" enctype="multipart/form-data">
@{
await Html.RenderPartialAsync("PartialViews/PhoneFormPartialView", Model);
}
<button class="btn btn-outline-warning">Отправить</button>
</form>
</div>
</div>
@section Scripts
{
@{
await Html.RenderPartialAsync("_ValidationScriptsPartial");
}
}
@model Phone
@{
ViewBag.Title = "title";
Layout = "_Layout";
}
<div class="card text-center">
<div class="card-header">
Ахтунг!
</div>
<div class="card-body">
<h5 class="card-title">Вы уверены?</h5>
<p class="card-text">Данные могут быть безвозвратно удалены.</p>
<form asp-action="Delete" asp-controller="Phones" method="post">
<input type="text" name="phoneId" value="@Model.Id" hidden>
<a class="btn btn-outline-warning" asp-action="Index">Отмена</a>
<button class="btn btn-danger">Удалить</button>
</form>
</div>
</div>
\ No newline at end of file
@model PhoneCreateViewModel
@{
ViewBag.Title = "Редактирование";
Layout = "_Layout";
}
<div class="row">
<div class="col-md-6">
<form asp-action="Edit" asp-controller="Phones" method="post" enctype="multipart/form-data">
@{
await Html.RenderPartialAsync("PartialViews/PhoneFormPartialView");
}
<input type="text" hidden asp-for="@Model.Id">
<input type="text" hidden asp-for="@Model.Image">
<button type="submit"
class="btn btn-outline-warning">
Изменить
</button>
</form>
</div>
</div>
@section Scripts
{
@{
await Html.RenderPartialAsync("_ValidationScriptsPartial");
}
}
\ No newline at end of file
@model IndexViewModel
@{
ViewBag.Title = "Смартфоны";
Layout = "_Layout";
}
@if (Model.Phones.Count() == 0)
{
<h2>Список пуст.</h2>
}
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>
<table style="width: 100%">
<tr>
<th>Минитюра</th>
<th>Наименование</th>
<th>Компания</th>
<th>Стоимость</th>
<th></th>
<th></th>
</tr>
@foreach (var phone in Model.Phones)
{
<tr>
<td><img class="phone_img" src="@phone.Image" alt="картинка паламатая"/></td>
<td>@phone.Name</td>
<td>@phone.Brand.Name</td>
<td>@phone.Price</td>
<td>
<div class="dropdown">
<button class="btn btn-secondary dropdown-toggle"
type="button"
id="dropdownMenu2"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false">
Выполнить...
</button>
<div class="dropdown-menu" aria-labelledby="dropdownMenu2">
<a class="dropdown-item" asp-route-id="@phone.Id"
asp-action="Add"
asp-controller="Baskets">
В корзину
</a>
<a asp-route-phoneId="@phone.Id"
asp-action="Create"
asp-controller="Orders"
class="dropdown-item">
Заказать
</a>
<a asp-route-phoneId="@phone.Id"
asp-action="Edit"
asp-controller="Phones"
class="dropdown-item">
Изменить
</a>
<a asp-route-phoneId="@phone.Id"
asp-action="About"
asp-controller="Phones"
class="dropdown-item">
Подробнее
</a>
<a asp-route-phoneId="@phone.Id"
asp-action="Delete"
asp-controller="Phones"
class="dropdown-item bg-danger">
Удалить
</a>
</div>
</div>
</tr>
}
</table>
}
<div class="d-flex justify-content-end my-5">
<a class="btn btn-outline-info" asp-action="Create" asp-controller="Phones">Добавить устройство</a>
</div>
@model PhoneCreateViewModel
<div class="mb-3">
<label asp-for="Name" class="form-label">Наименование</label>
<input type="text" class="form-control" asp-for="Name">
<div class="form-text">
<span asp-validation-for="Name" class="text-danger"></span>
</div>
</div>
<div class="mb-3">
<label asp-for="Price" class="form-label">Стоимость</label>
<input type="text" class="form-control" asp-for="Price">
<div class="form-text">
<span asp-validation-for="Price" class="text-danger"></span>
</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>
<div class="mb-3">
<label asp-for="File" class="form-label">Добавьте изображение</label>
<input class="form-control" type="file" asp-for="File">
<div class="form-text">
<span asp-validation-for="File" class="text-danger"></span>
</div>
</div>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>@ViewData["Title"] - PhoneStore</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css"/>
<link rel="stylesheet" href="~/css/site.css"/>
</head>
<body>
<header>
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
<div class="container">
<a class="navbar-brand" asp-area="" asp-controller="Phones" asp-action="Index">PhoneStore</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent"
aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Phones" asp-action="Index">Смартфоны</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Orders" asp-action="Index">Заказы</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Brands" asp-action="Create">Добавить бренд</a>
</li>
</ul>
</div>
<div class="login_group">
@if(User.Identity.IsAuthenticated)
{
<p>@User.Identity.Name</p>
<form method="post" asp-controller="Account" asp-action="LogOff">
<button class="btn btn-outline-warning" type="submit">Выход</button>
</form>
}
else
{
<a asp-controller="Account" asp-action="Login">Вход</a>
<a asp-controller="Account" asp-action="Register">Регистрация</a>
}
</div>
</div>
</nav>
</header>
<div class="container">
<main role="main" class="pb-3">
@RenderBody()
</main>
</div>
<footer class="border-top footer text-muted">
<div class="container">
&copy; 2022 - PhoneStore - Created by Vitaly
</div>
</footer>
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>
\ No newline at end of file
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>
\ No newline at end of file
@using PhoneStore
@using PhoneStore.Models
@using PhoneStore.ViewModels
@using PhoneStore.ViewModels.Account
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
\ No newline at end of file
@{
Layout = "_Layout";
}
\ No newline at end of file
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}
{
"ConnectionStrings": {
"DefaultConnection": "Server=localhost;Port=5432;database=lesson63;Username=postgres;"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"PathToDefaultAvatar": {
"Path": "images\\phoneImages\\default.jpg"
},
"UsersPageSize" : 2
}
/* Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification
for details on configuring this project to bundle and minify static web assets. */
a.navbar-brand {
white-space: normal;
text-align: center;
word-break: break-all;
}
/* Provide sufficient contrast against white background */
a {
color: #0366d6;
}
.btn-primary {
color: #fff;
background-color: #1b6ec2;
border-color: #1861ac;
}
.nav-pills .nav-link.active, .nav-pills .show > .nav-link {
color: #fff;
background-color: #1b6ec2;
border-color: #1861ac;
}
/* Sticky footer styles
-------------------------------------------------- */
html {
font-size: 14px;
}
@media (min-width: 768px) {
html {
font-size: 16px;
}
}
.border-top {
border-top: 1px solid #e5e5e5;
}
.border-bottom {
border-bottom: 1px solid #e5e5e5;
}
.box-shadow {
box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);
}
button.accept-policy {
font-size: 1rem;
line-height: inherit;
}
/* Sticky footer styles
-------------------------------------------------- */
html {
position: relative;
min-height: 100%;
}
body {
/* Margin bottom by footer height */
margin-bottom: 60px;
}
.footer {
position: absolute;
bottom: 0;
width: 100%;
white-space: nowrap;
line-height: 60px; /* Vertically center the text there */
}
.btn-secondary {
color: #fff;
background-color: #6f42c1;
border-color: #6f42c1;
}
$(document).ready(function (){
$('#search').on('keyup', function (e){
e.preventDefault();
let searchTerm = $(this).val();
console.log(searchTerm)
searchTerm = encodeURIComponent(searchTerm);
console.log(searchTerm)
$('#results')
.load(`https://localhost:5001/Account/SearchAccounts?searchTerm=${searchTerm}`);
})
});
\ No newline at end of file
The MIT License (MIT)
Copyright (c) 2011-2018 Twitter, Inc.
Copyright (c) 2011-2018 The Bootstrap Authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
/*!
* Bootstrap Reboot v4.3.1 (https://getbootstrap.com/)
* Copyright 2011-2019 The Bootstrap Authors
* Copyright 2011-2019 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
*/
*,
*::before,
*::after {
box-sizing: border-box;
}
html {
font-family: sans-serif;
line-height: 1.15;
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
article, aside, figcaption, figure, footer, header, hgroup, main, nav, section {
display: block;
}
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
color: #212529;
text-align: left;
background-color: #fff;
}
[tabindex="-1"]:focus {
outline: 0 !important;
}
hr {
box-sizing: content-box;
height: 0;
overflow: visible;
}
h1, h2, h3, h4, h5, h6 {
margin-top: 0;
margin-bottom: 0.5rem;
}
p {
margin-top: 0;
margin-bottom: 1rem;
}
abbr[title],
abbr[data-original-title] {
text-decoration: underline;
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
cursor: help;
border-bottom: 0;
-webkit-text-decoration-skip-ink: none;
text-decoration-skip-ink: none;
}
address {
margin-bottom: 1rem;
font-style: normal;
line-height: inherit;
}
ol,
ul,
dl {
margin-top: 0;
margin-bottom: 1rem;
}
ol ol,
ul ul,
ol ul,
ul ol {
margin-bottom: 0;
}
dt {
font-weight: 700;
}
dd {
margin-bottom: .5rem;
margin-left: 0;
}
blockquote {
margin: 0 0 1rem;
}
b,
strong {
font-weight: bolder;
}
small {
font-size: 80%;
}
sub,
sup {
position: relative;
font-size: 75%;
line-height: 0;
vertical-align: baseline;
}
sub {
bottom: -.25em;
}
sup {
top: -.5em;
}
a {
color: #007bff;
text-decoration: none;
background-color: transparent;
}
a:hover {
color: #0056b3;
text-decoration: underline;
}
a:not([href]):not([tabindex]) {
color: inherit;
text-decoration: none;
}
a:not([href]):not([tabindex]):hover, a:not([href]):not([tabindex]):focus {
color: inherit;
text-decoration: none;
}
a:not([href]):not([tabindex]):focus {
outline: 0;
}
pre,
code,
kbd,
samp {
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
font-size: 1em;
}
pre {
margin-top: 0;
margin-bottom: 1rem;
overflow: auto;
}
figure {
margin: 0 0 1rem;
}
img {
vertical-align: middle;
border-style: none;
}
svg {
overflow: hidden;
vertical-align: middle;
}
table {
border-collapse: collapse;
}
caption {
padding-top: 0.75rem;
padding-bottom: 0.75rem;
color: #6c757d;
text-align: left;
caption-side: bottom;
}
th {
text-align: inherit;
}
label {
display: inline-block;
margin-bottom: 0.5rem;
}
button {
border-radius: 0;
}
button:focus {
outline: 1px dotted;
outline: 5px auto -webkit-focus-ring-color;
}
input,
button,
select,
optgroup,
textarea {
margin: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
button,
input {
overflow: visible;
}
button,
select {
text-transform: none;
}
select {
word-wrap: normal;
}
button,
[type="button"],
[type="reset"],
[type="submit"] {
-webkit-appearance: button;
}
button:not(:disabled),
[type="button"]:not(:disabled),
[type="reset"]:not(:disabled),
[type="submit"]:not(:disabled) {
cursor: pointer;
}
button::-moz-focus-inner,
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner {
padding: 0;
border-style: none;
}
input[type="radio"],
input[type="checkbox"] {
box-sizing: border-box;
padding: 0;
}
input[type="date"],
input[type="time"],
input[type="datetime-local"],
input[type="month"] {
-webkit-appearance: listbox;
}
textarea {
overflow: auto;
resize: vertical;
}
fieldset {
min-width: 0;
padding: 0;
margin: 0;
border: 0;
}
legend {
display: block;
width: 100%;
max-width: 100%;
padding: 0;
margin-bottom: .5rem;
font-size: 1.5rem;
line-height: inherit;
color: inherit;
white-space: normal;
}
progress {
vertical-align: baseline;
}
[type="number"]::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button {
height: auto;
}
[type="search"] {
outline-offset: -2px;
-webkit-appearance: none;
}
[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-file-upload-button {
font: inherit;
-webkit-appearance: button;
}
output {
display: inline-block;
}
summary {
display: list-item;
cursor: pointer;
}
template {
display: none;
}
[hidden] {
display: none !important;
}
/*# sourceMappingURL=bootstrap-reboot.css.map */
\ No newline at end of file
/*!
* Bootstrap Reboot v4.3.1 (https://getbootstrap.com/)
* Copyright 2011-2019 The Bootstrap Authors
* Copyright 2011-2019 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
*/*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}
/*# sourceMappingURL=bootstrap-reboot.min.css.map */
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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