Best Practices: Web Application with Class Library for Business Logic

8 posts, 0 answers
  1. Lev Rosenblit
    Lev Rosenblit avatar
    16 posts
    Member since:
    May 2008

    Posted 07 Oct 2009 Link to this post

    Requirements

    RadControls version 2009 Q2


    .NET version 3.5


    Visual Studio version 2008


    programming language C#


    browser support

    all browsers supported by RadControls


    PROJECT DESCRIPTION
    In a medium scale application (and for large scale applications) you should build a class library for your business logic.

    In this case I have the following:
    1) EntityModel = open access persistent classes
    2) DataAccess = open access DAL
    3) BusinessLayer = My business logic

    After reading all the best practices KB, I started implementing my business logic with a common base class that offers all of the scope related operations for the current http request.

    here is the code for my BusinessBase class:
        public class BusinessBase 
        { 
            protected static readonly string SCOPE_KEY = "ScopePerHttpRequest"
            protected IObjectScope scope; 
             
            protected BusinessBase() 
            { 
                if (HttpContext.Current != null) 
                { 
                    if (HttpContext.Current.Items[SCOPE_KEY] == null) 
                        HttpContext.Current.Items.Add(SCOPE_KEY, scope = DataAccessScopeProvider.GetNewObjectScope()); 
                    else 
                        scope = HttpContext.Current.Items[SCOPE_KEY] as IObjectScope; 
                } 
            } 
     
            public void StartTransaction() 
            { 
                if (!scope.Transaction.IsActive) 
                    scope.Transaction.Begin(); 
            } 
     
            public void CommitChanges() 
            { 
                try 
                { 
                    if (scope.Transaction.IsActive) 
                        scope.Transaction.Commit(); 
                    else 
                        throw new Exception(GetType().FullName + ".CommitChanges()"); 
                } 
                catch (Exception ex) 
                { 
                    RollBackChanges(); 
                    throw new Exception(ex); 
                } 
            } 
     
            public void RollBackChanges() 
            { 
                if (scope.Transaction.IsActive) 
                    scope.Transaction.Rollback(); 
            } 
     
            public static void Dispose() 
            { 
                if (HttpContext.Current.Items[SCOPE_KEY] == null) 
                    return; 
                IObjectScope scope = HttpContext.Current.Session[SCOPE_KEY] as IObjectScope; 
                if (scope != null) 
                    scope.Dispose(); 
                HttpContext.Current.Items.Remove(SCOPE_KEY);             
            } 
     
            protected void ValidateTransaction(string method) 
            { 
                if (!scope.Transaction.IsActive) 
                    throw new Exception(GetType().FullName + "." + method); 
            } 
        } 
     

    Every Business Object that implements my business logic in this class library is derived from this class, and then i can simply use the defined scope.

    For example:
        public class CategoryBO : BusinessBase 
        { 
            public Category GetCategory(short id) 
            { 
                try 
                { 
                    Category category = (from c in scope.Extent<Category>() 
                                         where c.Id == id 
                                         select c).First(); 
                    return category; 
                } 
                catch (Exception ex) 
                { 
                    return null; 
                }                        
            } 
        } 

    I leave it to the user of the business logic to decide where to start or commit transactions (sometimes i want to commit before other db operations and not necessarily upon dispose).
    for example:
    CategoryBO categoryBo = new CatregoryBO();
    Category category = categoryBo.GetCategory(1);
    categoryBo.StartTransaction(); 
    category.field = "lalala";
    categoryBo.CommitTransaction(); 



  2. Hugo Augusto
    Hugo Augusto avatar
    55 posts
    Member since:
    Jul 2004

    Posted 01 Jul 2010 Link to this post

    This example is one of the best here. Any chance of update it with the new Domain Model approach applied to a web app?
  3. Damyan Bogoev
    Admin
    Damyan Bogoev avatar
    581 posts

    Posted 07 Jul 2010 Link to this post

    Hello Hugo Augusto,

    Sure, we will update the example in a few days. We are quite busy preparing the Q2 release at the moment. Thank you for the request.

    All the best,
    Damyan Bogoev
    the Telerik team
    Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Public Issue Tracking system and vote to affect the priority of the items
  4. Damyan Bogoev
    Admin
    Damyan Bogoev avatar
    581 posts

    Posted 13 Jul 2010 Link to this post

    Hello Hugo Augusto,

    We have uploaded the following code library example: "Best Practices in web development with OpenAccess" where the requested from you scenario is demonstrated using the OpenAccessContext.
    I hope this example resembles the one that you have requested.

    Regards,
    Damyan Bogoev
    the Telerik team
    Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Public Issue Tracking system and vote to affect the priority of the items
  5. Hugo Augusto
    Hugo Augusto avatar
    55 posts
    Member since:
    Jul 2004

    Posted 15 Jul 2010 Link to this post

    Hi Damyan,

    in the example I believe there is an error. In BestPracticesMasterPage, Default.aspx.cs, you have in Page_Load event,

    context.SaveChanges();

    Shouldn't this be

    TestingPersonManager.Save(myPersons[0], context);

    Also, I'm having serious doubts about using OpenAccess the way you do. I believe that those who are using ORM's need some abstraction when using them so that we will be able to easily replace the current ORM with another one. Using this same example, what would be your suggestion to allow for absolutely zero coupling with the domain model (Open Access generated Model). The example in this website is exactly what I mean, and would love to see that implemented here with OpenAccess. Also, manually creating Assembler classes with the mapping from the Generated Domain Model classes to our own business object classes as I already saw, is absolutely insane if you have many tables and relations. If I have to make the mapping manually, what's the point of using an ORM? I'm not in any way criticizing OpenAccess, but just trying to get some discussion about this.

    Once again, it would be great to see an example addressing this issues.

    PS: I would also love to hear what you think about returning IQueryable<T> to the upper layers from an existing List of objects instead of a collection of objects like "TestingPersonList" or even the usual List<>.


  6. Damyan Bogoev
    Admin
    Damyan Bogoev avatar
    581 posts

    Posted 16 Jul 2010 Link to this post

    Hi Hugo Augusto,

    1. Using the following code snippet in the Page_Load method:

    TestingPersonList myPersons = TestingPersonManager.GetList(context);
    myPersons[0].Name = "John Doe";
    context.SaveChanges();

    we are retrieving the first person from the collection, change its name and then persist the change to the database with the SaveChanges method.
    This example demonstrates how to use the OpenAccessContext, not the IObjectScope .
    2. You could find useful the following code library example which demonstrates how to implement a context factory using Telerik OpenAccess ORM. Another code library which uses a repository pattern can be found here but it is used in a scenario with WPF.
    3. Passing an IQueryable instance is better approach than a List instance. When a List instance is passed all the data should be fetched and passed, while the IQueryable will give you the ability to fetch the data you need in the upper layer.
    Hope that helps.

    All the best,
    Damyan Bogoev
    the Telerik team
    Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Public Issue Tracking system and vote to affect the priority of the items
  7. Solomon
    Solomon avatar
    17 posts
    Member since:
    Feb 2008

    Posted 03 Aug 2010 Link to this post

    This is an excellent article for getting a quick web framework in place! I used it and it saved my butt under a deadline. 

    I do have one point. I am not sure of the implications of this - so maybe someone can give their thoughts.

    What about using System.Web.HttpRuntime.Cache instead of HttpContext. The HttpContext is not always guarenteed to be there, and is not there in many WCF implementations. What System.Web.HttpRuntime.Cache attaches it to the IIS runtime directly.

    What about this as a constructor?

    protected BusinessServicesBase()
    {
        if (System.Web.HttpRuntime.Cache[ScopeKey] == null)
            System.Web.HttpRuntime.Cache.Insert(ScopeKey, ObjectScope = ObjectScopeProvider.GetNewObjectScope());
        else
            ObjectScope = System.Web.HttpRuntime.Cache[ScopeKey] as IObjectScope;
    }

  8. Damyan Bogoev
    Admin
    Damyan Bogoev avatar
    581 posts

    Posted 06 Aug 2010 Link to this post

    Hello Solomon,

    Yes, you could use the HttpRuntime.Cache to store an IObjectScope object. And in this case the IObjectScope instance will be available for all projects types. But the recommended approach is to store it in the HttpContext. You could find helpful this article which is about Linq To Sql but explains the best practices for handling data contexts. These ideas are applicable to IObjectScope instances as well.
    Hope you will find useful the provided information.

    Regards,
    Damyan Bogoev
    the Telerik team
    Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Public Issue Tracking system and vote to affect the priority of the items
Back to Top