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

Strategy for Separating UI and Back-end Code Development

4.00/5 (3 votes)
15 Jan 2015CPOL7 min read 21.5K   140  
Creating the Web UI

Introduction

Break down a larger problem into smaller parts and solve each of the smaller parts - a central pillar in many engineering tasks. This also applies to software engineering - divide and conquer. This is a simple working example of how this can be done for a web application.

You can use this technique in almost any project where you have a team of programmers working on a UI tied to back-end code. This example just happens to use MVC4 and ADO.NET. This strategy is agnostic of the persistence implementations or programming language or any project specific technologies.

The usefulness of this is two fold. If you can figure out how to break down a problem into smaller components, the tasks then become more manageable and easier to complete. Also, the work can now be divided between programmers.

A natural separation point in software development has always been UI and "other" code. This was not always possible, for example, in case of ASP pages, you often see database, business logic, and user interface elements side by side in the same file. Now with more advanced .NET Frameworks and better design templates like MVC, we can more cleanly separate these elements.

This project shows two methods of mocking up data quickly so that the UI developer can go off and start working on the UI and the back-end developer has enough information to go off and start working up the business logic layer, database layer, and database. The meeting point will be the controllers where we will get to integrate the two parts.

This example is useful because on most projects, there will be several developers involved and finding natural ways of breaking up tasks and jump starting a project into actual coding can be useful. It gets you down the road to working production code.

Background

Create a new MVC4 basic project .NET 4.5 framework. Make sure you use Razor as your view engine and no unit tests. Name it anything you'd like. I named my solution and project the same - Rates. This solution will contain just the one project.

You need to be comfortable with the MVC project type in .NET and need to have worked through a project or two before attempting to do this project. A lot of the setup for this project assumes you have a MVC background and this tip will not explain the details of each of these steps.

Using the Code

We are starting from a basic project because it allows us to jump right into the interesting coding parts without having to worry about setting up some of the basic libraries and folders/files used in MVC4. I find it easier to just delete out the things I don't need from the basic project versus getting things to work nicely from an empty project. I think there is value in understanding where things are coming from when you select a project type, but that's for another article.

Image 1

Now, create these additional folders that could be used later - Home under the Views folder, DAL (database access layer), BLL (business logic layer), and the Types folders under the root. We are just setting some things up. Some of these will not get touched. I will leave the other layers to you if you want to use a database. In the end, you should have a folder structure that looks like this. Organization and naming is vital as a project gets bigger and more complicated, so get into the habit.

Image 2

Under the Views\Home folder, right click and add a view called "Index". Take the default options. You should now have an Index.cshtml page in the Home folder. Now go the Controllers folder and right click and click Add -> Controller... and rename the controller "HomeController". You should now have a HomeController.cs file in the Controllers folder. Controller names and relative locations of the files do matter because of routing in the MVC framework so be exact on your naming and folder structure.

C#
public class HomeController : Controller
{
    //
    // GET: /Home/

    public ActionResult Index()
    {
        return View();
    }
}

You will get the above piece of code free when you create the controller. Run the project and you should be able to see the Index page in the browser at URL http://localhost:53834/Home/Index or http://localhost:53834. Your port number may be different than mine (53834). Now everything is tested and working.

Now we are ready to create a useful view. Create a new view under the Views\Home folder called "RateConfiguration". Add two new classes into the Models folder. The first class will be the RateModel class which represents the main object of the table inside the RateConfiguration.cshtml page.

C#
 using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Web;
 using System.Data;
 using System.IO;
 using System.Xml;
 
 namespace Rates.Models
 {
     public class RateModel 
     {
         public RateModel()
         {
      
         }
       
         public String ROW_ID
         {
             get;
             set;
         }
 
         public Int32 SCENARIO_ID
         {
             get;
             set;
         }
 
         public Int32? PK_SCENARIO_ID
         {
             get;
             set;

         }
 
         public Int32 STORE_NUMBER
         {
             get;
             set;
         }
 
         public Int32 ZONE
         {
             get;
             set;
         }
         
         public Int32 SECTION
         {
             get;
             set;
         }
 
         public Int32? DEPARTMENT
         {
             get;
             set;
         }
 
         public Boolean DEPARTMENT_OVERRIDE
         {
             get;
             set;
         }
 
         public Decimal PRIMARY_RATE
         {
             get;             
             set;
         }
 
         public Decimal SECONDARY_RATE
         {
             get;
             set;
         }
 
         public Decimal? DEPARTMENT_RATE
         {
             get;
             set;
         }
 
         public String CONFIGURATION_TYPE
         {
             get;
             set;
         }
     }     
}  

