Introduction
An ASP.NET MVC website that I've developed needed preparation for demonstration purposes, of the views available sometime in the past. Furthermore, in the future, it'd be good to have an easy means for historical date user testing. I've created a side project (called VRSandbox
), which is a proof of concept for achieving this. It relies on a Session
variable, assigned by the end user in the client browser, for the desired test or demonstration date. This allows for easy change of the test date, as well as simultaneous specification of different dates by different users. Introducing this functionality basically comes down to adding a customer filter to controllers and/or controller actions, and making a minor modification to the View Models that need to be tested.
Background
This project was coded to support another, much bigger project of mine, called Voter's Revenge (henceforth, VR). See votersrevenge.org, and votersrevenge.info. Briefly, Voter's Revenge is tasked with enabling citizens to grow populist political power, by facilitating the formation of punitive voting blocs. Disruptive, punitive voting blocs (which have the political muscle to "fire" incumbents, even if too small/weak to "hire" their replacement of choice) will be particularly important in countries where reform forces of all sorts are weak due to apathy and demoralization, lack of organization, etc. Such is the case in the United States, where reformists of all political stripes who are opposed to the plutocratic status quo, expressed through the Duopoly of Democrats + Republicans, find themselves continually marginalized, disempowered, and/or co-opted.
I paused active development of VR back in the summer of 2016. Sometime after that point, all US campaigns whose data was in the program's database entered their final phases. To be useful in the future, fresh data needs to be loaded, and programming changes need to be added to seamlessly handle new campaigns. (Plus, decent interfaces for CRUD operations need to be introduced.) All in all, a fair amount of work.
I am tentatively planning on open sourcing VR. In order to both make the program display an interesting view, with its existing data and (virtually unchanged) coding, I needed a way to trick the program into showing views with respect to a historical date, taken well before most primary contests were over. This will also help future testers of the program.
VRSandbox (the name of this Visual Studio solution) was thus an ASP.NET MVC side project to VR, proper. I believe the code I developed for VRSandbox to be useful for other developers who may want to enable historical date testing, with only a smallish disruption to their existing code base.
Using the Code
To use the code, data models have to be modified similarly to how I extended my CampaignModel
class to AssignableDateCampaignModel
class. The key steps are:
- Installing NodaTime (via nuget) and adding namespace references, where needed.
- Adding a
private
member to hold a NodaTime LocalDate
variable (in my case, "assignedLocalDate
"). - Modifying the getter of your pre-existing
DateTime
field to return this LocalDate
variable (converted to DateTime
), when it has been assigned. - Creating a public means of setting the
private
LocalDate
variable. In my case, I added both a constructor that included a LocalDate
value, as well as an AssignLocalDate
method.
Here are my View Models:
public class CampaignModel
{
public string CandidateName { get; set; }
public DateTime QueryDateTime
{
get
{
return DateTime.UtcNow.Date.ToUniversalTime();
}
}
}
public class AssignableDateCampaignModel : CampaignModel
{
private LocalDate assignedLocalDate = new LocalDate();
public AssignableDateCampaignModel(string candidateName, LocalDate assignedLocalDate)
{
this.assignedLocalDate = assignedLocalDate;
this.CandidateName = candidateName;
}
public DateTime QueryDateTime
{
get
{
if (assignedLocalDate == new LocalDate())
return DateTime.UtcNow.Date.ToUniversalTime();
else
{
return assignedLocalDate.ToDateTime();
}
}
}
public void AssignLocalDate(LocalDate assignedLocalDate)
{
this.assignedLocalDate = assignedLocalDate;
}
}
Additionally, coders must:
- Add relevant files such as /Utils/*.*.
- Add the
SessionQueryDate
custom filter class, which I have added to /App_Start/FilterConfig.cs. - Add the custom filter attribute [
SessionQueryDate
] to controllers or actions for which you want to use the assigned date. - Provide some means for end users to set the "
sessionLocalDate
" Session variable to a date of their choice. In VRSandbox, I have hacked the code from the sample project. How to create and access session variables in ASP.NET MVC
Code for SessionQueryDate
action filter:
public class SessionQueryDate : ActionFilterAttribute, IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
LocalDate validLocalDate;
var sessionLocalDate = context.HttpContext.Session.Contents["sessionLocalDate"];
if (sessionLocalDate == null)
{
context.Controller.ViewBag.TestQueryLocalDate = null;
}
else
{
bool tryParsing = ((string)sessionLocalDate).IsValidLocalDate(out validLocalDate);
if (tryParsing)
{
context.Controller.ViewBag.TestQueryLocalDate = validLocalDate;
}
else
{
context.Controller.ViewBag.TestQueryLocalDate = null;
}
}
}
}
Here is the code for two actions which use [SessionQueryDate
]. The first of these is GetResults()
, which uses AssignableDateCampaignModel
. Since this model has the method AssignLocalDate()
, calling this with the LocalDate
value set in SessionQueryDate
results in the test date selected by the user being used in queries involving AssignableDateCampaignModel.QueryDateTime
.
[SessionQueryDate]
public ViewResult GetEvents()
{
ViewBag.Title = "GetEvents() - view model has an assignable date";
ViewBag.Assignable = true;
AssignableDateCampaignModel campaignModel =
new AssignableDateCampaignModel("Hillary Clinton", new LocalDate());
LocalDatePattern pattern0 = LocalDatePattern.CreateWithInvariantCulture("MM-dd-yyyy");
if (ViewBag.TestQueryLocalDate != null)
{
LocalDate localDate = ViewBag.TestQueryLocalDate;
campaignModel.AssignLocalDate(localDate);
ViewData["sessionLocalDate"] = pattern0.Format(localDate);
ViewData["sessionLocalDateStatus"] = "sessonLocalDate session variable set";
}
else
{
ViewData["sessionLocalDate"] = null;
ViewData["sessionLocalDateStatus"] = "sessonLocalDate session variable not set";
}
List<MyEvent> events = MyEvents.Where
(x => x.EventDate >= campaignModel.QueryDateTime).ToList();
return View(events);
}
This second sample action also calls campaignModel.AssignLocalDate()
. Even though the campaignModel
is of type CampaignModel
, and has no assignable date function, there is an extension method of that name defined in class NodaTimeUtils
, so the code still compiles. In this case, AssignLocalDate
essentially does nothing.
[SessionQueryDate]
public ViewResult GetEventsNoAssignableDate()
{
ViewBag.Title = "NodaSessionStateController.GetEventsNoAssignableDate()
- view model does not have an assignable date";
CampaignModel campaignModel =
new CampaignModel { CandidateName = "Donald J. Trump" };
LocalDatePattern pattern0 =
LocalDatePattern.CreateWithInvariantCulture("MM-dd-yyyy");
if (ViewBag.TestQueryLocalDate != null)
{
LocalDate localDate = ViewBag.TestQueryLocalDate;
campaignModel.AssignLocalDate(localDate);
ViewData["sessionLocalDate"] = pattern0.Format(localDate);
ViewData["sessionLocalDateStatus"] = "sessonLocalDate session variable set";
}
else
{
ViewData["sessionLocalDate"] =
System.Web.HttpContext.Current.Session["sessionLocalDate"] as String;
ViewData["sessionLocalDateStatus"] =
"sessonLocalDate session variable not set";
}
List<MyEvent> events = MyEvents.Where
(x => x.EventDate >= campaignModel.QueryDateTime).ToList();
return View("GetEvents", events);
}
Screen Shots
Home Page with All Data
Assign a Date, Before Submitting
After Submitting an Invalid Date
After Submitting a Valid Date
Query Results Using Assigned Date
Query Results Ignoring Assigned Date (executed 3/9/2017)
Notes
- NodaTime is used, but not really necessary for my purposes. NodaTime shines when dealing with time zones, but in my application, a granularity of
Date
is sufficient. If you don't anticipate needing to deal with time zones, you should be able to easily refactor the code to just use the .NET DateTime
library. - I have hard-coded the date format as "
MM-dd-yyyy
". - It's just fine to decorate an entire controller with [
SessionQueryDate
], as actions that use models that do not have assignable dates will not break.
My Development Environment
I developed in Visual Studio 2013 Ultimate, using .NET framework 4.5.2 (thus, C# 6). The 'database' is just a static collection.
History
- 15th March, 2017: Initial version