tag:blogger.com,1999:blog-42714672361819950022024-02-19T14:19:47.426-08:00Use Small IconsRandom writings of a cynical Software DeveloperAlanhttp://www.blogger.com/profile/11461540001324768384noreply@blogger.comBlogger43125tag:blogger.com,1999:blog-4271467236181995002.post-16080697325033579452020-07-13T13:34:00.000-07:002020-07-13T13:34:33.190-07:00MovedBlogger's editor sucks.<br />
<br />
As a result, I've copied all old posts to Github pages where I can author everything in markdown.<br />
<br />
<a href="https://alanparr.github.io/">https://alanparr.github.io/</a><br />
<br />
All new posts will be at the above url.Alanhttp://www.blogger.com/profile/11461540001324768384noreply@blogger.com0tag:blogger.com,1999:blog-4271467236181995002.post-55815655389891993472020-07-10T14:09:00.002-07:002020-07-11T15:22:29.351-07:00Supporting SameSite None in .Net 4.6 or lower.As I write this post, it is 4 days until Chromium begins enforcing the new SameSite rules again.<br />
When they first did this in March, it caused a number of issues including breaking website integrations with some payment gateways.<br />
If you're on .Net 4.7 or higher, Microsoft supports setting SameSite to None. The official recommendation is that if you want to use SameSite None, then you need to move up to .Net 4.7.2, which if you are able, you should absolutely do.<br />
However, there are those of us who are stuck on .Net lower than 4.7 and there is nothing we can do about it and our employers want to know that their sites aren't going to start breaking come the 14th of July.<br />
While trying to find a solution to this problem, I stumbled upon what appears to be a possible solution for those of us stuck on lower .Net versions.<br />
<pre class="brush: csharp">var cookie = new HttpCookie("myreallyimportantcookie")
{
Value = "myreallyimportantcookievalue",
Secure = true,
Path = "/",
HttpOnly = true
};
</pre>
As you'll see from the below image, we have a cookie with the secure attribute and httponly, but no samesite attribute.
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjCncAWkcft0NbeTsAGWuQNZXvle4W_LIBipvit3hZ5Xw6tvNh1NFNo5ALPCSaSMryF7hyphenhypheneffvbEAuekpxnUasdh_W3uIXNzy3_Iu6_FI6f-Qge6o_j2YOwoMrRIVZ1NVc6c7JbJICKYS4/s1600/cookie-nosamesite.png" imageanchor="1"><img border="0" data-original-height="65" data-original-width="1600" height="26" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjCncAWkcft0NbeTsAGWuQNZXvle4W_LIBipvit3hZ5Xw6tvNh1NFNo5ALPCSaSMryF7hyphenhypheneffvbEAuekpxnUasdh_W3uIXNzy3_Iu6_FI6f-Qge6o_j2YOwoMrRIVZ1NVc6c7JbJICKYS4/s640/cookie-nosamesite.png" width="640" /></a>
<br />
<h2>
Adding SameSite</h2>
In .Net 4.7.2, if we want to support SameSite, we simply add the SameSite attribute.
<br />
<pre class="brush:csharp">var cookie = new HttpCookie("myreallyimportantcookie")
{
Value = "myreallyimportantcookievalue",
Secure = true,
Path = "/",
HttpOnly = true,
SameSite = SameSiteMode.None
};
</pre>
Of course, we can't do this in .Net 4.5 as the SameSite property doesn't exist. Instead, we can do this somewhat gross thing:
<pre class="brush:csharp">var cookie = new HttpCookie("myreallyimportantcookie")
{
Value = "myreallyimportantcookievalue" + ";SameSite=None",
Secure = true,
Path = "/",
HttpOnly = true
};
</pre>
And now if we run the application, we can see we have the SameSite attribute set to None.
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj36O5SrAYuC5ZrY9s6HCYIwl6YQZDR8gl5Kc360HbnEVShgUd0oDFgFRGaUXLbVvSRj6wAFSy2WM2JVBu5GzW6g9_gO6bOKnIjfFlfMmwwTUJnlNpF4q14s0it9lTe5tkJbJpmGiapcFg/s1600/cookie-samesite.png" imageanchor="1"><img border="0" data-original-height="55" data-original-width="1600" height="22" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj36O5SrAYuC5ZrY9s6HCYIwl6YQZDR8gl5Kc360HbnEVShgUd0oDFgFRGaUXLbVvSRj6wAFSy2WM2JVBu5GzW6g9_gO6bOKnIjfFlfMmwwTUJnlNpF4q14s0it9lTe5tkJbJpmGiapcFg/s640/cookie-samesite.png" width="640" /></a>
<h2>
Disclaimer #1</h2>
This solution is completely unsupported and a bit gross. The approved solution is to move to .Net 4.7.2 and if you are able, you should absolutely do that. But sometimes the real world places limits upon us and if you are in that situation, hopefully this will get you out of a bind.
<h2>
Disclaimer #2</h2>
I have only tested this in an MVC solution in .Net 4.6.1. Theoretically, I think it will probably work in versions lower than that unless the cookie value-handling semantics changed massively at some point in the past. If you want to use this, test for yourself and post a comment if it worked as maybe that will help someone else.Alanhttp://www.blogger.com/profile/11461540001324768384noreply@blogger.com0tag:blogger.com,1999:blog-4271467236181995002.post-24026331567853290772020-06-13T13:29:00.002-07:002020-07-12T10:53:18.243-07:00API Head-to-head Update : AWS S3 Vs Windows Azure Table Storage Vs Rackspace Cloud Files<style type="text/css">
table.data {
border: 1px solid black;
text-align: center;
}
table.data tbody tr td, table.data thead tr th {
border:1px solid black;
}
table.data thead tr th {
background-color:lightgray;
}
</style>
This is an update to my last <a href="https://usesmallicons.blogspot.com/2014/08/api-head-to-head-aws-s3-vs-windows.html">API head to head from August 2014</a>, I'm nothing if not consistent with my inconsistent posting.
I've recently changed jobs to a new company that is moving to Azure but has some legacy Rackspace assets, so I thought it'd be fun to redo the test with Rackspace added.
Worth noting that the Rackspace support for C# is completely non-existent. The official Rackspace SDK hasn't been updated since 2013 and this test is using the openstack.net SDK which hasn't been updated since 2018.
<br />
<h2>
The code</h2>
<h3>
Initialise Provider</h3>
<pre class="brush: csharp">CloudIdentity cloudIdentity = new CloudIdentity()
{
APIKey = "mykey",
Username = "myusername"
};
var provider = new CloudFilesProvider(cloudIdentity);
</pre>
<h3>
Create container</h3>
<pre class="brush: csharp">provider.CreateContainer(containerName);
</pre>
<h3>
Upload file</h3>
<pre class="brush: csharp">provider.CreateObjectFromFile(containerName, testFile, blobName);
</pre>
<h3>
List Blobs (objects in Rackspace parlance)</h3>
<pre class="brush: csharp">provider.ListObjects(containerName).ToList();
</pre>
<h3>
Delete file</h3>
<pre class="brush: csharp">provider.DeleteObject(containerName, blobName);
</pre>
<h3>
Delete container</h3>
<pre class="brush: csharp">provider.DeleteContainer(containerName);
</pre>
Can't argue with the simplicity of the code, once you've initialised the provider, everything is one line.
<br />
<h2>
The results</h2>
Worth noting that I don't have the original test file, so the file being uploaded here is one I happened to have lying around of <a href="https://www.bing.com/images/search?q=odo">this guy</a>. It is only 1k larger, so don't expect it will have a massive effect on the results.
<br />
<table class="data">
<thead>
<tr>
<th style="width: 150px;">Operation</th>
<th style="width: 75px;">S3</th>
<th style="width: 75px;">Azure</th>
<th style="width: 75px;">Rackspace</th>
</tr>
</thead>
<tbody>
<tr><td>Create Container</td><td>847</td><td style="background-color: green;">668</td><td>1915</td></tr>
<tr><td>Upload 7Kb file</td><td style="background-color: green;">83</td><td>92</td><td>230</td></tr>
<tr><td>List Blobs (1)</td><td style="background-color: green;">40</td><td>172</td><td>51</td></tr>
<tr><td>Delete Blob</td><td>47</td><td style="background-color: green;">35</td><td>112</td></tr>
<tr><td>Delete Container</td><td>240</td><td style="background-color: green;">45</td><td>68</td></tr>
</tbody>
</table>
I ran the tests a few times and the numbers fluctuated but the positions pretty much stayed the same, except the Upload file winner switched between S3 and Azure.
<br />
<h2>
Conclusion</h2>
Rackspace was slowest, but that isn't terribly surprising considering I was using a third-party 2-year old library, it isn't necessarily a realistic comparison, just for my own amusement.Alanhttp://www.blogger.com/profile/11461540001324768384noreply@blogger.com0tag:blogger.com,1999:blog-4271467236181995002.post-24361664582861795472017-01-08T13:26:00.000-08:002017-01-20T03:38:22.026-08:00Converting docx to PDF in AzureAs part of a recent feature, we needed to implement conversion of docx to pdf. A quick look on Nuget revealed <a href="http://www.nuget.org/packages/FreeSpire.Doc/" target="_blank">Free Spire.Doc for .Net</a>. We thought this looked like a great use case for Azure Functions, so the member of the team who was implementing the feature quickly implemented this as an Azure Function, but when we deployed it, it didn't work. After a bit of googling, the cause of this was that, due to the sandboxed environment of Azure App Service, assemblies requiring access to GDI don’t work. Further Googling with Bing revealed that pretty much every .Net docx to PDF conversion library uses GDI, so we couldn’t just switch to a different library. <br />
We needed full access to Windows to do this, which meant a VM. The cheapest Windows VM in azure is a Basic A0 at less than £9 a month, much cheaper than commercial document conversion services I found, which were at least £20 a month and had really weird APIs that were going to be pretty tricky to integrate in to our application. <br />
I implemented a Windows service using Topshelf and the original Free Spire.Doc code for the actual conversion and installed this on to the VM. It simply polls an Azure Storage Queue for a message and deserializes the body to the following class. <br />
<pre class="brush:csharp">private class ConversionMessage
{
public string SourceBlobContainer { get; set; }
public string SourceBlobName { get; set; }
public string DestinationBlobContainer { get; set; }
public string DestinationBlobName { get; set; }
public string ConversionType { get; set; }
}
</pre>
This simply contains the Azure Blob container and the name for the source document, and the destination container and name for the converted document. There is also a ConversionType property which only has one valid value currently, I added this to facilitate adding other conversions in the future.
When a message is received, the service then converts the document with freespire and puts the converted document in the destination container.
Below is all the code for doing the conversion and saving it.
<pre class="brush:csharp">private void Convert(ConversionMessage message)
{
Console.WriteLine(message);
var inputBlob = GetBlobReference(message.SourceBlobContainer, message.SourceBlobName);
var outputBlob = GetBlobReference(message.DestinationBlobContainer, message.DestinationBlobName);
if(message.ConversionType == "docxtopdf")
{
LogInfo("Beginning conversion, type: docxtopdf");
ConvertDocxToPdf(inputBlob, outputBlob);
}
else
{
LogError($"Invalid conversion type {message.ConversionType} received");
}
}
private CloudBlockBlob GetBlobReference(string container, string blobName) => _blobClient.GetContainerReference(container).GetBlockBlobReference(blobName);
private void ConvertDocxToPdf(CloudBlockBlob inputDoc, CloudBlockBlob outputDoc)
{
var inputStream = new MemoryStream();
inputDoc.DownloadToStream(inputStream);
inputStream.Seek(0, SeekOrigin.Begin);
var doc = new Spire.Doc.Document();
doc.LoadFromStream(inputStream, FileFormat.Docx);
var outStream = new MemoryStream();
doc.SaveToStream(outStream, FileFormat.PDF);
outStream.Seek(0, SeekOrigin.Begin);
outputDoc.UploadFromStream(outStream);
LogInfo("Conversion successful");
}
</pre>
Holding all of this together is an Azure Function. This function is really simple, it just gets called whenever the docx file is created in Azure Blob Storage and creates the conversion message, and puts it in the queue for the Windows service on the VM to pick up.
<pre class="brush:csharp">public static void Run(CloudBlockBlob myBlob, CloudQueue queue, TraceWriter log)
{
log.Info($"ConvertWordQuoteToPdf function processed: {myBlob.Name}");
var filename = System.IO.Path.GetFileNameWithoutExtension(myBlob.Name);
var cm = new ConversionMessage();
cm.SourceBlobContainer = "docs";
cm.SourceBlobName = $"{filename}.docx";
cm.DestinationBlobContainer = "docs";
cm.DestinationBlobName = $"{filename}.pdf";
cm.ConversionType = "docxtopdf";
var msg = new CloudQueueMessage(Newtonsoft.Json.JsonConvert.SerializeObject(cm));
queue.AddMessage(msg);
}
</pre>
One of the coolest things about this in my view, is that all of this required no changes to the main application at all, we just reacted to the creation of the docx file that it was already doing.
<br />
<br />
Alanhttp://www.blogger.com/profile/11461540001324768384noreply@blogger.com0tag:blogger.com,1999:blog-4271467236181995002.post-26130162096362302062017-01-01T12:17:00.000-08:002017-01-20T03:36:39.619-08:00Automating repetitive tasks with Azure Functions.<div lang="en-US" style="font-family: Calibri; font-size: 11.0pt; margin: 0in;">
Since the
announcement of Azure Functions at Build 2016, I've been looking for an excuse
to use them and I finally found it.</div>
<div lang="en-US" style="font-family: Calibri; font-size: 11.0pt; margin: 0in;">
Whenever
we release a new version of our software, the actual process for building and
committing to source control is really simple and takes just a few minutes.
Then I get to spend at least 30 minutes
on our internal job management system telling it about the new release and
deprecating the old one. I then copy and paste the message from the release
commit, enter it in to said system, then reformat it a bit and send it out to
various internal users as release notes.</div>
<div lang="en-US" style="font-family: Calibri; font-size: 11.0pt; margin: 0in;">
It's just
wrong when doing admin after a release takes more time than the actual release,
plus I hate doing boring repetitive takes that I know a computer could do for
me.</div>
<div lang="en-US" style="font-family: Calibri; font-size: 11.0pt; margin: 0in;">
<br /></div>
<div lang="en-US" style="font-family: Calibri; font-size: 11.0pt; margin: 0in;">
Enter
Azure Functions, below is a quick diagram of how I wanted everything to work.</div>
<div lang="en-US" style="font-family: Calibri; font-size: 11.0pt; margin: 0in;">
<br /></div>
<div lang="en-US" style="font-family: Calibri; font-size: 11.0pt; margin: 0in;">
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgPHR5BrOvqfg0gKVQNUQLD1o_fDEpFmOlruwGW2pgHB9u4oqsh3ridzXWgfQnHeK6jx8qf24Akv2wPFPZ8OkddbE_rJwO6_2uGGvZ7ZdRhFiHR_sE0hDTEAOReVpTQyiqqmBM9e-M_DEo/s1600/blog-funcsdiagram.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="127" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgPHR5BrOvqfg0gKVQNUQLD1o_fDEpFmOlruwGW2pgHB9u4oqsh3ridzXWgfQnHeK6jx8qf24Akv2wPFPZ8OkddbE_rJwO6_2uGGvZ7ZdRhFiHR_sE0hDTEAOReVpTQyiqqmBM9e-M_DEo/s320/blog-funcsdiagram.PNG" width="320" /></a></div>
<br /></div>
<div lang="en-US" style="font-family: Calibri; font-size: 11.0pt; margin: 0in;">
<br /></div>
<div lang="en-US" style="font-family: Calibri; font-size: 11.0pt; margin: 0in;">
Source
control calls a function when we commit. This function determines if it is a
release by checking for a specific string in the message, it then puts a
message on a queue to another function which will then enter the release
details straight in to the database of our internal system (which we host in
azure). This system doesn't have an API so we'll do it the old fashioned way
with raw SQL.</div>
<div lang="en-US" style="font-family: Calibri; font-size: 11.0pt; margin: 0in;">
If this
is successful, it then puts messages on to 2 more queues, one is picked up by
another function which posts the message in to Slack, the other goes off to a
pre-existing web job which will email the release notes out.</div>
<div lang="en-US" style="font-family: Calibri; font-size: 11.0pt; margin: 0in;">
<br /></div>
<div lang="en-US" style="font-family: Calibri; font-size: 11.0pt; margin: 0in;">
<h3>
SETUP</h3>
</div>
<div lang="en-US" style="font-family: Calibri; font-size: 11.0pt; margin: 0in;">
I created
a function app in the azure portal and hooked up a repository in my Bitbucket
account to the function app.</div>
<div lang="en-US" style="font-family: Calibri; font-size: 11.0pt; margin: 0in;">
I'm going
to need to access a SQL database, so I put the connection string in my app
settings same as for any other App Service app.</div>
<div lang="en-US" style="font-family: Calibri; font-size: 11.0pt; margin: 0in;">
<br /></div>
<div lang="en-US" style="font-family: Calibri; font-size: 11.0pt; margin: 0in;">
Let's see
what the code looks like.</div>
<div lang="en-US" style="font-family: Calibri; font-size: 11.0pt; margin: 0in;">
<br /></div>
<div lang="en-US" style="font-family: Calibri; font-size: 11.0pt; margin: 0in;">
<br /></div>
<div lang="en-US" style="font-family: Calibri; font-size: 11.0pt; margin: 0in;">
This
first function simply takes a small json payload over a http post, this
structure of this is as below.</div>
<div lang="en-US" style="font-family: Calibri; font-size: 11.0pt; margin: 0in;">
<br /></div>
<div lang="en-US" style="font-family: Calibri; font-size: 11.0pt; margin: 0in;">
<pre class="brush: js">{
"Revision":"c1b49afddh7c",
"Author" : "Author Name",
"Created_At" : "2016-09-27T11:54:58+00:00",
"Log" : "Updated version to cpm 1.9.40
Added fluffy bunny controller",
"Branch" : "develop",
"Project":"cpm",
}
</pre>
</div>
<pre class="brush: csharp">#r "Newtonsoft.Json"
#r "Microsoft.WindowsAzure.Storage"
using System;
using System.Net;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Microsoft.WindowsAzure.Storage.Table;
public static async Task<object> Run(HttpRequestMessage req, ICollector<CommitMessageTableEntity> commitLogTable, ICollector<<ommitMessage> releaseQueue, TraceWriter log)
{
string jsonContent = await req.Content.ReadAsStringAsync();
log.Info(jsonContent);
var data = JsonConvert.DeserializeObject<CommitMessage>(jsonContent);
log.Info(data.Log);
var te = new CommitMessageTableEntity();
te.Set(data);
try
{
commitLogTable.Add(te);
}
catch (System.Exception ex)
{
log.Info("An error occurred: " + ex.Message);
return req.CreateResponse(HttpStatusCode.OK, "An error occurred, see log for details.");
}
if(te.IsRelease) {
log.Info("This is a release, adding to queue so it gets added to Radius.");
releaseQueue.Add(data);
}
return req.CreateResponse(HttpStatusCode.OK, "Success");
}
</pre>
<div lang="en-US" style="font-family: Calibri; font-size: 11.0pt; margin: 0in;">
<br />
We log this to an Azure Storage table, I've no use for this currently but it
costs practically nothing and is an easy way to check if the function was
called if I have any problems in the future.</div>
<div lang="en-US" style="font-family: Calibri; font-size: 11.0pt; margin: 0in;">
Then if
the commit was a release commit, as defined by checking for the commit message
to begin in a specific way, we put a message on a storage queue for the next
function to be triggered. Note that I'm just adding to ICollector in both
cases, the are no explicit references to Tables or Queues which is one of the
things I really like about functions.</div>
<div lang="en-US" style="font-family: Calibri; font-size: 11.0pt; margin: 0in;">
<br />
<br /></div>
<div lang="en-US" style="font-family: Calibri; font-size: 11.0pt; margin: 0in;">
Here is
the function.json, this defines all my inputs and outputs, note how the output
queue name shows up as an Icollector<string> parameter on my run method,
this is the power of the WebJobs SDK at work.</div>
<div lang="en-US" style="font-family: Calibri; font-size: 11.0pt; margin: 0in;">
<br /></div>
<div lang="en-US" style="font-family: Calibri; font-size: 11.0pt; margin: 0in;">
<pre class="brush: js">{
"bindings": [
{
"webHookType": "genericJson",
"type": "httpTrigger",
"direction": "in",
"name": "req"
},
{
"type": "http",
"direction": "out",
"name": "res"
},
{
"type": "table",
"name": "commitLogTable",
"tableName": "commitlog",
"connection": "SourceIntegrationSA",
"direction": "out"
},
{
"name": "releaseQueue",
"queueName": "releasequeue",
"connection": "SourceIntegrationSA",
"type": "queue",
"direction": "out"
}
],
"disabled": false
}
</pre>
</div>
<div lang="en-US" style="font-family: Calibri; font-size: 11.0pt; margin: 0in;">
<br /></div>
<div lang="en-US" style="font-family: Calibri; font-size: 11.0pt; margin: 0in;">
The next
function is the one that does all the work. There is a large
amount of SQL in this function. The key for me was to get this up and running
quickly and using SQL does that. Once it has proved it's worth, I'll tidy it up
a bit with all the time it saves me!</div>
<div lang="en-US" style="font-family: Calibri; font-size: 11.0pt; margin: 0in;">
I haven't included the code here as it is very specific to me, all it does is use
Dapper to insert a record for the new release and deprecate the previous one.</div>
<div lang="en-US" style="font-family: Calibri; font-size: 11.0pt; margin: 0in;">
To import
Dapper from Nuget, I added a project.json, see below. </div>
<div lang="en-US" style="font-family: Calibri; font-size: 11.0pt; margin: 0in;">
<br /></div>
<div lang="en-US" style="font-family: Calibri; font-size: 11.0pt; margin: 0in;">
<pre class="brush:js">{
"frameworks": {
"net46":{
"dependencies": {
"Dapper": "1.50.2"
}
}
}
}
</pre>
</div>
<div lang="en-US" style="font-family: Calibri; font-size: 11.0pt; margin: 0in;">
<br /></div>
<div lang="en-US" style="font-family: Calibri; font-size: 11.0pt; margin: 0in;">
With this
in place, whenever I redeploy my function, the Dapper Nuget will be downloaded
for me to use.</div>
<div lang="en-US" style="font-family: Calibri; font-size: 11.0pt; margin: 0in;">
<br /></div>
<div lang="en-US" style="font-family: Calibri; font-size: 11.0pt; margin: 0in;">
Right at
the end, once everything had been done, we pop a simple message on a queue to
be picked up by another function that will send it to slack, and put the
release notes from the commit message on another queue to be emailed out to
internal staff members who will need to know.</div>
<div lang="en-US" style="font-family: Calibri; font-size: 11.0pt; margin: 0in;">
<br /></div>
<br />
<div lang="en-US" style="font-family: Calibri; font-size: 11.0pt; margin: 0in;">
The Slack
function is really simple. It uses a Slack client class I found on the internet
somewhere and just passes the message along.</div>
Alanhttp://www.blogger.com/profile/11461540001324768384noreply@blogger.com0tag:blogger.com,1999:blog-4271467236181995002.post-38414286748162426282016-01-03T07:11:00.001-08:002016-01-03T07:11:12.921-08:00Windows 10 Mobile: An update<p>Even though my previous post was only published 6 days ago, it was written a week before that so I’ve now got 3 weeks of usage under my belt and felt the need to post an update.</p> <p>The biggest issue I’ve encountered over the last few weeks, and which I forgot to incude in the previous post, was battery life. This was suffering quite a bit in diaily use to the point where regular use of email, Readit, Facebook and Twitter was rendering the battery dead before dinner time. To combat this, I reduced the telemtry levels down which seemed to have no effect. I was on the verge of quitting my bold experiment and retreating back to WP8.1 when Readit released an update with improved performance which also seems to have solved my battery problems, so it seems the issues weren’t with the OS itself, just a single app. It’s a poignant lesson about how a single regularly used app can uniwttingly completely alter your perspective of a platform.</p> <p>My other issue, which I did highlight in my prevous post is performance. I’m glad to say that shortly after that post, this seemed to spontaneously improve dramatically. There were no new builds in this time period, I don’t know if there was some indexing going on that was taking an age to complete and was chomping CPU cycles, but all seems well now. I think the aforementioned performance issues in Readit may have also played a part in my negative impressions.</p> <p>I am still loving the ability to reply to a text without unlocking. My security concerns aside, it is really convenient!</p> <p>I’ve just started working on my first app, a basic music/podcast playing app with Band control integration (as MS have deemed to only update Band 2 with music controls, not Band 1, I’m doing my own!) While the model is quite different to what I am used to as a Win32 desktop and Web developer, it is quite consistent and I’m slowly starting to get my head around things. Debugging my app on the physical phone has been completely painless, although I wish I could say the same about using the emulator!</p> <p>Just 3 days ago, I was seriously contemplating going back to 8.1. I am glad to say that I am now pretty happy with WM10. It’ll be interesting to see if Microsoft keep the same pace of development with WM10 after release as they have with Windows 10 desktop, of if they repeat their previous mistake of releasing a new version and then dropping focus and putting their efforts in somewhere else.</p> <p>Realistically, MIcrosoft are never going to unseat Android and IOS from the 1 and 2 spots, but there is still a chance of carving a decent market share as a third-player, but only if they don’t screw it up again. This is their chance to waste, I hope they don’t.</p>Alanhttp://www.blogger.com/profile/11461540001324768384noreply@blogger.com0tag:blogger.com,1999:blog-4271467236181995002.post-22573292910335638472015-12-28T07:26:00.000-08:002015-12-28T07:26:07.599-08:00Windows 10 Mobile: Impressions after a week.<p>I’ve been keeping a close eye on Windows 10 Mobile, and I haven’t really liked what I’ve seen. The navigation is a clear lift from Android, and not in a good way, and paradigms like the Pivot control which made WP8.1 unique seem to have disappeared. But I’ve never been one for sticking my head in the sand and sticking to the current version of something because the new version looks scary and different so, rather than wait for WM10 to be released, I decided screw it, I’ll get what is essentially the RTM build on my Lumia 1020 by joining the Insider Preview programme.</p> <p>This was about a week ago, and these are my impressions after using WM10 on my daily driver for a week.</p> <h4>The Upgrade</h4> <h4> </h4> <p>The upgrade itself went very smoothly. Took about an hour and everything was exactly where I left it when it came back, even down to the Start layout which wasn't preserved when upgrading 8.1 to 10 on the desktop. Kudos for attention to detail though, as I had the old neutered Office app pinned to my start screen, the upgrade downloaded the new Excel, Word, and Powerpoint apps and put them in a tile group in the same spot where the old Office app was pinned. Not a massive feat of software engineering, but a nice touch. <p>Overall, the upgrade is much like going from Windows 8.1 to 10 on the desktop, generally a non-event with a few niggles that I'm confident will disappear over time. <h4>Navigation</h4> <p>It’s still screwy, although not as bad as I expected. The mail app for example, has the old ellipsis menu at the bottom of the page AND the new hamburger menu at the top of the page. This is just plain confusing and I hope that apps will settle on using one method of navigation over time, even if it is the hamburger. I'm yet to decide if the "hold down the Start button to bring the screen down so you can access controls at the top of the phone with one hand" feature is a nasty hack to get around the idiotic decision to follow Android and put all navigation at the top of the phone away from the user's hand, or a clever trick to get around the idiotic decision to follow Android and put all navigation at the top of the phone away from the user's hand.</p> <h4>Performance</h4> <p>My initial impression of performance was that it was generally comparable to Windows Phone 8.1 on the same device, slower in some areas but faster in others. After a few more days of working with it however, it is definitely slower overall. Loading the main apps I use, such as Mail, Twitter, and Readit, can take seconds. Once they’ve loaded, performance is about the same as on WP8.1, the only issue here is initial load time.</p> <h4>Miscellaneous</h4> <p>The settings app is immeasurably better. The old one was just a completely unorganised list of options, with no sane grouping and a number of really useless names that don’t help you figure out where to find the setting you want. The new one follows the same layout and groupings as Windows 10, so that’s one advantage to the OSes sharing a larger amount of functionality these days.</p> <p>The tiles are larger than on Windows 8, however the “Use More Tiles” option makes them too small in my opinion. Guess you can’t please everyone!</p> <p>The ability to reply to texts without unlocking your phone is really convenient, although I’m slightly concerned about the security issues with such a feature.</p> <h4>Conclusion</h4> <p>My overall impression is that this initial release is a little rough. Obvious, keep in mind that even though build 10586 is RTM, I’m still running the insider preview so there may be extra telemetry enabled and reduced optimisations that may be hampering the performance. Is it as smooth as WP8.1? No. However, I still prefer it to Android even in it’s current state, and given that Windows 10 has improved from it’s already pretty stable condition upon release, if Microsoft keep up the same pace with WM10 as they did with desktop post-release, then I’m confident the rough edges will be gone fairly soon.</p> <p>Will WM10 make Windows Phone a mainstream consumer phone OS finally? Again, no, I highly doubt it. But there are a lot of benefits to the shared code, features, and manageability of Windows 10 and it’s various derivations, including WM10, that may look very tempting to businesses, especially as that space is being rapidly vacated by Blackberry, there is room for a new mainstream business phone, and WM10 may just have a chance there.</p>Alanhttp://www.blogger.com/profile/11461540001324768384noreply@blogger.com0tag:blogger.com,1999:blog-4271467236181995002.post-68343967639853190212015-12-21T05:00:00.000-08:002015-12-21T05:00:30.083-08:00In-place upgrade Windows?! You’ve got to be kidding me!<p>Before the release of Windows 8, this was my default reply to anyone who dared suggest doing an in-place upgrade of Windows. I’d done it before in upgrading from 98 to ME and I’d seen and heard many horror stories of failed in-place upgrades that it become clear that it wasn’t even worth the effort, you were going to have to do a fresh install either way, so you may as well make it plan A.</p> <p>Then along came the £15 upgrade offer for Windows 8 shortly after it’s release in October 2012. It seemed like a no-brainer just to get the latest version of Windows for such a small price. So I went for it, in the expectation that I would have to do a clean install anyway, reducing the upgrade to simply the hoop I had to jump through to get the offer.</p> <p>Imagine my surprise when it worked. There were no BSODs, no applications failing to load after the update, no driver issues, nothing. It. Just. Worked. The only thing I had to do was re-intsall Linqpad to get Windows Search to show in the results lists when searching for “linq”, but in retrospect, if I’d given it a day or two to reindex everything it probably would’ve picked it up on it’s own eventually. In the months after, I upgraded several more machines and witnessed a number of other upgrades, all of them completed with at most minor issues easily solved by driver/Windows updates, or no issues at all. My faith in Windows in-place upgrades was restored.</p> <p>That upgraded OS served me faithfully until I elected do a clean install when replacing my spinning rust HDD with an SSD 6 months later. While I now trusted Windows upgrades, I still don’t trust transferring OSes between disks, been burned on that front numerous times too.</p> <p>Then in early 2015, Microsoft announced the Insider Preview programme for Windows 10. Why not I thought, so I took a laptop, signed up, and in the following 6 months, saw in-place upgrade afer in-place upgrade take place, successfully too for the most part, while keeping in mind this was pre-release so breakages were expected. By the time Windows 10 was released in July, I had no hesitation in just going ahead with the in-place upgrade. To my delight, but not really to my surprise anymore, it just worked. I have since updated a number of machines from both Windows 7 and 8.1 to 10 and haven’t had a single failure yet or major issue that hasn’t been simply resolved by running Windows updates.</p> <p>Whatever you may think of Windows 8 or it’s successors, Windows in-place upgrades are no longer the joke they once were. They’re a very appealing and extremely reliable way of updating to the most recent version of Windows without going through the chore of a clean install. I’ll take an hour to do an in-place upgrade vs spending a day doing a clean install any day!</p> <p>Having said that, always back up anything important before doing an upgrade. Even if the upgrade process works 9999 times out of 10,000, you don’t want to be that unlucky 1.</p>Alanhttp://www.blogger.com/profile/11461540001324768384noreply@blogger.com0tag:blogger.com,1999:blog-4271467236181995002.post-85470812682226123562015-12-17T12:31:00.001-08:002015-12-17T12:31:27.408-08:00OpenLiveWriter–It’s like Windows Live Writer but it works with my blog!<p>Since I started blogging, I’ve had to suffer the apalling mess that is the blogger editor.</p> <p>Windows Live Writer was hailed as the panacea of free editors, but whatever I tried I could never get it to work.</p> <p align="left">Then Scott Hanselman and a group of Microsofties resurrected it as Open Live Writer and less than a week later, blogger support now works!</p> <p align="left">Go download <a href="http://openlivewriter.org/">Open Live Writer</a> now!</p>Alanhttp://www.blogger.com/profile/11461540001324768384noreply@blogger.com0tag:blogger.com,1999:blog-4271467236181995002.post-84403510423441278132015-09-16T06:03:00.000-07:002015-09-16T06:03:00.571-07:00Windows 10 upgrade experienceMicrosoft have really upped their upgrade game.<div>
<br /></div>
<div>
Pre-Windows 8, there was no point even bothering with an upgrade. Even if it completed without bricking your machine, the end result would be so unstable you'd just pave it and start again anyway.</div>
<div>
<br /></div>
<div>
<br /></div>
<div>
The upgrade from 7 to 8 was relatively painless, I only needed to reinstall a handful of applications afterwards, not a big deal.</div>
<div>
<br /></div>
<div>
8 to 8.1 was seamless, as you'd expect given that 8.1 was essentially a service pack.</div>
<div>
<br /></div>
<div>
I've now upgraded 8.1 to 10 on 2 machines and the process has been almost perfect. On my home machine, I actually have one less bug than I had before!</div>
<div>
<br /></div>
<div>
About the only criticism I have is that you lose your start screen layout, but in reality this isn't a big deal and has encouraged me to do a little spring cleaning of my start screen apps.</div>
<div>
<br /></div>
<div>
I never thought the day would come when I'd start a Windows upgrade and not be already digging out my installer archive in preparation for a clean install.</div>
<div>
<br /></div>
<div>
Well done Microsoft, keep it up.</div>
Alanhttp://www.blogger.com/profile/11461540001324768384noreply@blogger.com0tag:blogger.com,1999:blog-4271467236181995002.post-4232325245799423362015-09-11T11:54:00.000-07:002015-09-11T11:54:11.242-07:00UniqueIdentifier as a primary key, that will solve all of our problems!No, no it won't.<br />
<br />
In evolving a single-instance website to multi-instance one, one of the many problems I have faced is how to deal with database access when your website instances are on the opposite side of the world.<br />
My solution to this was to use SQL Azure Data Sync, makes sense as my databases are already in SQL Azure anyway.<br />
Facilitating this involved changing all of the integer primar keys of every table to a different type with a lower possibility of collisions when syncing between databases.<br />
<br />
I thought a Guid in the .Net side and a UNIQUEIDENTIFIER in the database would be the perfect fit for this. I was very, very wrong.<br />
<br />
While using uniqueidentifiers as PKs has virtually eliminated any possibility of a sync collision, there is a very undesirable side effect of very high index fragmentation, bringing my site crashing to it's knees as soon as the indexes reach a critical level of fragmentation.<br />
<br />
As is customary, I decided to run some tests. Below are the results of inserting 10k records in to an empty table:<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi_n8sXWQ0bIvQhO3tCmnstRjtP5EnAiLJWYCu9twPrucaeOEDYmdMQW4KBXDXnmIU9oVhZu2W9nKOXp3YLg0WSqrnUWdEZPzKp1JZPCC9dyW1SlrYJqFBZOnjWkXst4bgkOjnJIiv-1mk/s1600/blog-guids-1.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi_n8sXWQ0bIvQhO3tCmnstRjtP5EnAiLJWYCu9twPrucaeOEDYmdMQW4KBXDXnmIU9oVhZu2W9nKOXp3YLg0WSqrnUWdEZPzKp1JZPCC9dyW1SlrYJqFBZOnjWkXst4bgkOjnJIiv-1mk/s400/blog-guids-1.PNG" /></a></div>
Ouch! 96% fragmentation vs 18% on 10k records. Now in reality I rarely insert 10k records at the same time, but certain operations involve hundreds and this level of fragmentation will occur over the course of time.
<br />
<br />
Regardless of what data type your PKs are, fragmentation will happen. But the massive downside of using uniqueidentifier is that this not only happens a lot faster, but also a simple defrag or rebuild indexes is not going to save you as the data is inherently, due to it's random nature, impossible to efficiently index.
<br />
<br />
My first idea was to use an identity column (in my case called clusterkey) for the clustered index and keep the PK as a Guid with the PK constraint being non-clustered. This would sort out the fragmentation problem. But unfortunately, SQL Azure Data Sync didn't like my clusterkey, I suspect because it is a non-PK identity column. Regardless of the reason, it's not viable, so I looked further.
<br />
<br />
A contact at Microsoft suggested using the SequentialId() function in SQL Azure (available as of the latest V12 release), but all of my Guids are generated in code, so this was too big a change for me. My colleague Dave came to the rescue by tracking down <a href="http://www.codeproject.com/Articles/388157/GUIDs-as-fast-primary-keys-under-multiple-database">this</a> article, which describes how to generate reasonably sequential guids in C# that should keep the clustered index happy.
<br />
<br />
I'll not repeat the contents of the article, which is a really informative read, but it seems to work. This is how my tests look now:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjWbhDSIkyV5jGOBhQ3yHIHyRQoxkKyLln0OBDHSvVHqiix_N7E6gIOFxyiO16leHuIrkmaNCaJpKAIevIYKsgctWJyxlGSqjafJUENBGm0tAghbb7QW5QxfLqDW2kfQpHStiKOVrxyTDU/s1600/blog-guids-2.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjWbhDSIkyV5jGOBhQ3yHIHyRQoxkKyLln0OBDHSvVHqiix_N7E6gIOFxyiO16leHuIrkmaNCaJpKAIevIYKsgctWJyxlGSqjafJUENBGm0tAghbb7QW5QxfLqDW2kfQpHStiKOVrxyTDU/s400/blog-guids-2.PNG" /></a></div>
I'll take that, much better fragmentation and approximately the same cost for inserts.
<br />
<br />
<b>Note</b>: The insert time includes the time taken to generate the Guid/SequentialGuid in code. Also, don't take the fact that SequentialGuid is smaller here as an indication that it is consistently faster. During testing, the number was generally the same as Guids and ints, but was consistently faster as soon as I came to actually measuring it for this blog post!
<br />
<br />
<br />
As these kinds of distributed environments become more common, I suspect more and more people will hit similar issues so hopefully this will help someone avoid making the same mistakes I have.Alanhttp://www.blogger.com/profile/11461540001324768384noreply@blogger.com1tag:blogger.com,1999:blog-4271467236181995002.post-52876465188832496582015-06-05T13:14:00.000-07:002015-06-05T13:14:18.779-07:00Windows Phone 10 - First ImpressionsAfter running the Windows 10 Technical Preview on my laptop for the last few months and being generally happy with it, bar the obvious problems you get running a Technical Preview, I've been itching to get my hands on Windows Phone 10 (officially referred to as Windows 10 Mobile, but I'm going to refer to it as WP10). As a very happy owner of a Lumia 1020, I'll be getting it eventually so thought it was worth taking a look at. Fortunately, a colleague has recently switched from WP to Apple, so he kindly leant me his Lumia 635 to use as a sacrifice to the Technical Preview gods.<br />
<br />
If you want to run the Technical Preview, details are <a href="http://windows.microsoft.com/en-gb/windows/preview-download-phone" target="_blank">here</a>. But seriously, heed Microsoft's warning. You don't want to apply this to your main phone. I haven't hit any bugs yet, but the many interaction problems mean I'd be really annoyed if I had installed this on my main phone.<br />
<br />
<h4>
The Good</h4>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhv9uPiXw2NzZJCgM2gQbcuw3mbX4vM2GdZzMasWzdT4GRLquwwKUnZIkdx6jUPCElsqk1SNxHjjvmoUVvNXaHAY7HDK6yXiE9SAVtw6vJGNdY6fOyrBBUe4MaDNB3cQKqVBydS-17oZnI/s1600/wp_ss_20150605_0012%255B1%255D.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhv9uPiXw2NzZJCgM2gQbcuw3mbX4vM2GdZzMasWzdT4GRLquwwKUnZIkdx6jUPCElsqk1SNxHjjvmoUVvNXaHAY7HDK6yXiE9SAVtw6vJGNdY6fOyrBBUe4MaDNB3cQKqVBydS-17oZnI/s200/wp_ss_20150605_0012%255B1%255D.png" width="120" /></a><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi9JR8aFRWfwfPwPn6yBuy5WllMzTJlWxVcb22ugFkNInRMsePCKOfQJxZZmbhWxJCaz7yi31RMmqMY6TND4JdbI7zUDl4hxc1w81yOcD5vZwWvCsnfrUJVy30DM5J9MUIyPr0aUSeIBXg/s1600/wp10-settings.jpg" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi9JR8aFRWfwfPwPn6yBuy5WllMzTJlWxVcb22ugFkNInRMsePCKOfQJxZZmbhWxJCaz7yi31RMmqMY6TND4JdbI7zUDl4hxc1w81yOcD5vZwWvCsnfrUJVy30DM5J9MUIyPr0aUSeIBXg/s200/wp10-settings.jpg" width="120" /></a>The settings app is vastly improved over the one on WP8.1, which is essentially a load of very narrowly scoped categories in a <i>really </i>long list on the page. The new app is well thought out, and the categorisations seem to broadly match those on Windows 10 on the desktop, which means you experience with one should transfer pretty well to the other.<br />
<br />
<br />
<br />
<div style="text-align: right;">
The quick access to commonly used settings that has appeared in the notifications flyout on Windows 10 is also present here. It's a welcome addition that should reduce the need to venture in to the Settings app.</div>
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<h4>
The Bad</h4>
Yes, that good section whizzed by rather fast didn't it? While I generally like Windows 10, even though it does remove some of the minor features of Windows 8.1 that I quite like, WP10 takes this a step further and removes pretty much everything I like about WP in one fell swoop. Let's go through the list shall we?<br />
<br />
<h3>
Command placement</h3>
WP was very predictable, all commands were at the bottom of the screen, easily reached by your thumb to summon them up. They were large and full width, so you could easily trigger them with the hand that is holding the phone without too much stretching. The overall one-handed experience with WP8.1 is very comfortable.<br />
A great deal of the commands seem to have moved to the new "hamburger" menus in WP10, which are situated in the top-left, about as far away from your phone-holding hand as you can get (unless you're left handed of course).<br />
<br />
<table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: left; margin-right: 1em; text-align: left;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhr3ZQ7CmXQ88jed-RC2s15ua3baknhNb8RauYuaNWe2UUjFRmszMtUDrpwUuHP2sfqkyJL-UoeRcObZrExeQ4fAkPM_vvzIDJ1ak9fTIkFQRGjQ75I-lncrYFuV-Egdljg71M_5N3pIKI/s1600/wp_ss_20150605_0011%255B1%255D.png" imageanchor="1" style="clear: left; margin-bottom: 1em; margin-left: auto; margin-right: auto;"><img border="0" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhr3ZQ7CmXQ88jed-RC2s15ua3baknhNb8RauYuaNWe2UUjFRmszMtUDrpwUuHP2sfqkyJL-UoeRcObZrExeQ4fAkPM_vvzIDJ1ak9fTIkFQRGjQ75I-lncrYFuV-Egdljg71M_5N3pIKI/s200/wp_ss_20150605_0011%255B1%255D.png" width="120" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Top, bottom, ellipsis, <br />where do I go?</td></tr>
</tbody></table>
<h3>
<b>Swiping</b></h3>
As part of their march to look like everyone else, MS have removed the pivot control from their apps. This means that instead of just swiping, again with your phone-holding hand, to move between screens, you now have to stretch<br />
to either the hamburger menu or, in the case of the dialer, to the buttons situated in the middle-top of the screen OR the buttons situated in the middle-bottom of the screen. Add to that, the dialer still has the older ellipsis menu from WP8.1, this one app has about every interaction model available, except for the one that suited it the most. Now I'm sure the ellipsis will go before release, but MS seem to have lost sight of the fact that the Pivot control was one of the greatest things about WP8.1 and I can't help but think that they could've got away with just making it a little smaller to make things more familiar to Android/iOS users while still retaining the superior interaction.<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<h3>
<b>Schizophrenic navigation in the UWP mail app.</b></h3>
I've noted this separately from the above as I genuinely don't know whether this is just because this is a TP or if this is how this is actually intended to work, but the interaction with the mail app is incredibly painful and I sincerely hope this is not a sign of what we can expect from the UWP platform. I generally don't like doing 1-to-1 comparisons against a preview build, but in this case it's necessary to show how much of backward step this is.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgse9JGJDfL5zpF29qMzT-NWIlAIlRJUwo5qE28TXB9dr1SI73fTFkOg99Jdkco9uv8gfh3CmPhBSaXpjCVlfotFEsAjTOrbwuVPbfCOT1ecb0wQ4JxzWMDenDhtYCQrHGwgdgWGpNGej4/s1600/wp8-outlook-show-options.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="178" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgse9JGJDfL5zpF29qMzT-NWIlAIlRJUwo5qE28TXB9dr1SI73fTFkOg99Jdkco9uv8gfh3CmPhBSaXpjCVlfotFEsAjTOrbwuVPbfCOT1ecb0wQ4JxzWMDenDhtYCQrHGwgdgWGpNGej4/s320/wp8-outlook-show-options.jpg" width="320" /></a></div>
<div style="text-align: left;">
To look at account settings in WP 8 is: Swipe up (or tap ellipsis) to bring up command bar (at the bottom of the screen), tap settings in the resulting menu which appears from the bottom of the screen.</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEicZmVJmJbvwSZbSv1nr6Ye0vfjWeMbIrUgT7_tHXMZV0thwwRWHzxMCft2dOpZ1F1ujZrHf8p_5FaJfICflHeSRLKIwVVx8HQbL8xH1WX5vJhXIFvVAV8x6hl-xHJopWyc2CiiUmcfJvY/s1600/wp10-outlook-show-options.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="133" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEicZmVJmJbvwSZbSv1nr6Ye0vfjWeMbIrUgT7_tHXMZV0thwwRWHzxMCft2dOpZ1F1ujZrHf8p_5FaJfICflHeSRLKIwVVx8HQbL8xH1WX5vJhXIFvVAV8x6hl-xHJopWyc2CiiUmcfJvY/s320/wp10-outlook-show-options.jpg" width="320" /></a></div>
<br />
In WP10: Tap hamburger (top-left), cog icon (bottom-right), tap options (in menu that appears from top of screen). My finger is going all over the phone here. It's quite a stretch and it's not even a big phone. On my Lumia 1020, I suspect this would be a 2-handed operation.<br />
<br />
<br />
<br />
<br />
<table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: right; text-align: right;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjip1XWtIkBCcv2fsWdykJX_dpAmw5dvlwMe4bJVvUVMujvwaDrur5EabkGZqy-DCE-fZ5PeVMBfdTf6EMCcYJqo5RI63HFDfq_0SzIEvCnsuKKlvQ2sNzFKM5bsxF7gxiWy8OMmAQqZ10/s1600/wp10-calendar-dropdown1.png" imageanchor="1" style="clear: right; margin-bottom: 1em; margin-left: auto; margin-right: auto;"><img border="0" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjip1XWtIkBCcv2fsWdykJX_dpAmw5dvlwMe4bJVvUVMujvwaDrur5EabkGZqy-DCE-fZ5PeVMBfdTf6EMCcYJqo5RI63HFDfq_0SzIEvCnsuKKlvQ2sNzFKM5bsxF7gxiWy8OMmAQqZ10/s200/wp10-calendar-dropdown1.png" width="120" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Ready to dropdown and<br />nowhere to go.</td></tr>
</tbody></table>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgsODgojbc70maTi3v7skB0ugxOPXSO9JDu4GtKMdj60gfNjKXRXGiU5yErT_uudX7wC0dvTEHlVp1maSBaWuk0JE-tFxk7TCiqAHDf_J6ibbUzWaTd4kX_GxrSsrEpaSY3CFpvYBjOUec/s1600/wp10-calendar-dropdown2.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgsODgojbc70maTi3v7skB0ugxOPXSO9JDu4GtKMdj60gfNjKXRXGiU5yErT_uudX7wC0dvTEHlVp1maSBaWuk0JE-tFxk7TCiqAHDf_J6ibbUzWaTd4kX_GxrSsrEpaSY3CFpvYBjOUec/s200/wp10-calendar-dropdown2.png" width="120" /></a><br />
<h3>
Desktop input controls on a phone</h3>
Most of the criticism aimed at Windows 8 is that it uses a phone UI on the desktop. It would appear misplaced UI elements is a two-way street. In the calendar app, and presumably all UWP apps, the dropdowns, rather than being full screen as they have been previously, are dropdowns just as they would be on the desktop. Except now they've got to contend with fat fingers and an on-screen keyboard that giv<br />
es them no vertical height to occupy.<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<br />
<br />
<br />
<h3>
<b>Slow animation on apps view</b></h3>
Now I'm nitpicking a bit, but WP at the moment is a very snappy, fast OS. You can get things done pretty quickly and there are no slow animations getting in your way, everything happens almost instantaneously. Long pressing on the back button to bring up the running apps view triggers in what feels like about 100-200ms and the animation is quick.<br />
On WP10, it feels more like 700-800ms and the animation is slow. I don't think this is down to the hardware as there is no visible lag, it just seems that they've slowed it down to make it smoother. Smooth === slow in this case.<br />
<br />
<h4>
Verdict</h4>
Not good. WP has always been a very different beast to other mobile operating systems, maybe this is part of the reason it hasn't taken off. But, in my view, the interaction with WP is faster, easier, and more natural than interacting with Android or iOS. I haven't had a great deal of contact with iOS and even less with BlackBerry, but I had Android phones for about 6 years before my current Lumia and while I didn't hate using them, WP was the first mobile operating system that I can actually describe as being pleasant to use.<br />
While there are a lot less WP users, those who stick with it tend to be much more satisfied with it than users of other operating systems. This <a href="http://www.pcmag.com/article2/0,2817,2416521,00.asp" target="_blank">survey</a>, while admittedly a couple of years old, proves the point (at least among the users Reader's Choice surveyed).<br />
<br />
In their attempts to try and appeal to Android and iOS users, MS seem to have forgotten the things that make WP so pleasant to use. While I do not blame them at all for trying to make the experience more familiar to users of other OSes so the move doesn't seem quite so scary to those used to Android or iOS, you've got to think of your existing users while trying to appeal to new ones.<br />
<br />
I suspect that, if the current preview is anything to go by, WP10 is just going to look like a pale imitation of Android or iOS, and that's a damn shame.Alanhttp://www.blogger.com/profile/11461540001324768384noreply@blogger.com0tag:blogger.com,1999:blog-4271467236181995002.post-18645456192525211592015-05-21T01:42:00.000-07:002015-05-21T01:58:40.716-07:00Getting started with Azure Application InsightsNow that pricing information has been released for Application Insights, I decided to take the dive and deploy it on a few of the web applications I work on.
The documentation is pretty good for your basic scenarios, but it glosses over what I think are probably common use cases.
<br />
<ul>
<li>The instrumentation key (commonly referred to as the iKey) is located in an applicationinsights.config file, which is not very helpful if you want to change it when deploying to Live or Staging environments.</li>
<li>If you use any monitoring system, such as Traffic Manager, Web Apps AlwaysOn, or any Web Testing application, this all gets included as "real traffic", which is fair enough as AppInsights has no way of knowing that it isn't real traffic. You may want to see it, but I personally do not so I wanted a way to filter it out.</li>
</ul>
There is a really great <a href="http://blogs.msdn.com/b/visualstudioalm/archive/2015/01/07/application-insights-support-for-multiple-environments-stamps-and-app-versions.aspx">blog post by Victor Mushkatin</a> which covers changing where AppInsights looks for the instrumentation key, adding your version number and your own tags to the telemetry so you can filter by version number or any tag you provide. I've added a siteIdentifier tag which contains an id for the particular instance of the application, identifying where in the world it is hosted.<br />
My particular variation of his code adds the SiteIdentifier and the assembly version to the telemetry.<br />
<pre class="brush: csharp"> public class AppInfoApplicationInsightsConfigInitializer : IContextInitializer
{
public void Initialize(TelemetryContext context)
{
context.Properties["SiteIdentifier"] = System.Configuration.ConfigurationManager.AppSettings["SiteIdentifier"];
try
{
var verAtt = (AssemblyInformationalVersionAttribute)Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyInformationalVersionAttribute), false)[0];
context.Component.Version = verAtt.InformationalVersion;
}
catch (Exception)
{
context.Component.Version = "Application Version Unknown";
}
}
}
</pre>
I also have a TelemetryInitializer that finds traffic from load balancers and web testers as explained at the top of this post, and reclassifies it as synthetic traffic, making it easier to exclude from charts and reports. I found that this traffic shows up as a different user every time, making my user count orders of magnitude higher than it should be.<br />
<pre class="brush: csharp"> public class SyntheticSourceInitializer : ITelemetryInitializer
{
public void Initialize(Microsoft.ApplicationInsights.Channel.ITelemetry telemetry)
{
if (HttpContext.Current == null)
return;
//Set traffic manager check and web test request to synthetic.
if (HttpContext.Current.Request.Url.ToString().EndsWith("/monitoring"))
{
telemetry.Context.Operation.SyntheticSource = "AzureAliveCheck";
}
//Set Azure Web Apps AlwaysOn pings to synthetic.
if (HttpContext.Current.Request.UserAgent == "AlwaysOn")
{
telemetry.Context.Operation.SyntheticSource = "AzureAliveCheck";
}
}
}
</pre>
You've got full access to the current request so you can identify the traffic however you need to (referrer, headers, request url, etc)<br />
I then tell AppInsights to use these classes with the below entries in Application_Start() in global.asax.cs<br />
<pre class="brush: csharp"> //Configure application insights.
TelemetryConfiguration.Active.InstrumentationKey = System.Configuration.ConfigurationManager.AppSettings["iKey"];
TelemetryConfiguration.Active.ContextInitializers.Add(new AppInfoApplicationInsightsConfigInitializer());
TelemetryConfiguration.Active.TelemetryInitializers.Add(new SyntheticSourceInitializer());
</pre>
<br />
As I dig further in to Application Insights, if I find more examples of useful overrides for default behaviour, I will add additional blog posts detailing these.Alanhttp://www.blogger.com/profile/11461540001324768384noreply@blogger.com3tag:blogger.com,1999:blog-4271467236181995002.post-817437522970018892015-04-15T12:24:00.001-07:002015-04-15T12:24:40.293-07:00Close a window by title in C#<p>When you're developing for embedded systems that don't have a mouse or keyboard attached, a misbehaving program that decides to pop up windows at random is suddenly a lot more inconvenient.</p>
<p>Cue the below code snippet, which takes in a window title and sets it's state to minimised, maximised, or normal depending on the parameters you pass in.
As usual, this is a Linqpad script. You just need to add a reference to <strong>System.Runtime.InteropServices</strong>, which is part of .net 4 and above.</p>
<br />
<pre class="brush: csharp">void Main()
{
var windowTitle = "Untitled - Notepad";
IntPtr hWnd = FindWindow(null, windowTitle);
if (!hWnd.Equals(IntPtr.Zero))
{
ShowWindowAsync(hWnd, SW_SHOWMINIMIZED);
}
}
// Define other methods and classes here
private const int SW_SHOWNORMAL = 1;
private const int SW_SHOWMINIMIZED = 2;
private const int SW_SHOWMAXIMIZED = 3;
[DllImport("user32.dll")]
private static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);
[DllImport("user32.dll", EntryPoint = "FindWindow")]
private static extern IntPtr FindWindow(string lp1, string lp2);
</pre>
<p>This just imports the FindWindow and ShowWindowAsync methods from the user32 assembly. FindWindow is used to find the window we want to close. This return a pointer to the window handle, which we then pass to ShowWindowAsync along with an int indicating what we want to do to the window.</p>
<p>
In the above example, I already know the window title, but this is unlikely to be the case in the real world. You can get this with the below snippet which will select out the window title and the process name. You can obviously add a where clause and modify the results based on your needs.</p>
<pre class="brush: csharp">Process.GetProcesses().Select (p => new{p.ProcessName, p.MainWindowTitle})
</pre>
Alanhttp://www.blogger.com/profile/11461540001324768384noreply@blogger.com0tag:blogger.com,1999:blog-4271467236181995002.post-83879795616684970072015-01-16T02:18:00.003-08:002015-01-16T02:27:52.710-08:00ProTip: Open Powershell as admin from PowershellJust a quick one as I find I have been using this trick a lot lately.<br />
If I am in a standard Powershell prompt and need to get an admin one open, I used to search for Powershell, right-click, run as admin. I'd do this even if I was already in a Powershell prompt as I can never remember the syntax for runas.exe.<br />
A much easier way, especially if you are already in a Powershell prompt is:<br />
<br />
Start-Process powershell -verb runas<br />
<br />
This works for any executable and will pop up UAC appropriately to allow you to enter credentials if you need to, or just run as admin if you are already an Administrator.<br />
<br />
Hope this helps some one.<br />
<br />
<b>Edit</b><br />
<br />
This can be further shortened to<br />
<br />
start powershell -verb runas<br />
<br />
Thanks anonymous user!Alanhttp://www.blogger.com/profile/11461540001324768384noreply@blogger.com1tag:blogger.com,1999:blog-4271467236181995002.post-34977415594024689192014-10-09T02:04:00.000-07:002014-10-13T11:54:26.938-07:00Copying records between tables in different Azure accounts : The Next GenerationA few months back I posted a small piece of code for copying objects between Azure Table Storage Accounts. I have 2 bug-bears with the code I previously posted.<br />
<br />
<ol>
<li> It's all very custom, you need to give it the type that is in the table and add lambdas to make sure it doesn't try to select the wrong object. </li>
<li>Due to a change in the Azure Storage Library, it no longer works. </li>
</ol>
<br />
I hadn't used this script in a while but now I have an impending need for something like it but better that will allow me to copy the contents of all the tables or a defined subset of tables in a given account, enter the DynamicTableEntity which allows you to get an entry from a table as a dynamic object.<br />
To run the code, open up <a href="http://linqpad.net/" target="_blank">Linqpad</a> and add a reference to the Windows Azure Storage library, best way to do this is using Nuget.<br />
<br />
<pre class="brush: csharp">void Main()
{
var srcClient = CreateClient("source account connection string");
var destClient = CreateClient("destination account connection string");
var mappings = new List<Tuple<string,string>>();
//Manually setup mappings.
//mappings.Add(new Tuple<string,string>("table1","table1copy"));
//mappings.Add(new Tuple<string,string>("table2","table2copy"));
//Copy all tables from the src account in to identically named tables in the destination account.
var tables = srcClient.ListTables(null, new TableRequestOptions(){PayloadFormat = TablePayloadFormat.JsonNoMetadata});
mappings = tables.Select (t => new Tuple<string,string>(t.Name,t.Name)).ToList();
Copy(srcClient,destClient,mappings);
}
public void Copy(CloudTableClient src, CloudTableClient dest, List<Tuple<string,string>> mappings) {
mappings.ForEach(x=>{
var st = src.GetTableReference(x.Item1);
var dt = dest.GetTableReference(x.Item2);
dt.CreateIfNotExists();
var query = new TableQuery<DynamicTableEntity>();
foreach (var entity in st.ExecuteQuery(query))
{
dt.Execute(TableOperation.InsertOrReplace(entity));
}
});
}
public CloudTableClient CreateClient(string connString){
var account = CloudStorageAccount.Parse(connString);
return account.CreateCloudTableClient();
}
</pre>
<br />
In the above code, we create CloudTableClients to represent the source and destination accounts, then we build a mapping of source and destination tables.<br />
We can do this manually if we only want to copy some tables and/or we want the destination tables to have different names to the source tables.<br />
Alternatively, we can get a list of all the tables from the source and use that to build a 1:1 map, this will have the result of copying all items in all tables from the source account to the destination.<br />
The Copy method simply takes the clients and mapping and does some iteration to get the items from each table from the source account and save them to the destination account.<br />
<br />
<b>Note:</b> The code above is horribly inefficient for copying large amounts of data as it inserts each request individually. In a follow up post, I'll make this more efficient by making use of the TableBatchOperation.Alanhttp://www.blogger.com/profile/11461540001324768384noreply@blogger.com0tag:blogger.com,1999:blog-4271467236181995002.post-12112508832087758202014-08-31T13:53:00.003-07:002014-08-31T13:53:29.979-07:00Using Custom Model Binders in ASP.Net MVCI answered a question on Reddit this week from someone starting out in MVC who had read an incorrect article about model binding which was mostly correct, but made using custom binders look like they require more code than they actually do, so I thought it was worth a post to clear that up.<br />
<h2>
What is (Custom) Model Binding?</h2>
Model Binding is the process through which MVC takes a form post and maps all of the form values in to a custom object, allowing you to have a POST action method which takes in a ViewModel and have it automagically populated for you. Custom Model Binders allow you to insert your own binders for particular scenarios where the default binding won't quite cut it.<br />
<h2>
Creating our custom binder</h2>
We have the following typical example ViewModel:
<br />
<pre class="brush: csharp"> public class MyViewModel
{
public string MyStringProperty { get; set; }
}
</pre>
It's just a class, nothing special about it at all. Now we want to manually handle the binding of this model because we want to add some text to the end of MyStringProperty when it gets bound. This is unlikely to be something you would want to do in real life, but we're just proving the point here.<br />
This is our binder:<br />
<pre class="brush: csharp"> public class MyViewModelBinder:IModelBinder
{
protected System.Collections.Specialized.NameValueCollection Form { get; set; }
private void Initialise(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
Form = controllerContext.HttpContext.Request.Form;
}
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
Initialise(controllerContext, bindingContext);
var msp = Form["MyStringProperty"];
return new MyViewModel {MyStringProperty = msp + " from my custom binder"};
}
}
</pre>
Model Binders need to implement IModelBinder and have a BindModel method. This gives you access to the controllerContext from which you can access HttpContext and the bindingContext, which admittedly I have never had to use.<br />
In our binder, we just manually pick up the MyStringProperty value from the form, add it to a new instance of our object and return it, adding our incredibly important piece of text to the end of the retrieved value.<br />
<h2>
Using our Custom Binder</h2>
There are 2 ways we can use our custom binder, which one we use depends on each scenario. If we need to override the binding of a class for a particular Action method, we can use the ModelBinder attribute on the relevant parameter of the Action Method:<br />
<pre class="brush: csharp"> [HttpPost]
public ActionResult Index([ModelBinder(typeof(MyViewModelBinder))]MyViewModel model)
{
return View(model);
}
</pre>
This will apply our Custom Binder to this property (MyViewModel) for this action only, no other actions or controllers will be affected.<br />
Alternatively, if we want to apply our custom binder to MyViewModel globally within the application, we can add the following line to Application_Start in global.asax.cs:<br />
<pre class="brush: csharp"> protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
ModelBinders.Binders[typeof(MyViewModel)] = new MyViewModelBinder();
}
</pre>
Using this method, everywhere a parameter of type MyViewModel is encountered on an ActionResult, our custom binder will be invoked instead of the standard one. Because this applies globally, we do not need the ModelBinder attribute on our Action Method so the overridden behaviour is completely transparent to the controller, promoting code reuse and keeping model binding logic where it belongs.Alanhttp://www.blogger.com/profile/11461540001324768384noreply@blogger.com0tag:blogger.com,1999:blog-4271467236181995002.post-34547035082017004502014-08-06T13:52:00.000-07:002014-08-07T13:55:37.028-07:00API Head-to-head: AWS S3 Vs Windows Azure Table Storage<style type="text/css">
table.data {
border: 1px solid black;
text-align: center;
}
table.data tbody tr td, table.data thead tr th {
border:1px solid black;
}
table.data thead tr th {
background-color:lightgray;
}
</style>
Recently, I was experimenting with using S3 as a tertiary backup for my photos, an honour which eventually went to Azure as it was cheaper and I am more familiar with the Azure APIs as I use them in my day job.<br />
<br />
I thought I’d take a deeper look at both APIs and see how they compare. I’ll go through some standard operations, comparing the amount of code required to perform the operation.<br />
<br />
If you want a comparison of features, there are plenty of blog posts on the subject, just <a href="http://www.bingle.nu/results.php?type=www&query=AWS%20S3%20VS%20Azure%20Blob%20Storage">Bingle It</a>
<br />
<br />
All the code in this test is being run in <a href="http://www.linqpad.net/">Linqpad</a>, using the <a href="https://www.nuget.org/packages/AWSSDK/">AWS SDK for .Net</a> and <a href="https://www.nuget.org/packages/WindowsAzure.Storage/">Windows Azure Storage</a> Nuget packages.
<br />
<br />
<h1>
Create the client</h1>
Both Azure and S3 have the concept of a client, this represents the service itself and is where you provide credentials for accessing the service.<br />
<br />
<h3>
Azure</h3>
<pre class="brush: csharp">var account = Microsoft.WindowsAzure.Storage.CloudStorageAccount.Parse("connectionstring");
var client = account.CreateCloudBlobClient();
</pre>
<h3>
S3</h3>
<pre class="brush: csharp">var client = AWSClientFactory.CreateAmazonS3Client("accessKey", "secret",RegionEndpoint.EUWest1);
</pre>
S3 wins on lines of code but I don’t like having to declare the datacenter the account is in. In my opinion, the application shouldn’t be aware of this. 1 point to Azure.
<br />
<br />
<h1>
Creating a container</h1>
This is a folder, Azure refers to is a container, S3 calls it a bucket.<br />
<br />
<h3>
Azure</h3>
<pre class="brush: csharp">var container = client.GetContainerReference("test-container");
container.CreateIfNotExists();
</pre>
<h3>
S3</h3>
<pre class="brush: csharp">try
{
client.PutBucket(new PutBucketRequest { BucketName = "my-testing-bucket-123456", UseClientRegion = true});
}
catch (AmazonS3Exception ex)
{
if(ex.ErrorCode != "BucketAlreadyOwnedByYou") {
throw;
}
}
</pre>
S3 loses big time on simplicity here. To my knowledge, this is the only way to do a blind create of a container, that is creating it without knowing up front if it already exists. Azure makes this trivial with CreateIfNotExists. 2 points to Azure.
<br />
<br />
<h1>
Uploading a file</h1>
<h3>
Azure</h3>
<pre class="brush: csharp">var container = client.GetContainerReference("test-container");
var blob = container.GetBlockBlobReference("testfile");
blob.UploadFromFile(@"M:\testfile1.txt",FileMode.OpenOrCreate);
</pre>
<h3>
S3</h3>
<pre class="brush: csharp">var putObjectRequest = new PutObjectRequest {BucketName = "my-testing-bucket-123456", FilePath = @"M:\testfile.txt", Key = "testfile", GenerateMD5Digest = true, Timeout=-1};
var upload = client.PutObject(putObjectRequest);
</pre>
They’re pretty much equal here, but the S3 code is more verbose. I like the idea of getting a reference to a blob while not knowing if it actually exists or not.
<br />
<br />
<h1>
List Blobs</h1>
<h3>
Azure</h3>
<pre class="brush: csharp">var container = client.GetContainerReference("test-container");
var blobs = container.ListBlobs(null, true, BlobListingDetails.Metadata);
blobs.OfType<cloudblockblob>().Select (cbb => cbb.Name).Dump();
</cloudblockblob></pre>
<h3>
S3</h3>
<pre class="brush: csharp">var listRequest = new ListObjectsRequest(){ BucketName = "my-testing-bucket-123456"};
client.ListObjects(listRequest).S3Objects.Select (so => so.Key).Dump();
</pre>
In terms of complexity, they’re pretty even here too. Azure has one more line but it’s not a difficult one. Notice that whereas with Azure, we get a reference to a container and then perform operations against that reference, with AWS all requests are individual so you end up having to explicitly tell the client for every operation what the bucket name is. Point to Azure.
<br />
<br />
<h1>
Deleting a Blob</h1>
<h3>
Azure</h3>
<pre class="brush: csharp">var dblob = container.GetBlockBlobReference("testfile");
dblob.Delete();
</pre>
<h3>
S3</h3>
<pre class="brush: csharp">var delRequest = new DeleteObjectRequest(){ BucketName = "my-testing-bucket-123456", Key="testfile"};
client.DeleteObject(delRequest);
</pre>
Neither code is particularly complicated here, but I prefer Azure’s simplicity with the container and blob reference model so point Azure.
<br />
<br />
<h1>
Delete a Container</h1>
<h3>
Azure</h3>
<pre class="brush: csharp">var container = client.GetContainerReference("test-container");
container.Delete();
</pre>
<h3>
S3</h3>
<pre class="brush: csharp">var delBucket = new DeleteBucketRequest(){ BucketName = "my-testing-bucket-123456"};
client.DeleteBucket(delBucket);
</pre>
Again, pretty equal. To micro-analyse the lines, you could say that for Azure, you’ve got one potentially reusable line, and one throw-away line. With S3, they’re both throw away. But in reality, unless you’re doing thousands of consecutive operations, it doesn’t really matter.
<br />
<br />
<h1>
Conclusion</h1>
In terms of complexity, Azure’s and S3’s APIs are pretty much equal, but it’s easy to see where they each have their uses. Azure’s API is a much thicker abstraction over REST, whereas the S3 API is such a thin-veneer that you could imagine a home-grown API not turning out that differently (but most likely not as reliable).<br />
<br />
In my mind, if you’re doing lots of operations against lots of different blobs and containers then S3’s API is more suitable as each operation is self-contained and there are no references to containers or blobs hanging around.<br />
<br />
If you’re doing operations which share common elements, such as performing numerous operations on a blob or working with lots of blobs within a few containers, Azure’s API seems better suited as you create the references and then reuse them, reducing the amount of repeated code.
<br /><br/>
<h1>
Bonus Section</h1>
If you could be bothered to read past my conclusion, congratulations on your determination! The comparative speed of Azure and AWS has been done to death, but I couldn’t resist getting my own stats.<br />
<br />
These are ridiculously simple stats, essentially Stopwatch calls wrapped around the code in this post. The file I am uploading is only 6k. The simple reason for this is that everyone tests how these services handle lots of large objects, but no one seems to cover the probably more common scenario of users uploading very small files. The average size is probably higher than 6kb, but this is what I’ve got hanging around so this is what I’m using.<br />
<br />
So here are my extremely simple and probably not at all reliable benchmarks.<br />
<br />
<table class="data">
<thead>
<tr>
<th style="width:150px;">Operation</th>
<th style="width:50px;">S3</th>
<th style="width:50px;">Azure</th>
</tr>
</thead>
<tbody>
<tr><td>Create Container</td><td>573</td><td style="background-color: green;">279</td></tr>
<tr><td>Upload 6Kb file</td><td>99</td><td style="background-color: green;">55</td></tr>
<tr><td>List Blobs (1)</td><td style="background-color: green;">41</td><td>103</td></tr>
<tr><td>Delete Blob</td><td>55</td><td style="background-color: green;">45</td></tr>
<tr><td>Delete Container</td><td>221</td><td style="background-color: green;">38</td></tr>
</tbody>
</table>
All times are in milliseconds. I’ve got to admit; I was expecting a more even spread here. Azure is significantly faster creating and deleting containers and uploading the file. It is also faster at deleting a blob, but the difference is insignificant. S3 wins significantly listing blobs.
<br/><br/>
Not covered in this post: Both APIs also have the Begin/End style of async operations and Azure has the bonus of async operations based on the async/await pattern, I may do another post on that in the future.
<br/><br/>
<b>TL;DR;</b> Azure's API is in my opinion a better abstraction and it's faster for most operations.
Alanhttp://www.blogger.com/profile/11461540001324768384noreply@blogger.com0tag:blogger.com,1999:blog-4271467236181995002.post-25640451946117470032014-07-18T07:05:00.000-07:002014-07-18T07:05:21.447-07:00Upgraded to Azure Storage Emulator 3.2, where have all my tables gone?In an attempt to solve a 400 error accessing tables on the Azure Storage Emulator 3.0 today, I upgraded to 3.2 using the Web Platform Installer. This resulted in a kind of good news, bad news situation.<br />
<br />
<b>Good -</b> The error stopped happening.<br />
<b>Bad -</b> Where the f**k have all my tables gone!<br />
<br />
I'll be buggered if I'm recreating and repopulating them all so I went hunting. I managed to find the emulator database is in C:\Users\<username>\. In that directory you'll find mdf files called WAStorageEmulatorDb**.mdf where ** is the version number. I had ones ending in 22, 30, 32. Each will be accompanied by a _log file.<br />
<br />
I loaded them up in Linqpad and the schemas looked the same, so for a punt I just renamed the files ending in 32 to something else and the renamed the files ending in 30 to 32.<br />
<br />
Start up the emulator and everything is present again. That saved me a few hours!Alanhttp://www.blogger.com/profile/11461540001324768384noreply@blogger.com0tag:blogger.com,1999:blog-4271467236181995002.post-56925670347232536752014-03-19T19:00:00.000-07:002014-03-20T05:45:27.510-07:00Copying records between tables in different Azure accountsToday I had to quickly throw up a new instance of a customer's service in Hong Kong as they've got a big demo event coming up and want things to be as quick as possible. Now I haven't quite got things to a point where I can have multiple geographically distributed instances of the service all happily talking to each other and sharing data so this instance is it's own little island, a completely separate instance to the main one in the EU.<br />
<br />
Deploying the new Cloud Service was easy.<br />
Taking a backup of the EU database and deploying it to Hong Kong was also easy.<br />
<br />
However, recently I've been making increasing use of Azure Table storage for trivial data storage scenarios where the data isn't relational and the data will want to be shared amongst multiple instances eventually without having to wait for a database sync. It was at this point that I realised, I have no way of copying data from one storage account to another.<br />
Time to correct that!<br />
<pre class="brush: csharp">public void Transfer<T>(Microsoft.WindowsAzure.Storage.CloudStorageAccount fromAcc, Microsoft.WindowsAzure.Storage.CloudStorageAccount toAcc, string table, Expression<Func<T,bool>> expr) where T: TableServiceEntity {
var fromTC = fromAcc.CreateCloudTableClient();
var fromT = fromTC.GetTableReference(table);
var toTC = toAcc.CreateCloudTableClient();
var toT = toTC.GetTableReference(table);
toT.CreateIfNotExists();
var fromContext = fromTC.GetTableServiceContext();
var toContext = toTC.GetTableServiceContext();
var fromData = fromContext.CreateQuery<T>(table).Where(expr);
foreach (var item in fromData)
{
toContext.AttachTo(table,item);
toContext.UpdateObject(item);
}
toContext.SaveChangesWithRetries(SaveChangesOptions.ReplaceOnUpdate);
}
</pre>
<br />
This Transfer method takes in a from account and a to account and the name of the table.<br />
The last parameter is an expression for the where clause. This is for scenarios where the same table contains multiple types of objects and you just want to query out the ones of a particular type for transfer using whatever clause is appropriate.<br />
<br />
T must derive from TableServiceEntity and be the type of the object from which the record originated, or one that is similarly shaped.<br />
<br />
The method is quite straight forward, it just fires up 2 table clients, gets a reference to the table specified by the table parameter, creates it on the receiving end if it doesn't exist (I think it's safe to assume that it already exists at the source end), queries out the data, then attaches it to the destination context and saves changes.<br />
This upserts all of the data in to the source table.<br />
<br />
Usage is simple:<br />
<pre class="brush: csharp">var fromAccount = Microsoft.WindowsAzure.Storage.CloudStorageAccount.Parse("DefaultEndpointsProtocol=https;AccountName=accountname;AccountKey=accesskey");
var toAccount = Microsoft.WindowsAzure.Storage.CloudStorageAccount.Parse("DefaultEndpointsProtocol=https;AccountName=accountname;AccountKey=accesskey");
Transfer<MyProject.MyType1>(fromAccount, toAccount, "sharedtable", p=>p.PartitionKey == "Type1");
Transfer<MyProject.MyType2>(fromAccount, toAccount, "sharedtable", p=>p.PartitionKey == "Type2");
Transfer<MyProject.SomeOtherType>(fromAccount, toAccount, "someothertype", p=>p.PartitionKey != "");
</pre>
Put all this together in Linqpad and you've got a simple way to transfer records between accounts on an ad hoc basis. As expected, it works with the Storage Emulator so you can use it to clone the contents of a production account down to your local dev machine and vice versa.Alanhttp://www.blogger.com/profile/11461540001324768384noreply@blogger.com0tag:blogger.com,1999:blog-4271467236181995002.post-75024653842666268372013-12-21T07:12:00.000-08:002013-12-21T07:12:28.445-08:00JSON vs XML: Challenging my assumptionsI was recently (today actually) working on optimising a particular section of my project. This section is basically a Q & A that uses a piece of server-generated XML which is placed in to a Razor view where some Javascript works with it to generate the input fields for the user.<br />
<br />
But XML is so 2010 right? If I swapped the XML for some JSON the payload would be smaller, it would generate faster, the javascript would be able to work with it faster, right?<br />
<br />
Let's test those assumptions one by one shall we?<br />
<br />
To do that, I duplicated the method that populates an object and serializes it to XML and changed the serialization to use JSON instead and ran them both at the same time so I could compare them side by side.<br />
<h3>
Payload size</h3>
I added a call to Encoding.ASCII.GetByteCount() around the serialized JSON and XML results and dumped this to the Trace windows. The results are below<br />
<br />
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse; width: 535px;">
<colgroup><col style="mso-width-alt: 3181; mso-width-source: userset; width: 65pt;" width="87"></col>
<col span="7" style="width: 48pt;" width="64"></col>
</colgroup><tbody>
<tr height="20" style="height: 15.0pt;">
<td height="20" style="background-color: #5b9bd5; background-position: initial initial; background-repeat: initial initial; border-left-color: rgb(91, 155, 213); border-left-width: 0.5pt; border-style: solid none none solid; border-top-color: rgb(91, 155, 213); border-top-width: 0.5pt; color: white; font-family: Calibri; font-size: 11pt; font-weight: 700; height: 15pt; text-underline-style: none; width: 65pt;" width="87">Type</td>
<td class="xl65" style="background-color: #5b9bd5; background-position: initial initial; background-repeat: initial initial; border-style: solid none none; border-top-color: rgb(91, 155, 213); border-top-width: 0.5pt; color: white; font-family: Calibri; font-size: 11pt; font-weight: 700; text-underline-style: none; width: 48pt;" width="64">1</td>
<td class="xl65" style="background-color: #5b9bd5; background-position: initial initial; background-repeat: initial initial; border-style: solid none none; border-top-color: rgb(91, 155, 213); border-top-width: 0.5pt; color: white; font-family: Calibri; font-size: 11pt; font-weight: 700; text-underline-style: none; width: 48pt;" width="64">2</td>
<td class="xl65" style="background-color: #5b9bd5; background-position: initial initial; background-repeat: initial initial; border-style: solid none none; border-top-color: rgb(91, 155, 213); border-top-width: 0.5pt; color: white; font-family: Calibri; font-size: 11pt; font-weight: 700; text-underline-style: none; width: 48pt;" width="64">3</td>
<td class="xl65" style="background-color: #5b9bd5; background-position: initial initial; background-repeat: initial initial; border-style: solid none none; border-top-color: rgb(91, 155, 213); border-top-width: 0.5pt; color: white; font-family: Calibri; font-size: 11pt; font-weight: 700; text-underline-style: none; width: 48pt;" width="64">4</td>
<td class="xl65" style="background-color: #5b9bd5; background-position: initial initial; background-repeat: initial initial; border-style: solid none none; border-top-color: rgb(91, 155, 213); border-top-width: 0.5pt; color: white; font-family: Calibri; font-size: 11pt; font-weight: 700; text-underline-style: none; width: 48pt;" width="64">5</td>
<td class="xl65" style="background-color: #5b9bd5; background-position: initial initial; background-repeat: initial initial; border-style: solid none none; border-top-color: rgb(91, 155, 213); border-top-width: 0.5pt; color: white; font-family: Calibri; font-size: 11pt; font-weight: 700; text-underline-style: none; width: 48pt;" width="64">6</td>
<td class="xl65" style="background-color: #5b9bd5; background-position: initial initial; background-repeat: initial initial; border-right-color: rgb(91, 155, 213); border-right-width: 0.5pt; border-style: solid solid none none; border-top-color: rgb(91, 155, 213); border-top-width: 0.5pt; color: white; font-family: Calibri; font-size: 11pt; font-weight: 700; text-underline-style: none; width: 48pt;" width="64">7</td>
</tr>
<tr height="20" style="height: 15.0pt;">
<td height="20" style="border-left-color: rgb(91, 155, 213); border-left-width: 0.5pt; border-style: solid none none solid; border-top-color: rgb(91, 155, 213); border-top-width: 0.5pt; font-family: Calibri; font-size: 11pt; height: 15pt; text-underline-style: none;">XML</td>
<td align="right" style="border-style: solid none none; border-top-color: rgb(91, 155, 213); border-top-width: 0.5pt; font-family: Calibri; font-size: 11pt; text-underline-style: none;">3338</td>
<td align="right" style="border-style: solid none none; border-top-color: rgb(91, 155, 213); border-top-width: 0.5pt; font-family: Calibri; font-size: 11pt; text-underline-style: none;">4133</td>
<td align="right" style="border-style: solid none none; border-top-color: rgb(91, 155, 213); border-top-width: 0.5pt; font-family: Calibri; font-size: 11pt; text-underline-style: none;">3487</td>
<td align="right" style="border-style: solid none none; border-top-color: rgb(91, 155, 213); border-top-width: 0.5pt; font-family: Calibri; font-size: 11pt; text-underline-style: none;">3255</td>
<td align="right" style="border-style: solid none none; border-top-color: rgb(91, 155, 213); border-top-width: 0.5pt; font-family: Calibri; font-size: 11pt; text-underline-style: none;">3465</td>
<td align="right" style="border-style: solid none none; border-top-color: rgb(91, 155, 213); border-top-width: 0.5pt; font-family: Calibri; font-size: 11pt; text-underline-style: none;">3194</td>
<td align="right" style="border-right-color: rgb(91, 155, 213); border-right-width: 0.5pt; border-style: solid solid none none; border-top-color: rgb(91, 155, 213); border-top-width: 0.5pt; font-family: Calibri; font-size: 11pt; text-underline-style: none;">1138</td>
</tr>
<tr height="20" style="height: 15.0pt;">
<td height="20" style="border-bottom-color: rgb(91, 155, 213); border-bottom-width: 0.5pt; border-left-color: rgb(91, 155, 213); border-left-width: 0.5pt; border-style: solid none solid solid; border-top-color: rgb(91, 155, 213); border-top-width: 0.5pt; font-family: Calibri; font-size: 11pt; height: 15pt; text-underline-style: none;">Json</td>
<td align="right" style="border-bottom-color: rgb(91, 155, 213); border-bottom-width: 0.5pt; border-style: solid none; border-top-color: rgb(91, 155, 213); border-top-width: 0.5pt; font-family: Calibri; font-size: 11pt; text-underline-style: none;">2266</td>
<td align="right" style="border-bottom-color: rgb(91, 155, 213); border-bottom-width: 0.5pt; border-style: solid none; border-top-color: rgb(91, 155, 213); border-top-width: 0.5pt; font-family: Calibri; font-size: 11pt; text-underline-style: none;">2717</td>
<td align="right" style="border-bottom-color: rgb(91, 155, 213); border-bottom-width: 0.5pt; border-style: solid none; border-top-color: rgb(91, 155, 213); border-top-width: 0.5pt; font-family: Calibri; font-size: 11pt; text-underline-style: none;">2292</td>
<td align="right" style="border-bottom-color: rgb(91, 155, 213); border-bottom-width: 0.5pt; border-style: solid none; border-top-color: rgb(91, 155, 213); border-top-width: 0.5pt; font-family: Calibri; font-size: 11pt; text-underline-style: none;">2110</td>
<td align="right" style="border-bottom-color: rgb(91, 155, 213); border-bottom-width: 0.5pt; border-style: solid none; border-top-color: rgb(91, 155, 213); border-top-width: 0.5pt; font-family: Calibri; font-size: 11pt; text-underline-style: none;">2234</td>
<td align="right" style="border-bottom-color: rgb(91, 155, 213); border-bottom-width: 0.5pt; border-style: solid none; border-top-color: rgb(91, 155, 213); border-top-width: 0.5pt; font-family: Calibri; font-size: 11pt; text-underline-style: none;">2061</td>
<td align="right" style="border-bottom-color: rgb(91, 155, 213); border-bottom-width: 0.5pt; border-right-color: rgb(91, 155, 213); border-right-width: 0.5pt; border-style: solid solid solid none; border-top-color: rgb(91, 155, 213); border-top-width: 0.5pt; font-family: Calibri; font-size: 11pt; text-underline-style: none;">621</td>
</tr>
</tbody></table>
<br />
This is in bytes, so the real world difference between the XML and the JSON in this case is at most a single kilobyte. This isn't always the case as I've swapped out XML for JSON in the past where it has been many times smaller.<br />
<h3>
Generating the output</h3>
Next job is to test how quickly the output is generated. Keep in mind that the method I'm testing includes other data access logic so these timings are not purely serialization. But as both will be doing the same work, it won't hurt to have that included as well to see how each performs in the real world.<br />
<br />
My initial results looked rather promising, see table below:<br />
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse; width: 589px;">
<colgroup><col style="mso-width-alt: 2816; mso-width-source: userset; width: 58pt;" width="77"></col>
<col span="8" style="width: 48pt;" width="64"></col>
</colgroup><tbody>
<tr height="20" style="height: 15.0pt;">
<td height="20" style="background-color: #5b9bd5; background-position: initial initial; background-repeat: initial initial; border-left-color: rgb(91, 155, 213); border-left-width: 0.5pt; border-style: solid none none solid; border-top-color: rgb(91, 155, 213); border-top-width: 0.5pt; color: white; font-family: Calibri; font-size: 11pt; font-weight: 700; height: 15pt; text-underline-style: none; width: 58pt;" width="77">Type</td>
<td class="xl63" style="background-color: #5b9bd5; background-position: initial initial; background-repeat: initial initial; border-style: solid none none; border-top-color: rgb(91, 155, 213); border-top-width: 0.5pt; color: white; font-family: Calibri; font-size: 11pt; font-weight: 700; text-underline-style: none; width: 48pt;" width="64">1</td>
<td class="xl63" style="background-color: #5b9bd5; background-position: initial initial; background-repeat: initial initial; border-style: solid none none; border-top-color: rgb(91, 155, 213); border-top-width: 0.5pt; color: white; font-family: Calibri; font-size: 11pt; font-weight: 700; text-underline-style: none; width: 48pt;" width="64">2</td>
<td class="xl63" style="background-color: #5b9bd5; background-position: initial initial; background-repeat: initial initial; border-style: solid none none; border-top-color: rgb(91, 155, 213); border-top-width: 0.5pt; color: white; font-family: Calibri; font-size: 11pt; font-weight: 700; text-underline-style: none; width: 48pt;" width="64">3</td>
<td class="xl63" style="background-color: #5b9bd5; background-position: initial initial; background-repeat: initial initial; border-style: solid none none; border-top-color: rgb(91, 155, 213); border-top-width: 0.5pt; color: white; font-family: Calibri; font-size: 11pt; font-weight: 700; text-underline-style: none; width: 48pt;" width="64">4</td>
<td class="xl63" style="background-color: #5b9bd5; background-position: initial initial; background-repeat: initial initial; border-style: solid none none; border-top-color: rgb(91, 155, 213); border-top-width: 0.5pt; color: white; font-family: Calibri; font-size: 11pt; font-weight: 700; text-underline-style: none; width: 48pt;" width="64">5</td>
<td class="xl63" style="background-color: #5b9bd5; background-position: initial initial; background-repeat: initial initial; border-style: solid none none; border-top-color: rgb(91, 155, 213); border-top-width: 0.5pt; color: white; font-family: Calibri; font-size: 11pt; font-weight: 700; text-underline-style: none; width: 48pt;" width="64">6</td>
<td class="xl63" style="background-color: #5b9bd5; background-position: initial initial; background-repeat: initial initial; border-style: solid none none; border-top-color: rgb(91, 155, 213); border-top-width: 0.5pt; color: white; font-family: Calibri; font-size: 11pt; font-weight: 700; text-underline-style: none; width: 48pt;" width="64">7</td>
<td style="background-color: #5b9bd5; background-position: initial initial; background-repeat: initial initial; border-right-color: rgb(91, 155, 213); border-right-width: 0.5pt; border-style: solid solid none none; border-top-color: rgb(91, 155, 213); border-top-width: 0.5pt; color: white; font-family: Calibri; font-size: 11pt; font-weight: 700; text-underline-style: none; width: 48pt;" width="64">Avg</td>
</tr>
<tr height="20" style="height: 15.0pt;">
<td height="20" style="border-left-color: rgb(91, 155, 213); border-left-width: 0.5pt; border-style: solid none none solid; border-top-color: rgb(91, 155, 213); border-top-width: 0.5pt; font-family: Calibri; font-size: 11pt; height: 15pt; text-underline-style: none;">XML</td>
<td align="right" style="border-style: solid none none; border-top-color: rgb(91, 155, 213); border-top-width: 0.5pt; font-family: Calibri; font-size: 11pt; text-underline-style: none;">87.4</td>
<td align="right" style="border-style: solid none none; border-top-color: rgb(91, 155, 213); border-top-width: 0.5pt; font-family: Calibri; font-size: 11pt; text-underline-style: none;">31.9</td>
<td align="right" style="border-style: solid none none; border-top-color: rgb(91, 155, 213); border-top-width: 0.5pt; font-family: Calibri; font-size: 11pt; text-underline-style: none;">33.6</td>
<td align="right" style="border-style: solid none none; border-top-color: rgb(91, 155, 213); border-top-width: 0.5pt; font-family: Calibri; font-size: 11pt; text-underline-style: none;">29.6</td>
<td align="right" style="border-style: solid none none; border-top-color: rgb(91, 155, 213); border-top-width: 0.5pt; font-family: Calibri; font-size: 11pt; text-underline-style: none;">31.6</td>
<td align="right" style="border-style: solid none none; border-top-color: rgb(91, 155, 213); border-top-width: 0.5pt; font-family: Calibri; font-size: 11pt; text-underline-style: none;">29.6</td>
<td align="right" style="border-style: solid none none; border-top-color: rgb(91, 155, 213); border-top-width: 0.5pt; font-family: Calibri; font-size: 11pt; text-underline-style: none;">45.9</td>
<td align="right" style="border-right-color: rgb(91, 155, 213); border-right-width: 0.5pt; border-style: solid solid none none; border-top-color: rgb(91, 155, 213); border-top-width: 0.5pt; font-family: Calibri; font-size: 11pt; text-underline-style: none;">41.37143</td>
</tr>
<tr height="20" style="height: 15.0pt;">
<td height="20" style="border-bottom-color: rgb(91, 155, 213); border-bottom-width: 0.5pt; border-left-color: rgb(91, 155, 213); border-left-width: 0.5pt; border-style: solid none solid solid; border-top-color: rgb(91, 155, 213); border-top-width: 0.5pt; font-family: Calibri; font-size: 11pt; height: 15pt; text-underline-style: none;">Json</td>
<td align="right" style="border-bottom-color: rgb(91, 155, 213); border-bottom-width: 0.5pt; border-style: solid none; border-top-color: rgb(91, 155, 213); border-top-width: 0.5pt; font-family: Calibri; font-size: 11pt; text-underline-style: none;">38.9</td>
<td align="right" style="border-bottom-color: rgb(91, 155, 213); border-bottom-width: 0.5pt; border-style: solid none; border-top-color: rgb(91, 155, 213); border-top-width: 0.5pt; font-family: Calibri; font-size: 11pt; text-underline-style: none;">20.7</td>
<td align="right" style="border-bottom-color: rgb(91, 155, 213); border-bottom-width: 0.5pt; border-style: solid none; border-top-color: rgb(91, 155, 213); border-top-width: 0.5pt; font-family: Calibri; font-size: 11pt; text-underline-style: none;">27.8</td>
<td align="right" style="border-bottom-color: rgb(91, 155, 213); border-bottom-width: 0.5pt; border-style: solid none; border-top-color: rgb(91, 155, 213); border-top-width: 0.5pt; font-family: Calibri; font-size: 11pt; text-underline-style: none;">21.2</td>
<td align="right" style="border-bottom-color: rgb(91, 155, 213); border-bottom-width: 0.5pt; border-style: solid none; border-top-color: rgb(91, 155, 213); border-top-width: 0.5pt; font-family: Calibri; font-size: 11pt; text-underline-style: none;">23.8</td>
<td align="right" style="border-bottom-color: rgb(91, 155, 213); border-bottom-width: 0.5pt; border-style: solid none; border-top-color: rgb(91, 155, 213); border-top-width: 0.5pt; font-family: Calibri; font-size: 11pt; text-underline-style: none;">24.4</td>
<td align="right" style="border-bottom-color: rgb(91, 155, 213); border-bottom-width: 0.5pt; border-style: solid none; border-top-color: rgb(91, 155, 213); border-top-width: 0.5pt; font-family: Calibri; font-size: 11pt; text-underline-style: none;">23.5</td>
<td align="right" style="border-bottom-color: rgb(91, 155, 213); border-bottom-width: 0.5pt; border-right-color: rgb(91, 155, 213); border-right-width: 0.5pt; border-style: solid solid solid none; border-top-color: rgb(91, 155, 213); border-top-width: 0.5pt; font-family: Calibri; font-size: 11pt; text-underline-style: none;">25.75714</td>
</tr>
</tbody></table>
<br />
<div>
Across the whole Q & A session, JSON comes out not far off 50% faster. So I should definitely swap it right?</div>
<div>
<br /></div>
<div>
But wait, the method that we're capturing also includes some data access. The JSON method is running second, could it be benefiting from the XML method's work?</div>
<div>
To test this out, I swapped them round so the JSON method was first. Below is the aggregated table from both runs.</div>
<div>
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse; width: 599px;">
<colgroup><col style="mso-width-alt: 3181; mso-width-source: userset; width: 65pt;" width="87"></col>
<col span="8" style="width: 48pt;" width="64"></col>
</colgroup><tbody>
<tr height="20" style="height: 15.0pt;">
<td class="xl68" height="20" style="background-color: #5b9bd5; background-position: initial initial; background-repeat: initial initial; border-color: windowtext rgb(91, 155, 213) windowtext windowtext; border-style: solid; border-width: 0.5pt; color: white; font-family: Calibri; font-size: 11pt; font-weight: 700; height: 15pt; text-underline-style: none; width: 65pt;" width="87">Type</td>
<td class="xl69" style="background-color: #5b9bd5; background-position: initial initial; background-repeat: initial initial; border-right-color: windowtext; border-right-width: 0.5pt; border-style: solid solid none none; border-top-color: windowtext; border-top-width: 0.5pt; color: white; font-family: Calibri; font-size: 11pt; font-weight: 700; text-underline-style: none; width: 48pt;" width="64">1</td>
<td class="xl70" style="background-color: #5b9bd5; background-position: initial initial; background-repeat: initial initial; border: 0.5pt solid windowtext; color: white; font-family: Calibri; font-size: 11pt; font-weight: 700; text-underline-style: none; width: 48pt;" width="64">2</td>
<td class="xl70" style="background-color: #5b9bd5; background-position: initial initial; background-repeat: initial initial; border: 0.5pt solid windowtext; color: white; font-family: Calibri; font-size: 11pt; font-weight: 700; text-underline-style: none; width: 48pt;" width="64">3</td>
<td class="xl70" style="background-color: #5b9bd5; background-position: initial initial; background-repeat: initial initial; border: 0.5pt solid windowtext; color: white; font-family: Calibri; font-size: 11pt; font-weight: 700; text-underline-style: none; width: 48pt;" width="64">4</td>
<td class="xl70" style="background-color: #5b9bd5; background-position: initial initial; background-repeat: initial initial; border: 0.5pt solid windowtext; color: white; font-family: Calibri; font-size: 11pt; font-weight: 700; text-underline-style: none; width: 48pt;" width="64">5</td>
<td class="xl70" style="background-color: #5b9bd5; background-position: initial initial; background-repeat: initial initial; border: 0.5pt solid windowtext; color: white; font-family: Calibri; font-size: 11pt; font-weight: 700; text-underline-style: none; width: 48pt;" width="64">6</td>
<td class="xl70" style="background-color: #5b9bd5; background-position: initial initial; background-repeat: initial initial; border: 0.5pt solid windowtext; color: white; font-family: Calibri; font-size: 11pt; font-weight: 700; text-underline-style: none; width: 48pt;" width="64">7</td>
<td class="xl71" style="background-color: #5b9bd5; background-position: initial initial; background-repeat: initial initial; border: 0.5pt solid windowtext; color: white; font-family: Calibri; font-size: 11pt; font-weight: 700; text-underline-style: none; width: 48pt;" width="64">Avg</td>
</tr>
<tr height="20" style="height: 15.0pt;">
<td class="xl66" height="20" style="border: 0.5pt solid windowtext; font-family: Calibri; font-size: 11pt; height: 15pt; text-underline-style: none;">XML
First</td>
<td align="right" class="xl65" style="border: 0.5pt solid windowtext; font-family: Calibri; font-size: 11pt; text-underline-style: none;">87.4</td>
<td align="right" class="xl65" style="border: 0.5pt solid windowtext; font-family: Calibri; font-size: 11pt; text-underline-style: none;">31.9</td>
<td align="right" class="xl65" style="border: 0.5pt solid windowtext; font-family: Calibri; font-size: 11pt; text-underline-style: none;">33.6</td>
<td align="right" class="xl65" style="border: 0.5pt solid windowtext; font-family: Calibri; font-size: 11pt; text-underline-style: none;">29.6</td>
<td align="right" class="xl65" style="border: 0.5pt solid windowtext; font-family: Calibri; font-size: 11pt; text-underline-style: none;">31.6</td>
<td align="right" class="xl65" style="border: 0.5pt solid windowtext; font-family: Calibri; font-size: 11pt; text-underline-style: none;">29.6</td>
<td align="right" class="xl65" style="border: 0.5pt solid windowtext; font-family: Calibri; font-size: 11pt; text-underline-style: none;">45.9</td>
<td align="right" class="xl67" style="border: 0.5pt solid windowtext; font-family: Calibri; font-size: 11pt; text-underline-style: none;">41.37143</td>
</tr>
<tr height="20" style="height: 15.0pt;">
<td class="xl66" height="20" style="border: 0.5pt solid windowtext; font-family: Calibri; font-size: 11pt; height: 15pt; text-underline-style: none;">XML
Second</td>
<td align="right" class="xl65" style="border: 0.5pt solid windowtext; font-family: Calibri; font-size: 11pt; text-underline-style: none;">39.2</td>
<td align="right" class="xl65" style="border: 0.5pt solid windowtext; font-family: Calibri; font-size: 11pt; text-underline-style: none;">25.3</td>
<td align="right" class="xl65" style="border: 0.5pt solid windowtext; font-family: Calibri; font-size: 11pt; text-underline-style: none;">26.7</td>
<td align="right" class="xl65" style="border: 0.5pt solid windowtext; font-family: Calibri; font-size: 11pt; text-underline-style: none;">24.3</td>
<td align="right" class="xl65" style="border: 0.5pt solid windowtext; font-family: Calibri; font-size: 11pt; text-underline-style: none;">26.5</td>
<td align="right" class="xl65" style="border: 0.5pt solid windowtext; font-family: Calibri; font-size: 11pt; text-underline-style: none;">37.1</td>
<td align="right" class="xl65" style="border: 0.5pt solid windowtext; font-family: Calibri; font-size: 11pt; text-underline-style: none;">21.6</td>
<td align="right" class="xl67" style="border: 0.5pt solid windowtext; font-family: Calibri; font-size: 11pt; text-underline-style: none;">28.67143</td>
</tr>
<tr height="20" style="height: 15.0pt;">
<td height="20" style="border-left-color: windowtext; border-left-width: 0.5pt; border-style: solid none none solid; border-top-color: rgb(91, 155, 213); border-top-width: 0.5pt; font-family: Calibri; font-size: 11pt; height: 15pt; text-underline-style: none;">JSON First</td>
<td align="right" class="xl65" style="border: 0.5pt solid windowtext; font-family: Calibri; font-size: 11pt; text-underline-style: none;">83.5</td>
<td align="right" class="xl65" style="border: 0.5pt solid windowtext; font-family: Calibri; font-size: 11pt; text-underline-style: none;">28.9</td>
<td align="right" class="xl65" style="border: 0.5pt solid windowtext; font-family: Calibri; font-size: 11pt; text-underline-style: none;">30.4</td>
<td align="right" class="xl65" style="border: 0.5pt solid windowtext; font-family: Calibri; font-size: 11pt; text-underline-style: none;">30.9</td>
<td align="right" class="xl65" style="border: 0.5pt solid windowtext; font-family: Calibri; font-size: 11pt; text-underline-style: none;">32.7</td>
<td align="right" class="xl65" style="border: 0.5pt solid windowtext; font-family: Calibri; font-size: 11pt; text-underline-style: none;">46.3</td>
<td align="right" class="xl65" style="border: 0.5pt solid windowtext; font-family: Calibri; font-size: 11pt; text-underline-style: none;">47.7</td>
<td align="right" class="xl67" style="border: 0.5pt solid windowtext; font-family: Calibri; font-size: 11pt; text-underline-style: none;">42.91429</td>
</tr>
<tr height="20" style="height: 15.0pt;">
<td class="xl72" height="20" style="border: 0.5pt solid windowtext; font-family: Calibri; font-size: 11pt; height: 15pt; text-underline-style: none;">JSON Second</td>
<td align="right" class="xl73" style="border: 0.5pt solid windowtext; font-family: Calibri; font-size: 11pt; text-underline-style: none;">38.9</td>
<td align="right" class="xl73" style="border: 0.5pt solid windowtext; font-family: Calibri; font-size: 11pt; text-underline-style: none;">20.7</td>
<td align="right" class="xl73" style="border: 0.5pt solid windowtext; font-family: Calibri; font-size: 11pt; text-underline-style: none;">27.8</td>
<td align="right" class="xl73" style="border: 0.5pt solid windowtext; font-family: Calibri; font-size: 11pt; text-underline-style: none;">21.2</td>
<td align="right" class="xl73" style="border: 0.5pt solid windowtext; font-family: Calibri; font-size: 11pt; text-underline-style: none;">23.8</td>
<td align="right" class="xl73" style="border: 0.5pt solid windowtext; font-family: Calibri; font-size: 11pt; text-underline-style: none;">24.4</td>
<td align="right" class="xl73" style="border: 0.5pt solid windowtext; font-family: Calibri; font-size: 11pt; text-underline-style: none;">23.5</td>
<td align="right" class="xl74" style="border: 0.5pt solid windowtext; font-family: Calibri; font-size: 11pt; text-underline-style: none;">25.75714</td>
</tr>
</tbody></table>
</div>
<div>
As you can see from the average, I was right to be suspicious. The first method to run is always slower than the second due to things like EF caching records. In reality, the difference between the 2 is so small that no one is ever going to notice.</div>
<div>
<br /></div>
<div>
These two simple benchmarks which took me about 10 minutes to complete shows that to substitute the current XML payload for JSON would be a micro-optimisation. I've got much bigger fish to fry than this so it is not worth my time to do this work.</div>
<div>
Will swapping out make things faster? Absolutely. Will anyone notice? Not remotely.</div>
<div>
<br /></div>
<div>
You'll notice I didn't perform the third test of measuring how the Javascript handled JSON as opposed to XML. I'm sure that would probably show a modest improvement as well but based on these numbers, my best course of action is to not waste any more time on this path.</div>
Alanhttp://www.blogger.com/profile/11461540001324768384noreply@blogger.com0tag:blogger.com,1999:blog-4271467236181995002.post-81852248220441610442013-12-03T11:56:00.000-08:002013-12-03T11:56:02.530-08:00Windows 8.1 - The good, the bad, and the missing.Little over a year after Windows 8, it gets an upgrade with the release of 8.1. Is this just a Service Pack by any other name or does it provide real improvement over it's predecessor that makes it worthy being a numbered release?<br />
<br />
<b>First, the good.</b><br />
<br />
Real effort has been made in 8.1 to reduce the jarring effect of moving between Windows' Classic and Modern UIs. Here are the main improvements:<br />
<br />
<ul>
<li>Search has been altered so that when you search, the search window and initial results appear as a flyout on the right side of the screen rather than taking over the entire screen.</li>
<li>The desktop background is now used as the background of the start screen. I though this sounded a lot like a gimmick when I first heard about it, but it genuinely does reduce the cognitive dissonance experienced when moving between the two UIs.</li>
<li>The Start button is back. I don't personally care as I've gotten used to it not being there, but hopefully it's return will go some way towards appeasing the masses. </li>
<li>Better help on install. When you first installed Windows 8, it was very much a "go and figure it out" experience. Beyond the brief tutorial that appeared when starting for the first time and which I'm fairly sure no one ever paid any attention to, you were on your own when it came to learning the new UI. Windows 8.1 adds in some nice big helper blocks that point out things like the hot corners and how you can use drag/swipe to perform certain actions. If you've been using Windows 8 already, these will probably be a bit annoying as it's just telling you things you already now, but to new users they will go a long way in improving the transition to Windows 8 Modern UI.</li>
<li>More freedom when using multiple applications. You can now pick the amount of space each app takes up on your screen and have more than 2 of them. This is really such a basic feature that it should have been there from the beginning, better late than never I suppose.</li>
</ul>
<br />
<br />
I haven't played with it much yet, but I really like the new aggregated search view which brings up results from lots of difference sources all in a single very usable view.<br />
The Windows store was beyond poor in Windows 8. It was fine if you were looking for something specific but it was absolutely useless for discovering apps. This has been remedied in Windows 8.1, I'm hoping this will allow existing developers to make more money out of their apps and encourage more developers to get writing Windows 8 apps.<br />
<br />
There are a load of other features added, such as 3D printing support which isn't that big a deal currently but I think Microsoft have made a sly move in baking in OS level support for what is a growing technology.<br />
<br />
<b>The bad</b><br />
The start button is back. Wait, didn't I just do this one? Yes I did, but in a prime example of "you can't please everyone all of the time", in the last year of using Windows 8 I've gotten used to having that extra slot on the taskbar and haven't remotely missed the venerable start button that used to occupy that number one slot. I completely agree with Microsoft's move of bringing it back, but they could've at least made it an option to not have it.<br />
<br />
That's pretty much it for the bad, there really isn't that much to moan about in this release. It genuinely seems like Microsoft have taken the time to listen to users and fix the problems that have really plagued them. Some would say they should have listened to users during the Beta and Preview periods, and they're probably right. Hopefully, Microsoft have been a little humbled by their grand UI plans not being embraced as they had hoped. The concessions made in this release certainly seem to suggest that is the case.<br />
<br />
<b>The missing</b><br />
WEI is gone! I'd really love to know the argument behind getting rid of the Windows Experience Index. It was a great tool that allowed your average user to easily see where the bottleneck was in their system without having to understand the various benchmarks or install software on their computer to perform tests against those benchmarks. I genuinely don't understand why this has been removed, perhaps Microsoft will come out with an explanation in the coming months.<br />
<br />
<b>Experience on my Dell Mini 9</b><br />
I have a little Dell Mini 9 which I decided to use a tester for how Windows 8 ran on low powered hardware. For reference, it has a 1.6Ghz Atom, 1Gb of RAM, and a 14Gb hard disk. Windows 8 ran okay on this at first but suffered the inevitable slow down as time wore on to the point where it was pretty much unusable for anything serious. IE10 was not even worth starting. Also, I only had a couple of Gb change on the 14Gb hard disk.<br />
After doing a fresh install of 8.1, I had 5.2Gb free space on the hard disk, which astonished me and this little device suddenly seems a lot nippier than it was with a fresh Windows 8 install. IE11 is a lot faster than it's predecessor and I now use the Mini for email and Campfire on a daily basis.<br />
Time will tell if it keeps this speed boost or if it falls away with continued use, but you can see that some effort has gone in to improving performance for this release.<br />
<br />
<br />
So is it worthy of being a .1 release? Yes, I think it is. There are lots of small improvements here, if it only had half of them it would probably be SP1 in my eyes, but there are enough improvements and extra features here that make this more than a Service Pack.Alanhttp://www.blogger.com/profile/11461540001324768384noreply@blogger.com0tag:blogger.com,1999:blog-4271467236181995002.post-40850962022705321832013-07-18T14:18:00.000-07:002013-08-05T13:19:38.829-07:00Slimming down your JSON<div class="MsoNormal">
Newtonsoft JSON.Net is the JSON serialization library that
is so good, Microsoft use it over their own.</div>
<div class="MsoNormal">
<o:p></o:p></div>
<div class="MsoNormal">
While converting an existing project from Linq to SQL to EF5
Code First, I hit an issue with the Unit Tests, which use test objects
serialized to XML files as the basis of the tests. This upset the XML Serializer as collections
in EF are ICollection<T> as opposed to Linq to SQL's EntitySet<T> – and the XML Serializer can’t handle interfaces.<o:p></o:p></div>
<br />
<div class="MsoNormal">
JSON.Net to the rescue - fortunately it can handle interfaces
so I chose to convert the test data to serialized Json instead. This was a
relatively trivial task, accomplished by the below Linqpad script (if you're not using <a href="http://www.linqpad.net/" target="_blank">Linqpad</a>, stop reading this blog and go and download it now).<br />
<br /></div>
<pre class="brush: csharp">void Main()
{
var filePath = @"C:\users\Alan\Downloads\";
var filename = "OrderItemTestData.json";
var deSerializationMode =2;
//1 for Json deserialize to object, 2 for cast (use for derived types of abstract classes where return type is the base class).
var update=false;
var result = LoadData<List<OrderItemBase>>(filePath + filename,deSerializationMode);
var updated = JsonConvert.SerializeObject(result, Newtonsoft.Json.Formatting.Indented,
new JsonSerializerSettings() { ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
TypeNameHandling = TypeNameHandling.All,
NullValueHandling= NullValueHandling.Ignore});
updated.Dump();
if(update){
File.WriteAllText(filePath + filename,updated);
}
}
</pre>
<div class="MsoNormal">
This simply takes the XML files, deserializes it to the
source objects (that’s all LoadData does), and then reserializes it to JSON.<o:p></o:p></div>
<div class="MsoNormal">
This solved my immediate problem but I was surprised to see
the size of the file, in some cases 3 times larger than the equivalent XML
file. But JSON is less characters, so how is that possible? A simple comparison
of the files shows the problem:<o:p></o:p></div>
<div class="MsoNormal">
<o:p></o:p></div>
<b>Excerpt from XML file: </b><br />
<pre class="brush: xml"> <Company>
<Id>201</Id>
<Name>Company 201</Name>
<Code>foo</Code>
<PlaquePrice>5</PlaquePrice>
<FreeSampleCount>10</FreeSampleCount>
<CompanyDispensers>
<CompanyDispenser>
<Dispenser>
<CompanyId>101</CompanyId>
</Dispenser>
</CompanyDispenser>
</CompanyDispensers>
</Company>
</pre>
<b>Excerpt from Json File:</b><br />
<pre class="brush: js"> "$type": "MyCompany.Model.Customer, MyCompany.Model",
"IsInternal": false,
"AvailableWorkFlows": {
"$type": "System.Collections.Generic.List`1[[System.String, mscorlib]], mscorlib",
"$values": [
"StandardWorkFlow"
]
},
"BillingAddress": null,
"BillingContact": null,
"PrimaryContact": null,
"ActiveUsers": {
"$type": "MyCompany.Model.User[], MyCompany.Model",
"$values": []
},
"Id": 201,
"Name": "Company 201",
"Code": "foo",
"RecordUpdate": "0001-01-01T00:00:00",
"RecordCreate": "0001-01-01T00:00:00",
"Orders": {
"$type": "System.Collections.Generic.List`1[[MyCompany.Model.Order, MyCompany.Model]], mscorlib",
"$values": []
},
"Products": {
"$type": "System.Collections.Generic.List`1[[MyCompany.Model.Product, MyCompany.Model]], mscorlib",
"$values": []
},
"Users": {
"$type": "System.Collections.Generic.List`1[[MyCompany.Model.User, MyCompany.Model]], mscorlib",
"$values": []
},
"IsValid": true
}
</pre>
<br />
<div class="MsoNormal">
In this case, the XML file comes out at 1617 characters, the
Json equivalent is 48833 characters<o:p></o:p></div>
<div class="MsoNormal">
<o:p></o:p></div>
The JSON.Net serializer has gone over the objects and
serialized every property, even ones that were null or default for their type and also empty collections.
This can be easily solved by setting the appropriate properties on the
serializer:<br />
<pre class="brush: csharp">new JsonSerializerSettings() { ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
TypeNameHandling = TypeNameHandling.All,
NullValueHandling= NullValueHandling.Ignore,
DefaultValueHandling = DefaultValueHandling.Ignore}
</pre>
Setting NullValueHandling and DefaultValueHandling to Ignore
solves the problem of properties that are at the default for their type, such
as datetimes, and null properties. However, this still leaves us with all of
the collections, which are initialised in the object’s constructor to new
List<T>.<br />
<div class="MsoNormal">
<o:p></o:p></div>
<div class="MsoNormal">
By default, Json.Net can’t be instructed to ignore these
empty lists, because ignoring them may not be the correct action in everyone’s
case. In ours it is, so we need to tell Json.Net that it is okay to ignore them
to reduce our file size.<o:p></o:p></div>
<div class="MsoNormal">
To do this we need to create a custom
DefaultContractResolver, the code is below:<o:p></o:p></div>
<pre class="brush: csharp">public class IgnoreEmptyCollectionsContractResolver : DefaultContractResolver
{
public new static readonly IgnoreEmptyCollectionsContractResolver Instance = new IgnoreEmptyCollectionsContractResolver();
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty property = base.CreateProperty(member, memberSerialization);
if ((property.PropertyType.Name.Contains("IEnumerable") || property.PropertyType.Name.Contains("ICollection")) && property.PropertyType.GenericTypeArguments.Count() == 1)
{
property.ShouldSerialize = instance =>
{
try{
var cnt = instance.GetType().GetProperty("Count").GetValue(instance,null);
return (int)cnt > 0;
}
catch(NullReferenceException){
return false;}
};
}
return property;
}
}
</pre>
The <catchyName>IgnoreEmptyCollectionsContractResolver</catchyName>
simply checks if the current property is an ICollection or IEnumerable and that
it has a single generic argument. It then checks the Count property and
instructs Json.Net to serialize/deserialize that property depending on whether
or not count is greater than 0. I’m sure this can be done a lot neater, but it
solves my problem.<br />
<div class="MsoNormal">
<o:p></o:p></div>
<div class="MsoNormal">
We then simply instruct Json.Net to use this as part of the
JsonSerializerSettings object:<o:p></o:p></div>
<pre class="brush: csharp">new JsonSerializerSettings() { ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
TypeNameHandling = TypeNameHandling.All,
NullValueHandling= NullValueHandling.Ignore,
DefaultValueHandling = DefaultValueHandling.Ignore,
ContractResolver = new IgnoreEmptyCollectionsContractResolver()});
</pre>
Now the serialized Json looks like this:<br />
<div class="MsoNormal">
<o:p></o:p></div>
<pre class="brush: js">{
"$type": "MyCompany.Model.Customer, MyCompany.Model",
"FreeSampleCount": 10,
"PlaquePrice": 5.0,
"AllDispensers": {
"$type": "MyCompany.Model.Dispenser[], MyCompany.Model",
"$values": []
},
"Id": 201,
"Name": "Company 201",
"Code": "foo",
"IsValid": true
}
</pre>
The total size has dropped from nearly 50000 characters to
1495, a much more acceptable size.<br />
<br />
I hope this is of use to someone. If the resolver can be done in a better way, use the comments.<br />
<div class="MsoNormal">
<o:p></o:p></div>
Alanhttp://www.blogger.com/profile/11461540001324768384noreply@blogger.com0tag:blogger.com,1999:blog-4271467236181995002.post-60841856464465854582013-07-01T12:25:00.000-07:002013-08-05T13:18:58.165-07:00A simple WebCache Helper<div class="MsoNormal">
<span style="font-family: Arial, Helvetica, sans-serif;">As mentioned in most of my previous posts, the main project
I work on is due to move to Azure in the future. Among the many gems of Azure
is their caching infrastructure, which can either be hosted on a dedicated
worker role or instructed to use spare memory on your web roles.<o:p></o:p></span></div>
<div class="MsoNormal">
<span style="font-family: Arial, Helvetica, sans-serif;">More information and pricing for Azure Caching can be found
at <a href="http://www.windowsazure.com/en-us/services/caching/">http://www.windowsazure.com/en-us/services/caching/</a><o:p></o:p></span></div>
<div class="MsoNormal">
<br /></div>
<div class="MsoNormal">
<span style="font-family: Arial, Helvetica, sans-serif;">I fully intend to make use of Azure’s in-built caching when
we get there, but I can’t wait to start implementing some sort of caching and I
don’t want to have to do a big find-replace in the code when we do get there,
so I wrote a simple WebCacheHelper which provides easy access to caching
anywhere in the application but is also easy to replace when I move to Azure.<o:p></o:p></span></div>
<br />
<div class="MsoNormal">
<span style="font-family: Arial, Helvetica, sans-serif;">The code is below.</span><o:p></o:p></div>
<pre class="brush: csharp"> public static class
WebCacheHelper
{
public static T TryGetFromCache<T>(string cacheName, string itemKey) where T:class
{
if (HttpContext.Current == null || HttpContext.Current.Session == null) { return null; }
var sessionResult = TryGetFromSessionCache<T>(cacheName, itemKey);
if (sessionResult != null){return sessionResult;}
var applicationResult = TryGetFromApplicationCache<T>(cacheName, itemKey);
return applicationResult;
}
public static T TryGetFromCache<T>(string cacheName, string itemKey,CachingLevel cachingLevel) where T:class
{
if (HttpContext.Current == null || HttpContext.Current.Session == null) { return null; }
switch (cachingLevel)
{
case CachingLevel.Session:
return TryGetFromSessionCache<T>(cacheName, itemKey);
case CachingLevel.Application:
return TryGetFromApplicationCache<T>(cacheName, itemKey);
}
return null;
}
public static void AddToCache(string cacheName, string itemKey, object cacheItem, CachingLevel cachingLevel=CachingLevel.Application)
{
if (HttpContext.Current == null || HttpContext.Current.Session == null) { return; }
switch (cachingLevel)
{
case CachingLevel.Session: AddToSessionCache(cacheName, itemKey, cacheItem);
break;
case CachingLevel.Application: AddToApplicationCache(cacheName, itemKey, cacheItem);
break;
}
}
public static void RemoveFromCache(string cacheName, string itemKey)
{
if (HttpContext.Current == null || HttpContext.Current.Session == null) { return; }
RemoveFromApplicationCache(cacheName,itemKey);
RemoveFromSessionCache(cacheName,itemKey);
}
public static void RemoveFromCache(string cacheName, string itemKey, CachingLevel cachingLevel)
{
if (HttpContext.Current == null || HttpContext.Current.Session == null) { return; }
switch (cachingLevel)
{
case CachingLevel.Session: RemoveFromSessionCache(cacheName, itemKey);
break;
case CachingLevel.Application: RemoveFromApplicationCache(cacheName, itemKey);
break;
}
}
private static string GetCacheKey(string cacheName, string itemKey)
{
return cacheName + "-" + itemKey;
}
#region Session Cache
private static void AddToSessionCache(string cacheName, string itemKey, object cacheItem)
{
HttpContext.Current.Session[GetCacheKey(cacheName, itemKey)] = cacheItem;
}
private static void RemoveFromSessionCache(string cacheName, string itemKey)
{
var result = HttpContext.Current.Session[GetCacheKey(cacheName, itemKey)];
if (result != null)
{
HttpContext.Current.Session.Remove(GetCacheKey(cacheName, itemKey));
}
}
public static T TryGetFromSessionCache<T>(string cacheName, string itemKey) where T:class
{
var result = HttpContext.Current.Session[GetCacheKey(cacheName, itemKey)];
if (result == null)
{
return null;
}
return (T) result;
}
#endregion
#region Application Cache
private static void AddToApplicationCache(string cacheName, string itemKey, object cacheItem)
{
HttpContext.Current.Application[GetCacheKey(cacheName, itemKey)] = cacheItem;
}
private static void RemoveFromApplicationCache(string cacheName, string itemKey)
{
var result = HttpContext.Current.Application[GetCacheKey(cacheName, itemKey)];
if (result != null)
{
HttpContext.Current.Application.Remove(GetCacheKey(cacheName, itemKey));
}
}
public static T TryGetFromApplicationCache<T>(string cacheName, string itemKey) where T:class
{
var result = HttpContext.Current.Application[GetCacheKey(cacheName, itemKey)];
if (result == null)
{
return null;
}
return (T) result;
}
#endregion
}
</pre>
<br />
<span style="font-family: Arial, Helvetica, sans-serif;">As you can see there is nothing clever going on here, there is a single AddToCache method for adding data to the cache, with a default parameter for cachingLevel so the calling code can override the default application-level caching to cache at the session level.</span><br />
<span style="font-family: Arial, Helvetica, sans-serif;"><br /></span>
<span style="font-family: Arial, Helvetica, sans-serif;"> There are a couple of RemoveFromCache methods, one where the calling code can specify which cache to remove the item from, the other removes the data from whichever cache it resides in.</span><br />
<span style="font-family: Arial, Helvetica, sans-serif;"> There are also two TryGet methods for retrieving from a specified caching level or from any that has a matching key.</span><br />
<span style="font-family: Arial, Helvetica, sans-serif;"><br /></span>
<span style="font-family: Arial, Helvetica, sans-serif;">CachingLevel is just an enum with items for Session and Application</span><br />
<pre class="brush: csharp"> public enum CachingLevel
{
Application,
Session
}
</pre>
<span style="font-family: Arial, Helvetica, sans-serif;">I also use the following static string class to save having magic strings peppered through the application</span><br />
<pre class="brush: csharp">
public static class WebCacheKeys {
public const string Users = "Users";
}
</pre>
<div style="font-family: Consolas; font-size: 13px;">
</div>
<span style="font-family: Arial, Helvetica, sans-serif;">I can just add strings as required and change the backing value if I need to without having to make a large number of changes elsewhere.</span>
<span style="font-family: Arial, Helvetica, sans-serif;">
</span>
<span style="font-family: Arial, Helvetica, sans-serif;">If I had IOC available, I probably wouldn't have made this static and would've abstracted the functionality behind an ICacheHelper interface. The way I look at it, this is the next best thing in terms of ability to make changes in the future. </span>
<span style="font-family: Arial, Helvetica, sans-serif;">
</span>
<span style="font-family: Arial, Helvetica, sans-serif;">When I move to Azure, I'll post the Azure-centric version of this helper.</span>Alanhttp://www.blogger.com/profile/11461540001324768384noreply@blogger.com0tag:blogger.com,1999:blog-4271467236181995002.post-55101901478995774002013-05-15T11:57:00.002-07:002013-05-15T11:57:30.235-07:00This just in-Windows 8 not that badYep, I said it. I may have just committed professional suicide and may never be offered a job again and just to make it worth it I'm going to say it again.<br />
<br />
Windows.8.Is.Not.That.Bad<br />
<br />
Why such gleaming praise? Have I lost my mind, or been bribed by Microsoft to say nice things about their crappy OS to my two readers (hi guys)? No, but unlike most of those who bash Windows 8 for it's split personality and it's absent Start button which is allegedly responsible for the deaths of many kittens, I've used it. I've used it at home since shortly after it came out and have been using it at work for a couple of months now.<br />
I published my first impressions <a href="http://usesmallicons.blogspot.co.uk/2013/01/my-experience-of-windows-8.html" target="_blank">here</a> a while back and I think it's time to refine my impressions a little and add a dash of reason.<br />
<br />
This week there has been a lot of talk about Windows 8.1 aka Blue and how Microsoft are going to put all our cheese back and beg for forgiveness. This won't happen. At most, there is going to be a minor submission, such as adding the Start button back but having it trigger the Start screen, or allowing boot to Desktop.<br />
<br />
6 months of use has led me to the inescapable conclusion that the Start screen is an almost perfect marriage of the Start Menu and the desktop. Let's face it, most people's desktops consist of loosely related groups of icons and maybe an extremely crucial business file that can never be replaced and is not backed up in any way shape or form.<br />
What is the Start screen but a load of groups of loosely related icons?<br />
The start screen is the perfect replacement for your cluttered and dis-organised desktop. You can't put files on it, therefore forcing you to put them somewhere sensible, or just putting them on the actual desktop where you've always put them.<br />
With just a little tidying up, at boot you are presented with a screen full of big icons for your main applications that are easily clickable through blurry eyes on a Monday morning before you've had your caffeine intake.<br />
<br />
So the Start screen is the solution to the icon-explosion on your desktop. But what about the Start Menu?<br />
<br />
The Start Menu, the theory goes, was a quick way to access commonly used applications and provided a nice hierarchical folder structure with which to access all your installed apps.<br />
In actuality, the quick app access came true but the hierarchical folder view soon becomes a dumping ground for any amount of shit that installers feel the desire to put in there. Often, applications from the same company will have wildly differing paths so your hierarchical structured view of your applications has absolutely no structure whatsoever!<br />
For years now, I have taken to manually organizing the folders in the start menu. This makes finding anything easy with the rather large caveat that when anything gets uninstalled, I have a dead entry sitting there for me to trip over at some point in the future.<br />
<br />
I suppose the closest replacement for the "view everything" mentality of the Start Menu is the All Apps view, which is possibly the worst example of a UI I have ever seen. It is completely intractable to me and I steer clear from it. If I need something that isn't on start, I search. If search doesn't bring it up, I dip down to Explorer.<br />
If I need to get to system functions, I use either Win+R to bring up Run or Win+X to bring up the new Quick Access menu.<br />
<br />
Having lived with Windows 8 for a while now, I don't feel I've lost anything. The functionality of the desktop (link farm) + start menu is there in the start screen for me. Anything else can be served by Search or Win+R/X. Overall my workflow is quicker and more streamlined-and no more manually organizing the Start Menu.<br />
<br />
Somehow, life goes on after the Start Menu and no kittens were killed in the use of this OS.<br />
<br />
Well, not many.Alanhttp://www.blogger.com/profile/11461540001324768384noreply@blogger.com0