The next class we need is the RateCollectionModel class which is a collection for RateModels. We will use this to iterate through to build the HTML table in our main view. This class is where the main C# work is being done. If you look at the two constructors, you'll see that we set the RateCollection property. We will pass the RateCollectionModel into our view and use it to build out our HTML table.

Our data source for that property right now is one of two possible options. You will see that in the methods of the RateCollectionModel class, either an XML file is used or a run-time set of RateModel objects. Eventually, you could make the data source any database. For now, we are using these method calls to build our RateCollectionModel object so we can build our view. Once we get the RateCollectionModel methods working and we can call them from our home controller, we can split out the main tasks - UI development work and the back-end code development.

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data;
using System.Xml;
using Rates.Types;

namespace Rates.Models
{
    public class RateCollectionModel
    {
        public IEnumerable<ratemodel> RateCollection  { get; set; }
        private DataSet RateDataSet { get; set; }
   
        public RateCollectionModel()
        {

            // this.RateDataSet = this.GetTestDataSetByXML();
            // OR

            this.RateDataSet = this.GetTestDataByObjects();
            this.RateCollection = this.DataSetToRateCollection(this.RateDataSet);
        }

        public RateCollectionModel(RateOptions iGetBy) 
        {
            switch (iGetBy)
            {
              
                case RateOptions.xml :
                    this.RateDataSet = this.GetTestDataSetByXML();
                    this.RateCollection = this.DataSetToRateCollection(this.RateDataSet);
                    break;

                case RateOptions.objects :
                    this.RateDataSet = this.GetTestDataByObjects();
                    this.RateCollection = this.DataSetToRateCollection(this.RateDataSet);                   
                    break;

                case RateOptions.extensionmethod :
         
                  // create a list of RateModel objects
                    List<ratemodel> lList = new List<ratemodel>()
                    {
                        new RateModel() { SCENARIO_ID = 1, CONFIGURATION_TYPE = "SCENARIO", 
                            STORE_NUMBER = 6010, ZONE = 10, SECTION = 324, PK_SCENARIO_ID = 1, 
                            PRIMARY_RATE = 4.0M, SECONDARY_RATE = 4.5M, DEPARTMENT = 1000,
                            DEPARTMENT_RATE = 9.0M, DEPARTMENT_OVERRIDE = true },
                        new RateModel() { SCENARIO_ID = 1, CONFIGURATION_TYPE = "SCENARIO", 
                            STORE_NUMBER = 6010, ZONE = 20, SECTION = 230, PK_SCENARIO_ID = 2, 
                            PRIMARY_RATE = 4.0M, SECONDARY_RATE = 4.5M, DEPARTMENT = 2000,
                            DEPARTMENT_RATE = 8.5M, DEPARTMENT_OVERRIDE = true },
                        new RateModel() { SCENARIO_ID = 1, CONFIGURATION_TYPE = "PRODUCTION", 
                            STORE_NUMBER = 6010, ZONE = 20, SECTION = 220, PK_SCENARIO_ID = null, 
                            PRIMARY_RATE = 4.0M, SECONDARY_RATE = 4.5M, DEPARTMENT = null,
                            DEPARTMENT_RATE = null, DEPARTMENT_OVERRIDE = false }
                    };

                    this.RateCollection = this.DataSetToRateCollection(lList.ToDataSet<ratemodel>("Rate"));
                    break;
                                
                default:
                    this.RateCollection = null;
                    break;
            }                     
        }

