0

In MVC, does it make sense to move logic inside a domain model? I'm trying to cut down too many classes as my project uses several APIs for every small data to be displayed on a page. e.g.

public class AccountModel{
    public int Id {get;set;}    //property  
    ....    

    public List<AccountModel> GetAccounts(){    //method  
        ....      
    }  
}

Otherwise, what can be a good practice with too many API calls involved?


Adding sample code structure similar to what I'm doing in my project.

    public class TestController : Controller
    {
        public ActionResult Index(string id)
        {
            var testService = new TestService();
            var testModel = new TestModel();

            testModel.UserData = testService.GetTestData(id);

            testModel.MenuList = testService.GetMenu(id);

            testModel.UserItems = testService.GetItems(id);

            return View(testModel);
        }   
    }
    -----------------------------------------------------------------
    public class TestService
    {
        public TestModel GetTestData(string id)
        {
            TestModel testData = null;
            try
            {
                using (HttpClient httpClient = new HttpClient())
                {
                    string requestUri = "http://10.8.200.1/test/" + id;

                    HttpRequestMessage testRequest = new HttpRequestMessage(HttpMethod.Get, requestUri);

                    HttpResponseMessage response = httpClient.SendAsync(testRequest).Result;
                    if (response.IsSuccessStatusCode)
                    {
                        var jsonData = response.Content.ReadAsStringAsync().Result;
                        testData = JsonConvert.DeserializeObject<TestModel>(jsonData);
                    }
                }
            }
            catch (Exception ex)
            {           
            }

            return testData;
        }

        public List<Menu> GetMenu(string id)
        {
            List<Menu> menus = null;
            try
            {
                using (HttpClient httpClient = new HttpClient())
                {
                    string requestUri = "http://10.8.200.1/menu/" + id;

                    HttpRequestMessage testRequest = new HttpRequestMessage(HttpMethod.Get, requestUri);

                    HttpResponseMessage response = httpClient.SendAsync(testRequest).Result;
                    if (response.IsSuccessStatusCode)
                    {
                        var jsonData = response.Content.ReadAsStringAsync().Result;
                        menus = JsonConvert.DeserializeObject<List<Menu>>(jsonData);
                    }
                }
            }
            catch (Exception ex)
            {           
            }

            return menus;
        }

        public List<UserItem> GetItems(string id)
        {
            List<UserItem> items = null;
            try
            {
                using (HttpClient httpClient = new HttpClient())
                {
                    string requestUri = "http://10.8.200.1/items/" + id;

                    HttpRequestMessage testRequest = new HttpRequestMessage(HttpMethod.Get, requestUri);

                    HttpResponseMessage response = httpClient.SendAsync(testRequest).Result;
                    if (response.IsSuccessStatusCode)
                    {
                        var jsonData = response.Content.ReadAsStringAsync().Result;
                        items = JsonConvert.DeserializeObject<List<Menu>>(jsonData);
                    }
                }
            }
            catch (Exception ex)
            {           
            }

            return items;
        }
    }
    -----------------------------------------------------------------
    public class TestModel
    {
        public string Name{get;set;}
        public string PublisherId{get;set;}
        public string AccountType{get;set;}

        public UserData UserData {get;set;}         //There will be a UserData model
        public List<Menu> MenuList {get;set;}       //There will be a Menu model
        public List<UserItem> UserItems {get;set;}  //There will be a UserItem model
    }
    ----------------------------------------------------------------    

Similarly, I have multiple controllers and multiple APIs and respective models, and service classes. Can you please suggest a better approach?

kinjaldave
  • 145
  • 1
  • 13

1 Answers1

3

Having too many classes should not be a bigger concern than having too much complexity in the class that you do define. Keep the responsibilities of your classes simple.

Ideally your POCOs should not contain CRUD methods. And in your example, your crud method there is an instance method, which means that you would have to instantiate an AccountModel to get a List<AccountModel>. Doesn't make much sense does it?

