AutoMapper map IdPost to Post - nhibernate

I'm trying to map int IdPost on DTO to Post object on Blog object, based on a rule.
I would like to achieve this: BlogDTO.IdPost => Blog.Post
Post would be loaded by NHibernate: Session.Load(IdPost)
How can I achieve this with AutoMapper?

You could define AfterMap action to load entities using NHibernate in your mapping definition. I'm using something like this for simmilar purpose:
mapperConfiguration.CreateMap<DealerDTO, Model.Entities.Dealer.Dealer>()
.AfterMap((src, dst) =>
{
if (src.DepartmentId > 0)
dst.Department = nhContext.CurrentSession.Load<CompanyDepartment>(src.DepartmentId);
if (src.RankId > 0)
dst.Rank = nhContext.CurrentSession.Load<DealerRank>(src.RankId);
if (src.RegionId > 0)
dst.Region = nhContext.CurrentSession.Load<Region>(src.RegionId);
});

you can do this easily with the ValueInjecter
it would be something like this:
//first you need to create a ValueInjection for your scenario
public class IntToPost : LoopValueInjection<int, Post>
{
protected override Post SetValue(int sourcePropertyValue)
{
return Session.Load(sourcePropertyValue);
}
}
// and use it like this
post.InjectFrom(new IntToPost().SourcePrefix("Id"), postDto);
also if you always have the prefix Id than you could set it in the constructor of the IntToPost
and use it like this:
post.InjectFrom<IntToPost>(postDto);

Create a new Id2EntityConverter
public class Id2EntityConverter<TEntity> : ITypeConverter<int, TEntity> where TEntity : EntityBase
{
public Id2EntityConverter()
{
Repository = ObjectFactory.GetInstance<Repository<TEntity>>();
}
private IRepository<TEntity> Repository { get; set; }
public TEntity ConvertToEntity(int id)
{
var toReturn = Repository.Get(id);
return toReturn;
}
#region Implementation of ITypeConverter<int,TEntity>
public TEntity Convert(ResolutionContext context)
{
return ConvertToEntity((int)context.SourceValue);
}
#endregion
}
Configure AM to auto create maps for each type
public class AutoMapperGlobalConfiguration : IGlobalConfiguration
{
private AutoMapper.IConfiguration _configuration;
public AutoMapperGlobalConfiguration(IConfiguration configuration)
{
_configuration = configuration;
}
public void Configure()
{
//add all defined profiles
var query = this.GetType().Assembly.GetExportedTypes()
.Where(x => x.CanBeCastTo(typeof(AutoMapper.Profile)));
_configuration.RecognizePostfixes("Id");
foreach (Type type in query)
{
_configuration.AddProfile(ObjectFactory.GetInstance(type).As<Profile>());
}
//create maps for all Id2Entity converters
MapAllEntities(_configuration);
Mapper.AssertConfigurationIsValid();
}
private static void MapAllEntities(IProfileExpression configuration)
{
//get all types from the my assembly and create maps that
//convert int -> instance of the type using Id2EntityConverter
var openType = typeof(Id2EntityConverter<>);
var idType = typeof(int);
var persistentEntties = typeof(MYTYPE_FROM_MY_ASSEMBLY).Assembly.GetTypes()
.Where(t => typeof(EntityBase).IsAssignableFrom(t))
.Select(t => new
{
EntityType = t,
ConverterType = openType.MakeGenericType(t)
});
foreach (var e in persistentEntties)
{
var map = configuration.CreateMap(idType, e.EntityType);
map.ConvertUsing(e.ConverterType);
}
}
}
Pay attention to MapAllEntities method. That one will scan all types and create maps on the fly from integer to any type that is of EntityBase (which in our case is any persistent type).
RecognizePostfix("Id") in your case might be replace with RecognizePrefix("Id")

Related

How can I use SumAsync to calculate sum of a customized value object in Asp.Net core

