Oren Eini

CEO of RavenDB

a NoSQL Open Source Document Database

Get in touch with me:

oren@ravendb.net +972 52-548-6969

Posts: 7,565
|
Comments: 51,184
Privacy Policy · Terms
filter by tags archive

Evaluating Atlas

time to read 19 min | 3681 words

Sorry, I meant that I am evaluating Microsoft(R) ASP.Net(TM) Ajax(Patent Pending) Extentions :-).

I mentioned before that I am not really feeling overly excited about that whole Ajax thing. Because of a client constraints, I am unable to use MonoRail (but I use just about everything else from the Castle stack :-) ), so I thought that I would give Atlas a try. This is still very early in the project, but I like to understand how things works so I know why they broke. And for some reason, just about anything breaks when I start playing with it.

At any rate, I took the cascading style list demo for a spin, and managed to hit issues with its error reporting almost immediately. I didn't specify the full path to the service, which meant that it had very helpfully showed me a [method error 500], which I was not pleased to see. Maybe there is a way to get more infomration from it, but I couldn't find it.

Once I got over this hurdle, it was much easier. This is a fairly core scenario, so I wasn't surprised that it was easy, just wire the extenders to the correct controls and write the service methods. Here is my code:

public class NewPolicyRequestService : BaseScriptService

{

    private NewPolicyRequestController controller;

 

    public NewPolicyRequestController Controller

    {

        get { return controller; }

        set { controller = value; }

    }

 

    [WebMethod]

    public CascadingDropDownNameValue[] GetOptionsForPolicy(

        string knownCategoryValues,

        string category)

    {

        StringDictionary kv = CascadingDropDown.ParseKnownCategoryValuesString(knownCategoryValues);

        string maybePolicyId = kv["Policies"];

        ICollection<PolicyOptions> options = Controller.GetOptionsForPolicy(maybePolicyId);

        List<CascadingDropDownNameValue> values = new List<CascadingDropDownNameValue>(options.Count);

        foreach (PolicyOptions option in options)

        {

            values.Add(new CascadingDropDownNameValue(option.Name, option.Id.ToString()));

        }

        return values.ToArray();

    }

 

    [WebMethod]

    public CascadingDropDownNameValue[] GetPolicies(

        string knownCategoryValues,

        string category)

    {

        ICollection<Policy> policies = Controller.GetPolicies();

        List<CascadingDropDownNameValue> values = new List<CascadingDropDownNameValue>(policies.Count);

        foreach (Policy policy in policies)

        {

            values.Add(new CascadingDropDownNameValue(policy.Name, policy.Id.ToString()));

        }

        return values.ToArray();

    }

}

Couple of things to note here, BaseScriptService has all the appropriate attributes [ScriptService], [WebService], [Etc] :-). It is also responsible for injecting the correct controller, which is the one who is responsbile for the actual work. The service is here merely to unpack the data from the request and then translate the returned results to something that Atlas can understand.

So far, very easy. I don't like the fact that I have to duplicate code here, though. There may be a better way, but the only thing that I can think about is to add a template method that accept a couple of delegates, and I don't see that as adding to the maintainability of the system.

The more important concern that I have, though, is what happen when I need to extend this example. Imagine a scenario where I have this:

(Image from clipboard).png

When a policy is selected, I need to update the child drop down, but I also need to update the policy description. I know how I can do it in MonoRail (easy), and I know how I can do it in using HTML/Javascript (not tough). I have no idea how hard it would be to make it work with ASP.Net/Atlas.*

I figure that I'll probably need to drop the drop down extenders completely (and handle all the updates to the drop downs myself {I have way more than 2, by the way} ), in order to be able to recieve more information than what the extender gives me. More annoying then that, I would have to use the horrible <%= Policies.ClientID %> all over the place, and get the lovely "Control Collection Could Not Be Modified" exceptions when I try to do more advance stuff.**

* I find it sad that I need to seperate ASP.Net from HTML

** Experiance hurts, I suggest avoiding it.

time to read 39 min | 7739 words

