Dependency injection net core InvalidOperationException - c#

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);

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?

JSON deserialization of polymorphic and complex objects in ASP.Net Core without Newtonsoft

Deserialization of polymorphic and complex objects in ASP.Net is a well know topic.
Common solutions I came across rely on JsonConverter or JsonSubTypes.
However, the challenge here is NOT to use Newtonsoft.Json at all but rely on the new System.Text.Json and Microsoft.AspNetCore.Mvc.ModelBinding instead. The reason: my classes already are heavily 'Netwtonsoft decorated' but this decoration (class/property attributes) is optimized and customized for purposes other than ASP.Net deserialization.
Microsoft has a solution relying on ModelBinder attribute described here. I am able to correctly deserialize polymorphic objects but not complex objects. That is, polymorphic objects containing collection of other, non-polymorphic objects do not get deserialized properly.
public abstract class Vehicle
{
public abstract string Kind { get; set; }
public string Make { get; set; }
public RepairRecord[]? RepairHistory { get; set; }
public override string ToString()
{
return JsonSerializer.Serialize(this);
}
}
public class Car : Vehicle
{
public override string Kind { get; set; } = nameof(Car);
public int CylinderCount { get; set; }
}
public class Bicycle : Vehicle
{
public override string Kind { get; set; } = nameof(Bicycle);
public bool HasStand { get; set; }
}
public class RepairRecord
{
public DateTime DateTime { get; set; }
public string Description { get; set; }
}
[HttpPut]
public IActionResult Create([ModelBinder(typeof(VehicleModelBinder))] Vehicle vehicle)
{
_logger.LogInformation(vehicle.ToString());
return new OkResult();
}
The problem: deserialized vehicle is missing RepairHistory records in the Create() method.
What am I missing? Please advise.
Complete, working code below.
using System.Text.Json;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers(options =>
{
options.ModelBinderProviders.Insert(0, new VehicleModelBinderProvider());
});
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
{
c.UseAllOfForInheritance(); // enabling inheritance - this allows to maintain the inheritance hierarchy in any generated client model
});
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
public abstract class Vehicle
{
public abstract string Kind { get; set; }
public string Make { get; set; }
public RepairRecord[]? RepairHistory { get; set; }
public override string ToString()
{
return JsonSerializer.Serialize(this);
}
}
public class Car : Vehicle
{
public override string Kind { get; set; } = nameof(Car);
public int CylinderCount { get; set; }
}
public class Bicycle : Vehicle
{
public override string Kind { get; set; } = nameof(Bicycle);
public bool HasStand { get; set; }
}
public class RepairRecord
{
public DateTime DateTime { get; set; }
public string Description { get; set; }
}
[ApiController]
[Route("")]
public class Controller : ControllerBase
{
private readonly ILogger<Controller> _logger;
public Controller(ILogger<Controller> logger)
{
_logger = logger;
}
[HttpPost]
public IActionResult Create([ModelBinder(typeof(VehicleModelBinder))] Vehicle vehicle)
{
_logger.LogInformation(vehicle.ToString());
return new OkResult();
}
}
public class VehicleModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context.Metadata.ModelType != typeof(Vehicle))
{
return null;
}
var subclasses = new[] { typeof(Car), typeof(Bicycle), };
var binders = new Dictionary<Type, (ModelMetadata, IModelBinder)>();
foreach (var type in subclasses)
{
var modelMetadata = context.MetadataProvider.GetMetadataForType(type);
binders[type] = (modelMetadata, context.CreateBinder(modelMetadata));
}
return new VehicleModelBinder(binders);
}
}
public class VehicleModelBinder : IModelBinder
{
private Dictionary<Type, (ModelMetadata, IModelBinder)> binders;
public VehicleModelBinder(Dictionary<Type, (ModelMetadata, IModelBinder)> binders)
{
this.binders = binders;
}
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
var modelKindName = ModelNames.CreatePropertyModelName(bindingContext.ModelName, nameof(Vehicle.Kind));
var modelTypeValue = bindingContext.ValueProvider.GetValue(modelKindName).FirstValue;
IModelBinder modelBinder;
ModelMetadata modelMetadata;
if (modelTypeValue == nameof(Car))
{
(modelMetadata, modelBinder) = binders[typeof(Car)];
}
else if (modelTypeValue == nameof(Bicycle))
{
(modelMetadata, modelBinder) = binders[typeof(Bicycle)];
}
else
{
bindingContext.Result = ModelBindingResult.Failed();
return;
}
var newBindingContext = DefaultModelBindingContext.CreateBindingContext(
bindingContext.ActionContext,
bindingContext.ValueProvider,
modelMetadata,
bindingInfo: null,
bindingContext.ModelName);
await modelBinder.BindModelAsync(newBindingContext);
bindingContext.Result = newBindingContext.Result;
if (newBindingContext.Result.IsModelSet)
{
// Setting the ValidationState ensures properties on derived types are correctly
bindingContext.ValidationState[newBindingContext.Result.Model] = new ValidationStateEntry
{
Metadata = modelMetadata,
};
}
}
}
Unfortunately, currently (September 2022) there is no good solution available.
See my discussion with Microsoft here.
Supposedly, the problem will be solved by [JsonDerivedType] attibute when .Net 7 becomes available.

.Net Core 3 AutoMapperMappingException: Missing type map configuration or unsupported mapping

Here's how I setup the auto mapper in startUp.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddDbContext<BikeStoreContext>(
options => options.UseSqlServer(Configuration.GetConnectionString("bikeStore")));
services.AddScoped<ICustomerRepository, CustomerRepository>();
services.AddAutoMapper(typeof(CustomerProfile));
}
The profile.cs
public class CustomerProfile : Profile
{
public CustomerProfile()
{
CreateMap<Customer,CustomerDto>();
CreateMap<CustomerDto, Customer>();
}
}
CustomerDto.cs
public class CustomerDto
{
public int CustomerId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Phone { get; set; }
public string Email { get; set; }
public string Street { get; set; }
public string City { get; set; }
public string State { get; set; }
public string ZipCode { get; set; }
}
}
and this is the getCustomers function in my repository
public async Task<IEnumerable<Customer>> GetCustomers()
{
return await _context.Customers.ToListAsync();
}
I use autoMapper in the controller as below
[HttpGet]
public async Task<IActionResult> GetAsync ()
{
var result = await _repo.GetCustomers();
return Ok(_mapper.Map<CustomerDto>(result));
}
And I get the below error when trying to map Customer to CustomerDto
AutoMapperMappingException: Missing type map configuration or unsupported mapping.
I tried to change the configuration in startUp.cs as below but it didn't work.
var mapperConfig = new MapperConfiguration(mc =>
{
mc.AddProfile(new CustomerProfile());
});
IMapper mapper = mapperConfig.CreateMapper();
services.AddSingleton(mapper);
What am I doing wrong here?
Repository returns IEnumerable<Customer> and you are trying to map it to single CustomerDTO, you need to map to collection, for example List or just another IEnumerable:
return Ok(_mapper.Map<List<CustomerDto>>(result));

automapper exception when add Id in ViewModel

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.

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; }
}

Categories