Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Allow Users to Selectively Override your Website's Default Date

0.00/5 (No votes)
15 Mar 2017 1  
Low impact method for using a Session variable + custom Action filter to allow end users to select a test date of their choosing, thus overriding the default date used during web requests.

Image 1

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:

  1. Installing NodaTime (via nuget) and adding namespace references, where needed.
  2. Adding a private member to hold a NodaTime LocalDate variable (in my case, "assignedLocalDate").
  3. Modifying the getter of your pre-existing DateTime field to return this LocalDate variable (converted to DateTime), when it has been assigned.
  4. 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 midnight UTC
            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 we didn't assign a query date (stored as assignedLocalDate)
            if (assignedLocalDate == new LocalDate())
                // return midnight UTCNow
                return DateTime.UtcNow.Date.ToUniversalTime();
            else
            {
                // return midnight of LocalDate
                return assignedLocalDate.ToDateTime();
            }
        }
    }

    public void AssignLocalDate(LocalDate assignedLocalDate)
    {
        this.assignedLocalDate = assignedLocalDate;
    }
}

Additionally, coders must:

  1. Add relevant files such as /Utils/*.*.
  2. Add the SessionQueryDate custom filter class, which I have added to /App_Start/FilterConfig.cs.
  3. Add the custom filter attribute [SessionQueryDate] to controllers or actions for which you want to use the assigned date.
  4. 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
{ 
    // set ViewBag.TestQueryLocalDate if parse successful
    // otherwise, set it to null
    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;
    // 2 example models
    // ============================
    // CampaignModel does not have an assignable date
    // CampaignModel campaignModel = new CampaignModel { CandidateName = "Donald J. Trump" };
 
    // AssignableDateCampaignModel, used here in GetEvents() does have an assignable date
    AssignableDateCampaignModel campaignModel = 
              new AssignableDateCampaignModel("Hillary Clinton", new LocalDate());
 
    LocalDatePattern pattern0 = LocalDatePattern.CreateWithInvariantCulture("MM-dd-yyyy");
 
    // ViewBag.TestQueryLocalDate is set in the SessionQueryDate Action Filter
    if (ViewBag.TestQueryLocalDate != null)
    {
        // assign the LocalDate to the model
        LocalDate localDate = ViewBag.TestQueryLocalDate;
        // add to model
        campaignModel.AssignLocalDate(localDate);
        // return the same value in the session variable
        ViewData["sessionLocalDate"] = pattern0.Format(localDate);
        // update status
        ViewData["sessionLocalDateStatus"] = "sessonLocalDate session variable set"; 
    }
    else
    {
        // return the same (bad format or empty) value
        ViewData["sessionLocalDate"] = null;  // as String;
        // update status
        ViewData["sessionLocalDateStatus"] = "sessonLocalDate session variable not set";
    }

    // construct the view model using .QueryDateTime
    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";

            // 2 example models
            // ============================
            // CampaignModel does not have an assignable date
            CampaignModel campaignModel = 
                          new CampaignModel { CandidateName = "Donald J. Trump" };

            //// AssignableDateCampaignModel does have an assignable date
            //AssignableDateCampaignModel campaignModel = 
            //new AssignableDateCampaignModel("Hillary Clinton", new LocalDate());

            LocalDatePattern pattern0 = 
                     LocalDatePattern.CreateWithInvariantCulture("MM-dd-yyyy");

            // ViewBag.TestQueryLocalDate is set in the SessionQueryDate Action Filter
            if (ViewBag.TestQueryLocalDate != null)
            {
                // assign the LocalDate to the model
                LocalDate localDate = ViewBag.TestQueryLocalDate;
                // add to model, where appropriate   
                // since the current model has no assignable date, 
                // AssignLocalDate() will not do anything to the model
                campaignModel.AssignLocalDate(localDate);
                // return the same value in the session variable
                ViewData["sessionLocalDate"] = pattern0.Format(localDate);
                // update status
                ViewData["sessionLocalDateStatus"] = "sessonLocalDate session variable set";
            }
            else
            {
                // return the same (bad format or empty) value
                ViewData["sessionLocalDate"] = 
                    System.Web.HttpContext.Current.Session["sessionLocalDate"] as String;
                // update status
                ViewData["sessionLocalDateStatus"] = 
                                     "sessonLocalDate session variable not set";
            }

            // construct the view model using .QueryDateTime
            List<MyEvent>  events = MyEvents.Where
                           (x => x.EventDate >= campaignModel.QueryDateTime).ToList();

            return View("GetEvents", events);
        }

Screen Shots

Home Page with All Data

Image 2

Assign a Date, Before Submitting

Image 3

After Submitting an Invalid Date

Image 4

After Submitting a Valid Date

Image 5

Query Results Using Assigned Date

Image 6

Query Results Ignoring Assigned Date (executed 3/9/2017)

Image 7

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

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here