diff --git a/src/MusicStore.Spa/Apis/AlbumsApiController.cs b/src/MusicStore.Spa/Apis/AlbumsApiController.cs index 68b2f04f80..f0c63d17e9 100644 --- a/src/MusicStore.Spa/Apis/AlbumsApiController.cs +++ b/src/MusicStore.Spa/Apis/AlbumsApiController.cs @@ -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; } } } diff --git a/src/MusicStore.Spa/Infrastructure/BuddyModelMetadataProvider.cs b/src/MusicStore.Spa/Infrastructure/BuddyModelMetadataProvider.cs deleted file mode 100644 index 265c323263..0000000000 --- a/src/MusicStore.Spa/Infrastructure/BuddyModelMetadataProvider.cs +++ /dev/null @@ -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 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; - } - } -} \ No newline at end of file diff --git a/src/MusicStore.Spa/Infrastructure/BuddyTypeAttribute.cs b/src/MusicStore.Spa/Infrastructure/BuddyTypeAttribute.cs deleted file mode 100644 index e758523d98..0000000000 --- a/src/MusicStore.Spa/Infrastructure/BuddyTypeAttribute.cs +++ /dev/null @@ -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(); - - if (attribute != null) - { - return attribute.MetadataBuddyType; - } - - return null; - } - } -} \ No newline at end of file diff --git a/src/MusicStore.Spa/Infrastructure/BuddyValidatorProvider.cs b/src/MusicStore.Spa/Infrastructure/BuddyValidatorProvider.cs deleted file mode 100644 index 3a4934c7cd..0000000000 --- a/src/MusicStore.Spa/Infrastructure/BuddyValidatorProvider.cs +++ /dev/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 GetValidators(ModelMetadata metadata, IEnumerable 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(); - } - } -} \ No newline at end of file diff --git a/src/MusicStore.Spa/Infrastructure/SimpleMapper.cs b/src/MusicStore.Spa/Infrastructure/SimpleMapper.cs deleted file mode 100644 index 609b6ae782..0000000000 --- a/src/MusicStore.Spa/Infrastructure/SimpleMapper.cs +++ /dev/null @@ -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, Delegate> _mapCache = new ConcurrentDictionary, Delegate>(); - - public static TDest Map(TSource source, TDest dest) - { - var map = (Func)_mapCache.GetOrAdd(Tuple.Create(typeof(TSource), typeof(TDest)), _ => MakeMapMethod()); - return map(source, dest); - } - - private static Func MakeMapMethod() - { - // 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>(blockExp, srcArg, destArg); - - return map.Compile(); - } - - private static IEnumerable 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(); - - 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; - } - } -} \ No newline at end of file diff --git a/src/MusicStore.Spa/Startup.cs b/src/MusicStore.Spa/Startup.cs index 45340a1edd..143f3a045e 100644 --- a/src/MusicStore.Spa/Startup.cs +++ b/src/MusicStore.Spa/Startup.cs @@ -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(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(); + Mapper.CreateMap(); + Mapper.CreateMap(); + Mapper.CreateMap(); + Mapper.CreateMap(); + Mapper.CreateMap(); + Mapper.CreateMap(); + Mapper.CreateMap(); } public void Configure(IApplicationBuilder app) diff --git a/src/MusicStore.Spa/project.json b/src/MusicStore.Spa/project.json index a25c23025d..e394d76151 100644 --- a/src/MusicStore.Spa/project.json +++ b/src/MusicStore.Spa/project.json @@ -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": {} } -} \ No newline at end of file +}