Saturday, 9 July 2011
It's Friday I'm In Love
I am building a new service, which is going to be Events.
While doing so, I came across some interesting question: how to get the client culture settings?
Because I want to know which day is the first day of the week in my user's world.
Turns out all you have to do is to setup your page so that it gets these settings automatically,
and attaches them to the current thread.
So I set up my calendar so that my first day of week is Tuesday (which is kind of true).
But still, my thread current culture (Thread.CurrentThread.CurrentCulture.DateTimeFormat.FirstDayOfWeek) says it is Monday.
Turns out you don't get the complete culture info from the client, which actually makes sense. Instead, you get the preferred language chosen in the user's browser settings, which is used to determine the culture settings.
Change it to en-US and you'll get Sunday.
Yes, you can see the skeleton of calendar on the background.
While doing so, I came across some interesting question: how to get the client culture settings?
Because I want to know which day is the first day of the week in my user's world.
Turns out all you have to do is to setup your page so that it gets these settings automatically,
<%@ Page Title="Events" Language="C#" MasterPageFile="~/Site.master" AutoEventWireup="true"
CodeBehind="Events.aspx.cs" Inherits="romama.Calendar.Events" ClientIDMode="Predictable"
UICulture="auto" Culture="auto" %>
So I set up my calendar so that my first day of week is Tuesday (which is kind of true).
But still, my thread current culture (Thread.CurrentThread.CurrentCulture.DateTimeFormat.FirstDayOfWeek) says it is Monday.
Turns out you don't get the complete culture info from the client, which actually makes sense. Instead, you get the preferred language chosen in the user's browser settings, which is used to determine the culture settings.
Change it to en-US and you'll get Sunday.
Yes, you can see the skeleton of calendar on the background.
Tuesday, 5 July 2011
Search!
So now with the search. Search is performed by tags only, because text is stored encrypted.
Note titles are not encrypted, but they are not considered so far.
Sunday, 3 July 2011
Client encryption: final version
function getKey()
{
var key = localStorage.PBK;
if (!key)
{
var password = prompt("Enter data encryption key. Please do not use your password!");
if (!password) { return key; }
var p = {};
p.iter = 1000;
p.salt = [0xD1F6D8FF, 0x482648A7];
key = sjcl.misc.cachedPbkdf2(password, p).key.slice(0, 4);
localStorage.PBK = key;
}
return key;
}
function encryptElements()
{
var key = getKey();
if (!key || !key.length) return;
$(".cryptable").each(
function (idx)
{
if ($(this).val() && !$(this).val().match(/\{iv\:".*",salt\:".*",ct\:".*"\}/))
{
try
{
$(this).val(sjcl.encrypt(key, $(this).val()));
}
catch (e)
{
error("Cannot encrypt: " + e);
return false;
}
}
}
);
return true;
}
function decryptElements()
{
var key = getKey();
if (!key || !key.length) return;
$(".cryptable").each(
function (idx)
{
if ($(this).val() && $(this).val().match(/\{iv\:".*",salt\:".*",ct\:".*"\}/))
{
try
{
$(this).val(sjcl.decrypt(key, $(this).val()));
}
catch (e)
{
// could not decrypt, data will be shown as is.
}
}
}
);
}
It is not totally safe because anyone who has an access to the local machine can obtain the key from the local storage, but it satisfies my needs: as a provider of a service, I will not be able to see the user data, even myself.
Saturday, 2 July 2011
There is your problem
So, after 2 days of struggling with Chrome, I finally figured out the reason why my client encryption didn't work there.
This is the simple page which shows the issue:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<title>Test validate</title>
<script type="text/javascript">
//<![CDATA[
function validate()
{
var t = document.getElementsByTagName("textarea");
for (var i = 0; i < t.length; i++)
{
alert("Text: " + t[i].innerHTML);
}
return false;
}
//]]>
</script>
</head>
<body>
<form method="post" id="a" action="test.htm" onsubmit="return validate();">
<textarea type="text" name="myTextArea" rows="2" cols="20" id="myTextArea">Some text</textarea>
<input type="submit" name="myButton" value="Submit" id="myButton" />
</form>
</body>
</html>
We have a form with textarea and we want to do something with the textarea modified text before the form is submitted to the server. This can be validation, or encryption, as in my case, or anything else.
The script from the example works perfectly fine in IE8. InnerHTML returns the actual up-to-date text, reflecting all the changes user made before pressing "Submit" button. So does InnerText, textContent and $(this).text().
However, what I get in Chrome is always the text as it was when the form was initially loaded. Changes are simply not there. Neither they can be obtained through InnerText, textContent or $(this).text().
Surprisingly, if I use technically non-existing "value" property, I do get the latest changes, as in IE, so in Chrome.
Using "value" property seem to solve the problem, at least for IE8 and Chrome, except I cannot use jQuery selector anymore, because object returned by $(this) does not have this property.
Client encryption, new version:
function encryptElements()
{
var cryptable = getElementsByClassName("cryptable");
var len = cryptable.length;
for (var i = 0; i < len; i++)
{
cryptable[i].value = sjcl.encrypt("password", cryptable[i].value);
}
return true;
}
function decryptElements()
{
$(".cryptable").each(
function (idx)
{
if ($(this).text())
{
$(this).text(sjcl.decrypt("password", $(this).text()));
}
}
);
}
decryptElements();
Now I need to decide where to store the password.
UPD: Thanks to my smarter colleague, I figured out I can use $(this).val() and achieve the result I need.
So now it look like that:
This is the simple page which shows the issue:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<title>Test validate</title>
<script type="text/javascript">
//<![CDATA[
function validate()
{
var t = document.getElementsByTagName("textarea");
for (var i = 0; i < t.length; i++)
{
alert("Text: " + t[i].innerHTML);
}
return false;
}
//]]>
</script>
</head>
<body>
<form method="post" id="a" action="test.htm" onsubmit="return validate();">
<textarea type="text" name="myTextArea" rows="2" cols="20" id="myTextArea">Some text</textarea>
<input type="submit" name="myButton" value="Submit" id="myButton" />
</form>
</body>
</html>
We have a form with textarea and we want to do something with the textarea modified text before the form is submitted to the server. This can be validation, or encryption, as in my case, or anything else.
The script from the example works perfectly fine in IE8. InnerHTML returns the actual up-to-date text, reflecting all the changes user made before pressing "Submit" button. So does InnerText, textContent and $(this).text().
However, what I get in Chrome is always the text as it was when the form was initially loaded. Changes are simply not there. Neither they can be obtained through InnerText, textContent or $(this).text().
Surprisingly, if I use technically non-existing "value" property, I do get the latest changes, as in IE, so in Chrome.
Using "value" property seem to solve the problem, at least for IE8 and Chrome, except I cannot use jQuery selector anymore, because object returned by $(this) does not have this property.
Client encryption, new version:
function encryptElements()
{
var cryptable = getElementsByClassName("cryptable");
var len = cryptable.length;
for (var i = 0; i < len; i++)
{
cryptable[i].value = sjcl.encrypt("password", cryptable[i].value);
}
return true;
}
function decryptElements()
{
$(".cryptable").each(
function (idx)
{
if ($(this).text())
{
$(this).text(sjcl.decrypt("password", $(this).text()));
}
}
);
}
decryptElements();
Now I need to decide where to store the password.
UPD: Thanks to my smarter colleague, I figured out I can use $(this).val() and achieve the result I need.
So now it look like that:
function encryptElements()
{
var key = getKey();
if (!key || !key.length) return;
$(".cryptable").each(
function (idx)
{
if ($(this).val() && !$(this).val().match(/\{iv\:".*",salt\:".*",ct\:".*"\}/))
{
try
{
$(this).val(sjcl.encrypt(key, $(this).val()));
}
catch (e)
{
error("Cannot encrypt: " + e);
return false;
}
}
}
);
return true;
}
Friday, 1 July 2011
Adding encryption
I know I don't want to trust myself to keep anyone else's secrets, so I want the data to be encrypted in a browser.
I found out quite pretty javascript crypto library: Stanford Javascript Crypto Library
And as a proof of concept, I could get this working in IE.
function encryptElement(eTarget)
{
$(eTarget).text(sjcl.encrypt("password", $(eTarget).text()));
$(eTarget).addClass("enc");
}
function decryptElements()
{
$(".enc").each(
function (idx)
{
if ($(this).text())
{
$(this).text(sjcl.decrypt("password", $(this).text()));
$(this).removeClass("enc");
}
}
);
}
decryptElements();
However, everything breaks in Chrome, because, despite the encryptElement function is called, the data is submitted as it was before the encryption. Also, ScriptManager.RegisterStartupScript, doesn't seem to work as expected in Chrome, so I never get my decryptElements function called after the UpdatePanel is updated.
I am pretty tired of all these browser quirks. The client scripting is so fragile it really freaks me out.
I found out quite pretty javascript crypto library: Stanford Javascript Crypto Library
And as a proof of concept, I could get this working in IE.
function encryptElement(eTarget)
{
$(eTarget).text(sjcl.encrypt("password", $(eTarget).text()));
$(eTarget).addClass("enc");
}
function decryptElements()
{
$(".enc").each(
function (idx)
{
if ($(this).text())
{
$(this).text(sjcl.decrypt("password", $(this).text()));
$(this).removeClass("enc");
}
}
);
}
decryptElements();
However, everything breaks in Chrome, because, despite the encryptElement function is called, the data is submitted as it was before the encryption. Also, ScriptManager.RegisterStartupScript, doesn't seem to work as expected in Chrome, so I never get my decryptElements function called after the UpdatePanel is updated.
I am pretty tired of all these browser quirks. The client scripting is so fragile it really freaks me out.
Wednesday, 29 June 2011
Combining resources
It is recommended that you download all your scripts/styles in a single request to the server, instead of getting files one by one, thus blocking the browser.
So, instead of:
I really want to have something like this:
<script type="text/javascript" src="../Scripts/combined.js"></script>
Even if there are ready-to-use solution available, like Yahoo! combo handler, this never stopped me before from implementing my own custom solution.
So, instead of:
<script type="text/javascript" src="../Scripts/romama.js"></script>
<script type="text/javascript" src="../Scripts/notes.js"></script>
<script type="text/javascript" src="../Scripts/combined.js"></script>
Even if there are ready-to-use solution available, like Yahoo! combo handler, this never stopped me before from implementing my own custom solution.
What I want for myself is to be able to put one single script tag in a master page and have all my scripts downloaded as a single file.
I am going to achieve this with the custom HttpHandler, which will know which resources are needed for any particular page. Here is implementation:
The thing I need to look into is whether the scripts going to be cached in a browser.
<script type="text/javascript" src="CombineResources.axd?type=script"></script>
public class CombineResources : IHttpHandler
{
#region Static Members
private static Dictionary<string, List<string>> ScriptMap = new Dictionary<string, List<string>>()
{
{"/Notes/CategoryEditor.aspx", new List<string>() {"/Scripts/romama.js", "/Scripts/categoryEditor.js"}}
};
#endregion
#region IHttpHandler Members
public bool IsReusable
{
get { return true; }
}
public void ProcessRequest(HttpContext context)
{
string resourceType = context.Request.QueryString["type"] as String;
if (String.IsNullOrWhiteSpace(resourceType))
{
return;
}
string localPath = context.Request.UrlReferrer.LocalPath;
string combined = context.Cache[localPath + ":" + resourceType] as String;
if (combined != null)
{
context.Response.Write(combined);
return;
}
List<string> resources = new List<string>();
if (resourceType == "script")
{
if (ScriptMap.ContainsKey(localPath))
{
resources = ScriptMap[localPath];
}
}
else if (resourceType == "style")
{
// TODO:
}
else
{
throw new InvalidOperationException(String.Format("unrecognized resource type: {0}", resourceType));
}
StringBuilder sb = new StringBuilder();
foreach(var resource in resources)
{
sb.AppendLine(File.ReadAllText(context.Server.MapPath("\\") + resource));
}
sb.AppendLine(@"// Generated: " + DateTime.Now.ToLocalTime());
context.Response.Write(sb.ToString());
context.Cache.Insert(localPath + ":" + resourceType, sb.ToString(), null, DateTime.UtcNow.AddMinutes(1), Cache.NoSlidingExpiration);
}
#endregion
}
Thursday, 16 June 2011
State, still
How we want to see it working:
a. User requests page
b. Page is sent to user
c. User clicks buttons, page handles events
As I understand, this is how (more or less) databound server controls are working.
a. User requests page
b. Page is sent to user
c. User clicks buttons, page handles events
How it is working in reality:
a. User requests page
b. Page is sent to user and discarded
c. User clicks buttons, new page handles events
How to make the reality look like a dream:
1. Get the data from the database and render dynamic controls in Page_PreRender
2. Save the data which is used to render controls between requests in a ViewState
3. When user submits the page back to server (Page.IsPostback == true), recreate controls as they were, from the ViewState
4. Happily handle all the events
5. Discard all restored controls in Page_PreRender and start all over again.
As I understand, this is how (more or less) databound server controls are working.
Wednesday, 15 June 2011
Stateless means stateless
Or how to screw up everything without true understanding of the technology.
Let's say we want show some dynamic data on the web page, like notes. Notes will be represented by the NoteControl which will be a bunch of textboxes, labels and buttons:
and will be rendered to something like that:
Let's say we get our data from the database every time user reloads the page:
And every NoteControl will have the event handler for the save button:
This event handler will be *magically* called when you click save button in the browser. Am I wrong?
Without even knowing that we are getting the new notes now.
And surprisingly we get controls rendered with the same ID's:
What kind of makes sense, since these values will be submitted as parameters and extracted from the Request object automatically every time the page will be in postback.
Let's say we want show some dynamic data on the web page, like notes. Notes will be represented by the NoteControl which will be a bunch of textboxes, labels and buttons:
<asp:TextBox ID="TitleTextBox" runat="server"></asp:TextBox>
<asp:TextBox ID="NoteTextTextBox" runat="server" TextMode="MultiLine"></asp:TextBox>
<asp:ImageButton
ID="AcceptNoteButton"
runat="server"
ImageUrl="~/Resources/Icons/64x64/circle-check.png"
AlternateText="Save"
CausesValidation="true" />
and will be rendered to something like that:
<input name="ctl00$MainContent$NotesView$ctl03$TitleTextBox" type="text" id="MainContent_NotesView_ctl03_TitleTextBox" />
<textarea name="ctl00$MainContent$NotesView$ctl03$NoteTextTextBox" id="MainContent_NotesView_ctl03_NoteTextTextBox" />
<input type="image" name="ctl00$MainContent$NotesView$ctl03$AcceptNoteButton" id="MainContent_NotesView_ctl03_AcceptNoteButton" data-objectid="57101f10-5aed-4285-99e7-2709c3ac41e9" src="../Resources/Icons/64x64/circle-check.png" alt="Save" />
protected void Page_Load(object sender, EventArgs e)
{
List<Note> notes = GetNotesFromDb();
RenderNotes(notes);
}
private void RenderNotes(List<Note> notes)
{
Panel notePanel = new Panel();
Notes.ContentTemplateContainer.Controls.Add(notePanel);
for (int i = 0; i < notes.Count; i++)
{
var note = notes[i];
// Here is our NoteControl
NoteControl noteCtrl = (NoteControl)Page.LoadControl("~/Controls/NoteControl.ascx");
noteCtrl.Note = note;
notePanel.Controls.Add(noteCtrl);
}
}
protected void acceptButton_Click(object sender, ImageClickEventArgs e)
{
var button = sender as ImageButton;
Guid id = Guid.Parse(button.Attributes[ObjectIdAttribute]);
UpdateNoteDb(id);
}
And because we attached this event handler in the markup:
<asp:ImageButton
ID="AcceptNoteButton"
runat="server"
ImageUrl="~/Resources/Icons/64x64/circle-check.png"
AlternateText="Save"
CausesValidation="true"
OnClick="acceptButton_Click" />
OK, let's say now we edited some note and now press the save button:
In a meanwhile, while we were editing this note, we opened the same page from the different machine and simply removed this note, and created a new one instead.
So when we hit the save button we get into Page_Load and... get all the notes from the database:
List<Note> notes = GetNotesFromDb();
And then we are rendering our page:
NoteControl noteCtrl = (NoteControl)Page.LoadControl("~/Controls/NoteControl.ascx");
...
notePanel.Controls.Add(noteCtrl);
<input name="ctl00$MainContent$NotesView$ctl03$TitleTextBox" type="text" value="Another one" id="MainContent_NotesView_ctl03_TitleTextBox"/>
<textarea name="ctl00$MainContent$NotesView$ctl03$NoteTextTextBox" id="MainContent_NotesView_ctl03_NoteTextTextBox">Indeed</textarea>
<input type="image" name="ctl00$MainContent$NotesView$ctl03$AcceptNoteButton" id="MainContent_NotesView_ctl03_AcceptNoteButton" data-objectid="78ae8c7f-7236-4a16-950d-b226f710485d" src="../Resources/Icons/64x64/circle-check.png" alt="Save" />
But before they will be rendered, all the events must fire... And *magically* we still handle the acceptButton_Click event. On the newly created control, which hasn't even been on the client yet.
Non-magically, data-objectid will be object id of the new note.
TitleTextBox.Text and NoteTextTextBox.Text will still get old values, because we set them only on initial page load:
if (!Page.IsPostBack)
{
TitleTextBox.Text = ...;
NoteTextTextBox.Text = ...;
}
Short story: we update the wrong object.
What can I say... You have to learn or you'll be the monkey which can code anything, if only the requirements are clear.
Subscribe to:
Comments (Atom)