My site was down because of DasBlog today. I have no idea why it started to misbehave again, and I don't really care at this point, that was the final reason that I needed to move to SubText. Here is how I did it:

  • Put App_Offline.htm page in the blog directory, since otherwise DasBlog would kill the server
  • Back up the existing blog.
  • Prepare a SubText skin matching my current blog look & feel.
  • I then took the DasBlog to BlogML importer here, which is still not ready, and started to play with the code. This is definately something that would require a developer to do, by the way.
    I had to give up on the trackbacks because they issues an IsInRole() call and I am not connected to the office at the moment. I took the short way our and sprinkled Thread.CurrentPrincipal = new GenericPrincipal(..) all over the place (seem to reset itself for some reason), and it worked.
  • I then tried imprted the resulting file to SubText. I have a large blog, and this meant that I had to increase the timeout values. I then discovered that my posts are not legal in SubText, it was warning me about issues with <script> tags in the posts and then failed with some whitespace error that took some time to check in depth. Appernatly I had literal "<<" somewhere, which SgmlReader interpreted as whitespace, and that caused an error when it tried to save that.
  • The posts are appearing in reverse order (the oldest are on the top), that is a side affect of the way BlogML worked, by output it to the export file in reverse order, and SubText accepted it in order. The fix:

    UPDATE  subtext_content

    SET     DateSyndicated = DateAdded

  • The next issues, categories, I am pretty strict about categories, and it looked like the move messed them up, I got mutliply categories with the same name, probably from multiply attempts to import the same set of data, I cleaned it up with:

    DELETE  FROM subtext_linkcategories

    WHERE   categoryid NOT IN ( SELECT  categoryid

                        FROM    subtext_links )

    That still didn't solve the problem, I had an issue with categories that with duplicated names differencing in whitespace, so I found out about them with this query:

    SELECT  LTRIM(RTRIM(title)),

            MIN(categoryid),

            MAX(categoryid),

            COUNT(*)

    FROM    subtext_linkcategories

    GROUP BY LTRIM(RTRIM(title))

    HAVING  COUNT(*) > 1

    Note that I only had two duplicates categories overall, so it was easy to find the values. Then, I fixed the duplication with this query:

    UPDATE  subtext_links

    SET     categoryid = ( CASE categoryid

                             WHEN 452 THEN 460

                             WHEN 464 THEN 527

                             WHEN 465 THEN 504

                             WHEN 477 THEN 508

                             ELSE categoryid

                           END )

    All that was left was cleaning up the empty categories, using the second query above.
    If you haven't figured it out yet, I really like the ability to run queries on my blog :-)
  • The next target was the comments, I first run this:

    update  subtext_feedback

    set     isBlogAuthor = 1

    where   author = 'Ayende Rahien'

    So I could claim ownership of over 600 comments on my blog. Then it was a matter of handling the stats correctly:

    UPDATE  subtext_config

    SET     commentcount = ( SELECT COUNT(*)

                             FROM   subtext_feedback

                             WHERE  feedbacktype = 1

                           ),

            PingTrackCount = ( SELECT   COUNT(*)

                               FROM     subtext_feedback

                               WHERE    feedbacktype = 2

                             )

    One thing that bugs me with the feedback is that there are multiply places that are using "order by id" to sort by date, in my case, this is not the case, it is actually the reverse of what really should happen.
  • Now, the issues of preserving the old URLs. That one is very important to me, since I have a lot of links directed to my site that I don't want to break. I first had to run a check to see how many duplicate entries name I had, and realized that I had merely 12 duplicate entries, this is something that I can live with. I should explain that the importing process didn't give me a way to correlate between the DasBlog entry and the SubText entry. What I did have was the entry title and date, which should be enough, since I didn't post entries with duplicated names on the same day. I created the following table:

    CREATE TABLE subtext_MappingFromDasBlog

    (

      Id int identity(1, 1)

             primary key

             not null,

      subtextId int references dbo.subtext_content ( id )

                    not null,

      date datetime not null,

      dasBlogId uniqueidentifier not null,

      dasblogUrl nvarchar(255) not null

    )

    And I run the following little boo script:

    import System

    import System.Xml

    import System.IO

    import System.Data

    import System.Data.SqlClient

     

    con = SqlConnection("Data Source=localhost;Initial Catalog=SubtextData;Trusted_connection=yes;")

    con.Open()

     

    xdoc = XmlDocument()

    xdoc.Load("C:\\export.xml")

    nsMgr = XmlNamespaceManager(xdoc.NameTable)

    nsMgr.AddNamespace("blog", "http://www.blogml.com/2006/09/BlogML")

    for node as XmlNode in xdoc.SelectNodes("/blog:blog/blog:posts/blog:post", nsMgr):

          id = Guid(node.SelectSingleNode("@id").Value)

          url = node.SelectSingleNode("@post-url").Value;

          title = node.SelectSingleNode("blog:title/text()", nsMgr).Value;

          dateCreated = date.Parse(node.SelectSingleNode("@date-created").Value)

         

          print "Id ${id} url ${url} title ${title} date ${dateCreated}"

         

          using cmd = con.CreateCommand():

                cmd.CommandText = """SELECT Id FROM subtext_Content

                            WHERE Title = @title and year(DateAdded) = @year

                            and month(DateAdded) = @month and day(DateAdded) = @day""";

                cmd.Parameters.AddWithValue("title", title);

                cmd.Parameters.AddWithValue("year", dateCreated.Year)

                cmd.Parameters.AddWithValue("month", dateCreated.Month)

                cmd.Parameters.AddWithValue("day", dateCreated.Day)

                subtextId as int = cmd.ExecuteScalar()

                cmd.Parameters.Clear()

                cmd.CommandText = """INSERT INTO subtext_MappingFromDasBlog

                            VALUES(@subtextId, @date, @dasBlogId, @dasblogUrl);"""

                cmd.Parameters.AddWithValue("subTextId", subtextId)

                cmd.Parameters.AddWithValue("dasBlogId", id)

                # just the date, not the time

                cmd.Parameters.AddWithValue("date", dateCreated.Date)  
                #trim the http://www.ayende.com/Blog/yyyy-mm-dd/

                cmd.Parameters.AddWithValue("dasblogUrl", url.Substring(38) )

                cmd.ExecuteNonQuery()

         

    con.Dispose()

    Just let me iterate that the above is not production code, it is one off code.
    Now, to get some use out of it...
    SubText supports UrlRewriting, with HttpHandlers, so the first thing I needed to do is enable going to a post just by its id. I had to add this to the HandlerConfiguration/httpHandlers section:

    <HttpHandler pattern="(?:/archive/\d+\.aspx)$" controls="viewpost.ascx,Comments.ascx,PostComment.ascx"/>

    Now urls such as /archive/343.aspx are valid.
    Now for the more complex stuff, I wanted to be able to support the following formats.
    • CommentView,guid,{guid}.aspx
    • PermaLink,guid,{guid}.aspx
    • NormalizedPostTitle.aspx
    • /yyyy/mm/dd/NormalizedPostTitle.aspx

