Optimize fluent validation code from duplicating to one extension method - asp.net-web-api

public interface IAdditional
{
int Id { get; set; }
string Version { get; set; }
}
public interface IUser
{
string Name { get; set; }
}
// dto
public class CreateUser : IUser
{
public string Name { get; set; }
}
// dto
public class UpdateUser : IAdditional, IUser
{
int Id { get; set; }
string Version { get; set; }
public string Name { get; set; }
}
internal class UserValidator : AbstractValidator<IUser>
{
public UserValidator()
{
When(u => u is IAdditional, () =>
{
RuleFor(u => (u as IAdditional).Id)
.GreaterThan(0);
RuleFor(u => (u as IAdditional).Version)
.NotEmpty();
});
RuleFor(u => u.Name).NotEmpty();
}
}
So, how I could optimize this code part:
When(u => u is IAdditional, () =>
{
RuleFor(u => (u as IAdditional).Id)
.GreaterThan(0);
RuleFor(u => (u as IAdditional).Version)
.NotEmpty();
});
This code part i use in every controller. Every controller has f-validator and in every f-validator I use this code lines. It should be great to move it to separate class and reuse it in every f-validator. Tried look to extension method, but I can't understand how it to use make working

Related

Asp.Net Core / Entity Framework Core - System.Linq.Enumerable+SelectEnumerableIterator`2[MyClass,System.String]

I am trying to return a json array of my 3rd level depth related data, the issue here is that I get the result with the right property name but with a non clear value content, I failed to find a similar case to solve it. From the returned value message it looks like I am returning a queryable instead of the final result and I need to iterate over it, I've tried several ways to achive that but failed to find the right one.
The json result:
[
{
"registeredYear": "System.Linq.Enumerable+SelectEnumerableIterator`2[MyPath.Groups.GroupYear,System.String]"
}
]
The api endpoint
public async Task<ActionResult<IEnumerable<UserGroup>>> GetUserGroupYears(string email, string groupName)
{
var groupYears = await _repo.GetUserGroupYears(email, groupName);
var mappedEntities = _mapper.Map<GroupYearsListDto[]>(groupYears);
return Ok(mappedEntities);
}
The Repository method
public async Task<IEnumerable<UserGroup>> GetUserGroupYears(string email, string groupName)
{
var userGroupYears = _context.UserGroups
.Include(uo => uo.Group.GroupYears)
.ThenInclude( oy => oy.Year)
.Where(uo => uo.Group.Name == groupName && uo.Email == email );
return await userGoupYears.ToArrayAsync();
}
The used classes:
public class UserGroup
{
public string Email { get; set; }
public string UserId { get; set; }
public virtual User User { get; set; }
public string GroupId { get; set; }
public virtual Group Group { get; set; }
}
public class Group
{
public string Name { get; set; }
public virtual ICollection<UserGroup> Users { get; set; }
public virtual ICollection<GroupYear> GroupYears { get; }
}
public class GroupYear {
public string GroupId { get; set; }
public virtual Group Group { get; set; }
public string YearId { get; set; }
public virtual Year Year { get; set; }
public string RegisteredYear { get; set; }
}
The data transfer object and the mapping:
public class GroupYearsListDto
{
public string RegisteredYear { get; set; }
}
public CoreMappingProfiles()
{
CreateMap<UserGroup, GroupYearsListDto>()
.ForMember(
dest => dest.RegisteredYear,
opt => opt.MapFrom(src => src.Group.GroupYears.Select(x => x.RegisteredYear))
);
}
Update: Attaching a debugger shows that the repository method is returning an IQueryable including the correct values and the controller method makes something wrong when mapping. So I think the following line is responsible of that wrong result:
var mappedEntities = _mapper.Map<GroupYearsListDto[]>(GroupYears);
You are getting this JSON result:
[
{
"registeredYear": "System.Linq.Enumerable+SelectEnumerableIterator`2[MyPath.Groups.GroupYear,System.String]"
}
]
Because you are mapping an IEnumerable<string> to a string, as I mentioned in my comment. So essentially you are getting the same as:
CreateMap<UserGroup, GroupYearsListDto>()
.ForMember(
dest => dest.RegisteredYear,
opt => opt.MapFrom(src =>
{
IEnumerable<string> registeredYears = src.Group.GroupYears.Select(x => x.RegisteredYear);
return registeredYears.ToString();
})
);
And registeredYears.ToString() is "System.Linq.Enumerable+SelectEnumerableIterator`2[MyPath.Groups.GroupYear,System.String]".
I imagine you will either:
Only have one - so do something like: src.Group.GroupYears.Select(x => x.RegisteredYear).Single()
Have multiples - so do something like: string.Join(", ", src.Group.GroupYears.Select(x => x.RegisteredYear))
You have many options, but you need to actually return a string to that property or else you will just get the ToString() version of IEnumerable<string>.
UPDATE:
Based on your comments below, you can try this:
Repository:
public IQueryable<GroupYear> GetGroupYears(string email, string groupName)
{
return _context
.UserGroups
.Where(x => x.Group.Name == groupName && x.Email == email)
.SelectMany(x => x.Group.GroupYears);
}
Controller:
public async Task<ActionResult<GroupYearsListDto[]>> GetGroupYears(string email, string groupName)
{
var groupYears = _repo.GetGroupYears(email, groupName);
var projection = _mapper.ProjectTo<GroupYearsListDto>(groupYears)
var mappedEntities = await projection.ToArrayAsync();
return Ok(mappedEntities);
}
Profile:
CreateMap<GroupYears, GroupYearsListDto>();

