MVC


What features would you like to see in ASP.NET MVC 4?

E-mail Print PDF
User Rating: / 12
PoorBest 
There are no translations available.

If there was a single feature I would like to see in ASP.NET MVC 4 that would be to remove/deprecate ViewBag/ViewData. Those two constructs lead to very ugly code in the views and should be avoided. Here are few of the things I hate about them:

 

  • They are not strongly typed and you need to cast in your views in order to obtain the actual type
  • They are not refactor friendly because they rely on magic strings
  • They lead to brittle unit tests because of the magic strings
  • They lead to spaghetti code in the views

 

Here’s the diff patch I would love to see applied for the ViewDataDictionary.cs class in ASP.NET MVC 4:

 

diff --git ViewDataDictionary.cs ViewDataDictionary.cs
index 4c6299f..1965e3e 100644
--- ViewDataDictionary.cs
+++ ViewDataDictionary.cs
@@ -95,12 +95,10 @@ namespace System.Web.Mvc {
 
         public object this[string key] {
             get {
-                object value;
-                _innerDictionary.TryGetValue(key, out value);
-                return value;
+                throw new Expcetion("Don't use ViewData");
             }
             set {
-                _innerDictionary[key] = value;
+                throw new Expcetion("Don't use ViewData");
             }
         }
 
Last Updated on Sunday, 20 February 2011 12:40
 

Sample MVC project (updated)

E-mail Print PDF
User Rating: / 18
PoorBest 
There are no translations available.

I’ve updated the sample MVC project I wrote for using ASP.NET MVC 3 and the Razor view engine.

A minor change is that I no longer use the FluentValidationModelValidatorProvider but the standard one. So I decorate the view model with the necessary data annotations:

 

[Validator(typeof(UserViewModelValidator))]
public class UserViewModel
{
    public int Id { get; set; }

    [DisplayName("First name *")]
    public string FirstName { get; set; }

    [DisplayName("Last name *")]
    public string LastName { get; set; }

    public int? Age { get; set; }
}

 

And the validator becomes:

 

public class UserViewModelValidator : AbstractValidator<UserViewModel>
{
    public UserViewModelValidator()
    {
        RuleFor(x => x.FirstName)
            .NotEmpty()
            .WithMessage("First name is required");

        RuleFor(x => x.LastName)
            .NotEmpty()
            .WithMessage("Last name is required");
    }
}

 

 

And here are the corresponding views using Razor:

 

Index.cshtml

@model IEnumerable<SampleMvc.Web.Models.UserViewModel>

@{
    ViewBag.Title = "Index";
}

<h2>Indexh2>
@(Html
    .Grid<UserViewModel>(Model)
    .Columns(column => {
        column.Custom(model => Html.Partial("_TableLinks", model));
        column.For(model => model.FirstName);
        column.For(model => model.LastName);
        column.For(model => model.Age);
    })
)

<p>
    @(Html.ActionLink<UsersController>(c => c.New(), "Create New"))
p>

 

_TableLinks.cshtml

@model SampleMvc.Web.Models.UserViewModel

@(Html.ActionLink<UsersController>(c => c.Edit(Model.Id), "Edit")) |
@(Html.ActionLink<UsersController>(c => c.Show(Model.Id), "Details")) |
@using (Html.BeginForm<UsersController>(c => c.Destroy(Model.Id))) 
{
    @Html.HttpMethodOverride(HttpVerbs.Delete)
    <input type="submit" value="Delete" />
}

 

Edit.cshtml

@model SampleMvc.Web.Models.UserViewModel
@{
    ViewBag.Title = "Edit";
}

<h2>Edith2>
@using (Html.BeginForm<UsersController>(c => c.Update(null)))
{
    @Html.ValidationSummary(true)
    @Html.HttpMethodOverride(HttpVerbs.Put)
    @Html.HiddenFor(model => model.Id)
    @Html.EditorForModel()
    <p>
        <input type="submit" value="Save" />
    p>
}
<div>
    @(Html.ActionLink<UsersController>(c => c.Index(), "Back to List"))
div>

 

New.cshtml

@model SampleMvc.Web.Models.UserViewModel

@{
    ViewBag.Title = "New";
}

<h2>Newh2>
@using (Html.BeginForm<UsersController>(c => c.Create(null))) 
{
    @Html.ValidationSummary(true)
    @Html.EditorForModel()
    <p>
        <input type="submit" value="Create" />
    p>
}
<div>
    @(Html.ActionLink<UsersController>(c => c.Index(), "Back to List"))
div>

 

Show.cshtml

@model SampleMvc.Web.Models.UserViewModel
           
@{
    ViewBag.Title = "Show";
}

<h2>Showh2>

@Html.DisplayForModel()
<p>
    @(Html.ActionLink<UsersController>(c => c.Edit(Model.Id), "Edit")) |
    @(Html.ActionLink<UsersController>(c => c.Index(), "Back to List"))
p>

 

UserViewModel.cshtml editor template

@model SampleMvc.Web.Models.UserViewModel

<fieldset>
    <legend>Fieldslegend>
            
    <div class="editor-label">
        @Html.LabelFor(model => model.FirstName)
    div>
    <div class="editor-field">
        @Html.EditorFor(model => model.FirstName)
        @Html.ValidationMessageFor(model => model.FirstName)
    div>
            
    <div class="editor-label">
        @Html.LabelFor(model => model.LastName)
    div>
    <div class="editor-field">
        @Html.EditorFor(model => model.LastName)
        @Html.ValidationMessageFor(model => model.LastName)
    div>
            
    <div class="editor-label">
        @Html.LabelFor(model => model.Age)
    div>
    <div class="editor-field">
        @Html.EditorFor(model => model.Age)
        @Html.ValidationMessageFor(model => model.Age)
    div>
fieldset>

UserViewModel.cshtml display template

@model SampleMvc.Web.Models.UserViewModel

<fieldset>
    <legend>Fieldslegend>
        
    <div class="display-label">Iddiv>
    <div class="display-field">@Html.DisplayFor(x => x.Id)div>
        
    <div class="display-label">FirstNamediv>
    <div class="display-field">@Html.DisplayFor(x => x.FirstName)div>
        
    <div class="display-label">LastNamediv>
    <div class="display-field">@Html.DisplayFor(x => x.LastName)div>
        
    <div class="display-label">Agediv>
    <div class="display-field">@Html.DisplayFor(x => x.Age)div>
fieldset>

 

The source code is available on github.

Last Updated on Sunday, 20 February 2011 11:33
 

How we do ASP.NET MVC

E-mail Print PDF
User Rating: / 42
PoorBest 
There are no translations available.

Sample MVC Solution

In this post I will show a sample ASP.NET MVC 2.0 project structure illustrating different concepts such as data access, user input validation and mapping between the domain and the view model. The project is still under construction but the source code is available at github.

I will illustrate the usage of the following frameworks:

  • MvcContrib bringing useful extension methods and strongly typed helpers to ASP.NET MVC
  • AutoMapper enabling easy mapping between the domain and the view models
  • FluentValidation - a small validation library for .NET that uses a fluent interface and lambda expressions for building validation rules for your business objects
  • NHibernate – a popular ORM in the .NET world
  • FluentNHibernate - a statically compiled alternative to NHibernate's standard hbm xml mapping
  • Spring.NET – object container and dependency Injection framework for .NET
  • Rhino.Mocks - A dynamic mock object framework for the .Net platform. It's purpose is to ease testing by allowing the developer to create mock implementations of custom objects and verify the interactions using unit testing

 

Armed with this arsenal of frameworks let’s start exploring the solution structure. I’ve opted for 2 projects solution but in many real world applications more levels of abstraction could be brought to the business layer. Personally I favor to have less big assemblies rather than many small assemblies into the solution. Fewer the assemblies, faster the load time and faster the IDE. In this case particular attention should be brought to bring proper separation of concerns inside the same assembly

 

ASP.Net MVC project structure

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

The domain consists of a single User class and a repository interface defining the different operations on this model:

ASP.Net MVC Domain with Repository

 

Mapping

The next step is to define the mapping between our domain and a relational model expressed in a fluent manner:

public class UserMap : ClassMap<User>
{
public UserMap()
{
Table(
"users");
Id(x
=> x.Id, "usr_id");
Map(x
=> x.FirstName, "usr_firstname");
Map(x
=> x.LastName, "usr_lastname");
Map(x
=> x.Age, "usr_age");
}
}

 

And here’s the implementation of the repository:

public class SqlUsersRepository : HibernateDaoSupport, IUsersRepository
{
public IEnumerable<User> GetUsers()
{
return HibernateTemplate.LoadAll<User>();
}

public User Get(int id)
{
return HibernateTemplate.Get<User>(id);
}

public void Delete(int id)
{
HibernateTemplate.Delete(
new User { Id = id });
}

public int Save(User user)
{
return (int)HibernateTemplate.Save(user);
}

public void Update(User user)
{
HibernateTemplate.Update(user);
}
}

 

HibernateDaoSupport is a base class defined by the Spring Framework managing SQL transactions and NHibernate session.

Once we have implemented the data access layer we could move on to the web part. The application consists of a single RESTful UsersController allowing the standard CRUD operations with our users model. As all our views are strongly typed we shall define a view model for each view and a mapping between the domain model and this view model. In our simple case the view model will simply have the same structure as the domain model but in real world scenarios it will be a projection of the domain model for a particular view.

[Validator(typeof(UserViewModelValidator))]
public class UserViewModel
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int? Age { get; set; }
}

