AutoMapping Object with Constructor Arguments - c#

I am trying to configure AutoMapper to work with a class which requires a constructor argument. I've done quite a bit of online research into this, and found several examples...but they either don't use instance-based AutoMapper, or they are for older versions of AutoMapper.
I'm using AutoMapper to deep clone objects. Here's the profile I've created:
public class ScannerAutoMapProfile : Profile
{
public ScannerAutoMapProfile()
{
CreateMap<AzureConfiguration>();
CreateMap<CommunityUser>();
CreateMap<Community>();
CreateMap<Contact>();
CreateMap<DatabaseConnection>();
CreateMap<DatabaseConfiguration>();
CreateMap<LoginManagement>();
CreateMap<MaxLoadSeconds>();
CreateMap<ScanTime>();
CreateMap<ScanningAcceleration>();
CreateMap<ScanningWindow>();
CreateMap<ScanningInterval>();
CreateMap<Scanning>();
CreateMap<SearchParameterUser>();
CreateMap<SearchParameter>();
CreateMap<ScannerConfiguration>();
}
private void CreateMap<TModel>()
{
CreateMap<TModel, TModel>();
}
}
The problem is occurring with ScannerConfiguration, which takes a single string parameter.
Here's the Autofac module I'm using:
public class AutoMapperModule : Module
{
protected override void Load( ContainerBuilder builder )
{
base.Load( builder );
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
builder.RegisterAssemblyTypes( assemblies )
.Where( t => typeof(Profile).IsAssignableFrom( t ) && !t.IsAbstract && t.IsPublic )
.As<Profile>();
builder.Register( c => new MapperConfiguration( cfg =>
{
foreach( var profile in c.Resolve<IEnumerable<Profile>>() )
{
cfg.AddProfile( profile );
}
} ) )
.AsSelf()
.SingleInstance();
builder.Register( c => c.Resolve<MapperConfiguration>().CreateMapper( c.Resolve ) )
.As<IMapper>()
.SingleInstance();
}
}
(thanx to http://www.protomatter.co.uk/blog/development/2017/02/modular-automapper-registrations-with-autofac/ for this).
I can successfully instantiate an IMapper, which shows the Autofac stuff is working properly. But when I attempt to call Map:
_onDisk = Mapper.Map<ScannerConfiguration>( _inMemory );
it fails with a "ScannerConfiguration has no parameterless constructor" exception.
Unfortunately, while I'm pretty sure I need to provide some options to that Map() call, I haven't been able to figure out how to do it. The parameter I need to pass in to the constructor is a public property of ScannerConfiguration, called SourceFilePath.

Since ScannerConfiguration requires a parameter, why don't you initialize it yourself?
var sc = new ScannerConfiguration("some string value");
_onDisk = Mapper.Map( _inMemory, sc );

If Automapper can't create an instance of the destination using a default constructor, you can give Automapper a function that calls a constructor and returns the new instance with ConstructUsing. After it constructs the object it continues mapping as usual.
For example here's a source and destination class. The destination class can't be created without calling a non-default constructor:
public class SourceClass
{
public string SourceStringProperty { get; set; }
public int OtherSourceProperty { get; set; }
public bool SameNameInBoth { get; set; }
}
public class DestinationClass
{
public DestinationClass(string destinationString)
{
DestinationStringPropertyFromConstructor = destinationString;
}
public string DestinationStringPropertyFromConstructor { get; }
public int OtherDestinationProperty { get; set; }
public bool SameNameInBoth { get; set; }
}
The mapping would look like this:
Mapper.Initialize(cfg =>
{
cfg.CreateMap<SourceClass, DestinationClass>()
.ConstructUsing(hasString => new DestinationClass(hasString.SourceStringProperty))
.ForMember(dest => dest.OtherDestinationProperty,
opt => opt.MapFrom(src => src.OtherSourceProperty));
});
I added the other properties just to demonstrate that other than specifying a constructor, the other configuration details are all the same.

Related

How to hide a request field in Swagger with .Net Web API on GET Request [duplicate]

I'm using Swashbuckle to generate swagger documentation\UI for a webapi2 project. Our models are shared with some legacy interfaces so there are a couple of properties I want to ignore on the models. I can't use JsonIgnore attribute because the legacy interfaces also need to serialize to JSON so I don't want to ignore the properties globally, just in the Swashbuckle configuration.
I found a method of doing this documented here:
https://github.com/domaindrivendev/Swashbuckle/issues/73
But this appears to be out of date with the current Swashbuckle release.
The method recommended for the old version of Swashbuckle is using an IModelFilter implementation as follows:
public class OmitIgnoredProperties : IModelFilter
{
public void Apply(DataType model, DataTypeRegistry dataTypeRegistry, Type type)
{
var ignoredProperties = … // use reflection to find any properties on
// type decorated with the ignore attributes
foreach (var prop in ignoredProperties)
model.Properties.Remove(prop.Name);
}
}
SwaggerSpecConfig.Customize(c => c.ModelFilter<OmitIgnoredProperties>());
But I'm unsure how to configure Swashbuckle to use the IModelFilter in the current version? I'm using Swashbuckle 5.5.3.
If you need to do this but without using JsonIgnore (maybe you still need to serialize/deserialize the property) then just create a custom attribute.
[AttributeUsage(AttributeTargets.Property)]
public class SwaggerExcludeAttribute : Attribute
{
}
Then a schema filter similar to Johng's
public class SwaggerExcludeFilter : ISchemaFilter
{
#region ISchemaFilter Members
public void Apply(Schema schema, SchemaRegistry schemaRegistry, Type type)
{
if (schema?.properties == null || type == null)
return;
var excludedProperties = type.GetProperties()
.Where(t =>
t.GetCustomAttribute<SwaggerExcludeAttribute>()
!= null);
foreach (var excludedProperty in excludedProperties)
{
if (schema.properties.ContainsKey(excludedProperty.Name))
schema.properties.Remove(excludedProperty.Name);
}
}
#endregion
}
Don't forget to register the filter
c.SchemaFilter<SwaggerExcludeFilter>();
Solution for .NET Core 3.1 and .NET Standard 2.1:
Use JsonIgnore from System.Text.Json.Serialization namespace.
( JsonIgnore from Newtonsoft.Json will NOT work )
public class Test
{
[System.Text.Json.Serialization.JsonIgnore]
public int HiddenProperty { get; set; }
public int VisibleProperty { get; set; }
}
If you mark field/property as internal or protected or private, it will be ignored automatically by swashbuckle in swagger documentation.
Update: Obviously, those properties/fields won't be populated in request/response.
The code below is very much based on #Richard's answer, but I am including it as a new answer because it has three completely new, useful features which I have added:
Runs on .NET Core on the latest version of Swashbuckle (v5)
Allows the SwaggerIgnore attribute to be applied to fields not just to properties
Handles the fact that property and field names may have been overridden using the JsonProperty attribute
EDIT: Now correctly handles camelCasing of originally TitleCased fields or properties (prompted by #mattruma's answer)
So the revised code is:
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public class SwaggerIgnoreAttribute : Attribute
{
}
internal static class StringExtensions
{
internal static string ToCamelCase(this string value)
{
if (string.IsNullOrEmpty(value)) return value;
return char.ToLowerInvariant(value[0]) + value.Substring(1);
}
}
public class SwaggerIgnoreFilter : ISchemaFilter
{
public void Apply(OpenApiSchema schema, SchemaFilterContext schemaFilterContext)
{
if (schema.Properties.Count == 0)
return;
const BindingFlags bindingFlags = BindingFlags.Public |
BindingFlags.NonPublic |
BindingFlags.Instance;
var memberList = schemaFilterContext.SystemType // In v5.3.3+ use Type instead
.GetFields(bindingFlags).Cast<MemberInfo>()
.Concat(schemaFilterContext.SystemType // In v5.3.3+ use Type instead
.GetProperties(bindingFlags));
var excludedList = memberList.Where(m =>
m.GetCustomAttribute<SwaggerIgnoreAttribute>()
!= null)
.Select(m =>
(m.GetCustomAttribute<JsonPropertyAttribute>()
?.PropertyName
?? m.Name.ToCamelCase()));
foreach (var excludedName in excludedList)
{
if (schema.Properties.ContainsKey(excludedName))
schema.Properties.Remove(excludedName);
}
}
}
and in Startup.cs:
services.AddSwaggerGen(c =>
{
...
c.SchemaFilter<SwaggerIgnoreFilter>();
...
});
The AspNetCore solution looks like:
public class SwaggerExcludeSchemaFilter : ISchemaFilter
{
public void Apply(Schema schema, SchemaFilterContext context)
{
if (schema?.Properties == null)
{
return;
}
var excludedProperties = context.SystemType.GetProperties().Where(t => t.GetCustomAttribute<SwaggerExcludeAttribute>() != null);
foreach (PropertyInfo excludedProperty in excludedProperties)
{
if (schema.Properties.ContainsKey(excludedProperty.Name))
{
schema.Properties.Remove(excludedProperty.Name);
}
}
}
}
Well, with a bit of poking I found a way to do this using ISchemaFilter:
public class ApplyCustomSchemaFilters : ISchemaFilter
{
public void Apply(Schema schema, SchemaRegistry schemaRegistry, Type type)
{
var excludeProperties = new[] {"myProp1", "myProp2", "myProp3"};
foreach(var prop in excludeProperties)
if (schema.properties.ContainsKey(prop))
schema.properties.Remove(prop);
}
}
then when calling httpConfiguration.EnableSwagger I set the SwaggerDocsConfig to use this SchemaFilter as follows:
c.SchemaFilter<ApplyCustomSchemaFilters>();
Hope this helps someone. I'd still be curious on whether it's possible to use the IModelFilter somehow though.
For people like me who are using .Net Core and are using the build in app.UseSwaggerUi3WithApiExplorer()
Use [JsonIgnore] tag using Newtonsoft.Json;
public class Project
{
[Required]
public string ProjectName { get; set; }
[JsonIgnore]
public string SomeValueYouWantToIgnore { get; set; }
}
It will be excluded from your documentation.
I have here a working example with DotNetCore 3 and Swashbuckle 5. It took me a few hours to get it in place so I thought to come back to this thread which helped me but didn't solve my issue.
Create a dummy custom attribute:
[AttributeUsage(AttributeTargets.Property)]
public class SwaggerExcludeAttribute : Attribute { }
Create a SchemaFilter which will be used by swagger to generate the API Model Schema
public class SwaggerExcludeFilter : ISchemaFilter
{
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
if (!(context.ApiModel is ApiObject))
{
return;
}
var model = context.ApiModel as ApiObject;
if (schema?.Properties == null || model?.ApiProperties == null)
{
return;
}
var excludedProperties = model.Type
.GetProperties()
.Where(
t => t.GetCustomAttribute<SwaggerExcludeAttribute>() != null
);
var excludedSchemaProperties = model.ApiProperties
.Where(
ap => excludedProperties.Any(
pi => pi.Name == ap.MemberInfo.Name
)
);
foreach (var propertyToExclude in excludedSchemaProperties)
{
schema.Properties.Remove(propertyToExclude.ApiName);
}
}
}
Then, inside the Startup.cs file add this to the swagger configuration
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" });
c.SchemaFilter<SwaggerExcludeFilter>();
});
You can now use the custom attribute on a property that you want to exclude from the API Mode Shema like this
public class MyApiModel
{
[SwaggerExclude]
public Guid Token { get; set; }
public int Id { get; set; }
public string Name { get; set; }
}
Based on Stef Heyenrath's answer.
Attribute to mark properties to exclude from the Swagger documentation.
[AttributeUsage(AttributeTargets.Property)]
public class SwaggerExcludeAttribute : Attribute
{
}
The filter to exclude the properties from the Swagger documentation.
public class SwaggerExcludeSchemaFilter : ISchemaFilter
{
public void Apply(Schema schema, SchemaFilterContext context)
{
if (schema?.Properties == null)
{
return;
}
var excludedProperties =
context.SystemType.GetProperties().Where(
t => t.GetCustomAttribute<SwaggerExcludeAttribute>() != null);
foreach (var excludedProperty in excludedProperties)
{
var propertyToRemove =
schema.Properties.Keys.SingleOrDefault(
x => x.ToLower() == excludedProperty.Name.ToLower());
if (propertyToRemove != null)
{
schema.Properties.Remove(propertyToRemove);
}
}
}
}
The schema.Properties.Keys are camelCase, while the properties themselves are PascalCase. Tweaked the method to convert both to lower case and compare to see what should be excluded.
Swashbuckle now has support for Newtonsoft.
https://github.com/domaindrivendev/Swashbuckle.AspNetCore#systemtextjson-stj-vs-newtonsoft
dotnet add package --version 5.3.1 Swashbuckle.AspNetCore.Newtonsoft
`services.AddSwaggerGenNewtonsoftSupport(); // explicit opt-in - needs tobe placed after AddSwaggerGen();`
You can use the Swashbuckle.AspNetCore.Annotations package, it allows you to mark that some properties are only displayed in the input parameters, and some are only displayed in the output.
for example, if you want to hide the AlertId in the input parameter of the post, you just need to do this by the [SwaggerSchema]:
public class Alert
{
[SwaggerSchema(ReadOnly = true)]
public string AlertId { get; set; }
public string Type { get; set; }
}
See more about it in the Documentation
Here is what I used with Newtonsoft.Json.JsonIgnoreAttribute:
internal class ApplySchemaVendorExtensions : Swashbuckle.Swagger.ISchemaFilter
{
public void Apply(Schema schema, SchemaRegistry schemaRegistry, Type type)
{
foreach (var prop in type.GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance)
.Where(p => p.GetCustomAttributes(typeof(Newtonsoft.Json.JsonIgnoreAttribute), true)?.Any() == true))
if (schema?.properties?.ContainsKey(prop.Name) == true)
schema?.properties?.Remove(prop.Name);
}
}
Referring to https://stackoverflow.com/a/58193046/11748401 answer, for creating a filter you can simply use the following code:
public class SwaggerExcludeFilter : ISchemaFilter
{
public void Apply(OpenApiSchema model, SchemaFilterContext context)
{
var excludeProperties = context.ApiModel.Type?.GetProperties().Where(prop => Attribute.IsDefined(prop, typeof(SwaggerExcludeAttribute)));
if (excludeProperties != null)
{
foreach (var property in excludeProperties)
{
// Because swagger uses camel casing
var propertyName = $"{ToLowerInvariant(property.Name[0])}{property.Name.Substring(1)}";
if (model.Properties.ContainsKey(propertyName))
{
model.Properties.Remove(propertyName);
}
}
}
}
}
This is an older question, but an low-effort, intermediate solution has since become available in Swashbuckle.
Hiding legacy properties from documentation doesn't do much to discourage usage of these properties - it just delays discovery. After all, they're still part of the model. In fact, leaving them undocumented means consumers have no way of knowing they shouldn't use them!
Rather than have them go undocumented, you should simply consider marking them [Obsolete].
Swashbuckle will then mark them as deprecated in the swagger.json. In the UI, this will hide them in the Example Value sections, and in the Schema sections, they will show as grayed out with strikethrough on the names.
If you still want them to be completely hidden from the documentation, you can then set in SwaggerGeneratorOptions.IgnoreObsoleteProperties = true.
This was not a possible solution at the time this question was originally asked. The deprecated flag is a feature of OpenAPI v3, which was not released until 2017.
(Based on mutex's answer.)
I added another line to not have problems with NullReferenceException.
public void Apply(Schema schema, SchemaRegistry schemaRegistry, Type type)
{
var excludeProperties = new[] { "myProp1", "myProp2, myProp3"};
foreach (var prop in excludeProperties)
if(schema.properties != null) // This line
if (schema.properties.ContainsKey(prop))
schema.properties.Remove(prop);
}
If you want to delete all schemas
public void Apply(Schema schema, SchemaRegistry schemaRegistry, Type type)
{
schema.properties = null;
}
I get inspired by the blog of Ignoring properties from controller action model in Swagger using JsonIgnore.
I'm using .net core 2.1 and Swashbuckle.AspNetCore 5.3.1.
The code below solved the problem.
Add a new filter
public class SwaggerJsonIgnoreFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
var ignoredProperties = context.MethodInfo.GetParameters()
.SelectMany(p => p.ParameterType.GetProperties()
.Where(prop => prop.GetCustomAttribute<JsonIgnoreAttribute>() != null))
.ToList();
if (!ignoredProperties.Any()) return;
foreach (var property in ignoredProperties)
{
operation.Parameters = operation.Parameters
.Where(p => (!p.Name.Equals(property.Name, StringComparison.InvariantCulture)))
.ToList();
}
}
}
Use the Filter in Startup.cs
public void ConfigureServices(IServiceCollection services)
{
......
services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo { Title = "CustomApi", Version = "v1" });
options.OperationFilter<SwaggerJsonIgnoreFilter>();
});
......
}
In my case I wanted to keep my Application Layer DTOs clean (without any annotation like JsonIngore) but still being able to use them in my Controllers Web APIs.
So, in my Application Layer I have a DTO like this:
public class CreateItemCommand {
public Guid ContainerId { get; set; }
public string Name { get; set; }
}
And my API design for creating an item is something like:
POST /containers/{containerId}/items
As the ContainerId is coming from the api route, I don't want the asp.net core trying to bind it into the command DTO and I don't want swashbuckle listing it neither.
So my solution is to inherit the original DTO in the API layer like this:
public class CreateItemCommandMod : CreateItemCommand {
#pragma warning disable IDE0051
private new ContainerID { get; }
#pragma warning restore IDE0051
}
...
[HttpPost("{containerId}/items}")]
public Task Create(
[FromRoute] Guid containerId,
[FromBody] CreateItemCommandMod command,
) => useCase.Create(command.Apply(r => r.ContainerId = containerId));
The useCase.Create from the ApplicationLayer expects the base class CreateItemCommand.
.Apply is just a very simple extension method that i've made to easily set the routing parameter value into the correspondent dto property.
I needed more control to remove properties which were declared elsewhere and couldn't easly use a removal attribute.
The filter created removed all items which it came accross from my excludes list:
public class SwaggerExcludeFilter : ISchemaFilter
{
private static readonly List<string> excludes = new List<string>()
{
"StoredProcedureName", "ValidationErrors", "changeTracker",
"code", "customerId", "IsDebug",
};
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
if (schema?.Properties == null || context == null)
return;
// Find all properties by name which need to be removed
// and not shown on the swagger spec.
schema.Properties
.Where(prp => excludes.Any(exc => string.Equals(exc, prp.Key, StringComparison.OrdinalIgnoreCase)))
.Select(prExclude => prExclude.Key)
.ToList()
.ForEach(key => schema.Properties.Remove(key));
}
}
In startup or program.cs for you .Net 6 fans.
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info
{
Version = "2.5",
Title = "My Swagger Doc G",
});
c.SchemaFilter<SwaggerExcludeFilter>();
...
Very userful solution form #Jay Shah, but if you using N'Tier architecture you can not reach protected or private DAL data from BL. to solve this, you can make this prop's acces modifier as "protected internal"
public class Test
{
protected internal int HiddenProperty { get; set; }
}
with this you can access aforementioned data from BL but not PL. or API layer.
I'm using dotnet core 3.1 and Swashbuckle 6.3.1, here is updated version with the similar logic for using ISchemaFilter to filter properties marked with customed attribute SwaggerExcludeAttribute
public class SwaggerExcludeFilter : ISchemaFilter
{
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
var type = context.Type;
if (!schema.Properties.Any() || type == null)
{
return;
}
var excludedPropertyNames = type
.GetProperties()
.Where(
t => t.GetCustomAttribute<SwaggerExcludeAttribute>() != null
).Select(d => d.Name).ToList();
if (!excludedPropertyNames.Any())
{
return;
}
var excludedSchemaPropertyKey = schema.Properties
.Where(
ap => excludedPropertyNames.Any(
pn => pn.ToLower() == ap.Key
)
).Select(ap => ap.Key);
foreach (var propertyToExclude in excludedSchemaPropertyKey)
{
schema.Properties.Remove(propertyToExclude);
}
}
}

