Given the requirement:
Take an object graph, set all enum type properties based on the processed value of a second string property. Convention dictates that the name of the source string property will be that of the enum property with a postfix of "Raw".
By processed we mean we'll need to strip specified characters e.t.c.
I've looked at custom formatters, value resolvers and type converters, none of which seems like a solution for this?
We want to use AutoMapper as opposed to our own reflection routine for two reasons, a) it's used extensively throughout the rest of the project and b) it gives you recursive traversal ootb.
-- Example --
Given the (simple) structure below, and this:
var tmp = new SimpleClass
{
CountryRaw = "United States",
Person = new Person { GenderRaw="Male" }
};
var tmp2 = new SimpleClass();
Mapper.Map(tmp, tmp2);
we'd expect tmp2's MappedCountry enum to be Country.UnitedStates and the Person property to have a gender of Gender.Male.
public class SimpleClass1
{
public string CountryRaw {get;set;}
public Country MappedCountry {get;set;}
public Person Person {get;set;}
}
public class Person
{
public string GenderRaw {get;set;}
public Gender Gender {get;set;}
public string Surname {get;set;}
}
public enum Country
{
UnitedStates = 1,
NewZealand = 2
}
public enum Gender
{
Male,
Female,
Unknown
}
Thanks
I did it with the ValueInjecter,
here is the whole thing:
I've added one more prop to the SimpleClass just to show you how it works
public class SixFootUnderTest
{
[Test]
public void Test()
{
var o = new SimpleClass1
{
CountryRaw = "United States",
GenderRaw = "Female",
Person = new Person { GenderRaw = "Male" }
};
var oo = new SimpleClass1();
oo.InjectFrom(o)
.InjectFrom<StrRawToEnum>(o);
oo.Person.InjectFrom<StrRawToEnum>(o.Person);
oo.Country.IsEqualTo(Country.UnitedStates);
oo.Gender.IsEqualTo(Gender.Female);
oo.Person.Gender.IsEqualTo(Gender.Male);
}
public class SimpleClass1
{
public string CountryRaw { get; set; }
public Country Country { get; set; }
public string GenderRaw { get; set; }
public Gender Gender { get; set; }
public Person Person { get; set; }
}
public class Person
{
public string GenderRaw { get; set; }
public Gender Gender { get; set; }
public string Surname { get; set; }
}
public class StrRawToEnum : LoopValueInjection
{
protected override bool UseSourceProp(string sourcePropName)
{
return sourcePropName.EndsWith("Raw");
}
protected override string TargetPropName(string sourcePropName)
{
return sourcePropName.RemoveSuffix("Raw");
}
protected override bool TypesMatch(Type sourceType, Type targetType)
{
return sourceType == typeof(string) && targetType.IsEnum;
}
protected override object SetValue(object sourcePropertyValue)
{
return Enum.Parse(TargetPropType, sourcePropertyValue.ToString().Replace(" ", ""), true);
}
}
public enum Country
{
UnitedStates = 1,
NewZealand = 2
}
public enum Gender
{
Male,
Female,
Unknown
}
}
also in case you need to do it from CountryRaw to MappedCountry
you could do it like this:
oo.InjectFrom(new StrRawToEnum().TargetPrefix("Mapped"), o);
Related
I still get zero as response on my Edentity Column after SaveChangesAsync().
Here's my entity
public class Person
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int PersonID { get; set; }
[Required, Column(TypeName = "VARCHAR"), StringLength(50)]
public string? Name { get; set; }
[Required, Column(TypeName = "VARCHAR"), StringLength(50), DataType(DataType.EmailAddress)]
public string? Email { get; set; }
public DateTime DateOfBirth { get; set; }
[StringLength(100)]
public string? Address { get; set; }
}
public static class PersonExtensionMethod
{
public static PersonResponse ToResponse(this Person person)
{
return new PersonResponse()
{
PersonID = person.PersonID,
Name = person.Name,
Email = person.Email,
DateOfBirth = person.DateOfBirth,
Address = person.Address,
};
}
}
Here's my insert method from service
public Task<PersonResponse?> AddPersonAsync(PersonAddRequest addRequest)
{
Person newPerson = addRequest.ToPerson();
_dbContext.Persons.Add(newPerson);
_dbContext.SaveChangesAsync();
return Task.FromResult<PersonResponse?>(newPerson.ToResponse());
}
But I Still Get 0 as PersonID on response
You have missed await and insert into database performed later than response has been generated:
public async Task<PersonResponse?> AddPersonAsync(PersonAddRequest addRequest)
{
Person newPerson = addRequest.ToPerson();
_dbContext.Persons.Add(newPerson);
await _dbContext.SaveChangesAsync();
return newPerson.ToResponse();
}
I am calling an API that returns nested classes (example below) and I am struggling to bind these to a Picker.
Is it possible to bind them nested classes to a picker as is? or do I need to somehow add them to a IList?
<Picker Title="Select a Currency" ItemsSource="{Binding CurrencyClass}" ItemDisplayBinding="{Binding currencyName}"/>
class MainPageViewModel : INotifyPropertyChanged
{
private Currencies _CurrencyClass;
public Currencies CurrencyClass
{
get { return _CurrencyClass; }
set
{
_CurrencyClass = value;
OnPropertyChanged();
}
}
}
This is a cut of the class they get desterilized too
public class Currencies
{
public class Rootobject
{
public Results results { get; set; }
}
public class Results
{
public XCD XCD { get; set; }
public EUR EUR { get; set; }
}
public class XCD
{
public string currencyName { get; set; }
public string currencySymbol { get; set; }
public string id { get; set; }
}
public class EUR
{
public string currencyName { get; set; }
public string currencySymbol { get; set; }
public string id { get; set; }
}
}
And this a cut of the json I am receiving.
{
"results": {
"XCD": {
"currencyName": "East Caribbean Dollar",
"currencySymbol": "$",
"id": "XCD"
},
"EUR": {
"currencyName": "Euro",
"currencySymbol": "€",
"id": "EUR"
}
}
}
So I figured out a work around for what I was aiming to achieve, this may not be a direct answer to my question but it is a solution for my issue.
I ended up just deserializing the JSON differently into a list of a Currency and then binding easily like you normally would.
class Currency
{
public string currencyName { get; set; }
public string currencySymbol { get; set; }
public string id { get; set; }
}
and how I deserialized it to fit in that class here, I parsed the JSON into a JObject and then for each child of each child I deserialize it into my Currency class.
List<Currency> cList = new List<Currency>();
HttpResponseMessage response = await client.GetAsync(urlAPI);
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
JObject jo = JObject.Parse(responseBody);
var children = jo.SelectToken("results").Children();
foreach(var child in children)
{
var childrenOfChild = child.Children();
foreach(var c in childrenOfChild)
{
cList.Add(JsonConvert.DeserializeObject<Currency>(JsonConvert.SerializeObject(c)));
}
}
I am working on a .NET Core Web API
So far I used to return anonymous types in my controllers but now I want to start using the full power of swagger with auto documentation of the return types.
Which lead me to start using view models.
But I am struggling with converting between the auto-generated database model classes
and the auto-generated swagger view model classes.
It works for a single instance (see GetPerson method in the controller below) but fails when I want to return lists.
So my questions:
How do I cast/convert collections/lists of objects between view models and database models
Is the code in the controller correct? Are there easier/shorter/better ways to do the conversion? (I read about using the implicit operator)
Error message I get:
Cannot implicitly convert type 'System.Linq.IQueryable' to 'System.Collections.Generic.List'. An explicit conversion exists (are you missing a cast?)
It gives me an InvalidCastException if I cast them explicitly like
List result = (List)_dbContext.Person....
there seems to be a problem with generics in the display of stackoverflow
Assume I used the generic lists with giving a type PersonView
My code looks like:
Database models
public partial class Person
{
public Person()
{
}
public int Id { get; set; }
public string Firstname { get; set; }
public string Lastname { get; set; }
public int? MainAdressId { get; set; }
public virtual Adress MainAdress { get; set; }
}
public partial class Adress
{
public Adress()
{
Person = new HashSet();
}
public int Id { get; set; }
public string CityName { get; set; }
public int CityPostalCode { get; set; }
public string StreetName { get; set; }
public string HouseNumber { get; set; }
public string FloorNumber { get; set; }
public string DoorNumber { get; set; }
public virtual ICollection Person { get; set; }
}
View models
public class City
{
public string Name { get; set; }
public int PostalCode { get; set; }
}
public class Street
{
public string Name { get; set; }
public string HouseNumber { get; set; }
public string FloorNumber { get; set; }
public string DoorNumber { get; set; }
}
public class AdressView
{
public Street Street { get; set; }
public City City { get; set; }
}
public class PersonView
{
public string FirstName { get; set; }
public string Lastname { get; set; }
public AdressView Adress { get; set; }
}
The controller class which is working for a single instance but not for lists
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Swashbuckle.SwaggerGen.Annotations;
using PersonExample.ModelsPersonDB;
using PersonExample.ModelsViewPerson;
namespace PersonExample.Controllers
{
[Route("api/[controller]")]
public class PersonViewTestController : Controller
{
private readonly PersonDBContext _dbContext;
private readonly ILogger _logger;
public PersonViewTestController(PersonDBContext dbContext, ILogger logger)
{
_dbContext = dbContext;
_logger = logger;
_logger.LogDebug("{0} > new instance created", GetType().Name);
}
[HttpGet("{id:int}", Name = "GetPerson")]
[ProducesResponseType(typeof(PersonView), 200)]
[SwaggerOperation("GetPerson")]
public virtual IActionResult GetPerson([FromRoute]int id)
{
PersonView result = _dbContext.Person
.Include(p => p.MainAdress)
.Where(p => p.Id == id)
.Select(p => new PersonView()
{
FirstName = p.Firstname,
Lastname = p.Lastname,
Adress = (p.MainAdress == null) ? null :
new AdressView()
{
Street = new Street()
{
Name = p.MainAdress.StreetName,
HouseNumber = p.MainAdress.HouseNumber,
FloorNumber = p.MainAdress.FloorNumber,
DoorNumber = p.MainAdress.DoorNumber
},
City = new City()
{
Name = p.MainAdress.CityName,
PostalCode = p.MainAdress.CityPostalCode
}
}
}
)
.FirstOrDefault();
return new ObjectResult(result);
}
[HttpGet(Name = "GetPersonList")]
[ProducesResponseType(typeof(List), 200)]
[SwaggerOperation("GetPersonList")]
public virtual IActionResult GetPersonList()
{
List result = _dbContext.Person
.Include(p => p.MainAdress)
.Select(p => new PersonView()
{
FirstName = p.Firstname,
Lastname = p.Lastname,
Adress = (p.MainAdress == null) ? null :
new AdressView()
{
Street = new Street()
{
Name = p.MainAdress.StreetName,
HouseNumber = p.MainAdress.HouseNumber,
FloorNumber = p.MainAdress.FloorNumber,
DoorNumber = p.MainAdress.DoorNumber
},
City = new City()
{
Name = p.MainAdress.CityName,
PostalCode = p.MainAdress.CityPostalCode
}
}
}
);
return new ObjectResult(result);
}
}
}
you can use AutoMapper https://github.com/AutoMapper/AutoMapper/wiki/Getting-started
here some examples: Simple Automapper Example
example with EF core and ASP.NET WebApi: https://github.com/chsakell/aspnet5-angular2-typescript
I missed the .ToList() at the end of the query.
The full controller know looks like:
[HttpGet(Name = "GetPersonList")]
[ProducesResponseType(typeof(List), 200)]
[SwaggerOperation("GetPersonList")]
public virtual IActionResult GetPersonList()
{
List result = _dbContext.Person
.Include(p => p.MainAdress)
.Select(p => new PersonView()
{
FirstName = p.Firstname,
Lastname = p.Lastname,
Adress = (p.MainAdress == null) ? null :
new AdressView()
{
Street = new Street()
{
Name = p.MainAdress.StreetName,
HouseNumber = p.MainAdress.HouseNumber,
FloorNumber = p.MainAdress.FloorNumber,
DoorNumber = p.MainAdress.DoorNumber
},
City = new City()
{
Name = p.MainAdress.CityName,
PostalCode = p.MainAdress.CityPostalCode
}
}
}
).ToList(); //missed that line
return new ObjectResult(result);
}
By injecting values into my domain object, I would keep the values of some properties.
Example:
Domain model
public class Person
{
public string Name { get; set; }
public Guid ID { get; set; }
public DateTime CreateAt { get; set; }
public string Notes { get; set; }
public IList<string> Tags { get; set; }
}
View Model
public class PersonViewMode
{
public string Name { get; set; }
public Guid ID { get; set; }
public DateTime CreateAt { get; set; }
public string Notes { get; set; }
public IList<string> Tags { get; set; }
public PersonViewMode() { ID = Guid.NewGuid(); } //You should use this value when it is the Target
}
Sample
var p = new Person
{
ID = Guid.NewGuid() //Should be ignored!
,
Name = "Riderman"
,
CreateAt = DateTime.Now
,
Notes = "teste de nota"
,
Tags = new[] {"Tag1", "Tag2", "Tag3"}
};
var pvm = new PersonViewMode();
pvm.InjectFrom(p); //Should use the ID value generated in the class constructor PersonViewMode
if you delete the set; from from the ViewModel's ID then it won't be set;
otherwise you could save the value of ID in a separate variable and put it back after injecting,
or you can create a custom valueinjection that would ignore "ID" or would receive a list of properties to ignore as a parameter
here's the example for a custom injection that receives a list of property names to ignore:
public class MyInj : ConventionInjection
{
private readonly string[] ignores = new string[] { };
public MyInj(params string[] ignores)
{
this.ignores = ignores;
}
protected override bool Match(ConventionInfo c)
{
if (ignores.Contains(c.SourceProp.Name)) return false;
return c.SourceProp.Name == c.TargetProp.Name && c.SourceProp.Type == c.TargetProp.Type;
}
}
and use it like this:
pvm.InjectFrom(new MyInj("ID"), p);
if you need to ignore more, you can do like this:
pvm.InjectFrom(new MyInj("ID","Prop2","Prop3"), p);
Given the classes:
public class Person
{
public string Name { get; set; }
}
public class Student : Person
{
public int StudentId { get; set; }
}
public class Source
{
public Person Person { get; set; }
}
public class Dest
{
public string PersonName { get; set; }
public int? PersonStudentId { get; set; }
}
I want to use Automapper to map Source -> Dest.
This test obviously fails:
Mapper.CreateMap<Source, Dest>();
var source = new Source() { Person = new Student(){ Name = "J", StudentId = 5 }};
var dest = Mapper.Map<Source, Dest>(source);
Assert.AreEqual(5, dest.PersonStudentId);
What would be the best approach to mapping this given that "Person" is actually a heavily used data-type throughout our domain model.
Edit: The intent is to persist the "Dest" objects which will have fields defined for all properties of the sub-types of "Person". Hence we could have source objects like the following and would prefer not to have to create Dest objects for every possible combination of "Person" sub-classes:
public class Source2
{
public Person Value1 { get; set; }
public Person Value2 { get; set; }
public Person Value3 { get; set; }
public Person Value4 { get; set; }
public Person Value5 { get; set; }
}
Well using Jimmy's suggestion I've settled on the following solution:
public class Person
{
public string Name { get; set; }
}
public class Student : Person
{
public int StudentId { get; set; }
}
//all subtypes of person will map to this dto
public class PersonDto
{
public string Name { get; set; }
public int? StudentId { get; set; }
}
public class Source
{
public Person Person { get; set; }
}
public class DestDto
{
public PersonDto Person { get; set; }
}
public class Dest
{
public string PersonName { get; set; }
public int? PersonStudentId { get; set; }
}
[TestFixture]
public class RandomTests
{
[Test]
public void Test1()
{
Mapper.CreateMap<Person, PersonDto>();
Mapper.CreateMap<Student, PersonDto>();
Mapper.CreateMap<Source, DestDto>();
Mapper.CreateMap<DestDto, Dest>();
var source = new Source() { Person = new Student() { Name = "J", StudentId = 5 } };
var destDto = Mapper.Map<Source, DestDto>(source);
var destFinal = Mapper.Map<DestDto, Dest>(destDto);
Assert.AreEqual(5, destFinal.PersonStudentId);
}
}
Would love to hear suggestions/improvements.