Why use Automappers ValueResolver? - automapper

Why do this
Mapper.CreateMap<MyObject, AnotherObject>().
ForMember(x => x.DateAsString, m => m.ResolveUsing<StringToDateTimeFormatter>());
private class StringToDateTimeFormatter : ValueResolver<DateTime, string>
{
protected override string ResolveCore(DateTimesource)
{
return source.ToString("yyyy-MM-dd");
}
}
when you can do this
Mapper.CreateMap<MyObject, AnotherObject>().
ForMember(x => x.DateAsString, m => m.MapFrom(x => x.Date.ToString("yyy-MM-dd")));
???
Update
Here's an example on how to do more complex business logic
Mapper.CreateMap<MyObject, AnotherObject>().
ForMember(x => x.DateAsString, m => m.MapFrom(n => MyMethod(n.DateAsString)));
private object MyMethod(string dateTime)
{
if(!MyDomainObjectIsValid(dateTime))
{
throw new MyValidationException();
}
// do more stuff
}
I still don't see the need for a ValueResolver...

Obviously for your example it is more reasonable to use just MapFrom.
ValueResolvers are needed for more complicated cases. For example when you need to do some validation and throw exception accordingly.
EDIT
ValueResolvers provide access to the destination type and value. Here is small example.
public class FakeResolver : IValueResolver
{
public ResolutionResult Resolve(ResolutionResult source)
{
if (source.Context.DestinationType == typeof(string) && source.Context.DestinationValue == "test")
throw new Exception();
return source;
}
}

Related

Unit testing that the swagger doc is correct without starting a server