And the respective validator:

public class UserViewModelValidator : AbstractValidator<UserViewModel>
{
public UserViewModelValidator()
{
RuleFor(x
=> x.FirstName)
.NotEmpty()
.WithMessage(
"First name is required")
.DisplayName(
"First name *");

RuleFor(x
=> x.LastName)
.NotEmpty()
.WithMessage(
"Last name is required")
.DisplayName(
"Last name *");
}
}

And mapper between the domain and view model:

 

public class UserMapper : IMapper
{
static UserMapper()
{
Mapper.CreateMap
<User, UserViewModel>();
Mapper.CreateMap
<UserViewModel, User>();
}

public object Map(object source, Type sourceType, Type destinationType)
{
return Mapper.Map(source, sourceType, destinationType);
}
}

 

This bidirectional mapper will be used by our RESTful controller:

public class UsersController : BaseController<IUsersRepository>
{
public UsersController(IUsersRepository repository, IMapper userMapper)
:
base(repository, userMapper)
{ }

[AutoMap(
typeof(IEnumerable<User>), typeof(IEnumerable<UserViewModel>))]
public ActionResult Index()
{
// return all users
var users = Repository.GetUsers();
return View(users);

}

public ActionResult New()
{
// return an HTML form for describing a new user
return View();
}

[HttpPost]
[AutoMap(
typeof(User), typeof(UserViewModel))]
public ActionResult Create(UserViewModel userView)
{
// create a new user
if (!ModelState.IsValid)
{
return View("New", userView);
}
var user
= (User)ModelMapper.Map(userView, typeof(UserViewModel), typeof(User));
Repository.Save(user);
return RedirectToAction("Index", "Users");
}

[AutoMap(
typeof(User), typeof(UserViewModel))]
public ActionResult Show(int id)
{
// find and return a specific user
var user = Repository.Get(id);
return View(user);
}

[AutoMap(
typeof(User), typeof(UserViewModel))]
public ActionResult Edit(int id)
{
// return an HTML form for editing a specific user
var user = Repository.Get(id);
return View(user);
}

[HttpPut]
public ActionResult Update(UserViewModel userView)
{
// find and update a specific user
if (!ModelState.IsValid)
{
return View("Edit", userView);
}
var user
= (User)ModelMapper.Map(userView, typeof(UserViewModel), typeof(User));
Repository.Update(user);
return RedirectToAction("Index", "Users");
}

[HttpDelete]
public ActionResult Destroy(int id)
{
// delete a specific user
Repository.Delete(id);
return RedirectToAction("Index", "Users");
}
}

 

