Click here to Skip to main content
15,886,603 members
Articles / Web Development / ASP.NET

Multi Tier Framework in C#

Rate me:
Please Sign up or sign in to vote.
4.25/5 (3 votes)
22 Sep 2010CPOL2 min read 23.3K   390   44  
Using Microsoft SQL Server or XML files as datastore

Introduction

This is a step by step guide to use a custom N-Tier Framework based on Linq to SQL and/or Linq to XML. This framework of abstract classes and interfaces should do the repeating work so you can focus on the business logic.

In the attached solution, you can find a Console project to create the test-database. Don't forget to customize the Config-file in both the Console and the Website project! 

Background

The reason I started this project was that I got tired of repeating the same stuff. I tried to make a template solution that I could use for other future projects. It was not my intention to automatise everything but to still give as much control as possible to the developer.

Using the Code

Steps to add a new Item:

  1. Project EntityModel
  2. Project Core
    1. Create a Business Object
    2. Create a SearchObject (optional)
    3. Modify SearchObjectFactory
  3. Project DAL
    1. Create a Service
    2. Modify ServiceFactory
  4. Project BLL
    1. Create a Manager
    2. Modify ManagerFactory
  5. Website.Core (optional)
  6. Website
    1. Create a ListControl
    2. Create a DetailControl
    3. Create Pages

1. Project EntityModel

Drag or add a new class to your DataContext and implement the ILinqEntity interface (required):

C#
partial class Item : ILinqEntity {
}

2. Project Core

2.1 Create a new Business Object, inherit from EntityBase (required) and implement interfaces (optional):
C#
public class Item : EntityBase, IEntityWithDescription, IEntityWithOnlineCheck {
    public string Title { get; set; }
    public string Description { get; set; }
    public decimal? Price { get; set; }
    public bool Online { get; set; }
    public object SectionId { get; set; }
    public Section Section { get; set; }

    public Item()
        : base() {
        Online = true;
    }
}

Possible interfaces:

  • IEntityWithTitle
  • IEntityWithDescription
  • IEntityWithSortOrder
  • IEntityWithOnline

2.2 Create a new SearchObject (optional), inherit from SearchObjectBase (required) and implement interfaces (optional).

The SearchObject is some kind of wrapper around a Dictionary that is extendable to other types of SearchObjects. This will enable automated joins in the DAL later. It can also fill its parameters from a QueryString-like string which will be useful in the usercontrols.

C#
public enum SortItemsBy
{
    DateInputDesc = 0,
    TitleAsc = 1,
    TitleDesc = 2,
    PriceAsc = 3,
    PriceDesc = 4,
    DateInputAsc = 5,
    DateUpdateAsc = 6,
    DateUpdateDesc = 7,
    Nothing = 8
}

