Click here to Skip to main content
6,305,776 members and growing! (17,009 online)
Email Password   helpLost your password?
Web Development » ASP.NET » Data     Intermediate License: The Code Project Open License (CPOL)

N-Tier development with ASP.NET MVC, WCF & LINQ

By Emmanuel Nuyttens

This article is presenting a demo bugtracking application build on ASP.NET MVC, WCF & LINQ to SQL
C#, .NET (.NET 3.5), ASP.NET, WCF, Architect, Dev
Version:10 (See All)
Posted:5 Jan 2009
Views:13,350
Bookmarked:34 times
Unedited contribution
Announcements
Loading...
 
Search    
Advanced Search
printPrint   Broken Article?Report       add Share
  Discuss Discuss   Recommend Article Email
11 votes for this article.
Popularity: 3.84 Rating: 3.69 out of 5
1 vote, 9.1%
1

2
2 votes, 18.2%
3
4 votes, 36.4%
4
4 votes, 36.4%
5

Introduction

This article is about a possible way to implement a N-Tier architecture for ASP.NET MVC (Model View Control), using WCF (Windows Communication Foundation = replacement for .NET Remoting) and LINQ to SQL. This article will not go in depth explaining the ins and outs of MVC, WCF & LINQ (there's already enough stuff which explains these technologies ...), instead, i'll be showing a possible way to implement these features in an N-Tier environment, this by means of a simple bugtracking demo application.

Requirments of the BugTracking Demo Application

First we should state the requirments of our demo application. These are quit straight forward, as mentioned beneath:

  • We want the application to produce a list of current added BugTracking tickets.
  • We want the possibility that a user can add a ticket to the system, which envolves adding a ticket date, select a user to whom we grant the ticket, add a ticket date and let the user add some comments.
  • Before adding a new ticket to the system, we want to be sure that the user has supplied a valid date, selected a ticket type and user, and added some comments for the ticket.

Architectural Overview

architecture.jpg

The .NET Solution consists of 3 main projects:

  • BtDataLayer: Contains the model classes and the data access routines.
  • BtWcfService: Contains the services which the presentation layer may call.
  • BugTracking: Contains the presentation layer (ASP.NET MVC app.).

Another project, not mentioned on the diagram above is CommonLib, which contains commonly used routines (like a business base class which each model class derives from, method for cloning objects & a business rule validator engine).

Using the code

The DataAccessLayer (BtDataLayer)

  • The model Classes (BugTracker.dbml):

bugtrackrelations.jpg

These are the LINQ-TO-SQL classes retrieved from the SQL-SERVER Bugtracker database (database template included in zip file). As this model should be remotable, the serialization mode on the datacontext should be set to Unidirectional.

  • The DataAccessCode (btDataManger.cs):

We want to be able to load a userlist, so we can select a user for whom the ticket applies to:

public static List GetUserList()
{
   // Create the DataContext to retrieve the Users from.
   BugTrackerDataContext db = new BugTrackerDataContext();

   // Return a list of Users.
   return db.Users.ToList();
}

We want to be able to load tickettype list, so we can select the type of ticket (BUG, INFO, etc ...) for the ticket:

public static List GetTicketTypeList()
{
   // Create the DataContext to retrieve the Ticket Types from.
   BugTrackerDataContext db = new BugTrackerDataContext();

   // Return a list of TicketTypes.
   return db.TicketTypes.ToList();
}

We want to be able to retrieve all the current tickets from our ticket table:

public static IEnumerable GetTickets()
{
   // Create the DataContainer to retrieve the tickets.
   BugTrackerDataContext db = new BugTrackerDataContext();
            
   // Return a List of Tickets
   return db.Tickets;
}

Finaly, we want to have the possibility to persist a newly added ticket (one at a time for the demo application, but we make the method all-purpose, so it can accept many added or modified objects):

public static bool PersistTickets(ref TicketList p_tickets)
{
            // Only persist if any tickets to add or modify.
            if (p_tickets == null || p_tickets.Count == 0)
                return false;

            // Create the persistence datacontext
            BugTrackerDataContext db = new BugTrackerDataContext();

            // Persist any new or modified tickets.
            foreach (Ticket t in p_tickets)
            {
                if (t.TicketId == 0)
                {
                    db.Tickets.InsertOnSubmit(t);

                }
                else
                {
                    db.Tickets.Attach(t, t.IsDirty);
                }
            }

            try
            {
                db.SubmitChanges(ConflictMode.ContinueOnConflict);

                // Reset the isDirty flag.
                foreach (Ticket t in p_tickets)
                {
                    t.IsDirty = false;
                }

            }
            catch (ChangeConflictException ex)
            {
                throw ex;
            }

            return true;

}

The Service Layer

The purpose of our WCF enabled service layer is the make the model & data-access routines (to forsee the model of data ...) available to the presentation layer).

Our service layer consists mainly of 2 routines:

  • The service Contract (which contains the routines which can be called by our Web App).