I have a Model with a property of a value object type as following:
public class Course : AggregateRoot, ISpModel
{
...
public UnsignedNumber MaximumCapacity { get; private set; }
...
}
with UnsignedNumber being a value object containing a short value:
public class UnsignedNumber : BaseValueObject<UnsignedNumber>
{
public short Value { get; }
...
}
What I need to do is to sum all the MaximumCapacities of courses which correspond with certain conditions, but when I try to add a SumAsync(x => x.MaximumCapacity) at the end of the query, I get a syntax error
the syntax error
and when I try to do the same with it's value, I get a linq error in runtime.
"The LINQ expression '(int)(EntityShaperExpression: \r\n EntityType: Course\r\n ValueBufferExpression: \r\n (ProjectionBindingExpression: Outer)\r\n IsNullable: False\r\n).MaximumCapacity.Value' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync(). See https://go.microsoft.com/fwlink/?linkid=2101038 for more information."
Edit:
Here's the Linq Expression that encounters the problem:
var query = _dbContext.Courses.AsQueryable();
query = query.Include(x => BunchOfIncludes(x));
var res = await query.Where(x => BunchOfClauses(x)).SumAsync(x => x.MaximumCapacity.Value);
Edit2: the classes mentioned above:
public abstract class AggregateRoot : Entity
{
private readonly List<IDomainEvent> _events;
protected AggregateRoot() => _events = new List<IDomainEvent>();
public AggregateRoot(IEnumerable<IDomainEvent> events)
{
if (events == null) return;
foreach (var #event in events)
((dynamic)this).On((dynamic)#event);
}
protected void AddEvent(IDomainEvent #event) => _events.Add(#event);
public IEnumerable<IDomainEvent> GetEvents() => _events.AsEnumerable();
public void ClearEvents() => _events.Clear();
}
public interface ISpModel
{
}
public abstract class BaseValueObject<TValueObject> : IEquatable<TValueObject>
where TValueObject : BaseValueObject<TValueObject>
{
...
public static bool operator ==(BaseValueObject<TValueObject> right, BaseValueObject<TValueObject> left)
{
if (right is null && left is null)
return true;
if (right is null || left is null)
return false;
return right.Equals(left);
}
...
}

ASP.NET Core WebAPI XML Method argument deserialization

I'm trying to make a WebAPI controller on .NET Core 3.1 witch supports both JSON and XML as request/response content-type.
Controller works perfectly when it receive JSON with "application/json", but when it receive XML with "application/xml", method argument are created with default values, not values that was posted in request body.
Example project - https://github.com/rincew1nd/ASPNetCore_XMLMethods
Additional XML serializer in startup:
services.AddControllers().AddXmlSerializerFormatters();
Controller with method and test model:
[ApiController]
[Route("[controller]")]
public class TestController : ControllerBase
{
[HttpPost, Route("v1")]
[Consumes("application/json", "application/xml")]
[Produces("application/json", "application/xml")]
public TestRequest Test([FromBody] TestRequest data)
{
return data;
}
}
[DataContract]
public class TestRequest
{
[DataMember]
public Guid TestGuid { get; set; }
[DataMember]
public string TestString { get; set; }
}
P.S. Project contains Swagger for API testing purposes.
Your xml post request body uses camel cases which results in the model binding as null.
Add using Swashbuckle.AspNetCore.SwaggerGen; in starup.cs and try to configure like below code:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers().AddXmlSerializerFormatters();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "Neocase <-> 1C Integration", Version = "v1" });
c.SchemaFilter<XmlSchemaFilter>();
});
}
public class XmlSchemaFilter : Swashbuckle.AspNetCore.SwaggerGen.ISchemaFilter
{
public void Apply(OpenApiSchema model, SchemaFilterContext context)
{
if (model.Properties == null) return;
foreach (var entry in model.Properties)
{
var name = entry.Key;
entry.Value.Xml = new OpenApiXml
{
Name = name.Substring(0, 1).ToUpper() + name.Substring(1)
};
}
}
}
Don't use FromBody attribute for application/xml.
When a parameter has [FromBody], Web API uses the Content-Type header to select a formatter. In this example, the content type is "application/json" and the request body is a raw JSON string (not a JSON object).
Using [FromBody]
After some more research i found that swagger generates wrong xml examples without even noticing custom naming of classes or properties.
I wrote custom schema for naming xml attributes as they are named by XML attributes.
Only problem i faced is that SchemaFilterContext doesn't provide description of properties of Enum type. So to name Enums i use custom attribute for swagger name and XMLElementAttribute on property with same names (yeah, it's junky but works).
public class XmlSchemaFilter : ISchemaFilter
{
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
//Try to find XmlRootAttribute on class
var xmlroot = context.Type.GetAttributeValue((XmlRootAttribute xra) => xra);
if (xmlroot != null)
{
schema.Xml = new OpenApiXml
{
Name = xmlroot.ElementName
};
}
//Try to find XmlElementAttribute on property
if (context.MemberInfo != null)
{
var xmlelement = context.MemberInfo.GetAttributeValue((XmlElementAttribute xea) => xea);
if (xmlelement != null)
{
schema.Xml = new OpenApiXml
{
Name = xmlelement.ElementName
};
}
}
//Try to find XmlEnumNameAttribute on enums
if (context.Type.IsEnum)
{
var enumname = context.Type.GetAttributeValue((XmlEnumNameAttribute xea) => xea);
if (enumname != null)
{
schema.Xml = new OpenApiXml
{
Name = enumname.ElementName
};
}
}
}
}
public static class AttributeHelper
{
public static TValue GetAttributeValue<TAttribute, TValue>(
this Type type,
Func<TAttribute, TValue> valueSelector)
where TAttribute : Attribute
{
var att = type.GetCustomAttributes(
typeof(TAttribute), true
).FirstOrDefault() as TAttribute;
if (att != null)
{
return valueSelector(att);
}
return default(TValue);
}
public static TValue GetAttributeValue<TAttribute, TValue>(
this MemberInfo mi,
Func<TAttribute, TValue> valueSelector)
where TAttribute : Attribute
{
var att = mi.GetCustomAttributes(
typeof(TAttribute), true
).FirstOrDefault() as TAttribute;
if (att != null)
{
return valueSelector(att);
}
return default(TValue);
}
}

