Click here to Skip to main content
15,074,077 members
Articles / Web Development / ASP.NET
Article
Posted 28 Dec 2014

Tagged as

Stats

354.8K views
120 bookmarked

ASP.NET MVC.Template introduction

Rate me:
Please Sign up or sign in to vote.
4.79/5 (84 votes)
28 Jan 2017MIT8 min read
MVC.Template is a starter template for ASP.NET MVC based solutions

Introduction

As the name implies, it's a project starter template for ASP.NET MVC based solutions (mainly for multi-paged enteprise solutions, which could change in the future if there will be need for it).

This project was born after working on maintaining and improving few MVC projects and seeing how much of a headache these projects were bringing with them. All because of a bad project foundation, which in the long run started to ruin program's design and maintainability even more and more. Even though there are a lot of examples of good ASP.NET MVC practises on the web, collecting them all and applying in the first applications is not an easy task.

Background

There are few rules this project will try to follow:

  • Simplicity - a good indicator of any program is it's simplicity thus resulting in lack of code. Any new ideas or findings on making code simplier to understand and use will be incorporated first. Hard to understand code should be reported to the issue police at GitHub along with reasoning on why it was hard to understand and use (suggestions are also welcomed in the comments).
  • Activity - by now there are a lot of good projects which are no longer actively developed (dead). And it is very sad. MVC.Template will try to be in the loop for years to come.
  • Open - this project will always be open sourced with MIT license. Use it, like it, hate it, do whatever.
  • Up to date - any new releases of frameworks used in the project will always be updated to their newest releases. So even though we all love MVC5, it will not be supported after vNext release will be sufficient to replace it and so on.

Features

  • Model-View-ViewModel architectural design.
  • Lowercase or normal ASP.NET urls.
  • Custom membership providers.
  • Protection from CSRF, XSS, etc.
  • Easy project renaming.
  • Dependency injection.
  • Custom error pages.
  • Globalization.
  • Audit log.
  • Site map.
  • Tests.

Project structure

  • MvcTemplate - is used for keeping any project specific implementation, which can not reside in other projects. Mainly it will be custom project components, which can not be easily reused because of hard dependencies to other solution projects, like data access layer.
  • MvcTemplate.Components - should only contain reusable components. It means no references to any MvcTemplate.Xxxxx project, with an exception of MvcTemplate.Resources. This assembly is intented to keep all the reusable "goodies" which are born while implementing a specific project. So that it can be reused in the next projects and save us some more time for programming more interesting code.
  • MvcTemplate.Controllers - separates controllers and their routing configuration from other assemblies. It's known that separation of controllers has some disadvantages (like not being able to generate strongly typed helpers with T4MVC). But until someone proves this approach wrong, controllers will be separated.
  • MvcTemplate.Data - hides data access layer implementation from other assemblies. So that assemblies which are using data access layer would not have to reference frameworks like EntityFramework. Or, know implementation details about domain model mappings with view models.
  • MvcTemplate.Objects - contains all domain and view model classes in one place. Any other classes should not be here.
  • MvcTemplate.Resources - keeps all our "beloved magic strings" in healthy environment. It is also used for easy solution globalization.
  • MvcTemplate.Services - is the assembly which does the main job. And by the main job I mean managing program state, like creating, editing, deleting domain entities; sending emails; eating CPU cycles and what not. This is where the processing logic should be, not in the controller. Controllers should only act as a brainless routing switch based on services and validation outputs.
  • MvcTemplate.Tests - yet another main reason why MVC applications become as bad as they are. One of the main problem MVC architecture is trying to address is testability, and not testing MVC application should be considered a sin. This assembly covers most (~99%) of the solution asemblies code base. Mainly through unit testing.
  • MvcTemplate.Validators - separates all domain validation logic from controllers and their services. So it can be reused in other controllers if needed (e.g. account registration and profile update shares same validation concepts).
  • MvcTemplate.Web - and finally web assembly, which is kind of empty. Because it mainly consists of UI representation views, styling sheets, scripts and other client side components.