It took a while, but I manage to integrate that into SubText cleanly (an additional handler and changing the web.config), so the old urls should still work. The issue was mainly
Important: If you notice a borkne link please let me know about it.

  • Syndication - RSS / Atom - I get a lot more readers on the site than on RSS, but it is obviously very important thing to keep. Nothing is as annoying as a broken link. In this case, dasBlog is using SyndicationService.asmx
  • Binaries - I merely kept them in the same place (I am talking about images, attachments, etc.

The reason that I wrote all of that is that I would actaully remember what I did next time that I would have to upgrade / modify the blog.

The last thing that you need to know is how to configure the url mapping:

<HandlerConfiguration defaultPageLocation="DTP.aspx" type="Subtext.Framework.UrlManager.HandlerConfiguration, Subtext.Framework">

    <HttpHandlers>

        <HttpHandler pattern="login.aspx" handlerType="Page" pageLocation="Login.aspx"/>

              <HttpHandler pattern="(?:/admin\/handlers\/BlogMLExport\.ashx)" type="Subtext.BlogML.BlogMLHttpHandler, Subtext.BlogML" handlerType="Direct"/>

              <HttpHandler pattern="/Admin/" handlerType="Directory" directoryLocation="Admin"/>

              <HttpHandler pattern="/Providers/" handlerType="Directory" directoryLocation="Providers"/>

              <HttpHandler pattern="/rss\.aspx$" type="Subtext.Framework.Syndication.RssHandler, Subtext.Framework" handlerType="Direct"/>

              <HttpHandler pattern="/SyndicationService\.asmx/GetRss$" type="Subtext.Framework.Syndication.RssHandler, Subtext.Framework" handlerType="Direct"/>

              <HttpHandler pattern="/atom\.aspx$" type="Subtext.Framework.Syndication.AtomHandler, Subtext.Framework" handlerType="Direct"/>

              <HttpHandler pattern="/SyndicationService\.asmx/GetAtom$" type="Subtext.Framework.Syndication.AtomHandler, Subtext.Framework" handlerType="Direct"/>

              <HttpHandler pattern="/comments/commentRss/\d+\.aspx$" type="Subtext.Framework.Syndication.RssCommentHandler, Subtext.Framework" handlerType="Direct"/>

              <HttpHandler pattern="/aggbug/\d+\.aspx$" type="Subtext.Framework.Tracking.AggBugHandler, Subtext.Framework" handlerType="Direct"/>

              <HttpHandler pattern="/customcss\.aspx$" type="Subtext.Web.UI.Handlers.BlogSecondaryCssHandler, Subtext.Web" handlerType="Direct"/>

              <HttpHandler pattern="(?:/category\/(\d|\w|\s)+\.aspx/rss/?)$" type="Subtext.Framework.Syndication.RssCategoryHandler, Subtext.Framework" handlerType="Direct"/>

              <HttpHandler pattern="(?:/ArchivePostPage.aspx)$" controls="ArchivePostPage.ascx"/>

              <HttpHandler pattern="(?:/LinkPage.aspx)$" controls="LinkPage.ascx"/>

              <HttpHandler pattern="(?:/ArticleCategories.aspx)$" controls="ArticleCategories.ascx"/>

              <HttpHandler pattern="(?:/archives\.aspx)$" controls="SingleColumn.ascx"/>

              <HttpHandler pattern="(?:/archive/\d{4}/\d{2}/\d{2}/\d+\.aspx)$" controls="viewpost.ascx,Comments.ascx,PostComment.ascx"/>

              <HttpHandler pattern="(?:/archive/\d+\.aspx)$" controls="viewpost.ascx,Comments.ascx,PostComment.ascx"/>

              <HttpHandler pattern="(?:/archive/\d{4}/\d{2}/\d{2}/[-_,+\.\w]+\.aspx)$" controls="viewpost.ascx,Comments.ascx,PostComment.ascx"/>

              <HttpHandler pattern="(?:/archive/\d{4}/\d{1,2}/\d{1,2}\.aspx)$" controls="ArchiveDay.ascx"/>

              <HttpHandler pattern="(?:/archive/\d{4}/\d{1,2}\.aspx)$" controls="ArchiveMonth.ascx"/>

              <HttpHandler pattern="(?:/articles/\d+\.aspx)$" controls="viewpost.ascx,Comments.ascx,PostComment.ascx"/>

              <HttpHandler pattern="(?:/articles/[-_,+\.\w]+\.aspx)$" controls="viewpost.ascx,Comments.ascx,PostComment.ascx"/>

              <HttpHandler pattern="(?:/\d{4}/\d{2}/\d{2}/[\d\w]+\.aspx)$" type="UrlRewriting.For.DasBlog.FromDasBlogToSubText, UrlRewriting.For.DasBlog"  handlerType="Factory" />

              <HttpHandler pattern="(?:commentview,guid,[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}\.aspx)$" type="UrlRewriting.For.DasBlog.FromDasBlogToSubText, UrlRewriting.For.DasBlog"  handlerType="Factory" />

              <HttpHandler pattern="(?:commentview\.aspx)$" type="UrlRewriting.For.DasBlog.FromDasBlogToSubText, UrlRewriting.For.DasBlog"  handlerType="Factory" />

              <HttpHandler pattern="(?:/contact\.aspx)$" controls="Contact.ascx"/>

              <HttpHandler pattern="(?:/posts/|/story/|/archive/)" type="Subtext.Web.UI.Handlers.RedirectHandler, Subtext.Web" handlerType="Direct"/>

              <HttpHandler pattern="(?:/gallery\/\d+\.aspx)$" controls="GalleryThumbNailViewer.ascx"/>

              <HttpHandler pattern="(?:/gallery\/image\/\d+\.aspx)$" controls="ViewPicture.ascx"/>

              <HttpHandler pattern="(?:/(?:category|stories)/(\w|\s)+\.aspx)$" controls="CategoryEntryList.ascx"/>

              <HttpHandler pattern="(?:/comments\/\d+\.aspx)$" type="Subtext.Framework.Syndication.CommentHandler, Subtext.Framework" handlerType="Direct"/>

              <HttpHandler pattern="(?:/services\/trackbacks/\d+\.aspx)$" type="Subtext.Framework.Tracking.TrackBackHandler, Subtext.Framework" handlerType="Direct"/>

              <HttpHandler pattern="(?:/services\/pingback\.aspx)$" type="Subtext.Framework.Tracking.PingBackService, Subtext.Framework" handlerType="Direct"/>

              <HttpHandler pattern="(?:/services\/metablogapi\.aspx)$" type="Subtext.Framework.XmlRpc.MetaWeblog, Subtext.Framework" handlerType="Direct"/>

 

              <HttpHandler pattern="(?:/default\.aspx)$" controls="homepage.ascx"/>

              <HttpHandler pattern="(?:/[\d\w]+\.aspx)$" type="UrlRewriting.For.DasBlog.FromDasBlogToSubText, UrlRewriting.For.DasBlog"  handlerType="Factory" />

 

              <!-- this needs to be last -->

        <HttpHandler pattern="(?:((\/\/default\.aspx)?|(\/\/?))?)$" controls="homepage.ascx"/>

       </HttpHandlers>

</HandlerConfiguration>

 


And you can find the code for the actual URL mapping here.
It is simple, it works, and that is all I care about.

If you haven't guess, this migration was NOT a walk in the park. I hope that I got all the kinks out, but let me know if there is anything that I missed.

Moved to subtext

time to read 1 min | 178 words


Around 3AM yesterday I had a nasty surprised when I tried to post something to my site, it was down. Not just the blog, but the whole site.
A quick check with my host turned out that my blog was using 100% CPU for a long period of time, so they had to remove the site. I have some issues with why they didn't bother to notify me when they did it, but that is another matter. After I spoke with them the site went back up, but the blog was down.
That was 10 hours ago or so, and since then I was trying to move everything that I had to SubText as quickly as I can. I have a lengthy post detailing the procedure, but for now all you need to know that I am on SubText, that I think that I like it, and that is it.
Now I am going to do something interesting.

You can expect some problems with the site until I beat it back to working shape.  
time to read 1 min | 89 words

Hi,

My hosting company has moved me to a new server, so it might take a while to setup correctly. I am still planning to a new host company, and am probably going to choose webhosting4life.

The move to the new server is a stop gag measure, to bring performance to an acceptable level again. Take into account that this also means that I may not get emails for the next day or so (not a bad thing in itself).

time to read 2 min | 247 words

For the last three years I had my site hosted on Mirshetet, and I couldn't be happier. I had zero headaches from the site, and when I called, I got really good people, who understood what I was talking about. Recently, however, I had a number of serious problems that resulted in unacceptable slow downs and the site being down. (This is disregarding my mistake with no renewing the domain). Just to clarify, I am getting response times from dasBlog in the order of minutes.

The final stroke was trying to go to my website during the DNRTV recording and finding that it was broken. I am currently looking for a new host. My setup is Cuyahoga and dasBlog, which means that I need a .Net (preferably 2.0, but not actually required) and a database server (I am currently running of Cuyahoga on MySQL database, but it is not a problem to change, thanks to NHibernate).

I am currently using roughly 350Mb of diskspace (a couple of MB in the database) and a bandwidth of about roughly 30Gb per month, which is fairly modest.

I have been recommended Reyox, anyone else that I should consider?

Oh, am if you have a good host and a promo code that would give a discount, I would love to hear about it (but I do not promise to do anything about it).

Web Message Box

time to read 4 min | 760 words

This is probably a really bad idea, but here it is:

public class WebMsgBox

{

      public static void Show(string msg)

      {

            if(HttpContext.Current==null && HttpContext.Current.Handler is Page)

                  return;

            Page currentPage = (Page) HttpContext.Current.Handler;

            currentPage.ClientScript

                  .RegisterStartupScript(currentPage.GetType(),"msgBox",

                      string.Format("alert('{0}');", msg.Replace("'","\\'")),

                      true);

      }

}

It can be called from anywhere in the code, not just from a page.

FUTURE POSTS

No future posts left, oh my!

RECENT SERIES

  1. Production Postmortem (52):
    07 Apr 2025 - The race condition in the interlock
  2. RavenDB (13):
    02 Apr 2025 - .NET Aspire integration
  3. RavenDB 7.1 (6):
    18 Mar 2025 - One IO Ring to rule them all
  4. RavenDB 7.0 Released (4):
    07 Mar 2025 - Moving to NLog
  5. Challenge (77):
    03 Feb 2025 - Giving file system developer ulcer
View all series

RECENT COMMENTS

Syndication

Main feed Feed Stats
Comments feed   Comments Feed Stats
}