.NET Performance Blog

March 28, 2011

OpenFaq (Part 2): Business Requirements in Code

Filed under: .NET,ASP.NET MVC,BDD,General,TDD — Eric P @ 3:38 am

Previous posts in OpenFaq series
OpenFaq (Part1): The beginning

There has been a lot of discussion about self documenting code. You can use good class/method names, proper unit tests and comments to make it clear what the code does.
But how do you specify and enforce what the code is SUPPOSED to do from business perspective, versus what it does? Usually there is a separate Business requirements document with a set of User Acceptance criteria that is used to QA the application. What if you could automate user acceptance criteria tests and run them from beginning (even before writing any code). That’s where BDD comes in.

The Joy of BDD

Brandon Santrom has a nice presentation on using SpecFlow, WatiN with MVC to write acceptance tests:

Video is here:
http://channel9.msdn.com/Series/mvcConf/mvcConf-2-Brandom-Satrom-BDD-in-ASPNET-MVC-using-SpecFlow-WatiN-and-WatiN-Test-Helpers

MSDN article (that covers a bit different scenario) is here:
http://msdn.microsoft.com/en-us/magazine/gg490346.aspx

In the presentations, Brandon Santrom promotes the following way of doing devlelopment:
BDD Approach

The first module I wanted to implement for OpenFaq was UserModule which will handle CRUD for User and related objects and will also implement Custom Membership Provider that will use EF4.
But before doing that, following BDD, I will write some UAC tests to make sure that user can login and register using default Membership Provider that can be used with MVC 3.

Setting up solution

Before writing any tests I am going to setup a new solution/project for OpenFaq.
Following YAGNI, I will only create projects as I need them.

To Start I am going to have 3 projects:

  • OpenFaq.Web – MVC 3 project
  • OpenFaq.Web.Tests – unit tests for controllers
  • OpenFaq.AcceptanceTests – acceptance tests

I also setup NUGet package manager that ScottGu mentioned many times in his blog.
I used it to add references to WatiN, SpecFlow for “OpenFaq.AcceptanceTests” project. I noticed that NUGet was installing packages into “packages” directory under solution directory. Since my project structure is:

\src – solution goes here
\lib – external dependencies go here

So I changed NuGet package directory to put packages into “\lib” using instructions here:
http://stackoverflow.com/questions/4092759/is-it-possible-to-change-the-location-of-packages-for-nuget

Login & Register – Starting BDD

Before writing any new code I create the following two Features using SpecFlow:

Login Feature

Feature: Login a site user
	In order to use OpenFaq features
	As a site user
	I want to be able to login to the OpenFaq site

Scenario: Login with valid information
	Given I am on the site home page
	When I click the "Log On" link
	And I complete the form with the following information:
		| Field           | Value							|
		| UserName        | openfaquser1					|
		| Password        | password1						|
	And I click the "Log On" button
	Then I should see a link with the text "Log Off" on the page


Scenario: Login with invalid information
	Given I am on the site home page
	When I click the "Log On" link
	And I complete the form with the following information:
		| Field           | Value							|
		| UserName        | unknowuser						|
		| Password        | password1						|
	And I click the "Log On" button
	Then I should see a validation summary "Login was unsuccessful"
	And  I should see a field error "The user name or password provided is incorrect"

Register Feature

Feature: Register a new site user
	In order to ask/answer questions
	As a site user
	I want to be be able to register new account

@mytag
Scenario: Register with valid information
	Given I am on the site home page
		And I click the "Log On" link
		And I click the "Register" link	
	When I enter a random username
		And I complete the form with the following information:
			| Field				| Value					|
			| Email				| openfaquser@test.com	|
			| Password			| p@bla12				|
			| ConfirmPassword	| p@bla12				|
		And I click the "Register" button
	Then I should see a link with the text "Log Off" on the page

