Step-by-step guide
Many companies are still using .NET 4.x with MVC, in this article we will explore the basics of building an MVC 4.x Web app and use a database first approach to using Entity Framework. We will cover how to scaffold an MVC Controller and views.
Step 1: Create a new "Blank Solution"
Using visual studio select "create a new Project" then lookup and select the "blank solution" template, I called mine Classic3, hit the create button.
Step2: Create a src folder
Right click on the Solution within visual studio and choose Add|New Solution folder, I always name mine "src".
Step3: Create an ASP.NET Web Application (.NET Framework)
Right click on the "src" folder and choose Add|New Project
In the new project dialog search for the template ASP.NET Web Application (.NET Framework), this has the potential to support web forms, MVC, Web API and many other features.
Click Next
In the "Configure your new Project" dialog, Set the Project name to something appropriate for example Microsite, or UI, the default is WebApplication1
Framework:
For this tutorial I chose .NET Framework 4.8.1
Click Create
In the "Create a new ASP.NET Web Application" dialog choose MVC, I left the Web API and Web forms checkboxes unchecked, set these to suit your additional requirements.
Click Create
Finally from the menu choose Build, to build the Project and then when it builds run the Project as a sanity check that everything so far is working.
Scaffold an Entity framework connection to your database
Step 1:
Under your src/WebAppliation folder create a new folder I will call this EFModels, we create the folder to help keep our project clean in addition using a seperate folder will make it simpler scaffold additional Entity framework connections should we need them.
Step 2: Create an Entity Framework model
Right click on the EFModels folder and Select Add, in the popup dialog, on the left choose Data, then in the right hand pane choose ADO.NET Entity Data Model, Give an appropriate name in Name input at the bottom of the dialog then click Add
The Entity Data Model Wizard dialog will appear,
choose EF Designer from database, click Next
For the "Data Connection" choose "New connection"
For the "Data Source" I chose Microsoft SQL Server (SqlClient)
For the "Server Name" enter the path to your SQL Server location, I entered ".\sqlexpress" but this could be a remote server such for example uk.sql10.yourservers.com,1486
Set your preferred Authentication, I chose "Windows Authentication", you may want to change to SQL Server Authentication in which case you will need a username/password
You may also need to set the checkbox "Trust Server Certificate"
Finally "Select or enter a database name", from the drop down list choose a database name
Click Ok
Now the Connection wizard has completed you will be back in the "Entity Data Model Wizard",
Enter the name you wish to use in Web.config as the connection string
Click Next
Now you will need to choose which objects to include in your model. Expand the tables tree and choose the table(s) you require.
Adjust the default "Model Namespace" as required, I left it as WinnersAppDbModel
Click Finish
Heres my example WebApplication folder view after scaffolding the Entity FrameWork model
Scaffold an MVC Controller for one of your models
Now that we have an Entity framework model we can go ahead and scaffold an MVC controller and views for one of our models
Step 1:
Right click on the Controller folder select Add | New Scaffolded item
Choose MVC Controller with views, using Entity Framework
Click Add
In the popup dialog choose the Model Class and the Data Context Class then adjust the Controller name input as required,
Click Add
Once completed you will now have a new Controller in the Controllers folder and a corresponding set of (crud) views under the Views folder
Step 2: Add a Navigation dropdown menu
Now we have a controller and views we need a way to navigate to these pages, obviously you will have your own ideas and requirements for this but here's an example navigation solution as an example.
Open the file /Shared/_Layout.cshtml
Modify _Layout.cshml to include a dropdown navigation menu to the new scaffolded Views (highlighted below in bold)
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-dark bg-dark">
<div class="container">
@Html.ActionLink("Application name", "Index", "Home", new { area = "" }, new { @class = "navbar-brand" })
<button type="button" class="navbar-toggler" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" title="Toggle navigation" aria-controls="navbarSupportedContent"
aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse d-sm-inline-flex justify-content-between">
<ul class="navbar-nav flex-grow-1">
<li>@Html.ActionLink("Home", "Index", "Home", new { area = "" }, new { @class = "nav-link" })</li>
<li>@Html.ActionLink("About", "About", "Home", new { area = "" }, new { @class = "nav-link" })</li>
<li>@Html.ActionLink("Contact", "Contact", "Home", new { area = "" }, new { @class = "nav-link" })</li>
<!-- Contacts Dropdown -->
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="contactsDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
Contacts
</a>
<ul class="dropdown-menu" aria-labelledby="contactsDropdown">
<li><a class="dropdown-item" href="@Url.Action("Index", "Contacts")">List Contacts</a></li>
<li><a class="dropdown-item" href="@Url.Action("Create", "Contacts")">Create Contact</a></li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
Heres is the generated EF contact.cs class
public partial class contact
{
public System.Guid contact_id { get; set; }
public string firstname { get; set; }
public string lastname { get; set; }
public string email { get; set; }
public Nullable<bool> winner { get; set; }
}
Here is the generated controller, contactController.cs
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Threading.Tasks;
using System.Net;
using System.Web;
using System.Web.Mvc;
using WebApplication1.EFModels;
namespace WebApplication1.Controllers
{
public class contactsController : Controller
{
private dbconn db = new dbconn();
// GET: contacts
public async Task<ActionResult> Index()
{
return View(await db.contacts.ToListAsync());
}
// GET: contacts/Details/5
public async Task<ActionResult> Details(Guid? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
contact contact = await db.contacts.FindAsync(id);
if (contact == null)
{
return HttpNotFound();
}
return View(contact);
}
// GET: contacts/Create
public ActionResult Create()
{
return View();
}
// POST: contacts/Create
// To protect from overposting attacks, enable the specific properties you want to bind to, for
// more details see https://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Create([Bind(Include = "contact_id,firstname,lastname,email,winner")] contact contact)
{
if (ModelState.IsValid)
{
contact.contact_id = Guid.NewGuid();
db.contacts.Add(contact);
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(contact);
}
// GET: contacts/Edit/5
public async Task<ActionResult> Edit(Guid? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
contact contact = await db.contacts.FindAsync(id);
if (contact == null)
{
return HttpNotFound();
}
return View(contact);
}
// POST: contacts/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to, for
// more details see https://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Edit([Bind(Include = "contact_id,firstname,lastname,email,winner")] contact contact)
{
if (ModelState.IsValid)
{
db.Entry(contact).State = EntityState.Modified;
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(contact);
}
// GET: contacts/Delete/5
public async Task<ActionResult> Delete(Guid? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
contact contact = await db.contacts.FindAsync(id);
if (contact == null)
{
return HttpNotFound();
}
return View(contact);
}
// POST: contacts/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<ActionResult> DeleteConfirmed(Guid id)
{
contact contact = await db.contacts.FindAsync(id);
db.contacts.Remove(contact);
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
db.Dispose();
}
base.Dispose(disposing);
}
}
}
How to add validation attribues (using View Models)
The default models from the database will probably not include any data validation attributes, but we cant really add validation attributes to the EF model classes as they may get overridden if the models are re-generated.
There are various solutions to adding validation attributes, the most widely used is to use View Models, the idea with View Models is to create a seperate copy of the EF model class is created which serves to both better define which fields are visible to the view and to to apply data/validation attributes.
Example:
Create a copy of one of your EF classes from the EFModels folder to be used as a "View Model", in this example I copied the contact.cs class and pasted it into my Models folder and renamed it ContactViewModel.cs then added data attributes
ContactViewModel.cs
using System;
using System.ComponentModel.DataAnnotations;
namespace WebApplication1.ViewModels
{
public class ContactViewModel
{
public Guid contact_id { get; set; }
[Required(ErrorMessage = "First Name is required.")]
[StringLength(50, MinimumLength = 3, ErrorMessage = "First Name must be between 3 and 50 characters.")]
public string firstname { get; set; }
[Required(ErrorMessage = "Last Name is required.")]
[StringLength(50, MinimumLength = 3, ErrorMessage = "Last Name must be between 3 and 50 characters.")]
public string lastname { get; set; }
[Required(ErrorMessage = "Email is required.")]
[EmailAddress(ErrorMessage = "Invalid Email Address.")]
public string email { get; set; }
public bool? winner { get; set; }
}
}
Modify the Controller to use the View Model
using System;
using System.Data.Entity;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using System.Web.Mvc;
using WebApplication1.EFModels;
namespace WebApplication1.Controllers
{
public class contactsController : Controller
{
private dbconn db = new dbconn();
// GET: contacts
public async Task<ActionResult> Index()
{
var contacts = await db.contacts.ToListAsync();
var viewModel = contacts.Select(c => new ContactViewModel
{
contact_id = c.contact_id,
firstname = c.firstname,
lastname = c.lastname,
email = c.email,
winner = c.winner
}).ToList();
return View(viewModel);
}
// GET: contacts/Details/5
public async Task<ActionResult> Details(Guid? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
var contact = await db.contacts.FindAsync(id);
if (contact == null)
{
return HttpNotFound();
}
var viewModel = new ContactViewModel
{
contact_id = contact.contact_id,
firstname = contact.firstname,
lastname = contact.lastname,
email = contact.email,
winner = contact.winner
};
return View(viewModel);
}
// GET: contacts/Create
public ActionResult Create()
{
return View();
}
// POST: contacts/Create
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Create(ContactViewModel viewModel)
{
if (ModelState.IsValid)
{
var contact = new contact
{
contact_id = Guid.NewGuid(),
firstname = viewModel.firstname,
lastname = viewModel.lastname,
email = viewModel.email,
winner = viewModel.winner
};
db.contacts.Add(contact);
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(viewModel);
}
// GET: contacts/Edit/5
public async Task<ActionResult> Edit(Guid? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
var contact = await db.contacts.FindAsync(id);
if (contact == null)
{
return HttpNotFound();
}
var viewModel = new ContactViewModel
{
contact_id = contact.contact_id,
firstname = contact.firstname,
lastname = contact.lastname,
email = contact.email,
winner = contact.winner
};
return View(viewModel);
}
// POST: contacts/Edit/5
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Edit(ContactViewModel viewModel)
{
if (ModelState.IsValid)
{
var contact = await db.contacts.FindAsync(viewModel.contact_id);
if (contact == null)
{
return HttpNotFound();
}
contact.firstname = viewModel.firstname;
contact.lastname = viewModel.lastname;
contact.email = viewModel.email;
contact.winner = viewModel.winner;
db.Entry(contact).State = EntityState.Modified;
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(viewModel);
}
// GET: contacts/Delete/5
public async Task<ActionResult> Delete(Guid? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
var contact = await db.contacts.FindAsync(id);
if (contact == null)
{
return HttpNotFound();
}
var viewModel = new ContactViewModel
{
contact_id = contact.contact_id,
firstname = contact.firstname,
lastname = contact.lastname,
email = contact.email,
winner = contact.winner
};
return View(viewModel);
}
// POST: contacts/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<ActionResult> DeleteConfirmed(Guid id)
{
var contact = await db.contacts.FindAsync(id);
db.contacts.Remove(contact);
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
db.Dispose();
}
base.Dispose(disposing);
}
}
}