        private List<ratemodel> DataSetToRateCollection(DataSet ds)
        {
            // throw new NotImplementedException();

            DataTable dt = ds.Tables["Rate"];
            RateModel lRateModel = null;
            List<ratemodel> lListRateModel = new List<ratemodel>();

            foreach (DataRow row in dt.Rows)
            {
                lRateModel = new RateModel();

                if (String.IsNullOrEmpty(row["PK_SCENARIO_ID"].ToString()))
                {
                    lRateModel.PK_SCENARIO_ID = null;
                }
                else
                {
                    lRateModel.PK_SCENARIO_ID = Convert.ToInt32(row["PK_SCENARIO_ID"]);
                }

                lRateModel.STORE_NUMBER = Convert.ToInt32(row["STORE_NUMBER"]);
                lRateModel.ZONE = Convert.ToInt32(row["ZONE"]);
                lRateModel.SECTION = Convert.ToInt32(row["SECTION"]);
                if (String.IsNullOrEmpty(row["DEPARTMENT"].ToString()))
                {
                    lRateModel.DEPARTMENT = null;
                }
                else
                {
                    lRateModel.DEPARTMENT = Convert.ToInt32(row["DEPARTMENT"]);
                }
                lRateModel.PRIMARY_RATE = Convert.ToDecimal(row["PRIMARY_RATE"]);
                lRateModel.SECONDARY_RATE = Convert.ToDecimal(row["SECONDARY_RATE"]);
                if (String.IsNullOrEmpty(row["DEPARTMENT_RATE"].ToString()))
                {
                    lRateModel.DEPARTMENT_RATE = null;
                }
                else {
                    lRateModel.DEPARTMENT_RATE = Convert.ToDecimal(row["DEPARTMENT_RATE"]);
                
                }
                if (String.IsNullOrEmpty(row["DEPARTMENT_OVERRIDE"].ToString()))
                {
                    lRateModel.DEPARTMENT_OVERRIDE = false;
                }
                else
                {
                    lRateModel.DEPARTMENT_OVERRIDE = Convert.ToBoolean(row["DEPARTMENT_OVERRIDE"]);

                }

                lRateModel.CONFIGURATION_TYPE = row["CONFIGURATION_TYPE"].ToString();
                lRateModel.SCENARIO_ID = Convert.ToInt32(row["SCENARIO_ID"]);
                lListRateModel.Add(lRateModel);
            }
            return lListRateModel;
        }

        private DataSet GetTestDataByObjects() {

            List<ratemodel> collectionOfRateModel = new List<ratemodel>()
            {
                new RateModel() { SCENARIO_ID = 1, CONFIGURATION_TYPE = "SCENARIO", 
                    STORE_NUMBER = 6010, ZONE = 10, SECTION = 324, PK_SCENARIO_ID = 1, 
                    PRIMARY_RATE = 4.0M, SECONDARY_RATE = 4.5M, DEPARTMENT = 1000,
                    DEPARTMENT_RATE = 9.0M, DEPARTMENT_OVERRIDE = true },
                new RateModel() { SCENARIO_ID = 1, CONFIGURATION_TYPE = "SCENARIO", 
                    STORE_NUMBER = 6010, ZONE = 20, SECTION = 230, PK_SCENARIO_ID = 2, 
                    PRIMARY_RATE = 4.0M, SECONDARY_RATE = 4.5M, DEPARTMENT = 2000,
                    DEPARTMENT_RATE = 8.5M, DEPARTMENT_OVERRIDE = true },
                new RateModel() { SCENARIO_ID = 1, CONFIGURATION_TYPE = "SCENARIO", 
                    STORE_NUMBER = 6010, ZONE = 10, SECTION = 200, PK_SCENARIO_ID = 1,
                    PRIMARY_RATE = 4.0M, SECONDARY_RATE = 4.5M, DEPARTMENT = null,
                    DEPARTMENT_RATE = null, DEPARTMENT_OVERRIDE = false },
                new RateModel() { SCENARIO_ID = 1, CONFIGURATION_TYPE = "PRODUCTION",
                    STORE_NUMBER = 6010, ZONE = 20, SECTION = 210, PK_SCENARIO_ID = null, 
                    PRIMARY_RATE = 4.0M, SECONDARY_RATE = 4.5M, DEPARTMENT = null,
                    DEPARTMENT_RATE = null, DEPARTMENT_OVERRIDE = false },
                new RateModel() { SCENARIO_ID = 1, CONFIGURATION_TYPE = "PRODUCTION", 
                    STORE_NUMBER = 6010, ZONE = 20, SECTION = 220, PK_SCENARIO_ID = null, 
                    PRIMARY_RATE = 4.0M, SECONDARY_RATE = 4.5M, DEPARTMENT = null,
                    DEPARTMENT_RATE = null, DEPARTMENT_OVERRIDE = false }
            };
     
            Type elementType = typeof(RateModel);
            DataSet ds = new DataSet();
            DataTable t = new DataTable();
            t.TableName = "Rate";
            ds.Tables.Add(t);

            foreach (var propInfo in elementType.GetProperties())
            {
                Type ColType = Nullable.GetUnderlyingType(propInfo.PropertyType) ?? 
                propInfo.PropertyType;
                t.Columns.Add(propInfo.Name, ColType);
            }

            foreach (RateModel rm in collectionOfRateModel) 
            {
                DataRow dr = t.NewRow();

                foreach (var propInfo in elementType.GetProperties()) {

                    dr[propInfo.Name] = propInfo.GetValue(rm, null) ?? DBNull.Value;
                }

                t.Rows.Add(dr);
            }
            return ds;
        }