SpecFlow uses language called Gerhkin to describe business requirements. Not quite English and not quite code it is meant to bridge a gap between software developers and business analysts.
When you create SpecFlow feature files above, SpecFlow automatically creates CS files that interpret Gerhkin into C# code. When you run the tests, each step Given, When, Then… expects there to be a function that actually implements this functionality.

So to implement “Scenario: Login with valid information”, here is the file you would provide with Step Definitions:

using Microsoft.VisualStudio.TestTools.UnitTesting;
using OpenFaq.AcceptanceTests.StepHelpers;
using TechTalk.SpecFlow;
using WatiN.Core;

namespace OpenFaq.AcceptanceTests.Steps
{
	[Binding]
	public class Login
	{

		[Given(@"I am on the site home page")]
		public void GivenIAmOnTheSiteHomePage()
		{
			WebBrowser.Current.GoTo("http://localhost/OpenFaq.Web");
		}

		[When("I click the \"(.*)\" link")]
		public void WhenIClickALinkNamed(string linkName)
		{
			var link = WebBrowser.Current.Link(Find.ByText(linkName));

			if (!link.Exists)
				Assert.Fail(string.Format("Could not find '{0}' link on the page", linkName));

			link.Click();
		}

		[When(@"I complete the form with the following information:")]
		public void WhenICompleteTheFormWithTheFollowingInformation(Table table)
		{
			foreach (var tableRow in table.Rows)
			{
				var field = WebBrowser.Current.TextField(Find.ByName(tableRow["Field"]));

				if (!field.Exists)
					Assert.Fail(string.Format("Could not find {0} field on the page", field));

				field.TypeText(tableRow["Value"]);
			}
		}

		[When("I click the \"(.*)\" button")]
		public void WhenIClickAButtonWithValue(string buttonValue)
		{
			var button = WebBrowser.Current.Button(Find.ByValue(buttonValue));

			if (!button.Exists)
				Assert.Fail(string.Format("Could not find '{0}' button on the page", buttonValue));

			button.Click();
		}

		[Then("I should see a link with the text \"(.*)\" on the page")]
		public void ThenIShouldSeeALinkWithTheTextOnThePage(string linkText)
		{
			Assert.IsTrue(WebBrowser.Current.Link(Find.ByText(linkText)).Exists,
				string.Format("The following link text was not found on the page: {0}", linkText));
		}
	}
}

You may have noticed that in many cases instead of specifying actual text, I use a regular expression to pass Button value or link text to the function.

[Then("I should see a link with the text \"(.*)\" on the page")]

//instead of

[Then("I should see a link with the text \"Log On\" on the page")]

This will allow me to re-use the steps, in many different scenarios.

MembershipProvider – where art thou

When I ran acceptance tests I received errors having to do with MembershipProvider not being setup. To set it up on my local Sql Server DB I used steps here:
http://helios.ca/2009/04/22/aspnet-mvc-sqlmembershipprovider/

Now all acceptance tests have passed.

Tests Passed

Yes, I do use Resharper 5.1.

Did they live happily ever after?

Not quite yet…

In next part of this series I will replace SqlMembershipProvider with the one that supports EF 4.0 Code First.

For now you can get all the code from here:
http://openfaq.codeplex.com/releases/view/63346

Current project road map is here:
http://openfaq.codeplex.com/wikipage?title=Road%20Map%20%26%20Progress

Advertisements

March 27, 2011

OpenFaq (Part 1): The beginning

Filed under: .NET,ASP.NET,ASP.NET MVC,DDD,General,TDD — Eric P @ 1:43 pm

It is time to learn some new technologies.

For this task I am going to write FAQ applicaton called OpenFaq.

OpenFaq – The future of FAQ

Whenever we buy any new product there is usually some questions or issues that come up, that may not be covered in manual or by asking the uncle who (supposedly) knows everything. How do I …? Why am I getting this f#$x error? etc…

In some cases you may go to product’s website and try to find an answer there. In about 5 minutes (depending on how quickly you reach the boiling point) you give up and just do a google search.

Why are so many sites so bad in helping you find what you need?

