How do I use one method for multiple DTO? - c#

I have an Address entity as follows:
public class Address
{
public string? City { get; set; }
public string? Country { get; set; }
public string? Contact { get; set; }
public string? Street1 { get; set; }
public string? Street2 { get; set; }
public string? State { get; set; }
public string? Zip { get; set; }
}
According to the best practices, I use different DTOs for different parts of app according to the users' needs. Suppose that I have two DTOs from the Address entity as follows:
public class AddressDto1
{
public string? City { get; set; }
public string? Country { get; set; }
public string? Contact { get; set; }
public string? Street1 { get; set; }
public string? Street2 { get; set; }
public string? State { get; set; }
public string? Zip { get; set; }
}
public class AddressDto2
{
[Requierd]
public string Street1 { get; set; }
[Requierd]
public string Street2 { get; set; }
public string? State { get; set; }
public string? Zip { get; set; }
}
User1 can see and create the Address by AddressDto1 field, and User2 can see and create the Address by AddressDto2 field.
I have a method in the service that inherit from an interface and I use AutoMapper to map the DTO to Entity.
public interface IAddressService
{
public Task<AddressDto1> CreateAddressAsync(AddressDto1 AddressDto1);
}
public class AddressService:IAddressService
{
public Task<AddressDto1> CreateAddressAsync(AddressDto1 addressDto1)
{
var newAddress = _mapper.Map<Address>(addressDto1);
var addedAddress = await _dbContext.Addresses.AddAsync(newAddress);
await _dbContext.SaveChangesAsync();
return _mapper.Map<AddressDto1>(addedAddress.Entity);
}
}
And it's mapping profile:
public MappingProfile()
{
CreateMap<AddressDto1, Address>().ReverseMap();
CreateMap<AddressDto2, Address>().ReverseMap();
}
I'd like to use CreateAddressAsync for any AddressDtos.
What should be the best way to implement CreateAddressAsync method so that the AddressDto2 can be added to the bank with the same method?

You need to make you method generic so that you can call the service for both models
public async Task<T> CreateAddressAsync<T>(T addressDto1)
{
var newAddress = _mapper.Map<T, Address>(T);
var addedAddress = await _dbContext.Addresses.AddAsync(newAddress);
await _dbContext.SaveChangesAsync();
return _mapper.Map<Address, T>(addedAddress.Entity);
}
//calling service
var address1= await _addressService.CreateAddressAsync<AddressDto>(model);
You can have more control over input parameters by having a base model for address dto after you can add this where T: baseAddress
Also you can make data layer generic by _dbContext.Set<TEntity>.

Related

How unflattening commands to complex types

