Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

Customer Invoicing System/Applying Visitor Design Pattern using ASP.NET MVC5 and C#

4.48/5 (9 votes)
7 Jun 2015CPOL9 min read 71K   3.6K  
This article shows you how to apply Visitor Design pattern in real life software scenario. Also this article covers various features of ASP.NET MVC-5, Entity Framework and C# language features.

Visitor Demo Screenshot

Background

Recently I published here an article on Observer design pattern. My purpose was to show application of Observer design pattern in real life scenario. Those who may be interested in this article can read it here. Real Time StockFeed Dashboard (Applying Observer Design Pattern using WPF and C#)

In this article I will be exploring one more popular design pattern Visitor and demonstrate it's practical application. The application I have developed for this is based on Northwind database. It is Customer Invoicing Application for a fictitious company ABC LTD. and it is used to track the list of customers and it's orders. It also helps you to generate the invoice against any order and you can save the Invoice in XML format for future reference.This application is being developed using asp.net MVC. I have used Visual Studio 2013 to develop this application.

Visitor design pattern is a way of separating an algorithm from an object structure on which it operates. A practical result of this separation is an ability to add new operations to existing object structures without modifying those structures. It is one way to follow the open/closed principle. For more details on this design pattern please refer this link.

Introduction

Purpose of this article is to demonstrate the application of Visitor Design pattern in real life scenario. In this article I have taken an example of Customer Invoicing System where you can track list of customers and their orders.Since this is an invoicing system you can generate the Invoice against any order and send it to the customer. The application also enables you to save the invoice in XML format. I have used two types of visitors in this application as shown below

  • Visitor to Generate Invoice
  • Visitor to save Invoice in XML format

This article will provide detailed implementation of this pattern using ASP.NET MVC and C#. I will also explore other features such as

  • Implement Generic PagedList<T> class to implement paging
  • Features of Bootstrap.CSS
  • Entity Framework
  • Lambda Expressions

Design Overview

I have prepared two class diagrams to explain the key classes and their roles in this application. One diagram would have looked cluttered so I have logically grouped them based on their responsibilities. Figure 1a shows the first class diagram which contains key classes which are related to Generation of Invoice and saving of Invoice in XML format. There are two key interfaces defined InvoiceElement and IVisitor. The detailed explanation is given below.

Class Diagram of Customer Invoicing System

Figure 1a. Class Diagram of Customer Invoicing System

From the above class diagram you can see that Invoice is a conceptual class responsible for defining the structure of an invoice. It implements InvoiceElement interface and also maintains list of all invoice components. Invoice for this example contains three key InvoiceElements namely HeaderElement, OrderElement and FooterElement. Each of these classes implements InvoiceElement interface. InvoiceElement interface expose one method accept() which accepts IVisitor as its parameter. All the concrete visitor classes must implement this interface. This method enables to extend the functionality of the original class, Invoice in our case by implementing different types of visitors without doing changes to these classes. Hence Invoice class clearly simplify its functionality by delegating the implementation of its extended features to different types of visitors.

IVisitor interface exposes visit() method and has several overloaded versions of it so that it can accept different types of InvoiceElement as its paramters. So concrete implementation of visitor can operate based on specific types of InvoiceElement passed to it and take appropriate action. In this example we have two concrete visitor classes namely InvoiceController and SaveInvoiceInXML. I have used InvoiceController as one of the concrete visitor because I want to generate Invoice and render it in html. It has all the suitable references and infrastructure to interact with View and Partial views. I will cover detailed implementation in later section. SaveInvoiceInXML is a visitor responsible for saving the Invoice and its components in XML format.

Figure 1b shows another class diagram of customer invoicing system. This contains key controller classes CustomerController,OrderController and Business layer classes and other helper classes. These are explained below.

Class Diagram of Customer Invoicing System

Figure 1b. Class Diagram of Customer Invoicing System

CustomerController is a controller class and is responsible for the interaction with Customer view. It also implements paging by using PagedList<Customer> list using generic custom class PagedList<T>. This class enables to wrap the list of any reference type entity and exposes key methods and properties required to implement paging functionality. OrderController class is responsible for interaction with Order view. It also implements paging by using PagedList<Order>. BONorthWindFacade class acts as a facade and provides an unified interface between controller classes and data layer. It exposes two key methods GetCustomers() and GetOrdersForTheCustomer(string customerID). The data layer has been implemented using Entity Framework and NorthWindEntities is a key class which provides a data context and access to key entitities such as Customer,Order,Product and Order_Details.

Business Layer

Business layer consists of key domain entities such as Customer,Order and Product. BONorthwindFacade is a class which act as a facade and provides unified interface for interaction with the data layer and controllers.The next section will explain the data layer and the detailed implementation of Invoice class, visitor classses and other functionality features are covered in later sections.

Data Layer

Data layer is implemented using Entity Framework. NorthwindEntities is a key class which acts as a data context and provides access to all key to the entities such as Customer, Order,Product,Order_Details. The entity diagram is shown in figure 2. All the data layer classes are auto generated using Visual Studio. Please refer the source code for more details.

Class Diagram of Customer Invoicing System

Figure 2. ER diagram of Customer Invoicing System

Implementation of Visitors

Since this article is to explain the application of the visitor design pattern I will first cover the detailed implementation of the following classes Invoice, InvoiceController and SaveInvoiceInXML.

Implementation of Invoice Class

Invoice class is a conceptual class and holds the structure of the invoice and all its components. This class implements InvoiceElement interface and has following key methods.

  • initInvoiceElements()
  • accept(IVisitor visitor).

Below code shows the details of this class.

    public class Invoice:InvoiceElement
    {
        
        private List
<InvoiceElement> _invoiceElements;
        public Invoice(Order o){
        
            InitInvoice(o);
        }

        private HeaderVM InitHeader(){
            HeaderVM vm = new HeaderVM();
            vm.Name = "ABC PLC";
            vm.Address1="121,J.B.Road";
            vm.Address2 = "Fleet Street";
            vm.City = "London";
            vm.Zip = "LS0 5TQ";
            vm.Country = "UK";
            vm.Email = "sales@abcplcltd.com";
            vm.Website = "http://www.abcplc.co.uk";
            return vm;
        }
        private void InitInvoice(Order o)
        {
 	        _invoiceElements = new List<InvoiceElementgt;();
            HeaderElement header= new HeaderElement(InitHeader());
            _invoiceElements.Add(header);
            OrderElement orderElemt = new OrderElement(o);
            _invoiceElements.Add(orderElemt);
            FooterElement footElement = new FooterElement("Thank you for doing Business with us!");
            _invoiceElements.Add(footElement);
        }
        // Implementation of InvoiceElement Interface.
        public void accept(IVisitor visitor)
        {
     
            _invoiceElements.ForEach(ie => ie.accept(visitor));
        }
    }

From the above code you can see that Invoice class maintains list of other InvoiceElement components. It's constructor accept instance of Order entity as its parameter and then invokes InitInvoice() method inside the constructor. This method initialises all Invoice components and adds them to the list. The main method is accept() which in turn invokes accept() method of all InvoiceElement initialised earlier. Please see the use of lambda expression. Please refer source code for more details of the implementation details of HeaderElement,OrderElement and FooterElement.

Implementing InvoiceController visitor

InvoiceController class is a concrete visitor which is responsible to generate Invoice. Since we are generating the Invoice in HTML format and as the controller class has all the suitable infrastructure to interact with the views, I have just extended this class by implementing IVisitor interface. This class maintains a list of HTML strings. These html is returned from Partial Views and is rendered by visit() method by operating on different InvoiceElement components. Please see the code below for the details.

public class InvoiceController : Controller,IVisitor
 {
     //
     // Define data context
     private NORTHWNDEntities _dbContext = new NORTHWNDEntities();

     //  list of all htmlsection
     private List<string> _htmlsectionList = new List<string>();


      /// <summary>
      /// Method:Index
      /// Purpose:Returns the Invoice view
      /// </summary>
      /// <param name="id"></param>
      /// <returns></returns>

     public ActionResult Index(int id=0)
     {
         Order od = _dbContext.Orders.Find(id);
         if (od == null)
             return HttpNotFound();
         Invoice newInvoice = new Invoice(od);
         ViewBag.CustomerID = od.CustomerID;
         newInvoice.accept(this);
         ViewBag.OrderID = id;
         return View(_htmlsectionList);
     }


     public ActionResult BackToOrder(string id, int page = 1)
     {
         return RedirectToAction("Index", "Order", new { id = id, page = page });

     }

     /// <summary>
     /// Method:Save
     /// Purpose:Handler to save the Invoice in XML format
     /// </summary>
     /// <param name="id"></param>
     /// <returns></returns>

     public ActionResult Save(int id=0)
     {
         Order od = _dbContext.Orders.Find(id);
         if (od == null)
             return HttpNotFound();
         Invoice newInvoice = new Invoice(od);
         SaveInvoiceInXML saveVisitor = new SaveInvoiceInXML();
         newInvoice.accept(saveVisitor);
         string filePath = string.Format("{0}Order_{1}_{2}.xml", Request.PhysicalApplicationPath, od.CustomerID, od.OrderID);
         try
         {
             if(System.IO.File.Exists(filePath))
             {
                 System.IO.File.Delete(filePath);
             }
             saveVisitor.InvoiceRoot.Save(filePath);
         }
         catch (Exception ex) {

             throw ex;
         }
         return View(od);
     }


     public void visit(HeaderElement headerElement)
     {

         _htmlsectionList.Add(RenderRazorViewToString("_Header", headerElement.Header));
     }
     public void visit(OrderElement orderElement)
     {
         _htmlsectionList.Add(RenderRazorViewToString("_Order", orderElement.CurrentOrder));
     }

     public void visit(FooterElement footerElement)
     {
         _htmlsectionList.Add(RenderRazorViewToString("_Footer", footerElement));
     }


     public void visit(InvoiceElement invoiceElement)
     {
         //Do nothing...  //throw new NotImplementedException();
     }

     public string RenderRazorViewToString(string viewName, object model)
     {
         ViewData.Model = model;
         using (var sw = new StringWriter())
         {
             var viewResult = ViewEngines.Engines.FindPartialView(ControllerContext, viewName);
             var viewContext = new ViewContext(ControllerContext, viewResult.View, ViewData, TempData, sw);
             viewResult.View.Render(viewContext, sw);
             viewResult.ViewEngine.ReleaseView(ControllerContext, viewResult.View);
             return sw.GetStringBuilder().ToString();
         }
     }
 }

From the above code you can see that each version of visit() method operates on specific InvoiceElement and returns appropriate Html markup from partial view and adds to the _htmlSectionList collection. RenderRazorViewToString() method is an important one as it converts the PartialViewResult as html string. The Index() method is simple but interesting method. It first finds the Order based on id passed. It then creates the instance of Invoice class and then invokes it's accept() method. It's this method which in turn invokes respective visit() method to populate the _htmlSectionList collection. Finally it returns the Invoice view by passing _htmlSectionList collection as model to the Index view. The implementation of this view is very simple as it renders all the partial views passed to it via _htmlSectionList collection. The code for the index view is shown below.

@model List<string>
@{
    ViewBag.Title = "Invoice";
}
<div class="container">
   
   @foreach (var item in Model) { 
       
       @Html.Raw(item);
       
   }

<div id="footer">
    <ul class="nav nav-pills">

        <li>
            @Html.ActionLink("Back To Orders", "BackToOrder", new { id = ViewBag.CustomerID, page = 1 })


        </li>
        <li>
            @Html.ActionLink("Save", "Save", new { id = ViewBag.OrderID })


        </li>
    </ul>
</div>

Above code is simple. All it does is render the html using Html.Raw() method. Rest of the code is to show links and handler associated with it. See the use of Bootstrap.CSS. The sample Invoice output is shown in Figure 3.

Sample Invoice

Figure 3. Sample Invoice

Implementation of SaveInvoiceInXML visitor

SaveInvoiceInXML is another visitor which is used to save the invoice in XML format. It's visit() methods operate on respective InvoiceElement and saves it's content in XML format. You need to ensure the read/write permission on the folder where it saves the XML file. Below code shows the details of this class.

public class SaveInvoiceInXML:IVisitor
    {
        private XElement _root = new XElement("invoice");
        public void visit(InvoiceElement invoiceElement)
        {
            //throw new NotImplementedException();
        }

        /// <summary>
        /// Method:visit
        /// Purpose:save headerElement in XML format.
        /// </summary>
        /// <param name="headerElement"></param>
        public void visit(HeaderElement headerElement)
        {
            _root.Add(new XElement("header",
                            new XElement("company",headerElement.Header.Name),
                            new XElement("address1",headerElement.Header.Address1),
                            new XElement("address2",headerElement.Header.Address1),
                            new XElement("city",headerElement.Header.City),
                            new XElement("country",headerElement.Header.Country),
                            new XElement("zip",headerElement.Header.Zip),
                            new XElement("email",headerElement.Header.Email),
                            new XElement("website",headerElement.Header.Website)

                        ));
        }

        /// <summary>
        /// Method:visit
        /// purpose:Saves the order content in XML format.
        /// </summary>
        /// <param name="orderElement"></param>

        public void visit(OrderElement orderElement)
        {
            // Add order and orderElement
         
            Decimal orderAmount = orderElement.CurrentOrder.Order_Details.Sum(odet=>(odet.UnitPrice*odet.Quantity));
            Decimal taxAmount = 0.20M*orderAmount;
            orderAmount +=taxAmount;
            _root.Add( new XElement("order",
                       new XAttribute("orderid",orderElement.CurrentOrder.OrderID),
                       new XElement("orderdate",string.Format("{0:d}",orderElement.CurrentOrder.OrderDate)),
                       new XElement("shipping_details",
                           new XElement("contact_person",orderElement.CurrentOrder.Customer.ContactName),
                           new XElement("company",orderElement.CurrentOrder.Customer.CompanyName),
                           new XElement("address",orderElement.CurrentOrder.ShipAddress),
                           new XElement("city",orderElement.CurrentOrder.ShipCity),
                           new XElement("zip",orderElement.CurrentOrder.ShipPostalCode),
                           new XElement("country",orderElement.CurrentOrder.ShipCountry)
                           ),
                        new XElement("orderdetails",
                            from odet in orderElement.CurrentOrder.Order_Details
                            select new XElement("order_detail",
                                new XElement("productid", odet.ProductID),
                                new XElement("description",odet.Product.ProductName),
                                new XElement("unitprice",odet.UnitPrice),
                                new XElement("quantity",odet.Quantity)
                                )
                            ),
                        new XElement("vat",string.Format("{0:0.00}",taxAmount)),
                        new XElement("totalamount",string.Format("{0:0.00}",orderAmount))
                        
                ));
        }

        /// <summary>
        /// Method:visit
        /// Purpose:Saves footer.
        /// </summary>
        /// <param name="footerElement"></param>
        public void visit(FooterElement footerElement)
        {
           _root.Add(new XElement("footer",footerElement.Footer));
        }

        public XElement InvoiceRoot { get { return _root; } }
    }

From the above code you can see that the _root variable is a root of the XML and it of type XElement. Each visit() method then adds the content of each type of InvoiceElement to this root element. Save() handler method in InvoiceController class initialises this visitor and passes to accept() method of Invoice class which does the trick to populate the XML. Figure 4 shows XML format of the invoice.

Sample Invoice XML

Figure 4. Sample Invoice XML

Implementation of Other Controllers and PagedList<T> class

CustomerController and OrderController classes are responsible to display the Customer and Order views. Both these views implement paging feature and this is achieved thru custom PagedList<T> class. I decided to built this feature from scratch instead of using any third party control for this purpose. This class and some of the pagination features of Bootstrap.CSS simplify implementing this feature.

Implementation of PagedList<T>

PagedList<T> is a generic class which holds an original collection of any reference type and exposes some properties and methods required for paging. It exposes following key methodes and properties.

  • getListFromPage(int page)
  • NoOfPages
  • CurrentPage

Implementation of this class is simple and the code is shown below.

/// <summary>
    /// Generic PagedList class
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class PagedList<T> where T:class
    {
        private List<T> _orgList;
        private int _noPages;
        private int _pageSize;
        private int _minPage = 1;
        private int _lastPage = 1;
        
        public PagedList (List<T> tlist,int pageSize){
    
                _orgList =tlist;
                _pageSize = pageSize;
                if (tlist.Count % _pageSize == 0) {
                    _noPages = tlist.Count / _pageSize;
                }
                else
                {
                    _noPages = tlist.Count / _pageSize + 1;
                }
            }

        public int CurrentPage { get { return _lastPage; } set { _lastPage = value; } }
        public int NoPages { get { return _noPages; } }
       /// <summary>
       /// Method:GetListFromPage
       /// Purpose:Returns subset based on page index.
       /// </summary>
       /// <param name="pageNo"></param>
       /// <returns></returns>
        public List<T> GetListFromPage(int pageNo) {
            int pageCount = _pageSize;
            if (pageNo < _minPage)
            {
                pageNo = _minPage;
            }
            if (pageNo >= _noPages)
            {
                pageNo = _noPages;
                pageCount = _orgList.Count - (_pageSize * (pageNo - 1));
            }
            _lastPage = pageNo;
            return _orgList.Skip(_pageSize * (pageNo - 1)).Take(pageCount).ToList<T>();
        }
        
    }

From the above code you can see that the constructor of PagedList<T> class accept any generic list holding collection of reference types and also you can specify the size of the page. Based on this it computes NoOfPages. GetListFromPage() method return subset of collection based on page index. Please the use of Skip() and Take() extension methods provided by System.Linq.Enumerable class.

Implementation of CustomerController and OrderController classes

Please refer to source code for detailed implementation of these classes.

Points of Interest

If you are interested to know more about Bootstrap css then you visit their site here. Also below I am sharing few links which may of interest to you.

Those who are interested in other design patterns then you use below links for more details.

Conclusion

Design patterns provide solution to recurring problems faced in software development. Visitor is popular design pattern and I hope this article will provide you the insight of how this pattern works and its application in real life scenarios. I hope you will enjoy reading this article. If you have any queries and need more information then you can e-mail me. Thank you.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)