Thursday, October 25, 2012

Automating Website Authentication

Recently I had to implement automated logging on the website. In my particular case, that was Yahoo.com website, so the code snippets will be specific to this site. It should not be hard to modify them for other purposes. I developed two separate ways to achieve that, the first one has more code and is more complex (have to subscribe to two events and make more logical checks), but I figured it out first. It makes use of the WebBrowser class.

Create an instance of the WebBrowser and subscribe to Navigated and DocumentCompleted events

_browser = new WebBrowser();
_browser.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(browser_DocumentCompleted);
_browser.Navigated += new WebBrowserNavigatedEventHandler(browser_Navigated);

On a timeline, first meaningful event that will be caught is browser_DocumentCompleted on the login.yahoo.com. The code then will analyse the controls on the page. For successful operation, I need to know actual names of the login and password input elements. I find them by name, and set the values to actual login and password. Then I simulate the click on the login button.

Next meaningful event is browser_Navigated on my.yahoo.com page - see below.

After that, I'll point the browser to the url of the document I want to read or download. I'll catch browser_DocumentCompleted again, on that page, and read the contents using the WebBrowser.Document.Body.InnerText (end of the code snippet).

void browser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
 //loaded the Yahoo login page
 if (_browser.Url.AbsoluteUri.Contains(LoginUrl))
 {
  if (_browser.Document != null)
  {
   //Find and fill the "username" textbox
   HtmlElementCollection collection = _browser.Document.GetElementsByTagName("input");
   foreach (HtmlElement element in collection)
   {
    string name = element.GetAttribute("id");
    if (name == "username")
    {
     element.SetAttribute("value", _login);
     break;
    }
   }

   //Find and fill the "password" field
   foreach (HtmlElement element in collection)
   {
    string name = element.GetAttribute("id");
    if (name == "passwd")
    {
     element.SetAttribute("value", _password);
     break;
    }
   }

   //Submit the form
   collection = _browser.Document.GetElementsByTagName("button");
   foreach (HtmlElement element in collection)
   {
    string name = element.GetAttribute("id");
    if (name == ".save")
    {
     element.InvokeMember("click");
     break;
    }
   }
  }
 }
 
 //downloaded "quote.csv"
 if(_browser.Url.AbsoluteUri.Contains(".csv"))
 {
  if (_browser.Document != null && _browser.Document.Body != null)
  {
   string s = _browser.Document.Body.InnerText;
  }
 }
}

Here I actually copy the cookies, but that is not necessary. The WebBrowser will keep them internally and use them. The purpose of this code is to check if the browser is redirected to "my.yahoo.com", which is the indication of successful login. Further logic may be applied from here.
void browser_Navigated(object sender, WebBrowserNavigatedEventArgs e)
{
 //Successful login takes to "my.yahoo.com"
 if (_browser.Url.AbsoluteUri.Contains(MyYahoo))
 {
  if (_browser.Document != null && !String.IsNullOrEmpty(_browser.Document.Cookie))
  {
   _cookies = _browser.Document.Cookie;
  }
 }
}

The second approach is shorted, but it took me longer to figure out. Here I have to explicitly use the CookieContainer to save the cookies "harvested" by the HttpWebRequest which does the login, and use them in the HttpWebRequest which asks for the file after authentication. Of course, I still need to know what are the names of login and password elements, because I'm sending the values in the POST data.

Step one - authentication

string strPostData = String.Format("login={0}&passwd={1}", _login, _password);

// Setup the http request.
HttpWebRequest wrWebRequest = WebRequest.Create(LoginUrl) as HttpWebRequest;
wrWebRequest.Method = "POST";
wrWebRequest.ContentLength = strPostData.Length;
wrWebRequest.ContentType = "application/x-www-form-urlencoded";
_yahooContainer = new CookieContainer();
wrWebRequest.CookieContainer = _yahooContainer;