Installation

  • Download one of the release versions from GitHub, or clone develop branch to your computer.
  • Before opening the project run "Rename.exe" located in the project's root folder (running it later can cause some compilation problems, because of generated dll's and such). It will:
    • Remove unnecessary files like CONTRIBUTION.md, LICENSE.txt.
    • Set initial assembly version to 0.1.0.0.
    • Clears README.md file contents.
    • Rename solution and projects.
    • Rename root namespace.
    • Rename company name.
  • It will ask for your desired root namespace and company's name. So entering project's root namespace "Oyster" and company's name "Tegram". Would result in project structure like this:

    Namespaces like:
    C#
    namespace Oyster.Controllers.Administration
    namespace Oyster.Services
    namespace Oyster.Objects
    And company name will be visible in assembly declarations:
  • Next, open solution and build project but not run it, so that NuGet packages can be restored.
  • After NuGet packages are restored, set Web as default start up project.
  • Open Package Manager Console and run "update-database", on Data project. This creates initial database in local SQLEXPRESS instance.
  • Project uses some of commonly used VS extensions, you should download them if you don't have them already:
    • Web Essentials 201X - css files are generated by using "Web Essentials" build in less compiler. Other then that Web Essentials provides other good VS extensions for modern web developers.
    • You may have already seen a lot of red text in "Package Manager Console", especially those who are using VS2013. It comes from T4Scaffolding not fully supporting VS2013.
      • If you are using VS2013, download and install Windows Management Framework 4.0. Which is needed for VS2013 scaffolding to work.
      • If you area using VS2015, remove T4Scaffolding.Core package, because VS2015 does not support this kind of scaffolding.
      • Depending on your machine configuration you may need to enable powershell scripts to be run on your machine by using "Set-ExecutionPolicy" command.
  • After all this your project should compile and run. Default admin username and password can be found in EntityFramework configuration class under ~/Root namespace/Data/Migrations/Configuration.cs.

Using the code

Models

As you might know in traditional MVC, models are the ones to hold all business logic. But in this template they are just POCOs for representing database tables. The same applies to representation models also known as view models.

C#
public class Account : BaseModel // All domain models should inherit base model with shared Id and CreationDate columns
{
    [Required]
    [StringLength(128)]
    [Index(IsUnique = true)]
	public String Username { get; set; } // Only member declarations, no business logic

    [Required]
    public Boolean IsAdmin { get; set; }
}

public class AccountView : BaseView // All view models should inherit base view with shared Id and CreationDate columns
{
    [Required]
    [StringLength(128)]
    [Index(IsUnique = true)]
	public String Username { get; set; } // Only member declarations, no business logic

    // Declaring only user visible fields. So that "IsAdmin" property can not be edited or over-posted by the user
}

Database

Registering newly created model with the database is relatively easy. First of all, new DbSet has to be added to the main Context class:

C#
public class Context : DBContext
{
    ...

    protected DbSet<Account> Accounts { get; set; } // protected is used, so that testing context can inherit the same database structure
    
    ...
}

MVC.Template is using code first approach for database management, so after adding new DbSet to the context you will need to run "Add-Migration <your_migration_name>", followed by "update-database" command in "Package Manager Console".

Controllers

Any new controller should inherit BaseController, which can be used as an extension point for every controller. Also it keeps shared controller methods, like authorization and redirection to static pages (e.g. "404 not found", "403 unauthorized"). Other controller bases include:

  • ServicedController - for controllers with injected service instance,
  • ValidatedController - for controllers with injected service and validator instances.

The one to choose should be obvious from your needs in a controller. All these controller bases are under [AuthorizationFilter] attribute, so they will all be authorized by default.

C#
public class AccountsController : ValidatedController<IAccountValidator, IAccountService> // Using ValidatedController, because contoller will be using validator and service instances
{
    ...

    [HttpGet]
    public ActionResult Edit(String id)
    {
        return NotEmptyView(Service.Get<AccountView>(id)); // Getting view from service, and returning account view only if account with given id still exists, otherwise redirecting to not found page.
    }