Swagger listing IFormFile parameter as type "object"

I have a controller that requests a model containing an IFormFile as one of it's properties. For the request description, the Swagger UI (I'm using Swashbuckle and OpenApi 3.0 for .NET Core) lists the type of the file property as type object. Is there some way to make the Swagger UI denote the exact type and it's JSON representation to help the client?
The controller requesting the model looks as follows.
[HttpPost]
[Consumes("multipart/form-data")
public async Task<IActionResult> CreateSomethingAndUploadFile ([FromForm]RequestModel model)
{
// do something
}
And the model is defined as below:
public class AssetCreationModel
{
[Required}
public string Filename { get; set; }
[Required]
public IFormFile File { get; set; }
}
We've been exploring this issue today. If you add the following to your startup it will convert IFormFile to the correct type
services.AddSwaggerGen(c => {
c.SchemaRegistryOptions.CustomTypeMappings.Add(typeof(IFormFile), () => new Schema() { Type = "file", Format = "binary"});
});
Also see the following article on file upload in .net core
https://learn.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads?view=aspnetcore-2.1
This problem was already tackled in the following github issue/thread.
This improvement was already merged into Swashbuckle.AspNetCore master (as per 10/30/2018), but i don't expect that to be available as a package soon.
There are simple solutions if you only have a IFormFile as a parameter.
public async Task UploadFile(IFormFile filePayload){}
For simple case you can take a look at the following answer.
For complicated cases like container cases, you can take a look at the following answer.
internal class FormFileOperationFilter : IOperationFilter
{
private struct ContainerParameterData
{
public readonly ParameterDescriptor Parameter;
public readonly PropertyInfo Property;
public string FullName => $"{Parameter.Name}.{Property.Name}";
public string Name => Property.Name;
public ContainerParameterData(ParameterDescriptor parameter, PropertyInfo property)
{
Parameter = parameter;
Property = property;
}
}
private static readonly ImmutableArray<string> iFormFilePropertyNames =
typeof(IFormFile).GetTypeInfo().DeclaredProperties.Select(p => p.Name).ToImmutableArray();
public void Apply(Operation operation, OperationFilterContext context)
{
var parameters = operation.Parameters;
if (parameters == null)
return;
var #params = context.ApiDescription.ActionDescriptor.Parameters;
if (parameters.Count == #params.Count)
return;
var formFileParams =
(from parameter in #params
where parameter.ParameterType.IsAssignableFrom(typeof(IFormFile))
select parameter).ToArray();
var iFormFileType = typeof(IFormFile).GetTypeInfo();
var containerParams =
#params.Select(p => new KeyValuePair<ParameterDescriptor, PropertyInfo[]>(
p, p.ParameterType.GetProperties()))
.Where(pp => pp.Value.Any(p => iFormFileType.IsAssignableFrom(p.PropertyType)))
.SelectMany(p => p.Value.Select(pp => new ContainerParameterData(p.Key, pp)))
.ToImmutableArray();
if (!(formFileParams.Any() || containerParams.Any()))
return;
var consumes = operation.Consumes;
consumes.Clear();
consumes.Add("application/form-data");
if (!containerParams.Any())
{
var nonIFormFileProperties =
parameters.Where(p =>
!(iFormFilePropertyNames.Contains(p.Name)
&& string.Compare(p.In, "formData", StringComparison.OrdinalIgnoreCase) == 0))
.ToImmutableArray();
parameters.Clear();
foreach (var parameter in nonIFormFileProperties) parameters.Add(parameter);
foreach (var parameter in formFileParams)
{
parameters.Add(new NonBodyParameter
{
Name = parameter.Name,
//Required = , // TODO: find a way to determine
Type = "file"
});
}
}
else
{
var paramsToRemove = new List<IParameter>();
foreach (var parameter in containerParams)
{
var parameterFilter = parameter.Property.Name + ".";
paramsToRemove.AddRange(from p in parameters
where p.Name.StartsWith(parameterFilter)
select p);
}
paramsToRemove.ForEach(x => parameters.Remove(x));
foreach (var parameter in containerParams)
{
if (iFormFileType.IsAssignableFrom(parameter.Property.PropertyType))
{
var originalParameter = parameters.FirstOrDefault(param => param.Name == parameter.Name);
parameters.Remove(originalParameter);
parameters.Add(new NonBodyParameter
{
Name = parameter.Name,
Required = originalParameter.Required,
Type = "file",
In = "formData"
});
}
}
}
}
}
You need to look into how you can add some/an OperationFilter that is suitable for your case.