I'm not yet dependent to either Mapster or AutoMapper. For now I'm using handwritten mappings because I couldn't find a mapper who could do this with smaller code.
The problem is how do we map flatten structures to complex objects? I think a lot of people could benefit from a good mapping example for such a complex object. I've got even a mapping condition based on CopyOfficeAddressAsInvoiceAddress whether or not the office address needs to be copied as invoice address. I've looked all over the place but couldn't get it to work.
Maybe I should also use a different naming to make it more clear for the mapping algorithm?!
The biggest question could such a map being resolved by a mapper or is this to complex? Al the demo's I've seen were using dto and model objects that are quite similar to each other. I didn't get the point of mapping an object to another object that 99% similar to each other.
I have a Command (I'm using Mediatr) that looks like as follows:
public class Command : IRequest<IActionResult>
{
public string AccountName { get; set; }
public string ContactFirstName { get; set; }
public string ContactLastName { get; set; }
public string ContactEMail { get; set; }
public string ContactPhoneNumber { get; set; }
public string BankAccount { get; set; }
public string Bank { get; set; }
public string OfficeName { get; set; }
public string OfficeAddressStreet { get; set; }
public int OfficeAddressStreetNumber { get; set; }
public string? OfficeAddressStreetNumberAddition { get; set; }
public string OfficeAddressPostalcode { get; set; }
public string OfficeAddressCity { get; set; }
public string OfficeAddressCountry { get; set; }
public string? OfficeInvoiceAddressStreet { get; set; } = null;
public int? OfficeInvoiceAddressStreetNumber { get; set; } = null;
public string? OfficeInvoiceAddressStreetNumberAddition { get; set; } = null;
public string? OfficeInvoiceAddressPostalcode { get; set; } = null;
public string? OfficeInvoiceAddressCity { get; set; } = null;
public string? OfficeInvoiceAddressCountry { get; set; } = null;
//[Ignore]
public bool? CopyOfficeAddressAsInvoiceAddress { get; set; } = false;
public string? AssociationIdentifier { get; set; } = null;
}
And I want it to be mapped to the following models:
public class Account
{
public int Id { get; set; }
public string AccountName { get; set; }
public IList<Contact> Users { get; set; }
public IList<Office> Offices { get; set; }
public string Bank { get; set; }
public string BankAccount { get; set; }
public string? AssociationIdentifier { get; set; }
}
public class Office
{
public int Id { get; set; }
public string Name { get; set; }
public Address ContactAddress { get; set; }
public Address InvoiceAddress { get; set; }
public bool HeadQuarter { get; set; }
}
public class Address
{
public int Id { get; set; }
public string Street { get; set; }
public string Postalcode { get; set; }
public int StreetNumber { get; set; }
public string StreetNumberAddition { get; set; }
public string City { get; set; }
public string Country { get; set; }
}
public class Contact
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string EMail { get; set; }
public string PhoneNumber { get; set; }
}
First of all, my experience is mainly using Automapper, and it is definitely possible to map complex types like this.
But your command does not need to be completely flat. There is nothing inherently wrong with DTOs being similar to your domain models. Using Automapper this is fairly easy as properties with the same name are mapped 1:1.
It could be that you are submitting a form with all the properties flattened in one object. In that case you could define either a seperate map for this object and each domain object.
CreateMap<AccountDto, Account>(); // mapping logic omitted
CreateMap<AccountDto, Office>();
...
Or you could map the one object to a range of objects using Tuples.
CreateMap<AccountDto, (Account, Office, ...)>(); // mapping logic omitted
But if you define seperate DTOs and make mapping profiles for them, it will probably ease your whole mapping experience. For copying the address, you can simply do something like this, in that case.
if (copyAddress)
{
office.InvoiceAddress = _mapper.Map<Address>(addressDto);
}

Validation of Class with FluentValidation returning object is required for non-static field

I am trying to add FluentValidator to my .net core 3.1 Worker Service. I created a class that will hold my CSV parsed files.
public partial class Subjects
{
public Guid SubjectId { get; set; }
public string Code { get; set; }
public Guid OrganizationId { get; set; }
public string PreferredName { get; set; }
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string LastName { get; set; }
public DateTime? DateOfBirth { get; set; }
public string Gender { get; set; }
public string LastNameInitial { get; set; }
public string CodeDisplay { get; set; }
public Guid? RaceId { get; set; }
public Guid? MaritalStatusId { get; set; }
public Guid? StatusId { get; set; }
public string Rank { get; set; }
public string Email { get; set; }
public string MobilePhone { get; set; }
public bool MobilePhoneDoNotLeaveMsg { get; set; }
public bool MobilePhoneDoNotText { get; set; }
public string WorkPhone { get; set; }
public bool WorkPhoneDoNotLeaveMsg { get; set; }
}
Then according to the documentation, I create the Validation class implementing the AbstractValidator interface:
class SubjectsValidation : AbstractValidator<Subjects>
{
public SubjectsValidation()
{
RuleFor(subject => Subjects.FirstName).NotEmpty();
}
}
According to the documentation, I need to add the rules in the constructor. However, when I pass in the lambda for the rule I got this error:
"An object reference is required for non-static field, method, or
property"
So I modified the constructor like this:
class SubjectsValidation : AbstractValidator<Subjects>
{
public SubjectsValidation()
{
Subjects subject = new Subjects();
RuleFor(x=>subject.FirstName).NotEmpty();
}
}
Which got of the error, but when I try to call the validation I get an error when I instantiate the object for testing:
Subjects subject = new Subjects();
subject.FirstName = "John";
SubjectsValidation validator = SubjectsValidation();
This returns a failed message because I had to create a new subject in the Subject Validation so it return a failure. So following the instructions on the website, I just can't get this working. How do I fix this?
The RuleFor method is expecting a delegate. So your first attempt will work if you use the parameter of the delegate within the body (subject instead of Subjects):
class SubjectsValidation : AbstractValidator<Subjects>
{
public SubjectsValidation()
{
RuleFor(subject => subject.FirstName).NotEmpty();
}
}

nPoco - Get single object with nested data

