FluentValidator. Add _validateService for all models - c#

I need to creat fluent _validator where I will not use only one model. If I need more models in my controller? I will create for each model _validator{modelName}?
My code
public class UserDtoValidator : AbstractValidator<UserDTO>
{
public UserDtoValidator()
{
RuleFor(p => p.Email).NotEmpty().EmailAddress()
.WithMessage("{PropertyName} should be not empty.");
RuleFor(p => p.Password).NotNull().Length(5, 30)
.WithMessage("{PropertyName} should be not empty.");
RuleFor(p => p.PasswordConfirm).Equal(p => p.Password);
}
}
Controller.cs
private readonly IValidator<UserDTO> _validator;
public UserController(IValidator<UserDTO> validator)
{
_validator = validator;
}
HttpPost
var validResult = _validator.Validate(model);
if (validResult.IsValid)
{
}
Startup
services.AddControllers().AddFluentValidation();
services.AddSingleton<IValidator<UserDTO>, UserDtoValidator>();
It is work good. But I need multiple models in my _validator.

I don't think it makes much sense to make fluent validator universal, because all models may have different attributes and require different validations to validate attributes.
Unless these models of yours inherit a common class, then you can use a BaseValidator to share a validator method.
public class Teacher
{
public string Name { get; set; }
public int Age { get; set; }
public string Gender { get; set; }
}
public class UserDTO
{
public string Email { get; set; }
public string Password { get; set; }
public string PasswordConfirm { get; set; }
}
public class BaseValidator<T> : AbstractValidator<T>
{
public BaseValidator()
{
}
}
public class TeacherValidator : BaseValidator<Teacher>
{
public TeacherValidator()
{
RuleFor(p => p.Name).NotEmpty().WithMessage("{PropertyName} should be not empty.");
RuleFor(p => p.Age).InclusiveBetween(18, 60)
.WithMessage("{PropertyName} should between 18-60.");
RuleFor(p => p.Gender).Must(x => new string[] { "Male", "Female" }.Contains(x)).WithMessage("Then gender can only be male or female");
}
}
public class UserDtoValidator : BaseValidator<UserDTO>
{
public UserDtoValidator()
{
RuleFor(p => p.Email).NotEmpty().EmailAddress()
.WithMessage("{PropertyName} should be not empty.");
RuleFor(p => p.Password).NotNull().Length(5, 30)
.WithMessage("{PropertyName} should be not empty.");
RuleFor(p => p.PasswordConfirm).Equal(p => p.Password);
}
}
Or you can have a look for this.

Related

Autofixture rule for properties