[ServiceContract]
    public interface IBtService
    {
        [OperationContract()]
        IEnumerable GetUserList();

        [OperationContract()]
        IEnumerable GetTicketTypeList();

        [OperationContract()]
        bool PersistTickets(ref TicketList p_tickets);

        [OperationContract()]
        IEnumerable GetTickets();
    }

  • The Service Implementation, which implements our contract and calls the datalayer.
        public IEnumerable GetUserList()
        {
            return BtDataManager.GetUserList();
        }

        public IEnumerable GetTicketTypeList()
        {
            return BtDataManager.GetTicketTypeList();
        }

        public bool PersistTickets(ref TicketList p_tickets)
        {
            return BtDataManager.PersistTickets(ref p_tickets);
        }

        public IEnumerable GetTickets()
        {
            return BtDataManager.GetTickets();
        }

The Presentation Layer

The presentation layer contains our ASP MVC application. As already mentioned above, I willnot go in depth on MVC, as there is already a lot of info available on the net. But important  to retain is that MVC contains 3 important items, namely the Model (which represents our class a validation logic, and is remotly available through the Service Layer), the View (our   webpage) and the  Controller (which basically "hooks" the Model to the View).

The Views and the Controllers

Our demo application hosts next MVC enabled web Pages:
  • Ticket.aspx :

ticket.jpg