    [HttpPost]
    [ValidateAntiForgeryToken] // Validate antiforgery token on all post actions
    public ActionResult Edit(AccountView account) // Action accepting AccountView class from the request
    {
        if (!Validator.CanEdit(account)) // One call to validate everything about the model
            return View(account); // Return to same model view, if any validation failed

        Service.Edit(account); // Otherwise perform model mutation by using a service

        return RedirectIfAuthorized("Index"); // Redirecting to "Index" action, only then current user is authorized to view it, otherwise redirects to home page
    }

    ...
}

Services

Services are ment to keep business logic for changing domain model or doing other business tasks. One service class should only "serve" one domain model, thus the name of the service should generally be <Domain model name>Service (e.g. AccountService, RoleService). In addition to that, every service should have an interface which inherits IService.

C#
public interface IAccountService : IService // Inheriting shared service interface
{
    ...

    TView Get<TView>(String id) where TView : BaseView; // Defining generic GetView method, so that account model can be automatically mapped to any mapped view

    void Edit(AccountView view); // Defining edit action for account view

    ...
}

public AccountService : IAccountService
{
    ...

    public TView Get<TView>(String id) where TView : BaseView
    {
        return UnitOfWork.GetAs<Account, TView>(id); // Getting account domain model with given id from the database, and mapping it to TView type
    }

    public void Edit(AcountView view)
    {
        Account account = UnitOfWork.To<Account>(view); // Mapping view back to model

        UnitOfWork.Update(account); // Updating account
        UnitOfWork.Commit(); // Commiting transaction
    }

    ...
}

Validators

Validators, unlike services are ment to hold all business validation logic for domain models. Validator implementation follow the same pattern as services. Validation class for one domain model with validator interface.

C#
public interface IAccountValidator : IValidator // Inheriting shared validator interface
{
    // Other account validator interface code

    Boolean CanEdit(AccountView view); // Defining needed validation methods for account view

    // Other account validator interface code
}

public AccountValidator : IAccountValidator
{
    ...

    public Boolean CanEdit(AcountView view)
    {
        Boolean isValid = IsUniqueUsername(view); // Making any custom validation to the view
        isValid &= ModelState.IsValid; // Always include ModelState validation check, so that controller never has to call ModelState.IsValid

        return isValid;
    }

    ...
}

Views

Views should always be written for view models like "AccountView", "RoleView", but never for domain models.

HTML
@model AccountEditView

<div class="col-xs-12">
    <div class="widget-box">
        <div class="widget-header">
            <span class="widget-header-icon fa fa-th-list"></span>
            <h5>@Headers.AdministrationAccounts</h5>
            <div class="widget-header-buttons">
                @if (Html.IsAuthorizedFor("Details"))
                {
                    <a class="btn" href="@Url.Action("Details", new { id = Model.Id })">
                        <i class="fa fa-info"></i><span class="text">@Actions.Details</span>
                    </a>
                }
            </div>
        </div>
        <div class="widget-content">
            @using (Html.BeginForm())
            {
                @Html.AntiForgeryToken() @* Validating anti forgery token on all post actions. *@
                <div class="form-group">
                    <div class="control-label col-xs-12 col-md-3 col-lg-2">
                        @Html.FormLabelFor(model => model.Username) @* Custom label helper to add required '*' spans and localizing labels. *@
                    </div>
                    <div class="control-content col-xs-12 col-md-9 col-lg-5">
                        @Html.FormTextBoxFor(model => model.Username) @* Custom text box helper to add necessary attributes, like readonly on not editable fields. *@
                    </div>
                    <div class="control-validation col-xs-12 col-lg-5">
                        @Html.ValidationMessageFor(model => model.Username)
                    </div>
                </div>
                <div class="form-group">
                    <div class="form-actions col-xs-12 col-lg-7">
                        <input class="btn btn-primary" type="submit" value="@Actions.Submit" />
                    </div>
                </div>
            }
        </div></div>
</div>

Globalization