The problem(s) this application will try to solve is:
On many sites FAQ is created early on and not frequently updated. It quickly becomes forgotten.
The questions are grouped according to how website administrator feels like they should be grouped. Not based on frequency of asking.
User interaction is limited to reading an answer

The Solution
Make FAQ dynamic, so admin can edit questions and answers without doing rollout
Get users involved with asking, answering, voting and comments (similar to stack overflow)
Use voting and other statistics to determine which questions are frequent/popular and which are not

Some Business Requirements

Some preliminary requirements for this application are:
* User can login
* User can post question
* User can answer question
* User can comment on question and answer
* User can vote on question and answer
* User can use keyword search to quickly find questions, answers

Maybe a touch of Technology

In this series I will try out some new technologies from MS and use some of the latest methodologies in application development.

For a while now I have been reading about many new technologies and approaches for software development. Here is the list that I plan to use for OpenFaq.

Approaches

  • BDD – behavior driven development
  • TDD – test driven development

Principles

  • YAGNI – you are not going to need it
  • KISS – keep it simple stupid
  • DRY – don’t repeat yourself

Technology

For this project I am going to use Microsoft stack.

  • ORM – EF 4 Code First
  • Web Framework – MVC 3
  • Template Engine – Razor
  • Package/Dependecy Manager – NuGet
  • BDD – SpecFlow
  • UI Testing/Acceptance test – WatiN
  • Unit testing – MS Test
  • Version Control – Mercurial
  • Project Site – Codeplex.com

February 2, 2010

ASP.NET GridView – edit records using JQuery Dialog

Filed under: .NET,ASP.NET,JQuery — Eric P @ 6:25 am

There are many ways in which you could modify modular data in Grid View:
1. Inline by making rows editable when u click on Edit
2. By using separate pages for Edit
3. By using popups/dialogs for adding/editing

In this post I will show how to implement the 3rd approach using JQuery UI Dialog for editing/adding data in ASP.NET GridView.

The following features have been implemented:
1. List customers
2. Add customer – Save/Cancel
3. Edit customer – Save/Cancel
4. Delete customer
5. Server side validation
6. Edit dialogs appear next to trigger links

You can check out the demo here:
http://samples.entechsolutions.com/GridWithEditDialog

The code is available here:
http://samples.entechsolutions.com/Downloads/GridWithEditDialog.zip

Implementation

Data source

The grid lists customers which are stored in list which is initialized for each new session. This way each user trying out this sample will not affect other users.
CustomerService is a class wrapped around list of customers that allows to add/edit/list customers:

public class CustomerService
{
	private List<Customer> Customers
	{
		get
		{
			List<Customer> customers;
			if (HttpContext.Current.Session["Customers"] != null)
			{
				customers = (List<Customer>)HttpContext.Current.Session["Customers"];
			}
			else
			{
				//Create customer data store and save in session
				customers = new List<Customer>();

				InitCustomerData(customers);

				HttpContext.Current.Session["Customers"] = customers;
			}

			return customers;
		}
	}


	public Customer GetByID(int customerID)
	{
		return this.Customers.AsQueryable().First(customer => customer.ID == customerID);
	}


        ...
        //Add/Update/Delete/GetAll

Add/Edit dialog

There are two main operations that are handled using JQuery dialog
* Add Customer
* Edit Customer

In both cases the same dialog is used, as well as the same content div and Update Panel

	<div id="divEditCustomerDlgContainer">	
			<div id="divEditCustomer" style="display:none">
					
				<asp:UpdatePanel ID="upnlEditCustomer" runat="server">
					<ContentTemplate>
						<asp:PlaceHolder ID="phrEditCustomer" runat="server">
							<table cellpadding="3" cellspacing="1">
							<tr>
								<td>
									*First Name:
								</td>
								<td>
									<asp:TextBox ID="txtFirstName" Columns="40" MaxLength="50" runat="server" />
								        ...
								</td>
							</tr>
							<tr>
								<td>
									*Last Name:
								</td>
								...
							</tr>
							<tr>
								<td colspan="2" align="right">
									<asp:Button ID="btnSave" onclick="btnSave_Click" Text="Save" runat="server" />
									<asp:Button ID="btnCancel" onclick="btnCancel_Click" onClientClick="closeDialog()" CausesValidation="false" Text="Cancel" runat="server" />
								</td>
							</tr>
							</table>
								