Getting error on constructors using reactiveui

I have a view model that has two view models as properties:
public class PokemonViewModel : ReactiveObject, IEnableLogger
{
private readonly IPokemonService PokemonService;
[Reactive]
public PokemonListViewModel PokemonListViewModel { get; set; }
private readonly ObservableAsPropertyHelper<PokemonDetailsViewModel> pokemonDetailsViewModel;
public PokemonDetailsViewModel PokemonDetailsViewModel => pokemonDetailsViewModel.Value;
public PokemonViewModel(IPokemonService pokemonService = null)
{
PokemonService = pokemonService ?? Locator.Current.GetService<IPokemonService>();
PokemonListViewModel = new PokemonListViewModel();
pokemonDetailsViewModel = this.WhenAnyValue(x => PokemonListViewModel.SelectedPokemon)
.SelectMany(GetPokemonDetailsViewModelAsync)
.ToProperty(this, x => x.PokemonDetailsViewModel);
}
//private methods...
}
The PokemonListViewModel contains a SelectedPokemon property which is not initialized on constructor, so it's null at startup
Given this, when I run the application, I get the following error: "Unsupported expression of type 'Constant'. Did you miss the member access prefix in the expression?"
How can I solve this?
It should be x => x.PokemonListViewModel in the WhenAnyValue call.

