Email Reminders

This commit is contained in:
2015-10-28 10:25:19 -04:00
parent 9177a8dbb3
commit cfe753fb00
13 changed files with 272 additions and 10 deletions
@@ -3,6 +3,7 @@ using System.Linq;
using System.Web.Mvc; using System.Web.Mvc;
using MileageTraker.Web.Attributes; using MileageTraker.Web.Attributes;
using MileageTraker.Web.DAL; using MileageTraker.Web.DAL;
using MileageTraker.Web.Email;
using MileageTraker.Web.Models; using MileageTraker.Web.Models;
using MileageTraker.Web.Utility; using MileageTraker.Web.Utility;
using MileageTraker.Web.ViewModels; using MileageTraker.Web.ViewModels;
@@ -213,5 +214,15 @@ namespace MileageTraker.Web.Controllers
var names = DataService.GetServiceCenterNamesAutocomplete(term); var names = DataService.GetServiceCenterNamesAutocomplete(term);
return Json(names, JsonRequestBehavior.AllowGet); return Json(names, JsonRequestBehavior.AllowGet);
} }
[ActionLog]
[Authorize(Roles = "Developer")]
public ActionResult SendVehicleServiceReminderEmails()
{
var emailService = new ServiceReminderEmailService();
emailService.SendAllNotificationEmails();
SetStatusMessage("Vehicle Service Reminders Sent");
return Redirect(Request.UrlReferrer.ToString());
}
} }
} }
+7
View File
@@ -1,6 +1,7 @@
using System.Configuration; using System.Configuration;
using System.Net.Mail; using System.Net.Mail;
using System.Reflection; using System.Reflection;
using System.Web.Security;
using MileageTraker.Web.Models; using MileageTraker.Web.Models;
using log4net; using log4net;
@@ -56,10 +57,16 @@ namespace MileageTraker.Web.Email
SendMessage(new MailMessage(_emaialFromAddress, user.Email, _initializePasswordSubject, body)); SendMessage(new MailMessage(_emaialFromAddress, user.Email, _initializePasswordSubject, body));
} }
public void SendServiceReminder(ServiceReminderEmail email)
{
SendMessage(new MailMessage(_emaialFromAddress, email.Recipient.Email, email.Subject, email.Body));
}
private void SendMessage(MailMessage mailMessage) private void SendMessage(MailMessage mailMessage)
{ {
try try
{ {
Logger.Debug("Email sending to " + mailMessage.To + ", subject: " + mailMessage.Subject);
_smtpClient.Send(mailMessage); _smtpClient.Send(mailMessage);
} }
catch (SmtpException ex) catch (SmtpException ex)
+78
View File
@@ -0,0 +1,78 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using MileageTraker.Web.Models;
namespace MileageTraker.Web.Email
{
public class ServiceReminderEmail
{
private readonly string _emailSubject;
private readonly string _emailBody;
public User Recipient { get; set; }
public IList<ServiceReminder> ServiceReminders { get; set; }
public string Subject
{
get
{
if (!ServiceReminders.Any())
return "No upcoming vehicle services currently scheduled";
var vehicles = ServiceReminders.Select(sr => sr.Vehicle.VehicleId).Distinct().ToList();
var vehicleDesc = vehicles.Count > 1 ? vehicles.Count.ToString() + " vehicles" : "Vehicle Id " + vehicles.First();
var subject = string.Format(_emailSubject, vehicleDesc);
return subject;
}
}
public string Body
{
get
{
var reminderTexts = GetReminderTexts();
var body =
string.Format(_emailBody,
Recipient.FullName,
Environment.NewLine + Environment.NewLine
+ string.Join(Environment.NewLine, reminderTexts)
);
return body;
}
}
public ServiceReminderEmail(User recipient, IList<ServiceReminder> serviceReminders)
{
_emailSubject = ConfigurationManager.AppSettings["ServiceReminderSubject"];
_emailBody = ConfigurationManager.AppSettings["ServiceReminderBody"];
Recipient = recipient;
ServiceReminders = serviceReminders;
}
private IEnumerable<string> GetReminderTexts()
{
if (!ServiceReminders.Any())
return new[] {"No upcoming services currently scheduled"};
return
from sr in ServiceReminders
let diff =
sr.TargetOdometer - (sr.Vehicle.CurrentOdometer.HasValue ? sr.Vehicle.CurrentOdometer.Value : 0)
orderby diff
let description =
!string.IsNullOrEmpty(sr.Description)
? String.Format(", description is '{0}'", sr.Description)
: string.Empty
let assignedTo =
!string.IsNullOrEmpty(sr.Vehicle.Assigned)
? String.Format("assigned to {0}", sr.Vehicle.Assigned)
: "unassigned"
select
string.Format("Vehicle with Id {0}, which is {3}, has a reminder for service {1} miles{2}.",
sr.Vehicle.VehicleId, diff < 0 ? " overdue " + (-diff).ToString() : " in " + diff.ToString(),
description,
assignedTo);
}
}
}
+70
View File
@@ -0,0 +1,70 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using MileageTraker.Web.DAL;
using MileageTraker.Web.Models;
using log4net;
namespace MileageTraker.Web.Email
{
public class ServiceReminderEmailService : IDisposable
{
private static readonly ILog Logger = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
protected readonly DataService DataService = new DataService();
protected readonly EmailNotificationService EmailService = new EmailNotificationService();
public void SendAllNotificationEmails()
{
Logger.Info("Starting vehicle service notifications");
var serviceReminders = DataService.GetUpcomingServiceReminders().ToList();
Logger.Debug("Got " + serviceReminders.Count + " service reminders.");
SendAdminEmails(serviceReminders);
SendUserEmails(serviceReminders);
}
private void SendAdminEmails(IList<ServiceReminder> serviceReminders)
{
var admins =
(from u in DataService.GetUsers()
where u.Roles.Any(r => r.RoleName == "Vehicle Admin")
select u).Distinct().ToList();
var adminEmails =
from admin in admins
select new ServiceReminderEmail(admin, serviceReminders);
foreach (var email in adminEmails)
{
EmailService.SendServiceReminder(email);
}
}
private void SendUserEmails(IList<ServiceReminder> serviceReminders)
{
// send email to user
var userEmails =
from sr in serviceReminders
group sr by sr.Vehicle.Assigned
into g
let driver = DataService.FindUserByFullName(g.Key)
where driver != null
let userReminders = g.ToList()
select new ServiceReminderEmail(driver, userReminders);
foreach (var email in userEmails)
{
EmailService.SendServiceReminder(email);
}
}
public void Dispose()
{
DataService.Dispose();
}
}
}
@@ -19,14 +19,32 @@ namespace MileageTraker.Web.Migrations
.PrimaryKey(t => t.ServiceReminderId) .PrimaryKey(t => t.ServiceReminderId)
.ForeignKey("dbo.Vehicle", t => t.Vehicle_VehicleId, cascadeDelete: true) .ForeignKey("dbo.Vehicle", t => t.Vehicle_VehicleId, cascadeDelete: true)
.Index(t => t.Vehicle_VehicleId); .Index(t => t.Vehicle_VehicleId);
Sql(@"INSERT INTO [Role]
([RoleId]
,[RoleName]
,[Description])
VALUES
('dd28bb32-afb6-4d54-bce6-04457bcf79d9'
,'Vehicle Admin'
,'Vehicle Administrator')");
Sql(@"INSERT INTO [RoleUser]
([User_UserId], [Role_RoleId])
VALUES
('A1720B63-5970-4313-9AC3-90B844FABD65',
'dd28bb32-afb6-4d54-bce6-04457bcf79d9')");
} }
public override void Down() public override void Down()
{ {
DropForeignKey("dbo.ServiceReminder", "Vehicle_VehicleId", "dbo.Vehicle"); DropForeignKey("dbo.ServiceReminder", "Vehicle_VehicleId", "dbo.Vehicle");
DropIndex("dbo.ServiceReminder", new[] { "Vehicle_VehicleId" }); DropIndex("dbo.ServiceReminder", new[] { "Vehicle_VehicleId" });
DropTable("dbo.ServiceReminder"); DropTable("dbo.ServiceReminder");
Sql(@"DELETE FROM [Role]
WHERE [RoleId] = 'dd28bb32-afb6-4d54-bce6-04457bcf79d9'");
Sql(@"DELETE FROM [RoleUser]
WHERE [Role_RoleId] = 'dd28bb32-afb6-4d54-bce6-04457bcf79d9'");
} }
} }
} }
+30
View File
@@ -0,0 +1,30 @@
using System;
using Hangfire;
using Microsoft.Owin;
using MileageTraker.Web.Email;
using Owin;
[assembly: OwinStartup(typeof(MileageTraker.Web.Startup))]
namespace MileageTraker.Web
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
GlobalConfiguration.Configuration
.UseSqlServerStorage("MileageTrakerContext");
app.UseHangfireDashboard();
app.UseHangfireServer();
SetupRecurringJobs();
}
private void SetupRecurringJobs()
{
RecurringJob.AddOrUpdate<ServiceReminderEmailService>(
"serviceReminderJob", s => s.SendAllNotificationEmails(), Cron.Weekly(DayOfWeek.Monday, 6));
}
}
}
+1
View File
@@ -7,6 +7,7 @@
@Html.Partial("_StatusMessage") @Html.Partial("_StatusMessage")
<h2 class="center-content"><i class="fa fa-clock-o"></i> @ViewBag.Title</h2> <h2 class="center-content"><i class="fa fa-clock-o"></i> @ViewBag.Title</h2>
<h4 class="center-content">Reminder for future vehicle service</h4>
@using (Html.BeginForm("Create", "ServiceReminder", FormMethod.Post, new { @class = "form-horizontal well center-content" })) @using (Html.BeginForm("Create", "ServiceReminder", FormMethod.Post, new { @class = "form-horizontal well center-content" }))
{ {
+6 -2
View File
@@ -42,7 +42,11 @@
<li>@Html.ActionLink("Vehicles", "Index", "Vehicle")</li> <li>@Html.ActionLink("Vehicles", "Index", "Vehicle")</li>
<li>@Html.ActionLink("Vehicle Service", "Index", "VehicleService")</li> <li>@Html.ActionLink("Vehicle Service", "Index", "VehicleService")</li>
<li>@Html.ActionLink("Fuel Logs", "Index", "FuelLog")</li> <li>@Html.ActionLink("Fuel Logs", "Index", "FuelLog")</li>
<li>@Html.ActionLink("Cost Report", "VehicleCostIndex", "Vehicle")</li> <li>@Html.ActionLink("Cost Report", "VehicleCostIndex", "Vehicle")</li>
@if (User.IsInRole("Developer"))
{
<li>@Html.ActionLink("Send Service Reminder Emails", "SendVehicleServiceReminderEmails", "VehicleService")</li>
}
</ul> </ul>
</li> </li>
<li id="user-nav">@Html.ActionLink("Users", "Index", "User")</li> <li id="user-nav">@Html.ActionLink("Users", "Index", "User")</li>
@@ -50,7 +54,7 @@
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"><span>Config</span> <i class="fa fa-caret-down"></i></a> <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"><span>Config</span> <i class="fa fa-caret-down"></i></a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li>@Html.ActionLink("Cities", "Index", "City")</li> <li>@Html.ActionLink("Cities", "Index", "City")</li>
<li>@Html.ActionLink("Purposes", "Index", "Purpose")</li> <li>@Html.ActionLink("Purposes", "Index", "Purpose")</li>
</ul> </ul>
</li> </li>
} }
+1
View File
@@ -5,6 +5,7 @@
} }
<h2 class="center-content"><i class="fa fa-wrench"></i> @ViewBag.Title</h2> <h2 class="center-content"><i class="fa fa-wrench"></i> @ViewBag.Title</h2>
<h4 class="center-content">Track completed service for a vehicle</h4>
@using (Html.BeginForm("Create", "VehicleService", FormMethod.Post, new { @class = "form-horizontal well center-content" })) @using (Html.BeginForm("Create", "VehicleService", FormMethod.Post, new { @class = "form-horizontal well center-content" }))
{ {
+9 -2
View File
@@ -4,6 +4,7 @@
ViewBag.Title = "Vehicle Service"; ViewBag.Title = "Vehicle Service";
var grid = new WebGrid(Model.ServiceItems, rowsPerPage: 45); var grid = new WebGrid(Model.ServiceItems, rowsPerPage: 45);
var queryParams = new { Model.Year, Model.Month, Model.MonthRange, Model.VehicleId }; var queryParams = new { Model.Year, Model.Month, Model.MonthRange, Model.VehicleId };
var serviceOverdueBadge = Model.UpcomingServiceReminders.Any(sr => sr.IsServiceOverdue) ? "badge-warning" : "";
} }
@section Scripts { @section Scripts {
@@ -31,9 +32,15 @@
<div class="btn-toolbar pull-left"> <div class="btn-toolbar pull-left">
@Html.ActionLink("Add Service", "Create", null, new { @class = "btn" }) @Html.ActionLink("Add Service", "Create", null, new { @class = "btn" })
@Html.ActionLink("Export", "Export", queryParams, new { @class = "btn" }) @Html.ActionLink("Export", "Export", queryParams, new { @class = "btn" })
<a class="qtip-show-next-div btn">Upcoming Services <span class="badge">@Model.UpcomingServiceReminders.Count</span></a> <a class="qtip-show-next-div btn">Upcoming Services <span class="badge @serviceOverdueBadge">@Model.UpcomingServiceReminders.Count</span></a>
<div class="hidden"> <div class="hidden">
@Html.Partial("UpcomingServiceReminders", Model.UpcomingServiceReminders) @if (@Model.UpcomingServiceReminders.Count > 0)
{
@Html.Partial("UpcomingServiceReminders", Model.UpcomingServiceReminders)
}
else {
<h5>No upcoming services currently scheduled</h5>
}
</div> </div>
</div> </div>
+2
View File
@@ -19,6 +19,8 @@
<add key="ResetPasswordBody" value="Hello {1}, please open this link to set a new password for your Mileage Traker account: {0}" /> <add key="ResetPasswordBody" value="Hello {1}, please open this link to set a new password for your Mileage Traker account: {0}" />
<add key="InitializePasswordSubject" value="Initialize Mileage Traker Account" /> <add key="InitializePasswordSubject" value="Initialize Mileage Traker Account" />
<add key="InitializetPasswordBody" value="Hello {1}, welcome to Mileage Traker. Your username is {2}. Please open this link to initialize your password: {0}" /> <add key="InitializetPasswordBody" value="Hello {1}, welcome to Mileage Traker. Your username is {2}. Please open this link to initialize your password: {0}" />
<add key="ServiceReminderSubject" value="Vehicle Serivce Reminder for {0}" />
<add key="ServiceReminderBody" value="Hello {0}, please note the following vehicles due for service: {1}" />
</appSettings> </appSettings>
<system.net> <system.net>
<mailSettings> <mailSettings>
+30 -4
View File
@@ -15,7 +15,7 @@
<AssemblyName>MileageTraker</AssemblyName> <AssemblyName>MileageTraker</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion> <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<MvcBuildViews>false</MvcBuildViews> <MvcBuildViews>false</MvcBuildViews>
<UseIISExpress>false</UseIISExpress> <UseIISExpress>true</UseIISExpress>
<FileUpgradeFlags> <FileUpgradeFlags>
</FileUpgradeFlags> </FileUpgradeFlags>
<UpgradeBackupLocation> <UpgradeBackupLocation>
@@ -71,11 +71,27 @@
<Reference Include="ExcelLibrary, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL"> <Reference Include="ExcelLibrary, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\ExcelLibrary.1.2011.7.30\lib\ExcelLibrary.dll</HintPath> <HintPath>..\packages\ExcelLibrary.1.2011.7.30\lib\ExcelLibrary.dll</HintPath>
</Reference> </Reference>
<Reference Include="Hangfire.Core, Version=1.5.2.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Hangfire.Core.1.5.2\lib\net45\Hangfire.Core.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Hangfire.SqlServer, Version=1.5.2.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Hangfire.SqlServer.1.5.2\lib\net45\Hangfire.SqlServer.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="log4net, Version=1.2.13.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a, processorArchitecture=MSIL"> <Reference Include="log4net, Version=1.2.13.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion> <SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\log4net.2.0.3\lib\net40-full\log4net.dll</HintPath> <HintPath>..\packages\log4net.2.0.3\lib\net40-full\log4net.dll</HintPath>
</Reference> </Reference>
<Reference Include="Microsoft.CSharp" /> <Reference Include="Microsoft.CSharp" />
<Reference Include="Microsoft.Owin, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Owin.3.0.0\lib\net45\Microsoft.Owin.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Owin.Host.SystemWeb, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Owin.Host.SystemWeb.3.0.0\lib\net45\Microsoft.Owin.Host.SystemWeb.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Web.Infrastructure, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> <Reference Include="Microsoft.Web.Infrastructure, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<Private>True</Private> <Private>True</Private>
<HintPath>..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll</HintPath> <HintPath>..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll</HintPath>
@@ -87,6 +103,14 @@
<SpecificVersion>False</SpecificVersion> <SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\MvcCheckBoxList.1.4.4.5\lib\net40\MvcCheckBoxList.dll</HintPath> <HintPath>..\packages\MvcCheckBoxList.1.4.4.5\lib\net40\MvcCheckBoxList.dll</HintPath>
</Reference> </Reference>
<Reference Include="Newtonsoft.Json, Version=4.5.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.5.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Owin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f0ebd12fd5e55cc5, processorArchitecture=MSIL">
<HintPath>..\packages\Owin.1.0\lib\net40\Owin.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.configuration" /> <Reference Include="System.configuration" />
<Reference Include="System.Data.DataSetExtensions" /> <Reference Include="System.Data.DataSetExtensions" />
<Reference Include="System.Web.ApplicationServices" /> <Reference Include="System.Web.ApplicationServices" />
@@ -146,6 +170,9 @@
<Compile Include="Controllers\AccountController.cs" /> <Compile Include="Controllers\AccountController.cs" />
<Compile Include="Controllers\CityController.cs" /> <Compile Include="Controllers\CityController.cs" />
<Compile Include="Controllers\ControllerBase.cs" /> <Compile Include="Controllers\ControllerBase.cs" />
<Compile Include="Email\ServiceReminderEmail.cs" />
<Compile Include="Email\ServiceReminderEmailService.cs" />
<Compile Include="Startup.cs" />
<Compile Include="ViewModels\DriverMileageFlattenedViewModel.cs" /> <Compile Include="ViewModels\DriverMileageFlattenedViewModel.cs" />
<Compile Include="ViewModels\VehicleService\UpdateServiceRemindersViewModel.cs" /> <Compile Include="ViewModels\VehicleService\UpdateServiceRemindersViewModel.cs" />
<Compile Include="Controllers\FuelLogController.cs" /> <Compile Include="Controllers\FuelLogController.cs" />
@@ -636,12 +663,11 @@
<VisualStudio> <VisualStudio>
<FlavorProperties GUID="{349c5851-65df-11da-9384-00065b846f21}"> <FlavorProperties GUID="{349c5851-65df-11da-9384-00065b846f21}">
<WebProjectProperties> <WebProjectProperties>
<UseIIS>False</UseIIS> <UseIIS>True</UseIIS>
<AutoAssignPort>True</AutoAssignPort> <AutoAssignPort>True</AutoAssignPort>
<DevelopmentServerPort>2977</DevelopmentServerPort> <DevelopmentServerPort>2977</DevelopmentServerPort>
<DevelopmentServerVPath>/</DevelopmentServerVPath> <DevelopmentServerVPath>/</DevelopmentServerVPath>
<IISUrl> <IISUrl>http://localhost:2977/</IISUrl>
</IISUrl>
<NTLMAuthentication>False</NTLMAuthentication> <NTLMAuthentication>False</NTLMAuthentication>
<UseCustomServer>False</UseCustomServer> <UseCustomServer>False</UseCustomServer>
<CustomServerUrl> <CustomServerUrl>
+7
View File
@@ -5,6 +5,9 @@
<package id="EntityFramework" version="6.1.3" targetFramework="net45" /> <package id="EntityFramework" version="6.1.3" targetFramework="net45" />
<package id="ExcelLibrary" version="1.2011.7.30" /> <package id="ExcelLibrary" version="1.2011.7.30" />
<package id="FontAwesome" version="4.4.0" targetFramework="net45" /> <package id="FontAwesome" version="4.4.0" targetFramework="net45" />
<package id="Hangfire" version="1.5.2" targetFramework="net45" />
<package id="Hangfire.Core" version="1.5.2" targetFramework="net45" />
<package id="Hangfire.SqlServer" version="1.5.2" targetFramework="net45" />
<package id="JonSkeet.MiscUtil" version="0.1" targetFramework="net40" /> <package id="JonSkeet.MiscUtil" version="0.1" targetFramework="net40" />
<package id="jQuery" version="2.1.4" targetFramework="net45" /> <package id="jQuery" version="2.1.4" targetFramework="net45" />
<package id="jQuery.Migrate" version="1.2.1" targetFramework="net40" /> <package id="jQuery.Migrate" version="1.2.1" targetFramework="net40" />
@@ -16,7 +19,11 @@
<package id="Microsoft.AspNet.Razor" version="3.2.3" targetFramework="net45" /> <package id="Microsoft.AspNet.Razor" version="3.2.3" targetFramework="net45" />
<package id="Microsoft.AspNet.WebPages" version="3.2.3" targetFramework="net45" /> <package id="Microsoft.AspNet.WebPages" version="3.2.3" targetFramework="net45" />
<package id="Microsoft.jQuery.Unobtrusive.Validation" version="3.2.3" targetFramework="net45" /> <package id="Microsoft.jQuery.Unobtrusive.Validation" version="3.2.3" targetFramework="net45" />
<package id="Microsoft.Owin" version="3.0.0" targetFramework="net45" />
<package id="Microsoft.Owin.Host.SystemWeb" version="3.0.0" targetFramework="net45" />
<package id="Microsoft.Web.Infrastructure" version="1.0.0.0" targetFramework="net40" /> <package id="Microsoft.Web.Infrastructure" version="1.0.0.0" targetFramework="net40" />
<package id="MvcCheckBoxList" version="1.4.4.5" targetFramework="net40" requireReinstallation="True" /> <package id="MvcCheckBoxList" version="1.4.4.5" targetFramework="net40" requireReinstallation="True" />
<package id="Newtonsoft.Json" version="5.0.1" targetFramework="net45" />
<package id="Owin" version="1.0" targetFramework="net45" />
<package id="Twitter.Bootstrap" version="2.2.2" targetFramework="net40" /> <package id="Twitter.Bootstrap" version="2.2.2" targetFramework="net40" />
</packages> </packages>