I have below code in xUnit framework for testing:
using AutoFixture.Xunit2;
using AutoMapper;
using FluentAssertions;
using Moq;
using MyRBV.AppService.Cars;
using MyRBV.AppService.Contract.Cars;
using MyRBV.DomainService.Cruds;
using MyRBV.Model.Cars;
namespace AppService.Test.Cars;
public class CarServiceTest
{
private readonly ICarService _service;
private readonly IMapper _mapper;
public CarServiceTest([Frozen] Func<IMapper> mapper)
{
_mapper = mapper();
_service = new CarService(GetDomain(), _mapper);
}
//[Fact]
[Theory, AutoData]
public async Task Add_CheckInvalidCarName_ThrowsValidationException(Func<IMapper> mapper)
{
var car = new CarDto("abghjkl;jhl;kjhkl;'lkjh" +
"sdfghjksdfgsfghgfdsfghdsfg" +
"sdfgdsfgsfgdsfgdsfghdsfgdsfg" +
"sadfgdsfgdsfgsfgdsfgdsfgdsfg" +
"sdfghdfsghdsfgdsfghdfgdghdfg");
var action = new Func<Task<CarDto>>(() => _service.Add(car));
await action.Should().ThrowAsync<ArgumentOutOfRangeException>();
}
private ICrudDomainService<Car> GetDomain()
{
var domainMock = new Mock<ICrudDomainService<Car>>();
domainMock.Setup(domain => domain.Add(It.IsAny<Car>()));
return domainMock.Object;
}
}
But after running the code I get below error:
AutoFixture.ObjectCreationExceptionWithPath: AutoFixture was unable to
create an instance from AutoMapper.IMapper because it's an interface.
There's n...
any one can help me?
below code is my CarDto
namespace MyRBV.AppService.Contract.Cars;
public class CarDto:BaseDto
{
public CarDto(string name)
{
Name = name;
}
private string _name;
private string? _description;
public string Name
{
get => _name;
set
{
if (value.Length is < 2 or > 40)
throw new ArgumentOutOfRangeException();
_name = value;
}
}
public string? Description { get => _description;
set
{
if (value is { Length: > 800 })
throw new ArgumentOutOfRangeException();
_description=value;
}
}
}
I did some search's, some of it says I need to create new class for customizing new IMapper format. but I couldn't understand it.
If you need any more information please let me know
Related
So I have been at it for days, and for the life of me cannot find any documentation that fits my situation exactly here.
I have essentially set up a custom navigation service and would like to call the command from my ViewModel Class directly from my User Control.
I think I'm on the edge of having it here, but my lack of experience with C# is shooting me in the foot.
Here is the section of code from my Login.xaml.cs in question:
private LoginViewModel _loginViewModel;
public Login(LoginViewModel loginViewModel)
{
_loginViewModel = loginViewModel;
}
private void GrantAccess()
{
int userAccess = Int16.Parse(User.Access);
if (userAccess == 1)
{
MessageBox.Show("The bottom man");
}
if (userAccess == 2)
{
MessageBox.Show("The little boss");
}
if (userAccess == 3)
{
MessageBox.Show("The little big boss");
}
if (userAccess == 4)
{
{
_loginViewModel.NavigateMM1Command.Execute(null);
}
}
}
and here is the command I'm trying to reference from the ViewModel:
public class LoginViewModel : BaseViewModel
{
public ICommand NavigateMM1Command { get; }
public LoginViewModel(NavigationStore navigationStore)
{
NavigateMM1Command = new NavigateCommand<MM1ViewModel>(new NavigationService<MM1ViewModel>(navigationStore, () => new MM1ViewModel(navigationStore)));
}
}
Basically I've been going through tutorial after tutorial trying to apply what they teach to what I need and its worked for the most part but now _loginViewModel is throwing a null reference exception and I'm not sure why.
I have tried:
LoginViewModel loginViewModel = new loginViewModel();
but its asking me to pass a navigationStore argument through it and that feels wrong.
Any help here will cure my temporary insanity XD
You're receiving a Null Object Reference because navigationStore is null when LoginViewModel is being constructed.
That is, you have not configured a means to instantiate the type navigationStore when constructing LoginViewModel.
Dependency Injection (DI), or Invocation of Control (IoC) is bit more comprehensive a subject to cover in this answer.
Having said that,
I'll provide code to review here. It represents a means to configure a service provider using a collection of type mappings.
In this complete, ConsoleApp example, we'll explicitly instantiate a ServiceCollection, add Service Types (specifying mapping where application), and Build the ServiceProvider; With that provider, we'll resolve and instantiate Login type using GetService -- instantiating all the types;
The Types are essentially mockups of the types you've specified, but I've modified some aspects (an made up notioned like what your Execute method and usage of NavigationStore was).
DemoNavTypes
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleDemo.NavLoginDemo
{
public interface ICommand
{
void Execute(string? userName);
}
public interface INavigationStore {
public bool this[string index] { get;set; }
}
public interface INavigationService {
void GrantAccessToUser(string userName);
}
public interface INavigationViewModel { }
internal class NavigationStore : INavigationStore
{
private Dictionary<string, bool> userAccessDict;
public NavigationStore() {
userAccessDict = new Dictionary<string, bool>();
}
public bool this[string index] {
get => userAccessDict.TryGetValue(index, out var val) && val;
set => userAccessDict[index] = value;
}
}
internal class NavigationService : INavigationService
{
private readonly INavigationStore _navigationStore;
public NavigationService(INavigationStore navigationStore)
{
_navigationStore = navigationStore;
}
public void GrantAccessToUser(string? userName)
{
if (string.IsNullOrWhiteSpace(userName))
throw new ArgumentException(nameof(userName));
_navigationStore[userName!] = true;
}
}
internal class NavigationCommand : ICommand
{
private readonly INavigationService _navigationService;
public NavigationCommand(INavigationService navigationService)
{
_navigationService = navigationService;
}
public void Execute(string? userName)
{
if (userName != null)
{
_navigationService.GrantAccessToUser(userName);
}
}
}
internal class User
{
public string? Name { get; set; }
public string Access { get; set; } = "1";
}
public abstract class BaseViewModel
{
internal User User { get; set; } = new User();
protected BaseViewModel() { }
}
internal class LoginViewModel : BaseViewModel, INavigationViewModel
{
private readonly ICommand _command;
public LoginViewModel(ICommand command) : base()
{
_command = command;
}
internal ICommand NavigateMM1Command => _command;
}
internal class Login
{
private User User => _loginViewModel.User;
private readonly LoginViewModel _loginViewModel;
public Login(LoginViewModel loginViewModel)
{
_loginViewModel = loginViewModel;
}
internal void SetAccess(int access)
{
SetAccess($"{access}");
}
internal void SetAccess(string access)
{
User.Access = access;
}
internal void SetUserName(string userName) { User.Name = userName; }
internal async Task GrantAccessAsync()
{
await Task.Yield();
int userAccess = Int16.Parse(User.Access);
switch (userAccess)
{
case 1:
Console.WriteLine("The bottom man");
break;
case 2:
Console.WriteLine("The little boss");
break;
case 3:
Console.WriteLine("The little big boss");
break;
case 4:
_loginViewModel.NavigateMM1Command.Execute(User.Name);
break;
default:
throw new NotImplementedException();
}
}
}
}
Program.cs (using Microsoft.Extensions.DependencyInjection)
using Microsoft.Extensions.DependencyInjection;
using System.Collections.Immutable;
using System.ComponentModel.Design;
using System.Linq;
using ConsoleDemo.NavLoginDemo;
internal class Program
{
private static async Task Main(string[] args)
{
var services = new ServiceCollection();
var provder = ConfigureServices(services);
var login = provder.GetService<Login>();
if (login != null)
{
await login.GrantAccessAsync();
login.SetAccess(2);
await login.GrantAccessAsync();
login.SetAccess(3);
await login.GrantAccessAsync();
login.SetUserName("James Bond");
login.SetAccess(4);
await login.GrantAccessAsync();
}
}
private static IServiceProvider ConfigureServices(IServiceCollection services)
{
return services
.AddScoped<INavigationStore, NavigationStore>()
.AddScoped<INavigationService, NavigationService>()
.AddScoped<ICommand, NavigationCommand>()
.AddScoped<LoginViewModel>()
.AddScoped<Login>()
.BuildServiceProvider();
}
}
Note
-- However,
In your application, you'll probably already have a ServiceCollection instance in your Program.cs or Startup.cs file. And the ServiceProvider (or HostProvider) will be managed over by the application; So, you probably won't need to explicitly resolve (or GetService<T>) -- just add the Service Type (mappings) in ServiceCollection. Those parameter types will be instantiated and 'injected' into the constructor of Type that is itself being instantiated.
So I have a generic repository like this:
using System.Collections.Generic;
using System.Data.Entity;
using System.Threading.Tasks;
namespace TrackIt.UI.Data.Repositories
{
public class GenericRepository<TEntity, TContext> : IGenericRepository<TEntity>
where TContext: DbContext
where TEntity: class
{
protected readonly TContext Context;
protected GenericRepository(TContext context)
{
this.Context = context;
}
public virtual async Task<TEntity> GetByIdAsync(int id)
{
return await Context.Set<TEntity>().FindAsync(id);
}
public bool HasChanges()
{
return Context.ChangeTracker.HasChanges();
}
}
}
And a FriendRepository that inherits from it, with some of its own methods:
using System.Data.Entity;
using System.Threading.Tasks;
using TrackIt.DataAccess;
using TrackIt.Model;
namespace TrackIt.UI.Data.Repositories
{
public class FriendRepository : GenericRepository<Friend, TrackItDbContext>,
IFriendRepository
{
public FriendRepository(TrackItDbContext context): base(context)
{
}
public override async Task<Friend> GetByIdAsync(int id)
{
return await Context.Friends.Include(f => f.PhoneNumbers).SingleAsync(f => f.Id == id);
}
}
}
where TrackItDbContext is just a class inheriting from DbContext, where it has properties that "maps" to the database's tables.
Then, I have an implementing class like this:
namespace TrackIt.UI.ViewModel
{
public class FriendDetailViewModel : DetailViewModelBase, IFriendDetailViewModel
{
public bool HasChanges
{
get { return _hasChanges; }
set
{
if(_hasChanges != value)
{
_hasChanges = value;
OnPropertyChanged();
((DelegateCommand)SaveCommand).RaiseCanExecuteChanged();
}
}
}
private Friend _friend;
public Friend Friend
{
get { return _friend; }
private set
{
_friend = value;
OnPropertyChanged();
}
}
private ICommand _addPhoneNumberCommand;
public ICommand AddPhoneNumberCommand
{
get { return _addPhoneNumberCommand; }
set { _addPhoneNumberCommand = value; }
}
public ObservableCollection<FriendPhoneNumberDecorator> PhoneNumbers { get; }
private IFriendRepository _friendRepository;
private IMessageDialogService _messageDialogServiceProvider;
public FriendDetailViewModel(IFriendRepository friendRepository,
IEventAggregator eventAggregator,
IMessageDialogService messageDialogServiceProvider) : base(eventAggregator)
{
_friendRepository = friendRepository;
_messageDialogServiceProvider = messageDialogServiceProvider;
PhoneNumbers = new ObservableCollection<FriendPhoneNumberDecorator>();
AddPhoneNumberCommand = new DelegateCommand(OnAddPhoneNumberExecute);
DeletePhoneNumberCommand = new DelegateCommand(OnDeletePhoneNumberExecute, OnDeletePhoneNumberCanExecute);
}
private void FriendPhoneNumberWrapper_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
// Ensures that the HasChanges property of the viewmodel is updated
if (!HasChanges)
{
HasChanges = _friendRepository.HasChanges();
}
// If the change in phone number causes validation errors, disable the save button
if (e.PropertyName == nameof(FriendPhoneNumberDecorator.HasErrors))
{
((DelegateCommand)SaveCommand).RaiseCanExecuteChanged();
}
}
private void OnAddPhoneNumberExecute()
{
var newNumber = new FriendPhoneNumber();
newNumber.PropertyChanged += FriendPhoneNumberWrapper_PropertyChanged;
PhoneNumbers.Add(newNumber);
Friend.PhoneNumbers.Add(newNumber);
newNumber.Number = "";
}
#endregion
}
}
When using this class, an instance of IFriendRepository is injected by a DI container and assigned to _friendRepository. The line Friend.Model.PhoneNumbers.Add in the OnAddPhoneNumberExecute method adds a PhoneNumber instance (newNumber) to Friend.PhoneNumber. This addition is tracked by Context in HasChanges method in the GenericRepository method (I checked this by debugging), and thus when the _friendRepository.HasChanges() method in FriendPhoneNumberWrapper_PropertyChanged event handler is triggered by the newNumber.Number = "" line, it returns true.
But, when I test if the HasChanges property of the class is updated when I raise the command AddPhoneNumberCommand using the following test:
namespace TrackIt.UITests.ViewModel
{
public class FriendDetailViewModelTests
{
private FriendDetailViewModel _viewModel;
private Mock<IEventAggregator> _eventAggregatorMock;
private Mock<IMessageDialogService> _messageDialogServiceMock;
private Mock<IFriendRepository> _friendRepositoryMock;
private readonly Friend testFriend = new Friend
{
Id = 2,
FirstName = "asdf",
LastName = "asdfasdf",
Email = "test#test.com",
FavoriteLanguage = new ProgrammingLanguage { Id = 1, Name = "C++"},
PhoneNumbers = new List<FriendPhoneNumber> {
new FriendPhoneNumber(){ id = 1, Number = "0123" },
new FriendPhoneNumber(){ id = 2, Number = "4567" },
}
};
public FriendDetailViewModelTests()
{
// Set up dependencies for FriendDetailViewModel class.
_messageDialogServiceMock = new Mock<IMessageDialogService>();
_eventAggregatorMock = new Mock<IEventAggregator>();
_friendRepositoryMock = new Mock<IFriendRepository>();
// Mock the friendrepository getbyidasync method
_friendRepositoryMock.Setup(dp => dp.GetByIdAsync(testFriend.Id)).ReturnsAsync(testFriend);
// Finally, create the FriendDetailViewModel!
_viewModel = new FriendDetailViewModel(_friendRepositoryMock.Object, _eventAggregatorMock.Object, _messageDialogServiceMock.Object);
}
[Fact]
public void ShouldAddNewPhoneNumberEvent()
{
// ARRANGE
// Assign testFriend to Friend (required so that the created phone number can be assigned to an instance, otherwise we get null reference exception)
PrivateObject _viewModelPrivated = new PrivateObject(_viewModel);
_viewModelPrivated.SetProperty(nameof(Friend), testFriend);
// Create a test empty phone number, to be compared later against the phone number created when user presses "Add"
var emptyPhoneNumber = new FriendPhoneNumber();
emptyPhoneNumber.Number = "";
// ACT
_viewModel.AddPhoneNumberCommand.Execute(null);
// ASSERT
// Check if the created phone number is an empty phone number, the dbContext sees there's a change,
// and that the save button can be pressed
Xunit.Assert.Contains(emptyPhoneNumber, _viewModel.PhoneNumbers,
new ByPropertyComparer<FriendPhoneNumberDecorator>(nameof(emptyPhoneNumber.Number)));
Xunit.Assert.True(_viewModel.HasChanges);
Xunit.Assert.True(_viewModel.SaveCommand.CanExecute(null));
}
}
}
the test fails at Xunit.Assert.True(_viewModel.HasChanges); line, which means the _friendRepository.HasChanges() is not updating automatically (i.e. not tracking) when the Friend.Model.PhoneNumbers.Add is called. I realize that this would be due to me mocking the IFriendRepository, and so it may not actually be tracking any changes. Am I right? Should I also mock HasChanges() of _friendRepositoryMock ? Or is there a better way to do this, maybe an in-memory database? But then it would not be a unit test?
This is the code to replicate the issue. Basically it fails on deserialization, with error "A reference-tracked object changed reference during deserialization". It's kind of interesting, if I remove the following line from IExecEnv setup:
metaType.AsReferenceDefault = true;
then the test passes. But this sounds odd. Is it a limitation of Protobuf-net?
using ProtoBuf;
using ProtoBuf.Meta;
namespace QRM.Analytics.Serialization.Commit.Tests;
public class UnitTest1
{
private interface IExecEnv
{
int GetId();
}
[Immutable]
private class ExecEnv : IExecEnv
{
public static readonly ExecEnv DefaultEnv = new ExecEnv(1);
private readonly int _id;
private ExecEnv(int id)
{
_id = id;
}
public int GetId()
{
return _id;
}
}
[Serializable]
private sealed class ExecEnvSurrogate
{
[ProtoConverter]
public static IExecEnv FromSurrogate(ExecEnvSurrogate surrogate)
{
if (surrogate == null)
return null;
return ExecEnv.DefaultEnv;
}
[ProtoConverter]
public static ExecEnvSurrogate ToSurrogate(IExecEnv env)
{
if (env == null)
return null;
return new ExecEnvSurrogate();
}
}
private class WithExecEnv
{
private IExecEnv _env;
public WithExecEnv(IExecEnv env)
{
_env = env;
}
public IExecEnv Env => _env;
}
private void SetupModel()
{
var metaType = RuntimeTypeModel.Default.Add(typeof(IExecEnv), false);
metaType.AsReferenceDefault = true;
var metaType3 = RuntimeTypeModel.Default.Add(typeof(WithExecEnv), false);
metaType3.UseConstructor = false;
metaType3.AddField(1, "_env");
var metaType4 = RuntimeTypeModel.Default.Add(typeof(ExecEnvSurrogate), false);
metaType4.UseConstructor = false;
metaType.SetSurrogate(typeof(ExecEnvSurrogate));
}
[Test]
public void CloneExecEnvWithSurrogate()
{
SetupModel();
var withExecEnv = new WithExecEnv(ExecEnv.DefaultEnv);
using (var stream = new MemoryStream())
{
Serializer.Serialize(stream, withExecEnv);
stream.Seek(0, SeekOrigin.Begin);
var clone = Serializer.Deserialize<WithExecEnv>(stream);
Assert.NotNull(clone);
Assert.Equal(withExecEnv.Env.GetId(), clone.Env.GetId());
}
}
}
Yes, reference-tracking is ... a massive PITA; it works in some limited scenarios, but there are situations where it becomes unreliable. It also isn't part of the core protobuf specification, but is hacked on top at the library level. For these reasons, it is not recommended, and is deprecated going forward; it cannot be made reliable, and I'd rather not encourage people to get into dangerous territory.
after some discussion with AutoMapper team, they recommended me to put my question here.
In short, if I map the expressions that I need using
MapExpression<T>(source)
it works perfectly fine (considering using the AutoMapper.Extensions.ExpressionMapping framework).
BTW, the authors said me that, even if I try to map using
Map(object, sourceType, targetType)
it should work normally but, when I use this method, I have the error as described in the title of this post.
To help, I wrote a full example on how to reproduce the problem as it follows bellow:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using AutoMapper;
using AutoMapper.Extensions.ExpressionMapping;
namespace AutoMapperExpressionMappingTest
{
public class PresentationModelPerson
{
public string Name { get; set; }
public DateTime Birthday { get; set; }
}
public class ApplicationModelPerson
{
public string Name { get; set; }
public DateTime Birthday { get; set; }
}
public class DomainModelPerson
{
public string Name { get; set; }
public DateTime Birthday { get; set; }
}
public class PresentationPerson
{
private readonly IMapper _mapper;
public PresentationPerson(IMapper mapper) => _mapper = mapper;
public IEnumerable<PresentationModelPerson> List(int take, int skip,
Expression<Func<IQueryable<PresentationModelPerson>, IOrderedQueryable<PresentationModelPerson>>> orderBy,
Expression<Func<PresentationModelPerson, bool>> where, IList<Expression<Func<PresentationModelPerson, object>>> includes)
{
var applicationTake = take;
var applicationSkip = skip;
/*
* if I map this way the mapping on domain class will fail with the following error:
* System.InvalidOperationException: 'Code supposed to be unreachable'
* (there's a reason on my project to use this way)
*/
dynamic applicationOrderByObject = _mapper.Map(orderBy,
typeof(Expression<Func<IQueryable<PresentationModelPerson>, IOrderedQueryable<PresentationModelPerson>>>
),
typeof(Expression<Func<IQueryable<ApplicationModelPerson>, IOrderedQueryable<ApplicationModelPerson>>
>));
/*
// if I map this way, it works perfectly //
var applicationOrderBy =
_mapper
.MapExpression<
Expression<Func<IQueryable<ApplicationModelPerson>, IOrderedQueryable<ApplicationModelPerson>>>
>(orderBy);
*/
var applicationWhere = _mapper.MapExpression<Expression<Func<ApplicationModelPerson, bool>>>(where);
var applicationInclude =
_mapper.MapExpressionList<Expression<Func<ApplicationModelPerson, object>>>(includes).ToList();
var applicationPerson = new ApplicationPerson(_mapper);
applicationPerson.List(applicationTake, applicationSkip, applicationOrderByObject, applicationWhere,
applicationInclude);
throw new NotImplementedException();
}
}
public class ApplicationPerson
{
private readonly IMapper _mapper;
public ApplicationPerson(IMapper mapper) => _mapper = mapper;
public IEnumerable<ApplicationModelPerson> List(int take, int skip,
Expression<Func<IQueryable<ApplicationModelPerson>, IOrderedQueryable<ApplicationModelPerson>>> orderBy,
Expression<Func<ApplicationModelPerson, bool>> where, IList<Expression<Func<ApplicationModelPerson, object>>> includes)
{
var domainTake = take;
var domainSkip = skip;
// this mapping will fail whatever I use this way or _mapper.Map(object, sourceType, targetType) //
var domainOrderBy =
_mapper
.MapExpression<
Expression<Func<IQueryable<DomainModelPerson>, IOrderedQueryable<DomainModelPerson>>>
>(orderBy);
var domainWhere = _mapper.MapExpression<Expression<Func<DomainModelPerson, bool>>>(where);
var domainInclude =
_mapper.MapExpressionList<Expression<Func<DomainModelPerson, object>>>(includes).ToList();
var domainPerson = new DomainPerson(_mapper);
domainPerson.List(domainTake, domainSkip, domainOrderBy, domainWhere,
domainInclude);
throw new NotImplementedException();
}
}
public class DomainPerson
{
private readonly IMapper _mapper;
public DomainPerson(IMapper mapper) => _mapper = mapper;
public IEnumerable<DomainModelPerson> List(int take, int skip,
Expression<Func<IQueryable<DomainModelPerson>, IOrderedQueryable<DomainModelPerson>>> orderBy,
Expression<Func<DomainModelPerson, bool>> where, IList<Expression<Func<DomainModelPerson, object>>> includes)
{
throw new NotImplementedException();
}
}
public class ModelProfile : Profile
{
public ModelProfile()
{
CreateMap<PresentationModelPerson, ApplicationModelPerson>().ReverseMap();
CreateMap<ApplicationModelPerson, DomainModelPerson>().ReverseMap();
}
}
public class ExpressionProfile : Profile
{
public ExpressionProfile()
{
CreateMap<Expression<Func<PresentationModelPerson, bool>>,
Expression<Func<ApplicationModelPerson, bool>>>().ReverseMap();
CreateMap<Expression<Func<IQueryable<PresentationModelPerson>,
IOrderedQueryable<PresentationModelPerson>>>,
Expression<Func<IQueryable<ApplicationModelPerson>, IOrderedQueryable<ApplicationModelPerson>>>>().ReverseMap();
CreateMap<IList<Expression<Func<PresentationModelPerson, object>>>,
IList<Expression<Func<ApplicationModelPerson, object>>>>().ReverseMap();
CreateMap<Expression<Func<ApplicationModelPerson, bool>>,
Expression<Func<DomainModelPerson, bool>>>().ReverseMap();
CreateMap<Expression<Func<IQueryable<ApplicationModelPerson>,
IOrderedQueryable<ApplicationModelPerson>>>,
Expression<Func<IQueryable<DomainModelPerson>, IOrderedQueryable<DomainModelPerson>>>>().ReverseMap();
CreateMap<IList<Expression<Func<ApplicationModelPerson, object>>>,
IList<Expression<Func<DomainModelPerson, object>>>>().ReverseMap();
}
}
public class Container
{
public IMapper Mapper { get; }
public Container()
{
var mapperConfiguration = new MapperConfiguration(
configuration =>
{
configuration.AddExpressionMapping();
configuration.AddProfile<ModelProfile>();
configuration.AddProfile<ExpressionProfile>();
configuration.AllowNullCollections = true;
});
Mapper = mapperConfiguration.CreateMapper();
Mapper.ConfigurationProvider.AssertConfigurationIsValid();
}
}
internal class Program
{
private static void Main(string[] args)
{
var mapper = new Container().Mapper;
var presentationPerson = new PresentationPerson(mapper);
Expression<Func<IQueryable<PresentationModelPerson>, IOrderedQueryable<PresentationModelPerson>>> orderBy = persons =>
persons.OrderByDescending(person => person.Birthday);
Expression<Func<PresentationModelPerson, bool>> where = person => !string.IsNullOrEmpty(person.Name);
presentationPerson.List(1, 100, orderBy, where,
new List<Expression<Func<PresentationModelPerson, object>>>());
}
}
}
Is there anything that I'm missing here? In time: .NET Core 2.2, AutoMapper 9.0, AutoMapper.Extensions.ExpressionMapping 3.0.1 and nothing else.
Thank you.
Author answered and confirmed that it's a bug so, I'm posting the workaround they gave me on their Github page.
Here's the link for the issue: https://github.com/AutoMapper/AutoMapper.Extensions.ExpressionMapping/issues/40
We need to create a helper for it, like this:
public static class ExpressionMappingHelper
{
public static LambdaExpression MapExpression(this IMapper mapper, LambdaExpression expression, Type sourceExpressionType, Type destExpressionType)
{
if (expression == null)
return default;
//This calls public static TDestDelegate MapExpression<TSourceDelegate, TDestDelegate>(this IMapper mapper, TSourceDelegate expression)
//in AutoMapper.Extensions.ExpressionMapping.MapperExtensions
return (LambdaExpression)"MapExpression".GetMapExpressionMethod().MakeGenericMethod
(
sourceExpressionType,
destExpressionType
).Invoke(null, new object[] { mapper, expression });
}
private static MethodInfo GetMapExpressionMethod(this string methodName)
=> typeof(AutoMapper.Extensions.ExpressionMapping.MapperExtensions).GetMethods().Single(m => m.Name == methodName && m.GetGenericArguments().Length == 2);
}
Then call the extension method like this:
dynamic applicationOrderByObject = _mapper.MapExpression(orderBy,
typeof(Expression<Func<IQueryable<PresentationModelPerson>, IOrderedQueryable<PresentationModelPerson>>>
),
typeof(Expression<Func<IQueryable<ApplicationModelPerson>, IOrderedQueryable<ApplicationModelPerson>>
>));
This will be the workaround until the AutoMapper team fixes it.
Hope that this can help someone as it helped me.
I have a constructor
[ReadFromFile(#"C:\SampleData\login.json")]
public AccountController(IReadRepository<LoginMockDataModel> repository, string filePath) : base(repository)
{
}
The attribute contains a property "FilePath".
public string FilePath {get;set;}
I would like to retrieve the value of "FilePath" which would be "C:\SampleData\login.json" in the above case.
Is it possible to retrieve the value using Ninject's IContext?
The idea is to retrieve the property's value and then use it in the binding as follows:
// FileReadRepo contains a constructor with the argument "filePath"
// which will need a string passed to it which I am trying to retrieve
// from the Attribute above
Bind(typeof(IReadRepository<>)).To(typeof(FileReadRepo<>))
.WhenMemberHas<ReadFromFileAttribute>()
.WithConstructorArgument("filePath", CheckAttributePath);
where CheckAttributePath would be the delegate:
private object CheckAttributePath(IContext arg)
{
throw new NotImplementedException();
}
I'm not sure how to obtain the attribute's value.
Accessing the constructor of the AccountController is done through IContext.Request.Target.Member. So this works:
private static object CheckAttributePath(IContext context)
{
var attributes = context.Request.Target.Member
.GetCustomAttributes(typeof(ReadFromFileAttribute), false);
return ((ReadFromFileAttribute)attributes[0]).Path;
}
Complete test code (employs xunit and FluentAssertions):
using System;
using Ninject;
using Ninject.Activation;
using Xunit;
using FluentAssertions;
public interface IReadRepository<T>
{
string FilePath { get; }
}
public class FileReadRepo<T> : IReadRepository<T>
{
private readonly string filePath;
public FileReadRepo(string filePath)
{
this.filePath = filePath;
}
public string FilePath { get { return this.filePath; } }
}
[AttributeUsage(AttributeTargets.Constructor, AllowMultiple = false, Inherited = false)]
public class ReadFromFileAttribute : Attribute
{
public readonly string Path;
public ReadFromFileAttribute(string path)
{
this.Path = path;
}
}
public class AccountController
{
public readonly IReadRepository<string> Repository;
[ReadFromFile(IntegrationTest.SampleFilePath)]
public AccountController(IReadRepository<string> repository)
{
this.Repository = repository;
}
}
public class IntegrationTest
{
public const string SampleFilePath = #"C:\SampleData\login.json";
[Fact]
public void Test()
{
var kernel = new StandardKernel();
kernel.Bind(typeof(IReadRepository<>)).To(typeof(FileReadRepo<>))
.WhenMemberHas<ReadFromFileAttribute>()
.WithConstructorArgument("filePath", CheckAttributePath);
kernel.Get<AccountController>().Repository.FilePath.Should().Be(SampleFilePath);
}
private static object CheckAttributePath(IContext context)
{
var attributes = context.Request.Target.Member.GetCustomAttributes(
typeof(ReadFromFileAttribute), false);
return ((ReadFromFileAttribute)attributes[0]).Path;
}
}