Cannot map Interface to a class with AutoMapper, but manually I can - asp.net-web-api

I currently work on a small ASP.NET Core Web API Project. In my "VehicleModelsController" I have a class "VehicleModel":
public class VehicleModel
{
public Guid Id { get; set; }
public string Name { get; set; }
public Guid VehicleMakeId { get; set; }
}
In the controller I also have a method for retrieving single vehicle model:
// GET api/<controller>/Guid
[HttpGet("{id}")]
public async Task<IActionResult> GetModelById(Guid id)
{
var model = await Service.GetVehicleModelById(id);
if (model == null)
return NotFound();
var vehicleModel = new VehicleModelsController.VehicleModel()
{
Id = model.Id,
Name = model.Name,
VehicleMakeId = model.VehicleMakeId,
};
return Ok(vehicleModel);
}
As you can see in this method I am calling "GetVehicleModelById" method in my Service which is defined as follows:
public async Task<IVehicleModel> GetVehicleModelById(Guid vehicleModelId)
{
return await Repository.GetModelById(vehicleModelId);
}
As you can see, it returns vehicle model object of type "IVehicleModel" which is an interface defined as follows:
public interface IVehicleModel
{
Guid Id { get; set; }
string Name { get; set; }
Guid VehicleMakeId { get; set; }
}
This everything works when I'm doing manual mapping of IVehicleModel interface to a VehicleModel class in controller, as you already seen above, but when I try to do mapping with AutoMapper in controller, like this:
var vehicleModel = AutoMapper.Mapper.Map<VehicleModelsController.VehicleModel>(model);
I get an error:
AutoMapperMappingException: Missing type map configuration or unsupported mapping.
Why is that so? Why can't I do the same with Automapper, what I already done manually?
I have defined mapping in my Mapping Profile class:
CreateMap<VehicleModelsController.VehicleModel, IVehicleModel>().ReverseMap();
so that is not a problem.
EDIT
This is my Mapping Profile class:
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<VehicleModelsController.VehicleModel, IVehicleModel>().ReverseMap();
}
}
In "ConfigureServices" method in "Startup.cs" class I have:
services.AddAutoMapper();
EDIT #2
This is exact error that I get:
AutoMapper Mapping Exception

From Automapper Source, now you can pass an assembly containing your automapper profile implementation or any type from that assembly.
Also mentioned in ReadMe page
To use, with an IServiceCollection instance and one or more assemblies:
services.AddAutoMapper(assembly1, assembly2 /*, ...*/);
or marker types:
services.AddAutoMapper(type1, type2 /*, ...*/);
So you can pass Startup.cs as marker type as below
services.AddAutoMapper(typeof(Startup));
Or your assembly containing Automapper implementation class.

Related

Is there something like [Bind(Exclude="Property")] for asp.net web api?

I'm trying to exclude a property from my Post Action in a web api controller, is there something like [Bind(Exclude="Property")] for asp.net web api?
This is my model:
public class ItemModel
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
I want to exclude the Id in the Post Action, because it is autogenerated, but I need to return it in my Get Action.
I Know I could have two models, one for my Post action and one for my Get action, but I'm trying to do this with just one model.
I would favour mapping models but this could be achieved by checking if the request is a POST in a ShouldSerialize method:
public class MyModel
{
public string MyProperty1 { get; set; }
public string MyProperty2 { get; set; }
public bool ShouldSerializeMyProperty2()
{
var request = HttpContext.Current.Request;
if (request.RequestType == "POST") return false;
return true;
}
}
Where your method name is the name of the property prefixed with ShouldSerialize.
Note this will work for JSON. For XML, you will need to add the following line to your config:
config.Formatters.XmlFormatter.UseXmlSerializer = true;
You can simply create a DTO for POST.

An entity object cannot be referenced by multiple instances of IEntityChangeTracker

I'm getting this error when trying to add an "Entity" to site.
"An entity object cannot be referenced by multiple instances of IEntityChangeTracker"
The Relation Between Site and "Entity" is one to many.
My Models Looks as below:
Site:
public class Site : Model
{
// The collection of Entities belonging to this site
public virtual ICollection<Entity> Entities { get; set; }
}
Entity:
public class Entity : Model
{
public string Label { get; set; }
public string Name { get; set; }
public virtual Site Site { get; set; }
}
My Code in Controller:
[HttpPost]
public ActionResult Add(EntityModel _entityModel)
{
var model = _entityModel.ToEntity();
if (ModelState.IsValid)
{
model.Site = _siteRepository.Find(1);
model.Label = model.Name.Replace(' ','_').ToLower();
_entityRepository.Add(model);
}
return RedirectToAction("Index");
}
Code in EFEntityRepository.cs:
public void Add(Entity entity)
{
DateTime dateModified = DateTime.Now;
entity.CreatedOn = dateModified;
entity.LastUpdatedOn = dateModified;
this._context.Entities.Add(entity);
this._context.SaveChanges();
}
Please suggest a solution.
It sounds like somewhere you have multiple Context classes holding a reference to this object, possibly by key.
Ensure your context is disposed after every request and in addition, there is no other operation causing another context instance to hold onto this entity.