How to change the collection name on an index

When I save a document that has a generic type DataView<Customer>, I'm manually setting the collection name to "customers". However, I'm having some trouble making an index using AbstractIndexCreationTask with a non-default collection name. Here's my index:
public class customers_Search
: AbstractIndexCreationTask<DataView<Customer>, customers_Search.Result>
{
public class Result
{
public string Query { get; set; }
}
public customers_Search()
{
Map = customers =>
from customer in customers
where customer.Data != null
select new
{
Query = AsDocument(customer.Data).Select(x => x.Value)
};
Index(x => x.Query, FieldIndexing.Analyzed);
}
}
When this gets deployed, it looks like this:
from customer in docs.DataViewOfCustomer
where customer.Data != null
select new {
Query = customer.Data.Select(x => x.Value)
}
This doesn't work obviously, and if I change DataViewOfCustomer to "customers" it works just fine.
I'd rather not have to use non-type-checked (string) indexes to deploy. Is there a way to set the collection name that from the AbstractIndexCreationTask class?
Update
Since my data class is generic, I made a generic index which fixes up the names.
public class DataViewQuery<TEntity>
: AbstractIndexCreationTask<DataView<TEntity>, DataViewQueryResult>
{
private readonly string _entityName;
private readonly string _indexName;
// this is to fix the collection name for the index name
public override string IndexName { get { return _indexName; } }
// this is to fix the collection name for the index query
public override void Execute(IDatabaseCommands databaseCommands, DocumentConvention documentConvention)
{
var conventions = documentConvention.Clone();
conventions.FindTypeTagName =
type =>
typeof(DataView<TEntity>) == type
? _entityName
: documentConvention.FindTypeTagName(type);
base.Execute(databaseCommands, conventions);
}
public DataViewQuery(string entityName)
{
_entityName = entityName;
_indexName = String.Format("{0}/{1}", entityName, "Query");
Map = items =>
from item in items
where item.Data != null
select new
{
Query = AsDocument(item.Data).Select(x => x.Value)
};
Index(x => x.Query, FieldIndexing.Analyzed);
}
}
public class DataViewQueryResult
{
public string Query { get; set; }
}
Then I can create a specific index which has all the configuration in it.
// sets the collection type (DataView<Customer>) for the index
public class CustomerQuery : DataViewQuery<Customer>
{
// sets the collection name for the index
public CustomerQuery() : base(EntityName.Customers) { }
}
You need to configure this in the conventions.
The property to configure is FindTypeTagName

