Using Cache in Play Framework - caching

I'm trying to implement a quiz application. The application loads the questions with ajax one by one. When a user clicks the 'to next question' button his/her answer is saved in cache. But when I debug, cache list is always null...
This code creates the first cache array:
public static void viewQuiz(#Required String user, #Required String test) {
if(validation.hasErrors()) {
flash.error("Hoop kullanıcı lazım…");
index();
} else{
TestClass selectedTest = TestClass.find("title", test).first();
List<String> choiceList = new ArrayList<String>();
session.put("testID", selectedTest.id);
Cache.set("choices", choiceList, "30mn");
render();
}
}
And this code is trying to save the answers one by one:
public static void question(#Required Long id, String answer){
Long testId = Long.parseLong(session.get("testID"));
TestClass test = TestClass.findById(testId);
List<Question> questList = Question.find("test_id", test.id.intValue()).fetch();
Question quest = questList.get(id.intValue());
if(answer != null){
List<String> choiceList= Cache.get("choices",List.class);
choiceList.add(id.intValue(), answer);
Cache.set("choices", choiceList, "30mn");
}
int count = questList.size()-1;
render(quest, count, id);
}
And this code is the html view of the second:
#{extends 'main.html' /}
#{set title:'question.html' /}
<script type="text/javascript">
var questionId = ${id};
$('#nextButton').click(function(){
$('#questionDiv').html('<p><img id = "loaderGif" src="public/images/loading.gif"/></p>');
$('#questionDiv').load("/test/" + ++questionId);
});
$('#endButton').click(function(){
$('#questionDiv').html('<p><img id = "loaderGif" src="public/images/loading.gif"/></p>');
$('#questionDiv').load("/result");
});
</script>
<legend>Soru ${id+1}</legend>
<p>&{quest.question}</p>
#{list items:quest.choices, as:'choice'}
<p><input type="radio" name = "answer" id = "answer" size="30" value="${choice}"/>&{choice}</p>
#{/list}
#{if id < count}
<input id = "nextButton" name="nextButton" type="button" value="İleri"/>
#{/if}
#{else}
<input id = "endButton" name="endButton" type="button" value="Bitti"/>
#{/else}

Don't use the cache to 'store' objects. Either store it in the session or create a new model to store the answers. Generally, you cannot expect the cache to retain the objects you put into; it's a cache, not a store.
To quote from the Play! website: http://www.playframework.org/documentation/1.2.2/cache
It is important to understand that the cache contract is clear: when
you put data in a cache, you can’t expect that data to remain there
forever. In fact you shouldn’t. A cache is fast, but values expire,
and the cache generally exists only in memory (without persistent
backup).

Cache is not reliable and you may get it as null in dev mode. This is expected and you can try changing it to prod mode and see its behavior.

Related

To create a session explicitly for first the first time page loads in Grails