I have two Dto's:
[TableName("Address")]
public class AddressDto
{
public int Id { get; set; }
public string Name { get; set; }
public string City { get; set; }
public string Street { get; set; }
public string Building { get; set; }
public string Appartment { get; set; }
public string ZipCode { get; set; }
public string Floor { get; set; }
public DateTime CreatedDate { get; set; }
}
[TableName("DistributionPoint")]
public class DistributionPointDto
{
[Column("Id")]
public int Id { get; set; }
public string Name { get; set; }
[Reference(ReferenceType.Foreign, ColumnName = "AddressId", ReferenceMemberName = "Id")]
public AddressDto Address { get; set; }
}
How can I get DistributionPointDto with nested AddressDto using nPoco?
I have a generic repository for CRUD with method:
public T FindById<T>(int id)
{
return _db.SingleById<T>(id);
}
But, when I'm trying to get DistributionPointDto, AddressDto is null
Curious if you got your code to work, but I have found that you need to use Query instead of SingleById to include referenced properties, and then you can use an Include statement/clause, however I don't think generics work with this, you have to explicitly mention the referenced property name, which is AddressDTO for you.
_db.Query<DistributionPointDTO>()
.Include(x => x.AddressDTO)
.Where(x => x.Id == id).First();

How can i expose dto objects using wcf data service with ef code first?

I am trying to make a wcf data service where i dont want to get acces to the database models but instead i want to use Data transfer objects. I have been reading a lot on the internet about how to accomplish this but i cant get a good answer for my problem. It is the first time for me doing something with wcf data services so i am a little inexperienced.
Oke here are my models that are linked to my database using Entity Framework
public class User
{
[Key]
public int UserId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string UserName { get; set; }
public string Email { get; set; }
public string CountryCode { get; set; }
public string PhoneNumber { get; set; }
public ICollection<User> Contacts { get; set; }
public virtual Language Language { get; set; }
public User()
{
Contacts = new List<User>();
}
}
public class Message
{
[Key]
public int MessageId { get; set; }
public DateTime SentDate { get; set; }
public virtual User Sender { get; set; }
public virtual User Receiver { get; set; }
public string Content { get; set; }
public string OriginalCultureInfoEnglishName { get; set; }
public string ForeignCultureInfoEnglishName { get; set; }
}
public class Language
{
[Key]
public int LanguageId { get; set; }
public string CultureInfoEnglishName { get; set; }
}
Now i made a Service.svc which has my DatabaseContext so it can directly acces my database models. What i want to achieve is that instead of directly getting the database models i would like to get the DTO models when i query against my service.
A Example of how my dto's would look like
public class UserDTO
{
public int UserId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string UserName { get; set; }
public string Email { get; set; }
public string Country { get; set; }
public string PhoneNumber { get; set; }
public ICollection<ContactDTO> Contacts { get; set; }
public virtual LanguageDTO Language { get; set; }
public UserModel()
{
Contacts = new List<ContactDTO>();
}
}
public class ContactDTO
{
public int UserId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string UserName { get; set; }
public string Email { get; set; }
public string Country { get; set; }
public string PhoneNumber { get; set; }
public virtual LanguageDTO Language { get; set; }
}
public class LanguageDTO
{
public int LanguageId { get; set; }
public string CultureInfoEnglishName { get; set; }
}
public class MessageDTO
{
public int MessageId { get; set; }
public DateTime SentDate { get; set; }
public virtual ContactDTO Sender { get; set; }
public virtual ContactDTO Receiver { get; set; }
public string Content { get; set; }
public string OriginalCultureInfoEnglishName { get; set; }
public string ForeignCultureInfoEnglishName { get; set; }
}
Now is it possible to do it like this by making a different context that i can use in my service.svc or is there any other way to achieve the this?
for example i would like to get ContactDto by userid which is a user but with less properties because they are not relevant in the client application. I see this happening by a uri http://localhost:54895/Service.svc/ContactDto(1)
Hopefully anyone can clear this up for me because it is really frustrating :)
I'm not sure that what you're interested in is possible, exactly. You are looking to have multiple entity sets per type (aka MEST), and I don't know how well that's supported.
Beyond that point, and into a discussion around DTOs in general...
If you use custom providers, you can implement your own IDataServiceMetadataProvider and IDataServiceQueryProvider. When your service starts, you can make calls into the IDataServiceMetadataProvider to control what entities and properties are exposed or hidden -- including exposing properties that do not actually exist on your entity. The upshot is that you end up with a DTO without coding a DTO class. The exposed metadata is the DTO. This is a good resource for creating your own providers.
In your case, this isn't a 100% solution, because you can't selectively choose when a property is exposed and when it's not.
Hope this helps...

Entity Framework MVC 4 Class Issue

I am trying to build a code first database with the Entity Framework with ASP.Net MVC 4.
I'm new to MVC & Entity Framework and I'm struggling with how to design my Class Objects.
I want a Members Class Like the one that follows, that has a data property of the AddressInformation Class :-
public class Member
{
public virtual int MemberID { get; set; }
public virtual string Forename { get; set; }
public virtual string Surname { get; set; }
public virtual int age { get; set; }
public virtual AddressInformation Address { get; set; }
public virtual string EmailAddress { get; set; }
public virtual string HomePhoneNumber { get; set; }
public virtual string MobileNumber { get; set; }
}
public class AddressInformation
{
public virtual int MemberID { get; set; }
public virtual string HouseNoName { get; set; }
public virtual string StreetName { get; set; }
public virtual string Town { get; set; }
public virtual string County { get; set; }
public virtual string PostCode { get; set; }
public virtual string Country { get; set; }
}
I also have another class that inherits from DbContext :-
public class CentralDataStore :DbContext
{
public DbSet<Member> Members { get; set; }
public DbSet<AddressInformation> AddressInfo { get; set; }
}
When I add the controller I am not getting the abilty to enter AddressInformation, only members info has populated through to my View's.
Anyone suggest the best method to attack this with? As I say, I'm new to MVC.
You do not need to make all of your properties virtual, only the ones used for navigation. And you need to setup up the relationship between Member and AddressInformation using the Fluency API. Also your primary key needs to be named Id or use an attribute or Fluency API to specify it is a primary key. You are also missing the id for mapping the Member to the AddressInformation. Here is what your class definition should look like.
public class Member
{
public int ID { get; set; }
public string Forename { get; set; }
public string Surname { get; set; }
public int age { get; set; }
public virtual int AddressId { get; set; }
public virtual AddressInformation Address { get; set; }
public string EmailAddress { get; set; }
public string HomePhoneNumber { get; set; }
public string MobileNumber { get; set; }
}
public class AddressInformation
{
public int ID { get; set; }
public string HouseNoName { get; set; }
public string StreetName { get; set; }
public string Town { get; set; }
public string County { get; set; }
public string PostCode { get; set; }
public string Country { get; set; }
}
Note I added the property AddressId to provide the mapping to the AddressInformation object/table. Configure the relationships in the Fluency API like this.
public class MemberConfig : EntityTypeConfiguration<Member>
{
internal MemberConfig()
{
this.HasKey(m => m.ID);
this.HasRequired(m => m.Address)
.WithRequiredDependent(a => a.ID)
.HasForeignKey(m => m.AddressId);
}
}
By setting up the foreign key relationship EF will automatically load the AddresssInformation into the Member object.
As far as I know the standard templates for generating the views does not implement the input fields for nested objects. But there is an option to expand the standard templates of MVC applications like in this link. There you can add the generation of input fields for nested classes if you are a fimilar to T4 templates.
You must be careful using this template, you can EASILY get a stackoverflow
Especially when using Entity Framework, when you have two entities with navigation properties that point to each other
The default Object template prevents recursion to a specific depth to prevent an infinite loop. I didn't like this so I wrote my own:
/Views/Shared/object.cshtml
#model object
#using System.Text;
#using System.Data;
#{
ViewDataDictionary viewData = Html.ViewContext.ViewData;
TemplateInfo templateInfo = viewData.TemplateInfo;
ModelMetadata modelMetadata = viewData.ModelMetadata;
System.Text.StringBuilder builder = new StringBuilder();
string result;
// DDB #224751
if (templateInfo.TemplateDepth > 2)
{
result = modelMetadata.Model == null ? modelMetadata.NullDisplayText
: modelMetadata.SimpleDisplayText;
}
foreach (ModelMetadata propertyMetadata in modelMetadata.Properties
.Where(pm => pm.ShowForEdit
&& pm.ModelType != typeof(System.Data.EntityState)
&& !templateInfo.Visited(pm)))
{
builder.Append(Html.Editor(propertyMetadata.PropertyName).ToHtmlString());
}
result = builder.ToString();
}
#Html.Raw(result)

Categories