Friday, February 27, 2009

DotNetNuke, Roles and Invalid value for 'encrypted ticket parameter'

On http://www.RunSaturday.com I was using DNN's roles for friends... then one day I couldn't log on.

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

From http://www.csharp411.com/lighten-and-darken-colors-in-net/ (and other sources) you can use ControlPaint to help with lightening and darkening colours.

Integrating Twitter

I've been playing with www.twitter.com quite a lot over the last week or two.

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

Someone's emailed me from Martinique.

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

Dodgy error from the compiler....

"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

When using my facebook connect authentication plugin (on http://www.snowcovered.com/Snowcovered2/Default.aspx?tabid=242&PackageID=13359), the password generated by DNN is far, far too secure for most users.

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

I've been using YetAnotherForum on runSaturday and have found it very good.

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:

        const double NumSecondsBetweenMailSendAttempts = 600;
        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:

        private static void AddCaching(HttpContext context)
        {
            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

Sorry I've not posted much here for a short while.

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

So DotNetNuke5 is lovely... and so was DotNetNuke4....

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;

        }

 

Save settings:

        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...

Hi All

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

This change (for Jules) worked:

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

Got distracted this morning - was supposed to be working on http://www.runsaturday.com on people search - but instead wrote a paper on Azure and stacka:

Monday, February 02, 2009

Another twist on stacka

Since it really really snowed last night and today....

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

Before I start......

Sorry if this blog post is a bit too brief and too technical. I could write about this topic all day and still not get it covered - so I've gone brief and techie. You will probably need at least a little experience in Azure (or GAE or EC2) to understand this...

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....

Firstly, I had organised my comments and my ratings (partition key and row key) so that they were easily accessible in time order from an individual "stacka" (or site in the couddotnet case):
- 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.

For problem 1:
  • 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)

For problem 2:
  • 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 :)
For problem 3:
  • 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.
Some side notes:
  • 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...
And the conclusion:

You can see the results on:
Well, I think it was worth it anyway :)

Sunday, February 01, 2009

Can I have some spam with my cloud?

So it looks like I've got to add some admin functions to clouddotnet and to stacka.....

e.g. this is the first spam entry already on clouddotnet:

http://www.clouddotnet.com/cloud/samujjal/samujjal

Kind of funny I suppose...

Saturday, January 31, 2009

A HUGE recommendation for parsing CSV formats (and other similar formats too)

This CSV parser is just superb:
http://www.codeproject.com/KB/database/CsvReader.aspx

Use it and love it.

Fast, well engineered and very powerful - downloaded it yesterday and had all sorts of stuff running 10 minutes later - then it helped me work out and workaround errors in the incoming files - just a really good bit of software for a really essential task.

(and while I'm recommending... Paint.Net looks like a very handy bit of kit too- http://www.getpaint.net/ !)

Friday, January 30, 2009

What a bunch of miserable take take take bastards all my blog readers are...

Or rather.... thanks to http://www.beingnew.net for pointing out that I'd forgotten to enable comments on my blog.

Oops!

Thursday, January 29, 2009

Some notes following yesterday's London .Net user group

I'm not sure my notes are going to be the best on last night... as I was up to talk last, it did vaguely distract me...

Andrew Myhre
Digital Wall - Online urban decent, graphite in the 22nd century - a bit of a gimmick at the moment - but one that in a creative space could turn heads... and I would quite like my moblie phone to show me a digital wall... maybe (like twitter) this might yet have a future one day...

Scott Cowan
So there I was thinking I was good knowing about NUnit and having the intention of using NMock and RhinoMocks (and maybe even Castle's Inversion of Control) one day soon. And then along comes Behaviour Driven Testing... clearly yet more good intentions for me to add to my list - excellent blog name too - http://sleepoverrated.com/

Christian Blunden vs Ben Hall
Battle of the cruise controls.... as Harry Hill would say - fight!
I've got admit we used to use Cruise Control at my last job - and that it broke in the first month and we couldn't be bothered to fix it... oops. However, both these UIs looked much simpler to use... next time (more good intentions)
BlogUrlMissing vs http://blog.benhall.me.uk/

Abid Quereshi
An interesting overview of some lessons learned changing a company's culture... Interesting to hear about the pain... it's not something I've seen before - especially having never worked for a big company, having always had plenty of cheek (rather than courage) and having always got on with building stuff (new term learnt tonight - "lean" development)
BlogUrlMissing

Justin Davies
Unit testing with classic ASP. Really really really puts me to shame - a nice compact framework built around ASP (which he quite correctly described as "shit") to allow NUnit tests to be run - nice to see code rather than ppoints (again puts me to shame)
Blog - BeingNew

Ben Nunney
PowerShell - very amusing stories about what makes Ben get up every day. A bit of me dislikes all this empowering of sys admins with programming skills... but most of me knows that it means it frees me to go on and work on much more fun stuff.
twitter: @bennuk
BlogUrlMissing

Sebastien Lambla
Fluent-nhibernate - what can I say - my first sighting of Windows 7 in the wild - and it was running on a Mac Air... chapeau!
BlogUrlMissing

Me....
Next time less slides (no slides) and maybe a demo instead - but wifi is a necessity not a luxury :)
This is my blog!

Did I miss anyone?

Visual Studio's strangest error message yet....

All I want to do is add a file to my project.... but "the computer says no"

I get a message box that says:
"Attempted to read or write protected memory. This is often an indication that other memory is corrupt"

So what does this mean? Well, googling for the error reveals all sorts of hits - e.g. this thread is quite interesting http://social.msdn.microsoft.com/Forums/en-US/csharpgeneral/thread/6adca20b-649f-41a4-8fa1-09534882d76c/ - but I haven't found a solution (yet)

Ho hum!


Update - I think this was something to do with the fact that one of my projects was a 2.0 target - while everything else was 3.5 - but really I'm still in the dark.

Wednesday, January 28, 2009

My presentation to the London Dot Net Users Group this evening...

The contents are here http://www.slideshare.net/cirrious/10-things-ive-learnt-in-the-clouds-presentation

I suspect I won't fit much of it in my 5 minute slot (I do like to talk...)

If you did come and you want me to do another talk ever again... then please login to stacka and upload a stack...

Alternatively if you came and you never want me to talk again... then please login to stacka and upload 2 stacks...

If you know anyone who likes to run/cycle/ski/walk with a GPS please do point them at RunSaturday - it's still growing :)

Here's the presentation embedded:


One other note - I've got lots of fun little development projects going on at the moment - all .NET 3.5, all C# - if anyone wants to get involved, then let me know and I'll try to work out how to parcel up some work. Not open source as such, but community collab on a small scale.