Working with interfaces and EF Fluent API

Code
I will show you the code and then explain the problem
Interfaces
public interface IUser
{
Guid ID { get; set; }
string Name { get; set; }
ICollection<IRole> Roles { get; set; }
}
public interface IRole
{
Guid ID { get; set; }
string Name { get; set; }
}
Notice that the interface IUser define a collection Roles of type IRole
Implementation
public class Role : IRole
{
public Guid ID { get; set; }
public string Name { get; set; }
}
public class User : IUser
{
public Guid ID { get; set; }
public string Name { get; set; }
public ICollection<IRole> Roles { get; set; }
}
EF Fluent API Configuration
public class RoleConfiguration : EntityTypeConfiguration<Role>
{
public RoleConfiguration()
{
HasKey(p => p.ID)
.Property(p => p.ID);
Property(p => p.Name)
.IsRequired()
.HasMaxLength(70);
}
}
public class UserConfiguration : EntityTypeConfiguration<User>
{
public UserConfiguration()
{
HasKey(p => p.ID)
.Property(p => p.ID)
.IsRequired();
Property(p => p.Name)
.HasMaxLength(60)
.IsRequired();
HasMany(r => r.Roles).WithMany();
}
}
Note that the configuration EntityTypeConfiguration where T is the implementation and not the interface (the EF does not allow to put the interface as T)
Problem
#1 situation:
If you run the application, to generate the relational model, the following error occurs:
The navigation property 'Roles' is not a declared property on type 'User'. Verify that it has not been explicitly excluded from the model and that it is a valid navigation property.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.InvalidOperationException: The navigation property 'Roles' is not a declared property on type 'User'. Verify that it has not been explicitly excluded from the model and that it is a valid navigation property.
Source Error:
Line 58: public IQueryable<Project> GetAll(int pageIndex, int pageSize, params Expression<Func<Project, object>>[] includeProperties)
Line 59: {
Line 60: return includeProperties.Aggregate<Expression<Func<Project, object>>,
Line 61: IQueryable<Project>>(Context.Projects, (current, includeProperty) => current.Include(includeProperty)).OrderBy(p => p.Name).Skip(pageIndex).Take(pageSize);
Line 62: }
#2 situation:
If you comment out the line HasMany(r => r.Roles).WithMany(); EF will generate the relational model with no relationship between User and Role (which should be many to many)
I believe this is because the User class, there is a collection type ICollection<IRole> and not of kind ICollection.
Question
The question is, how to solve this problem?
How to map the collection public ICollection<IRole> Roles { get; set; } using Fluent API EF
CodeFirst doesn't support mapping interfaces. You will need to change it to reference the Role concrete class.
Here's how I would implement your interfaces in order to work around the problem that Betty described.
public class Role : IRole
{
public Guid ID { get; set; }
public string Name { get; set; }
}
public class User : IUser
{
public Guid ID { get; set; }
public string Name { get; set; }
// Explicit implementation of the IUser.Roles property.
// Exists to satisfy the IUser interface but is not normally used.
// Used only when the client code types this object the interface, IUser.
// Otherwise ignored.
ICollection<IRole> IUser.Roles
{
get { return this.Roles as ICollection<IRole>; }
set { this.Roles = value as ICollection<Role>; }
}
// This property will be used in most cases.
// Used when the client code types this object as the concrete type, User.
public ICollection<Role> Roles { get; set; }
}

Code first DbContext with current user filter

