diff --git a/SportsStore.Domain/Abstract/IOrderProcessor.cs b/SportsStore.Domain/Abstract/IOrderProcessor.cs new file mode 100644 index 0000000..07c8cd2 --- /dev/null +++ b/SportsStore.Domain/Abstract/IOrderProcessor.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using SportsStore.Domain.Entities; + +namespace SportsStore.Domain.Abstract +{ + public interface IOrderProcessor + { + void ProcessOrder(Cart cart, ShippingDetails shippingDetails); + } +} diff --git a/SportsStore.Domain/Concrete/EmailOrderProcessor.cs b/SportsStore.Domain/Concrete/EmailOrderProcessor.cs new file mode 100644 index 0000000..a0ddaf6 --- /dev/null +++ b/SportsStore.Domain/Concrete/EmailOrderProcessor.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using System.Net; +using System.Net.Mail; +using System.Text; +using SportsStore.Domain.Abstract; +using SportsStore.Domain.Entities; + + +namespace SportsStore.Domain.Concrete +{ + public class EmailSettings + { + public string MailToAddress = "order@example.com"; + public string MailFromAddress = "sportsstore@example.com"; + public bool UseSsl = true; + public string Username = "MySmtpUsername"; + public string Password = "MySmtpPassword"; + public string ServerName = "smtp.example.com"; + public int ServerPort = 587; + public bool WriteAsFile = false; + public string FileLocation = @"c:\sports_store_emails"; + } + + public class EmailOrderProcessor : IOrderProcessor + { + private EmailSettings emailSettings; + + public EmailOrderProcessor(EmailSettings settings) + { + emailSettings = settings; + } + + public void ProcessOrder(Cart cart, ShippingDetails shipingInfo) + { + using (var smtpClient = new SmtpClient()) + { + smtpClient.EnableSsl = emailSettings.UseSsl; + smtpClient.Host = emailSettings.ServerName; + smtpClient.Port = emailSettings.ServerPort; + smtpClient.UseDefaultCredentials = false; + smtpClient.Credentials = new NetworkCredential(emailSettings.Username, emailSettings.Password); + + if (emailSettings.WriteAsFile) + { + smtpClient.DeliveryMethod = SmtpDeliveryMethod.SpecifiedPickupDirectory; + smtpClient.PickupDirectoryLocation = emailSettings.FileLocation; + smtpClient.EnableSsl = false; + } + + StringBuilder body = new StringBuilder() + .AppendLine("A new order has been submitted") + .AppendLine("---") + .AppendLine("Items:"); + + foreach (var line in cart.Lines) + { + var subtotal = line.Product.Price * line.Quantity; + body.AppendFormat("{0} x {1} (subtotal: {2:c}", line.Quantity, line.Product.Name, subtotal); + } + + body.AppendFormat("Total order value: {0:c}", cart.ComputeTotalValue()) + .AppendLine("---") + .AppendLine("Ship to:") + .AppendLine(shipingInfo.Name) + .AppendLine(shipingInfo.Line1) + .AppendLine(shipingInfo.Line2 ?? "") + .AppendLine(shipingInfo.Line3 ?? "") + .AppendLine(shipingInfo.City) + .AppendLine(shipingInfo.State ?? "") + .AppendLine(shipingInfo.Country) + .AppendLine(shipingInfo.Zip) + .AppendLine("---") + .AppendFormat("Gift wrap: {0}", shipingInfo.GiftWrap ? "Yes" : "No"); + + MailMessage mailMessage = new MailMessage( + emailSettings.MailFromAddress, + emailSettings.MailToAddress, + "New order submitted", + body.ToString()); + + if (emailSettings.WriteAsFile) + { + mailMessage.BodyEncoding = Encoding.ASCII; + } + + smtpClient.Send(mailMessage); + } + } + } + +} \ No newline at end of file diff --git a/SportsStore.Domain/Entities/ShippingDetails.cs b/SportsStore.Domain/Entities/ShippingDetails.cs new file mode 100644 index 0000000..a055980 --- /dev/null +++ b/SportsStore.Domain/Entities/ShippingDetails.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using System.ComponentModel.DataAnnotations; + +namespace SportsStore.Domain.Entities +{ + public class ShippingDetails + { + [Required(ErrorMessage = "Please enter a name")] + public string Name { get; set; } + + [Required(ErrorMessage = "Please enter the first address line")] + [Display(Name = "Line 1")] + public string Line1 { get; set; } + [Display(Name = "Line 2")] + public string Line2 { get; set; } + [Display(Name = "Line 3")] + public string Line3 { get; set; } + + [Required(ErrorMessage = "Please enter a city name")] + public string City { get; set; } + + [Required(ErrorMessage = "Please enter a state name")] + public string State { get; set; } + + public string Zip { get; set; } + + [Required(ErrorMessage = "Please enter a country name")] + public string Country { get; set; } + + public bool GiftWrap { get; set; } + } +} \ No newline at end of file diff --git a/SportsStore.Domain/SportsStore.Domain.csproj b/SportsStore.Domain/SportsStore.Domain.csproj index fba237c..6fb6bbd 100644 --- a/SportsStore.Domain/SportsStore.Domain.csproj +++ b/SportsStore.Domain/SportsStore.Domain.csproj @@ -105,11 +105,14 @@ + + + diff --git a/SportsStore.WebUI/Content/ErrorStyles.css b/SportsStore.WebUI/Content/ErrorStyles.css new file mode 100644 index 0000000..4a2262c --- /dev/null +++ b/SportsStore.WebUI/Content/ErrorStyles.css @@ -0,0 +1,5 @@ +.field-validation-error { color: #f00;} +.field-validation-valid { display: none;} +.input-validation-error {border: 1px solid #f00; background-color: #fee;} +.vlaidation-summary-errors {font-weight: bold; color: #f00;} +.validation-summary-valid {display: none;} \ No newline at end of file diff --git a/SportsStore.WebUI/Controllers/CartController.cs b/SportsStore.WebUI/Controllers/CartController.cs index 2db7a20..15ed0a4 100644 --- a/SportsStore.WebUI/Controllers/CartController.cs +++ b/SportsStore.WebUI/Controllers/CartController.cs @@ -12,54 +12,87 @@ namespace SportsStore.WebUI.Controllers public class CartController : Controller { private IProductRepository repository; + private IOrderProcessor orderProcessor; - public CartController(IProductRepository repo) + public CartController(IProductRepository repo, IOrderProcessor proc) { repository = repo; + orderProcessor = proc; } - public ViewResult Index(string returnUrl) + public ViewResult Index(Cart cart, string returnUrl) { - return View(new CartIndexViewModel { Cart = GetCart(), ReturnUrl = returnUrl }); + return View(new CartIndexViewModel { ReturnUrl = returnUrl, Cart = cart }); } - public RedirectToRouteResult AddToCart(int productId, string returnUrl) + public RedirectToRouteResult AddToCart(Cart cart, int productId, string returnUrl) { Product product = repository.Products .FirstOrDefault(p => p.ProductID == productId); if (product != null) { - GetCart().AddItem(product, 1); + //GetCart().AddItem(product, 1); + cart.AddItem(product, 1); } return RedirectToAction("Index", new { returnUrl }); } - public RedirectToRouteResult RemoveFromCart(int productId, string returnUrl) + public RedirectToRouteResult RemoveFromCart(Cart cart, int productId, string returnUrl) { Product product = repository.Products .FirstOrDefault(p => p.ProductID == productId); if (product != null) { - GetCart().RemoveLine(product); + //GetCart().RemoveLine(product); + cart.RemoveLine(product); } return RedirectToAction("Index", new { returnUrl }); } - - private Cart GetCart() + //private Cart GetCart() + //{ + // Cart cart = (Cart)Session["Cart"]; + // if (cart == null) + // { + // cart = new Cart(); + // Session["Cart"] = cart; + // } + + // return cart; + //} + + public PartialViewResult Summary(Cart cart) { - Cart cart = (Cart)Session["Cart"]; - if (cart == null) + return PartialView(cart); + } + + public ViewResult Checkout() + { + return View(new ShippingDetails()); + } + + [HttpPost] + public ViewResult Checkout(Cart cart, ShippingDetails shippingDetails) + { + if (cart.Lines.Count() == 0) { - cart = new Cart(); - Session["Cart"] = cart; + ModelState.AddModelError("", "Sorry, your cart is empty!"); } - return cart; + if (ModelState.IsValid) + { + orderProcessor.ProcessOrder(cart, shippingDetails); + cart.Clear(); + return View("Completed"); + } + else + { + return View(shippingDetails); + } } } diff --git a/SportsStore.WebUI/Global.asax.cs b/SportsStore.WebUI/Global.asax.cs index e1f863d..5cf2b7f 100644 --- a/SportsStore.WebUI/Global.asax.cs +++ b/SportsStore.WebUI/Global.asax.cs @@ -4,6 +4,8 @@ using System.Linq; using System.Web; using System.Web.Mvc; using System.Web.Routing; +using SportsStore.Domain.Entities; +using SportsStore.WebUI.Infrastructure.Binders; namespace SportsStore.WebUI { @@ -13,6 +15,8 @@ namespace SportsStore.WebUI { AreaRegistration.RegisterAllAreas(); RouteConfig.RegisterRoutes(RouteTable.Routes); + + ModelBinders.Binders.Add(typeof(Cart), new CartModelBinder()); } } } diff --git a/SportsStore.WebUI/Infrastructure/Binders/CartModelBinder.cs b/SportsStore.WebUI/Infrastructure/Binders/CartModelBinder.cs new file mode 100644 index 0000000..d021666 --- /dev/null +++ b/SportsStore.WebUI/Infrastructure/Binders/CartModelBinder.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using System.Web.Mvc; +using SportsStore.Domain.Entities; + +namespace SportsStore.WebUI.Infrastructure.Binders +{ + public class CartModelBinder : IModelBinder + { + private const string sessionKey = "Cart"; + + public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) + { + Cart cart = null; + if (controllerContext.HttpContext.Session != null) + { + cart = (Cart)controllerContext.HttpContext.Session[sessionKey]; + } + + if (cart == null) + { + cart = new Cart(); + if (controllerContext.HttpContext.Session != null) + { + controllerContext.HttpContext.Session[sessionKey] = cart; + } + } + + return cart; + } + } +} \ No newline at end of file diff --git a/SportsStore.WebUI/Infrastructure/NinjectDependencyResolver.cs b/SportsStore.WebUI/Infrastructure/NinjectDependencyResolver.cs index 9e86b1e..cfa17aa 100644 --- a/SportsStore.WebUI/Infrastructure/NinjectDependencyResolver.cs +++ b/SportsStore.WebUI/Infrastructure/NinjectDependencyResolver.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Configuration; using System.Linq; using System.Web; using System.Web.Mvc; @@ -43,6 +44,13 @@ namespace SportsStore.WebUI.Infrastructure kernel.Bind().To(); + EmailSettings emailSettings = new EmailSettings + { + WriteAsFile = bool.Parse(ConfigurationManager.AppSettings["Email.WriteAsFile"] ?? "false") + }; + + kernel.Bind().To().WithConstructorArgument("settings", emailSettings); + } } diff --git a/SportsStore.WebUI/SportsStore.WebUI.csproj b/SportsStore.WebUI/SportsStore.WebUI.csproj index 9dba5a6..8f64b70 100644 --- a/SportsStore.WebUI/SportsStore.WebUI.csproj +++ b/SportsStore.WebUI/SportsStore.WebUI.csproj @@ -113,6 +113,7 @@ + @@ -142,6 +143,7 @@ Global.asax + @@ -158,6 +160,9 @@ + + + Web.config diff --git a/SportsStore.WebUI/Views/Cart/Checkout.cshtml b/SportsStore.WebUI/Views/Cart/Checkout.cshtml new file mode 100644 index 0000000..f4b0e0a --- /dev/null +++ b/SportsStore.WebUI/Views/Cart/Checkout.cshtml @@ -0,0 +1,74 @@ +@model SportsStore.Domain.Entities.ShippingDetails + +@{ + ViewBag.Title = "SportStore Checkout"; +} + +

Checkout

+

Please enter your details, and we'll ship your goods right away!

+ +@using (Html.BeginForm()) +{ + @Html.ValidationSummary() + +

Ship to

+
+ + @Html.TextBoxFor(x => x.Name, new { @class = "form-control" } ) +
+ +

Address

+ + foreach (var property in ViewData.ModelMetadata.Properties) + { + if (property.PropertyName != "Name" && property.PropertyName != "GiftWrap") + { +
+ + @Html.TextBox(property.PropertyName, null, new { @class = "form-control" }) +
+ } + } + + @*
+ + @Html.TextBoxFor(x => x.Line1, new { @class = "form-control" }) +
+
+ + @Html.TextBoxFor(x => x.Line2, new { @class = "form-control" }) +
+
+ + @Html.TextBoxFor(x => x.Line3, new { @class = "form-control" }) +
+
+ + @Html.TextBoxFor(x => x.City, new { @class = "form-control" }) +
+
+ + @Html.TextBoxFor(x => x.State, new { @class = "form-control" }) +
+
+ + @Html.TextBoxFor(x => x.Zip, new { @class = "form-control" }) +
+
+ + @Html.TextBoxFor(x => x.Country, new { @class = "form-control" }) +
*@ + +

Options

+
+ +
+ +
+ +
+} + diff --git a/SportsStore.WebUI/Views/Cart/Completed.cshtml b/SportsStore.WebUI/Views/Cart/Completed.cshtml new file mode 100644 index 0000000..1e18cce --- /dev/null +++ b/SportsStore.WebUI/Views/Cart/Completed.cshtml @@ -0,0 +1,8 @@ + +@{ + ViewBag.Title = "SportsStore: Order Submitted"; +} + +

Thanks!

+Thanks for placing your order. We'll ship your goods as soon as possible. + diff --git a/SportsStore.WebUI/Views/Cart/Index.cshtml b/SportsStore.WebUI/Views/Cart/Index.cshtml index b2233d5..05b8ae0 100644 --- a/SportsStore.WebUI/Views/Cart/Index.cshtml +++ b/SportsStore.WebUI/Views/Cart/Index.cshtml @@ -3,8 +3,12 @@ ViewBag.Title = "Sports Store: Your Cart"; } + +

Your cart

- +
@@ -23,6 +27,14 @@ + } @@ -38,5 +50,6 @@
Quantity @((line.Quantity * line.Product.Price).ToString("c")) + @using (Html.BeginForm("RemoveFromCart", "cart")) + { + @Html.Hidden("ProductId", line.Product.ProductID) + @Html.HiddenFor(x => x.ReturnUrl) + + } +
Continue Shopping + @Html.ActionLink("Checkout now", "Checkout", null, new { @class = "btn btn-primary" })
diff --git a/SportsStore.WebUI/Views/Cart/Summary.cshtml b/SportsStore.WebUI/Views/Cart/Summary.cshtml new file mode 100644 index 0000000..6b3605f --- /dev/null +++ b/SportsStore.WebUI/Views/Cart/Summary.cshtml @@ -0,0 +1,15 @@ +@model SportsStore.Domain.Entities.Cart + + + + \ No newline at end of file diff --git a/SportsStore.WebUI/Views/Shared/_Layout.cshtml b/SportsStore.WebUI/Views/Shared/_Layout.cshtml index 740511c..0fc12ac 100644 --- a/SportsStore.WebUI/Views/Shared/_Layout.cshtml +++ b/SportsStore.WebUI/Views/Shared/_Layout.cshtml @@ -5,11 +5,13 @@ + @ViewBag.Title
diff --git a/SportsStore.WebUI/Web.config b/SportsStore.WebUI/Web.config index 743df41..b4712a1 100644 --- a/SportsStore.WebUI/Web.config +++ b/SportsStore.WebUI/Web.config @@ -16,6 +16,7 @@ +