Build a separate interface named something like IAccountModelBusiness to obtain your entities. Have implementations of that interface accept an IAccountModelDataAccess or IAccountModelRepository to separate the concern of data access from the concern of data management and rule enforcement.

UPDATE

Based on the sample code added to the question, here is a version of that sample with the some of the techniques mentioned in this answer applied:

This first class is the controller class (presumably built for ASP.Net MVC):

public class TestController : Controller
{
    private ITestAPI api;

    #warning If you want, implement and register an IHttpControllerActivator to inject the IAPI implementation and remove this constructor if you want to have DI all the way up your stack.
    public  TestController() : this(new TestAPI(new UserDataService("http://10.8.200.1/test/"), new MenuService("http://10.8.200.1/menu/"), new UserItemsService("http://10.8.200.1/items/"))) {}

    public TestController(ITestAPI api) { this.api = api; }

    public ActionResult Index(string id)
    {
        return View(this.api.GetTestModel(id));
    }   
}

The above controller class has a default constructor which I recommend removing and moving the default value arguments to a composition root using an IHttpControllerActivator implementation. Check out this article for details on how to do that. This class is meant to be a thin controller for hosting the real functionality encapsulated within another class. The reason that we do this is because .Net web service technologies change over time. In the last 15 years, we've had Active Service Method Services (ASMX), Windows Communication (WCF), ASP.Net MVC and ASP.Net Web API. That's not including third party service libraries, for example like ServiceStack and NancyFX. This design separates the hosting responsibility from the main query and assembly logic.

This next interface is for defining the methods that are to be hosted and exposed by the controller:

public interface ITestAPI
{
    TestModel GetTestModel(string id);
}

And this next class is for providing an implementation of the methods that are to be hosted and exposed:

public class TestAPI : ITestAPI
{
    private IUserDataService    userDataService;
    private IMenuService        menuService;
    private IUserItemsService   userItemsService;

    public TestAPI(IUserDataService userDataService, IMenuService menuService, IUserItemsService userItemsService)
    {
        this.userDataService    = userDataService;
        this.menuService        = menuService;
        this.userItemsService   = userItemsService;
    }


    public TestModel GetTestModel(string id)
    {
        var testModel = new TestModel();

        testModel.UserData  = this.userDataService.GetUserData(id);
        testModel.MenuList  = this.menuService.GetMenus(id);
        testModel.UserItems = this.userItemsService.GetUserItems(id);

        return testModel;
    }   
}

Note that this implementation relies on the service proxies to get user data, menus and user items, and so it is defined with three constructor parameters with which to inject those dependencies.

Here are the interfaces for the dependencies that the TestAPI class requires:

public interface IUserDataService  { UserData       GetUserData(string id); }
public interface IMenuService      { List<Menu>     GetMenus(string id); }
public interface IUserItemsService { List<UserItem> GetUserItems(string id); }

Here is an abstract service client class that implements the recurring idiomatic service client code in generic form:

public abstract class BaseHttpServiceClient<TEntity, TPrimaryKey> where TEntity : class
{
    private string remoteUri;

    protected BaseHttpServiceClient(string remoteUri) { this.remoteUri = remoteUri; }

    protected virtual TEntity GetRemoteItem(TPrimaryKey id)
    {
        TEntity testData = null;
        try
        {
            using (HttpClient httpClient = new HttpClient())
            {
                string requestUri = this.remoteUri + id.ToString();

                HttpRequestMessage testRequest = new HttpRequestMessage(HttpMethod.Get, requestUri);

                HttpResponseMessage response = httpClient.SendAsync(testRequest).Result;
                if (response.IsSuccessStatusCode)
                {
                    var jsonData = response.Content.ReadAsStringAsync().Result;
                    testData = JsonConvert.DeserializeObject<TEntity>(jsonData);
                }
            }
        }
        catch (Exception ex)
        {           
        }

        return testData;
    }
}

And here are three implementation classes for the dependencies needed by the API class. Note that they all derive from the BaseHttpServiceClient<TEntity, TPrimaryKey> abstract class and supply the types that they service and the data type of the primary id property for each type as type arguments to the base class.

