automapper exception when add Id in ViewModel - c#

This is my Model in Model project
public class CompanyName
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime CreateDate { get; set; }
public DateTime ModifyDate { get; set; }
public ICollection<CarModel> CarModels { get; set; }
public ICollection<CreateYear> CreateYear { get; set; }
public ICollection<Diversity> Diversities { get; set; }
public ICollection<CarPrice> CarPrices { get; set; }
}
and this is ViewModel for that
public class CompanyNameVM
{
public int Id { get; set; }
public string Name { get; set; }
}
Also this is my Code for p=mapping in ViewModel Project
public class MiMapping:Profile
{
public MiMapping()
{
CreateMap<Task<CompanyName>, Task<CompanyNameVM>>().ReverseMap();
}
}
after that for use it in Api I use this block
[Route("api/[controller]")]
[ApiController]
public class CompanyNameController : ControllerBase
{
private ICompanyNameRepository _companyNameRepository;
private IMapper _mapper;
public CompanyNameController(ICompanyNameRepository companyNameRepository,IMapper mapper)
{
_companyNameRepository = companyNameRepository;
_mapper = mapper;
}
[HttpGet]
public async Task<IActionResult> GetCarCompanyAsync()
{
var cmObj = await _companyNameRepository.GetAllAsync();
var cvvm = new List<CompanyNameVM>();
foreach (var obj in cmObj)
{
cvvm.Add(_mapper.Map<CompanyNameVM>(obj));
}
return Ok( cvvm);
}
}
after run when I call -
http://localhost:53199/api/companyName
got this error
AutoMapperMappingException: Missing type map configuration or unsupported mapping.
Mapping types:
Object -> CompanyNameVM
System.Object -> MI.ViewModel.CompanyNameVM
It worked fine until I add Id in My ViewModel.
how can I resolve that

If you want to create a map between CompanyName and CompanyNameVM then do it like -
CreateMap<CompanyName, CompanyNameVM>().ReverseMap();
No need to involve Task in mapping.
Also, no need to manually iterate over the CompanyName objects and map one at a time. You can simplify your controller code as -
[HttpGet]
public async Task<IActionResult> GetCarCompanyAsync()
{
var cmObj = await _companyNameRepository.GetAllAsync();
var cvvm = _mapper.Map<IEnumerable<CompanyNameVM>>(cmObj);
return Ok(cvvm);
}
I'm assuming _companyNameRepository.GetAllAsync() is returning an IEnumerable<CompanyName> in some form.

Related

When trying to join 2 tables, linked data is not loaded In ASP.NET Core

I am starting to learn ASP.NET Core and EF, I have a MySQL test database: https://i.stack.imgur.com/9PfL0.png
I read this: https://learn.microsoft.com/en-us/ef/core/querying/related-data/eager and https://learn.microsoft.com/en-us/ef/core/modeling/relationships?tabs=fluent-api%2Cfluent-api-simple-key%2Csimple-key#other-relationship-patterns but I'm not sure if I did it right.
Here is some of my code:
public class StoreDbContext : DbContext
{
public StoreDbContext(DbContextOptions<StoreDbContext> options)
: base(options) { }
public DbSet<Product> Product => Set<Product>();
public DbSet<Type_Product> Type_Product => Set<Type_Product>();
}
IStoreRepository.cs
public interface IStoreRepository
{
IQueryable<Product> Product { get; }
IQueryable<Type_Product> Type_Product { get; }
}
EFStoreRepository.cs
public class EFStoreRepository : IStoreRepository
{
private StoreDbContext context;
public EFStoreRepository(StoreDbContext ctx)
{
context = ctx;
}
public IQueryable<Product> Product => context.Product;
public IQueryable<Type_Product> Type_Product => context.Type_Product;
}
Product.cs
public class Product
{
[Key]
public long? productID { get; set; }
[ForeignKey("type_productID")]
public long? type_productID { get; set; }
public virtual Type_Product? Type_Product { get; set; }
public string name_product { get; set; } = String.Empty;
}
Type_Product.cs
public class Type_Product
{
[Key]
public long? type_productID { get; set; }
public string name_type_product { get; set; } = String.Empty;
public virtual ICollection<Product>? Product { get; set; }
}
HomeController.cs
private IStoreRepository repository;
public HomeController(IStoreRepository repo)
{
repository = repo;
}
ViewResult Index()
{
var t = repository.Product.Include(o => o.Type_Product).ToList();
return View(t);
}
Index.cshtml
#model IEnumerable<ClothingShop.Models.Product>
#foreach (var p in Model)
{
<p>#p. name_product </p> // normal
}
#foreach (var k in Model)
{
<p>#k.Type_Product.name_type_product</p> /error
}
I am getting an error:
Object reference not set to an instance of an object
since TypeProduct = null.
Is this due to the incorrect use of .Include in HomeController?
Because
var t = repository.Product.Include(o => o.Type_Product);
Type_Product belongs to Product, not repository.Type_Product?
Should I explicitly initialize Type_Product somewhere?
How to bring it to a working condition?