Currently default language is English, in addition to that Lithuanian was added, just for an example of multiple languages with different number and date formats in the system. You can easily disable Lithuanian language by removing it from the Languages.config file (and then you have time, removing it's resources or replacing them with your language of choice).

XML
<?xml version="1.0" encoding="utf-8" ?>
<languages>
    <language name="English" culture="en-GB" abbrevation="en" default="true" />
</languages>

Leaving only one language will remove any language selections in the system automatically.

Summary

In this article, I introduced you to MVC.Template, a starting project template for ASP.NET MVC based solutions. And explained starting project structure, it's installation and basic code usage. More examples can always be found in MVC.Template's codebase at GitHub. This project will be actively develop by me and my colleagues at work. And hopefully become ASP.NET MVC project starting point for new and experienced developers alike.

History

2017.01.25 Updates, accordingly with v1.7.

2016.05.07 Updates, accordingly with v1.6.

2015.10.26 Updates, accordingly with v1.5.

2015.07.06 Updates, accordingly with v1.4.

2015.04.12 Updates, accordingly with v1.3.

2015.01.05 Updates, accordingly with v1.2.

2014.11.09 Updates, accordingly with v1.1.

2014.09.22 Initial article for v1.0.

License

This article, along with any associated source code and files, is licensed under The MIT License

Share

About the Author


Comments and Discussions

 
AnswerRe: Adminiatrator: new User, new RolePrivilege Pin
User 1106098328-Dec-14 6:26
MemberUser 1106098328-Dec-14 6:26 
GeneralRe: Adminiatrator: new User, new RolePrivilege Pin
rghubert29-Dec-14 11:50
professionalrghubert29-Dec-14 11:50 
QuestionThis article disappeared ... I had it restored. Any comments? Pin
rghubert28-Dec-14 2:09
professionalrghubert28-Dec-14 2:09 
AnswerRe: This article disappeared ... I had it restored. Any comments? Pin
User 1106098328-Dec-14 2:22
MemberUser 1106098328-Dec-14 2:22 
GeneralRe: This article disappeared ... I had it restored. Any comments? Pin
rghubert28-Dec-14 5:06
professionalrghubert28-Dec-14 5:06 
GeneralRe: This article disappeared ... I had it restored. Any comments? Pin
User 1106098328-Dec-14 5:12
MemberUser 1106098328-Dec-14 5:12 
GeneralRe: This article disappeared ... I had it restored. Any comments? Pin
rghubert28-Dec-14 5:38
professionalrghubert28-Dec-14 5:38 
QuestionAdding virtual connection between BaseModel class Pin
Paulo Jorge Afonso10-Dec-14 13:55
professionalPaulo Jorge Afonso10-Dec-14 13:55 
Hi
I m using your MVC Template, but I got a problem I can't explain.

Under Administration Controllers i defined a class Contracts, with this decription:
C#
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace IT2E.Objects
{
    public class Contract : BaseModel
    {
        [Required]
        [StringLength(128)]
        [Index(IsUnique=true)]
        public String Name { get; set; }

        [Required]
        [StringLength(25)]
        [Index(IsUnique=true)]
        public string NIFC { get; set; }

        [StringLength(25)]
        public string Phone { get; set; }

        [Required]
        [EmailAddress]
        [StringLength(256)]
        public string Email { get; set; }

        public string IsActivated { get; set; }

        public virtual IList<Account> Accounts { get; set; }
    }
}


I have BaseView for this class, When I did this everything work well, Menu call for my Index of Contracts, everything... Thumbs Up | :thumbsup:

But when I add a new area Engineering, and defined new class Sites, Area, and all stuff to put the application works. But, I got a nigthmare. The Contracts doesn't work any more, and the Site menu also!
C#
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace IT2E.Objects
{
    public class Site : BaseModel
    {
        [Required]
        public String ContractId { get; set; }
        [Required]
        public String SiteName { get; set; }

        public virtual Contract Contracts { get; set; }
    }
}


And off course I add the following line at Contract.

C#
public virtual IList<Site> Sites { get; set; }


I only expose the ContractService code, but for Sites happen the some, and because I don't changed code on this Service after adds the IList<site> Sites { get; set; } , I thing the problem are somewhere on this part.

C#
using IT2E.Components.Mail;
using IT2E.Components.Security;
using IT2E.Data.Core;
using IT2E.Objects;
using IT2E.Resources.Views.ContractView;
using System;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Security;

namespace IT2E.Services
{
    public class ContractService : BaseService, IContractService
    {
        public ContractService(IUnitOfWork unitOfWork)
            : base(unitOfWork)
        {
        }

        public IQueryable<ContractView> GetViews()
        {
            return UnitOfWork
                .Repository<Contract>()
                .To<ContractView>()
                .OrderBy(view => view.Name);
        }
    }
}


My application when execute public IQueryable<contractview> GetViews(), I got the following error message.

An unhandled exception of type 'System.StackOverflowException' occurred in mscorlib.dll


on
C#
public IQueryable<TView> To<TView>() where TView : BaseView
{
    return repository.Project().To<TView>();
}

that are in \Template.Data\Core\Repository.cs, and the Troubleshooting tips send me to

Troubleshooting Exceptions: System.StackOverflowException


I do not know where I m doing the looping as VS run! So any one can give a glue to how to solve this?

Unsure | :~
AnswerRe: Adding virtual connection between BaseModel class Pin
User 1106098311-Dec-14 4:05
MemberUser 1106098311-Dec-14 4:05 
GeneralRe: Adding virtual connection between BaseModel class Pin
Paulo Jorge Afonso11-Dec-14 4:20
professionalPaulo Jorge Afonso11-Dec-14 4:20 
GeneralRe: Adding virtual connection between BaseModel class Pin
User 1106098311-Dec-14 4:30
MemberUser 1106098311-Dec-14 4:30 
GeneralRe: Adding virtual connection between BaseModel class Pin
Paulo Jorge Afonso11-Dec-14 4:51
professionalPaulo Jorge Afonso11-Dec-14 4:51 
GeneralRe: Adding virtual connection between BaseModel class Pin
User 1106098311-Dec-14 4:59
MemberUser 1106098311-Dec-14 4:59 
QuestionExamples if using your tool Pin
Paulo Jorge Afonso26-Nov-14 7:25
professionalPaulo Jorge Afonso26-Nov-14 7:25 
AnswerRe: Examples if using your tool Pin
User 1106098326-Nov-14 8:26
MemberUser 1106098326-Nov-14 8:26 
GeneralRe: Examples if using your tool Pin
AFMatambo11-Dec-14 3:39
MemberAFMatambo11-Dec-14 3:39 
QuestionILogger always null. Pin
rghubert24-Nov-14 19:30
professionalrghubert24-Nov-14 19:30 
AnswerRe: ILogger always null. Pin
User 1106098325-Nov-14 4:01
MemberUser 1106098325-Nov-14 4:01 
GeneralRe: ILogger always null. Pin
rghubert26-Nov-14 5:15
professionalrghubert26-Nov-14 5:15 
Questionnon SQLEXPRESS instance? Pin
rghubert13-Nov-14 5:57
professionalrghubert13-Nov-14 5:57 
AnswerRe: non SQLEXPRESS instance? Pin
User 1106098313-Nov-14 6:03
MemberUser 1106098313-Nov-14 6:03 
GeneralRe: non SQLEXPRESS instance? Pin
rghubert13-Nov-14 6:20
professionalrghubert13-Nov-14 6:20 
GeneralRe: non SQLEXPRESS instance? Pin
User 1106098313-Nov-14 6:34
MemberUser 1106098313-Nov-14 6:34 
GeneralRe: non SQLEXPRESS instance? Pin
rghubert13-Nov-14 6:40
professionalrghubert13-Nov-14 6:40 
GeneralRe: non SQLEXPRESS instance? Pin
User 1106098313-Nov-14 6:47
MemberUser 1106098313-Nov-14 6:47 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.