Geeks With Blogs
Path Notes of a Kodefu Master blog

When I set up my first KiGG site, I was surprised to discover that I had to manually publish articles. I assumed it would be an automated process that would run once a day. Since there are times I may not be able to log into my website, I set about figuring out how to automate the process.

I should note that during this process, I didn’t use best practices. I had one requirement: make a program that I can schedule to publish stories on KiGG. I wasn’t really sure what I would need to go about doing that. So, I did what I assume most developers do: create a console application prototype. I don’t claim this code is perfect, but if you want classes to automate publishing in KiGG (or control MVC applications), this will do the trick.

KiGG is an ASP.NET MVC application. This made it easy to figure out where to begin. I wanted to Publish, so I needed to find the admin link and see what it called. I guessed it would be something like, /Publish, and I was correct.

scriptManager.RegisterOnReady("Administration.set_publishUrl('{0}');"
    .FormatWith(Url.RouteUrl("Publish")));

set_publishUrl is simply a setter in the JavaScript to prevent a url from being hardcoded in script. Looking over the JavaScript I could tell there wasn’t much more needed than calling the proper url.

$.ajax(
    {
        url: Administration._publishUrl,
        type: 'POST',
        dataType: 'json',
        data : '__MVCASYNCPOST=true', // a fake param to fool iis for content-lenth,
        beforeSend: function()
        {
            $U.showProgress('Publishing stories...');
        },
        success: function(result)
        {
            $U.hideProgress();

            if (result.isSuccessful)
            {
                $U.messageBox('Success', 'Story publishing process completed.', false);
            }
            else
            {
                $U.messageBox('Error', result.errorMessage, true);
            }
        }
    }
);

The script is doing a POST on the /Publish url with fake data (to fool IIS?).

Next, it was time to check the routing for /Publish. Routing in KiGG is defined in Kigg.Web\BootstrapperTasks\RegisterRoutes.cs. It was done this way to be consistent with the Open Closed Principle.

_routes.MapRoute("Publish", "Publish", 
    new { controller = "Story", action = "Publish" });

Now we know the entry to this is in the story controller and that the method is Publish. The story controller being used is defined within the web.config file using Unity.

<typeAlias alias="StoryController" type="Kigg.Web.StoryController, Kigg.Web"/>

I set up a break point and wrote a quick routine to post to the publish url.