With this page, the user can add a bugticket to the system. The HomeController is responsible for rendering the page. While interacting with the page, there are 2 actions envolved, one for creating a new ticket (the "GET") version, and one for persisting a new ticket (when the user hits the submit button, (the "POST) version).

[AcceptVerbs("GET")]
        public ActionResult Ticket()
        {

            this.LoadSelectionLists();

            // Get the userlist
            var userList = new SelectList(_userList, "UserId", "Username");
            ViewData["UserId"] = userList;

            // Get the tickettypelist
            var ticketTypeList = new SelectList 
            (_ticketTypeList, "TicketTypeId", "TicketTypeDescription");
            ViewData["TicketTypeId"] = ticketTypeList;

            return View();
        }

When the user selects the "Create Ticket" in the index page, then we must create a new page where the user may enter the ticket details. As the user should have the possibility to select a user and tickettype for the ticket, these data are loaded from  the service and added to the viewstate of the form:

      private void LoadSelectionLists()
        {
            // Get a proxy to the data service provider
            BtServiceRef.BtServiceClient proxy = new   
            BugTracking.BtServiceRef.BtServiceClient();

            // Get the userlist
            _userList = proxy.GetUserList();
            
            // Get the tickettypelist
            _ticketTypeList = proxy.GetTicketTypeList();
           
        }

On the other hand, the post version is executed when the user hits the "submit" button. We will create a ticket object, map the properties to the field properties of the form, validate the objects data (see source code of the model for detailed validation information, as validation data is stored in the model classes) and persist the newlay added ticket through the service to the database.

[AcceptVerbs("POST")]
        public ActionResult Ticket(FormCollection p_form)
        {
            // Create placeholder for the ticket to add.
            var TicketToCreate = new Ticket();

            
            try
            {
                // Map the controls of the View to the PlaceHolder.
                this.UpdateModel(TicketToCreate, new[]  
                { "UserId", "TicketTypeId", "Comment", "GrantedToDate" });
                
                // Force Validation
                TicketToCreate.Validate();

                // First check if any validation Errors Exist on the newly 
                   added ticket
                if (TicketToCreate.HasErrors)
                {
                    
                    // View Validation Errors

                    foreach(DictionaryEntry entry in  
                    TicketToCreate.ValidationErrors)
                    {

                        ViewData.ModelState.AddModelError(entry.Key.ToString
                        (), entry.Value.ToString());
                    }
                    
                    throw new InvalidOperationException();
                }
                else
                {
                    // Create the WCF Remote service and let the service 
                       persist the new ticket.
                    BtServiceRef.BtServiceClient proxy = new 
                    BugTracking.BtServiceRef.BtServiceClient();
                    Ticket[] newTicketList = new Ticket[] { TicketToCreate };
                    proxy.PersistTickets(ref newTicketList);
                }
               
            }
            catch
            {
                this.LoadSelectionLists();

                var userList = new SelectList(_userList, "UserId", "Username");
                ViewData["UserId"] = userList;

                var ticketTypeList = new SelectList  
                (_ticketTypeList, "TicketTypeId", "TicketTypeDescription");
                ViewData["TicketTypeId"] = ticketTypeList;


                // Show possible validation errors.
                return View(TicketToCreate);
            }
            
            // Return to main page.
            return RedirectToAction("Index");
        }

There are 2 comboboxes on the ticket.aspx form, one for user selection and one for tickettype selection. As shown in the code above, we load the user and tickettype records from the service and add them as ViewData of the ticket page. Next we have to bind the ViewData as mentioned beneath: (you can also notice how we execute the bindings for validation purpose)

dropdown.jpg

  • Index.aspx:

When loading the application in the browser, the index page is the first page showed. We get a list of current tickets in the database and the possibility to add a new ticket to the system:

indexpage.jpg

Again, the HomeController is responsible for rendering the page, the action envolved is:

public ActionResult Index()
        {
            ViewData["Title"] = "Home Page";
            ViewData["Message"] = "Welcome to ASP.NET MVC Bugtracker !";

            // Create a proxy to the WCF service.
            BtServiceRef.BtServiceClient proxy = new BugTracking.BtServiceRef.BtServiceClient();

            // Get all tickets from the DataLayer.
            return View(proxy.GetTickets());
        }

As we will show the current tickets in this page, we first create a reference to our service. This service calls the GetTickets() method of our datalayer data-access class and adds it as input parameter of the view. Next to be sure that our Index Page  is aware of the loaded ticket-list, the BtDataLayer.Ticket type should be added as attribute to the derieved ViewPage :

indexpage_classdef.jpg

Finally, in the html code of our index.aspx page, we can loop through our loaded ticketlist and place each ticket in a datarow:

tickets_in_table.jpg

You may have noticed that I display the userId and TickeTypeId instead of username and tickettype description. If you want to view for example the username instead of the id, you should code : t.User.UserName, but this will return an error, as the user is not loaded with the tickets. You can change this by altering the data access method and add a dataloadoptions for the user and tickettype. In this case the related user and tickettype objects will be loaded too.

Final Word

Voilà, that's all to mention. This article showed a possible way to separate logical layers and pass data between bounderies using new technology such as WCF and LINQ-TO-SQL. Finally it showed a possible presentation layer, using the newest ASP.NET MVC technology. Special thanx goes to the people running the asp.net website www.asp.net and also special thanx goes to Beth Massi http://blogs.msdn.com/bethmassi/, for explaining LINQ-TO-SQL and N-Tier through WCF in a well structured and clear view.

License

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

About the Author

Emmanuel Nuyttens


Member
Working in the IT-Branch for more then 10 years now. Starting as a programmer in WinDev, moved to Progress and actualy working in .NET since 2003. At the moment i'm employed as a .NET architect and teamleader at BERCO N.V. at Ronse (Belgium). In my spare time, i'm a die hard mountainbiker and together with my son Jarne, we're climbing the hills in the "Flemish Ardens" and the wonderfull "Pays des Collines". I also enjoy "a p'tit Jack" (Jack Daniels Whiskey) or a "Duvel" (beer) for "l'après VTT !".
Occupation: Architect
Company: BERCO NV
Location: Belgium Belgium

Other popular ASP.NET articles:

Article Top
You must Sign In to use this message board.
FAQ FAQ 
 
Noise Tolerance  Layout  Per page   
 Msgs 1 to 17 of 17 (Total in Forum: 17) (Refresh)FirstPrevNext
GeneralCommonLib PinmemberEmmanuel Nuyttens10:12 23 Jun '09  
GeneralCommonLib dll is in the downloaded zip file [modified] Pinmemberbrittoa14:40 18 Jun '09  
GeneralMy vote of 1 PinmemberVipul4:16 11 Jun '09  
GeneralRe: My vote of 1 PinmemberMember 43133722:38 18 Jun '09  
QuestionCommonLib PinmemberAnne Spalding6:16 30 Apr '09  
QuestionBusiness/Data layer coupling Pinmembergojuro11:27 14 Apr '09  
AnswerRe: Business/Data layer coupling Pinmemberlustuyck4:02 29 Apr '09  
AnswerRe: Business/Data layer coupling PinmemberEmmanuel Nuyttens10:32 23 Jun '09  
QuestionWhere is the CommonLib [modified] Pinmembergojuro11:09 14 Apr '09  
GeneralWhat about scenario whit Transaction ? Pinmemberneppo758:05 28 Mar '09  
GeneralRe: What about scenario whit Transaction ? PinmemberEmmanuel Nuyttens10:24 23 Jun '09  
QuestionMissing CommonLib.csproj PinmemberTournesol6:06 26 Mar '09  
GeneralPresentation Layer referencing Data Layer PinmemberRoshambo22:48 19 Jan '09  
GeneralRe: Presentation Layer referencing Data Layer Pinmembermanu19710:47 20 Jan '09  
GeneralCommonLib Missing PinmemberRoshambo19:49 19 Jan '09  
GeneralSample DB missing PinmemberTodd Smith8:42 5 Jan '09  
GeneralCommonLib missing PinmemberTodd Smith8:37 5 Jan '09  

General General    News News    Question Question    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

PermaLink | Privacy | Terms of Use
Last Updated: 5 Jan 2009
Editor:
Copyright 2009 by Emmanuel Nuyttens
Everything else Copyright © CodeProject, 1999-2009
Web19 | Advertise on the Code Project