I'm building an ASP.NET MVC3 website with an code first database and have the following question:
Is it possible to make an instance of MyDbContext class with an additional argument set which will be used for filtering the results of calls to mydbcontext.
I want to use this for restricting the resultset to the current user that is logged in on my asp.net mvc website.
Any directions would be great!
I don't see why that should be a problem. Something like this should work:
public class Northwind : DbContext
{
public DbSet<Product> Products { get; set; }
public DbSet<Category> Categories { get; set; }
}
public class FilteredNorthwind : Northwind
{
public IQueryable<Products> GetFilteredProducts(string userRole)
{
return Products.Where(product => product.UserRole == userRole);
}
}
Update
To make it impossible for your MyDbContext to be abused, you could put all your database code and models into a separate project/assembly. Then make your DbContext an internal class (instead of public), then create a public class (FilteredDbContext) that wraps your MyDbContext and exposes methods that allow you to only grab the data your allowed to see. Then in your main assembly (your web project), you will only be able to use FilteredDbContext.
So, for example:
internal class Northwind : DbContext // note: internal class
{
public DbSet<Product> Products { get; set; }
public DbSet<Category> Categories { get; set; }
}
public class FilteredNorthwind // note: does not inherit from `Northwind`
{
private readonly _dbContext = new Northwind();
public IQueryable<Products> GetProducts(string userRole)
{
return _dbContext.Products.Where(product => product.UserRole == userRole);
}
}
If Northwind and FilteredNorthwind are in a separate assembly from your web app, you can instantiate only FilteredNorthwind from your web app.
Update 2
If you use a ViewModel, then your web app can't get back to the list of all products for a category because you extract out only the properties you need (and only the properties the user is allowed to see).
public class ProductViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
public IEnumerable<Products> GetProducts(string userRole)
{
return _dbContext.Products
.Where(product => product.UserRole == userRole)
.Select(product => new ProductViewModel
{
Id = product.Id,
Name = product.Name,
Price = product.Price
};
}
You could make a layer above and hide the generated one and create a your own DbContext which derives from the generated MyDbContext. Just a wild guess but it seems logical to me and so you can implement your own argument set and still use the generated one.
I would do this:
public interface IUserContext {
string User { get; set; }
}
public class Database : DbContext {
public IDbSet<Product> Products { get; set; }
}
public class AuthorizedDatabase {
private readonly Database _database;
private readonly IUserContext _userContext;
public AuthorizedDatabase(Database database, IUserContext userContext) {
_database = database;
_userContext = userContext;
}
private bool Authorize<TEntity>(TEntity entity) {
// Some code here to look at the entity and the _userContext and decide if it should be accessible.
}
public IQueryable<Product> Products {
get {
return _database.Products.Where(Authorize);
}
}
}
This would allow me to cleanly abstract the actual logic around the authorization (and your IUserContext interface can be as simple or complex as required to suite your exact needs.)
To ensure that the user is unable is circumvert this protection using a navigation property (Product.Category.Products, for example.) you might need to turn off lazy loading and explicitly load the required related entities.
Have a look at this post from ADO.NET Team Blog for ideas: loading related entities

Convert a string value to an entity in Automapper

I am trying to figure out how to use Automapper when my entity has a field of type entity.
I've got 3 classes like these:
public abstract class Entity<IdK>
{
public virtual IdK Code { get; protected set; }
}
public class Contact : Entity
{
public virtual string Name { get; set; }
public virtual Company Company { get; set; }
}
public class Company : Entity
{
public virtual string Name { get; set; }
}
My class Contact contain an element of type Company.
I've also created a ViewModel to trasfer some infos to my view:
public ContactViewModel()
{
public Guid Code { get; set; }
public int Version { get; set; }
[DisplayName("Contact")]
public string Name { get; set; }
[DisplayName("Company")]
public string Company { get; set; }
}
In my viewmodel I've defined a field Company of type string. This is going to contain a value the user will chose from a dropdown (list of companies).
I've defined a bootstrapper when my App starts:
public class AutoMapperConfiguration
{
public static void Configure()
{
Mapper.Initialize(x => {
x.CreateMap<Domain.Contact, ViewModels.ContactViewModel>();
x.CreateMap<ViewModels.ContactViewModel, Domain.Contact>()
});
}
}
When I try to remap my ViewModel to my entity I get a conversion error (AutoMapper.AutoMapperMappingException).
Automapper can't figure out how to convert my Company (string) into an object of type Company, member of Contact.
Is it possible to define a rule so that Automapper know how to transform the string (company) into the code of my Company object, member of Contact?
You need to use a Resolver. Something like:
public class CompanyTypeResolver : ValueResolver<string, Company>
{
protected override Company ResolveCore(string name)
{
return new Company {Name = name};
}
}
Then in mapping code you call it like:
.ForMember(dto => dto.Company, opt => opt.ResolveUsing<CompanyTypeResolver>().FromMember(src => src.Name))

Resources