In this section we'll add features that track attendees who have signed in via Twitter and Google and allow them to create a personal agenda.
-
Add a
Welcome.cshtml
razor page andWelcome.cshtml.cs
page model in thePages
folder. -
Add a user sign up form to
Welcome.cshtml
:@page @using ConferenceDTO @model WelcomeModel <h2>Welcome @User.Identity.Name</h2> <p> Register as an attendee to get access to cool features. </p> <form method="post"> <div asp-validation-summary="All" class="text-danger"></div> <input asp-for="Attendee.UserName" value="@User.Identity.Name" type="hidden" /> <div class="form-group"> <label asp-for="Attendee.FirstName" class="control-label"></label> <div class="row"> <div class="col-md-6"> <input asp-for="Attendee.FirstName" class="form-control" /> </div> </div> <span asp-validation-for="Attendee.FirstName" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="Attendee.LastName" class="control-label"></label> <div class="row"> <div class="col-md-6"> <input asp-for="Attendee.LastName" class="form-control" /> </div> </div> <span asp-validation-for="Attendee.LastName" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="Attendee.EmailAddress" class="control-label"></label> <div class="row"> <div class="col-md-6"> <input asp-for="Attendee.EmailAddress" class="form-control" /> </div> </div> <span asp-validation-for="Attendee.EmailAddress" class="text-danger"></span> </div> <div class="form-group"> <div class=""> <button type="submit" class="btn btn-primary">Save</button> </div> </div> </form> @section Scripts { @Html.Partial("_ValidationScriptsPartial") }
-
In
Welcome.cshtml.cs
, add logic that associates the logged in user with an attendee:using System.Threading.Tasks; using FrontEnd.Services; using FrontEnd.Pages.Models; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; namespace FrontEnd { public class WelcomeModel : PageModel { private readonly IApiClient _apiClient; public WelcomeModel(IApiClient apiClient) { _apiClient = apiClient; } [BindProperty] public Attendee Attendee { get; set; } public async Task<IActionResult> OnGetAsync() { // Redirect to home page if user is anonymous or already registered as attendee var attendee = User.Identity.IsAuthenticated ? await _apiClient.GetAttendeeAsync(HttpContext.User.Identity.Name) : null; if (!User.Identity.IsAuthenticated || attendee != null) { return RedirectToPage("/Index"); } return Page(); } public async Task<IActionResult> OnPostAsync() { await _apiClient.AddAttendeeAsync(Attendee); return RedirectToPage("/Index"); } } }
-
Logged in users can now be associated with an attendee by visiting this page.
-
Add a folder called
Filters
and a new class calledRequireLoginFilter.cs
under it. -
Add logic that redirects to the Welcome page if the user is authenticated (signed in with Twitter or Google) but not associated with an attendee:
public class RequireLoginFilter : IAsyncResourceFilter { private readonly IApiClient _apiClient; private readonly IUrlHelperFactory _urlHelperFactory; public RequireLoginFilter(IApiClient apiClient, IUrlHelperFactory urlHelperFactory) { _apiClient = apiClient; _urlHelperFactory = urlHelperFactory; } public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next) { var urlHelper = _urlHelperFactory.GetUrlHelper(context); var ignoreRoutes = new[] { urlHelper.Page("/Login"), urlHelper.Action("logout", "account"), urlHelper.Page("/Welcome") }; // If the user is authenticated but not associated *and* we're not ignoring this path // then redirect to /Welcome if (context.HttpContext.User.Identity.IsAuthenticated && !ignoreRoutes.Any(path => string.Equals(context.HttpContext.Request.Path, path, StringComparison.OrdinalIgnoreCase))) { var attendee = await _apiClient.GetAttendeeAsync(context.HttpContext.User.Identity.Name); if (attendee == null) { context.HttpContext.Response.Redirect(urlHelper.Page("/Welcome")); return; } } await next(); } }
-
Register the filter globally with MVC using its options in the
ConfigureServices
method inStartup.cs
:services.AddMvc(options => { options.Filters.AddService<RequireLoginFilter>(); })
-
This should force all logged in users to register as an attendee.
-
Add the following methods to
IApiClient
:Task<List<SessionResponse>> GetSessionsByAttendeeAsync(string name); Task AddSessionToAttendeeAsync(string name, int sessionId); Task RemoveSessionFromAttendeeAsync(string name, int sessionId);
-
Add the implementations to
ApiClient
:public async Task AddSessionToAttendeeAsync(string name, int sessionId) { var response = await _httpClient.PostAsync($"/api/attendees/{name}/session/{sessionId}", null); response.EnsureSuccessStatusCode(); } public async Task RemoveSessionFromAttendeeAsync(string name, int sessionId) { var response = await _httpClient.DeleteAsync($"/api/attendees/{name}/session/{sessionId}"); response.EnsureSuccessStatusCode(); } public async Task<List<SessionResponse>> GetSessionsByAttendeeAsync(string name) { // TODO: Would be better to add backend API for this var sessionsTask = GetSessionsAsync(); var attendeeTask = GetAttendeeAsync(name); await Task.WhenAll(sessionsTask, attendeeTask); var sessions = await sessionsTask; var attendee = await attendeeTask; if (attendee == null) { return new List<SessionResponse>(); } var sessionIds = attendee.Sessions.Select(s => s.ID); sessions.RemoveAll(s => !sessionIds.Contains(s.ID)); return sessions; }
-
Add a property
IsInPersonalAgenda
toSession.cshtml.cs
:public bool IsInPersonalAgenda { get; set; }
-
Compute the value of that property in
OnGetAsync
:var sessions = await _apiClient.GetSessionsByAttendeeAsync(User.Identity.Name); IsInPersonalAgenda = sessions.Any(s => s.ID == id);
-
Add a form to the bottom of
Session.cshtml
razor page that adds the ability to add/remove the session to the attendee's personal agenda:<form method="post"> <input type="hidden" name="sessionId" value="@Model.Session.ID" /> <p> <a authz-policy="Admin" asp-page="/Admin/EditSession" asp-route-id="@Model.Session.ID" class="btn btn-default btn-sm">Edit</a> @if (Model.IsInPersonalAgenda) { <button authz="true" type="submit" asp-page-handler="Remove" class="btn btn-default btn-sm" title="Remove from my personal agenda"> <span class="glyphicon glyphicon-star" aria-hidden="true"></span> </button> } else { <button authz="true" type="submit" class="btn btn-default btn-sm" title="Add to my personal agenda"> <span class="glyphicon glyphicon-star-empty" aria-hidden="true"></span> </button> } </p> </form>
-
Add
OnPostAsync
handlers toSession.cshtml.cs
that handles the adding/removing of the session to the personal agenda:public async Task<IActionResult> OnPostAsync(int sessionId) { await _apiClient.AddSessionToAttendeeAsync(User.Identity.Name, sessionId); return RedirectToPage(); } public async Task<IActionResult> OnPostRemoveAsync(int sessionId) { await _apiClient.RemoveSessionFromAttendeeAsync(User.Identity.Name, sessionId); return RedirectToPage(); }
-
Attendee should now be able to add/remove sessions to/from their personal agenda.
-
Add
MyAgenda.cshtml
andMyAgenda.cshtml.cs
files to thePages
folder. -
The Index page and MyAgenda page share the vast majority of their logic and rendering. We'll refactor the
Index.cshtml.cs
class so that it may be used as a base class for theMyAgenda
page. -
Add a
virtual
GetSessionsAsync
method toIndex.cshtml.cs
:protected virtual Task<List<SessionResponse>> GetSessionsAsync() { return _apiClient.GetSessionsAsync(); }
-
Change the
_apiClient
field inIndex.cshtml.cs
to beprotected
instead ofprivate
:protected readonly IApiClient _apiClient;
-
Change the logic in
OnGetAsync
to get session using the new virtual method we just added:Before
var sessions = _apiClient.GetSessionsAsync();
After
var sessions = await GetSessionsAsync();
-
Make the MyAgenda page model derive from the Index page model. Change
MyAgenda.cshtml.cs
to look like this:using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using ConferenceDTO; using FrontEnd.Services; namespace FrontEnd.Pages { public class MyAgendaModel : IndexModel { public MyAgendaModel(IApiClient client) : base(client) { } protected override Task<List<SessionResponse>> GetSessionsAsync() { return _apiClient.GetSessionsByAttendeeAsync(User.Identity.Name); } } }
-
Add the html to render the list of sessions on the attendee's personal agenda to
MyAgenda.cshtml
:@page @model MyAgendaModel @{ ViewData["Title"] = "Home Page"; } <div class="agenda"> <h1>NDC Sydney 2017</h1> <ul class="nav nav-pills"> @foreach (var day in Model.DayOffsets) { <li role="presentation" class="@(Model.CurrentDayOffset == day.Offset ? "active" : null)"> <a asp-route-day="@day.Offset">@day.DayofWeek?.ToString()</a> </li> } </ul> @foreach (var timeSlot in Model.Sessions) { <h4>@timeSlot.Key?.ToString("HH:mm")</h4> <div class="row"> @foreach (var session in timeSlot) { <div class="col-md-3"> <div class="panel panel-default session"> <div class="panel-body"> <p>@session.Track?.Name</p> <h3 class="panel-title"><a asp-page="Session" asp-route-id="@session.ID">@session.Title</a></h3> <p> @foreach (var speaker in session.Speakers) { <em><a asp-page="Speaker" asp-route-id="@speaker.ID">@speaker.Name</a></em> } </p> <form method="post"> <input type="hidden" name="sessionId" value="@session.ID" /> <p> <a authz-policy="Admin" asp-page="/Admin/EditSession" asp-route-id="@session.ID" class="btn btn-default btn-sm">Edit</a> @if (Model.UserSessions.Contains(session.ID)) { <button authz="true" type="submit" asp-page-handler="Remove" class="btn btn-default btn-sm" title="Remove from my personal agenda"> <span class="glyphicon glyphicon-star" aria-hidden="true"></span> </button> } else { <button authz="true" type="submit" class="btn btn-default btn-sm" title="Add to my personal agenda"> <span class="glyphicon glyphicon-star-empty" aria-hidden="true"></span> </button> } </p> </form> </div> </div> </div> } </div> } </div>
-
Go to the layout file
_Layout.cshtml
. -
Add a link that shows up only when authenticated under the
/Speakers
link:<li authz="true"><a asp-page="/MyAgenda">My Agenda</a></li>
-
You should be able to login as an attendee, add/remove sessions to your personal agenda and click on MyAgenda to have them show up.