Friday, February 27, 2009
DotNetNuke, Roles and Invalid value for 'encrypted ticket parameter'
The reason? I had too many friends :)
Basically the problem was that DNN was inserting a very long string into a cookie....
To work around this I'll need to either change my code to not use the DNN roles structure or insert an HTTPModule to tweak their Roles behaviour.
Here's the DNN thread: http://www.dotnetnuke.com/Community/Forums/tabid/795/forumid/111/threadid/234880/scope/posts/threadpage/7/Default.aspx
Tuesday, February 24, 2009
I was sure I'd already blogged this - colors (colours) darker and lighter
Integrating Twitter
On the send side, I used the tweetsharp project to send tweets - if you're a www.runsaturday.com user then you'll have seen this - if not, then you can't see it! - http://code.google.com/p/tweetsharp/
On the browse side, I used twitter.js to show people's tweets on their runsaturday profile - see http://www.runsaturday.com/Profile/TabId/82/UserId/3/Default.aspx for an example - http://remysharp.com/2007/05/18/add-twitter-to-your-blog-step-by-step/
Both libraries come recommended
Sunday, February 22, 2009
Starting to think about internationalisation
Yes, I had to lookup were that was! (It is on Google Maps but not on all of them - e.g. not on Google static maps!)
Anyways, it's got me thinking about internationlisation (i18n) for runsaturday. I think it should be possible to do this... I might have a go soon...
Need to go back over my code first and find out which modules have strings naughtily hardcoded... but then I think it should be possible to translate the existing site quite quickly - maybe in less than a week....
It must be done! But then so must a lot of other things....
Stuart
Saturday, February 21, 2009
Preprocessor directives must appear as the first non-whitespace character on a line
"Preprocessor directives must appear as the first non-whitespace character on a line"
What this actually meant was I'd tried to use two DataBinding expressions inside the same property -
<asp:Label ID="UserName" runat="server" Text='<%# Eval("UserName") %><%# Eval("TeamMemberNameText") %>'></asp:Label>
To solve it I just pulled it down to:
<asp:Label ID="UserName" runat="server" Text='<%# Eval("UserAndTeamMemberNameText") %>'></asp:Label>
Wednesday, February 18, 2009
Replacing the far too secure random password generation in DotNetNuke
It looks like ASH783934_w3w-r - i.e. not very easy to remember.
I wanted to replace it - so used a class like....
public class SlodgeDNNMembershipProvider : DotNetNuke.Security.Membership.AspNetMembershipProvider
{
static List<string> PasswordBase = new List<string>()
{
"fishfinger","rabbit",
... lots of other simple keywords,
"racing"
};
public override string GeneratePassword(int length)
{
// length ignored - hope this does not hurt the SQL layer!
return GeneratePassword();
}
public override string GeneratePassword()
{
Random r = new Random();
int index = r.Next(PasswordBase.Count);
if (index >= PasswordBase.Count) // according to the intellisense help this should not happen
index = PasswordBase.Count - 1;
int number = r.Next(100);
return string.Format("{0}{1:00}", PasswordBase[index], number);
}
}
And then inserted this into the web.config layer using:
<members defaultProvider="AspNetMembershipProvider">
<providers>
<clear/>
<add name="AspNetMembershipProvider" type="SlodgeDNNMembershipProvider.SlodgeDNNMembershipProvider, SlodgeDNNMembershipProvider" providerPath="~\Providers\MembershipProviders\AspNetMembershipProvider\"/>
</providers>
</members>
Seemed to work first time - which is always suspicious!
Monday, February 16, 2009
Notes on YetAnotherForum.net speed
However... I have also had some problems - which I think I've started to solve...
Here are my notes - as submitted to http://forum.yetanotherforum.net/yaf_postsm31803_Problems-with-slow-forum-not-on-godaddy--and-my-solution.aspx#post31803
I've been using this on my DotNetNuke site - http://www.runsaturday.com - and I've had some problems with slowness - similar I think to some of the GoDaddy problems much discussed here.
My speed problems (I think) were:
- that my mail server is occasionally slow to react - so the sending of emails is often slow... and this seems to be done on the forum main thread.
- that the user images returned from the forum are not sent with caching information - so they are requested far too often by clients.
To work around these problems:
1. I've added this code to the mail sender in forumpage.cs - I've not fully tested it works yet - but the speed improvement seems OK:
static DateTime MailLastSent = DateTime.Now;
class MailSendLock
{
private int i = 0;
}
static MailSendLock MailSendLockObject = new MailSendLock();
private void TriggerMailIfNecessary()
{
if ((DateTime.Now - MailLastSent).TotalSeconds > NumSecondsBetweenMailSendAttempts)
{
lock (MailSendLockObject)
{
if ((DateTime.Now - MailLastSent).TotalSeconds > NumSecondsBetweenMailSendAttempts)
{
MailLastSent = DateTime.Now;
System.Threading.ThreadPool.QueueUserWorkItem(new WaitCallback(SendMailThread));
}
}
}
}
2. I've added this code to the resource.ashx.cs file:
{
context.Response.Cache.SetExpires(DateTime.Now.AddDays(7.0));
context.Response.Cache.SetCacheability(HttpCacheability.Public);
context.Response.Cache.SetValidUntilExpires(false);
}
public void ProcessRequest( HttpContext context )
{
if ( context.Request.QueryString ["r"] != null )
{
// resource request
GetResource( context );
}
else if ( context.Session ["lastvisit"] != null )
{
if ( context.Request.QueryString ["u"] != null )
{
GetResponseLocalAvatar( context );
AddCaching(context);
}
else if ( context.Request.QueryString ["url"] != null && context.Request.QueryString ["width"] != null && context.Request.QueryString ["height"] != null )
{
GetResponseRemoteAvatar( context );
AddCaching(context);
}
else if ( context.Request.QueryString ["a"] != null )
{
GetResponseAttachment( context );
AddCaching(context);
}
}
else
{
// they don't have a session...
context.Response.Write( "Please do not link directly to this resource. You must have a session in the forum." );
}
}
I've been incredibly busy
I've been really very busy indeed writing Javascript of all things.
Lots of Google Maps code - for the output take a look at the new Course Mapper on RunSaturday - see:
http://www.runsaturday.com/Maps/tabid/98/Default.aspx
and:
http://www.runsaturday.com/Maps/tabid/99/course/1/Default.aspx
And you can also see it used in action in this iframe example: http://lodge.stuart.googlepages.com/examplepage
Monday, February 09, 2009
Reflection on my own base classes - DNN 4 vs DNN 5 pain
However, someone has really upset me today by completely changing the base class functionality of AuthenticationConfigBase - which means that it's really hard to make an authentication module which will work in both DNN4 and in DNN5. Basically you get haunted by MissingMethodException's surrounding GetPortalSetting and PortalController.GetPortalSettingsDictionary.
To get around this, I had to reflect on the Config's own base class to find out what to use...
Here's the code.... I'm sure it could be more efficient - but it doesn't matter too much for where it is (it's not loaded very often)
Load settings:
// Methods
protected Config(int portalId)
: base(portalId)
{
this._ApplicationKey = Null.NullString;
this._Enabled = Null.NullBoolean;
this._IncludeHelp = Null.NullBoolean;
this._SecretKey = Null.NullString;
this._LocationXDReceiver = Null.NullString;
bool tryDnn4Methods = false;
try
{
Dnn5LoadSettings(portalId);
}
catch (Exception /*e*/)
{
tryDnn4Methods = true;
}
if (!tryDnn4Methods)
return;
try
{
Dnn4LoadSettings();
}
catch (Exception e)
{
throw new ApplicationException("Cannot initialise Facebook Connect config", e);
}
}
private void Dnn4LoadSettings()
{
if (!bool.TryParse(HackGetValueForDnn4("FacebookConnect_Enabled"), out this._Enabled))
this._Enabled = false;
this._ApplicationKey = HackGetValueForDnn4("FacebookConnect_ApplicationKey");
this._SecretKey = HackGetValueForDnn4("FacebookConnect_SecretKey");
if (!bool.TryParse(HackGetValueForDnn4("FacebookConnect_IncludeHelp"), out this._IncludeHelp))
this._IncludeHelp = false;
this._LocationXDReceiver = HackGetValueForDnn4("FacebookConnect_LocationXDReceiver");
}
private void Dnn5LoadSettings(int portalId)
{
this._Enabled = PortalController.GetPortalSettingAsBoolean("FacebookConnect_Enabled", portalId, Null.NullBoolean);
this._ApplicationKey = PortalController.GetPortalSetting("FacebookConnect_ApplicationKey", portalId, Null.NullString);
this._SecretKey = PortalController.GetPortalSetting("FacebookConnect_SecretKey", portalId, Null.NullString);
this._IncludeHelp = PortalController.GetPortalSettingAsBoolean("FacebookConnect_IncludeHelp", portalId, Null.NullBoolean);
this._LocationXDReceiver = PortalController.GetPortalSetting("FacebookConnect_LocationXDReceiver", portalId, Null.NullString);
}
private string HackGetValueForDnn4(string valueToGet)
{
Type type = this.GetType();
System.Reflection.PropertyInfo info = type.GetProperty("ModuleSettings", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.SetProperty | System.Reflection.BindingFlags.Instance);
if (info == null)
throw new ApplicationException("Cannot initialise Facebook Connect - no ModuleSettings found");
System.Collections.Hashtable hashTable = (System.Collections.Hashtable)info.GetValue(this, null);
string value = (string)hashTable[valueToGet];
if (value == null)
value = string.Empty;
return value;
}
internal void HackSetValueForDnn4(string valueToSet, string value)
{
ModuleController controller = new ModuleController();
Type type = this.GetType();
System.Reflection.PropertyInfo info = type.GetProperty("AuthenticationModuleID", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.SetProperty | System.Reflection.BindingFlags.Instance);
if (info == null)
throw new ApplicationException("Cannot initialise Facebook Connect - no ModuleSettings found");
int authenticationModuleID = (int)info.GetValue(this, null);
controller.UpdateModuleSetting(authenticationModuleID, valueToSet, value);
}
public static void UpdateConfig(Config config)
{
try
{
Dnn5UpdateSettings(config);
}
catch (Exception /*e*/)
{
}
try
{
Dnn4UpdateSettings(config);
}
catch (Exception e)
{
// ignored
//throw new ApplicationException("Cannot save Facebook Connect config", e);
}
}
private static void Dnn4UpdateSettings(Config config)
{
config.HackSetValueForDnn4("FacebookConnect_Enabled", config.Enabled.ToString());
config.HackSetValueForDnn4("FacebookConnect_ApplicationKey", config.ApplicationKey);
config.HackSetValueForDnn4("FacebookConnect_SecretKey", config.SecretKey);
config.HackSetValueForDnn4("FacebookConnect_IncludeHelp", config.IncludeHelp.ToString());
config.HackSetValueForDnn4("FacebookConnect_LocationXDReceiver", config.LocationXDReceiver);
ClearConfig(config.PortalID);
}
private static void Dnn5UpdateSettings(Config config)
{
PortalController.UpdatePortalSetting(config.PortalID, "FacebookConnect_Enabled", config.Enabled.ToString());
PortalController.UpdatePortalSetting(config.PortalID, "FacebookConnect_ApplicationKey", config._ApplicationKey.ToString());
PortalController.UpdatePortalSetting(config.PortalID, "FacebookConnect_SecretKey", config.SecretKey.ToString());
PortalController.UpdatePortalSetting(config.PortalID, "FacebookConnect_IncludeHelp", config.IncludeHelp.ToString());
PortalController.UpdatePortalSetting(config.PortalID, "FacebookConnect_LocationXDReceiver", config._LocationXDReceiver.ToString());
ClearConfig(config.PortalID);
}
Sunday, February 08, 2009
Facebook Connect for DotNetNuke finally documented...
I've finally documented the facebook connect auth system I produced for dotnetnuke.
The doc is up on http://www.cirrious.com/Research/FacebookConnect/tabid/59/Default.aspx - pdf format to follow soon
I'll be releasing the software for a nominal fee through snowcovered and through DNN showcase in the very near future!
Got to get some sleep now!
Stuart
Thursday, February 05, 2009
Back on dnn user names again
update dnn_users set username='JulesR', displayname='JulesR' where username='JROSE02'
update aspnet_Users set username='JulesR' ,loweredusername='julesr' where username='JROSE02'
update yaf_user set name = 'JulesR' where name='JROSE02'
My previous post on this had a typo!
At least I hope the change worked....
Wednesday, February 04, 2009
White Paper Draft - Azure development
Monday, February 02, 2009
Another twist on stacka
I got inspired to spin out another version of stacka.
This time http://www.snowdotnet.com - a place to show your snowmen off :)
Not going to plug it too hard - I think I might have friend new website apathy by now....
However, one interesting techie point to make:
- because I've run out of webspace, this app is not running purely on Azure
- instead the storage is 100% azure
- but the web app is running on my GoGrid server
- and the async worker role has been abandoned - instead I'm doing the picture processing synchronously.
Anyone want to post any pictures, please do :)
Experiments with Azure - changing the structure of live data
On with the post....
The problems...
I wanted to update my http://www.stacka.com and http://www.clouddotnet.com Azure apps - especially adding recent comments and recent ratings to the front page.
And so the problems started.... it was very revealing about my data structure....
- I had the PartitionKey as StackId
- I had the RowKey as a reverse time index (a bit like Steve Marx's blog examples - see http://blog.smarx.com)
Problem 1: Now because there is no "order by" allowed in the Linq for Azure table storage - so the order returned is the partition key, row key order... this presented a challenge in how to get all my data back in the right order
Secondly, I had not stored simple data like Stack title (site title for clouddotnet) in the rating and comment entries
Problem 2: Because there is no "join" allowed in the Linq for Azure table storage (and nor is Contains allowed) then listing comments/ratings alongside their stack/site names was going to be really slow.
Thirdly, I wanted to present a random set of Stacks (or sites) - rather than just the latest
Problem 3: How do you pick a random set when there's no order by, no count, etc available?
To solve these problems....
I had to go back to my data schema - which in Azure, of course, is just the public properties of my data classes.
- I've bodged it....
- While my data size is so small, I'm actually pulling back all the comments and ratings into app memory and sorting them there. To help prevent some slowness I'm using the HttpContext.Current.Cache cacheing with a one minute absolute time expiration)
- What I need to do in the longer term is either to change the PartitionKey and RowKey so I can get results returned by time while still being able to search quickly by stack - I think this will be simple enough to do - but I think it will require a change in table name.
- (An alternative in the long term would be to add another table to act as an index - but I think in this case that is not needed)
- I had to add on the public property StackTitle to the CommentRow class - and similarly for the RatingRow class.
- I then had to write some code to update the existing data to include these StackTitle properties - I did this in a simple Windows Form app that I ran on my local PC - it used the same class libraries as the real ASP.Net app - and upgraded the data live without any users noticing - very easy.
- Then I changed the ASP.Net code to use the new structures and deployed the new code :)
- I've bodged it....
- While my data size is so small, I'm actually pulling back all the stacks and selecting a random set there
- What I need to do in the longer term is to create separate lists of random items - I think I would do this in a worker role - maybe creating a new random list once every minute - and storing up to 10 random list in the azure table storage at any one time? This would be simple enough to do - and not too bad on processing or redundancy.
- Azure storage is a bit of a mindf*&k - you have to stop thinking relational - you definitely have to stop thinking normalised.
- Do not read this and think - "this is horribly inefficient"!
- Azure storage (like Amazon's SimpleDB and like Google's BigTable) is very very efficient
- It's just that you have to keep thinking about efficiency in terms of speed of retrieving data for the user - not in terms of number of bytes stored or in terms of duplicated data.
- It's a classic MIPs versus Memory trade off - and in the world of the cloud, then MIPs and Memory are both cheap, but when it comes to responding to the user then you cut MIPs at the expense of Memory.
- Changing the schema was really simple
- Working out what is actually now stored in the data store is a bit tricky - at one point I added some rows to my live data store with an extra column... those cells will live on for all time now - but I can't work out right now how to actually work out which rows have those cells.
- The use of a desktop tool to upgrade the data was beautifully simple (and it used https - so it was secure)
- While the bodges might:
- They're a good programming approach - as long as I know they work and as long as I know that they can be changed, then it's actually a solution that is scalable in development effort terms?
- They're a p1ss poor programming approach... there is definitely an argument that it's better to do the fix right first time...
You can see the results on:
- Before the upgrade the page looked like - http://www.stacka.com
- After the upgrade the front page looked like - http://www.clouddotnet.com
Sunday, February 01, 2009
Can I have some spam with my cloud?
e.g. this is the first spam entry already on clouddotnet:
http://www.clouddotnet.com/cloud/samujjal/samujjal
Kind of funny I suppose...