Coming from EF to EF Core, how do I use the using statement correctly?

I must be doing something wrong in my context I have my context below which I want to wrap up in my manager class. And I want to be able to use the using statement so that my context is used only once.
public class xxxDbContext : DbContext
{
public xxxDbContext(DbContextOptions<xxxDbContext> options)
: base(options)
{ }
public DbSet<JobsList> JobListingss { get; set; }
public DbSet<Clients> ClientListings { get; set; }
public DbSet<Engineer> EngineerListing { get; set; }
public DbSet<Case> CasesListing { get; set; }
}
But when I want to use my context in my using statement such as
public class xxxContext
{
xxxDbContext _db = new xxxDbContext();
public List<Case> GetAllCases(int databaseId)
{
List<Case> q = new List<Case>();
using (var myContext = new xxxDbContext(what must I reference here ?)))
{
q = myContext.Cases
.Where(w => w.databaseID == databaseId)
.OrderBy(o => o.CustomerName).ToList();
}
return q;
}
}
Before I was just able to make a parameter less construct can I do same here for core or does that override what is meant to be done.
Also what is best practise should I have a separate class for all these functions or should I have a partial class based on my context?.
You will need to use the DbContextoptionsBuilder<T> generic class to get the instance of options back:
var optionsBuilder = new DbContextOptionsBuilder<xxxDbContext>();
and now you will have to use the created instance to be passed in the constructor:
using (var myContext = new xxxDbContext(optionsBuilder.Options)))
{
// code here
}
EDIT:
So what you need is first of all configure a service in services for getting the Options like:
public void ConfigureServices(IServiceCollection services)
{
// using sqlite as example via official docs example
services.AddDbContext<xxxDbContext>(options => options.UseSqlite("Data Source=blog.db"));
}
and then in the code where required to create the dbContext you could resolve it like:
var options = serviceProvider.GetService<DbContextOptions<xxxDbContext>>();
and now pass it to the Context class object creation time in constructor:
var options = serviceProvider.GetService<DbContextOptions<xxxDbContext>>();
using (var myContext = new xxxDbContext(options)))
{
// code here
}
You can refer to the following post for details at official docs:
Configuring a DbContext

How do I conditionally set the destination object to null using automapper

I am trying to do something like this:
AutoMapper.Mapper.CreateMap<UrlPickerState, Link>()
.ForMember(m=>m.OpenInNewWindow,map=>map.MapFrom(s=>s.NewWindow))
.AfterMap((picker, link) => link = !string.IsNullOrWhiteSpace(link.Url)?link:null) ;
var pickerState = new UrlPickerState();
var linkOutput = AutoMapper.Mapper.Map<Link>(pickerState);
However, the assigned value of link is not used in any execution path.
I would like linkOutput to be null, but it is not.
How would I make the destination object null?
Details of objects involved:
public class Link
{
public string Title { get; set; }
public string Url { get; set; }
public bool OpenInNewWindow { get; set; }
}
public class UrlPickerState
{
public string Title { get; set; }
public string Url { get; set; }
public bool NewWindow { get; set; }
//.... etc
}
Here's a fiddle: http://dotnetfiddle.net/hy2nIa
This is the solution I used in the end, it was a bit more manual internally, but does not require any extra plumbing.
If anyone has a more elegant solution, it would be appreciated.
config.CreateMap<UrlPickerState, Link>()
.ConvertUsing(arg =>
{
if (string.IsNullOrWhiteSpace(arg.Url))
{
return null;
}
return new Link()
{
Url = arg.Url,
OpenInNewWindow = arg.NewWindow,
Title = arg.Title,
};
});
I created the following extension method to solve this problem.
public static IMappingExpression<TSource, TDestination> PreCondition<TSource, TDestination>(
this IMappingExpression<TSource, TDestination> mapping
, Func<TSource, bool> condition
)
where TDestination : new()
{
// This will configure the mapping to return null if the source object condition fails
mapping.ConstructUsing(
src => condition(src)
? new TDestination()
: default(TDestination)
);
// This will configure the mapping to ignore all member mappings to the null destination object
mapping.ForAllMembers(opt => opt.PreCondition(condition));
return mapping;
}
For the case in question, it can be used like this:
Mapper.CreateMap<UrlPickerState, Link>()
.ForMember(dest => dest.OpenInNewWindow, opt => opt.MapFrom(src => src.NewWindow))
.PreCondition(src => !string.IsNullOrWhiteSpace(src.Url));
Now, if the condition fails, the mapper will return null; otherwise, it will return the mapped object.
I think that will have to be done outside the mapping. Since AutoMapper requires an instance to map to, setting the destination to null seems like it should go outside the mapping.
I would instead do something like:
AutoMapper.Mapper.CreateMap<UrlPickerState, Link>()
.ForMember(m=>m.OpenInNewWindow,map=>map.MapFrom(s=>s.NewWindow));
var pickerState = new UrlPickerState();
Link linkOutput = null;
if(!string.IsNullOrWhiteSpace(pickerState.Url)) // or whatever condition is appropriate
linkOutput = AutoMapper.Mapper.Map<Link>(pickerState);

Automapper profiles not working

Does anyone know why this works:
Mapper.Configuration.RecognizeDestinationPrefixes("Foo");
Mapper.CreateMap<A, B>();
But this doesn't:
Mapper.CreateProfile("FooPrefix").RecognizeDestinationPrefixes("Foo");
Mapper.CreateMap<A, B>()
.WithProfile("FooPrefix");
?
While this question is quite old now, I thought it would be useful to answer it given I spent ages trying to get profiles to work.
Although there are a bunch of ways to configure profiles, it seems that the only way what I could get it to work was as follows:
public class ExampleProfile : Profile
{
protected override void Configure()
{
ReplaceMemberName("Z", "A");
CreateMap<Source, Destination>(); // Notice this is CreateMap, NOT Mapper.CreateMap...
}
public override string ProfileName
{
get { return this.GetType().Name; }
}
}
Then, set up the profile in your config:
Mapper.Initialize(cfg => cfg.AddProfile<ExampleProfile>());
Given the Source and Destination classes as follows:
public class Source
{
public string Zabc { get; set; }
}
public class Destination
{
public string Aabc { get; set; }
}
This should now work:
var source = new Source { Zabc = "source" };
var dest = Mapper.Map<Destination>(source);
Assert.AreEqual(source.Zabc, dest.Aabc);
Profile names are different. You use FooxPrefix when creating the profile and then use FooPrefix when creating the map.

Categories