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.
Related
I would like to enhance final result that ModelBinder returns.
For example:
public class MyModel
{
public int Order {get;set;}
[MyUpperCaseAttribute]
public string Title {get;set;}
}
In API method I expect that all string properties in MyModel which has MyUpperCaseAttribute is in upper case.
For example:
[HttpPost("AddRecord")]
public async Task<ActionResult<int>> AddRecord(MyModel model)
{
model.Title should be upper case, even if send from client in lower case.
}
My idea was to override default ModelBinder and enumerate through all properties and check if property is string and has MyUpperCaseAttribute and correct property value to upper case. I check documentation, but doesn't examples doesn't fill right, since they completely redesign what is returned. I would like to just modify result properties.
What would be the best option to achieve desired behaviour?
Important: (edited):
It would be nice if directive attributes could be stacked:
public class MyModel
{
public int Order {get;set;}
[MyUpperCaseAttribute]
[RemoveSpacesAttribute]
public string Title {get;set;}
}
Edited:
It looks similar to this, but if not other, this is ASP.NET Core, and on link is just ASP.NET. Method, properties, interfaces... are not the same.
I should say, that it would be nice if JSON case rule would work:
public class MyModel
{
public int Order {get;set;}
public string Title {get;set;}
}
should work if {order: 1, title: "test"} (notice lowercase) is send from JavaScript.
This might not be the 'best' option, but I would just use .ToUpper() extension method instead of a custom attribute filter.
public class MyModel
{
private string _title;
public int Order {get;set;}
public string Title { get => _title.ToUpper(); set => _title = value.ToUpper(); }
}
There's a big red herring here, and that's the fact that it appears that this is the sort of thing that could and should be accomplished via model binding. Unfortunately, that's not the case in ASP.Net Core Web API: because the incoming data is JSON, it is in fact handled by input formatters, not model binders. Therefore, in order to achieve the desired effect, you need to create your own custom input formatter that replaces the standard JsonInputFormatter.
First the attribute:
[AttributeUsage(AttributeTargets.Property)]
public class ToUppercaseAttribute : Attribute
{
}
Then we decorate our model class with it:
public class MyModel
{
public int Order { get; set; }
[ToUppercase]
public string Title { get; set; }
}
Now create our custom input formatter that checks for that attribute and transforms the output if necessary. In this case, it simply wraps and delegates to JsonInputFormatter to do the heavy lifting as normal, then modifies the result if it finds our ToUppercaseAttribute attribute on any string property:
public class ToUppercaseJsonInputFormatter : TextInputFormatter
{
private readonly JsonInputFormatter _jsonInputFormatter;
public ToUppercaseJsonInputFormatter(JsonInputFormatter jsonInputFormatter)
{
_jsonInputFormatter = jsonInputFormatter;
foreach (var supportedEncoding in _jsonInputFormatter.SupportedEncodings)
SupportedEncodings.Add(supportedEncoding);
foreach (var supportedMediaType in _jsonInputFormatter.SupportedMediaTypes)
SupportedMediaTypes.Add(supportedMediaType);
}
public override Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding)
{
var result = _jsonInputFormatter.ReadRequestBodyAsync(context, encoding);
foreach (var property in context.ModelType.GetProperties().Where(p => p.PropertyType.IsAssignableFrom(typeof(string))
&& p.CustomAttributes.Any(a => a.AttributeType.IsAssignableFrom(typeof(ToUppercaseAttribute)))))
{
var value = (string)property.GetValue(result.Result.Model);
property.SetValue(result.Result.Model, value.ToUpper());
}
return result;
}
}
Next we create an extension method that makes it simple to substitute the default JsonInputFormatter with our custom formatter:
public static class MvcOptionsExtensions
{
public static void UseToUppercaseJsonInputFormatter(this MvcOptions opts)
{
if (opts.InputFormatters.FirstOrDefault(f => f is JsonInputFormatter && !(f is JsonPatchInputFormatter)) is JsonInputFormatter jsonInputFormatter)
{
var jsonInputFormatterIndex = opts.InputFormatters.IndexOf(jsonInputFormatter);
opts.InputFormatters[jsonInputFormatterIndex] = new ToUppercaseJsonInputFormatter(jsonInputFormatter);
}
}
}
And finally, call that method to effect the replacement in Startup.cs:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services
.AddMvc(options => options.UseToUppercaseJsonInputFormatter());
}
}
Et voilĂ !
You can do this thing inside your MyUpperCaseAttribute as follows:
public class MyUpperCaseAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if(value != null)
{
validationContext.ObjectType
.GetProperty(validationContext.MemberName)
.SetValue(validationContext.ObjectInstance, value.ToString().ToUpper(), null);
}
return null;
}
}
Property value will be converted to UpperCase during Model Binding. I have checked it in my side and it works perfectly.
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.
In Swagger UI I get a model like:
Inline Model [
Inline Model 1
]
Inline Model 1 {
Id (string, optional),
ConnectionString (string, optional),
ConnectionState (string, optional)
}
for a REST Get method like:
public IEnumerable<Device> Get()
{
return new List<Device>();
}
Why is it not displayed correctly?
Adding Swagger Config from comments
public class SwaggerConfig
{
public static void Register()
{
var thisAssembly = typeof(SwaggerConfig).Assembly;
GlobalConfiguration.Configuration .EnableSwagger(c => { c.SingleApiVersion("v1", "api"); }) .EnableSwaggerUi(c => { });
}
}
public class Device
{
public string Id { get; set; }
public string ConnectionString { get; set; }
public string ConnectionState { get; set; }
}
In C# Asp.Net web api, I did this:
1- In SwaggerConfig.cs
.EnableSwagger(c =>
{//add this line
c.SchemaFilter<ApplyModelNameFilter>();
}
2- add a class that implements ISchemaFilter:
class ApplyModelNameFilter : ISchemaFilter
{
public void Apply(Schema schema, SchemaRegistry schemaRegistry, Type type)
{
schema.title = type.Name;
}
}
I got the idea from here
It seems like Swagger and/or NSwag do not handle Generic List/IList/IEnumerable types very well as a base type, perhaps because some frameworks that may try to connect with Swagger don't understand them.
I have worked around this by wrapping my List in another object. So, in your case, you may need to do something like:
public ListResponseObject<T>()
{
public IEnumerable<T> ResponseList {get; set;}
}
And then return from your controller like this:
public ListResponseObject<Device> Get()
{
return new ListResponseObject<Device>{ResponseList = new List<Device>()};
}
Not as simple... but should get it through Swagger better.
We've leveraged this to our advantage. We've applied this technique to all controllers (or something similar) so we have a more standardized response. We can also do this:
public ListResponseObject<T>() : ResponseObject<T>
{
public IEnumerable<T> ResponseList {get; set;}
}
public ResponseObject<T>()
{
public string Message {get; set;}
public string Status {get; set;}
}
And now you have a container that will make downstream handling a little easier.
Not an exact answer, but a work-around that's worked for us. YMMV
UPDATE: Here's a response to a question I posted in the NSwag GitHub issues:
I think its correct as is. Currently swagger and json schema do not support generics (only for arrays) and thus all generic types are expanded to non-generic/specific types... altough the models should be correct but you may end up with lots of classes...
An enhancement for supporting generics is planned but this will be not compliant with swagger and only work with nswag... (No support in swagger ui)
I've got a small integration service which recieves XML files and parses it.
Also I've created classes from provided XSD for deserializing XML data. During parsing I need to copy properties from those XSD-generated classes to my own that I use in Data Layer. This is an example of my aproach
var supplierInfo = new SupplierInfo();
//coping properties
supplierInfo.Name = supplier.name;
supplierInfo.ShortName = supplier.shortName;
supplierInfo.BrandName = supplier.brandName;
supplierInfo.AdditionalFullName = supplier.additionalFullName;
supplierInfo.AdditionalCode = supplier.additionalCode;
supplierInfo.AdditionalInfo = supplier.additionalInfo;
//lot's of other properties
//...
supplierInfo.Tax = supplier.tax;
supplierInfo.RegistrationDate = supplier.registrationDate;
Some times ammount of properties is very big. Is there more eligant way to copy those properties?
Automapper has been out there since ages ago. Tried and tested. http://automapper.org/
Here's an example:
using System;
using AutoMapper;
public class Program
{
class SupplierInfo
{
public SupplierInfo( string name, string shortName, string brandName ) {
Name = name;
ShortName = shortName;
BrandName = brandName;
}
public string Name {get; private set; }
public string ShortName {get; private set; }
public string BrandName {get; private set; }
}
class Supplier
{
public string name {get; set; }
public string shortName {get; set; }
public string brandName {get; set; }
}
public static void Main()
{
var dto = new Supplier() {
name = "Name 1",
shortName = "Short Name 1",
brandName = "Brand Name 1"
};
//From the tutorial:
//You only need one MapperConfiguration instance typically per AppDomain and should be instantiated during startup.
var config = new MapperConfiguration(cfg => cfg.CreateMap<Supplier, SupplierInfo>());
var mapper = config.CreateMapper();
SupplierInfo info = mapper.Map<SupplierInfo>(dto);
Console.WriteLine( info.Name );
Console.WriteLine( info.ShortName );
Console.WriteLine( info.BrandName );
}
}
The official Getting Started guide can be found at https://github.com/AutoMapper/AutoMapper/wiki/Getting-started
I am happy to be corrected on this but I always find automapper (as per the other answer), which maps property values by name/convention, a little scary to use in production code.
I don't really have a decent alternative but I prefer to do it manually as per your code sample - it's easier to read and debug and if you end up renaming any properties in a class, it will be clear that the copying code is broken (or if you use some IDE tool to rename the property, it'll change the copy code accordingly).
First, install EntityLite.Core:
PM> Install-Package EntityLite.Core
Then use it:
using inercya.EntityLite.Extensions;
...
supplierInfo.AssignPropertiesFrom(supplier);
EntityLite is a micro ORM I developed. It has some little gems :-)
EDIT:
I guess you may not want to install EntityLite.Core just to copy some properties from an object to another. So here you have an implementation of AssignPropertiesFrom extension method that uses Reflection:
public static class ObjectExtensions
{
public static void AssignPropertiesForm(this object target, object source)
{
if (target == null || source == null) throw new ArgumentNullException();
var targetPropertiesDic = target.GetType().GetProperties().Where(p => p.CanWrite).ToDictionary(p => p.Name, StringComparer.CurrentCultureIgnoreCase);
foreach (var sourceProp in source.GetType().GetProperties().Where(p => p.CanRead))
{
PropertyInfo targetProp;
if (targetPropertiesDic.TryGetValue(sourceProp.Name, out targetProp))
{
targetProp.SetValue(target, sourceProp.GetValue(source, null), null);
}
}
}
}
Incidentally, this is not the EntityLite implementation. EntityLite uses dynamic IL generation.
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);