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