Last active
March 14, 2024 18:04
-
-
Save jesslilly/c9583551ac257e6a3c04d6d9092140d9 to your computer and use it in GitHub Desktop.
Jess Lilly Sample Code .NET 6 MVC
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Domain.Toolkit; | |
using Domain.Sample.AppModels.Attributes; | |
using Domain.Sample.DbModels.Sample; | |
namespace Domain.Sample.Services.Attributes; | |
public class AttributeCrudCommands : IAttributeCrudCommands | |
{ | |
private readonly SampleDbContext _db; | |
private readonly ILogger<AttributeCrudCommands> _logger; | |
public AttributeCrudCommands(SampleDbContext db, | |
ILogger<AttributeCrudCommands> logger) | |
{ | |
_db = db; | |
_logger = logger; | |
} | |
public async Task<int> Insert(AttributeModifyViewModel viewModel) | |
{ | |
try | |
{ | |
var model = new Attribute(); | |
_db.Entry(model).CurrentValues.SetValues(viewModel); | |
ModelUtils.TrimStringProperties(model); | |
ModelUtils.TruncateStringProperties(model); | |
await _db.Attribute.AddAsync(model); | |
await UpdateLinkAttributeProductTechnology(viewModel.ProductTechnologies, model); | |
await _db.SaveChangesAsync(); | |
viewModel.AttributeId = model.AttributeId; | |
return model.AttributeId; | |
} | |
catch (Exception e) | |
{ | |
_logger.LogError(e, "Failed to create Attribute"); | |
throw; | |
} | |
} | |
public async Task Update(AttributeModifyViewModel viewModel) | |
{ | |
try | |
{ | |
var model = await _db.Attribute | |
.SingleOrDefaultAsync(x => x.AttributeId == viewModel.AttributeId); | |
if (model == null) | |
{ | |
throw new Exception($"Attribute with id {viewModel.AttributeId} not found."); | |
} | |
_db.Entry(model).CurrentValues.SetValues(viewModel); | |
ModelUtils.TrimStringProperties(model); | |
ModelUtils.TruncateStringProperties(model); | |
await UpdateLinkAttributeProductTechnology(viewModel.ProductTechnologies, model); | |
await _db.SaveChangesAsync(); | |
} | |
catch (Exception e) | |
{ | |
_logger.LogError(e, $"Failed to update Attribute with id {viewModel.AttributeId}"); | |
throw; | |
} | |
} | |
private async Task UpdateLinkAttributeProductTechnology(List<LinkAttributeProductTechnologyEditViewModel> viewModels, Attribute attribute) | |
{ | |
var ids = viewModels | |
.Select(x => x.LinkAttributeProductTechnologyId) | |
.ToList(); | |
var models = await _db.LinkAttributeProductTechnology | |
.Where(x => ids.Contains(x.LinkAttributeProductTechnologyId)) | |
.ToDictionaryAsync(x => x.LinkAttributeProductTechnologyId); | |
foreach (var viewModel in viewModels) | |
{ | |
if (models.ContainsKey(viewModel.LinkAttributeProductTechnologyId)) | |
{ | |
if (viewModel.ProductTechnologyId == 0) | |
{ | |
// Delete | |
var model = models[viewModel.LinkAttributeProductTechnologyId]; | |
_db.LinkAttributeProductTechnology.Remove(model); | |
} | |
else | |
{ | |
// Update | |
var model = models[viewModel.LinkAttributeProductTechnologyId]; | |
model.ProductTechnologyId = viewModel.ProductTechnologyId; | |
} | |
} | |
else if(viewModel.ProductTechnologyId > 0) | |
{ | |
// Create new link | |
var model = new LinkAttributeProductTechnology | |
{ | |
ProductTechnologyId = viewModel.ProductTechnologyId, | |
Attribute = attribute | |
}; | |
await _db.LinkAttributeProductTechnology.AddAsync(model); | |
} | |
} | |
} | |
public async Task Delete(int id) | |
{ | |
try | |
{ | |
var model = await _db.Attribute | |
.Include(x => x.LinkAttributeProductTechnologies) | |
.Include(x => x.AttributeMaps) | |
.SingleOrDefaultAsync(x => x.AttributeId == id); | |
if (model == null) | |
{ | |
throw new Exception($"Attribute with id {id} not found."); | |
} | |
_db.Remove(model); | |
await _db.SaveChangesAsync(); | |
} | |
catch (Exception e) | |
{ | |
_logger.LogError(e, $"Failed to delete Attribute with id {id}"); | |
throw; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Domain.Sample.AppModels.AttributeMaps; | |
using Domain.Sample.AppModels.Attributes; | |
using Domain.Sample.Services.Shared.CRUD; | |
using Domain.Sample.Services.Shared.StringExtensions; | |
using Domain.Sample.Shared.CRUD; | |
namespace Domain.Sample.Services.Attributes; | |
public class AttributeCrudQueries : IAttributeCrudQueries | |
{ | |
private readonly SampleDbContext _db; | |
private readonly ISelectListQueries _selectListQueries; | |
private readonly IJsonService _jsonService; | |
public AttributeCrudQueries(SampleDbContext db, ISelectListQueries selectListQueries, IJsonService jsonService) | |
{ | |
_db = db; | |
_selectListQueries = selectListQueries; | |
_jsonService = jsonService; | |
} | |
public async Task<AttributeIndexViewModel> GetIndexViewModel(AttributeIndexViewModel viewModel = null) | |
{ | |
viewModel ??= new AttributeIndexViewModel(); | |
viewModel.Results = await _db.LinkAttributeProductTechnology | |
.Take(1000) | |
.ToListViewModel() | |
.OrderBy(x => x.ProductTechnologyName) | |
.ThenBy(x => x.Name) | |
.ToListAsync(); | |
return viewModel; | |
} | |
public async Task<AttributeDetailsViewModel> GetDetailsViewModel(int id) | |
{ | |
var viewModel = await _db.Attribute | |
.Where(x => x.AttributeId == id) | |
.ToDetailsViewModel() | |
.SingleOrDefaultAsync(); | |
if (viewModel == null) | |
{ | |
return null; | |
} | |
viewModel.LookupValues = _jsonService.DeserializeOrDefault<AttributeLookupValuesJsonModel>(viewModel.LookupValuesJson); | |
viewModel.ProductTechnologies = await _db.LinkAttributeProductTechnology | |
.Where(x => x.AttributeId == viewModel.AttributeId) | |
.OrderBy(x => x.ProductTechnology.Name) | |
.Select(x => x.ProductTechnology) | |
.ToLookupViewModel() | |
.ToListAsync(); | |
return viewModel; | |
} | |
public async Task<AttributeModifyViewModel> GetCreateViewModel(AttributeModifyViewModel viewModel = null) | |
{ | |
viewModel ??= new AttributeModifyViewModel(); | |
viewModel.UiOperation = UiOperation.Create; | |
viewModel.ProductTechnologies.Add(new LinkAttributeProductTechnologyEditViewModel()); | |
return await SetSelectLists(viewModel); | |
} | |
public async Task<AttributeModifyViewModel> GetEditViewModel(int id) | |
{ | |
var viewModel = await _db.Attribute | |
.Where(x => x.AttributeId == id) | |
.ToEditViewModel() | |
.SingleOrDefaultAsync(); | |
if (viewModel == null) | |
{ | |
return null; | |
} | |
viewModel.ProductTechnologies = await _db.LinkAttributeProductTechnology | |
.Where(x => x.AttributeId == viewModel.AttributeId) | |
.OrderBy(x => x.ProductTechnology.Name) | |
.ToEditViewModel() | |
.ToListAsync(); | |
// Cheap "Add a new row" solution without much JS: | |
viewModel.ProductTechnologies.Add(new LinkAttributeProductTechnologyEditViewModel()); | |
viewModel.UiOperation = UiOperation.Edit; | |
return await SetSelectLists(viewModel); | |
} | |
public async Task<AttributeModifyViewModel> SetSelectLists(AttributeModifyViewModel viewModel) | |
{ | |
viewModel.GlobalVariableSelectList = await _selectListQueries.GetGlobalVariableSelectList(); | |
viewModel.FormatSelectList = await _selectListQueries.GetFormatSelectList(); | |
viewModel.ProductTechnologySelectList = await _selectListQueries.GetProductTechnologySelectList(); | |
return viewModel; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Microsoft.AspNetCore.Mvc.Rendering; | |
using Domain.Sample.Shared.CRUD; | |
namespace Domain.Sample.AppModels.Attributes; | |
/// <summary> | |
/// Used for create and edit to avoid duplicate code and duplicate validators. | |
/// </summary> | |
public class AttributeModifyViewModel | |
{ | |
[Display(Name = @"ID")] | |
public int AttributeId { get; set; } | |
[Display(Name = @"Name")] | |
public string Name { get; set; } | |
[Display(Name = @"Display Label")] | |
public string DisplayLabel { get; set; } | |
[Display(Name = @"Description")] | |
public string Description { get; set; } | |
[Display(Name = @"Global Variable")] | |
public int? GlobalVariableId { get; set; } | |
public List<SelectListItem> GlobalVariableSelectList { get; set; } | |
[Display(Name = @"Format")] | |
public int FormatId { get; set; } | |
public List<SelectListItem> FormatSelectList { get; set; } | |
public List<SelectListItem> ProductTechnologySelectList { get; set; } | |
[Display(Name = @"Linked Product Technology")] | |
public List<LinkAttributeProductTechnologyEditViewModel> ProductTechnologies { get; set; } = new(); | |
[Display(Name = @"Lookup Values JSON")] | |
public string LookupValuesJson { get; set; } | |
public UiOperation UiOperation { get; set; } | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Domain.Sample.AppModels.AttributeMaps; | |
using Domain.Sample.AppModels.Attributes; | |
using Domain.Sample.Services.Shared.StringExtensions; | |
using Domain.Sample.Shared.Known; | |
namespace Domain.Sample.Services.Attributes; | |
public class AttributeModifyViewModelValidator : AbstractValidator<AttributeModifyViewModel> | |
{ | |
private readonly IJsonService _jsonService; | |
public AttributeModifyViewModelValidator(IJsonService jsonService) | |
{ | |
_jsonService = jsonService; | |
RuleFor(x => x.Name).NotEmpty().MaximumLength(100); | |
RuleFor(x => x.DisplayLabel).NotEmpty().MaximumLength(100); | |
RuleFor(x => x.Description).NotEmpty().MaximumLength(1000); | |
RuleFor(x => x.FormatId).GreaterThan(0); | |
RuleFor(x => x.ProductTechnologies) | |
.NotEmpty() | |
.Must(x => x.Any(y => y.ProductTechnologyId > 0)) | |
.WithMessage("You must have at least one assigned product technology."); | |
RuleFor(x => x.LookupValuesJson) | |
.NotEmpty().When(x => x.FormatId == KnownFormatId.Lookup) | |
.Custom(ValidateLookupValuesJson); | |
} | |
private void ValidateLookupValuesJson(string lookupValuesJson, ValidationContext<AttributeModifyViewModel> context) | |
{ | |
if (string.IsNullOrWhiteSpace(lookupValuesJson)) | |
{ | |
return; | |
} | |
var result = _jsonService.DeserializeOrDefault<AttributeLookupValuesJsonModel>(lookupValuesJson); | |
if (result == null) | |
{ | |
context.AddFailure("Lookup Values JSON is not valid JSON."); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Domain.Sample.Services.Attributes; | |
using Domain.Sample.Services.Shared.CRUD; | |
using Domain.Sample.Services.Shared.StringExtensions; | |
using Domain.Sample.Services.Tests.Shared; | |
using Domain.Sample.Services.Tests.Shared.Fixtures.Attributes; | |
namespace Domain.Sample.Services.Tests.Attributes; | |
[TestClass] | |
public class AttributeQueriesTests : InMemoryDbTest | |
{ | |
private AttributeCrudQueries _queries; | |
protected override async Task SetupData() | |
{ | |
await EnergyStarAttributesFixture.SetupData(_db); | |
} | |
protected override void SetupServices() | |
{ | |
var selectListQueries = new SelectListQueries(_db); | |
var jsonService = new JsonService(new NullLogger<JsonService>()); | |
_queries = new AttributeCrudQueries(_db, selectListQueries, jsonService); | |
} | |
[TestMethod] | |
public async Task GetAttributeIndexViewModel() | |
{ | |
// Arrange | |
// Act | |
var viewModel = await _queries.GetIndexViewModel(); | |
// Assert | |
Verify(viewModel); | |
} | |
[TestMethod] | |
public async Task GetAttributeDetailsViewModel() | |
{ | |
// Arrange | |
var id = await _db.Attribute.Select(x => x.AttributeId).FirstAsync(); | |
// Act | |
var viewModel = await _queries.GetDetailsViewModel(id); | |
// Assert | |
Verify(viewModel); | |
} | |
[TestMethod] | |
public async Task GetAttributeCreateViewModel() | |
{ | |
// Arrange | |
// Act | |
var viewModel = await _queries.GetCreateViewModel(); | |
// Assert | |
Verify(viewModel); | |
} | |
[TestMethod] | |
public async Task GetAttributeEditViewModel() | |
{ | |
// Arrange | |
var id = await _db.Attribute.Select(x => x.AttributeId).FirstAsync(); | |
// Act | |
var viewModel = await _queries.GetEditViewModel(id); | |
// Assert | |
Verify(viewModel); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Domain.Sample.AppModels.Attributes; | |
using Domain.Sample.Services.Attributes; | |
namespace Domain.Sample.Web.Areas.Admin.Controllers; | |
[Area(nameof(Admin))] | |
[Authorize(Policy = AdminAccessPolicy.Name)] | |
public class AttributesController : Controller | |
{ | |
private readonly IAttributeCrudCommands _commands; | |
private readonly IAttributeCrudQueries _queries; | |
public AttributesController(IAttributeCrudQueries queries, IAttributeCrudCommands commands) | |
{ | |
_queries = queries; | |
_commands = commands; | |
} | |
public async Task<IActionResult> Index() | |
{ | |
var viewModel = await _queries.GetIndexViewModel(); | |
return View(viewModel); | |
} | |
public async Task<IActionResult> Details(int id) | |
{ | |
var viewModel = await _queries.GetDetailsViewModel(id); | |
if (viewModel == null) | |
{ | |
return NotFound(); | |
} | |
return View(viewModel); | |
} | |
public async Task<IActionResult> Create() | |
{ | |
var viewModel = await _queries.GetCreateViewModel(); | |
return View(viewModel); | |
} | |
[HttpPost] | |
public async Task<IActionResult> Create(AttributeModifyViewModel viewModel) | |
{ | |
if (!ModelState.IsValid) | |
{ | |
viewModel = await _queries.SetSelectLists(viewModel); | |
return View(viewModel); | |
} | |
await _commands.Insert(viewModel); | |
return RedirectToAction("Index"); | |
} | |
public async Task<IActionResult> Edit(int id) | |
{ | |
var viewModel = await _queries.GetEditViewModel(id); | |
if (viewModel == null) | |
{ | |
return NotFound(); | |
} | |
return View(viewModel); | |
} | |
[HttpPost] | |
public async Task<IActionResult> Edit(AttributeModifyViewModel viewModel) | |
{ | |
if (!ModelState.IsValid) | |
{ | |
viewModel = await _queries.SetSelectLists(viewModel); | |
return View(viewModel); | |
} | |
await _commands.Update(viewModel); | |
return RedirectToAction("Details", new { id = viewModel.AttributeId }); | |
} | |
[HttpPost] | |
public async Task<IActionResult> Delete(int id) | |
{ | |
await _commands.Delete(id); | |
return RedirectToAction("Index"); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@model Domain.Sample.AppModels.Attributes.AttributeIndexViewModel | |
@{ | |
ViewData["Title"] = "Attributes"; | |
} | |
<h1>Attributes</h1> | |
<p> | |
<a asp-action="Create">+ Create new Attribute</a> | |
</p> | |
<table class="table" data-table> | |
<thead> | |
<tr> | |
<th> | |
@Html.DisplayNameFor(model => model.Results.First().ProductTechnologyName) | |
</th> | |
<th> | |
@Html.DisplayNameFor(model => model.Results.First().Name) | |
</th> | |
<th> | |
@Html.DisplayNameFor(model => model.Results.First().GlobalVariableName) | |
</th> | |
</tr> | |
</thead> | |
<tbody> | |
@foreach (var item in Model.Results) | |
{ | |
<tr> | |
<td> | |
@item.ProductTechnologyName | |
</td> | |
<td> | |
<a asp-action="Details" asp-route-id="@item.AttributeId" title="@item.Description">@item.Name</a> | |
</td> | |
<td> | |
@Html.DisplayFor(modelItem => item.GlobalVariableName) | |
</td> | |
</tr> | |
} | |
</tbody> | |
</table> | |
@section Scripts { | |
<script src="~/js/apply-jquery-datatable.js" asp-append-version="true"></script> | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment