Skip to content

Latest commit

 

History

History
400 lines (334 loc) · 14.3 KB

5. Add personal agenda.md

File metadata and controls

400 lines (334 loc) · 14.3 KB

Add attendee sign up

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 BackEnd attendee and FrontEnd user association

  1. Add a Welcome.cshtml razor page and Welcome.cshtml.cs page model in the Pages folder.

  2. 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")
    }
  3. 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");
            }
        }
    }
  4. Logged in users can now be associated with an attendee by visiting this page.

Add a filter to force logged in users to sign up on welcome page

  1. Add a folder called Filters and a new class called RequireLoginFilter.cs under it.

  2. 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();
        }
    }
  3. Register the filter globally with MVC using its options in the ConfigureServices method in Startup.cs:

    services.AddMvc(options =>
    {
        options.Filters.AddService<RequireLoginFilter>();
    })
  4. This should force all logged in users to register as an attendee.

Add personal agenda

Update the ApiClient

  1. Add the following methods to IApiClient:

    Task<List<SessionResponse>> GetSessionsByAttendeeAsync(string name);
    Task AddSessionToAttendeeAsync(string name, int sessionId);
    Task RemoveSessionFromAttendeeAsync(string name, int sessionId);
  2. 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 Add/Remove to personal agenda buttons to Session details page

  1. Add a property IsInPersonalAgenda to Session.cshtml.cs:

    public bool IsInPersonalAgenda { get; set; }
  2. Compute the value of that property in OnGetAsync:

    var sessions = await _apiClient.GetSessionsByAttendeeAsync(User.Identity.Name);
    
    IsInPersonalAgenda = sessions.Any(s => s.ID == id);
  3. 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>
  4. Add OnPostAsync handlers to Session.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();
    }
  5. Attendee should now be able to add/remove sessions to/from their personal agenda.

Add MyAgenda page

  1. Add MyAgenda.cshtml and MyAgenda.cshtml.cs files to the Pages folder.

  2. 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 the MyAgenda page.

  3. Add a virtual GetSessionsAsync method to Index.cshtml.cs:

    protected virtual Task<List<SessionResponse>> GetSessionsAsync()
    {
        return _apiClient.GetSessionsAsync();
    }
  4. Change the _apiClient field in Index.cshtml.cs to be protected instead of private:

    protected readonly IApiClient _apiClient;
  5. 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();
  6. 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);
            }
        }
    }
  7. 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>

Add the My Agenda link to the Layout

  1. Go to the layout file _Layout.cshtml.

  2. Add a link that shows up only when authenticated under the /Speakers link:

    <li authz="true"><a asp-page="/MyAgenda">My Agenda</a></li>
  3. 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.