Intro to ASP.Net 5
Everyone likes the technology that comes with an “open source” tag along with it. And yes! The new ASP.net 5 is an open source project and cross platform framework. I guess, you are already feeling excited to learn some ASP.net 5 now! Well I’m a big fan of Node.js, so when I heard about ASP.net 5 and its modularity like Node.js, I thought why not give it a try. Believe me guys! I’m quite impressed. ASP.net 5 really stand up alongside many modern web application building frameworks with all the well-known concepts like Middleware, Services etc. To know more about ASP.net 5 and its architecture you can read their official documentation at http://docs.asp.net/ . The documentation is also open source. You can contribute your expertise on any of the topic that has a wrench sign on it.
What are we going to build?
In short we are going to build a simple CMS app to manage content of a fictional technical talk event. I named it “TechTalkers”. The app has an admin panel where an admin can manage viewable contents for the app users. I made a little user story for our “TechTalkers” application along with the technology stack that we are going to use,
1. User Story:
1.1 Admin
- Admin logout/login system
- Admin Panel
- Managing Speakers
- Managing Speech Topic associate with a speaker
1.2 User
2. Technology Stack:
2.1 ASP.net 5
- MVC
- Authentication & Authorization
2.2 Entity Framework
- ORM (object relational mapping) for managing application data with a local database
- Entity framework code first
File > New Project
I’m on windows so I’m going to use Visual Studio 2015. Configuring Mac and Linux for ASP.net 5 is easy as pie. Please refer to this link.
http://aspnet.readthedocs.org/en/latest/getting-started/index.html
There you will find how to set things up for ASP.net 5 on a MAC or LINUX and start developing using Visual Studio Code or whatever Editor you want.
Open up Visual Studio 2015. Go to File > New Project, select ASP.NET Web Application. Give the project a name “TechTalkers” and hit ok. In the template screen, under ASP.net 5 preview templates, select Web Application and hit ok. The template follows ASP.net MVC pattern for structuring your web app. Also contains individual user account login/logout functionality built in it.
Project Structure
The project structure is very simple. You have your .cshtml
(.cshtml extension is used for Razor Views. Razor enables you to write C# code inside your html file) files in the Views
folder. All your data transfer objects/POCOs (plain old class object) resides in the Models
folder. And the controllers resides in the Controllers
folder. Controllers are used to route any incoming request to a specific business logic block in your app. Other than these, we have Services
and Migrations
folders. In Services
folder we have files to configure third party services like Email and SMS. In Migrations
folder we have snapshots of our database migraion history. These get generated by Entity Frameworks. [More about EF later]
Now, let’s run the project. Click on the play icon (select your favorite browser from browse with… option).
You will see a Registration
and a Login
page along with three other pages i.e. Home
, About
and Contact
pages. User can register himself into the system using the Registration
page and start using the application. Once you register yourself, all your credential info will be saved to a local database. Connection string of the local database can be found in the config.json
file
{
"AppSettings": {
"SiteTitle": "TechTalkers.Web"
},
"Data": {
"DefaultConnection": {
"ConnectionString": "Server=(localdb)\\mssqllocaldb;Database=aspnet5-TechTalkers.Web-6bfb60b0-3080-4310-b46c-9dc1331c9bfa;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}
}
You can access the database from SQL Server Object Explorer
window.
Our system is a little bit different from this architecture. In our system only one user can login/logout into and out of the system. He/She is of course an admin. We are going to explicitly create an admin user and place it right into our database. So, there is no need for a Registration
page. Again there is no need for the About
and Contact
pages either.
Creating Object Models
Okay let’s get our hands dirty. We need to create two model classes that will be mapped to two different tables in our database using Entity Framework. One of the table is for holding info about speakers and next one is holding info about the speech topic associated with a speaker.
Now, right click on the Models
folder and add two classes called Speaker
and Topic
. The model classes look like below
Speaker.cs
public class Speaker
{
public Guid SpeakerId { get; set; }
public string Name { get; set; }
public string Bio { get; set; }
}
Topic.cs
public class Topic
{
public Guid TopicId { get; set; }
public string Title { get; set; }
public Guid SpeakerId { get; set; }
public Speaker Speaker { get; set; }
}
The TopicId
and SpeakerId
will be mapped to the unique Identifier column/primary key to their respective tables. Every property defined in the classes will produce database column with appropiate column type except for the public Speaker Speaker { get; set; }
. This property is used to navigate to the associated Speaker object of the Topic. That’s why it is called navigation property. The public Guid SpeakerId { get; set; }
is used as a foreignkey in the Speaker table.
Object Relational Mapping with Entity Framework
We have our models, now we have to map those to our local database tables. Entity framework going to help us achieving that. In the IdentityModels.cs
class add two properties of type DbSet
namely Speakers
and Topics
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public DbSet<Speaker> Speakers { get; set; }
public DbSet<Topic> Topics { get; set; }
}
IdentityDbContext
is used to create a bridge between your application context and the database so that you can communicate back and forth. DbSet
of a generic type allows you to run Linq
or Lamda
queries on the respective database table.
We would have used the DbContext
class instead of IdentityDbContext
if we wanted to only generate our Topic
and Speaker
tables. But IdentityDbContext
helps us in generating tables to store information such as user credentials, user roles etc. in addition with other database tables. Okay now rebuild the project. To have these two new tables in our database we have to run a migration command. Let’s do it. Open up your command prompt and change directory to your project folder and run this command below
dnvm use 1.0.0-beta5
dnx . ef migration add AddedSpeakerTopicModels
dnx . ef migration apply
Running those command will produce a new migration file under Migrations
folder with a timestamp added to the front. It is the snapshot of the current database structure.
Next we will add controllers to interact with the Speaker
and database Table.
1.1 Adding Speaker Controllers and Views
Right click on the Controllers
folder then select add new item and add a MVC controller class; name it SpeakerController
.
The controller class only contains an Index()
method which returns an action result.
public IActionResult Index()
{
return View();
}
This action will be executed and return a view when a user go to the [application-url]/Speaker/Index
or just [application-url]/Speaker
URL. But since there is no View associated with this controller action we will see this error below
Time to add the index view for the speaker controller's Index()
action. Index
view will show a table that shows all the Speakers added into the system and will also contain a link to add a new speaker in to the system. Add a new folder under Views folder named Speaker
. Right click on the Speaker
folder and select add new item and select MVC View Page
. Give it the name Index
since it is a view for the Index()
action.
Copy and Paste the markup below
@model IEnumerable<TechTalkers.Web.Models.Speaker>
@{
ViewBag.Title = "Speakers";
}
<h2>@ViewBag.Title</h2>
<p>
<a asp-action="Add">Add a new speaker</a>
</p>
<table class="table">
<tr>
<th>
@Html.DisplayNameFor(model => model.Name)
</th>
<th>
@Html.DisplayNameFor(model => model.Bio)
</th>
<th></th>
</tr>
@foreach (var item in Model)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Name)
</td>
<td>
@Html.DisplayFor(modelItem => item.Bio)
</td>
<td>
<a asp-action="Detail" asp-route-speakerId="@item.SpeakerId">Detail</a> |
<a asp-action="Edit" asp-route-speakerId="@item.SpeakerId">Edit</a> |
<a asp-action="Delete" asp-route-speakerId="@item.SpeakerId">Delete</a>
</td>
</tr>
}
</table>
As you can see the view contains a table that shows all the Speakers available in the Speakers list. Also have links for viewing detail, editing and deleting a speaker. But we can't see it yet since we don't have any speakers avalable yet. To pass a list of speakers to this view we have to change our controller a bit. The modified controller looks like this
public class SpeakerController : Controller
{
ApplicationDbContext _db;
public SpeakerController(ApplicationDbContext db)
{
_db = db;
}
public IActionResult Index()
{
var speakers = _db.Speakers;
return View(speakers);
}
}
So we instantiated an object of type ApplicationDbContext
in the controller’s constructor. Now we can query on the database with Entity Framework using the context object _db
. In Index()
action we’ve queried and get all the speakers from the database in the speakers
list and pass the list to our Index
view. Now if you run the application and go to the same URL as before you will see this view below
We’ve Add action attached to the add a speaker
link. Let’s add two action controller for it
[HttpGet]
public IActionResult Add()
{
return View();
}
[HttpPost]
public IActionResult Add(Speaker speaker)
{
_db.Speakers.Add(speaker);
_db.SaveChanges();
return RedirectToAction("Index");
}
We have two controller for Add. This is simply because when an admin request to [application-url]/Add
URL he is requesting a ‘GET’ on our server which returns the Speaker
adding form. Then if he fills all the info and click on add he will request a ‘POST’ on our server which contains the object built from that provided info. After getting the passed in speaker object we save that speaker to our database and Redirect again to our Index
view. The speaker adding view looks like this,
@model TechTalkers.Web.Models.Speaker
@{
ViewBag.Title = "Add a Speaker";
}
<h2>@ViewBag.Title</h2>
<div>
<form asp-controller="Speaker" asp-action="Add" method="post">
<div class="form-group">
<label asp-for="Name"></label>
<input asp-for="Name" class="form-control" placeholder="name" />
</div>
<div class="form-group">
<label asp-for="Bio"></label>
<textarea asp-for="Bio" class="form-control" rows=5 placeholder="bio"></textarea>
</div>
<input type="submit" class="btn btn-default" value="Add" />
</form>
</div>
Running the project and adding a user shows the added speaker like this in the Index
view. Previously I’ve added three speaker so the list shows four speakers.
Creating Detail
view and its controller is also easy. From the Index
view we pass the speakerId
to our detail view
<a asp-action="Detail" asp-route-speakerId="@item.SpeakerId">Detail</a>
That’s why we need an http ‘GET’ controller that takes a speakerId
as an input parameter. All we have to do then is to search the speaker associated with the speakerId
and pass that speaker to our Detail
view. The controller looks like this
[HttpGet]
public IActionResult Detail(Guid speakerId)
{
var id = RouteData.Values["speakerId"];
Speaker speaker = _db.Speakers.FirstOrDefault(s => s.SpeakerId == speakerId);
return View(speaker);
}
This is the view
@model TechTalkers.Web.Models.Speaker
@{
ViewBag.Title = "Details";
}
<h2>@ViewBag.Title</h2>
<div>
<dl class="dl-horizontal">
<dt>@Html.DisplayNameFor(model => model.Name)</dt>
<dd>@Html.DisplayFor(model => model.Name)</dd>
<dt>@Html.DisplayNameFor(model => model.Bio)</dt>
<dd>@Html.DisplayFor(model => model.Bio)</dd>
</dl>
</div>
Here is what it looks like,
Like the add view we’ve two controllers for the Edit
view. One of these exactly looks like the Detail
controller because we have to pass the selected speaker to the Edit
view to have the previous values for the input field. The second controller is quite similar to the second Add
controller except that we update the speaker instead of adding it to the database. Here goes the two controllers
[HttpGet]
public IActionResult Edit(Guid speakerId)
{
Speaker speaker = _db.Speakers.FirstOrDefault(s => s.SpeakerId == speakerId);
return View(speaker);
}
[HttpPost]
public IActionResult Edit(Speaker speaker)
{
_db.Speakers.Update(speaker);
_db.SaveChanges();
return RedirectToAction("Index");
}
The Edit
view looks like below
@model TechTalkers.Web.Models.Speaker
@{
ViewBag.Title = "Edit Speaker";
}
<h2>@ViewBag.Title</h2>
<div>
<form asp-controller="Speaker" asp-action="Edit" method="post" asp-route-speakerId="@Model.SpeakerId">
<div class="form-group">
<label asp-for="Name"></label>
<input asp-for="Name" class="form-control" />
</div>
<div class="form-group">
<label asp-for="Bio"></label>
<textarea asp-for="Bio" rows=3 class="form-control"></textarea>
</div>
<input type="submit" class="btn btn-default" value="Edit" />
</form>
</div>
Last of all the Delete
controller. First we have to select the speaker with the passed in speakerId
and show detail about it in the delete view. In delete view we have to have a delete button which will ultimately delete the speaker. The controllers are given below
[HttpGet]
public IActionResult Delete(Guid speakerId)
{
Speaker speaker = _db.Speakers.FirstOrDefault(s => s.SpeakerId == speakerId);
return View(speaker);
}
public IActionResult DeleteConfirmed(Guid speakerId)
{
Speaker speaker = _db.Speakers.FirstOrDefault(s => s.SpeakerId == speakerId);
_db.Speakers.Remove(speaker);
_db.SaveChanges();
return RedirectToAction("Index");
}
The view looks like below
@model TechTalkers.Web.Models.Speaker
@{
ViewBag.Title = "Delete";
}
<h2>@ViewBag.Title</h2>
<div>
<dl class="dl-horizontal">
<dt>@Html.DisplayNameFor(model => model.Name)</dt>
<dd>@Html.DisplayFor(model => model.Name)</dd>
<dt>@Html.DisplayNameFor(model => model.Bio)</dt>
<dd>@Html.DisplayFor(model => model.Bio)</dd>
</dl>
<form asp-controller="Speaker" asp-action="DeleteConfirmed" asp-route-speakerId="@Model.SpeakerId">
<input type="submit" class="btn btn-default" value="Delete" />
</form>
</div>
Let’s give it a spin.
1.2 Adding Topic Controllers and Views
Most of the work is almost same as creating the Speaker
controllers and views. Additional works requires us to put a dropdown list of available speakers for selecting one associated with a topic. Let’s do it.
As before create a controller and name it TopicController
. Create an ApplicationDbContext
instance in the constructor. Create the Index()
controller as before except now pass the list of topics to the view. Don’t forget to include the navigational property we talked about
public IActionResult Index()
{
var topics = _db.Topics.Include(s=>s.Speaker);
return View(topics);
}
Nothing new in the view. Just copy and paste the markup given below,
@model IEnumerable<TechTalkers.Web.Models.Topic>
@{
ViewBag.Title = "Topics";
}
<h2>@ViewBag.Title</h2>
<p>
<a asp-action="Add">Add a new topic</a>
</p>
<table class="table">
<tr>
<th>
@Html.DisplayNameFor(model => model.Title)
</th>
<th>
@Html.DisplayNameFor(model => model.Speaker)
</th>
<th></th>
</tr>
@foreach (var item in Model)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.Speaker.Name)
</td>
<td>
<a asp-action="Detail" asp-route-topicId="@item.TopicId">Detail</a> |
<a asp-action="Edit" asp-route-topicId="@item.TopicId">Edit</a> |
<a asp-action="Delete" asp-route-topicId="@item.TopicId">Delete</a>
</td>
</tr>
}
</table>
Slight change in Add
controller, we should send the list of speakers to the view so that an admin can select one from it while creating a topic. So we pass the speakers list in a ViewBag
object.
[HttpGet]
public IActionResult Add()
{
Speakers = GetAllSpeakers();
ViewBag.Speakers = Speakers;
return View();
}
private IEnumerable<SelectListItem> GetAllSpeakers()
{
return _db.Speakers.ToList().Select(speaker => new SelectListItem
{
Text = speaker.Name,
Value = speaker.SpeakerId.ToString(),
});
}
The add topic view looks like this. We pass the speakers list available in the ViewBag
object to the html select
control
@model TechTalkers.Web.Models.Topic
@{
ViewBag.Title = "Add a Topic";
}
<h2>@ViewBag.Title</h2>
<div>
<form asp-controller="Topic" asp-action="Add" method="post">
<div class="form-group">
<label asp-for="Title"></label>
<input asp-for="Title" class="form-control" placeholder="title" />
</div>
<div class="form-group">
<select asp-for="SpeakerId" asp-items="@ViewBag.Speakers" class="form-control"></select>
</div>
<input type="submit" class="btn btn-default" value="Add"/>
</form>
</div>
Finally for the postback Add controller we have this,
[HttpPost]
public IActionResult Add(Topic topic)
{
_db.Topics.Add(topic);
_db.SaveChanges();
return RedirectToAction("Index");
}
Here goes the detail view and its associated controller
@model TechTalkers.Web.Models.Topic
@{
ViewBag.Title = "Details";
}
<h2>@ViewBag.Title</h2>
<div>
<dl class="dl-horizontal">
<dt>@Html.DisplayNameFor(model => model.Title)</dt>
<dd>@Html.DisplayFor(model => model.Title)</dd>
<dt>@Html.DisplayNameFor(model => model.Speaker)</dt>
<dd>@Html.DisplayFor(model => model.Speaker.Name)</dd>
</dl>
</div>
Topic Detail controller
[HttpGet]
public IActionResult Detail(Guid topicId)
{
Topic topic = _db.Topics.Include(s=>s.Speaker).FirstOrDefault(t => t.TopicId == topicId);
return View(topic);
}
Topic Edit view and its controller are below
@model TechTalkers.Web.Models.Topic
@{
ViewBag.Title = "Edit Topic";
}
<h2>@ViewBag.Title</h2>
<div>
<form asp-controller="Topic" asp-action="Edit" method="post" asp-route-topicId="@Model.TopicId">
<div class="form-group">
<label asp-for="Title"></label>
<input asp-for="Title" class="form-control" />
</div>
<div class="form-group">
<label asp-for="Speaker"></label>
<select asp-for="SpeakerId" asp-items="@ViewBag.Speakers" class="form-control"></select>
</div>
<input type="submit" class="btn btn-default" value="Edit" />
</form>
</div>
Again in Topic Edit controller we passed the speakers list in a ViewBag
so that the select control can get populated. Second controller is same as the second Edit
view of the Speaker
[HttpGet]
public IActionResult Edit(Guid topicId)
{
Speakers = GetAllSpeakers();
ViewBag.Speakers = Speakers;
Topic topic = _db.Topics.Include(s => s.Speaker).FirstOrDefault(t => t.TopicId == topicId);
return View(topic);
}
[HttpPost]
public IActionResult Edit(Topic topic)
{
_db.Topics.Update(topic);
_db.SaveChanges();
return RedirectToAction("Index");
}
The Delete View and Controller is given below
@model TechTalkers.Web.Models.Topic
@{
ViewBag.Title = "Delete";
}
<h2>@ViewBag.Title</h2>
<div>
<dl class="dl-horizontal">
<dt>@Html.DisplayNameFor(model => model.Title)</dt>
<dd>@Html.DisplayFor(model => model.Title)</dd>
<dt>@Html.DisplayNameFor(model => model.Speaker)</dt>
<dd>@Html.DisplayFor(model => model.Speaker.Name)</dd>
</dl>
<form asp-controller="Topic" asp-action="DeleteConfirmed" asp-route-topicId="@Model.TopicId">
<input type="submit" class="btn btn-default" value="Delete" />
</form>
</div>
Last of all the Delete
controllers. One for ‘GET’ and one for ‘POST’ request.
[HttpGet]
public IActionResult Delete(Guid topicId)
{
Topic topic = _db.Topics.Include(s => s.Speaker).FirstOrDefault(t => t.TopicId == topicId);
return View(topic);
}
public IActionResult DeleteConfirmed(Guid topicId)
{
Topic topic = _db.Topics.FirstOrDefault(t => t.TopicId == topicId);
_db.Topics.Remove(topic);
_db.SaveChanges();
return RedirectToAction("Index");
}
Let's have a look if we did any mistakes
Everything looks good.
Adding Admin Authentication
As I already said our system will consist of one single user who is ultimately an admin. Only an admin can add/remove/edit Topics
and Speakers
. So, there is no need for a registration view for outside users. You can remove most of the AccountController
code but I’m going to comment out for now. Commenting out code is good rather than deleting. You may need that block in future.
public IActionResult Register() { ... }
public async Task<IActionResult> Register(RegisterViewModel model) { ... }
We have controller for External [registration via google/facebook/Microsoft/twitter] user registration. Don’t worry about them because if you don’t have your app registered to those service you won’t get access to the external registration/login services. Means that commenting out those controllers or keeping them don’t matter anyways.
I’ve created a class which is responsible for initializing the database with a user in admin role. Here is what it looks like
public class DatabaseInitializer : IDatabaseInitializer
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly RoleManager<IdentityRole> _roleManager;
public DatabaseInitializer(UserManager<ApplicationUser> userManager, RoleManager<IdentityRole> roleManager)
{
_userManager = userManager;
_roleManager = roleManager;
}
public async Task CreateUserAndRoleAsync()
{
var user = await _userManager.FindByEmailAsync("fiyazhasan@gmail.com");
if (user == null)
{
user = new ApplicationUser {
UserName = "fiyazhasan@gmail.com",
Email = "fiyazhasan@gmail.com"
};
await _userManager.CreateAsync(user, "@Fizz1234");
}
var role = await _roleManager.FindByNameAsync("admin");
if (role == null) {
role = new IdentityRole {
Name = "admin"
};
await _roleManager.CreateAsync(role);
}
await _userManager.AddToRoleAsync(user, "admin");
}
}
public interface IDatabaseInitializer
{
Task CreateUserAndRoleAsync();
}
Implementing an interface is just for the sake of dependency injection. You don’t have to do that if you don’t want to. In DatabaseInitializer
we have to properties which helps us to talk to the Identity
module. To create a new user we use the UserManager
and to manage role for them we use RoleManager
classes. UserManager
is capable of working with the ApplicationUser
type which is just an implementation of IdentityUser
. This class can be found in the IdentityModels
class.
public class ApplicationUser : IdentityUser
{
}
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public DbSet<Speaker> Speakers { get; set; }
public DbSet<Topic> Topics { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
}
}
As you can see the ApplicationUser
class is empty. You can add additional properties here as a required field while registration else just throw it away and simply use IdentityUser
with UserManager which I did in the case of RoleManager
(used IdentityRole
in RoleManager
). Rest of the code is self-explanatory. All I did is create a user and assign a role of admin.
Now we have to inject this initializer class in to the ConfigureServices
method found in the startup class. Now to run the initializer when the app runs we call the CreateUserAndRoleAsync()
method found in the same class where the ConfigureServices()
resides. Here is the final look of the startup class.
public void ConfigureServices(IServiceCollection services)
{
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.Configure<FacebookAuthenticationOptions>(options =>
{
options.AppId = Configuration["Authentication:Facebook:AppId"];
options.AppSecret = Configuration["Authentication:Facebook:AppSecret"];
});
services.Configure<MicrosoftAccountAuthenticationOptions>(options =>
{
options.ClientId = Configuration["Authentication:MicrosoftAccount:ClientId"];
options.ClientSecret = Configuration["Authentication:MicrosoftAccount:ClientSecret"];
});
services.AddMvc();
services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<ISmsSender, AuthMessageSender>();
services.AddTransient<IDatabaseInitializer,DatabaseInitializer>();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IDatabaseInitializer databaseInitializer)
{
loggerFactory.MinimumLevel = LogLevel.Information;
loggerFactory.AddConsole();
if (env.IsDevelopment())
{
app.UseBrowserLink();
app.UseErrorPage(ErrorPageOptions.ShowAll);
app.UseDatabaseErrorPage(DatabaseErrorPageOptions.ShowAll);
}
else
{
app.UseErrorHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseIdentity();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
databaseInitializer.CreateUserAndRoleAsync();
}
1.1 Restricting outside users from adding, editing and deleting Speakers and Topics
This step is super simple, all we have to do is to decorate our Add
, Edit
and Delete
controller with the [Authorize(Roles = "admin")]
. Means that we only want to give the admin access to the controllers. Here is what looks like after adding the attribute to the speaker’s Add
, Edit
and Delete
controllers. Please do the same for Topic
controllers.
public class SpeakerController : Controller
{
readonly ApplicationDbContext _db;
public SpeakerController(ApplicationDbContext db)
{
_db = db;
}
public IActionResult Index()
{
var speakers = _db.Speakers;
return View(speakers);
}
[HttpGet]
[Authorize(Roles = "admin")]
public IActionResult Add()
{
return View();
}
[HttpPost]
[Authorize(Roles = "admin")]
public IActionResult Add(Speaker speaker)
{
_db.Speakers.Add(speaker);
_db.SaveChanges();
return RedirectToAction("Index");
}
[HttpGet]
[Authorize(Roles = "admin")]
public IActionResult Detail(Guid speakerId)
{
Speaker speaker = _db.Speakers.FirstOrDefault(s => s.SpeakerId == speakerId);
return View(speaker);
}
[HttpGet]
[Authorize(Roles = "admin")]
public IActionResult Edit(Guid speakerId)
{
Speaker speaker = _db.Speakers.FirstOrDefault(s => s.SpeakerId == speakerId);
return View(speaker);
}
[HttpPost]
[Authorize(Roles = "admin")]
public IActionResult Edit(Speaker speaker)
{
_db.Speakers.Update(speaker);
_db.SaveChanges();
return RedirectToAction("Index");
}
[HttpGet]
[Authorize(Roles = "admin")]
public IActionResult Delete(Guid speakerId)
{
Speaker speaker = _db.Speakers.FirstOrDefault(s => s.SpeakerId == speakerId);
return View(speaker);
}
[Authorize(Roles = "admin")]
public IActionResult DeleteConfirmed(Guid speakerId)
{
Speaker speaker = _db.Speakers.FirstOrDefault(s => s.SpeakerId == speakerId);
_db.Speakers.Remove(speaker);
_db.SaveChanges();
return RedirectToAction("Index");
}
}
Now, if you try to add/edit/delete a speaker or topic as an outside user, you will be taken to the login screen. If you logged in using the admin credentials then you can add/edit/delete a speaker or topic.
Time to remove the Registration
and Login
links from the Layout page. Go to Views > Shared >_LoginPartial and comment out these lines.
else
{
<ul class="nav navbar-nav navbar-right">
<li><a asp-action="Register" asp-controller="Account">Register</a></li>
<li><a asp-action="Login" asp-controller="Account">Log in</a></li>
</ul>
}
The line is responsible for showing a register and login links to outside users. Of course we don't need it. Admin can simply login by going to this lik - [application-url]/Account/Login.
Before leaving just add the Speaker
and Topic
view link in the _Layout
view. Replace the About and Contact Link with these two,
<li><a asp-controller="Topic" asp-action="Index">Topic</a></li>
<li><a asp-controller="Speaker" asp-action="Index">Speaker</a></li>
Full app overview
<iframe allowfullscreen="" frameborder="0" src="https://www.youtube.com/embed/iyflwFgX5l0"></iframe>
If the iframe doesn't work plese refer to this video link - Making web apps with Asp.net 5 MVC and Entity Framework 7
What I didn't do
I didt't apply validation attributes on my model's property. So, you will have exception if you try to do funny things with them; like making the name value of a speaker 500 charecters in length. Again I did't null safe my controller's code also didn't enclose them in try catch.
Conclusion
The application should behave fine. If you find any bugs please leave a comment below. I hope you enjoyed this post. See you in the next one. Bye bye geeks. :)