As soon as a page loads, it renders a lot of data and shows on the view which sort of slows down the performance. I want to restrict this and load the data only when a filter is applied .
I need a way in which a session variable can store the value on the 1st login and no data should be loaded in that 1st session i.e. when any user loads it for the very first time using his login. something like the below in the controller class:
if(session.dtstartDate && session.dtstartDate != '')
{
SimpleDateFormat nsdf = new SimpleDateFormat('yyyy-MM-dd')
Date startDateValue = nsdf.parse(session.dtstartDate.substring(0, session.dtstartDate.lastIndexOf("T")))
eq("startDate", startDateValue)//if any filter is applied
}
else{
if this is the 1st session the startdate should be null --> need a piece of code to be replaced here
}
I am unsure maybe I still have not got what you have tried to ask properly, I thought I should try to answer your question to how I understood your problem to be.
If we take this basic filter and add some stuff to it we may be able to get to what you wish to do using a better method ? I am unsure what startDate is actually representing but if we base it on if a user has hit a controller for the first time or not the answer would be something like this, you could replace the logic to startDate if it has other significance:
so adding some hashmap arrayset to your filter that gets called before the action is called when user clicks the controller/action:
in your conf/MyFiters.groovy
class MyFilters {
static final Set<HashMap<String[],String[]>> userControl =
([:] as Set).asSynchronized()
//where controller is controllerName and action is actionName
def filters = {
MyLogger() {
before = {
if (verifyClientMaster(session.id as String ,controllerName)==false) {
clientMaster.add(session.id as String:controllerName)
// now here you have a new user so set
// some session value for gsp or load something
//according
}else{
// user has hit it before do something else or set something else
}
}
}
}
}
Boolean verifyClientMaster(String sessionId,String controller) {
// iterate
boolean found = false
userControl.each { k,v -> if (k == sessionId && v == controller) {
found = true
}
}
}
something like this and you know if the user has hit the controller or not.. remember the session is per user. so a new user has a new session entity.
Hope it is of help and not off track..
E2A
Thinking about it you do go down this route then you would need to keep track of when session expires and to remove the user from clientMaster.. take a look at this project if you did go down this route.. personally I would even do it simpler than this... on a rethink...
class MyFilters {
def filters = {
MyLogger() {
before = {
if (!sessions."${controllerName}") {
sessions."${controllerName}"="${controllerName}"
// now here you have a new user so set
// some session value for gsp or load something
//according
}else{
// user has hit it before do something else or set something else
}
}
}
}
}
and even simple than any of this would be to use the intelligence built into a gsp if what you load can be based on it... (not tested any of it ha)
<g:if test="${!session."${controllerName}"}">
<g:set var="${controllerName}" value="${controllerName}" scope="session" />
<g:render template="firstTimeHitter"/>
</g:if>
<g:else>
<g:render template="secondTimeHitter"/>
</g:else>
or just your controller that checks and sets that and either renders something different or sets something gsp picks up on..
def myController {
def doSomething() {
boolean firstTime = false
if (!session."${controllerName}") {
// first time either render or set firsTime
firstTime = true
session."${controllerName}" = controllerName // or startDate
// render view: 'firstTime, model: [firstTime:firstTime, params:params]
} else{
// render view: 'firstTime, model: [firstTime:firstTime, params:params]
}
// if no render above:
render view: 'doSomething, model: [firstTime:firstTime, params:params]
// now in doSomething gsp you look for firstTime:
}
do someThing:
<g:if test="${firstTime.toString().equals('true')}">
<g:render template="firstTimeHitter"/>
</g:if>
<g:else>
<g:render template="secondTimeHitter"/>
</g:else>
The possibilities are endless, the differences being with a filter its a one fits all, i.e. it is checking every controller as it is hit by each user. In controller and gsp solution you have to declare it where needed. You could have an abstract controller that other controllers extend to repeat that check as a higher class that gets called to verify, regardless their all a lot more repetitive than a simple one off filter...
Final Edit to give other other alternatives would be:
final Set<Session> jsessions = ([] as Set).asSynchronized()
jsessions.add('controller1')
jsessions.add('controller2')
jsessions.add('controller3')
jsessions.add(controllerName)
println "=== ${jsessions}"
if (jsessions.contains(controllerName)) {
println "--- We have ${controllerName} defined in our session set.... jsessions"
}
ArrayList jsessions2 = []
jsessions2.add(controllerName)
session.jsessions2 = jsessions2
//repeat this on every call
ArrayList jsessionsret = session.jsessions2
jsessionsret.add('controller1')
jsessionsret.add('controller2')
jsessionsret.add('controller3')
session.jsessions2 = jsessionsret
if (jsessions2.contains(controllerName)) {
println "--- We have ${controllerName} defined in our session set.... jsessionsret"
}
println "222 --- ${jsessions2}"
This segment above are two different implementations of using first a session set that is global and could be used if you do not care if the controller is hit by usera userb etc so if usera hits it userb would also be considered as hitting it.. This is jsessions.
The bottom jsessions2 is an attempt to turn a single session key into an ArrayList. So rather than storing lots of single object i.e. session."${controllerName}" per call of a controller per user session. You could have 1 single session key per user that you append each controller they hit to.. and you then check to see if they have that controller

MVC3 Razor Passing ViewModel to Conroller List<> is null

I have a MVC 3 applicaiton in which I pass a vewmodel from the controller to the view. The vewmodel contains a couple of List<> properties.
public ActionResult MainView()
{
var model = GetViewModel();
return View("SignificantEventsView", model);
}
private SignificantEventsViewModel GetViewModel()
{
var viewModel = new SignificantEventsViewModel();
List<County_Codes> countyCodes = GetCountyCodeList();
List<String> stateNames = countyCodes.OrderBy(o=>o.County_st).Select(o => o.County_st ).Distinct().ToList();
viewModel.selectedState = stateNames.FirstOrDefault();
viewModel.CountyCodesList = countyCodes;
viewModel.StateNames = stateNames;
viewModel.SelectedCounties = new String[]{};
viewModel.SelectedCountyCodes = new String[] { };
viewModel.UnSelectedCounties = new String[] { };
viewModel.UnSelectedCountyCodes = new String[]{};
return viewModel;
}
The View looks like this:
#model ServicingPortal.ViewModels.SignificantEventsViewModel
#{
ViewBag.Title = "Significant Events";
}
<h2>SignificantEvents</h2>
#using (Html.BeginForm("RefreshCounties", "SignificantEvents", FormMethod.Post, new { id = "significantEventsForm", Model }))
{
<fieldset>
<span class="SpanTextboxEdit">
#Html.Label("states", "States")
<br />
<br />
#Html.DropDownListFor(o => #Model.selectedState
, new SelectList(Model.StateNames)
, new { id = "stateDropDown", onchange = "submit()", name = "test" })
</span>
</fieldset>
...
}
When the StateDropdownList is changed the veiwmodel is passed back to the controller, but the countyCodes list is always null.
I tried adding #Html.HiddenFor(o => #Model.CountyCodesList) in the view, but it still returns null. The only values that don't seem to be null are the primitive types such as String or String[]. Even the List stateNames is null.
I don't want to rebuild the county code list on each post back because there is substantial overhead involved. I have to create the list from all active loans in the database, of which there are thousands.
How can I get a List<> to persist from the view to the controller?
I should explain what I'm trying to acheive here.
I have a dropdown and a multiselect list box. The dropdown contains states and the listbox contains counties filtered by the selected state.
I need to filter the listbox contents when the selected state changes.
It would make sense to perform this task on the client side, but I have not found a good solution.
I will admit my javascript skills are quite limited.
All the solutions I researched one way or another involved filtering the county list on the server side.
I can accomplish this on the server side easy enough, but I thought that since I have already built the list, why not keep it intact instead of going to the backend each time.
The short answer is that you can't really do what you're trying to do. You're kind of trying to solve the wrong problem. You should look at using caching on the server side to prevent going back to the database to construct the county list every time.
I solved this by using TempData. On the postback action I can get the County List from temp data and set the ViewModel CountyCodeList to this value.