How to use Custom mapper with IObjectMapper on the service

Work on aspnetboilerplate asp.net core project, face difficulties on custom mapping. Want to create custom map and want to use it several times as like AUTOMAP profiler.Follow documentation but failed to implement this on my project.
My steps are
1)Create a class MyModule under the XXX.Core
[DependsOn(typeof(AbpAutoMapperModule))]
public class MyModule : AbpModule
{
public override void PreInitialize()
{
Configuration.Modules.AbpAutoMapper().Configurators.Add(config =>
{
config.CreateMap<CreateUserInput, CreatUserOutput>()
.ForMember(u => u.Password, options => options.Ignore())
.ForMember(u => u.OutputEmailAddress, options => options.MapFrom(input => input.EmailAddress));
});
}
}
public class CreateUserInput
{
public string Name { get; set; }
public string Surname { get; set; }
public string EmailAddress { get; set; }
public string Password { get; set; }
}
public class CreatUserOutput
{
public string OutputName { get; set; }
public string Surname { get; set; }
public string OutputEmailAddress { get; set; }
public string Password { get; set; }
}
2)Used above configuration on xxx.Application, service as like bellow
try
{
CreateUserInput te = new CreateUserInput
{
EmailAddress = "a#yahoo.com",
Name = "input",
Password = "test",
Surname = "sure"
};
CreatUserOutput ot = new CreatUserOutput();
var temp = _objectMapper.Map<CreatUserOutput>(te);
}
catch (System.Exception ex)
{
}
I don't understand how to use custom mapper with my injected IObjectMapper on the service.
Better to create separate automapper profile.
You need to create file exactly in Application layer
You can name it for example AutoMapperProfile.cs
with following:
public class AutoMapperProfile : AutoMapper.Profile {
public AutoMapperProfile () {
CreateMap<CreateUserInput, CreatUserOutput> ()
.ForMember (u => u.Password, options => options.Ignore ())
.ForMember (u => u.OutputEmailAddress, options => options.MapFrom (input => input.EmailAddress));
}
}
to order this code works make sure that you ApplicationModule.cs
contain following code which responsible for loading profiles.
public override void Initialize () {
var thisAssembly = typeof (LicenseManagerApplicationModule).GetAssembly ();
IocManager.RegisterAssemblyByConvention (thisAssembly);
Configuration.Modules.AbpAutoMapper ().Configurators.Add (
// Scan the assembly for classes which inherit from AutoMapper.Profile
cfg => cfg.AddProfiles (thisAssembly)
);
}

Entity Framework Core 2 querying multi level collections with theniclude