I'm writing integrational test to my UserController. And currently testing User creation method using AutoFixture. CreateUser method accepts CreateUserDto
public class CreateUserDto : IMapWith<User>
{
public string Username { get; init; }
public string Name { get; init; }
public string Surname { get; init; }
public DateTime BirthDay { get; init; }
public UserTypeId UserTypeId { get; init; }
public Guid CityId { get; init; }
public string Email { get; init; }
public string PhoneNumber { get; init; }
public Guid OrientationId { get; init; }
public Guid GenderId { get; init; }
public void Mapping(Profile profile)
{
profile.CreateMap<CreateUserDto, User>();
}
}
And i want to set Email to have and email look and PhoneNumber to have only 11 digits.
For email i tried this but it's not working it still generates guid instead of email:
var fixture = GetFixture();
fixture.Customizations.Add(new MailAddressGenerator());
var userDto = fixture.Create<CreateUserDto>();
I don't even know how to customize number.
EDIT
I tried to create it with custom customization:
internal class EmailAndPhoneNumberCustomization : ICustomization
{
public void Customize(IFixture fixture)
{
fixture.Customize<CreateUserDto>(c => c
.With(u => u.Email, "test#test.com")
.With(u => u.PhoneNumber, "87777777777"));
}
}
And it's ignoring it.
Code where i use it:
private async Task<IEnumerable<UserEntity>> GenerateUsers()
{
var fixture = GetFixture();
var users = fixture.CreateMany<UserEntity>(3);
return users;
}
private static Fixture GetFixture()
{
var fixture = new Fixture();
fixture.Behaviors.OfType<ThrowingRecursionBehavior>().ToList().ForEach(behavior => fixture.Behaviors.Remove(behavior));
fixture.Behaviors.Add(new OmitOnRecursionBehavior());
fixture.Customize(new EmailAndPhoneNumberCustomization());
return fixture;
}
As mentioned in the comments the generic type in CreateMany<>(3) is wrong, should be the customized type.
As for making it easier, depending on your testing framework you can make use of data attributes and inject the values into your test method.
To make data attribute creation easier, you can encapsulate the customization using the ICustomization interface.
The example below demonstrates how to encapsulate the customization into reusable classes and how to generate mail addresses and phone numbers.
/* recursion omitter customization */
public class OmitRecursionCustomization : ICustomization
{
public void Customize(IFixture fixture)
{
fixture.Behaviors.OfType<ThrowingRecursionBehavior>()
.ToList().ForEach(behavior => fixture.Behaviors.Remove(behavior));
fixture.Behaviors.Add(new OmitOnRecursionBehavior());
}
}
/* your DTO customizations */
public class DtoCustomization : ICustomization
{
public void Customize(IFixture fixture)
{
fixture.Customize<CreateUserDto>(c => c
.With(u => u.Email, fixture.Create<MailAddress>().Address)
.With(u => u.PhoneNumber, fixture.Create(
new RegularExpressionRequest("[1-9]{11}"),
new SpecimenContext(fixture))));
}
}
/* The data attribute customization */
public class DomainDataAttribute : AutoDataAttribute
{
public DomainDataAttribute()
: base(() => new Fixture().Customize(
new CompositeCustomization(
new OmitRecursionCustomization(),
new DtoCustomization())))
{
}
}
/* The test */
[Theory, DomainData]
public void Foo(List<CreateUserDto> users)
{
Assert.Equal(3, users.Count);
Assert.All(users, user => Assert.Matches(#"\#example\.(com|org|net)", user.Email));
Assert.All(users, user => Assert.Matches("[1-9]{11}", user.PhoneNumber));
}

How to configure an owned entity in order to have one to many relationship with an abstract class ef core .net 6

EF Core version: 6.0.6
I have a class User which owns many class Post. The class posts own many class Comment and now I would like to handle a relationship between comments and reactions(reaction is an abstract class which can be like, star etc). As I read in the official doc I cannot implement reactions as owned because it has an inheritance hierarchy.
public class User
{
private readonly List<Post> _posts;
public long Id{ get; }
public string Username{ get; }
public DateTime CreatedAt { get; }
public IReadOnlyCollection<Post> Posts => _posts.AsReadOnly();
private User() : base(default, default)
{
CreatedAt = DateTime.UtcNow;
UpdatedAt = DateTime.UtcNow;
_posts= new List<Post>();
}
//bla bla public constructor etc
}
public class Post
{
private readonly List<Comment> _comments;
public long PostId { get; }
public DateTime CreatedAt { get; }
public IReadOnlyCollection<Comment> Comments => _comments.AsReadOnly();
private Post()
{
CreatedAt = DateTime.UtcNow;
_comments = new List<Comment>();
}
//bla bla public constructor etc
}
public class Comment
{
private readonly List<Reaction> _reactions;
public long CommentId { get; }
public string Text { get; }
public DateTime CreatedAt { get; }
public IReadOnlyCollection<Reaction> Reactions => _reactions.AsReadOnly();
private Comment()
{
CreatedAt = DateTime.UtcNow;
_reactions = new List<Reaction>();
}
//bla bla public ctor
}
and finally the class Reaction
public abstract partial class Reaction
{
public long Id { get; }
protected Reaction() { }
protected Reaction(long id)
{
Id = id;
}
}
which has two child classes
the class Star
public abstract partial class Reaction
{
public class Star : Reaction
{
private readonly List<Caption> _captions;
public decimal Value { get; private set; }
public IReadOnlyCollection<Caption> Captions => _captions.AsReadOnly();
public Star(long id, decimal value, IReadOnlyCollection<Caption> captions) : base(id)
{
Value = value;
_captions = captions.ToList();
}
}
}
and the class Like
public abstract partial class Reaction
{
public class Like : Reaction
{
private readonly List<Caption> _captions;
public IReadOnlyCollection<Caption> Captions => _captions.AsReadOnly();
public Like(long id, Caption[] captions) : base(id)
{
_captions = captions.ToList();
}
}
}
Finally the class
public class Caption
{
public int Id { get; }
public string Text { get; }
public Caption(int id, string text)
{
Id = id;
Text = text;
}
}
I have stacked how to configure the database schema.
Comments cannot own many reactions due to an owned entity cannot have an inheritance hierarchy.
I tried to implement a table-per-type solution for reactions which is abstract class.
Here is the configuration
public void Configure(EntityTypeBuilder<User> builder)
{
builder.ToTable("Users");
builder.HasKey(x => x.Id);
builder.Property(x => x.Id).ValueGeneratedNever().IsRequired();
builder.Property(x => x.Username).IsRequired();
builder.Property(x => x.CreatedAt).IsRequired();
builder.OwnsMany(x => x.Posts, post =>
{
post.ToTable("Posts");
post.HasKey(PostsId);
post.Property(x => x.PostsId).ValueGeneratedNever().IsRequired();
post.Property(x => x.CreatedAt).IsRequired();
post.OwnsMany(x => x.Comments, comment =>
{
comment.ToTable("Comments");
comment.HasKey(CommentId);
comment.Property(x => x.CommentId).ValueGeneratedNever().IsRequired();
comment.Property(x => x.Text).HasMaxLength(100).IsRequired();
comment.Property(x => x.CreatedAt).IsRequired();
//how to continue the configuration of reactions and captions???
});
});
}
}
I don't know how to configure a relationship between comments and reactions.A comment could have many reactions(one to many) and a reaction could have many captions(one to many)

How to create a reusable mapping profile with Mapster?

I have a .Net 5 Web Api project and want to use
Mapster v7.2.0
to avoid mapping objects manually. The following code shows a sample scenario
setup a mapping configuration
map from multiple sources
map to fields with different names
.
[ApiController]
[Route("[controller]")]
public class MyController : ControllerBase
{
[HttpGet]
public ActionResult<UsernameWithTodoTitle> Get()
{
TypeAdapterConfig<(User, Todo), UsernameWithTodoTitle>
.NewConfig()
.Map(dest => dest, src => src.Item1) // map everything from user
.Map(dest => dest, src => src.Item2) // map everything from todo
.Map(dest => dest.TodoTitle, src => src.Item2.Title); // map the special fields from todo
var user = new User { Username = "foo", FieldFromUser = "x" };
var todo = new Todo { Title = "bar", FieldFromTodo = "y" };
var usernameWithTodoTitle = (user, todo).Adapt<(User, Todo), UsernameWithTodoTitle>();
return Ok(usernameWithTodoTitle);
}
}
public class User
{
public string Username { get; set; }
public string FieldFromUser { get; set; }
}
public class Todo
{
public string Title { get; set; } // !! map this one to the TodoTitle field !!
public string FieldFromTodo { get; set; }
}
public class UsernameWithTodoTitle
{
public string Username { get; set; }
public string TodoTitle { get; set; } // !! this one is special, is has a different name !!
public string FieldFromUser { get; set; }
public string FieldFromTodo { get; set; }
}
When running the app the mapping seems to work fine this way
I had to setup the configuration this way, other ways didn't work for me. But there are 3 things left to be solved
The configuration looks wrong to me. It maps everything from the todo and maps the special field again ... so it might loop through multiple times? This might get expensive, if there are multiple fields with different names
I created the configuration inside the controller. How can I create a reusable mapping profile class registered once globally?
When having a mapping profile this line var usernameWithTodoTitle = (user, todo).Adapt<(User, Todo), UsernameWithTodoTitle>(); looks quite messy to me. Better would be var usernameWithTodoTitle = UsernameWithTodoTitle.Adapt((user, todo)) /* pass in as a tuple */ because based on the parameter type it chooses the correct mapping profile
Do you guys have any ideas how to create such a mapping profile?
Updated: Couldn't find way to do what you are trying to do with Mapster, but here is an example of it working with Automapper.
using AutoMapper;
using System;
namespace ConsoleApp5
{
class A { public string FirstName { get; set; } }
public class B { public string Address1 { get; set; } }
public class C
{
public string FirstName { get; set; }
public string Address1 { get; set; }
}
public class DemoProfile : Profile
{
public DemoProfile()
{
CreateMap<(A, B), C>()
.ForMember(dest=> dest.FirstName, opts => opts.MapFrom(src => src.Item1.FirstName))
.ForMember(dest => dest.Address1, opts => opts.MapFrom(src => src.Item2.Address1));
}
}
class Program
{
static void Main(string[] args)
{
var config = new MapperConfiguration(cfg => {
cfg.AddProfile<DemoProfile>();
});
var mapper = config.CreateMapper();
var destination = mapper.Map<C>((new A { FirstName = "Test" }, new B { Address1 = "Addr" }));
Console.ReadKey();
}
}
}
Hey I haven't used Mapster before till now but here is what I gather. It is very specific about the type of tuple you use Tuple<T1,T2> over (T1,T2) but aside from that minor thing I was able to get it running and mapping without issues. Here is a small console example as example.
using Mapster;
using System;
namespace ConsoleApp5
{
class A { public string FirstName { get; set; } }
public class B { public string Address1 { get; set; } }
public class C
{
public string FirstName { get; set; }
public string Address1 { get; set; }
}
class Program
{
static void Main(string[] args)
{
// Option 1
TypeAdapterConfig<Tuple<A, B>, C>.NewConfig()
.Map(dest => dest.FirstName, src => src.Item1.FirstName)
.Map(dest => dest.Address1, src => src.Item2.Address1);
var destObject = new Tuple<A, B>(new A { FirstName = "Test" }, new B { Address1 = "Address 1" })
.Adapt<Tuple<A, B>, C>();
// Option 2
TypeAdapterConfig<(A, B), C>.NewConfig()
.Map(dest => dest.FirstName, src => src.Item1.FirstName)
.Map(dest => dest.Address1, src => src.Item2.Address1);
var destObject2 = (new A { FirstName = "Test" }, new B { Address1 = "Address 1" })
.Adapt<(A, B), C>();
Console.ReadKey();
}
}
}
I managed to do it with Mapster. What I did was
in Startup.cs
public void ConfigureServices(IServiceCollection services)
{
// Some other magical code
// Tell Mapster to scan this assambly searching for the Mapster.IRegister
// classes and execute them
TypeAdapterConfig.GlobalSettings.Scan(Assembly.GetExecutingAssembly());
}
Create another class like this
using Mapster;
namespace Your.Cool.Namespace
{
public class MappingConfig : IRegister
{
public void Register(TypeAdapterConfig config)
{
// Put your mapping logic here
config
.NewConfig<MySourceType, MyDestinyType>()
.Map(dest => dest.PropA, src => src.PropB);
}
}
}
The key part is using TypeAdapterConfig.GlobalSettings, which is a static public singleton used by Mapster to hold the mappig config. If you do what Jack suggests, it will be a complety new TypeAdapterConfig and not the actual one being used by Mapster and won't work (at least it didn't for me).
On your unit tests remember to load the mapping profile too
[AssemblyInitialize] // Magic part 1 ~(˘▾˘~)
public static void AssemblyInitialization(TestContext testContext)
{
// Magic part 2 (~˘▾˘)~
TypeAdapterConfig.GlobalSettings.Scan(AppDomain.CurrentDomain.GetAssemblies());
}
You can use next:
var config = new TypeAdapterConfig()
{
RequireExplicitMapping = true,
RequireDestinationMemberSource = true,
Compiler = exp => exp.CompileFast()
};
config.Scan("Your assembly");
services.AddSingleton(config);
services.AddTransient<IMapper, ServiceMapper>();
public class RegisterConfig : IRegister
{
public void Register(TypeAdapterConfig config)
{
config.NewConfig<TSource, TDestination>();
}
}
Where services is IServiceCollection
Based on #Felipe Ramos answer I wasn't able to solve it with Mapster but with Automapper. This is my solution just for the sake of completeness. Please let me know if there is a solution for Mapster!
I installed the packages
AutoMapper v10.1.1
AutoMapper.Extensions.Microsoft.DependencyInjection v8.1.1
Inside the method Startup.ConfigureServices I added the line services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
The whole code then looks like
[ApiController]
[Route("[controller]")]
public class MyController : ControllerBase
{
private readonly IMapper _mapper;
public MyController(IMapper mapper)
{
_mapper = mapper;
}
[HttpGet]
public ActionResult<UsernameWithTodoTitle> Get()
{
var user = new User { Username = "foo", FieldFromUser = "x" };
var todo = new Todo { Title = "bar", FieldFromTodo = "y" };
var usernameWithTodoTitle = _mapper.Map<UsernameWithTodoTitle>((user, todo));
return Ok(usernameWithTodoTitle);
}
}
public class User
{
public string Username { get; set; }
public string FieldFromUser { get; set; }
}
public class Todo
{
public string Title { get; set; } // !! map this one to the TodoTitle field !!
public string FieldFromTodo { get; set; }
}
public class UsernameWithTodoTitle
{
public string Username { get; set; }
public string TodoTitle { get; set; } // !! this one is special, is has a different name !!
public string FieldFromUser { get; set; }
public string FieldFromTodo { get; set; }
}
public class UsernameWithTodoTitleMappingProfile : Profile
{
public UsernameWithTodoTitleMappingProfile()
{
CreateMap<(User, Todo), UsernameWithTodoTitle>()
.ForMember(
destination => destination.Username,
memberOptions => memberOptions.MapFrom(source => source.Item1.Username))
.ForMember(
destination => destination.TodoTitle,
memberOptions => memberOptions.MapFrom(source => source.Item2.Title))
.ForMember(
destination => destination.FieldFromUser,
memberOptions => memberOptions.MapFrom(source => source.Item1.FieldFromUser))
.ForMember(
destination => destination.FieldFromTodo,
memberOptions => memberOptions.MapFrom(source => source.Item2.FieldFromTodo));
}
}