						</asp:PlaceHolder>
					</ContentTemplate>
							
				</asp:UpdatePanel>
			
			</div>
		</div>	<!-- divEditCustomerDlgContainer -->

You may notice that there are two divs around UpdatePanel – divEditCustomerDlgContainer and divEditCustomer. The reason for that is that when JQuery dialog opens – it is added after FORM tag in dom. That disables any ASP.NET submits and server side validation. The issue is described here:
http://www.trentjones.net/index.php/2009/03/jqueryui-dialog-with-aspnet-empty-post-values

To fix the issue I added a handler for JQuery Dialog open event that takes JQuery dialog DOM object and inserts it as a child of “divEditCustomerDlgContaine”. Note that JQuery dialog references divEditCustomer.

	<script type="text/javascript">
		$(document).ready(function() {
			$("#divEditCustomer").dialog({
				autoOpen: false,
				modal: true,
				minHeight: 20,
				height: 'auto',
				width: 'auto',
				resizable: false,
				open: function(event, ui) {
					$(this).parent().appendTo("#divEditCustomerDlgContainer");    //This is where JQuery dialog is added to DlgContainer
				},
			});
		});
     ...

When user clicks on Add/Edit customer links the following steps are followed:
1. JQuery dialog is opened
2. Content of dialog is overlayed with AJAX indicator and white background – BlockUI is used for that purpose
3. AJAX call is made that triggers refresh of upnlEditContent
4. On server side – dialog content is cleared for adding or loaded for editing.
5. Server side uses RegisterStartupScript to trigger a javascript call to UnlockDialog
6. Dialog is unlocked and available to user for editing

With this approach user sees the dialog right away and indicator tells him that data is being loaded. In most cases it should be pretty fast from click to being able to edit.

Positioning Dalog

You may notice that dialog appears to the right of the link that you click on. For that JQuery position() functionality is used:

	function openDialog(title, linkID) {
	
		var pos = $("#" + linkID).position();
		var top = pos.top;
		var left = pos.left + $("#" + linkID).width() + 10;
		
		
		$("#divEditCustomer").dialog("option", "title", title);
		$("#divEditCustomer").dialog("option", "position", [left, top]);
		
		$("#divEditCustomer").dialog('open');
	}

linkID is the ClientID of the link. Using the position() u can display dialog anywhere on the page or in relation to another control.

Running Client Side code using RegisterStartupScript in Async

To run some javascript script when response comes back from asynchronous request – function ScriptManager.RegisterStartupScript can be used.

The issue is that the first parameter in:
RegisterStartupScript Method (Control, Type, String, String, Boolean)

must be a control in update panel that will be rendered on the current request. In the “List Customers” page there are two update panels -“upnlEditCustomers” and “upnlCustomers” (for grid view).
The first panel is always updated on any async request (UpdateMode=”Always” by default). Originally I though to make it conditional which would be more efficient, but there were some issues to resolve and I decided it wasn’t worth the trouble in this case. As for “upnlCustomers” – we definitely don’t want that re-rendered on every async request – like opening/canceling the dialog, so this one is set to update conditionally.

To get RegisterStartupScript to work without worrying that triggering conrol is rendered in current request, I added another UpdatePanel whose sole purpose is to facilitate JavaScript calls from Async requests. This panel has an empty placeholder that’s used as a control in RegisterStartupScript:


	<asp:UpdatePanel ID="upnlJsRunner" UpdateMode="Always" runat="server">
		<ContentTemplate>
			<asp:PlaceHolder ID="phrJsRunner" runat="server"></asp:PlaceHolder>
		</ContentTemplate>
	</asp:UpdatePanel>

On server side there is a function RegisterStartupScript(key, script) that uses update panel above:

	private void RegisterStartupScript(string key, string script)
	{
		ScriptManager.RegisterStartupScript(phrJsRunner, phrJsRunner.GetType(), key, script, true);
	}

        private void TriggerClientGridRefresh()
	{
		string script = "__doPostBack(\"" + btnRefreshGrid.ClientID + "\", \"\");";
		RegisterStartupScript("jsGridRefresh", script);   //Trigger async grid refresh on Save
	}

	private void HideEditCustomer()
	{
		ClearEditCustomerForm();
		RegisterStartupScript("jsCloseDialg", "closeDialog();");    //Close dialog on client side
	}

Clear data in dialog

When user clicks on “Add Customer” – data in dialog is cleared and all validators are reset to Valid. For this I wrote a function that loops through all the controls in dialog and then performs a certain action on the controls of certain type:

	private void ClearEditCustomerForm()
	{
		//Empty out text boxes
		var textBoxes=new List<Control>();
		FindControlsOfType(this.phrEditCustomer, typeof(TextBox), textBoxes);
		
		foreach (TextBox textBox in textBoxes)
			textBox.Text = "";

		//Clear validators
		var validators=new List<Control>();
		FindControlsOfType(this.phrEditCustomer, typeof(BaseValidator), validators);
	
		foreach (BaseValidator validator in validators)
			validator.IsValid = true;
	}


	static public void FindControlsOfType(Control root, Type controlType, List<Control> list)
	{
		if (root.GetType() == controlType || root.GetType().IsSubclassOf(controlType))
		{
			list.Add(root);
		}

		//Skip input controls
		if (!root.HasControls())
			return;

		foreach (Control control in root.Controls)
		{
			FindControlsOfType(control, controlType, list);
		}
	}

Right now it only handles TextBoxes and Validators, but it can be easily expanded to support Text Areas, Drop Downs, etc…

JQuery Includes

This sample doesn’t include the files for JQuery UI and JQuery base library. Instead those are referenced at the google AJAX CDN.
The approach for including JQuery UI from Google AJAX CDN is covered here: http://encosia.com/2009/10/11/do-you-know-about-this-undocumented-google-cdn-feature/

<link href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.2/themes/start/jquery-ui.css" type="text/css" rel="Stylesheet" />
	
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.0/jquery.min.js"></script>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.2/jquery-ui.min.js"></script>

This makes the application and code download a lot smaller (a lot less files), so it is great for sample projects.

August 9, 2006

ENTechSolutions.com launched, XLib is available for download

Filed under: .NET,General,XLib Framework — Eric P @ 1:51 pm

I finally launched a web site for my consulting company:

http://www.entechsolutions.com

At the same time I packaged BETA versions of XLib and XWebSiteTemplate.  You can find these products as well as AutoSuggestBox control in the ‘Developer Corner’ section of the web site.

July 18, 2006

AJAX Modal Dialog using UpdatePanel

Filed under: .NET,XLib Framework — Eric P @ 5:57 pm

Please see complete post here:

http://www.entechsolutions.com/Blog/ViewPost.aspx?PostID=6

May 17, 2006

.NET Web Service For Email Labs

Filed under: .NET,General — Eric P @ 10:53 am

Recently, one of the clients of my consulting business asked for an integrated email solution. I did some research and found that EmailLabs would fit client needs. They are one of the few companies that provide an API to their email marketing solution. The only issue is that their API is very basic — it works by sending and receiving XML messages through HTTP.

To simplify things I wrote .NET web service that wraps around HTTP messages. It allows .NET developers to quickly tap in into list management, message building and sending functionality provided by EmailLabs. The web service is available at the following location:

http://samples.entechsolutions.com/EmailLabsWebService/Service.asmx

This web service encapsulates about 80% of functionality of EmailLabs API. For this release I concentrated on most useful functions. I didn’t implement functions used for managing Filters and adding/editing Demographics.

To demo the API you can contact EmailLabs to setup a demo account. For testing they let you send e-mails with up to 50 recipients. Or you can send me an e-mail I will give you access to my demo account.

Security Considerations

For API integration EmailLabs provides IP security. For every mail list you can specify a list of IP Addresses that can contact EmailLabs API. To test .NET web service you can provide security the following 2 ways:

A. Disable IP Security for the list

