Skip to content

Instantly share code, notes, and snippets.

@jesslilly
Last active March 14, 2024 18:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jesslilly/c9583551ac257e6a3c04d6d9092140d9 to your computer and use it in GitHub Desktop.
Save jesslilly/c9583551ac257e6a3c04d6d9092140d9 to your computer and use it in GitHub Desktop.
Jess Lilly Sample Code .NET 6 MVC
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;
}
}
}
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;
}
}
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; }
}
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.");
}
}
}
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);
}
}
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");
}
}
@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