I'm trying to do this query: SELECT * FROM Machine WHERE KlantId = [Id from url] in c# but its returing only 1 row when it should be 2 rows. I manualy change the input of KlantId in the url. The url looks like this: https://00.00.00.00:0000/api/Machine/1
Any help would be much appreciated.
Api output:
Api Model:
[
{
"SerienummerId": 1,
"Serienummer": "10K13321",
"Bouwjaar": "2020",
"Urenstand": "10",
"Locatie": "-23134123, 123123",
"KlantId": "1"
},
{
"SerienummerId": 2,
"Serienummer": "25K84314",
"Bouwjaar": "1998",
"Urenstand": "5010",
"Locatie": "-23134123, 123123",
"KlantId": "1"
}
]
C# MachineRepository
public class MachineRepository: IRepository<MachineModel>
{
private readonly DataContext _context;
public MachineRepository(DataContext context)
{
_context = context;
}
// Get Service by id
public async Task<MachineModel> GetDataById(int id)
{
return await _context.Machine.FirstOrDefaultAsync(x => x.KlantId == id);
}
}
MachineController:
[ApiController]
[Route("api/Machine")]
[Produces("application/json")]
public class MachineController : ControllerBase
{
private readonly IRepository<MachineModel> _repo;
private readonly IMapper _mapper;
public MachineController(IRepository<MachineModel> repo, IMapper mapper)
{
_repo = repo;
_mapper = mapper;
}
[HttpGet("{id}")]
public async Task<IActionResult> GetServiceById(int id)
{
Console.WriteLine("This is the comming id ");
Console.WriteLine(id);
var model = await _repo.GetDataById(id);
return Ok(_mapper.Map<MachineDto>(model));
}
}
FirstOrDefaultAsync will return the top 1 record.
Change 1 - In C# MachineRepository, Instead of FirstOrDefaultAsync, replace this with Where and change the return type to List
Change 2 - In MachineController, Map it to List DTO
Note: I assume Machine is having array of records
public class MachineRepository: IRepository<MachineModel>
{
private readonly DataContext _context;
public MachineRepository(DataContext context)
{
_context = context;
}
// Get Service by id
public async Task<List<MachineModel>> GetDataById(int id) //change the return type to LIST
{
return await _context.Machine.Where(x => x.KlantId == id).ToListAsync();
}
}
[ApiController]
[Route("api/Machine")]
[Produces("application/json")]
public class MachineController : ControllerBase
{
private readonly IRepository<MachineModel> _repo;
private readonly IMapper _mapper;
public MachineController(IRepository<MachineModel> repo, IMapper mapper)
{
_repo = repo;
_mapper = mapper;
}
[HttpGet("{id}")]
public async Task<IActionResult> GetServiceById(int id)
{
Console.WriteLine("This is the comming id ");
Console.WriteLine(id);
var model = await _repo.GetDataById(id);
return Ok(_mapper.Map<List<MachineDto>>(model)); //Map to List
}
}
Related
I am learning to code in c# and I am developing an API application. My GET method works but I have issues with my DELETE, it returns a success code(200) but it does not delete from my database which is connected to my application. I am using the unit of work and repository patterns and my code is as follows:
Controller code:
private readonly IOrderService _orderService;
public OrdersController(IOrderService orderService)
{
_orderService = orderService;
}
[HttpDelete("{id}")]
public async Task<ActionResult> RemoveOrder(int id)
{
try
{
await _orderService.Delete(id);
return StatusCode(200);
}
catch (Exception ex)
{
return StatusCode(500);
}
}
Service Interface
public interface IOrderService
{
Task<Order> Get(int id);
Task Add(Order order);
Task Delete(int id);
Task Update(int id, Order order);
Task<IEnumerable<Order>> GetAllOrdersAsync();
Task<IEnumerable<OrderDTO>> GetOrdersToCityAsync(string cityName);
Task<OrderDTO> GetEmployeeOrdersToCountryAsync
(
string countryName, string employeeLastName
);
}
Service class:
public class OrderService : IOrderService
{
private readonly IUnitOfWork _unitOfWork;
public OrderService(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
public async Task Delete(int id)
{
try
{
var order = await _unitOfWork.OrderRepository.Get(id);
_unitOfWork.OrderRepository.Delete(order);
await _unitOfWork.CommitAsync();
}
catch (Exception e)
{
await _unitOfWork.RollbackAsync();
throw;
}
}
}
Unit of work class:
public class UnitOfWork : IUnitOfWork
{
private readonly NorthwindContext _db;
private List<object> _customRepositories = new List<object>();
private IProductRepository _productRepository;
private IOrderRepository _orderRepository;
public UnitOfWork(
NorthwindContext db,
IProductRepository ProductRepository,
IOrderRepository orderRepository
)
{
_db = db;
_productRepository = ProductRepository;
_orderRepository = orderRepository;
}
uow interface:
public interface IUnitOfWork
{
IProductRepository ProductRepository { get; }
IOrderRepository OrderRepository { get; }
Task CommitAsync();
Task RollbackAsync();
}
Order repository interface which extends my genericRepository:
public interface IOrderRepository : IGenericRepository<Order>
{
Task<IEnumerable<OrderDTO>> GetOrdersToCityAsync(string cityName);
Task<OrderDTO> GetEmployeeOrdersToCountryAsync
(
string countryName, string employeeLastName
);
}
Order repository:
public class OrderRepository : GenericRepository<Order>, IOrderRepository
{
private readonly NorthwindContext _northwindContext;
public OrderRepository(NorthwindContext db) : base(db)
{
_northwindContext = db;
}
generic repository:
public class GenericRepository<T> : IGenericRepository<T> where T : class
{
protected readonly NorthwindContext _db;
public GenericRepository(NorthwindContext db)
{
_db = db;
}
public void Delete(T entity)
{
_db.Set<T>().Remove(entity);
}
Please ignore if the curly braces are not closed properly, in my application they are.
Please check the following items:
Unit of work implementation calls SaveChanges() on CommitAsync implementation.
NorthwindContext object instance passed to repositories is the same instance as the one injected into UnitOfWork class. Otherwise you'll be working in different transactions.
You could delete an entry from database while using the following code.
[HttpDelete{"id"}]
Public async<Task<ActionResult>> DeleteItem (int id)
{ try{
Var itemToDelete = await UnitofWork.OrderRepository.GetAsync(id);
If (Id!= null) {
await Orderservice.Delete(itemToDelete);
var result = await UnitofWork.SaveChangesAsync();
If (result) return StatusCode(200);
Else return BadRequest();
return ok(itemToDelete);
} catch(exception ex) {
Throw new Error("Problem while deleting!");
}
Return BadRequest();
}
}
Just check if the item you would want ro delete is null or not. Then attempt to delete it from database. Hoping it would give you an idea.
As an exercise I was asked to moved logic from controller to service layer. With methods such as Get or GetById it wasn't a problem. Difficult part is to move Update and Patch methods. Below, you will find what I do have in Controler class and how I've changed them to the new requirement.
ToursController.cs before change
public class ToursController : ControllerBase
{
private readonly ToursRepository _repo;
private readonly IMapper _mapper;
public ToursController(ToursRepository repository, IMapper mapper)
{
_repo = repository ?? throw new ArgumentNullException(nameof(repository));
_mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
}
[HttpPut("{id}")]
public async Task<ActionResult> Update(int id, TourForUpdateDto updateDto)
{
var tour = await _repo.GetByIdAsync(id);
if (tour is null)
{
return NotFound();
}
_mapper.Map(updateDto, tour);
await _repo.SaveChangesAsync();
return NoContent();
}
[HttpPatch("{id}")]
public async Task<ActionResult> Patch(int id, JsonPatchDocument patchDocument)
{
var tour = await _repo.GetByIdAsync(id);
if (tour is null)
{
return NotFound();
}
var updateDto = _mapper.Map<TourForUpdateDto>(tour);
patchDocument.ApplyTo(updateDto);
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (!TryValidateModel(patchDocument))
{
return BadRequest(ModelState);
}
_mapper.Map(updateDto, tour);
await _repo.SaveChangesAsync();
return NoContent();
}
}
ToursController.cs after change
public class ToursController : ControllerBase
{
private readonly ToursRepository _repo;
private readonly IMapper _mapper;
private readonly ToursService _toursService;
public ToursController(ToursRepository repository, IMapper mapper)
{
_repo = repository ?? throw new ArgumentNullException(nameof(repository));
_mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
_toursService = toursService ?? throw new ArgumentNullException(nameof(toursService));
}
[HttpPut("{id}")]
public async Task<ActionResult> Update(int id, TourForUpdateDto updateDto)
{
if (!_toursService.Exists(id))
{
return NotFound();
}
await _toursService.Update(id, updateDto);
return NoContent();
}
[HttpPatch("{id}")]
public async Task<ActionResult> Patch(int id, JsonPatchDocument patchDocument)
{
// No idea how to update this method.
var tour = await _repo.GetByIdAsync(id);
if (tour is null)
{
return NotFound();
}
var updateDto = _mapper.Map<TourForUpdateDto>(tour);
patchDocument.ApplyTo(updateDto);
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (!TryValidateModel(patchDocument))
{
return BadRequest(ModelState);
}
_mapper.Map(updateDto, tour);
await _repo.SaveChangesAsync();
return NoContent();
}
}
ToursService.cs after change
using AutoMapper;
using BandManager.Api.Models;
using BandManager.Api.Services.Repositories;
public class ToursService
{
private readonly ToursRepository _repo;
private readonly IMapper _mapper;
public ToursService(ToursRepository repo, IMapper mapper)
{
_repo = repo ?? throw new ArgumentNullException(nameof(repo));
_mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
}
public async Task Update(int id, TourForUpdateDto updateDto)
{
var tour = await GetByIdAsync(id);
if (tour is null)
return;
_mapper.Map(updateDto, tour);
await _repo.SaveChangesAsync();
}
public async Task<TourDto?> GetByIdAsync(int id)
{
return _mapper.Map<TourDto>(await _repo.GetByIdAsync(id));
}
public bool Exists(int id)
{
return _repo.Exists(id);
}
}
Questions and thoughts
Should I ever return objects which are implementing IActionResult or any MVC related types from my services? My gut feeling is - no.
Should service be able to use AutoMapper to convert entities to DTOs? I think, yes.
Should my service, in the same method call, be able to call SaveChangesAsync on repository?
I'm currently working with JsonResult. There's a problem: if I call JsonResult in my UserService, I won't be able to call it with a parameter, but if I call it like the same in the UserController, it works with my parameter. So my question now is, the architecture is the same as in the controller so what's wrong?
UserService.cs:
public class UserService : IUserService
{
private readonly IMapper mapper;
private readonly ILogger<UserService> logger;
public UserService(
IMapper mapper,
ILogger<UserService> logger)
{
this.mapper = mapper;
this.logger = logger;
}
private static IList<Contact> GetStaticContacts(string fileName)
{
var jsonText = System.IO.File.ReadAllText(fileName);
var contacts = JsonSerializer.Deserialize<IList<Contact>>(jsonText);
return JsonResult(contacts);
}
Task<IList<Contact>> IUserService.GetNationalCoordinators()
{
return new JsonResult(GetStaticContacts("Test1.json"));
}
Task<IList<Contact>> IUserService.GetLocalCoordinators()
{
return new JsonResult(GetStaticContacts("Test2.json"));
}
Task<IList<Contact>> IUserService.GetMedicalAdvisors()
{
return new JsonResult(GetStaticContacts("Test3.json"));
}
}
UsersController:
public async Task<IActionResult> GetLocalCoordinators(CancellationToken cancellationToken = default)
{
var contacts = await userService.GetLocalCoordinators();
var result = mapper.Map<IList<ContactDto>>(contacts);
return new JsonResult(result);
}
Check this out -> JsonResult in services layer
Also the documentation about JsonResult here https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.jsonresult?view=aspnetcore-6.0 specifies that the JsonResult object comes from ActionResult which is used in the Controller.
It was working earlier before adding actionedService which is similar to rejectionService, throws following error
An unhandled exception occurred while processing the request.
DependencyResolutionException: None of the constructors found with 'Autofac.Core.Activators.Reflection.DefaultConstructorFinder' on type 'Sx.Workflow.Server.Controllers.ReportController' can be invoked with the available services and parameters:
Cannot resolve parameter 'Sx.Workflow.Reporting.Services.IActionedService actionedService' of constructor 'Void .ctor(NLog.ILogger, AutoMapper.IMapper, Sx.Workflow.Reporting.Services.IRejectionService, Sx.Workflow.Reporting.Services.IActionedService)'.
Autofac.Core.Activators.Reflection.ReflectionActivator.GetValidConstructorBindings(ConstructorInfo[] availableConstructors, IComponentContext context, IEnumerable parameters) in ReflectionActivator.cs, line 160
Controller
namespace Sx.Workflow.Server.Controllers
{
[MenuItem("report")]
[ServiceFilter(typeof(SettingsFilter))]
[Authorize(Policy = Security.Constants.RolePolicy)]
public class ReportController : Controller
{
private readonly ILogger _logger;
private readonly IMapper _mapper;
private readonly IRejectionService _rejectionService;
private readonly IActionedService _actionedService;
public ReportController(ILogger logger, IMapper mapper, IRejectionService rejectionService, IActionedService actionedService)
{
_logger = logger;
_mapper = mapper;
_rejectionService = rejectionService;
_actionedService = actionedService;
}
[HttpGet]
public IActionResult Index()
{
_logger.Info("Report Controller");
return View();
}
[HttpPost]
[ApiExceptionFilter]
public async Task<IActionResult> Reject(RejectionReportRequestDto criteria)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
_logger.Info("Generate Rejection Report");
var result = await _rejectionService.Generate(criteria.From, criteria.To);
var items = _mapper.Map<RejectionReportDto>(result);
return Ok(items);
}
[HttpPost]
[ApiExceptionFilter]
public async Task<IActionResult> Actioned(ActionedReportRequestDto criteria)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
_logger.Info("Generate Actioned Report");
var result = await _actionedService.Generate(criteria.From, criteria.To);
var items = _mapper.Map<ActionedReportDto>(result);
return Ok(items);
}
}
}
Handler
namespace Sx.Workflow.Reporting.Handlers
{
public class ActionedReportHandler : IHandleEvent<ApplicationActionedEvent>
{
private readonly IActionedService _service;
public ActionedReportHandler(IActionedService service)
{
_service = service;
}
public Task Handle(ApplicationActionedEvent args)
{
var actioned = new Actioned
{
ApplicationNumber = args.ApplicationNumber,
AssigneeFrom = args.AssigneeFrom,
AssigneeTo = args.AssigneeTo,
DepartmentFrom = args.DepartmentFrom.Name,
DepartmentTo = args.DepartmentTo.Name,
Reason = args.RejectReasonName,
Comments = args.RejectReasonText,
RejectionDate = DateTime.Now
};
return _service.Save(actioned);
}
}
}
Service
namespace Sx.Workflow.Reporting.Services
{
public class ActionedService : IActionedService
{
private readonly ISaveActioned _saveActioned;
private readonly IGenerateActionedReport _actionedReport;
public ActionedService(ISaveActioned saveActioned, IGenerateActionedReport actionedReport)
{
_saveActioned = saveActioned;
_actionedReport = actionedReport;
}
public Task<ActionedReport> Generate(DateTime from, DateTime to)
{
return _actionedReport.Generate(from, to);
}
public Task Save(Actioned actioned)
{
return _saveActioned.Save(actioned);
}
}
}
Interface
namespace Sx.Workflow.Reporting.Services
{
public interface IActionedService
{
Task Save(Actioned actioned);
Task<ActionedReport> Generate(DateTime from, DateTime to);
}
}
Service Module
public class ServiceModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<RejectionService>()
.As<IRejectionService>()
.InstancePerLifetimeScope();
builder.RegisterType<ActionedService>()
.As<IActionedService>()
.InstancePerLifetimeScope();
}
}
Makes sense. While you are registering the type in DI, you have nothing for:
public ActionedService(ISaveActioned saveActioned, IGenerateActionedReport actionedReport)
So autofac assumes that there must be an empty constructor in ActionedService
So there are 2 solutions:
Remove the constructor parameters and create them without DI
Create the registrations for the two parameters of the constructor. Something like the following:
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<RejectionService>()
.As<IRejectionService>()
.InstancePerLifetimeScope();
builder.RegisterType<ActionedService>()
.As<IActionedService>()
.InstancePerLifetimeScope();
builder.RegisterType<SaveActioned>()
.As<ISaveActioned>()
.InstancePerLifetimeScope();
builder.RegisterType<GenerateActionedReport>()
.As<IGenerateActionedReport>()
.InstancePerLifetimeScope();
}
I have a method that returns a view, and in that view I want to display a list of something. In order to do this, I'll need a model with houses the list.
Microsoft's documentation (https://learn.microsoft.com/en-us/aspnet/core/mvc/overview?view=aspnetcore-2.2) says that it is the responsibility of the controller to select and work with the model(s) and that the responsibility of the model is to encapsulate business logic. This being said, I'm unsure of the best practice:
Logic in the controller:
Model:
public class DepartmentViewModel
{
public IEnumerable<DepartmentDto> lstDepartments { get; set; }
}
Controller:
public class DepartmentController : Controller
{
private readonly IUnitOfWork _work;
private readonly IMapper _mapper;
public DepartmentController(IUnitOfWork work, IMapper mapper)
{
_work = work;
_mapper = mapper;
}
public async Task<IActionResult> Index(DepartmentViewModel viewmodel)
{
var lstAllDepartments = _work.DepartmentRepository.GetAll(); // All departments from the database.
var lstDepartmentsForViewmodel = _mapper.Map<IEnumerable<Core.Entities.Department>, IEnumerable<DepartmentDto>>(lstAllDepartments); // Map to DTO.
viewmodel.lstDepartments = lstDepartmentsForViewmodel;
return View(viewmodel);
}
}
Logic in the model:
Model:
public class DepartmentViewModel
{
private readonly IUnitOfWork _work;
private readonly IMapper _mapper;
public DepartmentViewModel(IUnitOfWork work, IMapper mapper)
{
_work = work;
_mapper = mapper;
var lstAllDepartments = _work.DepartmentRepository.GetAll(); // All departments from the database.
var lstDepartmentsForViewmodel = _mapper.Map<IEnumerable<Core.Entities.Department>, IEnumerable<DepartmentDto>>(lstAllDepartments); // Map to DTO.
lstDepartments = lstDepartmentsForViewmodel;
}
public IEnumerable<DepartmentDto> lstDepartments { get; set; }
}
Controller:
public class DepartmentController : Controller
{
private readonly IUnitOfWork _work;
private readonly IMapper _mapper;
public DepartmentController(IUnitOfWork work, IMapper mapper)
{
_work = work;
_mapper = mapper;
}
public async Task<IActionResult> Index()
{
DepartmentViewModel viewmodel = new DepartmentViewModel(_work, _mapper);
return View(viewmodel);
}
}
Guidance of any kind would be most appreciated.
I would recommend you go for the first approach
Logic in the controller:
Model:
public class DepartmentViewModel
{
public IEnumerable<DepartmentDto> lstDepartments { get; set; }
}
Controller:
public class DepartmentController : Controller
{
private readonly IUnitOfWork _work;
private readonly IMapper _mapper;
public DepartmentController(IUnitOfWork work, IMapper mapper)
{
_work = work;
_mapper = mapper;
}
public async Task<IActionResult> Index(DepartmentViewModel viewmodel)
{
var lstAllDepartments = _work.DepartmentRepository.GetAll(); // All departments from the database.
var lstDepartmentsForViewmodel = _mapper.Map<IEnumerable<Core.Entities.Department>, IEnumerable<DepartmentDto>>(lstAllDepartments); // Map to DTO.
viewmodel.lstDepartments = lstDepartmentsForViewmodel;
return View(viewmodel);
}
}
Best practise is using DI to init services or dependency value in your constructor. If you go for second approach you have to send the data to the constructor like this
DepartmentViewModel viewmodel = new DepartmentViewModel(_work, _mapper);
And that is not ideally what if you have a lot of model ?