Sprint 4 : Menu with security and Joining Contest

The Sprint 4 was shorter due to some time restriction. However some good development have been done. The first one concern the menu where it is now possible to display depending of the user role. The first thing to do is to modify the _Layout.cshtml file. First, we need to add the @model to specify what we want to get from the controller the entity. This is required because the controller will pass the role of the user logged in the system. Depending of his role, we will adjust the menu. The second thing in the layout partial is that we will pass this model to the partial of the menu.

@using WebSite.Resources
@model object /*This is required to work with the model passed by the controller*/
<!DOCTYPE html>
<html>
    <body>
        @Html.Partial("_Header", Model)
        <div class="container body-content">
            @RenderBody()
        </div>
    </body>
</html>

Having the model set to object allow us to work with every model/view model possible. In the header partial it is possible to compare to an interface. If the view model that we pass from the controller inherit from the interface than we can check the role and proceed. Otherwise, we display nothing. The header partial do several thing and one of them is to parse to IUIPermission and pass this parsed object to the Menu partial.

//

The IUIPermission allows us to specify roles and check them to display or not elements of the menu.

public interface IUIPermission
{
    bool IsAdministrator { get; set; }
    bool IsModerator { get; set; }
}

Here is an example of the menu:

<div class="navbar-collapse collapse">
    <ul class="nav navbar-nav">
        <li>@Html.ActionLink("Home", "Index", "Home")</li>
        <li>@Html.ActionLink("Contests", "List", "UserContest", new { Area = Constants.Areas.CONTEST }, new { })</li>
            
        @if(Model.IsAdministrator || Model.IsModerator)
        {
            <li>
                <a href="#">Moderation</a>
                <ul class="dropdown-menu">
                    <li><a href="#">Split</a></li>
                    @if (Model.IsAdministrator || Model.IsModerator)
                    {
                        <li class="dropdown-header">Administrator</li>
                        <li><a href="#">DashBoard</a></li>
                        <li><a href="#">News</a></li>
                    }
                    <li><a href="#">Users</a></li>
                </ul>
            </li>
        }
    </ul>
</div>

The second big thing that has been done is concerning joining a contest for a user. A page with the description of the contest has been created. Under the description, a button is displayed to join the contest if the user logged is not already subscribed.

[HttpGet]
[Authorize(Roles = ApplicationRole.NORMAL_USER)]
public ActionResult Detail(int id)
{
    //Get the contest to show it information but also to know if the current user is subscribed to the contest
    var contestForUser = this.contestService.GetContestByIdWithContesters(id);
            
    //Transform back everything for the page
    var viewContestsForUser = this.MapperFacade.GetViewModel<Model.Entities.Contest.Contest, ContestDetailViewModel>(contestForUser);

    //View Model
    viewContestsForUser.IsCurrentUserRegisteredToThisContest = contestForUser.IsUserInContest(new ApplicationUser {Id = this.runningContext.GetUserId().ToString()});

    return ViewModel(viewContestsForUser);

}

The line before the return is the one that tell the view model if the user is in the contest or not. As you can see, we get the contest with all contestants. From here, we check if the user is in the list or not. The view change depending of if the user is already in the contest or not.

And the view if the user is already in the contest:

[HttpPost]
[Authorize(Roles = ApplicationRole.NORMAL_USER)]
[ValidateAntiForgeryToken]
public ActionResult Join(int id)
{
    //Get the contest information but also the confirmation that the user is not already registered for the contest
    IEnumerable<ValidationResult> validationsMessage;
    try
    {
            validationsMessage = this.contestService.SaveUserToContest(id, this.runningContext.GetUserId());
    }
    catch (Exception e)
    {    
        Log.Error(string.Format("Error during the UserPortefolio Join for contest id {0}", id), e);
        return View("JoinFail");
    }
    if (validationsMessage != null && validationsMessage.Any())
    {
        Log.Warning(string.Format("The user {0} is trying to join contest id {1} but has the error '{2}.'", this.runningContext.GetUserId(),id,string.Join(",",validationsMessage)));
        var viewModel = Mapper.Map<IEnumerable<ValidationResult>, GeneralValidationsMessageViewModel>(validationsMessage);
        return View("JoinFail", viewModel);
    }

    var contestForUser = this.contestService.GetContestById(id);

    //Transform back everything for the page
    var viewContestsForUser = this.MapperFacade.GetViewModel<Model.Entities.Contest.Contest, ContestDetailViewModel>(contestForUser);

    return ViewModel(viewContestsForUser);

}

Joining a contest require to verify if the user is already subscribed to the contest, if not we join. The controller call the service, which call the accessor.

public IEnumerable<ValidationResult> SaveUserToContest(int contestId, Guid userId)
{
    var contest = this.contestRepository.GetById(contestId); //This is in the accessor because later we could search in Cache

    if(contest.RegistrationRules.IsDateValidToSubscribeUser)
    {
        this.contestRepository.SaveUserToContest(contest, userId);
        this.unitOfWork.Commit();
    }
    else
    {
        return new List<ValidationResult>() {new ValidationResult(Resources.ContestValidationMessage.InvalidDateToSubscribe)};
    }
    return new List<ValidationResult>();
}

We do a quick validation about if the date is still okay to subscribe. Then, we go in the repository to save everything in the database. The repository verify some other thing like if the user already has a portfolio in this contest.

public void SaveUserToContest(Contest contest, Guid userId)
{
    var portefolioExist = this.UnitOfWork.Set<Portefolio>()
                                .Include(d=>d.Owners)
                                .Any(p => p.ContestId == contest.Id 
                                        && p.Owners.Any(d => d.UserId == userId.ToString()));
    if (portefolioExist)
    {
        throw new UserAlreadyInContestException(string.Format(Resources.ContestValidationMessage.PortefolioAlreadyCreatedForUserAndContest, contest.Id, userId));
    }
    var portefolio = this.UnitOfWork.Set<Portefolio>().Create();
        this.UnitOfWork.Entry(portefolio).State = EntityState.Added;
        portefolio.ResetFromContest(contest); //Required to set the default values like the initial capital
        var userPortefolio = new UserPortefolio() {Portefolio = portefolio, PortefolioId = portefolio.Id, UserId = userId.ToString(), DateJoin = this.RunningContext.GetCurrentTime()};
        this.Save(userPortefolio);
    
}

It is also the time where we set to the portfolio the default values from the contest registration rules. This is the case of the initial money for a portfolio.

More about Patrick Desjardins

Leave a Reply

Your email address will not be published. Required fields are marked *