  1. Edit mailing list in EmailLabs admin
  2. Uncheck the ‘Limit API access to these addresses:’ check box
  3. Save

For this approach you may want to create a new test list, so security for other lists stays the same.
B. Add IP Address ‘66.129.79.80’ to secure IP addresses list

  1. Edit mailing list in EmailLabs admin
  2. Check the ‘Limit API access to these addresses:’ check box.
    Add IP addresses ‘66.129.79.80’ to comma delimited list of allowed IP addresses.
  3. Save

This IP address is dynamic, so it is possible that it may change. Let me know if you get authentication error using this approach.

FYI: If you create a new mailing list using Web Service API – it will automatically add appropriate IP address to the list of allowed IP addresses.

April 28, 2006

Editable GridView with Add New/Insert button

Filed under: .NET,General — Eric P @ 6:05 pm

Please see the full post here:

http://www.entechsolutions.com/Blog/ViewPost.aspx?PostID=5 

ObjectDataSource: UpdateParameters ignored if DataObjectTypeName is used

Filed under: .NET,Bug Fix — Eric P @ 2:28 pm

If you specify 'DataObjectTypeName' parameter in ObjectDataSource — UpdateParameters get ignored. So if you have:

<asp:ObjectDataSource ID="objectDataSource" runat="server"

DataObjectTypeName="Customer" InsertMethod="Save"
UpdateMethod="Save"
DeleteMethod="Delete"
SelectMethod="LoadList"
TypeName="CustomerLogic">

<UpdateParameters>

<asp:Parameter Name="CategoryID" DefaultValue="5"/>

</UpdateParameters>

</asp:ObjectDataSource>

When you do update — parameter 'CategoryID' is not bind to Customer.CategoryID.

The solution to this problem is to add parameters dynamically in Updating event handler:

protected void objectDataSource_Updating(object sender, ObjectDataSourceMethodEventArgs e)

{

Customer customer=(Customer)e.InputParameters[0];

customer.CategoryID=5;

}

April 21, 2006

MSDN documentation is incorrect on using DataObjectTypeName with SelectMethod

Filed under: .NET — Eric P @ 6:39 pm

When reading MSDN documentation for ObjectDataSource.SelectMethod I found the following paragraph.

If the DataObjectTypeName property is set, the method is resolved in a different way. The ObjectDataSource looks for a method with the name that is specified in the SelectMethod property that takes one parameter of the type that is specified in the DataObjectTypeName property. In this case, the name of the parameter does not matter.

I tried it, but it doesn't look like it works. ObjectDataSource still looks for a method that has multiple SelectParameters arguements and not one 'DataObjectTypeName' arguement.

This is inconsistent with documentation for 'ObjectDataSource.DataObjectTypeName' where it says that:

Instead of specifying several parameters that are passed to the Update, Insert, and Delete methods, you can create one object that aggregates several data field values. This one object is passed to the methods, instead of several parameters.

Since my 'Select' method can have dozens of parameters I would rather use a class then dozens of arguements. I think the reason it works diferently from (Update/Insert/Delete) is that in some cases Search Arguements may be different from the ones required for Insert/Update/Delete. For example class Customer may have 'DateCreated', but CustomSearch will have 'DateCreatedFrom' and 'DateCreatedTo'.

The solution would be to provide another ObjectDataSource property 'SelectDataObjectTypeName' and hopefully MS can implement it for next update. For now they just need to fix up the documentation.

Blog at WordPress.com.