Creating an Insert Operation, however AFTER converting from the Model StudentView -> Student And Pass into InsertStudentAsync , the func returns null

I was trying to unit test a simple Create Operation using the Auto Mapper, I don't understand why the null is being returned.
The Data Model or Entity Model or Model that is used to Access the Database:
public partial class Student
{
public Guid Id { get; set; }
public string UserId { get; set; }
public string IdentityNumber { get; set; }
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string LastName { get; set; }
public DateTimeOffset BirthDate { get; set; }
public int Gender { get; set; }
public DateTimeOffset CreatedDate { get; set; }
public DateTimeOffset UpdatedDate { get; set; }
public Guid CreatedBy { get; set; }
public Guid UpdatedBy { get; set; }
}
The Model that is used for the Business Logic is:
public class StudentView
{
public Guid Id { get; set; }
public string UserId { get; set; }
public string IdentityNumber { get; set; }
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string LastName { get; set; }
public DateTimeOffset BirthDate { get; set; }
public Gender Gender { get; set; }
public DateTimeOffset CreatedDate { get; set; }
public DateTimeOffset UpdatedDate { get; set; }
public Guid CreatedBy { get; set; }
public Guid UpdatedBy { get; set; }
}
public enum Gender
{
Male,
Female,
Other
}
The Function that is being used to Call the operation:
public async Task<StudentView> RegisterStudentAsync(StudentView student)
{
Student inputStudent =
this.mapper.Map<Student>(student);
Student storageStudent =
await this.storageBroker.InsertStudentAsync(inputStudent);
StudentView storageStudentView =
this.mapper.Map<StudentView>(storageStudent);
return storageStudentView;
}
The AutoMapper Configuration using Profile:
public class MappingProfiles : Profile
{
public MappingProfiles() =>
CreateMap<StudentView, Student>()
.ReverseMap();
}
This is Added in the Startup to make my project know this profile:
services.AddAutoMapper(typeof(MappingProfiles));
There is no Inversion of Controll (IOC), so I ve instantiated the mapper configuration and made it known as a singleton that a mapperProfile exist, so that I can use the configured mapping.
public partial class StudentServiceTests
{
private readonly Mock<ILoggingBroker> loggingBrockerMock;
private readonly Mock<IStorageBroker> storageBrokerMock;
private readonly Mock<IDateTimeBroker> dateTimeBrokerMock;
private readonly IMapper mapperMock;
private readonly IStudentService studentService;
public StudentServiceTests()
{
this.loggingBrockerMock = new Mock<ILoggingBroker>();
this.storageBrokerMock = new Mock<IStorageBroker>();
this.dateTimeBrokerMock = new Mock<IDateTimeBroker>();
var configurationMapper = new MapperConfiguration(
options =>
options.AddProfile(new MappingProfiles()));
IMapper mapper = configurationMapper.CreateMapper();
this.mapperMock = mapper;
this.studentService = new StudentService(
loggingBroker: this.loggingBrockerMock.Object,
dateTimeBroker: this.dateTimeBrokerMock.Object,
storageBroker: this.storageBrokerMock.Object,
mapper:this.mapperMock);
}
The Unit test that was created:
[Fact]
public async Task ShouldRegisterStudentWhenPassedIn()
{
// Arrange - Given
StudentView randomStudentView = CreateRandomStudent();
StudentView inputStudentView = randomStudentView;
Student inputStudent =
this.mapperMock.Map<StudentView, Student>(inputStudentView);
Student returnedStorageStudent = inputStudent;
StudentView expectedStudentView = inputStudentView;
this.storageBrokerMock.Setup(broker =>
broker.InsertStudentAsync(inputStudent))
.ReturnsAsync(returnedStorageStudent);
// When - Act
StudentView actualStudent =
await this.studentService.RegisterStudentAsync(inputStudentView);
// Assert - Then
actualStudent.Should().BeEquivalentTo(expectedStudentView);
this.storageBrokerMock.Verify(brocker =>
brocker.InsertStudentAsync(inputStudent),
Times.Once);
this.storageBrokerMock.VerifyNoOtherCalls();
this.loggingBrockerMock.VerifyNoOtherCalls();
this.dateTimeBrokerMock.VerifyNoOtherCalls();
}
the Result that is being obtained:
public async Task<StudentView> RegisterStudentAsync(StudentView student)
{
// converts correctly
Student inputStudent =
this.mapper.Map<Student>(student);
// the storageStudent is Null, even though the inputStudent has
// values in the properties
Student storageStudent =
await this.storageBroker.InsertStudentAsync(inputStudent);
StudentView storageStudentView =
this.mapper.Map<StudentView>(storageStudent);
return storageStudentView;
}
The Function that is used to Hit the Database:
public async Task<Student> InsertStudentAsync(Student student)
{
EntityEntry<Student> entityEntryStudent =
await this.Students.AddAsync(student);
await this.SaveChangesAsync();
return entityEntryStudent.Entity;
}
If you like to reproduce, I can share my repository.
You are getting null, because mock is not configured correctly.
this.storageBrokerMock
.Setup(broker => broker.InsertStudentAsync(inputStudent))
.ReturnsAsync(returnedStorageStudent);
Configuration above will return returnedStorageStudent only when reference of inputStudent is passed to the InsertStudentAsync method.
Notice that in "production" code mapper will create new instance of Student.
So question is not about Automapper or Xunit, but about the mock framework you are using - How to configure mock to verify that correct object passed to the method?
Test can look like below
[Fact]
public async Task ShouldRegisterStudentWhenPassedIn()
{
var givenView = CreateRandomStudent();
var createdId = Guid.NewGuid();
this.storageBrokerMock
.Setup(broker => broker.InsertStudentAsync(It.IsAny<Student>()))
.ReturnsAsync(given =>
{
Id = createdId,
UserId = given.UserId,
// ..
});
var actualView = await this.studentService.RegisterStudentAsync(givenView);
var expectedView = new StudentView
{
Id = createdId,
UserId = givenView.UserId,
// ..
}
actualView.Should().BeEquivalentTo(expectedView);
}

Dependency injection net core InvalidOperationException

I'm new to AutoMapper and I'm trying to build an API. The default endpoint that comes when starting new API project (WeatherForecast) works fine. However when I'm trying to reach the endpoint api/nationalparks, I get this error:
Controller:
namespace NationalParksAPI.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class NationalParksController : Controller
{
private INationalParkRepository _npRepo;
private ParkyMappings _parksMapper;
public NationalParksController(INationalParkRepository npRepo, ParkyMappings parksMapper )
{
_npRepo = npRepo;
_parksMapper = parksMapper;
}
public IActionResult GetNationalParks()
{
var objList = _npRepo.GetNationalParks();
return Ok(objList);
}
}
}
Mapper:
public class ParkyMappings : Profile
{
public ParkyMappings()
{
CreateMap<NationalParkEntity, NationalParkDTO>().ReverseMap();
}
}
Configure services in start up (Note that AddAutoMapper method is working without using the package AutoMapper, unlike the tutorial I follow):
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContextPool<NationalParksDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("ParksDBConnection")));
services.AddScoped<INationalParkRepository, NationalParkRepository>();
services.AddAutoMapper(typeof(ParkyMappings));
services.AddControllers();
}
Entity:
public class NationalParkEntity
{
[Key]
public int Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public string state { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime Eistablished { get; set; }
}
DTO:
public class NationalParkDTO
{
public int Id { get; set; }
public string Name { get; set; }
public string state { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime Eistablished { get; set; }
}
Any inputs will be appreciated.
You shouldn't have ParkyMappings as a dependency of Controller. ParkyMappings (as a Profile subclass) is only used to initialize Automapper at the start of the application. There is no purpose for it to be in the Controller.
You should pass IMapper mapper to the Controller instead:
namespace NationalParksAPI.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class NationalParksController : Controller
{
private INationalParkRepository _npRepo;
private IMapper _mapper;
public NationalParksController(INationalParkRepository npRepo, IMapper mapper)
{
_npRepo = npRepo;
_mapper = mapper;
}
public IActionResult GetNationalParks()
{
var objList = _npRepo.GetNationalParks();
var entityList = _mapper.Map<NationalParkEntity[]>(objList)
return Ok(entityList);
}
}
}
And, perhaps, use this registration for Automapper instead:
services.AddAutoMapper(this.GetType().Assembly);

C# ASP.NET Core 2.1/Entity Framework: Entity collention dosen't saving

I have a simple ASP.NET Core (2.1) API that implements get & post methods with objects of this class:
public class Command
{
public uint id { get; set; }
public string command { get; set; }
public List<Client> clients { get; set; }
}
public class Client
{
public uint id { get; set; }
public string nameofpc { get; set; }
}
public class CommandContext : DbContext
{
public CommandContext(DbContextOptions<CommandContext> options) : base(options) { }
public DbSet<Command> Commands { get; set; }
}
I send POST request with this entity:
var command = new Command()
{
command = "/start^cmd.exe",
clients = new List<Client>()
{
new Client()
{
nameofpc = "Zerumi"
}
}
};
// Converting to JSON and sending to api...
In CommandController.cs located this code:
[Route("api/[controller]")]
[ApiController]
public class CommandController : ControllerBase
{
private readonly CommandContext _context;
public CommandController(CommandContext context)
{
_context = context;
}
[HttpGet]
public async Task<ActionResult<IEnumerable<Command>>> GetCommands()
{
return await _context.Commands.ToListAsync();
}
[HttpPost]
public async Task<ActionResult<Command>> PostCommand([FromBody] Command item)
{
_context.Commands.Add(item);
await _context.SaveChangesAsync();
return CreatedAtAction(nameof(GetCommand), new { item.id }, item);
}
}
The item parameter of the postcommand method is no different from what was sent. However, if i send GET request to /command after saving, i will get this:
[{"id":1,"command":"/start^cmd.exe","clients":null}]
Why colleсtion is null and what i need to do for good entity saving?
To me, it seems that something is missing, but maybe you configure stuff in OnModelCreating, hard to tell when I don't have your code. And you should use Pascal-casing in your EF-code and replace uint with int.
Then you should add DTO-classes (model-classes) for both Command and Client. Decorate each property in DTO with e.g.
[JsonProperty("command")]
in order to maintain correct casing (camel-casing).
public class Command
{
public uint id { get; set; }
public string command { get; set; }
public List<Client> clients { get; set; }
}
public class Client
{
public int CommandId { get; set; } // foreign key
[ForeignKey(nameof(CommandId))]
public Command Command { get; set; }
public uint id { get; set; }
public string nameofpc { get; set; }
}

How to send data to view?

I have tables, to which I can send data, everything is set up, now I only need to understand, how to send data to view. When I want to get data using Model word, I'm getting error: NullReferenceException: Object reference not set to an instance of an object.
index.cshtml
#model FaqVM
#{
Layout = "_Layout";
}
#Model.MainCategoryTitle // Error
Faq.cs
public class Faq : CanBeLocalized, IHaveIntegerId, ICanProvideCreatedTime, IHaveConcurrencyToken
{
public int Id { get; set; }
[Localize] public string MainCategoryTitle { get; set; }
public DateTime Created { get; set; } = DateTime.Now;
public string ConcurrencyToken { get; set; }
public DateTime? Disabled { get; set; }
public int SOrder { get; set; } = 0;
public DateTime? Updated { get; set; }
public ICollection<FaqSubcategory> FaqSubcategories { get; set; } = new HashSet<FaqSubcategory>();
}
FaqVM.cs
public class FaqVM
{
public string MainCategoryTitle { get; set; }
public List<FaqSubcategory> FaqSubcategories { get; set; }
}
public class CmsController : Controller
{
protected ILocale _locale;
protected ICacheManager _cacheManager;
protected RegionProvider _regionProvider;
protected IViewRenderService _viewRenderService;
private IServiceProvider _serviceProvider;
//...
}
public class FaqController : CmsController
{
private readonly Faq _faq;
public FaqController(
RegionProvider regionProvider,
ILocaleAccessor localeAccessor,
ILiteralProvider literalProvider,
CartRepo cartRepo,
IServiceProvider serviceProvider,
Faq faq) : base(serviceProvider)
{
_faq = faq;
}
public async Task<IActionResult> Index()
{
var vm = new FaqVM
{
MainCategoryTitle = _faq.MainCategoryTitle,
FaqSubcategories = _faq.FaqSubcategories.ToList()
};
return View(vm);
}
}
Inside your Index() method in your FaqController you need to create your ViewModel as fx
var vm = new FaqVM(){
MainCategoryTitle = "test",
FaqSubcategories = new List<FaqSubcategory>(){ new FaqSubcategory() }
}
and then you need to pass the ViewModel to the return part of your Index method:
return View(vm);
Typically you would create your vm from some data from a database or some user input.

Categories