How to automap a collection of components with Fluent NHibernate?

All of my entities and value objects implement marker interfaces IEntity and IValueObject. I have set them up to be treated as components like so:
public override bool IsComponent(Type type)
{
return typeof(IValueObject).IsAssignableFrom(type);
}
public override bool ShouldMap(Type type)
{
return typeof(IEntity).IsAssignableFrom(type) || typeof(IValueObject).IsAssignableFrom(type);
}
Unfortunately, this does not seem to allow entities that have collections of value objects to be automapped as component collections. For example:
public class MyEntity : IEntity
{
public IList<MyValueObject> Objects { get; set; }
}
public class MyValueObject : IValueObject
{
public string Name { get; set; }
public string Value { get; set; }
}
Is there any way to define a convention such that, any time an IEntity has an IList of a type that implements IValueObject, it gets mapped as if I had specified:
HasMany(x => x.Objects)
.Component(x => {
x.Map(m => m.Name);
x.Map(m => m.Value);
});
What I don't want to do is have to manually do these overrides for every class and write out each property for the value object again and again.
Create a new class that inherits from HasManyStep (FluentNHibernate.Automapping.Steps).
Override the ShouldMap() method with something like :
return base.ShouldMap(member) && IsCollectionOfComponents(member)
Add your logic to :
public void Map(ClassMappingBase classMap, Member member)
{ ... }
Replace the default step with your new one :
public class MyMappingConfiguration : DefaultAutomappingConfiguration
{
public override IEnumerable<IAutomappingStep> GetMappingSteps(AutoMapper mapper, IConventionFinder conventionFinder)
{
var steps = base.GetMappingSteps(mapper, conventionFinder);
var finalSteps = steps.Where(c => c.GetType() != typeof(FluentNHibernate.Automapping.Steps.HasManyToManyStep)).ToList();
var idx = finalSteps.IndexOf(steps.Where(c => c.GetType() == typeof(PropertyStep)).First());
finalSteps.Insert(idx + 1, new MyCustomHasManyStep(this));
return finalSteps;
}
}
Note : You could also get the original source code of HasManyStep.cs and copy it to your project to introduce your custom logic.