Monday, 28 September 2015

Building Web Apps With ASP.NET 5 MVC And Entity Framework 7

We will learn how to build web apps with ASP.NET 5 MVC and  Entity Framework 7

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.you are already feeling excited to learn ASP.NET 5 now! We have heard about ASP.NET 5 and its modularity like Node.js, thought why not give it a try. Believe guys! . 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 ASP.NET official documentation.




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. we named it “TechTalkers”. The app has an admin panel where an admin can manage viewable contents for the app users. we 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
    A viewable website
    Speakers List
    Speaker Details
    Topic List
    Topic Details

  2. Technology Stack:

    2.1 ASP.NET 5

    MVC
    Razor View Engine
    Authentication & Authorization
    Identity

    2.2 Entity Framework
    ORM (object relational mapping) for managing application data with a local database.
    Entity framework code first
File, then New Project

We are going to use Visual Studio 2015. Configuring Mac and Linux for ASP.net 5 is easy as pie.

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, then New Project and 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 migraTion history. These get generated by Entity Frameworks.



Now, let’s run the project. Click on the play icon (select your favourite 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.
  1. {
  2. "AppSettings": {
  3. "SiteTitle": "TechTalkers.Web"
  4. },
  5. "Data": {
  6. "DefaultConnection": {
  7. "ConnectionString": "Server=(localdb)\\mssqllocaldb;Database=aspnet5-TechTalkers.Web-6bfb60b0-3080-4310-b46c-9dc1331c9bfa;Trusted_Connection=True;MultipleActiveResultSets=true"
  8. }
  9. }
  10. }
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 looks like the following code snippet:

Topic.cs

  1. public class Topic
  2. {
  3. public Guid TopicId { get; set; }
  4. public string Title { get; set; }
  5. }
Speaker.cs

  1. public class Topic
  2. {
  3. public Guid TopicId { get; set; }
  4. public string Title { get; set; }
  5. public Guid SpeakerId { get; set; }
  6. public Speaker Speaker { get; set; }
  7. }
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 appropriate 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 foreign key in the Speaker table.

Object Relational Mapping with Entity FrameworkWe 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.

  1. public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
  2. {
  3. public DbSet<Speaker> Speakers { get; set; }
  4. public DbSet<Topic> Topics { get; set; }
  5. }
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 the following command:

  1. dnvm use 1.0.0-beta5
  2. dnx . ef migration add AddedSpeakerTopicModels
  3. 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.

Adding Speaker Controllers and ViewsRight 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.

  1. public IActionResult Index()
  2. {
  3. return View();
  4. }
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 the following error:



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 following mark-up
  1. @model IEnumerable<TechTalkers.Web.Models.Speaker>
  2. @{
  3. ViewBag.Title = "Speakers";
  4. }
  5. <h2>@ViewBag.Title</h2>
  6. <p>
  7. <a asp-action="Add">Add a new speaker</a>
  8. </p>
  9. <table class="table">
  10. <tr>
  11. <th>
  12. @Html.DisplayNameFor(model => model.Name)
  13. </th>
  14. <th>
  15. @Html.DisplayNameFor(model => model.Bio)
  16. </th>
  17. <th></th>
  18. </tr>
  19. @foreach (var item in Model)
  20. {
  21. <tr>
  22. <td>
  23. @Html.DisplayFor(modelItem => item.Name)
  24. </td>
  25. <td>
  26. @Html.DisplayFor(modelItem => item.Bio)
  27. </td>
  28. <td>
  29. <a asp-action="Detail" asp-route-speakerId="@item.SpeakerId">Detail</a> |
  30. <a asp-action="Edit" asp-route-speakerId="@item.SpeakerId">Edit</a> |
  31. <a asp-action="Delete" asp-route-speakerId="@item.SpeakerId">Delete</a>
  32. </td>
  33. </tr>
  34. }
  35. </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 available yet. To pass a list of speakers to this view we have to change our controller a bit. The modified controller looks like the following code snippet:

  1. public class SpeakerController : Controller
  2. {
  3. ApplicationDbContext _db;
  4. public SpeakerController(ApplicationDbContext db)
  5. {
  6. _db = db;
  7. }
  8. // GET: /<controller>/
  9. public IActionResult Index()
  10. {
  11. var speakers = _db.Speakers;
  12. return View(speakers);
  13. }
  14. }
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.

  1. [HttpGet]
  2. public IActionResult Add()
  3. {
  4. return View();
  5. }
  6. [HttpPost]
  7. public IActionResult Add(Speaker speaker)
  8. {
  9. _db.Speakers.Add(speaker);
  10. _db.SaveChanges();
  11. return RedirectToAction("Index");
  12. }
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 the following code snippet:

  1. @model TechTalkers.Web.Models.Speaker
  2. @{
  3. ViewBag.Title = "Add a Speaker";
  4. }
  5. <h2>@ViewBag.Title</h2>
  6. <div>
  7. <form asp-controller="Speaker" asp-action="Add" method="post">
  8. <div class="form-group">
  9. <label asp-for="Name"></label>
  10. <input asp-for="Name" class="form-control" placeholder="name" />
  11. </div>
  12. <div class="form-group">
  13. <label asp-for="Bio"></label>
  14. <textarea asp-for="Bio" class="form-control" rows=5 placeholder="bio"></textarea>
  15. </div>
  16. <input type="submit" class="btn btn-default" value="Add" />
  17. </form>
  18. </div>
Running the project and adding a user shows the added speaker like this in the Index view. Previously, I’ve added three speakers 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.

  1. <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 the following code snippet:

  1. [HttpGet]
  2. public IActionResult Detail(Guid speakerId)
  3. {
  4. var id = RouteData.Values["speakerId"];
  5. Speaker speaker = _db.Speakers.FirstOrDefault(s => s.SpeakerId == speakerId);
  6. return View(speaker);
  7. }
This is the view

  1. @model TechTalkers.Web.Models.Speaker
  2. @{
  3. ViewBag.Title = "Details";
  4. }
  5. <h2>@ViewBag.Title</h2>
  6. <div>
  7. <dl class="dl-horizontal">
  8. <dt>@Html.DisplayNameFor(model => model.Name)</dt>
  9. <dd>@Html.DisplayFor(model => model.Name)</dd>
  10. <dt>@Html.DisplayNameFor(model => model.Bio)</dt>
  11. <dd>@Html.DisplayFor(model => model.Bio)</dd>
  12. </dl>
  13. </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.

  1. [HttpGet]
  2. public IActionResult Edit(Guid speakerId)
  3. {
  4. Speaker speaker = _db.Speakers.FirstOrDefault(s => s.SpeakerId == speakerId);
  5. return View(speaker);
  6. }
  7. [HttpPost]
  8. public IActionResult Edit(Speaker speaker)
  9. {
  10. _db.Speakers.Update(speaker);
  11. _db.SaveChanges();
  12. return RedirectToAction("Index");
  13. }
The Edit view looks like the following screenshot:




  1. @model TechTalkers.Web.Models.Speaker
  2. @{
  3. ViewBag.Title = "Edit Speaker";
  4. }
  5. <h2>@ViewBag.Title</h2>
  6. <div>
  7. <form asp-controller="Speaker" asp-action="Edit" method="post" asp-route-speakerId="@Model.SpeakerId">
  8. <div class="form-group">
  9. <label asp-for="Name"></label>
  10. <input asp-for="Name" class="form-control" />
  11. </div>
  12. <div class="form-group">
  13. <label asp-for="Bio"></label>
  14. <textarea asp-for="Bio" rows=3 class="form-control"></textarea>
  15. </div>
  16. <input type="submit" class="btn btn-default" value="Edit" />
  17. </form>
  18. </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:

  1. [HttpGet]
  2. public IActionResult Delete(Guid speakerId)
  3. {
  4. Speaker speaker = _db.Speakers.FirstOrDefault(s => s.SpeakerId == speakerId);
  5. return View(speaker);
  6. }
  7. public IActionResult DeleteConfirmed(Guid speakerId)
  8. {
  9. Speaker speaker = _db.Speakers.FirstOrDefault(s => s.SpeakerId == speakerId);
  10. _db.Speakers.Remove(speaker);
  11. _db.SaveChanges();
  12. return RedirectToAction("Index");
  13. }
Here's the code for view:

  1. @model TechTalkers.Web.Models.Speaker
  2. @{
  3. ViewBag.Title = "Delete";
  4. }
  5. <h2>@ViewBag.Title</h2>
  6. <div>
  7. <dl class="dl-horizontal">
  8. <dt>@Html.DisplayNameFor(model => model.Name)</dt>
  9. <dd>@Html.DisplayFor(model => model.Name)</dd>
  10. <dt>@Html.DisplayNameFor(model => model.Bio)</dt>
  11. <dd>@Html.DisplayFor(model => model.Bio)</dd>
  12. </dl>
  13. <form asp-controller="Speaker" asp-action="DeleteConfirmed" asp-route-speakerId="@Model.SpeakerId">
  14. <input type="submit" class="btn btn-default" value="Delete" />
  15. </form>
  16. </div>
Let’s give it a spin.



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.

  1. public IActionResult Index()
  2. {
  3. var topics = _db.Topics.Include(s=>s.Speaker);
  4. return View(topics);
  5. }
Nothing new in the view. Just copy and paste the markup given below,

  1. @model IEnumerable<TechTalkers.Web.Models.Topic>
  2. @{
  3. ViewBag.Title = "Topics";
  4. }
  5. <h2>@ViewBag.Title</h2>
  6. <p>
  7. <a asp-action="Add">Add a new topic</a>
  8. </p>
  9. <table class="table">
  10. <tr>
  11. <th>
  12. @Html.DisplayNameFor(model => model.Title)
  13. </th>
  14. <th>
  15. @Html.DisplayNameFor(model => model.Speaker)
  16. </th>
  17. <th></th>
  18. </tr>
  19. @foreach (var item in Model)
  20. {
  21. <tr>
  22. <td>
  23. @Html.DisplayFor(modelItem => item.Title)
  24. </td>
  25. <td>
  26. @Html.DisplayFor(modelItem => item.Speaker.Name)
  27. </td>
  28. <td>
  29. <a asp-action="Detail" asp-route-topicId="@item.TopicId">Detail</a> |
  30. <a asp-action="Edit" asp-route-topicId="@item.TopicId">Edit</a> |
  31. <a asp-action="Delete" asp-route-topicId="@item.TopicId">Delete</a>
  32. </td>
  33. </tr>
  34. }
  35. </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.

  1. [HttpGet]
  2. public IActionResult Add()
  3. {
  4. Speakers = GetAllSpeakers();
  5. ViewBag.Speakers = Speakers;
  6. return View();
  7. }
  8. private IEnumerable<SelectListItem> GetAllSpeakers()
  9. {
  10. return _db.Speakers.ToList().Select(speaker => new SelectListItem
  11. {
  12. Text = speaker.Name,
  13. Value = speaker.SpeakerId.ToString(),
  14. });
  15. }
The add topic view looks like this. We pass the speakers list available in the ViewBag object to the html select control.

  1. @model TechTalkers.Web.Models.Topic
  2. @{
  3. ViewBag.Title = "Add a Topic";
  4. }
  5. <h2>@ViewBag.Title</h2>
  6. <div>
  7. <form asp-controller="Topic" asp-action="Add" method="post">
  8. <div class="form-group">
  9. <label asp-for="Title"></label>
  10. <input asp-for="Title" class="form-control" placeholder="title" />
  11. </div>
  12. <div class="form-group">
  13. <select asp-for="SpeakerId" asp-items="@ViewBag.Speakers" class="form-control"></select>
  14. </div>
  15. <input type="submit" class="btn btn-default" value="Add"/>
  16. </form>
  17. </div>
Finally for the postback Add controller we have this,

  1. [HttpPost]
  2. public IActionResult Add(Topic topic)
  3. {
  4. _db.Topics.Add(topic);
  5. _db.SaveChanges();
  6. return RedirectToAction("Index");
  7. }
Here goes the detail view and its associated controller

  1. @model TechTalkers.Web.Models.Topic
  2. @{
  3. ViewBag.Title = "Details";
  4. }
  5. <h2>@ViewBag.Title</h2>
  6. <div>
  7. <dl class="dl-horizontal">
  8. <dt>@Html.DisplayNameFor(model => model.Title)</dt>
  9. <dd>@Html.DisplayFor(model => model.Title)</dd>
  10. <dt>@Html.DisplayNameFor(model => model.Speaker)</dt>
  11. <dd>@Html.DisplayFor(model => model.Speaker.Name)</dd>
  12. </dl>
  13. </div>
Topic Detail controller

  1. [HttpGet]
  2. public IActionResult Detail(Guid topicId)
  3. {
  4. Topic topic = _db.Topics.Include(s=>s.Speaker).FirstOrDefault(t => t.TopicId == topicId);
  5. return View(topic);
  6. }
Topic Edit view and its controller are below:

  1. @model TechTalkers.Web.Models.Topic
  2. @{
  3. ViewBag.Title = "Edit Topic";
  4. }
  5. <h2>@ViewBag.Title</h2>
  6. <div>
  7. <form asp-controller="Topic" asp-action="Edit" method="post" asp-route-topicId="@Model.TopicId">
  8. <div class="form-group">
  9. <label asp-for="Title"></label>
  10. <input asp-for="Title" class="form-control" />
  11. </div>
  12. <div class="form-group">
  13. <label asp-for="Speaker"></label>
  14. <select asp-for="SpeakerId" asp-items="@ViewBag.Speakers" class="form-control"></select>
  15. </div>
  16. <input type="submit" class="btn btn-default" value="Edit" />
  17. </form>
  18. </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.

  1. [HttpGet]
  2. public IActionResult Edit(Guid topicId)
  3. {
  4. Speakers = GetAllSpeakers();
  5. ViewBag.Speakers = Speakers;
  6. Topic topic = _db.Topics.Include(s => s.Speaker).FirstOrDefault(t => t.TopicId == topicId);
  7. return View(topic);
  8. }
  9. [HttpPost]
  10. public IActionResult Edit(Topic topic)
  11. {
  12. _db.Topics.Update(topic);
  13. _db.SaveChanges();
  14. return RedirectToAction("Index");
  15. }
The Delete View and Controller is given below

  1. @model TechTalkers.Web.Models.Topic
  2. @{
  3. ViewBag.Title = "Delete";
  4. }
  5. <h2>@ViewBag.Title</h2>
  6. <div>
  7. <dl class="dl-horizontal">
  8. <dt>@Html.DisplayNameFor(model => model.Title)</dt>
  9. <dd>@Html.DisplayFor(model => model.Title)</dd>
  10. <dt>@Html.DisplayNameFor(model => model.Speaker)</dt>
  11. <dd>@Html.DisplayFor(model => model.Speaker.Name)</dd>
  12. </dl>
  13. <form asp-controller="Topic" asp-action="DeleteConfirmed" asp-route-topicId="@Model.TopicId">
  14. <input type="submit" class="btn btn-default" value="Delete" />
  15. </form>
  16. </div>
Last of all the Delete controllers. One for ‘GET’ and one for ‘POST’ request.

  1. [HttpGet]
  2. public IActionResult Delete(Guid topicId)
  3. {
  4. Topic topic = _db.Topics.Include(s => s.Speaker).FirstOrDefault(t => t.TopicId == topicId);
  5. return View(topic);
  6. }
  7. public IActionResult DeleteConfirmed(Guid topicId)
  8. {
  9. Topic topic = _db.Topics.FirstOrDefault(t => t.TopicId == topicId);
  10. _db.Topics.Remove(topic);
  11. _db.SaveChanges();
  12. return RedirectToAction("Index");
  13. }
Let's have a look if we did any mistakes.



Everything looks good.

Adding Admin AuthenticationAs we have 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.

  1. public IActionResult Register() { ... }
  2. 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.

We created a class which is responsible for initializing the database with a user in admin role. Here is what it looks like

  1. public class DatabaseInitializer : IDatabaseInitializer
  2. {
  3. private readonly UserManager<ApplicationUser> _userManager;
  4. private readonly RoleManager<IdentityRole> _roleManager;
  5. public DatabaseInitializer(UserManager<ApplicationUser> userManager, RoleManager<IdentityRole> roleManager)
  6. {
  7. _userManager = userManager;
  8. _roleManager = roleManager;
  9. }
  10. public async Task CreateUserAndRoleAsync()
  11. {
  12. var user = await _userManager.FindByEmailAsync("fiyazhasan@gmail.com");
  13. if (user == null)
  14. {
  15. user = new ApplicationUser {
  16. UserName = "fiyazhasan@gmail.com",
  17. Email = "fiyazhasan@gmail.com"
  18. };
  19. await _userManager.CreateAsync(user, "@Fizz1234");
  20. }
  21. var role = await _roleManager.FindByNameAsync("admin");
  22. if (role == null) {
  23. role = new IdentityRole {
  24. Name = "admin"
  25. };
  26. await _roleManager.CreateAsync(role);
  27. }
  28. await _userManager.AddToRoleAsync(user, "admin");
  29. }
  30. }
  31. public interface IDatabaseInitializer
  32. {
  33. Task CreateUserAndRoleAsync();
  34. }
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.

  1. public class ApplicationUser : IdentityUser
  2. {
  3. }
  4. public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
  5. {
  6. public DbSet<Speaker> Speakers { get; set; }
  7. public DbSet<Topic> Topics { get; set; }
  8. protected override void OnModelCreating(ModelBuilder builder)
  9. {
  10. base.OnModelCreating(builder);
  11. // Customize the ASP.NET Identity model and override the defaults if needed.
  12. // For example, you can rename the ASP.NET Identity table names and more.
  13. // Add your customizations after calling base.OnModelCreating(builder);
  14. }
  15. }
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.

  1. public void ConfigureServices(IServiceCollection services)
  2. {
  3. // Add Entity Framework services to the services container.
  4. services.AddEntityFramework()
  5. .AddSqlServer()
  6. .AddDbContext<ApplicationDbContext>(options =>
  7. options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]));
  8. // Add Identity services to the services container.
  9. services.AddIdentity<ApplicationUser, IdentityRole>()
  10. .AddEntityFrameworkStores<ApplicationDbContext>()
  11. .AddDefaultTokenProviders();
  12. // Configure the options for the authentication middleware.
  13. // You can add options for Google, Twitter and other middleware as shown below.
  14. // For more information see http://go.microsoft.com/fwlink/?LinkID=532715
  15. services.Configure<FacebookAuthenticationOptions>(options =>
  16. {
  17. options.AppId = Configuration["Authentication:Facebook:AppId"];
  18. options.AppSecret = Configuration["Authentication:Facebook:AppSecret"];
  19. });
  20. services.Configure<MicrosoftAccountAuthenticationOptions>(options =>
  21. {
  22. options.ClientId = Configuration["Authentication:MicrosoftAccount:ClientId"];
  23. options.ClientSecret = Configuration["Authentication:MicrosoftAccount:ClientSecret"];
  24. });
  25. // Add MVC services to the services container.
  26. services.AddMvc();
  27. // Uncomment the following line to add Web API services which makes it easier to port Web API 2 controllers.
  28. // You will also need to add the Microsoft.AspNet.Mvc.WebApiCompatShim package to the 'dependencies' section of project.json.
  29. // services.AddWebApiConventions();
  30. // Register application services.
  31. services.AddTransient<IEmailSender, AuthMessageSender>();
  32. services.AddTransient<ISmsSender, AuthMessageSender>();
  33. services.AddTransient<IDatabaseInitializer,DatabaseInitializer>();
  34. }
  35. // Configure is called after ConfigureServices is called.
  36. public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IDatabaseInitializer databaseInitializer)
  37. {
  38. loggerFactory.MinimumLevel = LogLevel.Information;
  39. loggerFactory.AddConsole();
  40. // Configure the HTTP request pipeline.
  41. // Add the following to the request pipeline only in development environment.
  42. if (env.IsDevelopment())
  43. {
  44. app.UseBrowserLink();
  45. app.UseErrorPage(ErrorPageOptions.ShowAll);
  46. app.UseDatabaseErrorPage(DatabaseErrorPageOptions.ShowAll);
  47. }
  48. else
  49. {
  50. // Add Error handling middleware which catches all application specific errors and
  51. // sends the request to the following path or controller action.
  52. app.UseErrorHandler("/Home/Error");
  53. }
  54. // Add static files to the request pipeline.
  55. app.UseStaticFiles();
  56. // Add cookie-based authentication to the request pipeline.
  57. app.UseIdentity();
  58. // Add authentication middleware to the request pipeline. You can configure options such as Id and Secret in the ConfigureServices method.
  59. // For more information see http://go.microsoft.com/fwlink/?LinkID=532715
  60. // app.UseFacebookAuthentication();
  61. // app.UseGoogleAuthentication();
  62. // app.UseMicrosoftAccountAuthentication();
  63. // app.UseTwitterAuthentication();
  64. // Add MVC to the request pipeline.
  65. app.UseMvc(routes =>
  66. {
  67. routes.MapRoute(
  68. name: "default",
  69. template: "{controller=Home}/{action=Index}/{id?}");
  70. // Uncomment the following line to add a route for porting Web API 2 controllers.
  71. // routes.MapWebApiRoute("DefaultApi", "api/{controller}/{id?}");
  72. });
  73. databaseInitializer.CreateUserAndRoleAsync();
  74. }