public class UserDataService : BaseHttpServiceClient<UserData, string>, IUserDataService
{
    public UserDataService(string remoteUri) : base(remoteUri) {}
    public UserData GetUserData(string id) { return this.GetRemoteItem(id); }
}

public class MenuService : BaseHttpServiceClient<List<Menu>, string>, IMenuService
{
    public MenuService(string remoteUri) : base(remoteUri) {}
    public List<Menu> GetMenus(string id) { return this.GetRemoteItem(id); }
}

public class UserItemsService : BaseHttpServiceClient<List<UserItem>, string>, IUserItemsService
{
    public UserItemsService(string remoteUri) : base(remoteUri) {}
    public List<UserItem> GetUserItems(string id) { return this.GetRemoteItem(id); }
}

Each Service class uses the GetRemoteItem method to retrieve and return instances of their bound data object types.

Here are the rest of the classes:

public class TestModel
{
    public string Name{get;set;}
    public string PublisherId{get;set;}
    public string AccountType{get;set;}

    public UserData UserData {get;set;}         //There will be a UserData model
    public List<Menu> MenuList {get;set;}       //There will be a Menu model
    public List<UserItem> UserItems {get;set;}  //There will be a UserItem model
}

public class UserData {}
public class Menu {}
public class UserItem {}

Overall, the above code net adds about approximately 8 lines of code over your sample version, but it adds in two layers of dependency injection and enables the ability to swap in different implementations as needed, like mocks for unit testing. This is especially useful if you follow the mockist style of unit testing using solitary tests.

Here is a dotnetfiddle showing a compilable version of this code.

Tyree Jackson
  • 2,588
  • 16
  • 22
  • Thanks Tyree. Yes, it doesn't make sense to instantiate a class to get a list of it's own type. Currently, I have quite a lot of model classes for each API return type as they are all different. So I have a Model class for each view and it has instances of several other Models. I'm trying to understand a good design to place the logic of calling APIs and returning the object of the result. If I place it in Controller then it becomes fat but atleast all logic is in one place. Is the resolution to have logic classes for each view and several domain models? – kinjaldave Jul 29 '15 at 23:15
  • @kinjaldave It sounds that the problem that you are trying to solve is the weight of maintaining the number of models/apis that you have. My recommendations to you are to fully implement 2-3 of these models from controller to database to get an idea of a good percentage of the responsibilities and concerns that you will need to deal with; try to follow the SOLID principles as much as you can; and employ appropriate best practices. If you want, update your question with an example of the full stack one of these models and I'll be happy to review and make recommendations about it. – Tyree Jackson Jul 30 '15 at 03:21
  • @kinjaldave Also you may want to check out my answer http://stackoverflow.com/questions/31646525/net-managing-layers-relationships/31659634#31659634 to this other question to get some ideas on how you can reduce the volume of your code by applying abstractions and leverage parametric abstractions to encapsulate your patterns for addressing your concerns. – Tyree Jackson Jul 30 '15 at 03:26
  • @kinjaldave Sorry I did not see the last line of your comment. Yes, the resolution is to separate your concerns into logic/business classes for each *model* so that those can be reused. You also separate the concerns of managing and validating the data from the concern of data I/O. Essentially Controller <-> IBusiness <-> IDataAccess <-> Data Storage – Tyree Jackson Jul 31 '15 at 15:09
  • Hey @Tyree, I completely missed to follow up on this thread. If you're available then can you please suggest me a good approach based on the code added in my main question? Apologies for this delay. – kinjaldave Aug 14 '15 at 23:07
  • 1
    @kinjaldave I've updated the answer with a modified version of your sample code along with a breakdown of the changes. – Tyree Jackson Aug 16 '15 at 07:31
  • Thanks @Tyree for your time. I'm starting to migrate to the pattern you have mentioned. I'll follow up if I get stuck somewhere. – kinjaldave Aug 17 '15 at 21:03
  • @kinjaldave Sure thing. Good luck! – Tyree Jackson Aug 17 '15 at 21:16