Separating your View From your View Model
Thinking deeply about this one for a second, it's not possible to decouple your View
from your View Model
. You can't start creating a web page without anticipating in some way or another what pieces of information are going to be displayed on the page and where - because that's precisely what writing HTML code IS. If you don't decide at least one of those two things, there isn't any HTML code to write. So if you have a page that displays information coming from your controller, you need to define a view.
The View Model
that you pass to your view should represent only the data fields that are to be displayed for a single view (or partial view) only. It's not "decouple-able", because you will never require multiple implementations of it - it is free of logic and hence there is no other implementation of it. It's the other pieces of your application that require decoupling in order to make them reusable and maintainable.
Even if you used the dynamic ViewBag
and used reflection to determine the properties that are contained within it to display your entire page dynamically, eventually you'd have to decide where that information is going to be displayed and in what order. If your are writing any of your HTML code anywhere other than within your view and related helpers, or executing anything other than display logic in your view, then you're probably breaking one of the fundamental principles of MVC.
All is not lost though, keep reading...
Developing a View Independently of the View Model
In terms two people separately developing your view and model independently
(as you quite clearly asked in the question), it's completely fine to have a view with no model defined. Just remove the @model
completely from the view, or comment it out ready to be uncommented later.
//@model RegistrationViewModel
<p>Welcome to the Registration Page</p>
Without a @model
defined, you don't have to pass through a model from your controller to your view:
public class HomeController : Controller
{
[HttpGet]
public ActionResult Index()
{
// Return the view, without a view model
return View();
}
}
You can also use non-strongly typed versions of the the HTML helpers for MVC. So with a view @model
defined, you might have written this:
@Html.LabelFor(m => m.UserName)
@Html.TextBoxFor(m => m.UserName)
Instead, use the versions without the For
at the end of the name, these accept a string as a name instead of referring directly to your model:
@Html.Label("UserName")
@Html.TextBox("UserName")
You can update these later with the strongly typed versions of the helpers later on when you have a View Model finished for the page. This will make your code a bit more robust later on.
General Comments on Objects in ASP.NET MVC
On the back of the comments, I'll attempt to show you with code how I tend to lay out my code in MVC and the different objects that I use in order to separate things out... which will truly make your code more maintainable by multiple people. Sure, it is a bit of an investment in time, but it's well worth it in my opinion as you application grows out.
You should have different classes for different purposes, some cross layers and some reside in a specific layer and aren't accessed from outside those layers.
I normally have the following types of models with my MVC projects:
Domain Models
- Models that represent the the rows in the database, I tend to manipulate these ONLY in my service layer because I use Entity Framework so I don't have a 'data access layer' as such.
DTOs
- Data Transfer objects, for passing specific data between the Service Layer
and UI Layer
View Models
- Models that are just referenced within your view and controllers, you map your DTOs to these before you pass them to your view.
Here's how I make use of them (You asked for code, so here's an example that I just drummed together that is similar to yours, but just for a simple registration):
Domain Model
Here's a domain model that simply represents a User
and it's columns as they are in the database. My DbContext
uses domain models and I manipulate domain models in my Service Layer
.
public User
{
public string UserName { get; set; }
public string Password { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
}
Data Transfer Objects (DTOs)
Here are some data transfer objects that I map in my UI Layer
in my controllers and pass to my Service Layer
and vice versa. Look how clean they are, they should contain only the fields required for passing data back and forth between layers, each one should have a specific purpose, like to be received or returned by a specific method in your service layer.
public class RegisterUserDto()
{
public string UserName { get; set; }
public string Password { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
}
public class RegisterUserResultDto()
{
public int? NewUserId { get; set; }
}
View Models
Here's a view model which lives in my UI layer
only. It is specific to a single view and is never touched within your service layer! You can use this for mapping the values that are posted back to your controller, but you don't have to - you could have a whole new model specifically for this purpose.
public class RegistrationViewModel()
{
public string UserName { get; set; }
public string Password { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
}
Service Layer
Here's the code for the service layer. I have an instance of the DbContext which uses the Domain Models to represent the data. I map the response of the registration into a DTO
that I created specifically for the response of the RegisterUser()
method.
public interface IRegistrationService
{
RegisterUserResultDto RegisterUser(RegisterUserDto registerUserDto);
}
public class RegistrationService : IRegistrationService
{
public IDbContext DbContext;
public RegistrationService(IDbContext dbContext)
{
// Assign instance of the DbContext
this.DbContext = dbContext;
}
// This method receives a DTO with all of the data required for the method, which is supposed to register the user
public RegisterUserResultDto RegisterUser(RegisterUserDto registerUserDto)
{
// Map the DTO object ready for the data access layer (domain)
var user = new User()
{
UserName = registerUserDto.UserName,
Password = registerUserDto.Password,
Email = registerUserDto.Email,
Phone = registerUserDto.Phone
};
// Register the user, pass the domain object to your DbContext
// You could pass this up to your Data Access LAYER if you wanted to, to further separate your concerns, but I tend to use a DbContext
this.DbContext.EntitySet<User>.Add(user);
this.DbContext.SaveChanges();
// Now return the response DTO back
var registerUserResultDto = RegisterUserResultDto()
{
// User ID generated when Entity Framework saved the `User` object to the database
NewUserId = user.Id
};
return registerUserResultDto;
}
}
Controller
In the controller we map a DTO to send up to the service layer and in return we receive a DTO back.
public class HomeController : Controller
{
private IRegistrationService RegistrationService;
public HomeController(IRegistrationService registrationService)
{
// Assign instance of my service
this.RegistrationService = registrationService;
}
[HttpGet]
public ActionResult Index()
{
// Create blank view model to pass to the view
return View(new RegistrationViewModel());
}
[HttpPost]
public ActionResult Index(RegistrationViewModel requestModel)
{
// Map the view model to the DTO, ready to be passed to service layer
var registerUserDto = new RegisterUserDto()
{
UserName = requestModel.UserName,
Password = requestModel.Password,
Email = requestModel.Email,
Phone = requestModel.Phone
}
// Process the information posted to the view
var registerUserResultDto = this.RegistrationService.RegisterUser(registerUserDto);
// Check for registration result
if (registerUserResultDto.Id.HasValue)
{
// Send to another page?
return RedirectToAction("Welcome", "Dashboard");
}
// Return view model back, or map to another view model if required?
return View(requestModel);
}
}
View
@model RegistrationViewModel
@{
ViewBag.Layout = ~"Views/Home/Registration.cshtml"
}
<h1>Registration Page</h1>
<p>Please fill in the fields to register and click submit</p>
@using (Html.BeginForm())
{
@Html.LabelFor(x => x.UserName)
@Html.TextBoxFor(x => x.UserName)
@Html.LabelFor(x => x.Password)
@Html.PasswordFor(x => x.Password)
@Html.LabelFor(x => x.Email)
@Html.TextBoxFor(x => x.Email)
@Html.LabelFor(x => x.Phone)
@Html.TextBoxFor(x => x.Phone)
<input type="submit" value="submit" />
}
Duplication of Code
You are quite right about what you said in the comments, there is a bit (or a lot) of object code duplication, but if you think about it, you need to do this if you want to truly separate them out:
View Models != Domain Models
In many cases the information you display on a view doesn't contain information from only a single domain model
and some of the information should never make it down to your UI Layer
because it should never be displayed to the application user - such as the hash of a user's password.
In your original example you have the model GuestResponse
with validation attributes decorating the fields. If you made your GuestResponse
object double up as a Domain Model
and View Model
, you have polluted your domain models with attributes that may only be relevant to your UI Layer
or even a single page!
If you don't have tailored DTOs for your service layer
methods, then when you add a new field to whatever class it is that the method returns, you'll have to update all of the other methods that return that particular class to include that piece of information too. Chances are you'll hit a point where you add a new field is only relevant or calculated in the one single method you're updating to returning it from? Having a 1:1 relat