Pages

Tuesday, June 21, 2011

Using Google C++ Test Framework with Visual Studio 2008

I recently needed to add a testing framework to a Visual Studio 2008 C++ project and I stumbled on Google's C+ Testing Framework. A lot of people seemed to be happy with it and I figured I'd give it a try, if it's written by Google it can't be bad :) ^_^!

It's been a couple of years since I've done any C++ testing and as always: StackOverflow had the answer for me. The guide is for Visual Studio 2005, but it works just as good with Visual Studio 2008. I was even able to get it all to build and run in 64 bit mode, so I was a pretty happy camper!

Here are the instructions that worked for me with some minor modifications (verbatim from nonsensical101's SO answer):

(These instructions get the testing framework working for the Debug configuration. It should be pretty trivial to apply the same process to the Release configuration.)

Get Google C++ Testing Framework

Download the latest gtest framework
Unzip to C:\gtest

Build the Framework Libraries

Open C:\gtest\msvc\gtest.sln in Visual Studio
Set Configuration to "Debug"
Build Solution

Create and Configure Your Test Project

Create a new solution and choose the template Visual C++ > Win32 > Win32 Console Application
** If you choose to create an empty project, you will not have Stdafx.h (you may not actually need it anyway).
Right click the newly created project and choose Properties
Change Configuration to Debug.
Configuration Properties > C/C++ > General > Additional Include Directories: Add C:\gtest\include
Configuration Properties > C/C++ > Code Generation > Runtime Library: If your code links to a runtime DLL, choose Multi-threaded Debug DLL (/MDd). If not, choose Multi-threaded Debug (/MTd).
** Note that both your project and gtest have to be build with the same Code Generation options: i.e. both have to be /MDd or /MTd
Configuration Properties > Linker > General > Additional Library Directories: Add C:\gtest\msvc\gtest\Debug
Configuration Properties > Linker > Input > Additional Dependencies: Add gtestd.lib

Verifying Everything Works

Open the cpp in your Test Project containing the main() function.
Paste the following code:


// #include "stdafx.h" // <-- I didn't need stdafx.h
#include

#include "gtest/gtest.h"

TEST(sample_test_case, sample_test)
{
EXPECT_EQ(1, 1);
}

int main(int argc, char** argv)
{
testing::InitGoogleTest(&argc, argv);
RUN_ALL_TESTS();
std::getchar(); // keep console window open until Return keystroke
}

Debug > Start Debugging

If everything worked, you should see the console window appear and show you the unit test results.



Indeed, everything worked and I was able to see the test results. As I mentioned in my comments: make sure that you compile both the testing framework and the test project with the same output options and everything should go without a hitch. Now I'm off to testing some C++ code, woohoo!

Sunday, April 24, 2011

Asynchronous HTTP Client

I recently came across the need to create an asynchronous HTTP client and I went through some examples online but I found that asynchronous socket connections can be somewhat difficult to manage. Due to the "unreliable" nature of the internet, there can be multiple exceptions and errors that may leave your HTTP client hanging in an invalid state. So instead of writing numerous exception cases and verifications at each stage, I tried to simplify the design by making the client behave like a state machine. So let's look at some of the code...

First, we'll start with the underlying data required for data required for each asynchronous request (which I called an AsyncTask):


public class AsyncTask
{
public byte[] ReceiveBuffer { get; private set; }
public int TotalBytesReceived { get; set; }
public int BytesReceived { get; set; }
public string DocSource { get; set; }
public string Host { get; private set; }
public int Port { get; private set; }
public string Path { get; private set; }
public IPAddress[] Addresses { get; private set; }
public AsyncTask(string host, int port, string path)
{
ReceiveBuffer = new byte[256];
TotalBytesReceived = 0;
BytesReceived = -1; // can't be zero!
Host = host;
Port = port;
Path = path;
Addresses = Dns.GetHostEntry(Host).AddressList;
}
}


In addition to a data structure to hold the internals associated with each task, we also need enumeration of the possible client states. I've come up with 11 states, but you can get by with 7 just fine. The reason I have the extra 4 states is just for getting a bit more information as to what the client is currently doing.


public enum EClientState
{
Available = 0,
Connecting,
Connected,
Disconnecting,
Disconnected,
Sending,
SendDone,
Receiving,
ReceiveDone,
Failed,
CleanUp
}


The core function of the client is the ChangeState method, which acts as the brain of the client: it manages the changes of states and enforces strict rules on how state changes can occur. If the client attempts to perform an invalid change state, then the method throws an exception. The idea is that if there is a failure at any stage, then the client can just change the state to Failed and the class will automatically clean up the state in order to resume "normal" operation.


private void ChangeState(EClientState state)
{
lock (_sync) // re-entrant lock
{
try
{
switch (state)
{
case EClientState.Available:
if (_clientState != EClientState.CleanUp)
{
ThrowInvalidStateException(_clientState, state);
}
else
{
_clientState = state;
//Console.WriteLine("Client " + _clientId + ": " + _clientState);
}
break;
case EClientState.Connecting:
if (_clientState != EClientState.Available)
{
ThrowInvalidStateException(_clientState, state);
}
else
{
_clientState = state;
//Console.WriteLine("Client " + _clientId + ": " + _clientState);
BeginConnect();
}
break;
case EClientState.Connected:
if (_clientState != EClientState.Connecting)
{
ThrowInvalidStateException(_clientState, state);
}
else
{
_clientState = state;
//Console.WriteLine("Client " + _clientId + ": " + _clientState);
ChangeState(EClientState.Sending);
}
break;
case EClientState.Disconnecting:
if (_clientState == EClientState.Available ||
_clientState == EClientState.CleanUp)
{
ThrowInvalidStateException(_clientState, state);
}
else
{
_clientState = state;
//Console.WriteLine("Client " + _clientId + ": " + _clientState);
BeginDisconnect();
}
break;
case EClientState.Disconnected:
if (_clientState != EClientState.Disconnecting)
{
ThrowInvalidStateException(_clientState, state);
}
else
{
_clientState = state;
//Console.WriteLine("Client " + _clientId + ": " + _clientState);
ChangeState(EClientState.CleanUp);
}
break;
case EClientState.Sending:
if (_clientState != EClientState.Connected)
{
ThrowInvalidStateException(_clientState, state);
}
else
{
_clientState = state;
//Console.WriteLine("Client " + _clientId + ": " + _clientState);
BeginSend();
}
break;
case EClientState.SendDone:
if (_clientState != EClientState.Sending)
{
ThrowInvalidStateException(_clientState, state);
}
else
{
_clientState = state;
//Console.WriteLine("Client " + _clientId + ": " + _clientState);
ChangeState(EClientState.Receiving);
}
break;
case EClientState.Receiving:
if (_clientState != EClientState.SendDone &&
_clientState != EClientState.Receiving)
{
ThrowInvalidStateException(_clientState, state);
}
else
{
_clientState = state;
//Console.WriteLine("Client " + _clientId + ": " + _clientState);
BeginReceive();
}
break;
case EClientState.ReceiveDone:
if (_clientState != EClientState.Receiving)
{
ThrowInvalidStateException(_clientState, state);
}
else
{
_clientState = state;
//Console.WriteLine("Client " + _clientId + ": " + _clientState);
ChangeState(EClientState.Disconnecting);
}
break;
case EClientState.Failed:
if (_clientState == EClientState.CleanUp)
{
ThrowInvalidStateException(_clientState, state);
}
else
{
_clientState = EClientState.Failed;
//Console.WriteLine("Client " + _clientId + ": " + _clientState);
ChangeState(EClientState.CleanUp);
}
break;
case EClientState.CleanUp:
if (_socket.Connected)
{
ChangeState(EClientState.Disconnecting);
}
else
{
_clientState = state;
//Console.WriteLine("Client " + _clientId + ": " + _clientState);
CleanUp();
}
break;
default:
ThrowInvalidStateException(_clientState, state);
break;
}
}
catch (InvalidOperationException e)
{
throw e;
}
catch (Exception e)
{
Console.WriteLine("ChangeState Exception:\n\t[Source]: " + e.Source + "\n\t[Message]: " + e.Message);
ChangeState(EClientState.Failed);
}
}
}


Finally, here is the client itself:


public class AsyncHttpClient:IHttpClient
{
private static readonly int KILOBYTES = 1024;
private static readonly int _maxPageSize = 128 * KILOBYTES; // limit the page size to 128 KB

private Socket _socket;
private readonly int _clientId;
private EClientState _clientState;

private AsyncTask _asyncTask;
private readonly object _sync;

public AsyncHttpClient(int clientId)
{
_sync = new object();

// Initialize the socket
_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

// Set the socket to be non-blocking
_socket.Blocking = false;

// Set the send and receive timeouts to 10 seconds
_socket.SetSocketOption(SocketOptionLevel.Socket,
SocketOptionName.SendTimeout,
10000);

_socket.SetSocketOption(SocketOptionLevel.Socket,
SocketOptionName.ReceiveTimeout,
10000);

// Initialize the fields
_asyncTask = new AsyncTask(string.Empty, 80, string.Empty);
_clientId = clientId;
_clientState = EClientState.Available;
}

#region IHttpClient Members
public event OnGetCompleteCallback OnGetCompleted;

public void Get(string host)
{
Get(host, 80);
}

public void Get(string host, int port)
{
lock (_sync)
{
if (_clientState != EClientState.Available)
{
// Do not invoke a client that's allready working!
throw new InvalidOperationException("The client is not available! Current state: " + _clientState.ToString());
}
else
{
try
{
// Initialize the socket
_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

// Set the socket to be non-blocking
_socket.Blocking = false;

// Set the send and receive timeouts to 10 seconds
_socket.SetSocketOption(SocketOptionLevel.Socket,
SocketOptionName.SendTimeout,
10000);

_socket.SetSocketOption(SocketOptionLevel.Socket,
SocketOptionName.ReceiveTimeout,
10000);

_asyncTask = new AsyncTask(host, port, string.Empty);
ChangeState(EClientState.Connecting);
}
catch (Exception e)
{
Console.WriteLine("Exception: " + e.Message);
ChangeState(EClientState.Failed);
}
}
}
}
#endregion

private void ChangeState(EClientState state)
{
lock (_sync) // re-entrant lock
{
try
{
switch (state)
{
case EClientState.Available:
if (_clientState != EClientState.CleanUp)
{
ThrowInvalidStateException(_clientState, state);
}
else
{
_clientState = state;
//Console.WriteLine("Client " + _clientId + ": " + _clientState);
}
break;
case EClientState.Connecting:
if (_clientState != EClientState.Available)
{
ThrowInvalidStateException(_clientState, state);
}
else
{
_clientState = state;
//Console.WriteLine("Client " + _clientId + ": " + _clientState);
BeginConnect();
}
break;
case EClientState.Connected:
if (_clientState != EClientState.Connecting)
{
ThrowInvalidStateException(_clientState, state);
}
else
{
_clientState = state;
//Console.WriteLine("Client " + _clientId + ": " + _clientState);
ChangeState(EClientState.Sending);
}
break;
case EClientState.Disconnecting:
if (_clientState == EClientState.Available ||
_clientState == EClientState.CleanUp)
{
ThrowInvalidStateException(_clientState, state);
}
else
{
_clientState = state;
//Console.WriteLine("Client " + _clientId + ": " + _clientState);
BeginDisconnect();
}
break;
case EClientState.Disconnected:
if (_clientState != EClientState.Disconnecting)
{
ThrowInvalidStateException(_clientState, state);
}
else
{
_clientState = state;
//Console.WriteLine("Client " + _clientId + ": " + _clientState);
ChangeState(EClientState.CleanUp);
}
break;
case EClientState.Sending:
if (_clientState != EClientState.Connected)
{
ThrowInvalidStateException(_clientState, state);
}
else
{
_clientState = state;
//Console.WriteLine("Client " + _clientId + ": " + _clientState);
BeginSend();
}
break;
case EClientState.SendDone:
if (_clientState != EClientState.Sending)
{
ThrowInvalidStateException(_clientState, state);
}
else
{
_clientState = state;
//Console.WriteLine("Client " + _clientId + ": " + _clientState);
ChangeState(EClientState.Receiving);
}
break;
case EClientState.Receiving:
if (_clientState != EClientState.SendDone &&
_clientState != EClientState.Receiving)
{
ThrowInvalidStateException(_clientState, state);
}
else
{
_clientState = state;
//Console.WriteLine("Client " + _clientId + ": " + _clientState);
BeginReceive();
}
break;
case EClientState.ReceiveDone:
if (_clientState != EClientState.Receiving)
{
ThrowInvalidStateException(_clientState, state);
}
else
{
_clientState = state;
//Console.WriteLine("Client " + _clientId + ": " + _clientState);
ChangeState(EClientState.Disconnecting);
}
break;
case EClientState.Failed:
if (_clientState == EClientState.CleanUp)
{
ThrowInvalidStateException(_clientState, state);
}
else
{
_clientState = EClientState.Failed;
//Console.WriteLine("Client " + _clientId + ": " + _clientState);
ChangeState(EClientState.CleanUp);
}
break;
case EClientState.CleanUp:
if (_socket.Connected)
{
ChangeState(EClientState.Disconnecting);
}
else
{
_clientState = state;
//Console.WriteLine("Client " + _clientId + ": " + _clientState);
CleanUp();
}
break;
default:
ThrowInvalidStateException(_clientState, state);
break;
}
}
catch (InvalidOperationException e)
{
throw e;
}
catch (Exception e)
{
Console.WriteLine("ChangeState Exception:\n\t[Source]: " + e.Source + "\n\t[Message]: " + e.Message);
ChangeState(EClientState.Failed);
}
}
}

private void CleanUp()
{
if (OnGetCompleted != null)
{
OnGetCompleted(_asyncTask.DocSource, _asyncTask.Host, _clientId);
}
ChangeState(EClientState.Available);
}

private void ThrowInvalidStateException(EClientState currentState, EClientState newState)
{
throw new InvalidOperationException(
String.Format("Cannot change the state from {0} to {1}",
currentState, newState));
}

private void BeginConnect()
{
BeginConnect(0);
}

private void BeginConnect(int endPointIndex)
{
if (_asyncTask.Addresses.Length <= endPointIndex)
{
// We don't have anymore end points to connect to
ChangeState(EClientState.Disconnecting);
}
else
{
EndPoint ep = new IPEndPoint(_asyncTask.Addresses[endPointIndex], _asyncTask.Port);

// Setup the async event arguments
SocketAsyncEventArgs e = new SocketAsyncEventArgs();

e.DisconnectReuseSocket = true;
e.RemoteEndPoint = ep;
e.Completed += new EventHandler<SocketAsyncEventArgs>(ConnectCallback);
e.UserToken = endPointIndex + 1;

bool completedAsync = false;
try
{
// Connect to the first available end point
completedAsync = _socket.ConnectAsync(e);
}
catch (SocketException se)
{
Console.WriteLine("Socket Exception: " + se.ErrorCode + " Message: " + se.Message);
}

if (!completedAsync)
{
// The call completed synchronously so invoke the callback ourselves.
ConnectCallback(this, e);
}
}
}

private void ConnectCallback(object sender, SocketAsyncEventArgs e)
{
lock (_sync) // re-entrant lock
{
if (e.SocketError == SocketError.Success)
{
ChangeState(EClientState.Connected);
}
else if (_asyncTask.Addresses.Length > (int)e.UserToken)
{
// Try and connect to the next available end point
BeginConnect((int)e.UserToken);
}
else
{
Console.WriteLine("Socket Error: {0} when connecting to {1}",
e.SocketError,
_asyncTask.Host);
ChangeState(EClientState.Failed);
}
}
}

private void BeginDisconnect()
{
if (_socket.Connected)
{
_socket.Shutdown(SocketShutdown.Both);

SocketAsyncEventArgs e = new SocketAsyncEventArgs();
e.DisconnectReuseSocket = false; // do not reuse the socket
e.Completed += new EventHandler<SocketAsyncEventArgs>(DisconnectCallback);

bool completedAsync = false;
try
{
// fake async since we're not reusing the socket
completedAsync = true;
_socket.Close(); // close the socket
ChangeState(EClientState.Disconnected);
//completedAsync = _socket.DisconnectAsync(e);
}
catch (SocketException se)
{
Console.WriteLine("Socket Exception: " + se.ErrorCode + " Message: " + se.Message);
ChangeState(EClientState.Failed);
}

if (!completedAsync)
{
// The call completed synchronously so invoke the callback ourselves
DisconnectCallback(this, e);
}
}
}

private void DisconnectCallback(object sender, SocketAsyncEventArgs e)
{
if (e.SocketError == SocketError.Success)
{
ChangeState(EClientState.Disconnected);
}
else
{
Console.WriteLine("Socket Error: {0} when disconnecting from {1}",
e.SocketError,
_asyncTask.Host);
ChangeState(EClientState.Failed);
}
}

private void BeginSend()
{
_clientState = EClientState.Sending;
Encoding ASCII = Encoding.ASCII;
string Get = "GET /" + _asyncTask.Path + " HTTP/1.1\r\nHost: " + _asyncTask.Host + "\r\nConnection: Close\r\n\r\n";
byte[] buffer = ASCII.GetBytes(Get);

SocketAsyncEventArgs e = new SocketAsyncEventArgs();
e.SetBuffer(buffer, 0, buffer.Length);
e.Completed += new EventHandler<SocketAsyncEventArgs>(SendCallback);

bool completedAsync = false;

try
{
completedAsync = _socket.SendAsync(e);
}
catch (SocketException se)
{
Console.WriteLine("Socket Exception: " + se.ErrorCode + " Message: " + se.Message);
}

if (!completedAsync)
{
// The call completed synchronously so invoke the callback ourselves
SendCallback(this, e);
}

}

private void SendCallback(object sender, SocketAsyncEventArgs e)
{
if (e.SocketError == SocketError.Success)
{
ChangeState(EClientState.SendDone);
}
else
{
Console.WriteLine("Socket Error: {0} when sending to {1}",
e.SocketError,
_asyncTask.Host);
ChangeState(EClientState.Failed);
}
}

private void BeginReceive()
{
if ( _clientState == EClientState.Receiving)
{
if (_asyncTask.BytesReceived != 0 && _asyncTask.TotalBytesReceived <= _maxPageSize)
{
SocketAsyncEventArgs e = new SocketAsyncEventArgs();
e.SetBuffer(_asyncTask.ReceiveBuffer, 0, _asyncTask.ReceiveBuffer.Length);
e.Completed += new EventHandler<SocketAsyncEventArgs>(ReceiveCallback);
e.UserToken = _asyncTask.Host;

bool comletedAsync = false;
try
{
comletedAsync = _socket.ReceiveAsync(e);
}
catch (SocketException se)
{
Console.WriteLine("Error receiving data from: " + _asyncTask.Host);
Console.WriteLine("SocketException: {0} Error Code: {1}", se.Message, se.NativeErrorCode);

ChangeState(EClientState.Failed);
}

if (!comletedAsync)
{
// The call completed synchronously so invoke the callback ourselves
ReceiveCallback(this, e);
}
}
else
{
//Console.WriteLine("Num bytes received: " + _asyncTask.TotalBytesReceived);
ChangeState(EClientState.ReceiveDone);
}
}
}

private void ReceiveCallback(object sender, SocketAsyncEventArgs args)
{
lock (_sync) // re-entrant lock
{
// Fast fail: should not be receiving data if the client
// is not in a receiving state.
if (_clientState == EClientState.Receiving)
{
String host = (String)args.UserToken;

if (_asyncTask.Host == host && args.SocketError == SocketError.Success)
{
try
{
Encoding encoding = Encoding.ASCII;
_asyncTask.BytesReceived = args.BytesTransferred;
_asyncTask.TotalBytesReceived += _asyncTask.BytesReceived;
_asyncTask.DocSource += encoding.GetString(_asyncTask.ReceiveBuffer, 0, _asyncTask.BytesReceived);

BeginReceive();
}
catch (SocketException e)
{
Console.WriteLine("Error receiving data from: " + host);
Console.WriteLine("SocketException: {0} Error Code: {1}", e.Message, e.NativeErrorCode);

ChangeState(EClientState.Failed);
}
}
else if (_asyncTask.Host != host)
{
Console.WriteLine("Warning: received a callback for {0}, but the client is currently working on {1}.",
host, _asyncTask.Host);
}
else
{
Console.WriteLine("Socket Error: {0} when receiving from {1}",
args.SocketError,
_asyncTask.Host);
ChangeState(EClientState.Failed);
}
}
}
}
}

Monday, March 7, 2011

WYSIWYG: derobins wmd markdown editor in a partial view

Putting derobins wmd markdown editor in a partial view was very easy, so this should be a pretty straight forward post. The main benefit of placing your wysiwyg editor in a partial view is that you can treat it like a widget and display it anywhere on your web site with ease. However, I am yet to try and capture the content of the editor. I hope that capturing the input from the editor in the partial view will not be that difficult, but if it turns out to be something messy, then this idea might have to get scrapped.

Installation
The installation instructions were quite good, so the installation went without a hitch!

Usage
I found it useful to muck with the wmd test page a little, just to get a feel for the editor (which should be pretty familiar to people who are already familiar with StackOverflow).

Implementation
Suppose you've already added/merged the wmd style sheet to your web site's style sheet, now you just need to add the proper references in your master page, create a partial view with the wmd editor, and finally display the partial view on a page of your choice.

Step 1: Follow the installation instructions for wmd.
The installation are in the readme file of the wmd download.

Step 2: Create a partial view for the editor (I called mine MarkdownControl).


<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<dynamic>" %>

<div id="wmd-editor" class="wmd-panel">
<div id="wmd-button-bar"></div>
<textarea id="wmd-input"></textarea>
</div>
<div id="wmd-preview" class="wmd-panel"></div>


Step 3: Display the control in a page of your choice:

<% Html.RenderPartial("MarkdownControl"); %>


And there you have it: a WYSIWYG editor in a partial view which you can display anywhere on your web site! You can see my implementation in action here: www.mydevarmy.com/Competition/Propose

Saturday, March 5, 2011

ASP.NET MVC: using DotNetOpenAuth to create a simple OpenID log in

I recently started dabbling in ASP.NET MVC in an effort to make my own competative crowdsourcing web site. The nature of crowdsourcing is such that it requires multiple people to come together on the internet and work on the same project, but I wouldn't necessarily know who those people are, what are their intentions and much less have a reasonable way to ensure that they're all going to "play good." Developing the web site requires that the contributors have access to some parts of the project which should not be publicly available: e.g. the Web.config file (which contains the connection string of the database) and subsequently access to the database, since both the Web.config and the database will have to be modified as the web site grows. Of course this poses a serious problem for the privacy of the users: their personal information, such as e-mail address and password, may be accessible to members of the community as long as those members are also contributing to the web site.

There are probably some very good ways to prevent the personal information of the users from leaking out, perhaps by providing various credential levels for the contributors and less trusted users will have a restricted access to the database, and/or coming up with a complex scheme to protect private information. In any case, the security solutions only seem to mask the problem and the root of the problem is that I had to store the passwords and verify the user log in against the database. Naturally, the best way to secure such private information is not to store it at all, so I turned to OpenID! I only allow users to log in to the web site with an open ID and that model seems to work very well for other successful web sites, such as StackOverflow.

I got the latest DotNetOpenAuth library and since I'm as a total "noob" I cracked open the sample projects I opened up the ASP.NET MVC sample only to find out that the sample is missing the M from the MVC, which is a pretty important letter! A few days later I stumbled across a question on StackOverflow that gave me an opportunity to share my newly gained knowledge with the world! The OP was very satisfied with my answer and suggested that I write it in my blog, alas here I am writing about it!

So what would a very simple OpenID authentication in the MVC framework look like? Well, have a look for yourself:

1. We need a Model:


public class User
{
[DisplayName("User ID")]
public int UserID{ get; set; }

[Required]
[DisplayName("OpenID")]
public string OpenID { get; set; }
}

public class FormsAuthenticationService : IFormsAuthenticationService
{
public void SignIn(string openID, bool createPersistentCookie)
{
if (String.IsNullOrEmpty(openID)) throw new ArgumentException("OpenID cannot be null or empty.", "OpenID");

FormsAuthentication.SetAuthCookie(openID, createPersistentCookie);
}

public void SignOut()
{
FormsAuthentication.SignOut();
}
}



2. We need a View:


<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>


Log in - YourWebSiteName



<%--- If you have a domain, then you should sign up for an affiliate id with MyOpenID or something like that ---%>
Please log in with your OpenID or create an
OpenID with myOpenID
if you don't have one.


<%
string returnURL = HttpUtility.UrlEncode(Request.QueryString["ReturnUrl"]);
if (returnURL == null)
{
returnURL = string.Empty;
}

using (Html.BeginForm("LogIn", "User", returnURL))
{%>
<%= Html.LabelFor(m => m.OpenID)%>:
<%= Html.TextBoxFor(m => m.OpenID)%>

<%
} %>

<%--- Display Errors ---%>
<%= Html.ValidationSummary()%>



3. And finally, we need a Controller:

[HandleError]
public class UserController : Controller
{
private static OpenIdRelyingParty openid = new OpenIdRelyingParty();
public IFormsAuthenticationService FormsService { get; set; }

protected override void Initialize(RequestContext requestContext)
{
if (FormsService == null)
{
FormsService = new FormsAuthenticationService();
}

base.Initialize(requestContext);
}

// **************************************
// URL: /User/LogIn
// **************************************
public ActionResult LogIn()
{
if (User.Identity.IsAuthenticated)
{
return RedirectToAction("Profile", "User");
}

Identifier openID;
if (Identifier.TryParse(Request.QueryString["dnoa.userSuppliedIdentifier"], out openID))
{
return LogIn(new User { OpenID = openID }, Request.QueryString["ReturnUrl"]);
}
else
{
return View();
}
}

[HttpPost]
public ActionResult LogIn(User model, string returnUrl)
{
string openID = ModelState.IsValid?model.OpenID:Request.Form["openid_identifier"];

if (User.Identity.IsAuthenticated)
{
return RedirectToAction("Profile", "User");
}
else if (!string.IsNullOrEmpty(openID))
{
return Authenticate(openID, returnUrl);
}
else if(ModelState.IsValid)
{
ModelState.AddModelError("error", "The OpenID field is required.");
}

// If we got this far, something failed, redisplay form
return View(model);
}

// **************************************
// URL: /User/LogOut
// **************************************
public ActionResult LogOut()
{
if (User.Identity.IsAuthenticated)
{
FormsService.SignOut();
}

return RedirectToAction("Index", "Home");
}

// **************************************
// URL: /User/Profile
// **************************************
[Authorize]
public ActionResult Profile(User model)
{
if (User.Identity.IsAuthenticated)
{
// ------- YOU CAN SKIP THIS SECTION ----------------
model = /*some code to get the user from the repository*/;

// If the user wasn't located in the database
// then add the user to our database of users
if (model == null)
{
model = RegisterNewUser(User.Identity.Name);
}
// --------------------------------------------------

return View(model);
}
else
{
return RedirectToAction("LogIn");
}
}

private User RegisterNewUser(string openID)
{
User user = new User{OpenID = openID};

// Create a new user model

// Submit the user to the database repository

// Update the user model in order to get the UserID,
// which is automatically generated from the DB.
// (you can use LINQ-to-SQL to map your model to the DB)

return user;
}

[ValidateInput(false)]
private ActionResult Authenticate(string openID, string returnUrl)
{
var response = openid.GetResponse();
if (response == null)
{
// Stage 2: user submitting Identifier
Identifier id;
if (Identifier.TryParse(openID, out id))
{
try
{
return openid.CreateRequest(openID).RedirectingResponse.AsActionResult();
}
catch (ProtocolException ex)
{
ModelState.AddModelError("error", "Invalid OpenID.");

ModelState.AddModelError("error", ex.Message);
return View("LogIn");
}
}
else
{
ModelState.AddModelError("error", "Invalid OpenID.");
return View("LogIn");
}
}
else
{
// Stage 3: OpenID Provider sending assertion response
switch (response.Status)
{
case AuthenticationStatus.Authenticated:
Session["FriendlyIdentifier"] = response.FriendlyIdentifierForDisplay;
FormsAuthentication.SetAuthCookie(response.FriendlyIdentifierForDisplay, true);
if (!string.IsNullOrEmpty(returnUrl))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Profile", "User");
}
case AuthenticationStatus.Canceled:
ModelState.AddModelError("error", "Authentication canceled at provider.");
return View("LogIn");
case AuthenticationStatus.Failed:
ModelState.AddModelError("error", "Authentication failed: " + response.Exception.Message);
return View("LogIn");
}
}
return new EmptyResult();
}
}


Wanna see it all in action? Go to my web site and log in with your OpenID: http://www.mydevarmy.com/User/LogIn

Voila! You have a simple log in with OpenID!