How to Cache Multiple Versions of a Page using Razor Dynamic Query & WebCache

I decided to implement caching to improve the performance of the product pages.
Each page contains a large amount of the product's images.
I created the following code in a Razor view.
#{
var productID = UrlData[0].AsInt();
var cacheItemKey = "products";
var cacheHit = true;
var data = WebCache.Get(cacheItemKey);
var db = Database.Open("adldb");
if (data == null) {
cacheHit = false;
}
if (cacheHit == false) {
data = db.Query("SELECT * FROM Products WHERE ProductID = #0", productID).ToList();
WebCache.Set(cacheItemKey, data, 1, false);
}
}
I'm using the data with the following code:
#foreach (dynamic p in data)
{
<a href="~/Products/View/#p.ProductID"
<img src="~/Thumbnail/#p.ProductID"></a>
}
The caching code works well, but when passing the new query string parameter (changing the version of the page) the result in browser is the same for the declared cashing time.
How to make caching every version of the page?
Thanks
Oleg
A very simple approach might be to convert your key (productID) to a string and append it to the name of your cacheItemKey.
So you might consider changing the line:
var cacheItemKey = "products";
to read:
var cacheItemKey = "products" + productID.ToString();
This should produce the behavior you are looking for -- basically mimicking a VaryByParam setup.
ps. Please keep in mind I have not added any sort of defensive code, which you should do.
Hope that helps.

ViewModel Delete Concurrency Excecption Not Working

