Here is a step-by-step tutorial on how to create a task manager project in MVC5. You can download a ready sample in MVC5.
1 . Task manager app contains a number of tasks and employees. Employees can handle the assigned tasks.
2 . One task is assigned to one employee. It can also be reassigned to another employee by the manager.
3 . An employee can change the status of a task assigned to him/her and view task details.
4 . The app includes two types of users: the Manager and the Employee.
5 . The Manager can add, edit and close all tasks.
6 . Task status is defined by task color.
7 . All user types have different access rights. To login, a user must enter the user ID and the password.
Proceed with the tutorial by completing the steps below or download task manager sample in MVC5.
At the very beginning we will create a new Microsoft Visual Studio 2013 project and install DHTMLX Scheduler .NET via Nuget.
Your actions:
1 . Open Visual Studio 2013
2 . Go to File → New → Project and select ASP.NET MVC Web Application in the desired programming language. Name it TaskManager.MVC5
3 . Select Next and then select an empty MVC template in the opened window
In order to keep implementation of the login system separately from the rest of the application, we'll define it in a separate class-library project within the same solution.
We will build our membership system using ASP.NET Identity.
The following illustration shows the folder structure of the project:
Your actions:
1 . Select Solution → Add → New Project → Your language → Windows → Class Library
2 . In the Name box enter Scheduler.MVC5.Users
3 . In the Location box enter a name for the project folder
4 . Select Create directory for solution
5 . Click Ok
Add references to the library via Nuget (Package Manager Console).
Note: Please, check your Default Project in all installations of the Nuget packages:
PM>Install-Package EntityFramework
PM>Install-Package Microsoft.AspNet.Identity.Core
PM>Install-Package Microsoft.AspNet.Identity.EntityFramework
PM>Install-Package Microsoft.AspNet.Identity.Owin
PM>Install-Package Microsoft.Owin.Host.SystemWeb -Version 2.1.0
Add the following references: System.Web
Add connectionStrings to the app.config file:
<configuration>
<connectionStrings>
<add name="TaskManagerString" connectionString="data source=(LocalDB)\v11.0;attachdbfilename=|DataDirectory|\TaskManager.mdf;integrated security=True;connect timeout=30;MultipleActiveResultSets=True;App=EntityFramework" providerName="System.Data.SqlClient" />
</connectionStrings>
</configuration>
ApplicationUser class represents a user entry of the application. It should be inherited from the IdentityUser class, that is the default user implementation of EntityFramework.
UserIdentityDbContext represents a class that uses the default entity types for .NET Identity Users, Roles, Claims, Logins.
1 . Create a folder UserStore in the project
2 . Select UserStore, right click Add → New Item → Your language → Code → Class
3 . Enter ApplicationUser in the Name box
4 . Click Ok
Update the code as follows:
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
Â
namespace Scheduler.MVC5.Users
{
/// <summary>
/// represents default EntityFramework IUser implementation
/// </summary>
public class ApplicationUser : IdentityUser
{
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(AppUserManager manager)
{
return await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
}
}
/// <summary>
/// represents a class that uses the default entity types for ASP.NET Identity Users, Roles, Claims, Logins
/// </summary>
public class UserIdentityDbContext : IdentityDbContext<ApplicationUser>
{
public UserIdentityDbContext()
: base("TaskManagerString")
{
}
Â
public UserIdentityDbContext Create()
{
return new UserIdentityDbContext();
}
Â
protected override void OnModelCreating(System.Data.Entity.DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<ApplicationUser>().ToTable("Users");
modelBuilder.Entity<IdentityRole>().ToTable("Roles");
modelBuilder.Entity<IdentityUserRole>().ToTable("UserInRoles");
Â
}
}
}
Note: TaskManagerString is the name of the connection string for the task manager database. It is contained in the app.config
Exposes user related APIs, which will automatically save changes to the UserStore.
1 . Add a New Item to the project with the Name UserManager
2 . Update the code as follows:
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin;
Â
namespace Scheduler.MVC5.Users
{
/// <summary>
/// Exposes user related APIs which will automatically save changes to the UserStore
/// </summary>
public class AppUserManager : UserManager<ApplicationUser>
{
public AppUserManager(IUserStore<ApplicationUser> store) : base(store)
{
}
public AppUserManager Create(IdentityFactoryOptions<AppUserManager> options, IOwinContext context)
{
var manager =
new AppUserManager(new UserStore<ApplicationUser>(context.Get<UserIdentityDbContext>()));
Â
return manager;
}
}
}
UserIdentityDbContext class will delete, recreate, and optionally reseed the database only if the model has changed since the database was created.
1 . Select UserSrore and right click Add &rarr→ New Item&rarr→ Your language &rarr→ Code → Class
2 . Enter UserIdentityDbContext in the Name box
3 . Click Ok
4 . Update the code as follows:
using System.Data.Entity;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
Â
namespace Scheduler.MVC5.Users
{
public class UserIdentityDbContextInit : CreateDatabaseIfNotExists<UserIdentityDbContext>
{}
}
5 . If you need some default data in the database, you need to override the Seed() method.
The default implementation does nothing. We'll update it in order to create the default users and roles:
using System.Data.Entity;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
Â
namespace Scheduler.MVC5.Users
{
public class UserIdentityDbContextInit : CreateDatabaseIfNotExists<UserIdentityDbContext>
{
protected override void Seed(UserIdentityDbContext context)
{
InitTables(context);
base.Seed(context);
}
Â
private void InitTables(UserIdentityDbContext context)
{
var userManager = new AppUserManager(new UserStore<ApplicationUser>(context));
var rolemanager = new RoleManager<IdentityRole>(new RoleStore<IdentityRole>(context));
var names = new[]{"Manager","David","Dana","Linda","John"};
var roles = new[]{"Manager", "Employee"};
Â
//Create role(s) if not exists
Â
foreach (var role in roles)
{
if (!rolemanager.RoleExists(role))
{
rolemanager.Create(new IdentityRole(role));
}
}
Â
//Create user(s)
Â
foreach (var name in names)
{
var user = new ApplicationUser()
{
UserName = name
};
var result = userManager.Create(user, "password");
Â
//and adding user role(s)
Â
if (result.Succeeded)
{
string role = name == "Manager" ? "Manager" : "Employee";
userManager.AddToRole(user.Id, role);
}
}
}
}
}
This class will handle login/logout user attempts.
1 . Add a New Item to the project with the Name SignInHelper
2 . Update the code as follows:
using System.Threading.Tasks;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin.Security;
Â
namespace Scheduler.MVC5.Users
{
public class SignInHelper
{
public AppUserManager AppUserManager { get; private set; }
public IAuthenticationManager AuthenticationManager { get; set; }
Â
public SignInHelper(AppUserManager userManager, IAuthenticationManager authManager)
{
this.AppUserManager = userManager;
this.AuthenticationManager = authManager;
}
Â
Â
public async Task SignInAsync(ApplicationUser user, bool isPersistent, bool rememderBrowser)
{
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie,
DefaultAuthenticationTypes.TwoFactorCookie);
var userIdentity = await user.GenerateUserIdentityAsync(AppUserManager);
if (rememderBrowser)
{
var rememberBrowserIdentity = AuthenticationManager.CreateTwoFactorRememberBrowserIdentity(user.Id);
AuthenticationManager.SignIn(new AuthenticationProperties() {IsPersistent = isPersistent}, userIdentity,
rememberBrowserIdentity);
}
else
{
AuthenticationManager.SignIn(new AuthenticationProperties(){IsPersistent = isPersistent},userIdentity);
}
}
Â
public async Task<string> PasswordSignIn(string userName, string password, bool isPersistent)
{
var user = await AppUserManager.FindByNameAsync(userName);
if (user == null)
{
return SignInStatus.Failure.ToString();
}
if (await AppUserManager.CheckPasswordAsync(user, password))
{
await SignInAsync(user, isPersistent, false);
return SignInStatus.Success.ToString();
}
if (await AppUserManager.IsLockedOutAsync(user.Id))
{
return SignInStatus.LockedOut.ToString();
}
return SignInStatus.Failure.ToString();
}
Â
}
}
1 . Add into the project a new class named AppUserManagerProvider to the Scheduler.MVC5.Users
2 . Update the code as follows:
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin.Security;
Â
namespace Scheduler.MVC5.Users
{
public class AppUserManagerProvider
{
UserIdentityDbContext _dbUsers = new UserIdentityDbContext();
Â
UserIdentityDbContext Db
{
get
{
if (_dbUsers == null)
_dbUsers=new UserIdentityDbContext();
return _dbUsers;
}
}
Â
private static AppUserManager _userManager;
public static AppUserManager GetAppUserManager
{
get
{
return _userManager ??
(_userManager = HttpContext.Current.GetOwinContext().GetUserManager<AppUserManager>());
}
set
{
if (value != null)
{
_userManager = value;
}
}
}
Â
public List<ApplicationUser> Users
{
get { return Db.Users.ToList(); }
}
Â
public string UserId { get { return HttpContext.Current.User.Identity.GetUserId(); } }
Â
public List<string> GetUserRolesName(string id)
{
return Db.Roles.Select(o=>o.Name).ToList();
}
Â
public static IAuthenticationManager AuthenticationManager
{
get
{
return HttpContext.Current.GetOwinContext().Authentication;
}
}
}
}
After that Build Library and Add Reference to the TaskManager.MVC5 site.
The next step is to define the data model of our application and connect it to the database.
As at the previous step, we'll use the Code First approach.
The following illustration shows the folder structure of the project:
Your actions:
1 . Select Solution → Add → New Project → Your language → Windows → Class Library.
2 . In the Name box enter TaskManager.MVC5.Model.
3 . In the Location box, enter a name for the project folder.
4 . Select Create directory for solution.
5 . Click Ok
Add a package via Nuget(Package Manager Console):
Note: Please, check your Default Project in all installations of nuget packages:
PM> Install-Package EntityFramework
Add connectionStrings to the app.config file:
<configuration>
<connectionStrings>
<add name="TaskManagerString" connectionString="data source=(LocalDB)\v11.0;attachdbfilename=|DataDirectory|\TaskManager.mdf;integrated security=True;connect timeout=30;MultipleActiveResultSets=True;App=EntityFramework" providerName="System.Data.SqlClient" />
</connectionStrings>
</configuration>
1 . Add a New Folder with name Modelto the project
2 . Add → New Items into the created folder (Status.cs, Task.cs, TaskManagerDbContext.cs)
Write two simple classes named Status and Task entity as shown below:
namespace TaskManager.MVC5.Model
{
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
[Table("Statuses")]
public class Status {
public int id { get; set; }
[StringLength(50)]
public string title { get; set; }
[StringLength(10)]
public string color { get; set; }
}
}
namespace TaskManager.MVC5.Model
{
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
public class Task
{
public int id { get; set; }
[StringLength(256)]
public string text { get; set; }
public DateTime? start_date { get; set; }
public DateTime? end_date { get; set; }
[Column(TypeName = "text")]
public string details { get; set; }
public Guid? owner_id { get; set; }
public Guid? creator_id { get; set; }
public int? status_id { get; set; }
}
}
We'll also override the Seed() method in order to show some initial statuses:
namespace TaskManager.MVC5.Model
{
using System.Data.Entity;
Â
public class TaskManagerDbContext : DbContext
{
public TaskManagerDbContext()
: base("name=TaskManagerString")
{
}
Â
public virtual DbSet<Status> Statuses { get; set; }
public virtual DbSet<Task> Tasks { get; set; }
Â
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Task>()
.ToTable("Task");
Â
modelBuilder.Entity<Status>()
.ToTable("Status");
}
}
Â
public class TaskManagerDbContextInit : CreateDatabaseIfNotExists<TaskManagerDbContext>
{
protected override void Seed(TaskManagerDbContext context)
{
InitTables(context);
base.Seed(context);
}
Â
private void InitTables(TaskManagerDbContext context)
{
/*
if you need to create data tables
*/
context.Statuses.AddRange(new[]
{
new Status(){title = "Waiting",color = "#5B9BE0"},
new Status(){title = "In progress",color = "#FE7510"},
new Status(){title = "Done",color = "#76B007"}
});
}
}
}
A repository pattern is an abstraction layer between the logic of your application and the implementation of database access. Your access layer can be any from ADO.NET stored procedures to EF. It also excludes duplicated code and simplifies testing.
1 . Add a New Item Interface named IRepository to the current project
2 . Update the code as follows:
using System;
using System.Linq;
Â
namespace TaskManager.MVC5.Model
{
public interface IRepository : IDisposable
{
void Insert<T>(T entity) where T : class;
IQueryable<T> GetAll<T>() where T : class;
void Delete<T>(T entity) where T : class;
void Update<T>(T entity) where T : class;
}
}
3 . Add New Item Class named Repository
4 . Update the code as follows:
using System.Data.Entity;
using System.Linq;
Â
namespace TaskManager.MVC5.Model
{
public class Repository : IRepository
{
private readonly TaskManagerDbContext context = new TaskManagerDbContext();
Â
public void Dispose()
{
context.Dispose();
}
public void Insert<T>(T entity) where T : class
{
if (entity == null) return;
context.Entry(entity).State = EntityState.Added;
context.SaveChanges();
}
public IQueryable<T> GetAll<T>() where T : class
{
return context.Set<T>();
}
public void Delete<T>(T entity) where T : class
{
if (entity == null) return;
context.Entry(entity).State = EntityState.Deleted;
context.SaveChanges();
}
public void Update<T>(T entity) where T : class
{
if (entity == null) return;
context.Set<T>().Attach(entity);
context.Entry(entity).State=EntityState.Modified;
context.SaveChanges();
}
}
}
Finally, Build Library and Add Reference to the TaskManager.MVC5 site.
Install the following libraries to the created project via Nuget (Package Manager Console)
Note: Please, check your Default Project in all installations of NuGet packages:
PM>Install-Package Microsoft.AspNet.Mvc
PM>Install-Package jQuery
Install-Package DHTMLX.Scheduler.NET
PM>Install-Package EntityFramework
PM>Install-Package Microsoft.AspNet.Identity.EntityFramework
PM>Install-Package Microsoft.AspNet.Identity.Owin
Add the following references: System.Runtime.Serialization
The following illustration shows the final folder structure of the project:
Add connection string:
<configuration>
<connectionStrings>
<add name="TaskManagerString" connectionString="data source=(LocalDB)\v11.0;attachdbfilename=|DataDirectory|\TaskManager.mdf;integrated security=True;connect timeout=30;MultipleActiveResultSets=True;App=EntityFramework" providerName="System.Data.SqlClient" />
</connectionStrings>
</configuration>
Configure the globalization settings of the application:
<system.web>
<globalization culture="en-US"/>
</system.web>
To create a database, paste the following code to Global.asax
using System.Data.Entity;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using Scheduler.MVC5.Users;
using TaskManager.MVC5.Model;
namespace TaskManager.MVC5
{
public class MvcApplication : HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RouteConfig.RegisterRoutes(RouteTable.Routes);
//to create users table with/without data if not exists
Database.SetInitializer(new TaskManagerDbContextInit());
var db = new TaskManagerDbContext();
db.Database.Initialize(true);
//to create others tables with/without data if not exists
Database.SetInitializer(new UserIdentityDbContextInit());
var users = new UserIdentityDbContext();
users.Database.Initialize(true);
}
}
}
1 . Add the OWIN Startup Class to your task manager project:
2 . Paste the following code to the created file:
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.Owin;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.OAuth;
using Owin;
using Scheduler.MVC5.Users;
Â
[assembly: OwinStartup(typeof(TaskManager.MVC5.Startup))]
Â
namespace TaskManager.MVC5
{
public class Startup
{
public static OAuthBearerAuthenticationOptions OAuthBearerOptions { get; private set; }
Â
public void Configuration(IAppBuilder app)
{
// For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=316888
app.CreatePerOwinContext((new UserIdentityDbContext()).Create);
app.CreatePerOwinContext<AppUserManager>((new AppUserManager(new UserStore<ApplicationUser>(new UserIdentityDbContext()))).Create);
Â
Â
OAuthBearerOptions = new OAuthBearerAuthenticationOptions();
Â
//This will used the HTTP header: "Authorization" Value: "Bearer 1234123412341234asdfasdfasdfasdf"
app.UseOAuthBearerAuthentication(OAuthBearerOptions);
// Enable the application to use a cookie to store information for the signed in user
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/LogOn")
});
}
}
}
Add a New Folder to the project with the name Models. We will add the following models into it:
1 . Add → Class named AccountModels to the Models folder
2 . Update the code as follows:
using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Web.Security;
namespace TaskManager.MVC5.Models
{
#region Model
public class ChangePasswordModel
{
[Required]
[DataType(DataType.Password)]
[DisplayName("Current password")]
public string OldPassword { get; set; }
[Required]
[DataType(DataType.Password)]
[DisplayName("New password")]
public string NewPassword { get; set; }
[Required]
[DataType(DataType.Password)]
[DisplayName("Confirm new password")]
public string ConfirmPassword { get; set; }
}
public class LogOnModel
{
[Required]
[DisplayName("User name")]
public string UserName { get; set; }
[Required]
[DataType(DataType.Password)]
[DisplayName("Password")]
public string Password { get; set; }
[DisplayName("Remember me?")]
public bool RememberMe { get; set; }
}
public class RegisterModel
{
[Required]
[DisplayName("User name")]
public string UserName { get; set; }
[Required]
[DataType(DataType.EmailAddress)]
[DisplayName("Email address")]
public string Email { get; set; }
[Required]
[DataType(DataType.Password)]
[DisplayName("Password")]
public string Password { get; set; }
[Required]
[DataType(DataType.Password)]
[DisplayName("Confirm password")]
public string ConfirmPassword { get; set; }
}
#endregion
}
1 . Add → Class named TaskDetails.cs to the Models folder
2 . Paste the following code:
using System.Collections.Generic;
using TaskManager.MVC5.Model;
Â
namespace TaskManager.MVC5.Models
{
public class TaskDetails
{
public Task Task { get; set; }
public IEnumerable<Status> Statuses { get; set; }
public TaskDetails(Task ts, IEnumerable<Status> st)
{
Task = ts;
Statuses = st;
}
}
}
In the Views folder of the project create the following folders: Shared, Account and System.
1 . Shared
The Shared folder is used to store views shared between controllers (layout page). Also this folder contains a user control partial view.
At first you need to create a layout:
1 . Create a file called _Layout.cshtml inside of the shared folder 2 . Insert the following code into the created file:
<!DOCTYPE html>
Â
<html lang="en">
<head>
<meta name="viewport" content="width=device-width" />
<title>Task Manager</title>
<link href="~/Content/Site.css" rel="stylesheet" type="text/css" />
<link href="~/scripts/dhtmlxscheduler/dhtmlxscheduler_flat.css" rel="stylesheet" />
Â
</head>
Â
<body>
Â
<div id="header">
Â
<div id="logindisplay">
@Html.Partial("UserControl")
</div>
Â
</div>
<div class="page">
@RenderBody()
</div>
<div id="footer">
<p>©@DateTime.Now.Year - DHTMLX Scheduler .NET</p>
</div>
<style type="text/css"> /*for month events----------*/
.state_1 {
background-color: #5B9BE0;
}
Â
.state_2 {
background-color: #FE7510;
}
Â
.state_3 {
background-color: #76B007;
}
</style>
Â
<script type="text/javascript"> scheduler.templates.event_class = function (start, end, ev) {
return "state_" + ev.status_id;
}
</script>
</body>
Â
Â
</html>
1 . Inside of the Shared folder create a file with the name UserControl.cshtml
2 . Delete the existing code from the opened file and paste the following code instead:
@using Microsoft.AspNet.Identity
@if (Request.IsAuthenticated)
{
using (Html.BeginForm("LogOff", "Account", FormMethod.Post, new { id = "logoutForm", @class = "navbar-right" }))
{
@Html.AntiForgeryToken()
Â
<ul class="hr">
<li>
<a>Welcome @User.Identity.GetUserName() !</a>
</li>
<li><a href="javascript:document.getElementById('logoutForm').submit()">Log off</a></li>
</ul>
}
}
else
{
<ul class="hr">
<li>@Html.ActionLink("Register", "Register", "Account", routeValues: null, htmlAttributes: new { id = "registerLink" })</li>
<li>@Html.ActionLink("Log in", "LogOn", "Account", routeValues: null, htmlAttributes: new { id = "loginLink" })</li>
</ul>
}
2 . Account
The Account folder contains pages for logging in and registering user accounts.
@model TaskManager.MVC5.Models.LogOnModel
@{
ViewBag.Title = "Log in";
Layout = "~/Views/Shared/_Layout.cshtml";
}
Â
<div id="loginContent" class="row">
<p>Please enter your username and password.</p>
Â
<ul>
<li>Manager/password</li>
<li>David/password</li>
<li>Dana/password</li>
<li>Linda/password</li>
<li>John/password</li>
</ul>
@using (Html.BeginForm("LogOn", "Account"))
{
@Html.ValidationSummary(true, "Login was unsuccessful. Please correct the errors and try again.")
<div class="center-block ">
<div class="form-group">
<div class="editor-label">
@Html.LabelFor(m => m.UserName)
</div>
<div class="editor-field">
@Html.TextBoxFor(m => m.UserName, new { @class = "dhx_cal_ltext" })
@Html.ValidationMessageFor(m => m.UserName)
</div>
</div>
Â
<div class="form-group">
<div class="editor-label">
@Html.LabelFor(m => m.Password)
</div>
<div class="editor-field">
@Html.TextBoxFor(m => m.Password, new { @type = "password", @class = "dhx_cal_ltext" })
@Html.ValidationMessageFor(m => m.Password)
</div>
</div>
Â
<div class="editor-label form-group">
@Html.CheckBoxFor(m => m.RememberMe)
@Html.LabelFor(m => m.RememberMe)
</div>
Â
<div class="form-group">
<input type="submit" name="result" value="Log in" class="dhx_btn_set dhx_left_btn_set dhx_save_btn_set" style="width: 70px; float: right;" />
</div>
</div>
}
</div>
@model TaskManager.MVC5.Models.RegisterModel
@{
ViewBag.Title = "Register";
Layout = "~/Views/Shared/_Layout.cshtml";
}
Â
@using (Html.BeginForm("Register", "Account"))
{
<div class="center-block ">
Â
<div class="form-group">
<div class="editor-label">
@Html.LabelFor(m => m.UserName)
</div>
<div class="editor-field">
@Html.TextBoxFor(m => m.UserName)
@Html.ValidationMessageFor(m => m.UserName)
</div>
</div>
Â
<div class="form-group">
<div class="editor-label">
@Html.LabelFor(m => m.Email)
</div>
<div class="editor-field">
@Html.TextBoxFor(m => m.Email)
@Html.ValidationMessageFor(m => m.Email)
</div>
</div>
Â
<div class="form-group">
<div class="editor-label">
@Html.LabelFor(m => m.Password)
</div>
<div class="editor-field">
@Html.TextBoxFor(m => m.Password, new { @type = "password" })
@Html.ValidationMessageFor(m => m.Password)
</div>
</div>
Â
<div class="form-group">
<div class="editor-label">
@Html.LabelFor(m => m.ConfirmPassword)
</div>
<div class="editor-field">
@Html.TextBoxFor(m => m.ConfirmPassword, new { @type = "password" })
@Html.ValidationMessageFor(m => m.ConfirmPassword)
</div>
</div>
<div class="form-group">
<input type="submit" name="result" value="Register" class="dhx_btn_set dhx_left_btn_set dhx_save_btn_set" style="width: 70px; float: right;" />
</div>
</div>
}
3 . System
This folder has the following structure:
1) Employee
@{
ViewBag.Title = "Employee";
Layout = "~/Views/Shared/_Layout.cshtml";
}
@Html.Raw(Model.Render())
<div id="footer-top-border"></div>
<script>
Â
scheduler.attachEvent("onClick", function (id) {
window.location = '@Url.Action("TaskDetails","System")?id=' + id;
});
</script>
The Employee page will look like this:
2) Manager
@model TaskManager.MVC5.Controllers.SystemController.SystemModel
@{
ViewBag.Title = "Manage Tasks";
Layout = "~/Views/Shared/_Layout.cshtml";
}
Â
@Html.Raw(Model.Scheduler.Render())
<div id="footer-top-border"></div>
<style type="text/css">
@foreach(var state in Model.Statuses) {
<text> .dhx_cal_event.state_@state.id div{
background-color: @state.color;
}
</text>
}
</style>
Â
<script type="text/javascript">
var data = JSON.parse('@Html.Raw(Model.Users)');
scheduler.templates.event_header = function (start, end, ev) {
return scheduler.templates.event_date(start) + " - " + scheduler.templates.event_date(end) + " (" + findById(data, ev.owner_id) + ")";
};
Â
function findById(ar, id) {
for (var i = 0; i < ar.length; i++) {
if (ar[i].key == id) {
return ar[i].userName;
}
}
return "";
}
</script>
The resulting Manager page is presented below:
3) TaskDetails
In the System folder add a View named TaskDetails.cshtml
Paste the following code into the created file:
@{
ViewBag.Title = "TaskDetails";
Layout = "~/Views/Shared/_Layout.cshtml";
}
@model TaskManager.MVC5.Models.TaskDetails
Â
<div class="page row">
Â
<span>@ViewData["user"], TaskDetails</span>
Â
Â
@if (Model.Task != null)
{
using (Html.BeginForm("Index", "System/UpdateStatus", FormMethod.Post, null))
{
@Html.Hidden("id", Model.Task.id)
Â
<div class="center-block">
<div class=" text-right form-group">
<span>
Â
@String.Format("{0:MM/dd/yyyy}", Model.Task.start_date),
<b>
@String.Format("{0:HH:mm}", Model.Task.start_date)
– @String.Format("{0:HH:mm}", Model.Task.end_date)
</b>
</span>
</div>
<div class="text-left dhx_cal_ltext form-group">
<h1>@Model.Task.text</h1>
</div>
<div class="text-left details form-group">
<div @( string.IsNullOrEmpty(Model.Task.details) ? "style= 'color':#bbb;" : "") style="word-wrap: break-word;">@(string.IsNullOrEmpty(Model.Task.details) ? "Task has no description" : Model.Task.details)</div>
</div>
<div class="text-left form-group">
<span>Status: </span>
@Html.DropDownList("status_id", new SelectList(Model.Statuses, "id", "title", ViewData["status"]), new
{
@class = "dhx_cal_ltext",
@style = "width:172px;"
})
</div>
Â
<div class=" text-center form-group">
<input type="submit" name="result" value="Update"
class="dhx_btn_set dhx_left_btn_set dhx_save_btn_set" style="width: 70px;" />
Â
<input type="submit" name="result" value="Cancel"
class="dhx_btn_set dhx_right_btn_set dhx_delete_btn_set" style="float: right; width: 70px;" />
</div>
</div>
Â
}
}
Â
</div>
Below you can see how the task details are displayed for employees:
1 . Inside of the Controllers folder Add → Empty Controllers named Account and System
2 . The AccountController contains action methods for log in and registration. Update AccountController.cs as follows:
using System.Threading.Tasks;
using System.Web.Mvc;
using Scheduler.MVC5.Users;
using TaskManager.MVC5.Models;
Â
namespace TaskManager.MVC5.Controllers
{
public class AccountController : Controller
{
Â
#region sign in helper
//get the SignInHelper object during an MVC controller execution
private SignInHelper _helper;
Â
private SignInHelper SignInHelper
{
get { return _helper ?? (_helper = new SignInHelper(AppUserManagerProvider.GetAppUserManager, AppUserManagerProvider.AuthenticationManager)); }
}
#endregion
Â
public AccountController(){}
Â
public AccountController(AppUserManager userManager)
{
AppUserManagerProvider.GetAppUserManager = userManager;
}
Â
[HttpGet]
public ActionResult LogOn()
{
return View();
}
Â
[HttpPost]
public async Task<ActionResult> LogOn(LogOnModel model, string returnUrl)
{
if (!ModelState.IsValid) return View(model);
//Sign in if a model is valid
var result = await SignInHelper.PasswordSignIn(model.UserName, model.Password, model.RememberMe);
Â
switch (result)
{
case "Success":
{
return RedirectToAction("Index", "System");
}
default:
{
ModelState.AddModelError("", "The user name or password is incorrect.");
Â
}break;
Â
}
//if something failed, redisplay form
return View(model);
}
Â
public ActionResult LogOff()
{
AppUserManagerProvider.AuthenticationManager.SignOut();
return RedirectToAction("LogOn", "Account");
}
Â
public ActionResult Register()
{
return View();
}
Â
[HttpPost]
public async Task<ActionResult> Register(RegisterModel model)
{
if (!ModelState.IsValid) return View(model);
//if a model is valid,create a user with credentials
var user = new ApplicationUser {UserName = model.UserName, Email = model.Email};
var result = await AppUserManagerProvider.GetAppUserManager.CreateAsync(user, model.Password);
Â
if (result.Succeeded)
{
//add default role for a new user
await AppUserManagerProvider.GetAppUserManager.AddToRoleAsync(user.Id, "Employee");
//sign in
await SignInHelper.SignInAsync(user, isPersistent: true, rememderBrowser: false);
return RedirectToAction("Index", "System");
}
Â
foreach (var error in result.Errors)
{
ModelState.AddModelError("", error);
}
//if something failed, redisplay form
return View(model);
}
}
}
3 . Update SystemController as it is shown below:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Web.Mvc;
using DHTMLX.Common;
using DHTMLX.Scheduler;
using DHTMLX.Scheduler.Authentication;
using DHTMLX.Scheduler.Controls;
using DHTMLX.Scheduler.Data;
Â
using Scheduler.MVC5.Users;
using TaskManager.MVC5.Model;
using TaskManager.MVC5.Models;
using System.Runtime.Serialization.Json;
Â
namespace TaskManager.MVC5.Controllers
{
public class SystemController : Controller
{
private IRepository _repository;
private AppUserManagerProvider _appUserManagerProvider;
public IRepository Repository
{
get { return _repository ?? (_repository = new Repository()); }
}
Â
public AppUserManagerProvider AppUserManagerProvider
{
get { return _appUserManagerProvider ?? (_appUserManagerProvider = new AppUserManagerProvider()); }
}
Â
Â
Â
// GET: System
public ActionResult Index()
{
//redirect to login page unauthorized user
return !Request.IsAuthenticated ? RedirectToAction("LogOn", "Account") : RedirectToAction(User.IsInRole("Manager") ? "Manager" : "Employee", "System");
}
Â
public ActionResult Manager()
{
var scheduler = new DHXScheduler(this);
Â
#region check rights
if (!RoleIs("Manager"))// checks the role
return RedirectToAction("Index", "System");//in case the role is not manager, redirects to the login page
Â
#endregion
Â
#region configuration
Â
scheduler.Config.first_hour = 8;//sets the minimum value for the hour scale (Y-Axis)
scheduler.Config.hour_size_px = 88;
scheduler.Config.last_hour = 17;//sets the maximum value for the hour scale (Y-Axis)
scheduler.Config.time_step = 30;//sets the scale interval for the time selector in the lightbox.
scheduler.Config.full_day = true;// blocks entry fields in the 'Time period' section of the lightbox and sets time period to a full day from 00.00 the current cell date until 00.00 next day.
Â
scheduler.Skin = DHXScheduler.Skins.Flat;
scheduler.Config.separate_short_events = true;
Â
scheduler.Extensions.Add(SchedulerExtensions.Extension.ActiveLinks);
Â
#endregion
Â
#region views configuration
scheduler.Views.Clear();//removes all views from the scheduler
scheduler.Views.Add(new WeekView());// adds a tab with the week view
var units = new UnitsView("staff", "owner_id") { Label = "Staff" };// initializes the units view
Â
var users = AppUserManagerProvider.Users;
var staff = new List<object>();
foreach (var user in users)
{
if (AppUserManagerProvider.GetUserRolesName(user.Id).Contains("Employee"))
{
staff.Add(new { key = user.Id, label = user.UserName });
}
}
Â
units.AddOptions(staff);// sets X-Axis items to names of employees
scheduler.Views.Add(units);//adds a tab with the units view
scheduler.Views.Add(new MonthView()); // adds a tab with the Month view
scheduler.InitialView = units.Name;// makes the units view selected initially
Â
scheduler.Config.active_link_view = units.Name;
#endregion
Â
#region lightbox configuration
var text = new LightboxText("text", "Task") { Height = 20, Focus = true };// initializes a text input with the label 'Task'
scheduler.Lightbox.Add(text);// adds the control to the lightbox
var description = new LightboxText("details", "Details") { Height = 80 };// initializes a text input with the label 'Task'
scheduler.Lightbox.Add(description);
var status = new LightboxSelect("status_id", "Status");// initializes a dropdown list with the label 'Status'
status.AddOptions(Repository.GetAll<Status>().Select(s => new
{
key = s.id,
label = s.title
}));// populates the list with values from the 'Statuses' table
scheduler.Lightbox.Add(status);
//add users list
var sUser = new LightboxSelect("owner_id", "Employee");
sUser.AddOptions(staff);
//--
scheduler.Lightbox.Add(sUser);
Â
scheduler.Lightbox.Add(new LightboxTime("time"));// initializes and adds a control area for setting start and end times of a task
#endregion
Â
#region data
scheduler.EnableDataprocessor = true;// enables dataprocessor
scheduler.LoadData = true;//'says' to send data request after scheduler initialization
scheduler.Data.DataProcessor.UpdateFieldsAfterSave = true;// Tracks after server responses for modified event fields
#endregion
Â
Employees[] employees = users.Select(o => new Employees()
{
key = o.Id,
userName = o.UserName
}).ToArray();
Â
List<Status> statuses = Repository.GetAll<Status>().ToList();
Â
var js = new DataContractJsonSerializer(typeof(Employees[]));
var ms = new MemoryStream();
js.WriteObject(ms, employees);
ms.Position = 0;
var sr = new StreamReader(ms);
var json = sr.ReadToEnd();
sr.Close();
ms.Close();
var model = new SystemModel(scheduler, json,statuses);
return View(model);
}
Â
private bool RoleIs(string role)
{
return Request.IsAuthenticated && User.IsInRole(role);
}
Â
public ActionResult Employee()
{
var scheduler = new DHXScheduler(this);
Â
#region check rights
if (!RoleIs("Employee"))
{
return RedirectToAction("Index", "System");
}
#endregion
Â
#region configuration
Â
scheduler.Config.separate_short_events = true;
scheduler.Config.hour_size_px = 88;
Â
scheduler.Extensions.Add(SchedulerExtensions.Extension.Cookie);// activates the extension to provide cookie
scheduler.Extensions.Add(SchedulerExtensions.Extension.Tooltip);// activates the extension to provide tooltips
var template = "<b>Task:</b> {text}<br/><b>Start date:</b>";
template += "<%= scheduler.templates.tooltip_date_format(start) %><br/><b>End date:</b>";
template += "<%= scheduler.templates.tooltip_date_format(end) %>";
scheduler.Templates.tooltip_text = template; // sets template for the tooltip text
Â
scheduler.Skin = DHXScheduler.Skins.Flat;
#endregion
Â
#region views
scheduler.Views.Clear();//removes all views from the scheduler
scheduler.Views.Add(new WeekAgendaView());// adds a tab with the weekAgenda view
scheduler.Views.Add(new MonthView()); // adds a tab with the Month view
scheduler.InitialView = scheduler.Views[0].Name;// makes the weekAgenda view selected initially
#endregion
Â
#region data
scheduler.SetEditMode(EditModes.Forbid);// forbids editing of tasks
scheduler.LoadData = true;//'says' to send data request after scheduler initialization
scheduler.DataAction = "Tasks";//sets a controller action which will be called for data requests
scheduler.Data.Loader.PreventCache();// adds the current ticks value to url to prevent caching of the request
#endregion
Â
return View(scheduler);
}
Â
Â
public ActionResult Data()
{
//if the user is not authorized or not in the Manager Role, returns the empty dataset
if (!RoleIs("Manager")) return new SchedulerAjaxData();
Â
var tasks = Repository.GetAll<Task>()
.Join(Repository.GetAll<Status>(), task => task.status_id, status => status.id, (task, status) => new { Task = task, Status = status })
.Select(o => new
{
o.Status.color,
o.Task.id,
o.Task.owner_id,
o.Task.details,
o.Task.end_date,
o.Task.start_date,
o.Task.text,
o.Task.status_id
});
Â
var resp = new SchedulerAjaxData(tasks);
return resp;
Â
}
public ActionResult Save(Task task)
{
// an action against particular task (updated/deleted/created)
var action = new DataAction(Request.Form);
#region check rights
if (!RoleIs("Manager"))
{
action.Type = DataActionTypes.Error;
return new AjaxSaveResponse(action);
}
#endregion
Â
task.creator_id = Guid.Parse(AppUserManagerProvider.UserId);
try
{
switch (action.Type)
{
case DataActionTypes.Insert:
Repository.Insert(task);
break;
case DataActionTypes.Delete:
Repository.Delete(task);
break;
case DataActionTypes.Update:
Repository.Update(task);
break;
}
action.TargetId = task.id;
}
catch (Exception)
{
action.Type = DataActionTypes.Error;
}
Â
var color = Repository.GetAll<Status>().SingleOrDefault(s => s.id == task.status_id);
Â
var result = new AjaxSaveResponse(action);
result.UpdateField("color", color.color);
return result;
}
Â
public ActionResult Tasks()
{
#region check rights
if (!RoleIs("Employee"))
{
return new SchedulerAjaxData();//returns the empty dataset
}
#endregion
Â
Â
var result = Repository.GetAll<Task>()
.Join(Repository.GetAll<Status>(), task => task.status_id, status => status.id, (task, status) => new { Task = task, Status = status })
.Select(o => new
{
o.Status.color,
o.Task.id,
o.Task.owner_id,
o.Task.details,
o.Task.end_date,
o.Task.start_date,
o.Task.text,
o.Task.status_id
});
Â
var tasks = new List<object>();
Â
foreach (var r in result.ToList())
{
if (r.owner_id == Guid.Parse(AppUserManagerProvider.UserId))
{
tasks.Add(new
{
r.color,
r.id,
r.owner_id,
r.details,
r.end_date,
r.start_date,
r.text,
r.status_id
});
}
}
Â
var resp = new SchedulerAjaxData(tasks);
return resp;
}
Â
public ActionResult TaskDetails(int? id)
{
#region check rights
Â
if (!RoleIs("Employee"))
{
return RedirectToAction("Index", "System");
}
#endregion
Â
var task = default(Task);
if (id != null)
{
task = Repository.GetAll<Task>().FirstOrDefault(o => o.id == id);
Â
if (task.owner_id != Guid.Parse(AppUserManagerProvider.UserId))
task = default(Task);
}
Â
var statuses = Repository.GetAll<Status>().ToArray();
Â
ViewData["status"] = task != default(Task) ? task.status_id : statuses[0].id;
ViewData["user"] = User.Identity.Name;
return View(new TaskDetails(task, statuses));
}
Â
public ActionResult UpdateStatus(int? id)
{
if (!RoleIs("Employee") || this.Request.Form["result"] != "Update" || id == null)
return RedirectToAction("Index", "System");
Â
Â
var task = Repository.GetAll<Task>().SingleOrDefault(ev => ev.id == id);
Â
if (task == default(Task) && task.owner_id != Guid.Parse(AppUserManagerProvider.UserId))
return RedirectToAction("Index", "System");
Â
task.status_id = int.Parse(this.Request.Form["status_id"]);
UpdateModel(task);
Repository.Update(task);
Â
return RedirectToAction("Index", "System");
}
Â
public class SystemModel
{
public DHXScheduler Scheduler { get; set; }
public string Users { get; set; }
public List<Status> Statuses { get; set; }
public SystemModel(DHXScheduler sched, string users,List<Status> statuses )
{
Scheduler = sched;
Users = users;
Statuses = statuses;
}
}
Â
Â
//class for JSON string
[DataContract]
public class Employees
{
[DataMember]
public string key { get; set; }
[DataMember]
public string userName { get; set; }
}
}
}
The controller file in our application SystemController.cs defines the following controls:
In all the actions defined at this and further steps (except Index()), we will do 'rights checking' to protect the app from unauthorized access, in case a user skips the login page and loads our server-side login script directly in the browser.
To avoid repeating one and the same code several times, we'll also specify a function (RoleIs) that will implement such checking functionality and later, when we need to check rights, we'll just call this function.
Data ActionResult loads data from the database.
Save ActionResult saves changes to the database.
4 . Move to Solution Explorer → RouteConfig.cs file.
5 . Rewrite the default map route in the opened file as follows:
routes.MapRoute(
"Default", // the route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "System", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
1 . In the Content folder add a Style Sheet (create it, if it does not exist) and named it Site.css This file contains styles for all site.
2 . Paste the following code to the created file:
/*----------------------------------------------------------
The base color for this template is #5c87b2. If you'd like
to use a different color start by replacing all instances of
#5c87b2 with your new color.
----------------------------------------------------------*/
body {
background-color: #ffffff;
font-size: 14px;
font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
margin: 0;
padding: 0;
color: #000000;
}
Â
a:link {
color: #034af3;
text-decoration: underline;
}
Â
a:visited {
color: #505abc;
}
Â
a:hover {
color: #1d60ff;
text-decoration: none;
}
Â
a:active {
color: #12eb87;
}
Â
p, ul {
margin-bottom: 20px;
line-height: 1.6em;
}
Â
/* HEADINGS
----------------------------------------------------------*/
h1, h2, h3, h4, h5, h6 {
font-size: 18px;
color: #000;
font-family: Arial, Helvetica, sans-serif;
}
Â
h1 {
font-size: 36px;
padding-bottom: 0;
margin-bottom: 0;
}
Â
h2 {
font-size: 30px;
padding: 0 0 10px 0;
}
Â
h3 {
font-size: 24px;
}
Â
h4 {
font-size: 18em;
}
Â
h5, h6 {
font-size: 14em;
}
Â
/* this rule styles <h2> tags that are the
first child of the left and right table columns */
.rightColumn > h1, .rightColumn > h2, .leftColumn > h1, .leftColumn > h2 {
margin-top: 0;
}
/**/
body, html {
height: 90%;
margin: 0;
padding: 0;
}
Â
/* PRIMARY LAYOUT ELEMENTS
----------------------------------------------------------*/
Â
/* you can specify a greater or lesser percentage for the
page width. Or, you can specify an exact pixel width. */
.page {
height: 100%;
margin-left: auto;
margin-right: auto;
}
Â
#header {
position: relative;
margin-bottom: 0px;
color: #000;
padding: 0;
border-bottom: #5780AD 3px solid;
}
Â
#header h1 {
font-weight: bold;
padding: 5px 0;
margin: 0;
color: #fff;
border: none;
line-height: 2em;
font-family: Arial, Helvetica, sans-serif;
}
Â
#main {
padding: 30px 30px 15px 30px;
background-color: #fff;
margin-bottom: 30px;
_height: 1px; /* only IE6 applies CSS properties starting with an underscore */
}
Â
#footer {
color: #000000;
text-align: center;
line-height: normal;
bottom: 0;
margin-bottom: 0;
}
Â
#footer-top-border {
border-top: 1px solid #CECECE;
}
Â
/* FORM LAYOUT ELEMENTS
----------------------------------------------------------*/
Â
fieldset {
margin: 1em 0;
padding: 1em;
border: 1px solid #ffffff;
}
Â
fieldset p {
margin: 2px 12px 10px 10px;
}
Â
Â
Â
input[type="text"] {
width: 200px;
border: 1px solid #CCC;
}
Â
input[type="password"] {
width: 200px;
border: 1px solid #CCC;
}
Â
Â
/* MISC
----------------------------------------------------------*/
.clear {
clear: both;
}
Â
.error {
color: Red;
}
Â
#menucontainer {
margin-top: 40px;
}
Â
div#title {
display: block;
float: left;
text-align: left;
}
Â
#logindisplay {
display: block;
text-align: right;
margin: 10px;
color: #000000;
}
Â
#logindisplay a:link {
color: #000000;
text-decoration: underline;
}
Â
#logindisplay a:visited {
color: #000000;
text-decoration: underline;
}
Â
#logindisplay a:hover {
color: #000000;
text-decoration: none;
}
Â
/* Styles for validation helpers
-----------------------------------------------------------*/
.field-validation-error {
color: #ff0000;
}
Â
.field-validation-valid {
display: none;
}
Â
.input-validation-error {
border: 1px solid #ff0000;
background-color: #ffeeee;
}
Â
.validation-summary-errors {
font-weight: bold;
color: #ff0000;
}
Â
.validation-summary-valid {
display: none;
}
Â
/* Styles for editor and display helpers
----------------------------------------------------------*/
.display-label,
.editor-label,
.display-field,
.editor-field {
margin: 0.5em 0;
}
Â
.text-box {
width: 30em;
}
Â
.text-box.multi-line {
height: 6.5em;
}
Â
.tri-state {
width: 6em;
}
/*horizontal list*/
ul.hr {
margin: 0;
padding: 4px;
}
Â
ul.hr li {
display: inline;
margin-right: 5px;
padding: 3px;
}
Â
Â
.center-block {
width: 220px;
padding: 10px;
margin: 0 auto;
}
Â
.row {
margin-left: 15px;
margin-right: 15px;
}
Â
.text-left {
text-align: left;
}
Â
.text-center {
text-align: center;
}
Â
.text-right {
text-align: right;
}
Â
.form-group {
margin-top: 10px;
margin-bottom: 10px;
}
Â
input[type="text"], input[type="password"], input[type="checkbox"] {
padding: 7px 9px;
}
Â
input[type="submit"].dhx_save_btn_set:hover {
background-color: #528CCA;
}
Â
input[type="submit"].dhx_save_btn_set:active {
background-color: #6BA5E3;
}
Â
Â
input[type="submit"].dhx_delete_btn_set:hover {
background-color: #CCCCCC;
}
Â
input[type="submit"].dhx_delete_btn_set:active {
background-color: #949494;
}
Â
.details {
white-space: pre;
border: 1px solid #eeeeee;
padding: 10px;
}
Â
.form-bordered {
border: 1px solid #eeeeee;
}
Â
div.dhx_cal_ltext {
height: 26px !important;
}
/*for month events----------*/
.dhx_cal_event_clear {
padding: 2px 0 2px 5px !important;
-ms-border-radius: 13px !important;
border-radius: 13px !important;
line-height: 14px !important;
color: white !important;
}
That's all. You can download task manager sample in MVC5.