From 36a5616a6c9ca55c007817c6dcb7443ad985ac2c Mon Sep 17 00:00:00 2001 From: Jess Chadwick Date: Fri, 2 Nov 2012 22:54:40 -0400 Subject: [PATCH] Chapter 7 --- Ebuy.Common/DataAccess/IRepository.cs | 24 ++++ Ebuy.Common/DataAccess/Repository.cs | 139 +++++++++++++++++++++ Ebuy.Common/Ebuy.Common.csproj | 3 + Ebuy.Common/Entities/Auction.cs | 2 +- Ebuy.Common/Entities/Entity.cs | 60 +++++++++ Ebuy.Website/Api/AuctionCsvFormatter.cs | 79 ++++++++++++ Ebuy.Website/Api/AuctionsDataController.cs | 54 ++++++++ Ebuy.Website/Api/CustomExceptionFilter.cs | 20 +++ Ebuy.Website/App_Start/WebApiConfig.cs | 8 +- Ebuy.Website/Ebuy.Website.csproj | 6 + Ebuy.Website/packages.config | 1 + 11 files changed, 391 insertions(+), 5 deletions(-) create mode 100644 Ebuy.Common/DataAccess/IRepository.cs create mode 100644 Ebuy.Common/DataAccess/Repository.cs create mode 100644 Ebuy.Common/Entities/Entity.cs create mode 100644 Ebuy.Website/Api/AuctionCsvFormatter.cs create mode 100644 Ebuy.Website/Api/AuctionsDataController.cs create mode 100644 Ebuy.Website/Api/CustomExceptionFilter.cs diff --git a/Ebuy.Common/DataAccess/IRepository.cs b/Ebuy.Common/DataAccess/IRepository.cs new file mode 100644 index 0000000..dc97886 --- /dev/null +++ b/Ebuy.Common/DataAccess/IRepository.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; + +namespace Ebuy.DataAccess +{ + public interface IRepository : IDisposable + { + void Add(TModel instance) where TModel : class, IEntity; + void Add(IEnumerable instances) where TModel : class, IEntity; + + IQueryable All(params string[] includePaths) where TModel : class, IEntity; + + void Delete(object key) where TModel : class, IEntity; + void Delete(TModel instance) where TModel : class, IEntity; + void Delete(Expression> predicate) where TModel : class, IEntity; + + TModel Single(object key) where TModel : class, IEntity; + TModel Single(Expression> predicate, params string[] includePaths) where TModel : class, IEntity; + + IQueryable Query(Expression> predicate, params string[] includePaths) where TModel : class, IEntity; + } +} \ No newline at end of file diff --git a/Ebuy.Common/DataAccess/Repository.cs b/Ebuy.Common/DataAccess/Repository.cs new file mode 100644 index 0000000..8fde81c --- /dev/null +++ b/Ebuy.Common/DataAccess/Repository.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using System.Data.Entity; +using System.Data.Entity.Infrastructure; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Linq.Expressions; + +namespace Ebuy.DataAccess +{ + public class Repository : IRepository + { + private readonly DbContext _context; + private readonly bool _isSharedContext; + + public Repository(DbContext context, bool isSharedContext = true) + { + Contract.Requires(context != null); + + _context = context; + _isSharedContext = isSharedContext; + } + + + public void Add(TModel instance) + where TModel : class, IEntity + { + Contract.Requires(instance != null); + + _context.Set().Add(instance); + + if (_isSharedContext == false) + _context.SaveChanges(); + } + + public void Add(IEnumerable instances) + where TModel : class, IEntity + { + Contract.Requires(instances != null); + + foreach (var instance in instances) + { + Add(instance); + } + } + + + public IQueryable All(params string[] includePaths) + where TModel : class, IEntity + { + return Query(x => true, includePaths); + } + + + public void Dispose() + { + // If this is a shared (or null) context then + // we're not responsible for disposing it + if (_isSharedContext || _context == null) + return; + + _context.Dispose(); + } + + + public void Delete(object id) + where TModel : class, IEntity + { + Contract.Requires(id != null); + + var instance = Single(id); + Delete(instance); + } + + public void Delete(TModel instance) + where TModel : class, IEntity + { + Contract.Requires(instance != null); + + if (instance != null) + _context.Set().Remove(instance); + } + + public void Delete(Expression> predicate) + where TModel : class, IEntity + { + Contract.Requires(predicate != null); + + TModel entity = Single(predicate); + Delete(entity); + } + + + public TModel Single(object id) + where TModel : class, IEntity + { + Contract.Requires(id != null); + + var instance = _context.Set().Find(id); + return instance; + } + + public TModel Single(Expression> predicate, params string[] includePaths) + where TModel : class, IEntity + { + Contract.Requires(predicate != null); + + var instance = GetSetWithIncludedPaths(includePaths).SingleOrDefault(predicate); + return instance; + } + + + public IQueryable Query(Expression> predicate, params string[] includePaths) + where TModel : class, IEntity + { + Contract.Requires(predicate != null); + + var items = GetSetWithIncludedPaths(includePaths); + + if (predicate != null) + return items.Where(predicate); + + return items; + } + + + private DbQuery GetSetWithIncludedPaths(IEnumerable includedPaths) where TModel : class, IEntity + { + DbQuery items = _context.Set(); + + foreach (var path in includedPaths ?? Enumerable.Empty()) + { + items = items.Include(path); + } + + return items; + } + } +} diff --git a/Ebuy.Common/Ebuy.Common.csproj b/Ebuy.Common/Ebuy.Common.csproj index f5b576a..489a6c8 100644 --- a/Ebuy.Common/Ebuy.Common.csproj +++ b/Ebuy.Common/Ebuy.Common.csproj @@ -48,8 +48,11 @@ + + + diff --git a/Ebuy.Common/Entities/Auction.cs b/Ebuy.Common/Entities/Auction.cs index 5cb58a8..57bcd28 100644 --- a/Ebuy.Common/Entities/Auction.cs +++ b/Ebuy.Common/Entities/Auction.cs @@ -3,7 +3,7 @@ namespace Ebuy { - public class Auction + public class Auction : IEntity { public long Id { get; set; } diff --git a/Ebuy.Common/Entities/Entity.cs b/Ebuy.Common/Entities/Entity.cs new file mode 100644 index 0000000..1a1a5af --- /dev/null +++ b/Ebuy.Common/Entities/Entity.cs @@ -0,0 +1,60 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace Ebuy +{ + public interface IEntity + { + } + + public abstract class Entity : IEntity, IEquatable> + where TId : struct + { + [Key] + public virtual TId Id + { + get + { + if (_id == null && typeof(TId) == typeof(Guid)) + _id = Guid.NewGuid(); + + return _id == null ? default(TId) : (TId)_id; + } + protected set { _id = value; } + } + private object _id; + + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != typeof(Entity)) return false; + return Equals((Entity)obj); + } + + public bool Equals(Entity other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + if (other.GetType() != GetType()) return false; + + return other.Id.Equals(Id); + } + + public override int GetHashCode() + { + return Id.GetHashCode(); + } + + public static bool operator ==(Entity left, Entity right) + { + return Equals(left, right); + } + + public static bool operator !=(Entity left, Entity right) + { + return !Equals(left, right); + } + } +} \ No newline at end of file diff --git a/Ebuy.Website/Api/AuctionCsvFormatter.cs b/Ebuy.Website/Api/AuctionCsvFormatter.cs new file mode 100644 index 0000000..35a1f99 --- /dev/null +++ b/Ebuy.Website/Api/AuctionCsvFormatter.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net.Http.Formatting; +using System.Net.Http.Headers; + +namespace Ebuy.Website.Api +{ + public class AuctionCsvFormatter : BufferedMediaTypeFormatter + { + public AuctionCsvFormatter() + { + SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/csv")); + } + + public override bool CanWriteType(Type type) + { + if (type == typeof(Auction)) + { + return true; + } + else + { + Type enumerableType = typeof(IEnumerable); + return enumerableType.IsAssignableFrom(type); + } + } + + public override bool CanReadType(Type type) + { + return false; + } + + public override void WriteToStream(Type type, object value, Stream stream, System.Net.Http.HttpContent content) + { + var source = value as IEnumerable; + if (source != null) + { + foreach (var item in source) + { + WriteItem(item, stream); + } + } + else + { + var item = value as Auction; + if (item != null) + { + WriteItem(item, stream); + } + } + } + + private void WriteItem(Auction item, Stream stream) + { + var writer = new StreamWriter(stream); + writer.WriteLine("{0},{1},{2}", + Encode(item.Title), + Encode(item.Description), + Encode(item.CurrentPrice)); + writer.Flush(); + } + + static readonly char[] _specialChars = new char[] { ',', '\n', '\r', '"' }; + private string Encode(object o) + { + string result = ""; + if (o != null) + { + string data = o.ToString(); + if (data.IndexOfAny(_specialChars) != -1) + { + result = String.Format("\"{0}\"", data.Replace("\"", "\"\"")); + } + } + return result; + } + } +} \ No newline at end of file diff --git a/Ebuy.Website/Api/AuctionsDataController.cs b/Ebuy.Website/Api/AuctionsDataController.cs new file mode 100644 index 0000000..eb2bbe6 --- /dev/null +++ b/Ebuy.Website/Api/AuctionsDataController.cs @@ -0,0 +1,54 @@ +using System; +using System.Linq; +using System.Web.Http; +using AutoMapper; +using Ebuy.DataAccess; + +namespace Ebuy.Website.Api +{ + public class AuctionsDataController : ApiController + { + private readonly IRepository _repository; + + public AuctionsDataController(IRepository repository) + { + _repository = repository; + } + + public IQueryable Get() + { + return _repository.All().AsQueryable(); + } + + [CustomExceptionFilter] + public Auction Get(string id) + { + var result = _repository.Single(id); + + if (result == null) + { + throw new Exception("Item not Found!"); + } + + return result; + } + public void Post(Auction auction) + { + _repository.Add(auction); + } + + public void Put(string id, Auction auction) + { + var currentAuction = _repository.Single(id); + if (currentAuction != null) + { + Mapper.DynamicMap(auction, currentAuction); + } + } + + public void Delete(string id) + { + _repository.Delete(id); + } + } +} diff --git a/Ebuy.Website/Api/CustomExceptionFilter.cs b/Ebuy.Website/Api/CustomExceptionFilter.cs new file mode 100644 index 0000000..7d8fba6 --- /dev/null +++ b/Ebuy.Website/Api/CustomExceptionFilter.cs @@ -0,0 +1,20 @@ +using System.Net; +using System.Net.Http; +using System.Web.Http.Filters; + +namespace Ebuy.Website.Api +{ + public class CustomExceptionFilter : ExceptionFilterAttribute + { + public override void OnException(HttpActionExecutedContext context) + { + if (context.Response == null) + { + context.Response = new HttpResponseMessage(); + } + + context.Response.StatusCode = HttpStatusCode.NotImplemented; + context.Response.Content = new StringContent("Custom Message"); + } + } +} \ No newline at end of file diff --git a/Ebuy.Website/App_Start/WebApiConfig.cs b/Ebuy.Website/App_Start/WebApiConfig.cs index fe4de07..6e8224e 100644 --- a/Ebuy.Website/App_Start/WebApiConfig.cs +++ b/Ebuy.Website/App_Start/WebApiConfig.cs @@ -1,7 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web.Http; +using System.Web.Http; +using Ebuy.Website.Api; namespace Ebuy.Website { @@ -9,6 +7,8 @@ public static class WebApiConfig { public static void Register(HttpConfiguration config) { + config.Formatters.Add(new AuctionCsvFormatter()); + config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", diff --git a/Ebuy.Website/Ebuy.Website.csproj b/Ebuy.Website/Ebuy.Website.csproj index 809170b..a576595 100644 --- a/Ebuy.Website/Ebuy.Website.csproj +++ b/Ebuy.Website/Ebuy.Website.csproj @@ -39,6 +39,9 @@ 4 + + ..\packages\AutoMapper.2.2.0\lib\net40\AutoMapper.dll + ..\packages\Ninject.3.0.1.10\lib\net45-full\Ninject.dll @@ -168,6 +171,9 @@ + + + diff --git a/Ebuy.Website/packages.config b/Ebuy.Website/packages.config index 825a295..51a50e9 100644 --- a/Ebuy.Website/packages.config +++ b/Ebuy.Website/packages.config @@ -1,5 +1,6 @@  +