Restricting outside users from adding, editing and deleting Speakers and TopicsThis 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.

  1. public class SpeakerController : Controller
  2. {
  3. readonly ApplicationDbContext _db;
  4. public SpeakerController(ApplicationDbContext db)
  5. {
  6. _db = db;
  7. }
  8. // GET: /<controller>/
  9. public IActionResult Index()
  10. {
  11. var speakers = _db.Speakers;
  12. return View(speakers);
  13. }
  14. [HttpGet]
  15. [Authorize(Roles = "admin")]
  16. public IActionResult Add()
  17. {
  18. return View();
  19. }
  20. [HttpPost]
  21. [Authorize(Roles = "admin")]
  22. public IActionResult Add(Speaker speaker)
  23. {
  24. _db.Speakers.Add(speaker);
  25. _db.SaveChanges();
  26. return RedirectToAction("Index");
  27. }
  28. [HttpGet]
  29. [Authorize(Roles = "admin")]
  30. public IActionResult Detail(Guid speakerId)
  31. {
  32. Speaker speaker = _db.Speakers.FirstOrDefault(s => s.SpeakerId == speakerId);
  33. return View(speaker);
  34. }
  35. [HttpGet]
  36. [Authorize(Roles = "admin")]
  37. public IActionResult Edit(Guid speakerId)
  38. {
  39. Speaker speaker = _db.Speakers.FirstOrDefault(s => s.SpeakerId == speakerId);
  40. return View(speaker);
  41. }
  42. [HttpPost]
  43. [Authorize(Roles = "admin")]
  44. public IActionResult Edit(Speaker speaker)
  45. {
  46. _db.Speakers.Update(speaker);
  47. _db.SaveChanges();
  48. return RedirectToAction("Index");
  49. }
  50. [HttpGet]
  51. [Authorize(Roles = "admin")]
  52. public IActionResult Delete(Guid speakerId)
  53. {
  54. Speaker speaker = _db.Speakers.FirstOrDefault(s => s.SpeakerId == speakerId);
  55. return View(speaker);
  56. }
  57. [Authorize(Roles = "admin")]
  58. public IActionResult DeleteConfirmed(Guid speakerId)
  59. {
  60. Speaker speaker = _db.Speakers.FirstOrDefault(s => s.SpeakerId == speakerId);
  61. _db.Speakers.Remove(speaker);
  62. _db.SaveChanges();
  63. return RedirectToAction("Index");
  64. }
  65. }
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, then_LoginPartial and comment out these lines.

  1. else
  2. {
  3. <ul class="nav navbar-nav navbar-right">
  4. <li><a asp-action="Register" asp-controller="Account">Register</a></li>
  5. <li><a asp-action="Login" asp-controller="Account">Log in</a></li>
  6. </ul>
  7. }
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,

  1. <li><a asp-controller="Topic" asp-action="Index">Topic</a></li>
  2. <li><a asp-controller="Speaker" asp-action="Index">Speaker</a></li>   

No comments:

Post a Comment

The Future of Remote Work, According to Startups

  The Future of Remote Work, According to Startups No matter where in the world you log in from—Silicon Valley, London, and beyond—COVID-19 ...