// Post to the login form.
using (StreamWriter swRequestWriter = new StreamWriter(wrWebRequest.GetRequestStream()))
{
 swRequestWriter.Write(strPostData);
 swRequestWriter.Close();           
}

// Get the response.
HttpWebResponse hwrWebResponse = (HttpWebResponse)wrWebRequest.GetResponse();

Step two - accessing data using the cookies.

HttpWebRequest req = (HttpWebRequest)WebRequest.Create(_downloadUrl);
req.CookieContainer = _yahooContainer;
HttpWebResponse resp = (HttpWebResponse)req.GetResponse();

using(StreamReader streamReader = new StreamReader(resp.GetResponseStream()))
{
 string t = streamReader.ReadToEnd();
}

References:

WebBrowser control
submit a form data from external address !
C# Login to Website via program
how to login to yahoo website programatically by . Also posted on my website

Sunday, October 14, 2012

Crystal Reports, C#, Object as Data Source

Based on the Ping example from one of the recent posts, I'm continuing it with the Crystal Reports example, because I have never used Crystal Reports until now. So consider that the following class was added to the solution.

public class PingResult
{
 public string sPacketsSent;
 public string sPacketsReceived;
 public string sPacketsLost;
 public string sPacketTime;
}

An instance of the class is created and populated with results when the ping command runs and its results are parsed. So I have one of the simplest possible objects to use as a source for a report. The next step is to add a Crystal Report to the application. Visual Studio 2010 has an item to add called "Crystal Report", but they are not installed.

Crystal Report Online Template

When I select this item, I'm prompted with a download screen.

Download Crystal Reports

Installation is simple - just following the instructions. I chose the standard version, and the download size is 288MB. After a few short hours, I have a Crystal Report called pingReport.mht in my solution. I have an option to configure my report using the wizard, which I'm doing by choosing the following options:

On the first page, Using the Report Wizard, and Standard layout.

Create a New Crystal Report Document

On the next page, I choose to populate my report from .NET Object in project data. My PingResult class is in the list, and I move it to the Selected Tables.

Choose the data you want to report on

Then I choose the fields to display, of which I select all.

Choose the information to display on the report

I skip Grouping, Report Selection and Report Style, leaving default values. Now I have my report editor. I only want to do a little change - make the headers human readable, so I edit them in the following manner

Edit text object

Now some tricks: when I build my solution, I get the following error:

Warning 1 The referenced assembly "CrystalDecisions.CrystalReports.Engine, Version=13.0.2000.0, Culture=neutral, PublicKeyToken=692fbea5521e1304, processorArchitecture=MSIL" could not be resolved because it has a dependency on "System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" which is not in the currently targeted framework ".NETFramework,Version=v4.0,Profile=Client". Please remove references to assemblies not in the targeted framework or consider retargeting your project. PingTest

This is quite obvious, I need to add a reference to System.Web, but to do that I need to first change the Target Framework default setting of .NET Framework 4 Client Profile to just .NET Framework 4. Now the project builds.

In my toolbox I now have the pingReport1 component, which I add to the form.

pingReport1

I also need a report viewer, which I also add

CrystalReportViewer

The final effort: connect the report with the object that contains data. Here's how:

pingReport1 myReport = new pingReport1();
myReport.SetDataSource(new[] { pingResult });
pingReportViewer1.ReportSource = myReport;

Looks simple, just note how the objects are wrapped into array. This is important.

And the final trick, when I run the application, I get the FileNotFound exception

FileNotFoundException

which is resolved by adding the useLegacyV2RuntimeActivationPolicy="true" parameter to the startup node of my app.config which now looks like the following

<?xml version="1.0"?>
<configuration>
<startup  useLegacyV2RuntimeActivationPolicy="true">
  <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
</startup>
</configuration>

At this point I consider my small example complete.

Complete

References

Creating Crystal Reports using C# with Datasets
Very Odd situation with CrystalReport and/or Visual studio 2010 I don't know maybe .Net Framework
Can crystal reports get data from an object data source? by . Also posted on my website

Sunday, October 7, 2012

A Simple Show/Hide Technique with jQuery

Today I learned a simple technique to show/hide parts of content on the page. In my sample application, I'm using it to display a part of a long blog post, and then at the end of the part to display a button that will show the rest of the post. The button initially says "Show more". When the button is clicked, it displays the previously hidden part and its text changes to "Show less". So, in essence, it toggles the display state of the div it is connected to, and its own text.

Additionally, I'm displaying posts dynamically, so for each post there is a div with the first part of the post, the div with the rest, and the toggle button. Here's how it works:

For each blog post in the model a div is created for the rest of the post, and a button. The ID for both is assigned dynamically using a simple counter.

@model Recipes.ViewModels.BlogViewModel
@{ int i = 0; }
@foreach (var post in Model.Posts)
{
    string divID = "hide" + i.ToString();
    string btnID = "btn" + i.ToString();
    <div id="middlecolumn">
        <div class="blogcontainer">
            <h3 class="title"><a href="#">@post.Title</a></h3>
            <div class="info"><span class="submitted">Submitted on @post.DateCreated</span></div>
            @MvcHtmlString.Create(post.BriefContent)
            <div class="buttonlink"><a id=@btnID href="javascript:toggleDiv('@divID', '@btnID');">Show more</a></div>
            <div id=@divID  style="display:none">@MvcHtmlString.Create(post.RestOfContent)</div>
        </div>
    </div>
    i = i + 1;
}

The javaScript function toggleDiv() takes two parameters: div ID and button ID. First the function toggles the div display property by using the jQuery function toggle(). Next, based on the div display state, the button text is set to one of the two possible values. And that's it.

<script type="text/javascript">
<!--
    function toggleDiv(divId, btnId) {
        $("#" + divId).toggle();
        if ($("#" + divId).css('display') == 'none')
        {$("#" + btnId).html('Show more'); }
        else
        { $("#" + btnId).html('Show less'); }
    }
-->
</script>

Here's the example of the page with the blog post

Rest of the post hidden

Rest of the post expanded

References

How to hide, show, or toggle your div
How to hide, show, or toggle your div with jQuery by . Also posted on my website

Thursday, October 4, 2012

jQuery Show/Hide Toggle

When I have a long list on a web page, I would like to give the user an option to hide it. Turns out that is simple to do with a little bit of jQuery. All the contents that I would like to hide or show would go into a div element. Next to it, I will add a link to toggle the hide/show behaviour. Here's how it looks in my code:

<div class="buttonlink">
 <a href="#" class="show_hide">Show/Hide List</a>
</div>
<div class="slidingDiv">
 @foreach (var item in Model.Recipes)
 {
  <ol>
   <li class="styled">
    <div class="display-button">@Html.ActionLink("Edit", "Edit", new { id = item.RecipeID })
    </div>
    <div class="display-button">@Html.ActionLink("Details", "Details", new { id = item.RecipeID })</div>
    <div class="display-button">@Html.ActionLink("Delete", "Delete", new { id = item.RecipeID })</div>
    <div class="display-info">@item.RecipeName</div>
   </li>
  </ol>
 }
 <a href="#" class="show_hide">Hide</a>
</div>

Here is the jQuery function that is called when the link is clicked

$(document).ready(function () {

 $(".slidingDiv").hide();
 $(".show_hide").show();

 $('.show_hide').click(function () {
  $(".slidingDiv").slideToggle();
 });

});

And the styles for the classes that I've just introduced above.

.slidingDiv {
    height:300px;
    padding:20px;
    margin-top:35px;
    margin-bottom:10px;
}
 
.show_hide {
    display:none;
}

That's it.

Shown

Hidden

References

jquery show/hide div
Simple jQuery Show/Hide Div
Fiddle
by . Also posted on my website