Replaced BuddyType attribute with ModelMetadatType and SimpleMapper with Automapper
This commit is contained in:
parent
89f2e3e32a
commit
2e84d68973
|
|
@ -2,6 +2,7 @@
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Authorization;
|
||||
using Microsoft.AspNet.Mvc;
|
||||
using AutoMapper;
|
||||
using MusicStore.Infrastructure;
|
||||
using MusicStore.Models;
|
||||
using MusicStore.Spa.Infrastructure;
|
||||
|
|
@ -26,12 +27,12 @@ namespace MusicStore.Apis
|
|||
await _storeContext.Artists.LoadAsync();
|
||||
|
||||
var albums = await _storeContext.Albums
|
||||
//.Include(a => a.Genre)
|
||||
//.Include(a => a.Artist)
|
||||
.ToPagedListAsync(page, pageSize, sortBy,
|
||||
// .Include(a => a.Genre)
|
||||
// .Include(a => a.Artist)
|
||||
.ToPagedListAsync(page, pageSize, sortBy,
|
||||
a => a.Title, // sortExpression
|
||||
SortDirection.Ascending, // defaultSortDirection
|
||||
a => SimpleMapper.Map(a, new AlbumResultDto())); // selector
|
||||
a => Mapper.Map(a, new AlbumResultDto())); // selector
|
||||
|
||||
return Json(albums);
|
||||
}
|
||||
|
|
@ -46,7 +47,7 @@ namespace MusicStore.Apis
|
|||
.OrderBy(a => a.Title)
|
||||
.ToListAsync();
|
||||
|
||||
return Json(albums.Select(a => SimpleMapper.Map(a, new AlbumResultDto())));
|
||||
return Json(albums.Select(a => Mapper.Map(a, new AlbumResultDto())));
|
||||
}
|
||||
|
||||
[HttpGet("mostPopular")]
|
||||
|
|
@ -60,7 +61,7 @@ namespace MusicStore.Apis
|
|||
.ToListAsync();
|
||||
|
||||
// TODO: Move the .Select() to end of albums query when EF supports it
|
||||
return Json(albums.Select(a => SimpleMapper.Map(a, new AlbumResultDto())));
|
||||
return Json(albums.Select(a => Mapper.Map(a, new AlbumResultDto())));
|
||||
}
|
||||
|
||||
[HttpGet("{albumId:int}")]
|
||||
|
|
@ -76,7 +77,7 @@ namespace MusicStore.Apis
|
|||
.Where(a => a.AlbumId == albumId)
|
||||
.SingleOrDefaultAsync();
|
||||
|
||||
var albumResult = SimpleMapper.Map(album, new AlbumResultDto());
|
||||
var albumResult = Mapper.Map(album, new AlbumResultDto());
|
||||
|
||||
// TODO: Get these from the related entities when EF supports that again, i.e. when .Include() works
|
||||
//album.Artist.Name = (await _storeContext.Artists.SingleOrDefaultAsync(a => a.ArtistId == album.ArtistId)).Name;
|
||||
|
|
@ -99,8 +100,8 @@ namespace MusicStore.Apis
|
|||
|
||||
// Save the changes to the DB
|
||||
var dbAlbum = new Album();
|
||||
_storeContext.Albums.Add(SimpleMapper.Map(album, dbAlbum));
|
||||
await _storeContext.SaveChangesAsync();
|
||||
_storeContext.Albums.Add(Mapper.Map(album, dbAlbum));
|
||||
await _storeContext.SaveChangesAsync();
|
||||
|
||||
// TODO: Handle missing record, key violations, concurrency issues, etc.
|
||||
|
||||
|
|
@ -133,7 +134,7 @@ namespace MusicStore.Apis
|
|||
}
|
||||
|
||||
// Save the changes to the DB
|
||||
SimpleMapper.Map(album, dbAlbum);
|
||||
Mapper.Map(album, dbAlbum);
|
||||
await _storeContext.SaveChangesAsync();
|
||||
|
||||
// TODO: Handle missing record, key violations, concurrency issues, etc.
|
||||
|
|
@ -166,44 +167,44 @@ namespace MusicStore.Apis
|
|||
Message = "Album deleted successfully."
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[BuddyType(typeof(Album))]
|
||||
public class AlbumChangeDto
|
||||
[ModelMetadataType(typeof(Album))]
|
||||
public class AlbumChangeDto
|
||||
{
|
||||
public int GenreId { get; set; }
|
||||
|
||||
public int ArtistId { get; set; }
|
||||
|
||||
public string Title { get; set; }
|
||||
|
||||
public decimal Price { get; set; }
|
||||
|
||||
public string AlbumArtUrl { get; set; }
|
||||
}
|
||||
|
||||
public class AlbumResultDto : AlbumChangeDto
|
||||
{
|
||||
public AlbumResultDto()
|
||||
{
|
||||
public int GenreId { get; set; }
|
||||
|
||||
public int ArtistId { get; set; }
|
||||
|
||||
public string Title { get; set; }
|
||||
|
||||
public decimal Price { get; set; }
|
||||
|
||||
public string AlbumArtUrl { get; set; }
|
||||
Artist = new ArtistResultDto();
|
||||
Genre = new GenreResultDto();
|
||||
}
|
||||
|
||||
public class AlbumResultDto : AlbumChangeDto
|
||||
{
|
||||
public AlbumResultDto()
|
||||
{
|
||||
Artist = new ArtistResultDto();
|
||||
Genre = new GenreResultDto();
|
||||
}
|
||||
public int AlbumId { get; set; }
|
||||
|
||||
public int AlbumId { get; set; }
|
||||
public ArtistResultDto Artist { get; private set; }
|
||||
|
||||
public ArtistResultDto Artist { get; private set; }
|
||||
public GenreResultDto Genre { get; private set; }
|
||||
}
|
||||
|
||||
public GenreResultDto Genre { get; private set; }
|
||||
}
|
||||
public class ArtistResultDto
|
||||
{
|
||||
public string Name { get; set; }
|
||||
}
|
||||
|
||||
public class ArtistResultDto
|
||||
{
|
||||
public string Name { get; set; }
|
||||
}
|
||||
|
||||
public class GenreResultDto
|
||||
{
|
||||
public string Name { get; set; }
|
||||
}
|
||||
public class GenreResultDto
|
||||
{
|
||||
public string Name { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,45 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNet.Mvc;
|
||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||
|
||||
namespace MusicStore.Spa.Infrastructure
|
||||
{
|
||||
public class BuddyModelMetadataProvider : DataAnnotationsModelMetadataProvider
|
||||
{
|
||||
protected override CachedDataAnnotationsModelMetadata CreateMetadataPrototype(IEnumerable<object> attributes, Type containerType, Type modelType, string propertyName)
|
||||
{
|
||||
var realTypeMetadata = base.CreateMetadataPrototype(attributes, containerType, modelType, propertyName);
|
||||
var buddyType = BuddyTypeAttribute.GetBuddyType(modelType);
|
||||
|
||||
if (buddyType != null)
|
||||
{
|
||||
var buddyMetadata = base.CreateMetadataPrototype(attributes, containerType, buddyType, propertyName);
|
||||
foreach (var realProperty in realTypeMetadata.Properties)
|
||||
{
|
||||
var buddyProperty = buddyMetadata.Properties.SingleOrDefault(bp => string.Equals(bp.PropertyName, realProperty.PropertyName, StringComparison.Ordinal));
|
||||
if (buddyProperty != null)
|
||||
{
|
||||
// TODO: Only overwrite if the real type doesn't explicitly set it
|
||||
realProperty.IsReadOnly = buddyProperty.IsReadOnly;
|
||||
realProperty.IsRequired = buddyProperty.IsRequired;
|
||||
realProperty.DisplayName = buddyProperty.DisplayName;
|
||||
realProperty.DisplayFormatString = buddyProperty.DisplayFormatString;
|
||||
realProperty.SimpleDisplayProperty = buddyProperty.SimpleDisplayProperty;
|
||||
realProperty.DataTypeName = buddyProperty.DataTypeName;
|
||||
realProperty.Description = buddyProperty.Description;
|
||||
realProperty.EditFormatString = buddyProperty.EditFormatString;
|
||||
realProperty.NullDisplayText = buddyProperty.NullDisplayText;
|
||||
realProperty.ShowForDisplay = buddyProperty.ShowForDisplay;
|
||||
realProperty.ShowForEdit = buddyProperty.ShowForEdit;
|
||||
realProperty.TemplateHint = buddyProperty.TemplateHint;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return realTypeMetadata;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace MusicStore.Spa.Infrastructure
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class BuddyTypeAttribute : Attribute
|
||||
{
|
||||
private readonly Type _metadataBuddyType;
|
||||
private readonly Type _validatorBuddyType;
|
||||
|
||||
public BuddyTypeAttribute(Type buddyType)
|
||||
{
|
||||
_metadataBuddyType = buddyType;
|
||||
_validatorBuddyType = buddyType;
|
||||
}
|
||||
|
||||
public Type MetadataBuddyType
|
||||
{
|
||||
get { return _metadataBuddyType; }
|
||||
}
|
||||
|
||||
public Type ValidatorBuddyType
|
||||
{
|
||||
get { return _validatorBuddyType; }
|
||||
}
|
||||
|
||||
public static Type GetBuddyType(Type type)
|
||||
{
|
||||
var attribute = type.GetTypeInfo().GetCustomAttribute<BuddyTypeAttribute>();
|
||||
|
||||
if (attribute != null)
|
||||
{
|
||||
return attribute.MetadataBuddyType;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||
|
||||
namespace MusicStore.Spa.Infrastructure
|
||||
{
|
||||
public class BuddyValidatorProvider : DataAnnotationsModelValidatorProvider
|
||||
{
|
||||
protected override IEnumerable<IModelValidator> GetValidators(ModelMetadata metadata, IEnumerable<object> attributes)
|
||||
{
|
||||
var buddyType = BuddyTypeAttribute.GetBuddyType(metadata.ContainerType ?? metadata.ModelType);
|
||||
|
||||
if (buddyType != null)
|
||||
{
|
||||
var buddyProperty = buddyType.GetTypeInfo().GetDeclaredProperty(metadata.PropertyName);
|
||||
if (buddyProperty != null)
|
||||
{
|
||||
var buddyTypeAttributes = buddyProperty.GetCustomAttributes();
|
||||
// TODO: De-dupe?
|
||||
attributes = attributes.Concat(buddyTypeAttributes);
|
||||
return base.GetValidators(metadata, attributes);
|
||||
}
|
||||
}
|
||||
|
||||
return Enumerable.Empty<IModelValidator>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,93 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
|
||||
namespace MusicStore.Spa.Infrastructure
|
||||
{
|
||||
public class SimpleMapper
|
||||
{
|
||||
private static readonly Expression _emptyExp = Expression.Empty();
|
||||
private static ConcurrentDictionary<Tuple<Type, Type>, Delegate> _mapCache = new ConcurrentDictionary<Tuple<Type, Type>, Delegate>();
|
||||
|
||||
public static TDest Map<TSource, TDest>(TSource source, TDest dest)
|
||||
{
|
||||
var map = (Func<TSource, TDest, TDest>)_mapCache.GetOrAdd(Tuple.Create(typeof(TSource), typeof(TDest)), _ => MakeMapMethod<TSource, TDest>());
|
||||
return map(source, dest);
|
||||
}
|
||||
|
||||
private static Func<TSource, TDest, TDest> MakeMapMethod<TSource, TDest>()
|
||||
{
|
||||
// TODO: Support convention-based mapping, e.g. AlbumTitle <- Album.Title
|
||||
// TODO: Support mapping to/from fields
|
||||
|
||||
var sourceProps = typeof(TSource).GetRuntimeProperties().ToDictionary(p => p.Name);
|
||||
var destProps = typeof(TDest).GetRuntimeProperties().ToDictionary(p => p.Name);
|
||||
|
||||
var destArg = Expression.Parameter(typeof(TDest), "dest");
|
||||
var srcArg = Expression.Parameter(typeof(TSource), "src");
|
||||
|
||||
var assignments = MakeAssignments(typeof(TSource), typeof(TDest), srcArg, destArg);
|
||||
|
||||
if (!assignments.Any())
|
||||
{
|
||||
throw new InvalidOperationException(string.Format("No matching properties were found between the types {0} and {1}", typeof(TSource), typeof(TDest)));
|
||||
}
|
||||
|
||||
var assignmentsBlock = Expression.Block(assignments);
|
||||
var blockExp = Expression.Block(typeof(TDest), assignmentsBlock, destArg);
|
||||
var map = Expression.Lambda<Func<TSource, TDest, TDest>>(blockExp, srcArg, destArg);
|
||||
|
||||
return map.Compile();
|
||||
}
|
||||
|
||||
private static IEnumerable<Expression> MakeAssignments(Type sourceType, Type destType, Expression sourcePropertyExp, Expression destPropertyExp)
|
||||
{
|
||||
var sourceProps = sourceType.GetRuntimeProperties().ToDictionary(p => p.Name);
|
||||
var destProps = destType.GetRuntimeProperties().ToDictionary(p => p.Name);
|
||||
var assignments = new List<Expression>();
|
||||
|
||||
foreach (var srcProp in sourceProps)
|
||||
{
|
||||
if (!srcProp.Value.GetMethod.IsPublic) continue;
|
||||
|
||||
var destProp = destProps.ContainsKey(srcProp.Key) ? destProps[srcProp.Key] : null;
|
||||
if (destProp != null && destProp.SetMethod != null)
|
||||
{
|
||||
var destPropType = destProp.PropertyType;
|
||||
var srcPropType = srcProp.Value.PropertyType;
|
||||
|
||||
var srcPropExp = Expression.Property(sourcePropertyExp, srcProp.Value);
|
||||
var destPropExp = Expression.Property(destPropertyExp, destProp);
|
||||
|
||||
if (destPropType.GetTypeInfo().IsAssignableFrom(srcPropType.GetTypeInfo()) && destProp.SetMethod.IsPublic)
|
||||
{
|
||||
var assignmentExp = Expression.Assign(destPropExp, srcPropExp);
|
||||
// dest.Prop = src.Prop;
|
||||
assignments.Add(assignmentExp);
|
||||
}
|
||||
else if (destProp.GetMethod.IsPublic)
|
||||
{
|
||||
// The properties aren't assignable but they may have members that are
|
||||
var deepAssignmentExp = MakeAssignments(srcPropType, destPropType, srcPropExp, destPropExp);
|
||||
if (deepAssignmentExp.Any())
|
||||
{
|
||||
// Check if dest is null and if so skip for now
|
||||
// - if (dest.Foo != null && src.Foo != null) {
|
||||
// - dest.Foo.Bar = src.Foo.Bar;
|
||||
// - }
|
||||
var nullCheckExp = Expression.And(Expression.NotEqual(destPropExp, Expression.Constant(null)),
|
||||
Expression.NotEqual(srcPropExp, Expression.Constant(null)));
|
||||
var nullCheckExpBlock = Expression.IfThen(nullCheckExp, Expression.Block(deepAssignmentExp));
|
||||
assignments.Add(nullCheckExpBlock);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return assignments;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,9 +5,12 @@ using Microsoft.AspNet.Mvc;
|
|||
using Microsoft.Data.Entity;
|
||||
using Microsoft.Framework.ConfigurationModel;
|
||||
using Microsoft.Framework.DependencyInjection;
|
||||
using AutoMapper;
|
||||
using MusicStore.Apis;
|
||||
using MusicStore.Models;
|
||||
using MusicStore.Spa.Infrastructure;
|
||||
|
||||
|
||||
namespace MusicStore.Spa
|
||||
{
|
||||
public class Startup
|
||||
|
|
@ -19,7 +22,7 @@ namespace MusicStore.Spa
|
|||
.AddEnvironmentVariables();
|
||||
}
|
||||
|
||||
public IConfiguration Configuration { get; set; }
|
||||
public Microsoft.Framework.ConfigurationModel.IConfiguration Configuration { get; set; }
|
||||
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
|
|
@ -32,11 +35,6 @@ namespace MusicStore.Spa
|
|||
// Add MVC services to the service container
|
||||
services.AddMvc();
|
||||
|
||||
services.Configure<MvcOptions>(options =>
|
||||
{
|
||||
options.ModelValidatorProviders.Add(typeof(BuddyValidatorProvider));
|
||||
});
|
||||
|
||||
// Add EF services to the service container
|
||||
services.AddEntityFramework()
|
||||
.AddSqlServer()
|
||||
|
|
@ -61,6 +59,14 @@ namespace MusicStore.Spa
|
|||
options.AddPolicy("app-ManageStore", new AuthorizationPolicyBuilder().RequireClaim("app-ManageStore", "Allowed").Build());
|
||||
});
|
||||
|
||||
Mapper.CreateMap<AlbumChangeDto, Album>();
|
||||
Mapper.CreateMap<Album, AlbumChangeDto>();
|
||||
Mapper.CreateMap<Album, AlbumResultDto>();
|
||||
Mapper.CreateMap<AlbumResultDto, Album>();
|
||||
Mapper.CreateMap<Artist, ArtistResultDto>();
|
||||
Mapper.CreateMap<ArtistResultDto, Artist>();
|
||||
Mapper.CreateMap<Genre, GenreResultDto>();
|
||||
Mapper.CreateMap<GenreResultDto, Genre>();
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app)
|
||||
|
|
|
|||
|
|
@ -34,7 +34,8 @@
|
|||
"Microsoft.AspNet.Authentication.Cookies": "1.0.0-*",
|
||||
"Microsoft.AspNet.Identity.EntityFramework": "3.0.0-*",
|
||||
"Microsoft.Framework.ConfigurationModel": "1.0.0-*",
|
||||
"Microsoft.Framework.ConfigurationModel.Json": "1.0.0-*"
|
||||
"Microsoft.Framework.ConfigurationModel.Json": "1.0.0-*",
|
||||
"AutoMapper": "4.0.0-ci1026"
|
||||
},
|
||||
"commands": {
|
||||
"WebListener": "Microsoft.AspNet.Hosting --server Microsoft.AspNet.Server.WebListener --server.urls http://localhost:5102",
|
||||
|
|
@ -49,4 +50,4 @@
|
|||
"aspnet50": {},
|
||||
"aspnetcore50": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue