Sunday, December 18, 2011

Drag & Drop Shopping Cart Using ASP.NET MVC and JQuery

In most E-commerce web applications, there are Add to Cart features provided using UI like button or link for add product into a cart. But a few days ago, I came across one good JQuery Shopping Cart Plug-in Example. It provides a feature like you can drag your product and drop into your shopping cart.

After seeing that example, I realized that it can be used to add more interactivity into Add to cart features of E-commerce Web Application. Then I decided to use that plug-in with ASP.NET MVC.

To keep this application simple, I have used just one table in database for display product List and am not covering any other feature like Add Products, etc.

Requirements

To implement this, there is a requirement of two JQuery files:

  1. JQuery-1.6.2
  2. JQuery-ui-1.8.11

You can download this file from Jquery.com or in case you are using VS2010 and MVC3 or MVC4 applications, then that jQuery File is already available in the script folder.

The first step is to create a new MVC application in Visual Studio and check whether in script folder, both Jquery files are available or not. If not, then download that file and add files into the Script folder.

Now, create a database and add the below table named Products:

After creating products table, insert records into tables for display products into the list page. For product image, store image name only in table and create one folder named images into the root level of project, then copy all images into that folder. After that, add connection string of database into webconfig file.

Now, for retrieving data from the database, I have creating one model class named Products into the Models Folder. Product model class code will look like below:

public class Products
{
        public int ProductID ;
        public string ProductName;
        public decimal Price;
        public string ProductImage;
}

Now, create one new folder in the application root level named Data Access and one class into that folder named DALProducts. This class is used for the Direct Database Operation. Code of that class will look like below:

 public class DALProducts
    {
        public List GetProductList()
        {
            SqlConnection _con = new SqlConnection(ConfigurationManager.ConnectionStrings
                ["ProductDataBaseConnection"].ConnectionString);
            try
            {
                if (_con.State != System.Data.ConnectionState.Open)
                    _con.Open();
                SqlCommand _cmd = _con.CreateCommand();
                _cmd.CommandText = "Select * From Products";
                SqlDataReader _reader = _cmd.ExecuteReader();
                List _lstPoducts = new List();
                while (_reader.Read())
                {
                    Products _Products = new Products();
                    _Products.ProductID =Convert.ToInt32(_reader["ProductID"]);
                    _Products.ProductImage = _reader["ProductImage"].ToString();
                    _Products.ProductName = _reader["ProductName"].ToString();
                    _Products.Price = Convert.ToDecimal(_reader["Price"]);
                    _lstPoducts.Add(_Products);
                }
                return _lstPoducts;
            }
            catch (Exception ex)
            {
                throw ex;
            }
            finally
            {
                if (_con.State != System.Data.ConnectionState.Closed)
                    _con.Close();
            }
        }
    }

In the above code, GetProductList method returns a list of the products.

Add one controller into Controller folder named ProductController and add the below code into that:

public class ProductController : Controller
 {
       //
       // GET: /Product/
       public ActionResult Index()
       {
           DataAccess.DALProducts _obj = new DataAccess.DALProducts();
           return View(_obj.GetProductList());
       }
 }

Here, in Index action of the controller, call GetproductList method of DALProduct class and pass list of the products into the view.

Add View name index.aspx at View/Product location and add the following markup into that file:

<%@ Page Language="C#" Inherits="
System.Web.Mvc.ViewPage<IEnumerable<EcommerceApplication.Models.Products>>" %>
      <title>Index</title>
    <script type="text/javascript" src="../../Scripts/jquery-1.6.2.min.js"> </script>
    <script type="text/javascript" src="../../Scripts/jquery-ui-1.8.11.min.js"> </script>
    <style type="text/css">
        body
        {
            font-family: Arial, "Free Sans";
            margin: 0;
            padding: 0;
        }
        #main
        {
            background: #0099cc;
            margin-top: 0;
            padding: 2px 0 4px 0;
            text-align: center;
        }
        #main a
        {
            color: #ffffff;
            text-decoration: none;
            font-size: 12px;
            font-weight: bold;
            font-family: Arial;
        }
        #main a:hover
        {
            text-decoration: underline;
        }
        #item_container
        {
            width: 610px;
            margin: 0 auto;
            margin-top: 10px;
            margin-bottom: 10px;
        }
        .item
        {
            background: #fff;
            float: left;
            padding: 5px;
            margin: 5px;
            cursor: move;
            -webkit-box-shadow: 0 1px 2px rgba(0,0,0,.5);
            -moz-box-shadow: 0 1px 2px rgba(0,0,0,.5);
            box-shadow: 0 1px 2px rgba(0,0,0,.5);
            -webkit-border-radius: .5em;
            -moz-border-radius: .5em;
            border-radius: .5em;
            z-index: 5;
        }
        .title, .price
        {
            display: block;
            text-align: center;
            font-size: 14px;
            letter-spacing: -1px;
            font-weight: bold;
            cursor: move;
        }
        .title
        {
            color: #333;
        }
        .price
        {
            color: #0099cc;
            margin-top: 5px;
            -webkit-border-radius: .5em;
            -moz-border-radius: .5em;
            border-radius: .5em;
        }
        .icart, .icart label
        {
            cursor: e-resize;
        }
        .divrm
        {
            text-align: right;
        }
        .remove
        {
            text-decoration: none;
            cursor: pointer;
            font-weight: bold;
            font-size: 20px;
            position: relative;
            top: -7px;
        }
        .remove:hover
        {
            color: #999;
        }
        .clear
        {
            clear: both;
        }
        #cart_container
        {
            margin: 0 auto;
            width: 495px;
        }
        #cart_title span
        {
            border: 8px solid #666;
            border-bottom-width: 0;
            background: #333;
            display: block;
            float: left;
            color: #fff;
            font-size: 11px;
            font-weight: bold;
            padding: 5px 10px;
            -webkit-border-radius: .5em .5em 0 0;
            -moz-border-radius: .5em .5em 0 0;
            border-radius: .5em .5em 0 0;
        }
        #cart_toolbar
        {
            overflow: hidden;
            border: 8px solid #666;
            height: 190px;
            z-index: -2;
            width: 483px;
            -webkit-border-radius: 0 .5em 0 0;
            -moz-border-radius: 0 .5em 0 0;
            border-radius: 0 .5em 0 0;
            background: #ffffff;
        }
        #cart_items
        {
            height: 190px;
            width: 483px;
            position: relative;
            padding: 0 0 0 2px;
            z-index: 0;
            cursor: e-resize;
            border-width: 0 2px;
        }
        .back
        {
            background: #e9e9e9;
        }
        #navigate
        {
            width: 463px;
            margin: 0 auto;
            border: 8px solid #666;
            border-top-width: 0;
            -webkit-border-radius: 0 0 .5em .5em;
            -moz-border-radius: 0 0 .5em .5em;
            border-radius: 0 0 .5em .5em;
            padding: 10px;
            font-size: 14px;
            background: #333;
            font-weight: bold;
        }
        #nav_left
        {
            float: left;
        }
        #nav_left a
        {
            padding: 4px 8px;
            background: #fff;
            -webkit-border-radius: .5em;
            -moz-border-radius: .5em;
            border-radius: .5em;
            text-decoration: none;
            color: #0099cc;
        }
        #nav_left a:hover
        {
            background: #666;
            color: #fff;
        }
        #nav_right
        {
            float: right;
        }
        .sptext
        {
            background: #ffffff;
            padding: 4px 8px;
            margin-left: 8px;
            -webkit-border-radius: .5em;
            -moz-border-radius: .5em;
            border-radius: .5em;
            color: #666;
        }
        .count
        {
            color: #0099cc;
        }
        .drop-active
        {
            background: #ffff99;
        }
        .drop-hover
        {
            background: #ffff66;
        }
    </style>
    <script type="text/javascript">
        var total_items = 0;
        var total_price = 0;
        $(document).ready(function () {

            $(".item").draggable({
                revert: true
            });
            $("#cart_items").draggable({
                axis: "x"
            });

            $("#cart_items").droppable({
                accept: ".item",
                activeClass: "drop-active",
                hoverClass: "drop-hover",
                drop: function (event, ui) {
                    var item = ui.draggable.html();
                    var itemid = ui.draggable.attr("id");
                    var html = '<div class="item icart">';
                    html = html + '<div class="divrm">';
                    html = html + '<a onclick="remove(this)" 
                class="remove ' + itemid + '">×</a>';
                    html = html + '<div/>' + item + '</div>';
                    $("#cart_items").append(html);

                    // update total items
                    total_items++;
                    $("#citem").html(total_items);

                    // update total price
                    var price = parseInt(ui.draggable.find
                (".price").html().replace("$ ", ""));
                    total_price = total_price + price;
                    $("#cprice").html("$ " + total_price);

                    // expand cart items
                    if (total_items > 4) {
                        $("#cart_items").animate({ width: "+=120" }, 'slow');
                    }
                }
            });

            $("#btn_next").click(function () {
                $("#cart_items").animate({ left: "-=100" }, 100);
                return false;
            });
            $("#btn_prev").click(function () {
                $("#cart_items").animate({ left: "+=100" }, 100);
                return false;
            });
            $("#btn_clear").click(function () {
                $("#cart_items").fadeOut("2000", function () {
                    $(this).html("").fadeIn("fast").css({ left: 0 });
                });
                $("#citem").html("0");
                $("#cprice").html("$ 0");
                total_items = 0;
                total_price = 0;
                return false;
            });
        });
        function remove(el) {
            $(el).hide();
            $(el).parent().parent().effect("highlight", { color: "#ff0000" }, 1000);
            $(el).parent().parent().fadeOut('1000');
            setTimeout(function () {
                $(el).parent().parent().remove();
                // collapse cart items
                if (total_items > 3) {
                    $("#cart_items").animate({ width: "-=120" }, 'slow');
                }
            }, 1100);

            // update total item
            total_items--;
            $("#citem").html(total_items);

            // update totl price
            var price = parseInt($(el).parent().parent().
            find(".price").html().replace("$ ", ""));
            total_price = total_price - price;
            $("#cprice").html("$ " + total_price);
        }
       
    <div id="item_container">
     <% foreach (var item in Model)
           { %>
          <div id="i<%:item.ProductID %>" class="item">
              <img src="%3C%:%20Url.Content%28" />"/>
              <label class="title"><%:item.ProductName%></label>
              <label class="price">$ <%:item.Price %></label>
          </div>
         <% } %>
          <div class="clear"> </div>
      </div>
    <div id="cart_container">
          <div id="cart_title">
              <span>Shopping Cart </span>
              <div class="clear"></div>
          </div>
          <div id="cart_toolbar">
              <div class="back" id="cart_items"></div>
          </div>
          <div id="navigate">
              <div id="nav_left">
                  <a id="btn_prev"><</a>
                  <a id="btn_next">></a>
                  <a id="btn_clear">Clear Cart</a>
              </div>
              <div id="nav_right">
                  <span class="sptext">
                      <label>Items </label><label id="citem" class="count">0</label>
                  </span>
                  <span class="sptext">
                      <label>Price </label><label id="cprice" class="count">$ 0</label>
                  </span>
              </div>
              <div class="clear"></div>
          </div>
      </div>
</body>
</html>

In the above markup, to generate products list we iterate over product models. It will display product name, images and price of that products.

Now run the application and type URL into the browser like http://localhost/Product/. You will get a screen similar to screen shot and drag any product from the product list and add into the cart. You will notice that when you add products into cart, it will automatically add their price into the total amount and number of the items. You can navigate into the Cart and also clear the whole cart using clear Cart button. You can remove a single product using click on the close image on the product images. When you remove single product at that time, it will deduct that price from the Total Amount and decrease one product into total number of the products.

I've been using this plug-in to provide more interactivity to the end user for E-Commerce application. Hopefully, you'll find this as useful as I have found it for my code.

Reference :

http://superdit.com/2011/04/02/drag-drop-shopping-cart-using-jquery/

Tuesday, December 13, 2011

Custom Routes for MVC Application

When you request any page into MVC Application, it will go through the Routing Architecture. Your Routing system will decide your URL pattern. The default routing algorithm is like {controller}/ {action}/ {id} patterns. But it can be possible to change that pattern using Custom Routes.

In this article, we will show how to Create Custom Routes, Debugging Routes and Router Constraints.

Using Default Route

You can configure Route into the Global.aspx's Application Start Event. When you are creating an MVC application, it will already define Default route. Default route can be configured using the following code:

routes.MapRoute(
“Default”,  //Route Name
 “{controller}/{action}/{id}” //Url With Parameters
, new { controller = “Home”, action = “Index”, id = ““} // Parameter Defaults
);

Custom Routes

It is possible to develop an MVC application without creating a Custom Route. But sometimes, there is a situation to change the pattern of URL in which it makes sense to create Custom Routes.

Now, we take a simple example of the custom routes. Imagine that you want to create a blog application and you want to route a request look like: /Archive/12-25-2010.

The first step is to create an empty MVC application. And Add Controller File named BlogController.cs and Add Actions named Archive and insert that code will look like below:

public class BlogController : Controller
    {
        //
        // GET: /Blog/

        public string Archive(DateTime? entryDate)
        {
            return "You wants Blog Entry on Date:=" + entryDate.ToString();
        }


        public ActionResult Insert()
        {
            ViewData["Message"] = "Call From the Insert Action When Get Http Occurs";
            return View();
        }
    }

Now go the Global.asax file and add the following code into the Register Routes method.

//Simple Custom Route with out any Constraints
routes.MapRoute(
                "BlogWithoutConstraint",
                "Archive/{entrydate}",
                new { Controller = "Blog", action = "Archive", });

This code used to add Custom routes and it will match the URL like, Archive/12-25-2010.

Route Constraints

When you create a Custom Route, you can also include route constraints. These constraints are used to restrict the requests to match routes. There are three basic types of constraints:

  1. Regular Expression Constraints
  2. HttpMethod Constraints
  3. CatchAll Values

Regular Expression Constraints

You can use regular expression constraints to check the pattern of the Route and prevent invalid requests. Regular expression is used to check any pattern like Currency, Date, time, etc.

In the Above BlogArchive Example, Url likes Archive/12-25-2010 and Archive/mybirthday.

It will allow. Now we have to prevent parameter which is not a date. So it is possible using regular expression constraints. For that, add the following code into Global.aspx file Register Route method.

//Custom Route With Regular Expression Constraints
            routes.MapRoute(
                "Blog",
                "Archive/{entrydate}",
                new { Controller = "Blog", action = "Archive" }, 
        new { entryDate = @"\d{2}-\d{2}-\d{4}" });

The above code matches the entry date must match pattern of two decimals followed by dash followed by two decimals followed by dash followed by four decimals.

HTTP Method Constraints

You can match route with any type of the HTTP operation like POST, GET, etc. you want to prevent user to access particular URL when GET operation occurs but not POST operation occurs. You can also used AcceptVerbs instead of HttpMethod constraints.

For example, BlogArchive example inserts Action only executes when HTTP GET Operation performs. For that, you have to add the following code into Global.asax file Register Route method.

//Custom Route With HttpMethod Constraint
            routes.MapRoute(
                "HttpMethodConstraintRoute",
                "Blog/Insert",
                new { Controller = "Blog", action = "Insert" }, 
        new { method = new HttpMethodConstraint("GET") });

Catch All Routes

Generally, your URL must match with number of segments, but you want to match your URL, regardless of the number of the segments for that, we have to create Catch-All Parameter. Now take a simple example of the Catch All Routes for that add one controller named CatchAllController and add the following code into the controller.

public class CatchAllController : Controller
{
        //
        // GET: /Sort/
       public string Index(string AllValues)
       {
          string[] PassedValue = AllValues.Split('/');
          return "You are Passing Values:" + String.Join(",", PassedValue);
       }
}

Now to catch all parameters, add the following code into the global.asax file’s register route method.

//Custom Route Catch-All  Routes
      routes.MapRoute(
           "CatchAllRoute",
            "CatchAll/{*AllValues}",
            new { Controller = "CatchAll", action = "Index" });

Now it will match the following URL like, CatchAll/index/1/2/3/4/5/6/7 and CatchAll/a/b/c/d/e/f/g/h/.

A catch-all parameter must appear as the last parameter. Think of a catch-all parameter as a parameter array.

Debugging Routes

After creating Custom Routes, you have to check whether your routes work perfectly with your given URL or not. For that, you have to debug your routes. To Debug Routes, you have to download one DLL from the http://code.haacked.com/mvc-1.0/RouteDebug-Binary.zip and add a reference into your MVC Application.

Now debug route you have to write the following line of the code into the Global.asax Application Start Event.

RouteDebug.RouteDebugger.RewriteRoutesForTesting(RouteTable.Routes);

And run your application. You will see the below screen in your browser:

You can see all the custom routes into All Routes Table and it will show that your browser URL matches with which Routes. If it will match any Routes, it will show true into the Match Current Request Routes, otherwise it will show false. You can check with another URL, for that you have to write URL into the Address Bar.

Now write URL like, CatchAll/1/2/3/4/5/6/. It will match with the Catchall CustomRoutes.

In this way, you can check with other Routes too. It is possible that your URL matches with more than one Route, at that time the first match Routes Executes. Conclusion

The goal of this article is to demonstrate the flexibility of the routing system provided by ASP.NET MVC. Hope this helps!

Sunday, December 4, 2011

Creating your own MVC View Engine For MVC Application

The View engines used in ASP.NET MVC Framework are the Razor View Engine and Web Form View Engine. Razor View engine used .cshtml and .vbhtml. While Web Form View Engine used .aspx to design the layout of the user interface.

The ASP.NET MVC Framework was designed to support alternative view engines and there are already several open source alternatives to the web forms view engine like Nhaml (pronounced enamel), spark, Brail, nVelocity. The different View Engines enable to write your view in different ways.

It is possible to use multiple view engines used in one MVC application. For that, it is required to registering multiple engines in the global.aspx file.

Custom View Engines

It is very simple to create your own custom view engine. When you create your own view engine, you have to just think about how you want to write your views.

The easiest approach to create custom view engine is just derive a new view engine from abstract VirtualPathProviderViewEngine Class. This base class can take care of the all low-level mechanics of finding and caching views.

Now take a simple example of MyViewEngine it will return simple view.

The first step is to create an empty MVC application. Then add a class file named MyViewEngine.cs and inherit that class from VirtualPathProviderViewEngine and override createview and createpartialview methods. These methods return an instance of the MYView Class. Your class will look like below:


 public class MyViewEngine : VirtualPathProviderViewEngine
    {
        public MyViewEngine()
        {
            // Define the location of the View file
            this.ViewLocationFormats = new string[] 
        { "~/Views/{1}/{0}.myview", "~/Views/Shared/{0}.myview" };

            this.PartialViewLocationFormats = new string[] 
        { "~/Views/{1}/{0}.myview", "~/Views/Shared/{0}.myview" };
        }

        protected override IView CreatePartialView
    (ControllerContext controllerContext, string partialPath)
        {
            var physicalpath = controllerContext.HttpContext.Server.MapPath(partialPath);
            return new MyView(physicalpath);
        }

        protected override IView CreateView
    (ControllerContext controllerContext, string viewPath, string masterPath)
        {
            var physicalpath = controllerContext.HttpContext.Server.MapPath(viewPath);
            return new MyView(physicalpath);
        }
    }

Note that in the constructor, we set two properties of the base class. These properties indicate where the view engine should search to find a matching view or partial view.The parameter {1} represents the name of the controller and the parameter {0} represents the name of the action.

Now, create another class named MyView which implements IView interface. This class actually renders the view. MYView class code looks like below:

public class MyView : IView
    {
        private string _viewPhysicalPath;

        public MyView(string ViewPhysicalPath)
        {
            _viewPhysicalPath = ViewPhysicalPath;
        }

        #region IView Members

        public void Render(ViewContext viewContext, System.IO.TextWriter writer)
        {
            //Load File
            string rawcontents = File.ReadAllText(_viewPhysicalPath);

            //Perform Replacements
            string parsedcontents = Parse(rawcontents, viewContext.ViewData);

            writer.Write(parsedcontents);
        }

        #endregion

        public string Parse(string contents, ViewDataDictionary viewdata)
        {
            return Regex.Replace(contents, "\\{(.+)\\}", m => GetMatch(m,viewdata));
        }

        public virtual string GetMatch(Match m, ViewDataDictionary viewdata)
        {
            if (m.Success)
            {
                string key = m.Result("$1");
                if (viewdata.ContainsKey(key))
                {
                    return viewdata[key].ToString();
                }
            }
            return string.Empty;
        }
    }

Render method of the class loads the view files and injects view data into the view, and writes that result into a text writer.

Before use, custom view engine is required to register view engine into Global.asax file using the following code:

protected void Application_Start()
 {
       AreaRegistration.RegisterAllAreas();

       RegisterGlobalFilters(GlobalFilters.Filters);
       RegisterRoutes(RouteTable.Routes);

       //Register your View Engine Here.
       ViewEngines.Engines.Add(new MyViewEngine());
}

Now create a controller file into controller folder named myViewController and define index action into the controller. Your controller class will look like below:

public class myViewController : Controller
 {
      //
     // GET: /myView/
     public ActionResult Index()
     {
         ViewData["Message"] = "Hello World!";
         return View();
     }
}

Now add the simple HTML File into View/MyView/ folder and give the name of the file like index.myview. Your view file markup looks like below:

<html>
<head>
             <title>Index MyView </title>
</head>
<body>{message}
</body>
</html> 

Now run the application and type URL /MyView /Index. You will get output of the Hello World! into the browser. The MyView class loads the index.myview file and replaces {message} with hello world! and renders the HTML Page.

After developing Custom View Engine, we can say that MVC team has done an awesome job at providing a very flexible framework for us to tweak and customize it so it fits our applications.