Notice the AutoMapAttribute. This is a custom attribute allowing us to automatically convert the domain model retrieved by the repository to a view model and present it to the view:

[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class AutoMapAttribute : ActionFilterAttribute
{
public Type SourceType { get; private set; }
public Type DestType { get; private set; }

public AutoMapAttribute(Type sourceType, Type destType)
{
SourceType
= sourceType;
DestType
= destType;
}

public override void OnActionExecuted(ActionExecutedContext filterContext)
{
base.OnActionExecuted(filterContext);
var controller
= filterContext.Controller as IModelMapperController;
if (controller == null)
{
return;
}
var model
= filterContext.Controller.ViewData.Model;
if (model != null && SourceType.IsAssignableFrom(model.GetType()))
{
var viewModel
= controller.ModelMapper.Map(model, SourceType, DestType);
filterContext.Controller.ViewData.Model
= viewModel;
}
}
}

The OnActionExecuted method will be called after each action method has finished executing and it will use the model passed to the view and convert it to the appropriate view model. It simply substitutes the ViewData.Model property with the appropriate view model to finally pass it to the view for rendering.

 

The controller follows the standard RESTful conventions for naming the action and the HTTP verbs:

 

URL

HTTP Verb

Action

Description

/users/index GET Index() return all users
/users/show/id GET Show(int id) return a specific user
/users/new GET New() return an HTML form for creating a new user
/users/create POST Create(UserViewModel userView) create a new user
/users/edit/id GET Edit(int id) return an HTML form for editing a specific user
/users/update PUT Update(UserViewModel userView) update a specific user
/users/destroy/id DELETE Destroy(int id) delete a specific user

 

Because most browsers support submitting HTML forms only using the GET and POST verbs, there’s the Html.HttpMethodOverride helper which generates a hidden field inside the form and is used by the routing engine to dispatch to the proper controller action.

Unit Tests

Unit testing our controller actions is essential. I’ve been using the excellent MVCContrib.TestHelper in conjunction with the Rhino.Mocks framework to test controllers in isolation by mocking the HTTP context. Here’s how the test logic looks like:

[TestClass]
public class UsersControllerTests : TestControllerBuilder
{
private UsersController _sut;
private IUsersRepository _repositoryStub;
private IMapper _userMapperStub;

public UsersControllerTests()
{
}

private TestContext testContextInstance;

///  
///Gets or sets the test context which provides
///information about and functionality for the current test run.
/// 
public TestContext TestContext
{
get
{
return testContextInstance;
}
set
{
testContextInstance
= value;
}
}

// Use TestInitialize to run code before running each test
[TestInitialize()]
public void MyTestInitialize()
{
_repositoryStub
= MockRepository.GenerateStub<IUsersRepository>();
_userMapperStub
= MockRepository.GenerateStub<IMapper>();
_sut
= new UsersController(_repositoryStub, _userMapperStub);
InitializeController(_sut);
}

[TestMethod]
public void UsersController_Index()
{
// arrange
var users = new User[0];
_repositoryStub.Stub(x
=> x.GetUsers()).Return(users);

// act
var actual = _sut.Index();

// assert
actual
.AssertViewRendered()
.WithViewData
<User[]>()
.ShouldBe(users);
}

[TestMethod]
public void UsersController_New()
{
// act
var actual = _sut.New();

// assert
actual
.AssertViewRendered();
}

[TestMethod]
public void UsersController_Create_Invalid_Model_State()
{
// arrange
_sut.ModelState.AddModelError("FirstName", "First name is required");
var userView
= new UserViewModel();

// act
var actual = _sut.Create(userView);

// assert
actual
.AssertViewRendered()
.ForView(
"New")
.WithViewData
<UserViewModel>()
.ShouldBe(userView);
}

[TestMethod]
public void UsersController_Create_Success()
{
// arrange
var userView = new UserViewModel();
var user
= new User();
_userMapperStub
.Stub(x
=> x.Map(userView, typeof(UserViewModel), typeof(User)))
.Return(user);

// act
var actual = _sut.Create(userView);

// assert
actual
.AssertActionRedirect()
.ToAction
<UsersController>(c => c.Index());
_repositoryStub.AssertWasCalled(x
=> x.Save(user));
}

[TestMethod]
public void UsersController_Show()
{
// arrange
var id = 1;
var user
= new User();
_repositoryStub.Stub(x
=> x.Get(id)).Return(user);

// act
var actual = _sut.Show(id);

// assert
actual
.AssertViewRendered()
.WithViewData
<User>()
.ShouldBe(user);
}

[TestMethod]
public void UsersController_Edit()
{
// arrange
var id = 1;
var user
= new User();
_repositoryStub.Stub(x
=> x.Get(id)).Return(user);

// act
var actual = _sut.Edit(id);

// assert
actual
.AssertViewRendered()
.WithViewData
<User>()
.ShouldBe(user);
}

[TestMethod]
public void UsersController_Update_Invalid_Model_State()
{
// arrange
_sut.ModelState.AddModelError("FirstName", "First name is required");
var userView
= new UserViewModel();

// act
var actual = _sut.Update(userView);

// assert
actual
.AssertViewRendered()
.ForView(
"Edit")
.WithViewData
<UserViewModel>()
.ShouldBe(userView);
}

[TestMethod]
public void UsersController_Update_Success()
{
// arrange
var userView = new UserViewModel();
var user
= new User();
_userMapperStub
.Stub(x
=> x.Map(userView, typeof(UserViewModel), typeof(User)))
.Return(user);

// act
var actual = _sut.Update(userView);

// assert
actual
.AssertActionRedirect()
.ToAction
<UsersController>(c => c.Index());
_repositoryStub.AssertWasCalled(x
=> x.Update(user));
}

[TestMethod]
public void UsersController_Destroy()
{
// arrange
var id = 1;

// act
var actual = _sut.Destroy(id);

// assert
actual
.AssertActionRedirect()
.ToAction
<UsersController>(c => c.Index());
_repositoryStub.AssertWasCalled(x
=> x.Delete(id));
}
}

Views

And the last but not least part of the picture are the views:

Index.aspx

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>
<%@ Import Namespace="SampleMvc.Web.Models" %>
<%@ Import Namespace="SampleMvc.Web.Controllers" %>

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
Index
</asp:Content>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>Index
</h2>
 
<%: Html.Grid>(Model)
.Columns(column => {
column.For("TableLinks").Named("");
column.For(model => model.FirstName);
column.For(model => model.LastName);
column.For(model => model.Age);
})
%>
<p>
<%: Html.ActionLink>(c => c.New(), "Create New") %>
</p> 
</asp:Content> 

TableLinks.ascx

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
<%@ Import Namespace="SampleMvc.Web.Controllers" %>
<td>
<%: Html.ActionLink>(c => c.Edit(Model.Id), "Edit") %> |
<%: Html.ActionLink>(c => c.Show(Model.Id), "Details") %> |
<% using (Html.BeginForm>(c => c.Destroy(Model.Id))) { %>
<%: Html.HttpMethodOverride(HttpVerbs.Delete) %>
<input type="submit" value="Delete" />
<% } %>
</td>
 

Edit.aspx

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>
<%@ Import Namespace="SampleMvc.Web.Controllers" %>

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
Edit
</asp:Content> 

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>Edit
</h2>  
<% using (Html.BeginForm>(c => c.Update(null))) {%>
<%: Html.ValidationSummary(true) %>
<%: Html.HttpMethodOverride(HttpVerbs.Put) %>
<%: Html.HiddenFor(model => model.Id) %>
<%: Html.EditorForModel() %>
<p>
<input type="submit" value="Save" />
</p>
  <% } %>
<div>
<%: Html.ActionLink>(c => c.Index(), "Back to List") %>
</div> 
</asp:Content> 

New.aspx

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>
<%@ Import Namespace="SampleMvc.Web.Controllers" %>

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
New
</asp:Content> 

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>New
</h2>
<% using (Html.BeginForm>(c => c.Create(null))) {%>
<%: Html.ValidationSummary(true) %>

<%: Html.EditorForModel() %>
<p>
<input type="submit" value="Create" />
</p>
<% } %>
<div>
<%: Html.ActionLink>(c => c.Index(), "Back to List") %>
</div>
</asp:Content> 

UserViewModel.ascx editor template

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>

<fieldset>
<legend>Fields</legend >
<div class="editor-label">
<%: Html.LabelFor(model => model.FirstName) %>
 </div>
 
<div class="editor-field">
<%: Html.TextBoxFor(model => model.FirstName) %>
<%: Html.ValidationMessageFor(model => model.FirstName) %>
 </div>
<div class="editor-label">
<%: Html.LabelFor(model => model.LastName) %>  

 </div>
 
<div class="editor-field">
<%: Html.TextBoxFor(model => model.LastName) %>
<%: Html.ValidationMessageFor(model => model.LastName) %>
 
 </div>
<div class="editor-label">
<%: Html.LabelFor(model => model.Age) %>
 </div>
 <div class="editor-field">
<%: Html.TextBoxFor(model => model.Age) %>
<%: Html.ValidationMessageFor(model => model.Age) %>
 
</div>
 
</fieldset>

A very important aspect of the views is that they are all strongly typed to a view model and use only strongly typed helpers, even for generating the links. One day when Visual Studio becomes power enough you will be able to seamlessly refactor/rename a property without worrying about all those magic strings.

 

Further enchantments will include adding client side validation using the jQuery validate plugin in order to improve the user experience and preserve bandwidth.

Last Updated on Friday, 14 May 2010 12:58