I am working on catching concurrency exceptions. I am able to catch the concurrency exceptions when a user edits the data fine. I am having trouble catching the exception when a user deletes data.
On my Index page, I have a button to delete each Vehicle object. Pressing that button does a Post to the Delete action. Here is the Delete action:
[HttpPost, ActionName("Delete")]
public ActionResult DeleteConfirmed(VehicleIndexViewModel vehicleIndexViewModel)
{
try
{
Vehicle vehicle = db.Vehicles.Find(vehicleIndexViewModel.VehicleID);
//To test for concurrency errors
//vehicle.Timestamp = vehicleIndexViewModel.Timestamp;
db.Entry(vehicle).State = EntityState.Deleted;
db.SaveChanges();
return RedirectToAction("Index");
}
catch (DbUpdateConcurrencyException)
{
return RedirectToAction("Index",
new System.Web.Routing.RouteValueDictionary{{"concurrencyError", true }});
}
catch (DataException)
{
//Log the error (add a variable name after Exception)
ModelState.AddModelError(string.Empty, "The system was unable to delete that"
+ " vehicle. Try again, and if the problem persists"
+ " contact your system administrator.");
return RedirectToAction("Index");
}
}
No matter what, the user should be redirected to the Index page. Here is the Index page's Get action:
public ViewResult Index(bool? concurrencyError)
{
if (concurrencyError.GetValueOrDefault())
{
ViewBag.ConcurrencyErrorMessage = "The record you attempted to delete "
+ "was modified by another user after you got the original values. "
+ "The delete operation was canceled and the current values in the "
+ "database have been displayed. If you still want to delete this "
+ "record, click the Delete button again. Otherwise "
+ "click the Back to List hyperlink.";
}
IEnumerable<Vehicle> vehicles = db.Vehicles.Include(v => v.VehicleType);
IEnumerable<VehicleIndexViewModel> viewModel
= Mapper.Map<IEnumerable<Vehicle>,
IEnumerable<VehicleIndexViewModel>>(vehicles);
return View(viewModel);
}
The code never catches the concurrency error. I test by opening the index page twice. On one of the pages, I open the edit page of a vehicle and change something. Once that's saved, I go back to the other page and click "Delete." The Delete action fires, and the vehicle is deleted, but the concurrency error is not caught. You can see where I commented out vehicle.Timestamp = vehicleIndexViewModel.Timestamp;. I thought putting the value of the viewModel back into the actual would raise the error, but it doesn't work that way either.
I'm sure there's just something I don't understand, but what am I doing wrong?
EDIT
Erik Philips found the logic error that I had, but there was another issue that I ran into right away. My ViewModel was not returning the Timestamp data. In fact, the only data it was returning was the VehicleID.
When I tried to add a hidden field to the form, I would get an error. The code just below this would not work:
<input type="hidden" name="Timestamp" value="#item"/>
The Timestamp field needs to be a valid Base-64 string. The error you will get is:
The input is not a valid Base-64 string as it contains a non-base 64 character, more than >two padding characters, or a non-white space character among the padding characters.
This is how I ended up storing the Timestamp value on the view:
<input type="hidden" name="Timestamp" value="#Convert.ToBase64String(item.Timestamp)"/>
So, my whole Html.BeginForm looks like this:
#using (Html.BeginForm("Delete", "Vehicle", FormMethod.Post, null))
{
<input type="hidden" name="VehicleID" value="#item.VehicleID"/>
<input type="hidden" name="VehicleName" value="#item.VehicleName"/>
<input type="hidden" name="Timestamp" value="#Convert.ToBase64String(item.Timestamp)"/>
<input type="image" src="../../Content/Images/Delete.gif" value="Delete" name="deletevehicle #item.VehicleID" onclick="return confirm('Are you sure you want to delete #item.VehicleName.Replace("'", "").Replace("\"", "")?');"/>
}
Although, I really didn't need to put VehicleName in a hidden field.
Once that was done, all I needed to do was use AutoMapper to map the values back into a vehicle object, set the EntityState to Deleted, and try to SaveChanges;
try
{
Vehicle vehicle = Mapper.Map<VehicleIndexViewModel, Vehicle>(vehicleIndexViewModel);
db.Entry(vehicle).State = EntityState.Deleted;
db.SaveChanges();
return RedirectToAction("Index");
}
catch (DbUpdateConcurrencyException)
{
return RedirectToAction("Index", new System.Web.Routing.RouteValueDictionary { { "concurrencyError", true } });
}
That's it!
Here is what I see would happen if these events happened in this order:
User 1
Vehicle/Edit VehicleID=1 (Timestamp =1)
User 2
Vehicle/Edit VehicleID=1 (Timestamp =1)
User 1
Vehicle/Update VehicleID=1, Title="Some Text", (Timestamp=2)
User2
Vehicle/DeleteConfirmed VehicleID=1, Timestamp =1
/* DeleteConfirmed */
// Default Model Binder
VehicleIndexViewModel.VehicleID = 1
VehicleIndexViewModel.Timestamp = 1
Vehicle vehicle = db.Vehicles.Find(vehicleIndexViewModel.VehicleID);
//vehicle.VehicleID = 1
//vehicle.Timestamp = 2
// Set to Delete
db.Entry(vehicle).State = EntityState.Deleted;
// Delete in database
db.SaveChanges();
Basically all you're doing is retrieve the Vehicle in it's current state (updated from user 1) and deleting it. Unless, by some freak chance, someone did an update between Find and SaveChanges there will never be a DbUpdateConcurrencyException.
What I believe you can do instead would be to Attach the object to the context, delete it, then call SaveChanges().

How do I delete records from a child collection in LINQ to SQL?

I have two tables in my database connected by foreign keys: Page (PageId, other data) and PageTag (PageId, Tag). I've used LINQ to generate classes for these tables, with the page as the parent and the Tag as the child collection (one to many relationship). Is there any way to mark PageTag records for deletion from the database from within the Page class?
Quick Clearification:
I want the child objects to be deleted when the parent DataContext calls SubmitChanges(), not before. I want TagString to behave exactly like any of the other properties of the Page object.
I would like to enable code like the following:
Page page = mDataContext.Pages.Where(page => page.pageId = 1);
page.TagString = "new set of tags";
//Changes have not been written to the database at this point.
mDataContext.SubmitChanges();
//All changes should now be saved to the database.
Here is my situation in detail:
In order to make working with the collection of tags easier, I've added a property to the Page object that treats the Tag collection as a string:
public string TagString {
get {
StringBuilder output = new StringBuilder();
foreach (PageTag tag in PageTags) {
output.Append(tag.Tag + " ");
}
if (output.Length > 0) {
output.Remove(output.Length - 1, 1);
}
return output.ToString();
}
set {
string[] tags = value.Split(' ');
PageTags.Clear();
foreach (string tag in tags) {
PageTag pageTag = new PageTag();
pageTag.Tag = tag;
PageTags.Add(pageTag);
}
}
}
Basically, the idea is that when a string of tags is sent to this property, the current tags of the object are deleted and a new set is generated in their place.
The problem I'm encountering is that this line:
PageTags.Clear();
Doesn't actually delete the old tags from the database when changes are submitted.
Looking around, the "proper" way to delete things seems to be to call the DeleteOnSubmit method of the data context class. But I don't appear to have access to the DataContext class from within the Page class.
Does anyone know of a way to mark the child elements for deletion from the database from within the Page class?
After some more research, I believe I've managed to find a solution. Marking an object for deletion when it's removed from a collection is controlled by the DeleteOnNull parameter of the Association attribute.
This parameter is set to true when the relationship between two tables is marked with OnDelete Cascade.
Unfortunately, there is no way to set this attribute from within the designer, and no way to set it from within the partial class in the *DataContext.cs file. The only way to set it without enabling cascading deletes is to manually edit the *DataContext.designer.cs file.
In my case, this meant finding the Page association, and adding the DeleteOnNull property:
[Association(Name="Page_PageTag", Storage="_Page", ThisKey="PageId", OtherKey="iPageId", IsForeignKey=true)]
public Page Page
{
...
}
And adding the DeleteOnNull attribute:
[Association(Name="Page_PageTag", Storage="_Page", ThisKey="PageId", OtherKey="iPageId", IsForeignKey=true, DeleteOnNull = true)]
public Page Page
{
...
}
Note that the attribute needed to be added to the Page property of the PageTag class, not the other way around.
See also:
Beth Massi -- LINQ to SQL and One-To-Many Relationships
Dave Brace -- LINQ to SQL: DeleteOnNull
Sorry, my bad. That won't work.
It really looks like you need to be doing this in your repository, rather than in your Page class. There, you have access to your original data context.
There is a way to "attach" the original data context, but by the time you do that, it has become quite the code smell.
Do you have a relationship, in your Linq to SQL entity diagram, linking the Page and PageTags tables? If you don't, that is why you can't see the PageTags class from the Page class.
If the foreign key in the PageTags database table is set to Allow Nulls, Linq to SQL will not create the link when you drag the tables into the designer, even if you created a relationship on the SQL Server.
This is one of those areas where OR mapping can get kind of hairy. Providing this TagString property makes things a bit more convenient, but in the long run it obfuscates what is really happening when someone utilizes the TagString property. By hiding the fact that your performing data modification, someone can very easily come along and set the TagString without using your Page entity within the scope of a DataContext, which could lead to some difficult to find bugs.
A better solution would be to add a Tags property on the Page class with the L2S model designer, and require that the PageTags be edited directly on the Tags property, within the scope of a DataContext. Make the TagString property read only, so it can be genreated (and still provide some convenience), but eliminate the confusion and difficulty around setting that property. This kind of change clarifies intent, and makes it obvious what is happening and what is required by consumers of the Page object to make it happen.
Since Tags is a property of your Page object, as long as it is attached to a DataContext, any changes to that collection will properly trigger deletions or insertions in the database in response to Remove or Add calls.
Aaron,
Apparently you have to loop thru your PageTag records, calling DeleteOnSubmit for each one. Linq to SQL should create an aggregate query to delete all of the records at once when you call SubmitChanges, so overhead should be minimal.
replace
PageTags.Clear();
with
foreach (PageTag tag in PageTags)
myDataContext.DeleteOnSubmit(tag);
Aaron:
Add a DataContext member to your PageTag partial class.
partial class PageTag
{
DataClassesDataContext myDataContext = new DataClassesDataContext();
public string TagString {
..etc.
Larger code sample posted at Robert Harvey's request:
DataContext.cs file:
namespace MyProject.Library.Model
{
using Tome.Library.Parsing;
using System.Text;
partial class Page
{
//Part of Robert Harvey's proposed solution.
MyDataContext mDataContext = new TomeDataContext();
public string TagString {
get {
StringBuilder output = new StringBuilder();
foreach (PageTag tag in PageTags) {
output.Append(tag.Tag + " ");
}
if (output.Length > 0) {
output.Remove(output.Length - 1, 1);
}
return output.ToString();
}
set {
string[] tags = value.Split(' ');
//Original code, fails to mark for deletion.
//PageTags.Clear();
//Robert Harvey's suggestion, thorws exception "Cannot remove an entity that has not been attached."
foreach (PageTag tag in PageTags) {
mDataContext.PageTags.DeleteOnSubmit(tag);
}
foreach (string tag in tags) {
PageTag PageTag = new PageTag();
PageTag.Tag = tag;
PageTags.Add(PageTag);
}
}
}
private bool mIsNew;
public bool IsNew {
get {
return mIsNew;
}
}
partial void OnCreated() {
mIsNew = true;
}
partial void OnLoaded() {
mIsNew = false;
}
}
}
Repository Methods:
public void Save() {
mDataContext.SubmitChanges();
}
public Page GetPage(string pageName) {
Page page =
(from p in mDataContext.Pages
where p.FileName == pageName
select p).SingleOrDefault();
return page;
}
Usage:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(string pageName, FormCollection formValues) {
Page updatedPage = mRepository.GetPage(pageName);
//TagString is a Form value, and is set via UpdateModel.
UpdateModel(updatedPage, formValues.ToValueProvider());
updatedPage.FileName = pageName;
//At this point NO changes should have been written to the database.
mRepository.Save();
//All changes should NOW be saved to the database.
return RedirectToAction("Index", "Pages", new { PageName = pageName });
}

Resources