I'd like to test that the swagger document is correct for my application (mainly, because I've added a strategy to generate custom OperationIds and I want to ensure they are correctly unique)
However, the only solutions I found are all using a "real" server (cf https://stackoverflow.com/a/52521454/1545567), which is not an option for me since I do not have the database, message bus, etc... when I launch the unit tests in CI...
At the moment, I have the following but it always generate 0 paths and 0 models ...
using FluentAssertions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.OpenApi.Models;
using SampleCheckIn;
using Swashbuckle.AspNetCore.SwaggerGen;
using System;
using System.Linq;
using Xunit;
using SampleCheckIn.Def;
using Service.Utils;
using Swashbuckle.AspNetCore.Swagger;
using Microsoft.Extensions.Logging;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.FileProviders;
namespace D4Interop.Tests
{
public class TmpTest
{
[Fact]
public void Tmp()
{
var controllers = typeof(Startup).Assembly.GetTypes().Where(x => IsController(x)).ToList();
controllers.Any().Should().BeTrue();
var services = new ServiceCollection();
controllers.ForEach(c => services.AddScoped(c));
services.AddLogging(logging => logging.AddConsole());
services.AddControllers(); //here, I've also tried AddMvcCore and other ASP methods...
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("api", new OpenApiInfo { Title = Constants.SERVICE_NAME, Version = "_", Description = Constants.SERVICE_DESC });
//c.OperationFilter<SwaggerUniqueOperationId>(); //this is my filter that ensures the operationId is unique
c.CustomOperationIds(apiDesc =>
{
return apiDesc.TryGetMethodInfo(out var methodInfo) ? methodInfo.Name : null;
});
});
services.AddSingleton<IWebHostEnvironment>(new FakeWebHostEnvironment());
var serviceProvider = services.BuildServiceProvider();
var swaggerProvider = serviceProvider.GetRequiredService<ISwaggerProvider>();
var swagger = swaggerProvider.GetSwagger("api");
swagger.Should().NotBeNull();
swagger.Paths.Any().Should().BeTrue();
}
private bool IsController(Type x)
{
return typeof(Microsoft.AspNetCore.Mvc.ControllerBase).IsAssignableFrom(x);
}
}
internal class FakeWebHostEnvironment : IWebHostEnvironment
{
public FakeWebHostEnvironment()
{
}
public IFileProvider WebRootFileProvider { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
public string WebRootPath { get => "/root"; set => throw new NotImplementedException(); }
public string EnvironmentName { get => "dev"; set => throw new NotImplementedException(); }
public string ApplicationName { get => "app"; set => throw new NotImplementedException(); }
public string ContentRootPath { get => "/"; set => throw new NotImplementedException(); }
public IFileProvider ContentRootFileProvider { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
}
}
Ok, I've finally found that I just need to mix the linked answer with my code :
[Fact]
public async Task TestSwagger()
{
var server = Host.CreateDefaultBuilder()
.ConfigureWebHostDefaults(options => { options.UseStartup<Startup>(); })
.Build();
var swagger = server.Services
.GetRequiredService<ISwaggerProvider>()
.GetSwagger("xxx"); //xxx should be the name of your API
swagger.Should().NotBeNull();
swagger.Paths.Any().Should().BeTrue();
swagger.Components.Schemas.Should().NotBeNull();
}

ReactiveUI Xamarin.ios bootsrapper for clean app launch

I have been looking for some examples on how to launch a xamarin.ios app in reactive-ui. I am also new in Xamarin.ios so maybe I am missing a crucial step. All examples I've found are either old, outdated or requires too much manual setup. I am looking to make a setup in a way where calling RoutedViewHost will resolve the UIViewController, which currently does not work and I get black screens. I assume there's no easy way to hook things up but wanted to check here first and ask the question.
Similar to how you can bootstrap a Xamarin.forms project, and how my code below successfully works in Xamarin.Forms, I'd like to start one in native.ios too.. So far I could not get the bindings and routing to work on initial app launch. I have below code in AppDelegate;
cs
var bootstrap = new AppBootstrapper();
var rootViewController = bootstrap.CreateMainView();
Window.RootViewController = rootViewController;
Window.MakeKeyAndVisible();
AppBootstrapper is a ReactiveObject, IScreen object and I try to register a simple LoginView for starters to test this out. View is in Storyboard, assigned to LoginViewController.
cs
public class AppBootstrapper: ReactiveObject, IScreen
public AppBootstrapper()
{
_routingState = new RoutingState();
InitializeIoC();
RegisterViews();
RegisterRoutableViewModals();
_routingState.Navigate.Execute(new LoginViewModel(this));
...
private void InitializeIoC()
{
Locator.CurrentMutable.RegisterConstant(this, typeof(IScreen));
}
private void RegisterViews()
{
Locator.CurrentMutable.Register(() => new LoginViewController(), typeof(IViewFor<LoginViewModel>));
}
private void RegisterRoutableViewModals()
{
Locator.CurrentMutable.Register(() => new LoginViewModel(), typeof(IRoutableViewModel), typeof(LoginViewModel).FullName);
}
public UIViewController CreateMainView()
{
return new RoutedViewHost();
}
...
public partial class LoginViewController : ReactiveViewController<LoginViewModel>
{
public LoginViewController()
{
ViewModel = new LoginViewModel();
this.WhenActivated(disposables =>
{
this.BindCommand(ViewModel, x => x.LoginCommand, x => x.btnLogin)
.DisposeWith(disposables);
this.Bind(ViewModel, x => x.Username, x => x.txtPassword.Text)
.DisposeWith(disposables);
this.Bind(ViewModel, x => x.Password, x => x.txtPassword.Text)
.DisposeWith(disposables);
this.OneWayBind(ViewModel, x => x.Warning, x => x.lblWarning.Text)
.DisposeWith(disposables);
this.OneWayBind(ViewModel, x => x.IsExecuteEnabled, x => x.btnLogin.Enabled)
.DisposeWith(disposables);
});
}
...
public class LoginViewModel: ReactiveObject, IRoutableViewModel
{
public string UrlPathSegment => "Login";
public IScreen HostScreen { get; }
public LoginViewModel(IScreen screen = null)
{
HostScreen = screen ?? Locator.Current.GetService<IScreen>();
LoginCommand = ReactiveCommand.CreateFromTask(async => Login());
this.WhenAnyValue(x => x.Username, x => x.Password, x => x.LoginCommand,
(user, pass, command) =>
!string.IsNullOrEmpty(user) &&
!string.IsNullOrEmpty(pass) &&
user.Length >= 3 &&
!IsCommandExecuting &&
!_disableLoginButton)
.DistinctUntilChanged()
.ToProperty(this, x => x.IsExecuteEnabled, out _isExecuteEnabled);
LoginCommand.IsExecuting
.ToProperty(this, x => x.IsCommandExecuting, out _isCommandExecuting);
}

Configuring convention for all members with type in automapper

All my domain models have field public CurrencyId CurrencyId {get; set;}. All my view models have filed public CurrencyVm Currency {get; set;}. Automapper knows how to Map<CurrencyVm>(CurrencyId). How can I setup automatic convention, so I do not have to .ForMember(n => n.Currency, opt => opt.MapFrom(n => n.CurrencyId));?
ForAllMaps is the answer, thanks #LucianBargaoanu. This code kinda works, but hasn't been tested in all cases. Also I do not know how to check if exists mapping between choosen properties.
configuration.ForAllMaps((map, expression) =>
{
if (map.IsValid != null)
{
return; // type is already mapped (or not)
}
const string currencySuffix = "Id";
var currencyPropertyNames = map.SourceType
.GetProperties()
.Where(n => n.PropertyType == typeof(CurrencyId) && n.Name.EndsWith(currencySuffix))
.Select(n => n.Name.Substring(0, n.Name.Length - currencySuffix.Length))
.ToArray();
expression.ForAllOtherMembers(n =>
{
if (currencyPropertyNames.Contains(n.DestinationMember.Name, StringComparer.OrdinalIgnoreCase))
{
n.MapFrom(n.DestinationMember.Name + currencySuffix);
}
});
});

ReactiveCommand.Create throws "NotSupportedException": "Index expressions are only supported with constants."

The following line throws a runtime exception:
Accept = ReactiveCommand.Create(this.WhenAnyValue(x => x.Canexecute()));
Here's the code:
public class InstructionsViewModel : ReactiveObject
{
public InstructionsViewModel()
{
Accept = ReactiveCommand.Create(this.WhenAnyValue(x => x.CanExecute));
Accept.Subscribe(x =>
{
Debug.Write("Hello World");
});
}
public ReactiveCommand<object> Accept { get; }
bool _canExecute;
public bool CanExecute { get { return _canExecute; } set { this.RaiseAndSetIfChanged(ref _canExecute, value); } }
}
Error:
Cannot convert lambda expression to type 'IObserver' because
it is not a delegate type
I've also tried the following:
public InstructionsViewModel()
{
Accept = ReactiveCommand.Create(this.WhenAnyValue(x => x.Canexecute()));
Accept.Subscribe(x =>
{
Debug.Write("Hello World");
});
}
public ReactiveCommand<object> Accept { get; }
public bool Canexecute() => true;
I receive the following error:
An exception of type 'System.NotSupportedException' occurred in
ReactiveUI.dll but was not handled in user code
Additional information: Index expressions are only supported with
constants.
Is this even supported on Windows Phone 10?
I guess that your problem is not with ReactiveCommand, but with WhenAnyValue.
WhenAnyValue accepts a property, while you feed it with a method, which causes run time exception (see the sourcecode).
Check if this works (I changed CanExecute to be a property instead of a method):
public InstructionsViewModel()
{
Accept = ReactiveCommand.Create(this.WhenAnyValue(x => x.CanExecute));
Accept.Subscribe(x =>
{
Debug.Write("Hello World");
});
}
public ReactiveCommand<object> Accept { get; }
private bool _canExecute;
public bool CanExecute { get { return _canExecute; } set { this.RaiseAndSetIfChanged(ref _canExecute, value); } }
Also, as a general advice - do not nest your calls, this makes debugging harder. You should split creating command into two lines:
var canExecute = this.WhenAnyValue(x => x.CanExecute)
Accept = ReactiveCommand.Create(canExecute);

How do you Bind an Image to the HighlightedImage-Property of an ImageView?

I'm currently trying to bind two images to an iOS ImageView via MvvmCross.
One should be displayed when the ImageView is in 'default' state, the other one when the ImageView is highlighted.
By the following code I can bind the Image for the default state. But how do I bind the one for 'highlighted' state?
public CategoryCell(IntPtr handle): base(string.Empty, handle)
{
_imageViewLoader = new MvxImageViewLoader(() => this.imageView);
this.DelayBind(() =>
{
var set = this.CreateBindingSet<CategoryCell, MaterialCategory>();
set.Bind(titleLabel).To(materialCategory => materialCategory.Label);
set.Bind(_imageViewLoader).To(materialCategory => materialCategory.ImageActiveUri);
set.Bind(this).For(cell => cell.Selected).To(materialCategory => materialCategory.IsSelected);
set.Apply();
});
}
Another approach if you do not need image loading i.e. for lots of static UX.
You can set up as follows -
_imageView = new UIImageView(UIImage.FromFile("some/image/off.png"))
{
HighlightedImage = UIImage.FromFile("some/image/on.png")
};
And bind it e.g. an "Enabled" Property -
this.DelayBind(() =>
{
var set = this.CreateBindingSet<SomeView, SomeViewModel>();
set.Bind(_imageView).For(v => v.Highlighted).To(vm => vm.Enabled);
set.Apply();
});
And don't forget to add Highlighted to your LinkerPleaseInclude.cs.
Hope this helps
I think the best solution is to introduce an extra property ImageUri. In the setter of your IsSelected you set the ImageUri dependend on the selection state.
ViewModel:
public class MaterialCategory : MvxViewModel
{
//...
public string ImageActiveUri { ... } // call UpdateImageUri() here, too
public string ImageInactiveUri { ... } // call UpdateImageUri() here, too
public string ImageUri { ... }
public bool IsSelected
{
get { return _isSelected; }
set
{
_isSelected = value;
UpdateImageUri();
RaisePropertyChanged(() => IsSelected);
}
}
private void UpdateImageUri()
{
ImageUri = IsSelected ? ImageActiveUri : ImageInactiveUri;
}
}
Binding:
set.Bind(_imageViewLoader).To(materialCategory => materialCategory.ImageUri);
// instead of:
// set.Bind(_imageViewLoader).To(materialCategory => materialCategory.ImageActiveUri);

Resources