Applying entity configurations in a foreach loop

I was wondering if someone might have an idea how to apply entity configuration dynamically. I don't want to repeat let's say builder.ApplyConfiguration(new PersonConfiguration()); for each entity I have/might have in the future.
I was trying with the following code and for the sake of simplicity I have placed everything into a single file:
namespace WebApplication1.Data
{
public class ApplicationDbContext : IdentityDbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
var entityTypes = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(p => p.GetTypes())
.Where(p => typeof(IEntity).IsAssignableFrom(p) && p != typeof(IEntity));
foreach (var entityType in entityTypes)
{
// this doesn't work
//builder.ApplyConfiguration(new BaseEntityConfiguration<entityType>());
}
// this works, but I don't want to basically repeat it for each entity individually
//builder.ApplyConfiguration(new BaseEntityConfiguration<Person>());
//builder.ApplyConfiguration(new PersonConfiguration());
}
public virtual DbSet<Person> People { get; set; }
}
public interface IEntity
{
int Id { get; set; }
string RowModifyUser { get; set; }
DateTime RowModifyDate { get; set; }
byte[] RowVersion { get; set; }
}
public class Person : IEntity
{
public int Id { get; set; }
public string RowModifyUser { get; set; }
public DateTime RowModifyDate { get; set; }
public byte[] RowVersion { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class BaseEntityConfiguration<TEntity> : IEntityTypeConfiguration<TEntity> where TEntity : class, IEntity
{
public void Configure(EntityTypeBuilder<TEntity> builder)
{
builder.HasKey(m => m.Id);
builder.Property(m => m.RowModifyDate).IsRequired();
builder.Property(m => m.RowModifyUser).IsRequired();
builder.Property(m => m.RowVersion).IsRequired().IsConcurrencyToken();
}
}
public class PersonConfiguration : IEntityTypeConfiguration<Person>
{
public void Configure(EntityTypeBuilder<Person> builder)
{
builder.Property(m => m.FirstName).IsRequired();
builder.Property(m => m.LastName).IsRequired();
}
}
}
In addition to creating a generic BaseEntityConfiguration<TEntity> class as suggested in another answer, you also need to call a generic ModelBuilder.ApplyConfiguration<TEntity>(IEntityTypeConfiguration<TEntity> configuration) method via reflection.
Something like this (needs using System.Reflection;):
// Can be moved to a static readonly field of the class
var applyConfigurationMethodDefinition = typeof(ModelBuilder)
.GetTypeInfo()
.DeclaredMethods
.Single(m => m.Name == "ApplyConfiguration" &&
m.IsGenericMethodDefinition &&
m.GetParameters().Length == 1 &&
m.GetParameters()[0].ParameterType.IsGenericType &&
m.GetParameters()[0].ParameterType.GetGenericTypeDefinition() == typeof(IEntityTypeConfiguration<>));
foreach (var entityType in entityTypes)
{
var configurationType = typeof(BaseEntityConfiguration<>).MakeGenericType(entityType);
var configuration = Activator.CreateIntance(configurationType);
var applyConfigurationMethod = applyConfigurationMethodDefinition.MakeGenericMethod(entityType);
applyConfigurationMethod.Invoke(builder, new object[] { configuration });
}
Note that in EF Core 2.1 ModelBuilder class has 2 ApplyConfiguration method overloads which differ only by the type of the parameter, that's why finding the method includes all the checks.
Untested, but you could try the following:
foreach (Type entityType in entityTypes)
{
Type openConfigType = typeof(BaseEntityConfiguration<>);
Type genericConfigType = openConfigType.MakeGenericType(entityType);
builder.ApplyConfiguration(Activator.CreateInstance(genericConfigType));
}

How to exclude all virtual properties from classes inheritted from ClassMapper<T> by default?

I have a lot of POCO classes that contain several virtual properties each. Something like this:
public class Policy
{
public int Id { get; set; }
public int EntityId { get; set; }
public int ProgramId { get; set; }
public string PolicyNumber { get; set; }
public DateTime EffectiveDate { get; set; }
public DateTime ExpirationDate { get; set; }
public virtual Entity Entity{ get; set; }
public virtual Program Program { get; set; }
public virtual ICollection<Transaction> Transactions { get; set; }
}
To make Dapper.Extensions work, I need to write a mapping for each of these classes, which is fine. My problem is, if there are any virtual properties inside a class, they need to be explicitly marked as ignored, which I always forget to do.
public sealed class PolicyMapper : BaseMapper<Policy>
{
public PolicyMapper()
{
Map(p => p.Entity).Ignore();
Map(p => p.Program).Ignore();
Map(p => p.Transactions).Ignore();
AutoMap();
}
}
What would be great for me, if the Dapper.Extensions library will automatically exclude virtual properties, if any, when mapped to the POCO class. There is an extension for Automapper that does something similar (link). Is there a way to do that for Dapper.Extensions library? Possibly something like this:
public sealed class PolicyMapper : BaseMapper<Policy>
{
public PolicyMapper()
{
IgnoreAllVirtual();
AutoMap();
}
}
I found my own solution. Since all my mapping classes derive from BaseMapper class, I decided to override AutoMap() method that will exclude virtual properties:
public class BaseMapper<T> : ClassMapper<T> where T : BaseClass
{
public BaseMapper()
{
}
protected override void AutoMap()
{
CustomAutoMap(null);
}
private void CustomAutoMap(Func<Type, PropertyInfo, bool> canMap)
{
Type type = typeof(T);
bool hasDefinedKey = Properties.Any(p => p.KeyType != KeyType.NotAKey);
PropertyMap keyMap = null;
foreach (var propertyInfo in type.GetProperties())
{
// Exclude virtual properties
if (propertyInfo.GetGetMethod().IsVirtual)
{
continue;
}
if (Properties.Any(p => p.Name.Equals(propertyInfo.Name, StringComparison.InvariantCultureIgnoreCase)))
{
continue;
}
if ((canMap != null && !canMap(type, propertyInfo)))
{
continue;
}
PropertyMap map = Map(propertyInfo);
if (!hasDefinedKey)
{
if (string.Equals(map.PropertyInfo.Name, "id", StringComparison.InvariantCultureIgnoreCase))
{
keyMap = map;
}
if (keyMap == null && map.PropertyInfo.Name.EndsWith("id", true, CultureInfo.InvariantCulture))
{
keyMap = map;
}
}
}
if (keyMap != null)
{
keyMap.Key(PropertyTypeKeyTypeMapping.ContainsKey(keyMap.PropertyInfo.PropertyType)
? PropertyTypeKeyTypeMapping[keyMap.PropertyInfo.PropertyType]
: KeyType.Assigned);
}
}
}
}

Categories

Resources