var request = WebRequest.Create(http://localhost:1736/Publish);
request.ContentType = "application/x-www-form-urlencoded"; request.Method = "POST"; var form = Encoding.ASCII.GetBytes("__MVCASYNCPOST=true"); using (Stream requestStream = request.GetRequestStream()) { requestStream.Write(form, 0, form.Length); requestStream.Close(); } var response = request.GetResponse(); using (StreamReader reader = new StreamReader(response.GetResponseStream())) { var responseText = reader.ReadToEnd(); Debug.WriteLine(responseText); reader.Close(); }

Everything executed fine, but the return message indicated something was wrong: “{"isSuccessful":false,"errorMessage":"You are currently not authenticated."}.” It’s pretty obvious that authentication would be necessary, otherwise I could publish DotNetShoutOut whenever I wanted. Unfortunately, the obvious way to authenticate does not work.

request.Credentials = new NetworkCredential("admin", "password");

Since it is a NetworkCredential, I assume this code would work with Windows authentication turned on. However, we’re using forms authentication. I looked at the routes and found one for Login and Logout. At this point it was clear that I would be calling post multiple times, so I moved the code to a method that would accept a url and formdata as a string.

Post("http://localhost:1736/Login", "userName=admin&password=password");
Post("http://localhost:1736/Publish", "__MVCASYNCPOST=true");
Post("http://localhost:1736/Logout", "__MVCASYNCPOST=true");

This returned the following results:

{"isSuccessful":true,"errorMessage":null}
{"isSuccessful":false,"errorMessage":"You are currently not authenticated."}
{"isSuccessful":false,"errorMessage":"You are currently not logged in."}

I put a breakpoint in the Login method of the MembershipController, and from there was able to see what was happening. Although it was a few levels deep, it was clear that FormsAuthentication.SetAuthCookie was the important piece of code; I needed a way to retain cookies between requests.

Cookies are only available by using HttpWebRequest and HttpWebReponse rather than their base classes. So, I cast my variables to the proper class. One odd thing I found was the cookies have to be retrieved from the request rather than the response after you have requested the response. I wrapped this functionality up in a WebSession class.

public class WebSession
{
    protected CookieCollection Cookies { get; set; }

    public WebSession()
    {
        Cookies = new CookieCollection();
    }

    public string Post(string url, HttpDictionary formData)
    {
        return Post(url, formData.ToString());
    }

    public string Post(string url, string formData)
    {            
        var request = WebRequest.Create(url) as HttpWebRequest;
        if (request == null)
        {
            throw new HttpException();
        }
        request.ContentType = "application/x-www-form-urlencoded";
        request.Method = "POST";
        request.CookieContainer = new CookieContainer();
        request.CookieContainer.Add(request.RequestUri, Cookies);

        var form = Encoding.ASCII.GetBytes(formData);
        WriteToRequest(request, form);

        var response = request.GetResponse() as HttpWebResponse;
        if (response == null)
        {
            throw new HttpException();
        }

        var responseText = ReadResponse(response);
        Cookies = request.CookieContainer.GetCookies(request.RequestUri);

        Debug.WriteLine(responseText);
        
        return responseText;
    }

    private static void WriteToRequest(WebRequest request, byte[] form)
    {
        using (Stream requestStream = request.GetRequestStream())
        {
            requestStream.Write(form, 0, form.Length);
            requestStream.Close();
        }
    }

    private string ReadResponse(WebResponse response)
    {
        using (StreamReader reader = 
            new StreamReader(response.GetResponseStream()))
        {
            var responseText = reader.ReadToEnd();
            reader.Close();
            return responseText;
        }
    }
}

If you look closely you’ll notice an HttpDictionary class. This is because I wanted to treat the form data as a dictionary rather than a string. I suppose this could be written as an extension method on Dictionary<string, string> instead.

public class HttpDictionary : Dictionary<string, string>
{
    public override string ToString()
    {
        return this.Select(q => EncodePair(q))
            .Aggregate((a, b) => a + "&" + b);
    }

    private string EncodePair(KeyValuePair<string, string> pair)
    {
        return HttpUtility.HtmlEncode(pair.Key) +
            "=" + HttpUtility.HtmlEncode(pair.Value);
    }
}

I also felt it necessary to wrap up the result received from KiGG in a class that can be used. Otherwise, it’s difficult to know whether or not the call was successful. To convert from Json, I used Json.NET.

public class KiggResult
{        
    [JsonProperty(PropertyName="isSuccessful")]
    public bool IsSuccessful{ get; set; }

    [JsonProperty(PropertyName = "errorMessage")]
    public string ErrorMessage { get; set; }

    public static KiggResult Create(string json)
    {
        return JsonConvert.DeserializeObject<KiggResult>(json);
    }
}

Finally, I made a KiggBot class to provide us an interface that makes sense for accessing a KiGG site. This can be changed to throw an exception if the result indicates the call was not successful.

public class KiggBot 
{
    private static readonly HttpDictionary mvcAsyncPost = new HttpDictionary
    {
        {"__MVCASYNCPOST", "true"}
    };

    private WebSession session;
    private string url;

    public KiggBot(string url)
    {
        if (!url.EndsWith("/"))
            url += "/";

        this.url = url;
        session = new WebSession();
    }

    public bool Login(string userName, string password)
    {
        HttpDictionary dictionary = new HttpDictionary { 
            {"userName", userName}, {"password", password}};

        var result = KiggResult.Create(
            session.Post(url + "Login", dictionary));
        
        return result.IsSuccessful;
    }

    public bool Logout()
    {
        var result = KiggResult.Create(
            session.Post(url + "Logout", mvcAsyncPost));

        return result.IsSuccessful;
    }

    public bool Publish()
    {
        var result = KiggResult.Create(
            session.Post(url + "Publish", mvcAsyncPost));

        return result.IsSuccessful;
    }
}

With these classes, it’s fairly easy to create an automated publishing program. At the very least, write a console app and use scheduler to fire it daily.

KiggBot bot = new KiggBot("http://localhost:1736");
bot.Login("admin", "password");
bot.Publish();
bot.Logout();
Hope that helps. If anyone makes a cool GUI for this and releases it, might I recommend PiGG?Note: Cross posted from KodefuGuru.
Permalink
Posted on Tuesday, July 14, 2009 6:18 PM | Back to top


Comments on this post: Automating KiGG Publishing

# re: Automating KiGG Publishing
Requesting Gravatar...
Nice post,

you made it! It's a wise idea to automaticly relase posts

Thanks
Left by web development company on Aug 18, 2009 12:23 PM

# Automating KiGG Publishing
Requesting Gravatar...
great! I tried and it works perfectly :)
thanks
Left by Web Design Company on Oct 22, 2010 10:41 AM

# re: Automating KiGG Publishing
Requesting Gravatar...
This is because I wanted to treat the form data as a dictionary rather than a string.
Left by urdu and hindi sms on Jan 28, 2011 4:15 AM

# re: Automating KiGG Publishing
Requesting Gravatar...
I wish to say thank you for an exciting web site about a subject I have had an curiosity in for some time now. I have been looking in and reading the commentary and only wanted to voice my many thanks for giving me some pretty exciting reading material.
Left by quick weight loss on Feb 16, 2011 11:57 PM

# re: Automating KiGG Publishing
Requesting Gravatar...
The cook wares that depend on the pressure of steam created by the water boiling inside them are categorized as pressure cookware. The pressure cookware saves time enabling fast cooking and also banks the costly fuel. The food being cooked in pressure cookware may and may not need to be immersed in water. Pressure cook wares like pressure cooker need the food (rice, lentils, seeds et al) to be immersed in it and there are pressure pans which can cook the food (momos, Idli etc) without immersing them.
Left by PRESSURE Cookware on Apr 04, 2011 3:35 AM

# re: Automating KiGG Publishing
Requesting Gravatar...
Hey - great blog, just looking around some blogs, seems a pretty good platform you're utilizing.
Left by Subrata Roy on Oct 10, 2011 2:03 AM

Your comment:
 (will show your gravatar)


Copyright © Chris Eargle | Powered by: GeeksWithBlogs.net