Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Chapter 8

  • Loading branch information...
commit 491b7471e08bd3d732549a48288660b7a0f4adcb 2 parents d6a3b9f + 9660750
@jchadwick jchadwick authored
View
8 Ebuy.Common/DataAccess/EbuyDataContext.cs
@@ -13,5 +13,13 @@ public EbuyDataContext()
Database.SetInitializer(initializer);
#endif
}
+
+ protected override void OnModelCreating(DbModelBuilder modelBuilder)
+ {
+ modelBuilder.Entity<Bid>()
+ .HasRequired(x => x.Auction)
+ .WithMany()
+ .WillCascadeOnDelete(false);
+ }
}
}
View
11 Ebuy.Common/DataAccess/Repository.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Diagnostics.Contracts;
using System.Linq;
@@ -9,16 +10,10 @@ namespace Ebuy.DataAccess
{
public class Repository : IRepository
{
- private readonly EbuyDataContext _context;
+ private readonly DbContext _context;
private readonly bool _isSharedContext;
-
- public Repository()
- : this(new EbuyDataContext(), false)
- {
- }
-
- public Repository(EbuyDataContext context, bool isSharedContext = true)
+ public Repository(DbContext context, bool isSharedContext = true)
{
Contract.Requires(context != null);
View
9 Ebuy.Common/Ebuy.Common.csproj
@@ -41,6 +41,7 @@
<Reference Include="System.ComponentModel.DataAnnotations" />
<Reference Include="System.Core" />
<Reference Include="System.Data.Entity" />
+ <Reference Include="System.Web" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
@@ -50,9 +51,17 @@
<ItemGroup>
<Compile Include="DataAccess\IRepository.cs" />
<Compile Include="DataAccess\Repository.cs" />
+ <Compile Include="Entities\Annotations\UniqueAttribute.cs" />
<Compile Include="Entities\Auction.cs" />
<Compile Include="DataAccess\EbuyDataContext.cs" />
+ <Compile Include="Entities\Bid.cs" />
+ <Compile Include="Entities\Category.cs" />
+ <Compile Include="Entities\Currency.cs" />
<Compile Include="Entities\Entity.cs" />
+ <Compile Include="Entities\EntityKeyGenerationException.cs" />
+ <Compile Include="Entities\Payment.cs" />
+ <Compile Include="Entities\User.cs" />
+ <Compile Include="Entities\WebsiteImage.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
View
61 Ebuy.Common/Entities/Annotations/UniqueAttribute.cs
@@ -0,0 +1,61 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Data.Entity;
+using System.Linq;
+using System.Reflection;
+
+namespace Ebuy.DataAnnotations
+{
+ public class UniqueConstraintApplier
+ {
+ private const string UniqueConstraintQuery = "ALTER TABLE [{0}] ADD CONSTRAINT [{0}_{1}_unique] UNIQUE ([{1}])";
+
+ public void ApplyUniqueConstraints(DbContext context)
+ {
+ var modelTypes =
+ from dbContextProperties in context.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public)
+ let propertyTypeGenericArguments = dbContextProperties.PropertyType.GetGenericArguments()
+ where propertyTypeGenericArguments.Count() == 1
+ select propertyTypeGenericArguments.Single();
+
+ var modelsWithUniqueProperties =
+ from modelType in modelTypes
+ from property in modelType.GetProperties(BindingFlags.Instance | BindingFlags.Public)
+ from uniqueAttribute in property.GetCustomAttributes(true).OfType<UniqueAttribute>()
+ let propertyName = property.Name
+
+ group propertyName by modelType into uniquePropertiesByModel
+
+ select new {
+ Model = uniquePropertiesByModel.Key,
+ Properties = (IEnumerable<string>) uniquePropertiesByModel
+ };
+
+ foreach (var model in modelsWithUniqueProperties)
+ {
+ foreach (var property in model.Properties)
+ {
+ string tableName = GetTableName(model.Model);
+ string query = string.Format(UniqueConstraintQuery, tableName, property);
+ context.Database.ExecuteSqlCommand(query);
+ }
+ }
+ }
+
+ private string GetTableName(Type model)
+ {
+ var modelName = model.Name;
+
+ if (modelName.EndsWith("y"))
+ modelName = modelName.Substring(0, modelName.Length - 1) + "ie";
+
+ return modelName + "s";
+ }
+ }
+
+ [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
+ public class UniqueAttribute : RequiredAttribute
+ {
+ }
+}
View
150 Ebuy.Common/Entities/Auction.cs
@@ -1,26 +1,148 @@
-using System;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Diagnostics.Contracts;
+using System.Linq;
namespace Ebuy
{
- public class Auction : IEntity
+ [MetadataType(typeof(Auction.Metadata))]
+ public class Auction : Entity<Guid>
{
- public long Id { get; set; }
+ public virtual string Title { get; set; }
+ public virtual string Description { get; set; }
+ public virtual DateTime StartTime { get; set; }
+ public virtual DateTime EndTime { get; set; }
+ public virtual Currency StartPrice { get; set; }
+ public virtual Currency CurrentPrice { get; set; }
- [Required]
- [StringLength(50,
- ErrorMessage = "Title cannot be longer than 50 characters")]
- public string Title { get; set; }
+ public Guid? WinningBidId { get; set; }
+ public virtual Bid WinningBid { get; private set; }
- [Required]
- public string Description { get; set; }
+ public bool IsCompleted
+ {
+ get { return EndTime <= DateTime.Now; }
+ }
- [Range(1, 10000,
- ErrorMessage = "The auction's starting price must be at least 1")]
- public decimal StartPrice { get; set; }
+ public virtual bool IsFeaturedAuction { get; private set; }
- public decimal CurrentPrice { get; set; }
+ public virtual ICollection<Bid> Bids { get; set; }
- public DateTime EndTime { get; set; }
+ public virtual ICollection<Category> Categories { get; set; }
+
+ public virtual ICollection<WebsiteImage> Images { get; set; }
+
+ public long OwnerId { get; set; }
+ public virtual User Owner { get; set; }
+
+ public virtual CurrencyCode CurrencyCode
+ {
+ get
+ {
+ return (CurrentPrice != null) ? CurrentPrice.Code : null;
+ }
+ }
+
+ public Auction()
+ {
+ Bids = new Collection<Bid>();
+ Categories = new Collection<Category>();
+ Images = new Collection<WebsiteImage>();
+ }
+
+ public void FeatureAuction()
+ {
+ IsFeaturedAuction = true;
+ }
+
+ public Bid PostBid(User user, double bidAmount)
+ {
+ return PostBid(user, new Currency(CurrencyCode, bidAmount));
+ }
+
+ public Bid PostBid(User user, Currency bidAmount)
+ {
+ Contract.Requires(user != null);
+
+ if (bidAmount.Code != CurrencyCode)
+ throw new InvalidBidException(bidAmount, WinningBid);
+
+ if (bidAmount.Value <= CurrentPrice.Value)
+ throw new InvalidBidException(bidAmount, WinningBid);
+
+ var bid = new Bid(user, this, bidAmount);
+
+ CurrentPrice = bidAmount;
+ WinningBidId = bid.Id;
+
+ Bids.Add(bid);
+
+ return bid;
+ }
+
+
+ public class Metadata
+ {
+ [InverseProperty("Auction")]
+ public object Bids;
+
+ public object Categories;
+
+ [Required]
+ public object CurrentPrice;
+
+ [Required]
+ public object Description;
+
+ [Required]
+ public object EndTime;
+
+ [InverseProperty("Selling")]
+ public object Owner;
+
+ [Required]
+ [ForeignKey("Owner")]
+ public object OwnerId;
+
+ [Required]
+ public object StartTime;
+
+ [Required, StringLength(500)]
+ public object Title;
+
+ [ForeignKey("WinningBid")]
+ public object WinningBidId;
+ }
+ }
+
+ public class InvalidBidException : Exception
+ {
+ public Currency BidAmount { get; set; }
+ public Bid WinningBid { get; set; }
+
+ public InvalidBidException(Currency bidAmount, Bid winningBid = null)
+ {
+ BidAmount = bidAmount;
+ WinningBid = winningBid;
+ }
}
+
+
+ public static class AuctionExtensions
+ {
+
+ public static IEnumerable<Auction> Active(this IEnumerable<Auction> auctions)
+ {
+ return auctions.Where(x => x.IsCompleted == false);
+ }
+
+ public static IEnumerable<Auction> Featured(this IEnumerable<Auction> auctions)
+ {
+ return auctions.Where(x => x.IsFeaturedAuction);
+ }
+
+ }
+
}
View
71 Ebuy.Common/Entities/Bid.cs
@@ -0,0 +1,71 @@
+using System;
+using System.ComponentModel.DataAnnotations;
+using System.Diagnostics.Contracts;
+
+namespace Ebuy
+{
+ public class Bid : Entity<Guid>, IEquatable<Bid>
+ {
+ public Currency Amount { get; private set; }
+
+ public virtual Auction Auction { get; private set; }
+
+ public DateTime Timestamp { get; private set; }
+
+ public virtual User User { get; private set; }
+
+ public bool IsWinningBid
+ {
+ get
+ {
+ return Auction != null
+ && Id == Auction.WinningBidId;
+ }
+ }
+
+
+ public Bid(User user, Auction auction, Currency price)
+ {
+ Contract.Requires(user != null);
+ Contract.Requires(auction != null);
+ Contract.Requires(price != null);
+
+ User = user;
+ Auction = auction;
+ Amount = price;
+ Timestamp = DateTime.Now;
+ }
+
+ private Bid()
+ {
+ }
+
+ public bool Equals(Bid other)
+ {
+ if (ReferenceEquals(null, other)) return false;
+ if (ReferenceEquals(this, other)) return true;
+ return other.Id.Equals(Id);
+ }
+
+ public override int GetHashCode()
+ {
+ return Id.GetHashCode();
+ }
+
+
+ public class Metadata
+ {
+ [Required]
+ public object Auction;
+
+ [Required]
+ public object Amount;
+
+ [Required]
+ public object Timestamp;
+
+ [Required]
+ public object User;
+ }
+ }
+}
View
57 Ebuy.Common/Entities/Category.cs
@@ -0,0 +1,57 @@
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace Ebuy
+{
+ [MetadataType(typeof(Category.Metadata))]
+ public class Category : Entity<long>
+ {
+ public virtual ICollection<Auction> Auctions { get; set; }
+
+ public bool IsTopLevelCategory
+ {
+ get { return ParentId == null; }
+ }
+
+ public string Name { get; set; }
+
+ public long? ParentId { get; set; }
+
+ public virtual Category Parent { get; set; }
+
+ public virtual ICollection<Category> SubCategories { get; set; }
+
+
+ public Category()
+ {
+ Auctions = new Collection<Auction>();
+ SubCategories = new Collection<Category>();
+ }
+
+ public Category(string name)
+ {
+ Name = name;
+ }
+
+
+ protected override string GenerateKey()
+ {
+ if (string.IsNullOrWhiteSpace(Name))
+ // TODO: Localize
+ throw new EntityKeyGenerationException(GetType(), "Name is empty");
+
+ return KeyGenerator.Generate(Name);
+ }
+
+ public class Metadata
+ {
+ [Required, StringLength(100)]
+ public object Name;
+
+ [ForeignKey("Parent")]
+ public object ParentId;
+ }
+ }
+}
View
129 Ebuy.Common/Entities/Currency.cs
@@ -0,0 +1,129 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Diagnostics.Contracts;
+using System.Linq;
+
+namespace Ebuy
+{
+ [ComplexType]
+ public class CurrencyCode
+ {
+ private readonly string _value;
+
+ public CurrencyCode(string value) : this()
+ {
+ _value = value;
+ }
+
+ private CurrencyCode()
+ {
+ }
+
+ public static implicit operator CurrencyCode(string code)
+ {
+ return new CurrencyCode(code);
+ }
+
+ public static implicit operator string(CurrencyCode code)
+ {
+ return code == null ? null : code._value;
+ }
+ }
+
+ [ComplexType]
+ public class Currency : IEquatable<Currency>
+ {
+ public static IDictionary<char, string> CurrencyCodesBySymbol = new Dictionary<char, string>() {
+ { '', "EUR" },
+ { '£', "GBP" },
+ { '¥', "JPY" },
+ { '$', "USD" },
+ };
+
+ public string Code { get; private set; }
+ public double Value { get; private set; }
+
+
+ public Currency(CurrencyCode code, double value)
+ {
+ Code = code;
+ Value = value;
+ }
+
+ public Currency(string currency)
+ {
+ Contract.Requires(!string.IsNullOrWhiteSpace(currency));
+ Contract.Requires(currency.Length > 1);
+
+ Code = CurrencyCodesBySymbol[currency[0]];
+ Value = double.Parse(currency.Substring(1));
+ }
+
+ private Currency()
+ {
+ }
+
+
+ public bool Equals(Currency other)
+ {
+ if (ReferenceEquals(null, other)) return false;
+ if (ReferenceEquals(this, other)) return true;
+ return Equals(other.Code, Code) && other.Value == Value;
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(null, obj)) return false;
+ if (ReferenceEquals(this, obj)) return true;
+ if (obj.GetType() != typeof (Currency)) return false;
+ return Equals((Currency) obj);
+ }
+
+ public override string ToString()
+ {
+ var symbol = CurrencyCodesBySymbol.Single(x => x.Value == Code).Key;
+ return string.Format("{0}{1:N2}", symbol, Value);
+ }
+
+ public static Currency operator +(Currency x, double amount)
+ {
+ Contract.Requires(x != null);
+ return new Currency(x.Code, x.Value + amount);
+ }
+
+ public static Currency operator -(Currency x, double amount)
+ {
+ Contract.Requires(x != null);
+ return new Currency(x.Code, x.Value - amount);
+ }
+
+ public static bool operator ==(Currency left, Currency right)
+ {
+ return Equals(left, right);
+ }
+
+ public static bool operator !=(Currency left, Currency right)
+ {
+ return !Equals(left, right);
+ }
+
+ public static implicit operator Currency(string currency)
+ {
+ return new Currency(currency);
+ }
+
+ public static implicit operator string(Currency currency)
+ {
+ return currency.ToString();
+ }
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ return ((Code != null ? Code.GetHashCode() : 0)*397) ^ Value.GetHashCode();
+ }
+ }
+ }
+}
View
54 Ebuy.Common/Entities/Entity.cs
@@ -1,10 +1,20 @@
using System;
using System.ComponentModel.DataAnnotations;
+using System.Diagnostics.Contracts;
+using System.Web;
+using Ebuy.DataAnnotations;
namespace Ebuy
{
public interface IEntity
{
+ /// <summary>
+ /// The entity's unique (and URL-safe) public identifier
+ /// </summary>
+ /// <remarks>
+ /// This is the identifier that should be exposed via the web, etc.
+ /// </remarks>
+ string Key { get; }
}
public abstract class Entity<TId> : IEntity, IEquatable<Entity<TId>>
@@ -24,6 +34,19 @@ public virtual TId Id
}
private object _id;
+ [Unique, StringLength(50)]
+ public virtual string Key
+ {
+ get { return _key = _key ?? GenerateKey(); }
+ protected set { _key = value; }
+ }
+ private string _key;
+
+
+ protected virtual string GenerateKey()
+ {
+ return KeyGenerator.Generate();
+ }
public override bool Equals(object obj)
{
@@ -39,12 +62,26 @@ public bool Equals(Entity<TId> other)
if (ReferenceEquals(this, other)) return true;
if (other.GetType() != GetType()) return false;
+ if (default(TId).Equals(Id) || default(TId).Equals(other.Id))
+ return Equals(other._key, _key);
+
return other.Id.Equals(Id);
}
public override int GetHashCode()
{
- return Id.GetHashCode();
+ unchecked
+ {
+ if (default(TId).Equals(Id))
+ return Key.GetHashCode() * 397;
+
+ return Id.GetHashCode();
+ }
+ }
+
+ public override string ToString()
+ {
+ return Key;
}
public static bool operator ==(Entity<TId> left, Entity<TId> right)
@@ -56,5 +93,20 @@ public override int GetHashCode()
{
return !Equals(left, right);
}
+
+
+ public static class KeyGenerator
+ {
+ public static string Generate()
+ {
+ return Generate(Guid.NewGuid().ToString("D").Substring(24));
+ }
+
+ public static string Generate(string input)
+ {
+ Contract.Requires(!string.IsNullOrWhiteSpace(input));
+ return HttpUtility.UrlEncode(input.Replace(" ", "_").Replace("-", "_").Replace("&", "and"));
+ }
+ }
}
}
View
15 Ebuy.Common/Entities/EntityKeyGenerationException.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace Ebuy
+{
+ public class EntityKeyGenerationException : Exception
+ {
+ public Type EntityType { get; set; }
+
+ public EntityKeyGenerationException(Type entityType, string message)
+ : base(message)
+ {
+ EntityType = entityType;
+ }
+ }
+}
View
28 Ebuy.Common/Entities/Payment.cs
@@ -0,0 +1,28 @@
+using System;
+
+namespace Ebuy
+{
+ public class Payment : Entity<Guid>
+ {
+ public Currency Amount { get; private set; }
+
+ public Auction Auction { get; private set; }
+
+ public DateTime Timestamp { get; private set; }
+
+ public User User { get; set; }
+
+
+ public Payment(User user, Auction auction, Currency amount)
+ {
+ User = user;
+ Auction = auction;
+ Amount = amount;
+ Timestamp = DateTime.Now;
+ }
+
+ private Payment()
+ {
+ }
+ }
+}
View
70 Ebuy.Common/Entities/User.cs
@@ -0,0 +1,70 @@
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel.DataAnnotations;
+using System.Diagnostics.Contracts;
+using Ebuy.DataAnnotations;
+
+namespace Ebuy
+{
+ [MetadataType(typeof(User.Metadata))]
+ public class User : Entity<long>
+ {
+ public virtual ICollection<Auction> Selling { get; private set; }
+
+ public virtual string DisplayName
+ {
+ get { return _displayName ?? Username; }
+ set { _displayName = value; }
+ }
+ private string _displayName;
+
+ [Unique]
+ public string EmailAddress { get; set; }
+
+ public virtual ICollection<Payment> Payments { get; private set; }
+
+ [Unique]
+ public string Username { get; set; }
+
+ public virtual ICollection<Auction> WatchedAuctions { get; private set; }
+
+
+ public User()
+ {
+ Payments = new Collection<Payment>();
+ Selling = new Collection<Auction>();
+ WatchedAuctions = new Collection<Auction>();
+ }
+
+
+ protected override string GenerateKey()
+ {
+ if(string.IsNullOrWhiteSpace(Username))
+ throw new EntityKeyGenerationException(GetType(), "Username is empty");
+
+ return KeyGenerator.Generate(Username);
+ }
+
+
+ public void Bid(Auction auction, Currency bidAmount)
+ {
+ Contract.Requires(auction != null);
+ Contract.Requires(bidAmount != null);
+
+ auction.PostBid(this, bidAmount);
+ }
+
+
+ public class Metadata
+ {
+ [Required, StringLength(50)]
+ public object DisplayName;
+
+ [Required, StringLength(100, MinimumLength = 5)]
+ public object EmailAddress;
+
+ [Required, StringLength(100, MinimumLength = 3)]
+ public object Username;
+ }
+ }
+}
View
43 Ebuy.Common/Entities/WebsiteImage.cs
@@ -0,0 +1,43 @@
+using System;
+using System.ComponentModel.DataAnnotations;
+
+namespace Ebuy
+{
+ [MetadataType(typeof(WebsiteImage.Metadata))]
+ public class WebsiteImage : Entity<Guid>
+ {
+ public string ImageUrl { get; set; }
+
+ public string Title { get; set; }
+
+ public string ThumbnailUrl { get; set; }
+
+
+ public WebsiteImage()
+ {
+ }
+
+ public WebsiteImage(string imageUrl)
+ {
+ ImageUrl = imageUrl;
+ }
+
+
+ public static implicit operator WebsiteImage(string imageUrl)
+ {
+ return new WebsiteImage(imageUrl);
+ }
+
+ public class Metadata
+ {
+ [StringLength(2000)]
+ public object ImageUrl;
+
+ [StringLength(2000)]
+ public object ThumbnailUrl;
+
+ [StringLength(2000)]
+ public object Title;
+ }
+ }
+}
View
1  Ebuy.Mvc/Ebuy.Mvc.csproj
@@ -43,6 +43,7 @@
</ItemGroup>
<ItemGroup>
<Compile Include="ActionResults\JsonpActionResult.cs" />
+ <Compile Include="Extensions\CollectionExtensions.cs" />
<Compile Include="Extensions\JsonRequestExtensions.cs" />
<Compile Include="Filters\MultipleResponseFormatsAttribute.cs" />
<Compile Include="ModelBinders\JsonModelBinder.cs" />
View
24 Ebuy.Mvc/Extensions/CollectionExtensions.cs
@@ -0,0 +1,24 @@
+using System.Collections.Generic;
+using System.Diagnostics.Contracts;
+using System.Linq;
+
+namespace Ebuy
+{
+ public static class CollectionExtensions
+ {
+ public static IEnumerable<T> Page<T>(this IEnumerable<T> source, int pageIndex, int pageSize)
+ {
+ Contract.Requires(pageIndex >= 0, "Page index cannot be negative");
+ Contract.Requires(pageSize >= 0, "Page size cannot be negative");
+
+ int skip = pageIndex * pageSize;
+
+ if (skip > 0)
+ source = source.Skip(skip);
+
+ source = source.Take(pageSize);
+
+ return source;
+ }
+ }
+}
View
18 Ebuy.Website/Controllers/AuctionsController.cs
@@ -1,5 +1,4 @@
-using System;
-using System.Linq;
+using System.Linq;
using System.Web.Mvc;
using Ebuy.DataAccess;
@@ -50,21 +49,6 @@ public ActionResult PartialAuction(long id)
return PartialView("Auction", auction);
}
- public ActionResult Details(long id = 0)
- {
- var auction = new Auction
- {
- Id = id,
- Title = "Brand new Widget 2.0",
- Description = "This is a brand new version 2.0 Widget!",
- StartPrice = 1.00m,
- CurrentPrice = 13.40m,
- EndTime = DateTime.Parse("6-23-2012 12:34 PM"),
- };
-
- return View(auction);
- }
-
//
// GET: /Auctions/Create
View
82 Ebuy.Website/Controllers/SearchController.cs
@@ -0,0 +1,82 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Web.Mvc;
+using AutoMapper;
+using Ebuy.DataAccess;
+using Ebuy.Website.Models;
+
+namespace Ebuy.Website.Controllers
+{
+ public class SearchController : Controller
+ {
+ private readonly IRepository _repository;
+
+ public SearchController(IRepository repository)
+ {
+ _repository = repository;
+ }
+
+ public ActionResult Index(SearchCriteria criteria)
+ {
+ IQueryable<Auction> auctions;
+
+ // Filter auctions by keyword
+ if (!string.IsNullOrEmpty(criteria.SearchKeyword))
+ auctions = _repository.Query<Auction>(q => q.Description.Contains(criteria.SearchKeyword));
+ else
+ auctions = _repository.All<Auction>();
+
+ switch (criteria.GetSortByField())
+ {
+ case SearchCriteria.SearchFieldType.Price:
+ auctions = auctions.OrderBy(q => q.CurrentPrice.Value);
+ break;
+
+ case SearchCriteria.SearchFieldType.RemainingTime:
+ auctions = auctions.OrderBy(q => q.EndTime);
+ break;
+
+ case SearchCriteria.SearchFieldType.Keyword:
+ default:
+ auctions = auctions.OrderBy(q => q.Title);
+ break;
+ }
+
+ auctions = PageSearchResult(criteria, auctions);
+
+ // Populate the view model
+ var viewModel = new SearchViewModel();
+
+ // Copy values from the criteria object to the view model
+ Mapper.DynamicMap(criteria, viewModel);
+
+ // Map the matching Auctions to view models
+ viewModel.SearchResult = Mapper.DynamicMap<IEnumerable<AuctionViewModel>>(auctions);
+
+ return View("Search", viewModel);
+ }
+
+ private IQueryable<Auction> PageSearchResult(SearchCriteria criteria, IQueryable<Auction> auctionsData)
+ {
+ IQueryable<Auction> result;
+
+ var numberOfItems = auctionsData.Count();
+
+ if (numberOfItems > criteria.GetPageSize())
+ {
+ var maxNumberOfPages = numberOfItems/criteria.GetPageSize();
+
+ if (criteria.CurrentPage > maxNumberOfPages)
+ criteria.CurrentPage = maxNumberOfPages;
+
+ result = auctionsData.Page(criteria.CurrentPage, criteria.GetPageSize()).AsQueryable();
+ }
+ else
+ {
+ result = auctionsData;
+ }
+
+ return result;
+ }
+ }
+}
View
6 Ebuy.Website/Ebuy.Website.csproj
@@ -14,7 +14,7 @@
<RootNamespace>Ebuy.Website</RootNamespace>
<AssemblyName>Ebuy.Website</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
- <MvcBuildViews>false</MvcBuildViews>
+ <MvcBuildViews>True</MvcBuildViews>
<UseIISExpress>true</UseIISExpress>
<IISExpressSSLPort />
<IISExpressAnonymousAuthentication />
@@ -183,11 +183,14 @@
<Compile Include="Controllers\AccountController.cs" />
<Compile Include="Controllers\AuctionsController.cs" />
<Compile Include="Controllers\HomeController.cs" />
+ <Compile Include="Controllers\SearchController.cs" />
+ <Compile Include="Models\SearchViewModel.cs" />
<Compile Include="Filters\InitializeSimpleMembershipAttribute.cs" />
<Compile Include="Global.asax.cs">
<DependentUpon>Global.asax</DependentUpon>
</Compile>
<Compile Include="Models\AccountModels.cs" />
+ <Compile Include="Models\SearchCriteria.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
@@ -306,6 +309,7 @@
<Content Include="Views\Auctions\Create.cshtml" />
<Content Include="Views\Auctions\Auction.cshtml" />
<Content Include="Views\Auctions\Auctions.cshtml" />
+ <Content Include="Views\Search\Search.cshtml" />
</ItemGroup>
<ItemGroup>
<Folder Include="App_Data\" />
View
45 Ebuy.Website/Models/SearchCriteria.cs
@@ -0,0 +1,45 @@
+namespace Ebuy.Website.Models
+{
+ public class SearchCriteria
+ {
+ public enum SearchFieldType
+ {
+ Keyword,
+ Price,
+ RemainingTime
+ }
+
+ public string SearchKeyword { get; set; }
+ public string SortByField { get; set; }
+ public string PagingSize { get; set; }
+ public int CurrentPage { get; set; }
+
+ public int GetPageSize()
+ {
+ int result = 5;
+ if (!string.IsNullOrEmpty(this.PagingSize))
+ {
+ int.TryParse(this.PagingSize, out result);
+ }
+ return result;
+ }
+
+ public SearchFieldType GetSortByField()
+ {
+ SearchFieldType result;
+ switch (SortByField)
+ {
+ case "Price":
+ result = SearchFieldType.Price;
+ break;
+ case "Remaining Time":
+ result = SearchFieldType.RemainingTime;
+ break;
+ default:
+ result = SearchFieldType.Keyword;
+ break;
+ }
+ return result;
+ }
+ }
+}
View
35 Ebuy.Website/Models/SearchViewModel.cs
@@ -0,0 +1,35 @@
+using System.Collections.Generic;
+
+namespace Ebuy.Website.Models
+{
+ public class SearchViewModel
+ {
+ public string SearchKeyword { get; set; }
+
+ public int CurrentPage { get; set; }
+ public int MaxPages { get; set; }
+
+ public int PagingSize { get; set; }
+ public IEnumerable<int> PagingSizeList { get; set; }
+
+ public string SortByField { get; set; }
+ public IEnumerable<string> SortByFieldList { get; set; }
+
+ public IEnumerable<AuctionViewModel> SearchResult { get; set; }
+ }
+
+ public class AuctionViewModel
+ {
+ public string Image { get; set; }
+
+ public long Id { get; set; }
+
+ public string Title { get; set; }
+
+ public string Description { get; set; }
+
+ public string CurrentPrice { get; set; }
+
+ public string RemainingTimeDisplay { get; set; }
+ }
+}
View
90 Ebuy.Website/Views/Search/Search.cshtml
@@ -0,0 +1,90 @@
+@model SearchViewModel
+@{
+ ViewBag.Title = "Search";
+}
+
+@section scripts
+{
+<script type="text/javascript">
+ $(function () {
+ $("#SortByField").change(function () {
+ $("#CurrentPage").val(0);
+ SubmitForm();
+ });
+ $("#PagingSize").change(function () {
+ $("#CurrentPage").val(0);
+ SubmitForm();
+ });
+ $("#Previous").click(function () {
+ var currentPage = $("#CurrentPage").val();
+ if (currentPage != null && currentPage > 0) {
+ currentPage--;
+ $("#CurrentPage").val(currentPage);
+ }
+ SubmitForm();
+ });
+ $("#Next").click(function () {
+ var currentPage = $("#CurrentPage").val();
+ if (currentPage) {
+ currentPage++;
+ $("#CurrentPage").val(currentPage);
+ }
+ SubmitForm();
+ });
+ });
+ function SubmitForm() {
+ document.forms["SearchForm"].submit();
+ }
+</script>
+}
+
+@using (Html.BeginForm("Index", "Search", FormMethod.Post, new { id = "SearchForm" }))
+{
+ <div class="SearchKeyword">
+ @Html.TextBoxFor(m => m.SearchKeyword, new {@class="SearchBox"})
+ <input id="Search" type="submit" value=" " class="SearchButton" />
+ </div>
+ <h2>Search Result</h2>
+ <div>
+ <div class="SearchHeader">
+ @Html.Hidden("CurrentPage", @Model.CurrentPage)
+ <div class="PagingContainer">
+ <span class="CurrentPage">Page @Model.CurrentPage of @Model.MaxPages</span>
+ <img id="Previous" src="@Url.Content("~/Content/Images/PagingPrevious.png")" class="PagingButton" />
+ <img id="Next" src="@Url.Content("~/Content/Images/PagingNext.png")" class="PagingButton" />
+ <div class="PageSize">
+ @Html.DropDownListFor(m => m.PagingSize, new SelectList(Model.PagingSizeList))
+ </div>
+ </div>
+ <div class="SortingContainer">
+ <span>Sort By:</span>
+ @Html.DropDownListFor(m => m.SortByField, new SelectList(Model.SortByFieldList))
+ </div>
+ </div>
+ <div class="SearchResultContainer">
+ <table>
+ @foreach (var item in @Model.SearchResult)
+ {
+ var auctionUrl = Url.Action("Auction", "Auctions", new { id = item.Id });
+ <tr>
+ <td class="searchDescription">
+ <div class="fieldContainer">
+ <a href="@auctionUrl">
+ <img src="@item.Image" title="@item.Title"/>
+ </a>
+ </div>
+ <div class="fieldContainer">
+ <div class="fieldTitle">@item.Title</div>
+ <div class="fieldDescription">
+ @item.Description
+ </div>
+ </div>
+ </td>
+ <td class="centered-field">@item.CurrentPrice</td>
+ <td class="centered-field">@item.RemainingTimeDisplay</td>
+ </tr>
+ }
+ </table>
+ </div>
+ </div>
+}
View
1  Ebuy.Website/Views/Web.config
@@ -17,6 +17,7 @@
<add namespace="System.Web.Mvc.Html" />
<add namespace="System.Web.Optimization"/>
<add namespace="System.Web.Routing" />
+ <add namespace="Ebuy.Website.Models" />
</namespaces>
</pages>
</system.web.webPages.razor>
Please sign in to comment.
Something went wrong with that request. Please try again.