I created two classes:
public class A {
public int Id { get; set; }
public ICollection<B> Bs { get; set; }
}
public class B {
public ICollection<C> C1s { get; set; }
public ICollection<C> C2s { get; set; }
}
then I tried to fetch them with ThenInclude method:
var result = context.As //public DbSet<A> As { get; set; }
.Include(a => a.Bs)
.ThenInclude(b => b.C1s)
.Include(a => a.Bs)
.ThenInclude(b => b.C2s)
.SingleOrDefaultAsync(a => a.Id.Equals(id)); //id is given
return await result;
But unfortunately both C1s and C2s collections are empty.
How to retrieve C entities which are related to B one?
I replaced .ThenInclude() methods with
.Include("Points.MasterPoints")
.Include("Points.SlavePoints")
That solved my issue.

Automapper one to one mapping

I have working solution, but a bit doubt if I made it correctly. I have base class from which derive 3 other classes Ad:
public class Ad
{
public int Id { get; set; }
public string Title { get; set; }
public Address Address { get; set; }
}
My Address class look like this:
public class Address
{
[ForeignKey("Ad")]
public int AddressId { get; set; }
public string Street { get; set; }
public string City { get; set; }
public string ZipCode { get; set; }
public virtual Ad Ad { get; set; }
}
Now I'm using automapper with this mapping:
Mapper.Initialize(config =>
{
config.CreateMap<Auto, AutoViewModel>()
.ForMember(m => m.City, vm => vm.MapFrom(m => m.Address.City))
.ForMember(m => m.Street, vm => vm.MapFrom(m => m.Address.Street))
.ForMember(m => m.ZipCode, vm => vm.MapFrom(m => m.Address.ZipCode)).ReverseMap();
});
Where AutoViewModel looks like this:
public class AutoViewModel
{
public int Id { get; set; }
public string Title { get; set; }
public string Street { get; set; }
public string City { get; set; }
public string ZipCode { get; set; }
}
In my Create and Edit actions I use this binding:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, AutoViewModel vm)
{
Address address = new Address();
address.AddressId = vm.Id;
address.City = vm.City;
address.Street = vm.Street;
address.ZipCode = vm.ZipCode;
var auto = Mapper.Map<Auto>(vm);
auto.Address = address;
if (id != auto.Id)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(auto);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!AutoExists(auto.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction("Index");
}
return View(auto);
}
Is this way correct? Is there elegant way to do it? I had to specify AddressId explicit because otherwise I'm getting duplicate Foreign key error message...

Loading of references in EF7

I'm having two classes - author and blogpost:
public class Author
{
public Author()
{
Blogposts = new HashSet<Blogpost>();
}
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Blogpost> Blogposts { get; set; }
}
and
public class Blogpost
{
public Blogpost()
{
}
// Properties
public int Id { get; set; }
public string Text { get; set; }
public int AuthorId { get; set; }
public Author Author { get; set; }
}
Using EF7 (beta4), I'm connecting them the following way:
public partial class MyDbContext : DbContext
{
public virtual DbSet<Author> Author { get; set; }
public virtual DbSet<Blogpost> Blogpost { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Author>(entity =>
{
entity.Property(e => e.Id)
.ForSqlServer().UseIdentity();
});
modelBuilder.Entity<Blogpost>(entity =>
{
entity.Property(e => e.Id)
.ForSqlServer().UseIdentity();
});
modelBuilder.Entity<Blogpost>(entity =>
{
entity.Reference<Author>(d => d.Author).InverseCollection(p => p.Blogposts).ForeignKey(d => d.AuthorId);
});
}
}
When I access a blogpost Db.Blogpost.First(x => x.Id == id) I retrieve the Blogpost object - however, the .Author property is null. Also, when retrieving any Author object, it's .Blogposts collection is empty.
I understand the EF7 has neither implemented eager-loading nor lazy-loading yet. But how would I then retrieve/assign any objects referenced via foreign key?
EF 7 has implemented eager loading.
Use .Include
var post = context.Blogpost.First(); // post.Author will be null
var post = context.Blogpost.Include(b => b.Author).First(); // post.Author will be loaded
For more information on working with collections, see the answer to this question: How to work with collections

Resources