        private DataSet GetTestDataSetByXML()
        {

            XmlReader xmlFile;
            xmlFile = XmlReader.Create("C:\\Users\\giancarlo.rhodes\\Documents\\Visual Studio 2013\\
                       Projects\\Rates\\Rates\\App_Data\\rates.xml", new XmlReaderSettings());
            DataSet ds = new DataSet();
            ds.ReadXml(xmlFile);

            return ds;
        }       
    }
}

Here's an example of creating a RateCollectionModel object and using one of the constructors to set the RateCollection property inside a controller.

C#
public ActionResult RateConfiguration() {

         // RateCollectionModel rcm = new RateCollectionModel();

          RateCollectionModel rcm = new RateCollectionModel(RateOptions.xml);

          return View("RateConfiguration", rcm);
      }

You can see that in the code above, we are calling the constructor that takes a RateOptions enum as an argument and that we are in this case using an XML file to set the RateCollection. Other options we could have used for RateOptions are "objects" and "extensionmethod". These two are very similar in that they are creating objects at run-time and that their data is embedded in the methods and not as separate data. This is fine for our purposes of mocking data and getting started on the UI as soon as possible. You can change the code in the RateConfiguration method in the home controller to test and walk through the code for the three RateOptions to see the details how each of these is working. You can also use the default constructor which is commented out above.

The whole point is to get some data mocked up as quickly as possible so we can move on to developing the UI.

Image 3

Below is the pseudo-code for the RateConfiguration.cshtml. You can look at the actual Rates project zip file to see the details. We pass a RateCollectionModel in and then use it inside a foreach loop to create each row of the HTML table. The header row only needs to be created once so it is part of the if statement right before we hit the foreach loop.

C#
@model Rates.Models.RateCollectionModel
@using Rates.Models;

... all the javascript functions
 
 @using (Html.BeginForm("RateConfiguration", 
 "Home", FormMethod.Post, new { id = "formRate" }))
        {
             @if (Model.RateCollection.Count() > 0)
                {
                    ..... BUILD THE TABLE HEADER
                   
                        @foreach (RateModel lRate in Model.RateCollection)                           
                        {
                          ..... FILL THE TABLE
                        }
                }
        } 

The cshtml and JavaScript is fairly straightforward. The validation checks are done on the onblur events within the textboxes and the one checkbox. The updating, canceling, and cloning functions are done on the onclick events for the buttons. The actual database inserts and updates will be done as Ajax calls and the plumbing for that is already in place here. You will see the Ajax calls in the JavaScript and you will see the method SaveRowChanges in the home controller that will be posted to through the Ajax call.

Conclusion

I hope you find this method of mocking data helpful in jump starting your UI development.

History

  • 2015-01-08 First draft
  • 2015-01-16 Fixed broken images
  • 2015-01-29 Changed title and clarified what the example is attempting to show in the first couple paragraphs

License

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