public class ItemSearchObject : SearchObjectBase,
    ISearchObjectWithOnline, ISearchObjectWithParentId
{
    protected override string Prefix {
        get { return "i_"; }
    }
    public object ParentId {
        get { return SectionSearchObject.ItemId; }
        set { SectionSearchObject.ItemId = value; }
    }
    public decimal? PriceMin {
        get { return GetDecimal("PriceMin"); }
        set { SetDecimal("PriceMin", value); }
    }
    public decimal? PriceMax {
        get { return GetDecimal("PriceMax"); }
        set { SetDecimal("PriceMax", value); }
    }
    public bool? Online {
        get { return GetBoolean("Online"); }
        set { SetBoolean("Online", value); }
    }
    public SortItemsBy SortBy {
        get { return (SortItemsBy)(GetInt("sort") ?? 0); }
        set {
            if (value != SortItemsBy.DateInputDesc)
                SetInt("sort", (int)value);
            else
                SetInt("sort", null);
        }
    }

    public override bool HasParameters {
        get {
            return base.HasParameters
                || ParentId != null
                || PriceMin.HasValue
                || PriceMax.HasValue
                || Online.HasValue;
        }
    }
    public override bool HasExtendedParameters {
        get {
            return HasParameters
                || HasSectionParameters;
        }
    }
    public bool HasSectionParameters {
        get { return SectionSearchObject.HasParameters; }
    }

    public SectionSearchObject SectionSearchObject {
        get { return ExtendTo<SectionSearchObject>(); }
    }

    protected internal override string GetStringFromExtendedParameters() {
        return SectionSearchObject.GetStringFromParameters();
}

Possible interfaces:

  • ISearchObjectWithOnline
  • ISearchObjectWithParentId
2.3 Modify the SearchObjectFactory class (required if you created a SearchObject).
Add the following lines:
C#
else if (typeof(T) == typeof(Item))
    return new ItemSearchObject();

3. Project DAL

3.1 Create a service, inherit from SqlServiceBase<T,Y> or XmlServiceBase<T> (required):

C#
using Core;
using EntityModel;
using LinqItem = EntityModel.Item;
using CoreItem = Core.Item;

public class SqlItemService : SqlServiceBase<CoreItem, LinqItem>
{
    SqlSectionService ss;

    public SqlItemService()
        : base() {
    }
    public SqlItemService(string connectionstring)
        : base(connectionstring) {
    }
    internal SqlItemService(EntityDataContext dc)
        : base(dc) {
    }

    public override CoreItem GetItem(object id) {
        IQueryable<LinqItem> query = base.Table.Where(i => i.Id.Equals(id));
        return base.GetItem(query);
    }

    protected internal override IQueryable<LinqItem> 
	FilterQuery(IQueryable<LinqItem> query, ISearchObject so) {
        ItemSearchObject iso = so.ExtendTo<ItemSearchObject>();
        if (iso.ItemId != null)
            query = query.Where(i => iso.ItemId.Equals(i.Id));
        if (iso.PriceMin.HasValue)
            query = query.Where(i => i.Price >= iso.PriceMin.Value);
        if (iso.PriceMax.HasValue)
            query = query.Where(i => i.Price <= iso.PriceMax.Value);
        if (iso.Online.HasValue)
            query = query.Where(i => iso.Online.Value.Equals(i.Online));
        if (!String.IsNullOrEmpty(iso.Keywords))
            foreach (string keyword in iso.KeywordList) {
                string kw = keyword; //otherwise the SQL query will only 
				// use the last keyword
                query = query.Where(i => i.Title.Contains(kw) || 
					i.Description.Contains(kw));
            }
        return query;
    }
    protected internal override IQueryable<CoreItem> 
		SortQuery(IQueryable<CoreItem> query, ISearchObject so) {
        ItemSearchObject sso = so.CopyTo<ItemSearchObject>();
        switch (sso.SortBy) {
            case SortItemsBy.TitleAsc:
                query = query.OrderBy(q => q.Title);
                break;
            case SortItemsBy.TitleDesc:
                query = query.OrderByDescending(q => q.Title);
                break;
            case SortItemsBy.PriceAsc:
                query = query.OrderBy(q => q.Price);
                break;
            case SortItemsBy.PriceDesc:
                query = query.OrderByDescending(q => q.Price);
                break;
            case SortItemsBy.DateInputAsc:
                query = query.OrderBy(q => q.DateInput);
                break;
            case SortItemsBy.DateInputDesc:
                query = query.OrderByDescending(q => q.DateInput);
                break;
            case SortItemsBy.DateUpdateAsc:
                query = query.OrderBy(q => q.DateUpdate);
                break;
            case SortItemsBy.DateUpdateDesc:
                query = query.OrderByDescending(q => q.DateUpdate);
                break;
        }
        return query;
    }
    protected internal override IQueryable<LinqItem> 
	GetParentItemsQuery(IQueryable<LinqItem> query, ISearchObject so) {
        if (so.ExtendTo<ItemSearchObject>().HasSectionParameters) {
            ss = new SqlSectionService(DataContext);
            query = query.Join(
                ss.GetQuery(so, FilterInclude.Parent),
                i => i.SectionId,
                s => s.Id,
                (i, s) => i
            );
        }
        return query.Distinct();
    }

    protected internal override LinqItem ConvertCoreEntity(CoreItem item) {
        if (item == null)
            return null;
        LinqItem lItem = new LinqItem();
        int itemId = 0;
        if (item.Id != null)
            Int32.TryParse(item.Id.ToString(), out itemId);
        lItem.Id = itemId;
        int parentId = 0;
        if (item.SectionId != null)
            if (Int32.TryParse(item.SectionId.ToString(), out parentId))
                lItem.SectionId = parentId;
        lItem.Title = item.Title;
        lItem.Description = item.Description;
        lItem.Price = item.Price;
        lItem.Online = item.Online;
        lItem.DateInput = item.DateInput;
        lItem.DateUpdate = item.DateUpdate;
        return lItem;
    }
    protected internal override CoreItem ConvertLinqEntity(LinqItem item) {
        if (item == null)
            return null;
        CoreItem cItem = new CoreItem();
        cItem.Id = item.Id;
        cItem.SectionId = item.SectionId;
        cItem.Title = item.Title;
        cItem.Description = item.Description;
        cItem.Online = item.Online;
        cItem.DateInput = item.DateInput;
        cItem.DateUpdate = item.DateUpdate;
        return cItem;
    }
    protected internal override IQueryable<CoreItem> 
		ConvertLinqEntityQuery(IQueryable<LinqItem> query) {
        return query.Select(i =>
            new CoreItem {
                Id = i.Id,
                SectionId = i.SectionId,
                Title = i.Title,
                Description = i.Description,
                Price = i.Price,
                Online = i.Online,
                DateInput = i.DateInput,
                DateUpdate = i.DateUpdate
            }
        );
    }

    public override void Dispose() {
        if (ss != null)
            ss.Dispose();
        base.Dispose();
    }
}

3.2 Modify ServiceFactory (required).
Add the following lines:

C#
else if (typeof(T) == typeof(Item))
    return new SqlItemService() as IService<T>;

4. Project BLL

4.1 Create a manager, inherit from ManagerBase<T> (readonly) or ManagerModifiableBase<T> (required), and implement interfaces (optional):

C#
public class ItemManager : ManagerModifiableBase<Item>,
    IManagerWithSearchObject<Item>
{
    public Item GetItem(ISearchObject so) {
        return base.GetItemBySearchObject(so);
    }
    public IList<item /> GetItems(ISearchObject so) {
        return base.GetItemsBySearchObject(so);
    }
    public int GetCount(ISearchObject so) {
        return base.GetCountBySearchObject(so);
    }

    protected override bool IsValidInput(Item item) {
        return base.IsValidInput(item)
            && item.SectionId != null
            && !String.IsNullOrEmpty(item.Title)
            && !String.IsNullOrEmpty(item.Description);
    }
}

Possible interfaces:

  • IManagerWithSearchObject<T>
  • IManagerCachable<T>
  • IManagerModifiable<T>

4.2 Modify ManagerFactory<T> (required).
Add the following lines:

C#
if (typeof(T) == typeof(Item))
    return new ItemManager() as IManager<T>;

5. Website.Core

Modify UrlManager (optional):

C#
public static string CreateDetailUrl(Item item, QueryStringManager q) {
	return createUrl("/ItemDetail.aspx", new ItemSearchObject() 
		{ ItemId = item.Id }, q);
}
public static string CreateListUrl(ItemSearchObject iso, QueryStringManager q) {
	return createUrl("/ItemList.aspx", iso, q);
}

6. Website

6.1 Create a ListControl

This control will automatically filter the results based on the QueryString parameters:

C#
<asp:Repeater ID="ItemRepeater" runat="server" 
	onitemdatabound="ItemRepeater_ItemDataBound">
  <ItemTemplate>
    <p>
      <strong><asp:Literal ID="TitleLiteral" runat="server" /></strong>
      (<asp:Literal ID="PriceLiteral" runat="server" />)
    </p>
  </ItemTemplate>
</asp:Repeater>
C#
public partial class ItemList : ListControl<Item>
{
    protected void ItemRepeater_ItemDataBound(object sender, RepeaterItemEventArgs e) {
        Item item = e.Item.DataItem as Item;
        Literal title = e.Item.FindControl("TitleLiteral") as Literal;
        title.Text = "<a href=\"" + UrlManager.CreateDetailUrl(item, Q) + "\">" 
		+ StringHelper.StripHtml(item.Title) + "</a>";
        Literal price = e.Item.FindControl("PriceLiteral") as Literal;
        price.Text = StringHelper.ShowPrice(item.Price);
    }

    protected override void FillControl() {
        ItemRepeater.DataSource = base.Items;
        ItemRepeater.DataBind();
    }
}

6.2 Create a DetailControl

This control will automatically get the item based on the id included in the QueryString parameters

ASP.NET
<h3><asp:Literal ID="TitleLiteral" runat="server" /></h3>
<p><asp:Literal ID="DescriptionLiteral" runat="server" /></p>
<p><b>Price:</b> <asp:Literal ID="PriceLiteral" runat="server" /></p>
C#
public partial class ItemDetail : DetailControl<Item>
{
    protected override void FillControl() {
        TitleLiteral.Text = base.Item.Title;
        DescriptionLiteral.Text = base.Item.Description;
        PriceLiteral.Text = StringHelper.ShowPrice(base.Item.Price ?? 0);
    }
}

6.3 Create one or more Pages:

ASP.NET
<p><i><asp:Literal ID="CountLiteral" runat="server" Text="No" /> item(s) found</i></p>
<uc1:ItemList ID="ItemListControl" runat="server" />
C#
public partial class ItemListPage : CustomPage
{
	protected override void AfterLoad() {
		base.AfterLoad();
		CountLiteral.Text = ItemListControl.TotalCount.ToString();
	}
}

History

  • 23rd September, 2010: Initial post

License

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


Written By
Belgium Belgium
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
-- There are no messages in this forum --