How can I refactor my database access code outside my MVC project but keep my viewmodels inside?
Asked Answered
D

5

20

I have an asp.net-mvc website with the following folders:

  • Controllers
  • Scripts
  • Views
  • ViewModels
  • Models
  • DomainModel

I now want to access a lot of this business logic and database access code and data in another .net app (a windows console app so not web at all) so i am refactoring to remove as much stuff as possible outside of the MVC project and into other projects in the solution so that code could be shared with this other solutions.

I have 2 main issues;

  1. My main issue is that I am struggling to find a place to put the code that generates the ViewModel because a lot of this code I would like to reuse in my console app as the console app sends email which requires the same data that is in the view.

  2. Another main issue is that i am struggling to see how i can move my database access code out of the MVC project while still having the ViewModels inside when many of my functions that instantiate my viewmodels start with a bunch of database access code.

Here is a bit of the detail and my process so far:

Step 1 - Move DomainModel into another project - success

So moving the DomainModel project was simple (as that was a lot of raw objects with some business logic on top - nothing web about it).

Step 2 - Thin out controllers - success

I have thinned out as much of my controllers as possible and moved any business logic or complicated data access logic into the Models folder. When i tried to move the models folder outside the MVC project a few things broke:

Step 3 - Attempt to move Models folder outside MVC Project - struggle

In thinning out the controllers, I have a number of different controller actions that go to a model class and return my ViewModel that i pass back into the view. Something like this (in my controller class):

 public ActionResult ApplicationDetail(int id)
 {
      AppDetailViewModel applicationViewModel = Model.GenerateAppDetailViewModel(id);
      return View(applicationViewModel);
 }

So files in my Model folder are dependency on the ViewModel classes. I do want to centralize the GenerateAppDetailViewModel() function as that is used in multiple different controllers. Also, in my console app (which sends out email, i often want to get all the data that happens to be on some view so my code "wants" to leverage the viewmodel as well .. if i move it out of the MVC project then I can reuse but i think have the dependency issue (clearly i don't need SelectListItem in my console app but in other cases where they are just container objects of different data needed to generate a view I do want to reuse)

or another thing that broke was the dependency on:

System.Web.Mvc

because I have a lot of code that:

  1. queries a table in a database
  2. Converts that into a collection of objects (i am using nhibernate)
  3. Convert that into either some DTO object (which is sitting in ViewModels folder) or a List of SelectListItem objects (to be used to populate dropdowns in the view) which is part of System.web.mvc.

I wanted to look for suggestions on the best way to break out this dependency so i can move as much code out of the MVC project as possible for reuse.

The issue is that if i try to suck my ViewModel code into the Model folder and into another project then again i get stuck because the ViewModel classes have a lot of dependency on

System.Web.Mvc

due to things like SelectListItem.

Should i have 2 view models folders (one in the MVC project that has specific system.web.mvc references and another one that sits in a different project?). It seems like the dependency on SelectListItem is what keeps causing the contention

In most examples that i have seen ViewModels do have a dependency on System.Web.Mvc such as this tutorial

I have seen these questions:

which are sort of related but not sure they answer my specific overall refactoring question stated.

Dorey answered 3/7, 2013 at 17:58 Comment(5)
Is the other app NOT a web project? Is it a windows app?Zeebrugge
yes . . its is a windows console appsDorey
@leora, please expand on your goals. As Darin pointed out, View Models should be stored within your MVC project. But you seem to want something more subtle. If I understand you correctly, you have refactored some heavy logic -- moving it from your Controllers to View Models. Now, if I guess correctly, you want to organize or re-use this logic in other applications. How accurate is this?Medeah
@Dave A - i will update the question to verify. I found that some of the logic i use to generate emails need to leverage the view models as well so i need them out of the MVC project (or come up with an alternative)Dorey
@leora, to clarify further, would you want to organize view models and their dependant views together? In other words, are the view models in question used by specific views? can they also be moved along with your view models?Medeah
W
18

View models are specific to the particular application. I guess that the view models would differ between your web application and your console application. So have each application define its own view models and the corresponding mapping between the domain models and the view models. Don't have your domain models posses methods that convert them to view models because this way you are completely tying your domain layer to the UI layer which is the worst thing to happen. Use a mapping layer (which will be specific to each application type). AutoMapper is a great example of a mapping layer that you could have.

Don't even try to reuse ASP.NET MVC view models in a console application. As you have already found out they will contain references to System.Web.Mvc because for example a dropDownList in ASP.NET MVC is represented with the IEnumerable<SelectListItem> class whereas in a Console Application, god knows, maybe an IEnumerable<SomeItemViewModel>.

Conclusion: View models and mapping back and forth between the domain and view models belong to the UI layer (a.k.a ASP.NET MVC, Console, WPF, ...).

Weisler answered 5/7, 2013 at 21:20 Comment(6)
I am not trying to use my viewmodels in my console app. I am simply trying to move as much of my MVC code as possible outside the MVC project. I guess you are questioning the value of doing that. So if this code is in my MVC project where would it go (in the controller?, in another class?)Dorey
If it is MVC code, then it definitely should stay in your MVC application. Or in a separate assembly that has dependency on System.Web.Mvc but which you shouldn't be trying to reuse in other projects. What you should reuse is your domain models and business logic. That's something that could easily be moved to a separate layer which has no dependencies on MVC.Weisler
i updated the question to highlight one particular example where i send out emails from my console app and since the email data is almost identical to one of my views, i would like to leverage that ViewModel code. Clearly i don't need SelectListITem in my console app but the viewmodels that are just containers of various data and methods ideally should be reused . .Dorey
I wouldn't try to reuse any view models between different types of applications. You could have some base view models that do not contain view specific types and then have the actual view models derive from those base classes and add the specific types to the corresponding view.Weisler
In your first comment you state that "I am not trying to use my viewmodels in my console app" yet the second comment states "since the email data is almost identical to one of my views, i would like to leverage that ViewModel code". This appears to be a contradiction.Bedew
@JustinCrabtree - my point was that my goal in general was not to fully share viewmodel classes across solutions but i keep finding code that i want to avoid duplicating so its not a contradiction but rather the competing forces that i am trying to highlight . .looking for the best balance / compromiseDorey
G
2

I understand what you want to achieve, and I must tell you: it's absolutely normal to reuse the same ViewModels for different UI layers. After all, VM is part of MVVM pattern, and that pattern is all about separating concerns. And, separation means the ability to replace lower-level layers with another implementations. That is the purpose of separate software layers (among others).

  1. For the beginning, you must assume that your MVC web project is MVVM-based. That will mentally help you make correct decisions. I guess you already did this since you use ViewModel term.
  2. Make ViewModels become platform independent. It's possible, and it has nothing to do with SelectListItem. After all, SelectListItem simply contains the option text/value pair, plus the flag whether it's selected or not. You obviously can express the same information in a different way. After passing this Generic kind of ViewModel to the MVC, you can convert the generic "SelectListItem" to the MVC SelectListItem. Yes, it is sort of mapping, and it does not matter whether it takes place in the MVC view or before passing it to the View. But it must happen on the UI layer (MVC web project), since it's a platform specific mapping concern.
  3. You mentioned data access code: that's a separate software layer, usually abstractly injected into the ViewModels. I foresee no problems with this part.

Hence, you will end up having different software layers (most likely in different .NET libraries): Data Access Layer, ViewModel layer, MVC Web layer.

There is a possibility of having MVC ViewModels as well (one passed from Controller to View) defined in the MVC project, but those will be simply handling (probably accepting) the generic ViewModels and exposing things in the MVC View-specific terms (mapping, inheritance, your imagination?). And this is normal too - after all, MVC is a web-based UI and it definitely has platform-specific differences that must be handled on the platform level, not before. MVC specific ViewModel would be a good place to handle the mapping from generic to MVC SelectListItem.

Once this refactoring is done, there's nothing stopping you from implementing another UI layer - Console application that you are mentioning, by utilizing the same generic ViewModels as the other UI layer - MVC Web project. When using the generic ViewModels in the console application, if you face the Console platform-specific issues, you can come up with the platform-specific ViewModels in the same way as I explained above for the MVC-specific ViewModels.

Gladiolus answered 10/7, 2013 at 17:10 Comment(0)
K
1

You can create viewmodels in controller with extension methods:

Controller:

 public ActionResult ApplicationDetail(int id)
 {
      var model = _serviceLayer.GetSomeModel(id); 
      var viewModel = model.CreateInstance(model);      
      return View(viewModel);
 }

Create this SomeModelExtensions in your mvc project

public class SomeModelExtensions {
      public AppDetailViewModel CreateInstance(this SomeModel model) {
           var viewModel = new AppDetailViewModel();
           // here you create viewmodel object from model with logic
           return viewModel;
      }
}
Kwh answered 4/7, 2013 at 6:21 Comment(3)
To clarify, So you are saying move as much of the model class into another project and just put the code that reference the view model inside the MVC project with extension method? Not a bad ideaDorey
Yes, look here was similar question.Kwh
But what if in my view model construction code, i need database access to get data from a few different places . . i was trying to avoid having database access code in the MVC project . .maybe that unavoidableDorey
K
1

In general I use the following setup/architecture when laying out an MVC app where I may need to reuse the models:

MVC Project: everything web related. I define ViewModels here and map them to the domain models.

Models Project: class library with all your domain logic.

Repository project: This is a class library which access the DB and the Domain Models.

The way it works is that the MVC project will use the (hopefully injected) Repository library to get the Domain Model and map it to its own ViewModel.

If you want to separate the mapping as well, you can put the Mapping Layer, as others have suggested (using AutoMapper eventually), in a separate project. The mapping layer will reference the repositories (and the domain models), while the MVC app will only reference the Mapping layer.

The problem is that the mapping layer, in creating ViewModels, will need a reference to System.Web.Mvc as you found out and you cannot escape this. This is why others have said, and I agree, that you should have a mapping layer per project.

A nice way to get around this is to have a further generic mapping layer with mapping classes defined for the common cases (like the email). Then in the specific child classes you can define the mapping for specific cases (like the ones depending on System.Web.Mvc).

So the end stack would be something like the following, dependencies going down. Of course everything should be interface based.

    MVC App                      Console App
       |                            |
       |                            |
   MVC Specific Mapper         Console Specific Mapper
                 \               /  
                  \             /
                   \           /   
                   GenericMapper <- EmailMapper and EmailViewModel can be implemented here
                     |       |      
                     |       |
                Repository   |
                     |       |
                     |       |
                    DomainModels  

The above is not struggle free and probably the effort of splitting the mapping apart is not worth the candle if there are only one or two common cases. That way, from the Generic Mapper down you are free from the System.Web.Mvc library, while above you are free to forget about DB access code (In a sense the Mapper will act as a Repository for the App).

Kidnap answered 10/7, 2013 at 10:24 Comment(1)
from your suggestion, i think i am going to start by moving my models folder in the MVC project into a Models project and see how many "breaks" i get . .that will determine next stage of refactoringDorey
G
0

I assume the MVC web will use the same data as the console app + extra fields, right?

so, what about inheritance your ViewModel from Model? That way you would be able to reuse the model and get custom fields on your ViewModel as needed.

public class AppDetailModel
    {
        public int ID { get; set; }
        public string Name { get; set; }
    }


    public class AppDetailViewModel : AppDetailModel
    {
        public string ViewProperty { get; set; }
    }
Gann answered 10/7, 2013 at 3:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.