NUnitASP is a tool for automatically testing ASP.NET websites. It’s an application of Test-driven Development technique, commonly known as TDD. It was initially developed as NUnit extension. NUnitASP is a class library which allows you to load webpage within an invisible browser instance, lets you fill up textboxes, click buttons and verify actions afterwards. With the growing interest of ASP.NET developers, the author decided to make it any version of any testing framework compatible. I did not find it working with xUnit though, which of course is backed with the theory that he abandoned its further development. The objective of this post is to make it work with xUnit.

Here is a glimpse of how the test methods will look like:

[Fact]
public void GivenStatusUpdate_StatusLabel_ShouldUpdate()
{
    var sampleUpdate = "I am running a test using xUnit and NUnitASP";
 
    Browser.GetPage("http://localhost:8010/");
    
    // Set text
    var txtStatus = new TextBoxTester("txtStatus");
    txtStatus.Text = sampleUpdate;
 
    // Update it
    var btnUpdate = new ButtonTester("btnUpdate");
    btnUpdate.Click();
 
    // Verify it
    var lblStatus = new LabelTester("lblStatus");
    Assert.Equal<string>(lblStatus.Text, sampleUpdate);
} 
 
 

Steps to make it work with xUnit

  1. Add reference to NUnitASP.dll, xUnit.dll and nunit.framework.dll
  2. Add NUnitAdapter.cs from NUnitASP package to the Class Library project created for Test
  3. Comment out the Setup attribute in NUnitAdapter.cs. We need to do this because we are not running on NUnit.
    //[SetUp]
    public void MasterSetUp() 
    {
        setupCalled = true;
        HttpClient.Default = new HttpClient();
        SetUp();
    }

  4. Modify the AssertSetup like the following. We need to call this manually since the Setup will not be fired automatically because we are not running on NUnit.
    private void AssertSetUp()
    {
        if (!setupCalled) 
        {
            MasterSetUp();
        }
    }

  5. Write your tests and compile to an assembly
  6. Run from xUnit console runner: xunit.console.exe

 

See the nice XSLT driven HTML result produced by xUnit:

xUnitWithNUnitASP-result

Download Source Code of the Sample [xUnitWithNUnitASP]

You have a top secret site hosted on the web and you want to demo it to certain people only. There may be several ways to do it. I wanted to do it under 5 minutes. I created a HttpModule to redirect to a login page. That page will validate against an XML file, which is basically list of username/password like below:

<?xml version="1.0" encoding="utf-8" ?>
<allowed>
  <user name="saqib" password="dontknow" />
  <user name="tanzim" password="nopassword" />
</allowed>

 

A HttpModule is pluggable version of Global.asax. The reason why I am calling this because it can be just installed in web.config like the following and has capability to interact with the ASP.NET events like the Global.asax.

<httpModules>
  <add type="RedsideSecurity" name="RedsideSecurity" />
</httpModules>
 
 

HttpModules have full control over HTTP requests, so the HttpModule I created can intercept the requests can authorize depending on the cookie present in the browser. On Init of the module, I registered a delegate to the BeginRequest event, meaning during the journey through ASP.NET pipeline, this delegate is going to be invoked upon beginning of the request. I checked cookies and if it is not valid, showed link to SecureLogin.aspx page which will display the user/password inputs.

public void Init(HttpApplication context)
{
    context.BeginRequest += (sender, args) =>
    {
        var path = context.Request.Path.ToLowerInvariant();
 
        if (path != "/securelogin.aspx")
        {
            var cookie = context.Request.Cookies.Get(RED_SIDE_COOKIE_NAME);
            if (cookie == null 
                || cookie.Value == string.Empty 
                || Convert.ToDateTime(cookie.Value) > DateTime.Now.AddMinutes(COOKIE_TIMEOUT_MINUTES))
            {
                context.Response.Write("<span style=\"color: red; font-weight: bold;\""
                    + ">Sir, your ID please.</span> <a href=\"securelogin.aspx\">Login</a>");
                context.Response.End();
            }
        }
    };
}

Code for SecureLogin.aspx is easy as well. Try matching the credential supplied by user from the XML file, set cookie if authorized and redirect to default.aspx.

protected void btnLogin_Click(object sender, EventArgs e)
{
    if (Page.IsValid)
    {
        var cookie = 
            new HttpCookie(RedsideSecurity.RED_SIDE_COOKIE_NAME, DateTime.Now.ToString());
        
        cookie.Expires = DateTime.Now.AddMinutes(RedsideSecurity.COOKIE_TIMEOUT_MINUTES);
        Response.Cookies.Set(cookie);
        Response.Redirect("default.aspx");
    }
}
 
protected void CustomValidator1_ServerValidate(object source, ServerValidateEventArgs args)
{
    var path = Server.MapPath("~/App_Data/RedsideSecurity.xml");
    var allowedUsers = XElement.Load(path);
 
    var user = (from u in allowedUsers.Elements("user")
                where u.FirstAttribute.Value == txtUsername.Text 
                && u.LastAttribute.Value == txtPassword.Text
                select u).FirstOrDefault();
 
    args.IsValid = user != null;
}

There are just way too many social networking sites out there to keep up and that's where lifestreaming services come in. Lifestream is a time ordered stream of activities, that functions as a diary of your electronic "social" life. In this Web 2.0 (or more?) era, you get to use Twitter, Facebook, Delicious, Flickr, write blog posts, keep your friends up to date, and you are curious about what your friends are up to, too. There are many social websites that will help you to aggregate all of your social activities into one place. FriendFeed is such and one of the most popular services that does the lifestreaming for you. Those sites are still externally hosted that cannot replace your blog or current website, even though they may offer cooler features than your blog or site. Many may want to argue, but I strongly believe portals that host all your online (and social too) activities this is going to be the next generation of personal sites, and will replace your blogs and current static pages.

MyStream_Home

 

MyStream is a fully customizable, themable, easy to configure, an open source Portal framework built on top of ASP.NET 4, that you can host on your own server. You have got the full freedom to upgrade to new more themes yet to come and maintain your existing personal branding by customizing every little piece of it. The current codebase of this pluggable architecture can stream your blogposts by RSS/Atom, Delicious bookmarks, Flickr photostream and Tweets. You can write your own social plugin in minutes using this framework. This article is surely going to deliver some of the experiences I gained during practical development on ASP.NET 4 and new C# 4.0 features to those who are still wondering about, since it was made possible by Visual Studio 2010 Beta 1.

Article: http://www.codeproject.com/KB/aspnet/mystream.aspx

Project hosted at: http://mystream.googlecode.com/

You are more than welcome to join the MyStream development team, add new plugins, improve and scale the architecture, fix bugs, design attractive themes. I look forward to your feedback, contribution to the project and be part of development of next generation personal sites. When ASP.NET 4 hosting will be available you would be able to turn your current static website or blog into a lifestreaming portal with all your social activities with this project built on top of ASP.NET 4, C# 4.0, PLINQ, Task Parallel Library, Dependency Injection and Plugin architecture.

One of my friends work for a company where he has to work in a remote PC and where he lives he does not have good internet connection. He neither has permission for opening up third party email website such GMail, nor he could download email with 100MBs of attachment using Outlook. He tried for even whole night trying download those email. However his poor internet connection did not let him download completely such single email from server. He had to redownload all the email from the beginning. So he asked me to help him out in such situation as he did not have more than 6 hours in hand to finish downloading those email and reply them. Unfortunately he was not a developer and I even myself did not have the time for it. Still for the sake of friendship, I agreed to help him out, and built an app in less than 10 minutes for him. This project has been hosted at MSDN Code Gallery.

logo

I use many free components and tools everyday, I am grateful at, made my life lot easier. I wish I could write several blog posts on them, and thanking them for making it happening for me. One of them is OpenID and the .NET implementation DotNetOpenID that saved me a lot of coding and headache that day. My friend did not have any web server, so I built that app and hosted on my web server. But I already exceeded my database creation limit on that server and I really do not want to take a chance to screw up other existing databases. So I could not create tables to store membership information. His requirement was the site needs to be secured and password protected, and he can upload from remote PC and can download to his local PC through resume supported http downloaders such as Free Download Manager. Did I say I am a big fan of this free software too? Try it out for yourself.

From OpenID site:

OpenID eliminates the need for multiple usernames across different websites, simplifying your online experience.

You get to choose the OpenID Provider that best meets your needs and most importantly that you trust.  At the same time, your OpenID can stay with you, no matter which Provider you move to.  And best of all, the OpenID technology is not proprietary and is completely free.

For businesses, this means a lower cost of password and account management, while increasing site visitor registration conversion rates. OpenID lowers user frustration by letting users have control of their login.

For geeks, OpenID is an open, decentralized, free framework for user-centric digital identity. OpenID takes advantage of already existing internet technology (URI, HTTP, SSL, Diffie-Hellman) and realizes that people are already creating identities for themselves whether it be at their blog, photostream, profile page, etc. With OpenID you can easily transform one of these existing URIs into an account which can be used at sites which support OpenID logins.

And DotNetOpenID is a C# library adds OpenID 2.0 Provider and Relying Party, OAuth Consumer and Service Provider, and InfoCard Selector support to your web site both programmatically and through convenient drop-in ASP.NET controls.

 

How OpenID simplifies authentication

Here are the snapshots of the end product. There will be two other pages Upload and Browse which are pretty self-explanatory. You will be able to upload new file, browse files and delete unnecessary files. Most important part of this page is it has a login page which is not like regular login box where you enter username and password. It has a OpenID Login instead. You will have to type your OpenID URL here. If you do not have one, you can click register and have the same. The reason behind no username rather than OpenID URL is, there could be many different OpenID provider websites that will provide you with OpenID account creation opportunity. Many popular providers are ClaimID, MyOpenID, MyID and so on. No matter what you chose to open OpenID account with, you will still be able to access all the sites that support OpenID. That is why OpenID identifies yourself as an URL instead of Username/ID.

DropZone

When you type your OpenID URL there and hit Login, you will be redirected to the provider’s page like the following where you will have to type your password in and allow access the site to use your account information such as your name, email address and so on. See the highlighted key information below:

MyOpenID

If you click on Stay signed in, you will never have to write password again. Whenever the authenticated application in our case our DropZone application will redirect to the provider’s login page it will be automatically authenticated and redirected back to our app. So, you will not see the login page again. Consider OpenID as a global ID or passport that you can use in many giant and popular websites. Popularity of OpenID is being increased everyday exponentially.

Upload

When you are done with authenticating yourself to the application you will be redirected to the Upload page where you can select a file and upload it to server. The beauty of OpenID here is you do not have to worry about creating tables in your database for keeping track of users, change password, forgot password, register, login mechanism, manage sessions, cookies and what not functionalities you can imagine. You will also find another page named Browse which has a simple GridView that displays file list from the directory where files will be stored. We will see shortly how we would do that. You will also be able to delete files from the grid which results into deleting the actual uploaded file from the server

 Browse

Configuring for custom users and paths

Before we get into how we could setup OpenID for our application, let us take a look at some configuration that we will be using through out its development lifecycle. You can write comma separated values for allowed OpenID users who can authenticate and use this application. You may want to keep this list short and filled up with OpenID URLs of friends only. You will also need to configure the store path where files will be stored and URL prefix for the files to download. Here are the configuration you will have to place inside web.config file: 

<appSettings>
  <add key="allowed_users" value="user1@myopenid.com,user2@myopenid.com"/>
  <add key="store_path" value="d:\\dropzone\\"/>
  <add key="download_url_prefix" value="http://localhost/uploads/"/>
</appSettings>


Getting Started with DotNetOpenID

DotNetOpenID is an implementation of OpenID API and as the name suggests of course it is meant for .NET applications. After you have referenced the DLL you will be able to use the OpenID Login control in your WebForm. You can use the design-time view and set properties for the control like the following:

DotNetOpenID_Props

From HTML view it may look like the following snippet:

<cc1:OpenIdLogin ID="OpenIdLogin1" runat="server" CssClass="openid_login" RequestCountry="Request"
    RequestEmail="Request" RequestGender="Require" RequestPostalCode="Require" RequestTimeZone="Require"
    RememberMeVisible="False" PolicyUrl="~/PrivacyPolicy.aspx" TabIndex="1"
    OnLoggedIn="OpenIdLogin1_LoggedIn" OnCanceled="OpenIdLogin1_Canceled" OnFailed="OpenIdLogin1_Failed"  />

You will notice the RequestEmail, RequestCountry and so on properties, there are the items that will be asked to and bring back from the OpenID provider. You will also see event handlers are being registered to customize our needs to perform specific actions upon those events. Now that we have included the OpenID login control, its time for implementing the event handlers.

I used the same State class that was being shipped with the Samples for DotNetOpenID, which looks like the following:

public class State
{
    public static void Clear()
    {
        ProfileFields = null;
        FriendlyLoginName = null;
        PapePolicies = null;
    }
    public static ClaimsResponse ProfileFields
    {
        get { return HttpContext.Current.Session["ProfileFields"] as ClaimsResponse; }
        set { HttpContext.Current.Session["ProfileFields"] = value; }
    }
    public static string FriendlyLoginName
    {
        get { return HttpContext.Current.Session["FriendlyUsername"] as string; }
        set { HttpContext.Current.Session["FriendlyUsername"] = value; }
    }
    public static PolicyResponse PapePolicies
    {
        get { return HttpContext.Current.Session["PapePolicies"] as PolicyResponse; }
        set { HttpContext.Current.Session["PapePolicies"] = value; }
    }
}

This class holds information for the logged on user in static properties that include Profile detail as well as FriendlyLoginName which is the OpenID URL for the logged on user. You will also notice that the class has a Clear method which nullifies every property to state that there is currently no logged on user at the moment. LoggedIn is the event that gets fired when an user successfully login using his OpenID. So we need to write code to handle that:

   1:  protected void OpenIdLogin1_LoggedIn(object sender, OpenIdEventArgs e)
   2:  {
   3:      if (e.Response.Status == AuthenticationStatus.Authenticated)
   4:      {
   5:          var isAllowed = false;
   6:          var allowedUsers = ConfigurationManager.AppSettings["allowed_users"].Split(",".ToCharArray());
   7:          foreach (var allowedUser in allowedUsers)
   8:          {
   9:              if (e.Response.FriendlyIdentifierForDisplay.Equals(allowedUser, StringComparison.InvariantCultureIgnoreCase))
  10:              {
  11:                  isAllowed = true;
  12:                  break;
  13:              }
  14:          }
  15:   
  16:          if (isAllowed)
  17:          {
  18:              State.FriendlyLoginName = e.Response.FriendlyIdentifierForDisplay;
  19:              State.ProfileFields = e.Response.GetExtension<ClaimsResponse>();
  20:              State.PapePolicies = e.Response.GetExtension<PolicyResponse>();
  21:              Response.Redirect("Upload.aspx");
  22:          }
  23:          else
  24:          {
  25:              State.Clear();
  26:          }
  27:      }
  28:  }

This code takes care of the information that are passed to this method after successful login from OpenID, and iterates through the allowed OpenIDs from web.config to validate whether the user should get access to the resources. If the user is found, State is being populated with the information received from OpenID. Next thing is to protect pages from being accessed without authentication by hotlink. We could do this by validating whether the State has FriendlyLoginName data like string.IsNullOrEmpty(State.FriendlyLoginName).

 

Production Setup

1. Set write properties for your Uploads folder such as this from IIS:

Uploads

2. For additional security to the Uploads folder you could also set permission for Administrator/other users you like:

Admin

3. Also make sure the file is being uploaded can be handled by the server. For instance, if you upload 7z files and your server does not have a clue how to handle this request, you could set the mime type for that particular type of file like the following:

mime

This will ensure the response header would be set to application/x-zip-compressed which will cause the browser or file downloader software to initiate the download. However, regular extensions should work just fine without adding entries to mime types.

4. ASP.NET by default allows you to upload maximum 4MB size of file. Exceeding that limit would be easy to configure in web.config by the attribute in httpRuntime. You will have to determine the maximum limit of megabytes you are going to allow by multiplying with 1024. Like 200MB * 1024 = 204800 Kilobytes.

<httpRuntime maxRequestLength="204800" />

5. ASP.NET default maximum timeout for each request is 30 seconds, after that you request will be ended prematurely. If you need to upload larger files that needs sufficient timeout value, you can also set that up from the same XML element in web.config like the one mentioned above. This value is in seconds. We are setting here 12 hours timeout.

<httpRuntime executionTimeout="43200" />

 

Conclusion

Keep in mind that this application is open source which has not gone through thorough testing efforts and you should use this totally at your own risk. However, I used this application by myself to transfer hundreds of megabytes of large files without any problem. You will also find I did not put any effort for good architecture for this tiny tool, nor have I incorporated any exception handling mechanisms.

Do you know from where I got the theme for this application? FreeCssTemplates is another free website that I am grateful at, for letting me use numerous of their templates in my applications for no charge.

As you probably know from the earlier posts or you might have attended already Microsoft Day, I spoke on Development in ASP.NET [WebForms, LINQ, Dynamic Data, Futures] on June 20th. Some of the enthusiasts still communicating with me even after the Microsoft Day @ Dhaka. I really appreciate your passion guys. One of them just told me that after my session and private Q&A with me on LINQ, he could successfully convince his supervisor to welcome change to the existing architecture they were working on. They are going to replace their data access model with LINQ to SQL. Feels so good to hear that! I am happy because at least some people found it useful and are actually utilizing the knowledge in real life, they learned from the event. This is how community contributions add value to the industry.

Not everyone in the event was ASP.NET developers there that day. There were also business decision makers and developers who are switching to Microsoft stack of tools and technologies lately. Three persons personally met me after the session, owner/supervisor of their companies/engineering teams, to identify the opportunities with Dynamic Data and LINQ for their businesses and/or as prototyping tool.

 

I also talked in general about the event with the people who were attending the event. One really positive point they made was its a wonderful opportunity for them to do business networking with the industry's developers. one of them showed me bunch of his business cards he brought that day with him! They thanked Microsoft for bringing them all under the same umbrella. They said they were looking forward to such events in future. It was also a great get together for me with the industry experts and MVPs. I personally received a lot of good, positive and not-so-positive ;) feedback on the event. Despite the criticisms on some of the Level 100 sessions, the guys I've just talked about and the success story were the answer to those. However I would like to thank those who attended the event and provided with really constructive feedback no matter positive or negative. But, it still was a huge success!!

I hope this event is just the beginning, not the end. I would feel lucky if I could present in the same event next time. Its always fun working with the community. Thanks again guys for attending and making it a huge success.

As I have told earlier about the Microsoft Day @ Dhaka event, and as I have promised, this post is about the presentation slide the audience was asking for. I am so glad to receive appreciation about the session and overall event. I am really happy the developer community at Dhaka attend all the sessions with great delight. It was also a great opportunity for me to gather community feedback on many things ranging from technology to event management. There was about 200 people in the auditorium and also free lunch. Here are some of the photographs of the event:

Me, presenting Some of my audience Me, answeing questions

Here is the slide for my presentation attendees were asking for. For your convenience I have also uploaded the slide on Slideshare:

 

Enjoy, those who could not attend! I really appreciate those and would like to thank who could make it that day – great audience! Looking forward to see you guys again next time.

Microsoft Community in Bangladesh proudly presents Microsoft Day @ Dhaka. This is a special day dedicated to all Microsoft technology professionals and students in Bangladesh. We will be having the best Microsoft community technologists from Bangladesh - Microsoft Most Valuable Professionals (MVPs) delivering sessions at the event. This technology marathon is a great opportunity to learn from the best and network with each other. Both Microsoft developers and networking professionals would find the event worth attending. The event will also feature a demo bonanza with Windows 7 and an extensive Question and Answer panel with the Bangladesh MVPs to answer your queries. I will take the inaugural session on that day which starts from 9.30am.

Untitled

 

Date time:

Saturday June 20th, 2009. 9.00AM – 6.00PM

 

Venue:

IDB Auditorium
E/8-A Rokeya Sharani,
Sher-e-Bangla Nagar,
Agargaon, Dhaka 1207

For more information: http://msdnbangladesh.net/content/msday.aspx

 

Agenda

9:00

9:30

Opening Speech

Feroz Mahmood & Abhishek Kant

9:30

10:30

Development in ASP.NET

Tanzim Saqib

10:30

11:15

My First ASP.NET MVC App

Mehfuz Hossain MVP

11:15

11:45

Unit Testing in MVC and Deo of dotnetshoutouts.com

Kazi Manzur Rashid MVP

11:45

12:30

Developing in Silverlight

Faisal Hossain Khan

12:30

1:30

Lunch

1:30

2:00

Introduction to MS Sharepoint Server

MJ Ferdous

2:00

2:45

Production Challenges of ASP.NET Websites

Omar Al Zabir MVP

2:45

3:15

Windows Azure

Ashic Mahtab

3:15

3:45

Tea Break

3:45

4:30

Overview of Visual Studio Team System 2010

Mohammad Ashraful Alam MVP

4:30

5:00

Features of Windows 71

Omi Azad MVP

5:00

5:30

IE8 and Windows Live Features1

Irtija A. Akhter


You can also download the full agenda. I will post links and downloads after my session in this blog. See ya there!

I have described before in my prior post how you can run your ASP.NET MVC application in Visual Studio 2010. There is currently no support for ASP.NET MVC project types in Visual Studio 2010 Beta 1. So what I did is I created a starter kit for ASP.NET MVC 1.0 which you can download from here. Now follow the steps:

1. Execute the AspNetMvc2010.vsi, you will see the following dialog box. Proceed.

StarterKitInstall


2. Say yes to whatever comes along.

Warning


3. Now go to File > New Project, you will find ASP.NET MVC Application in Visual C# > Web tab.

ProjectCreation

 

Hope this saves some time!

You probably already know that ASP.NET MVC is not included with Visual Studio 2010 Beta1 since MVC was released when Visual Studio release was being locked down. I hope it will be available from Beta2. It also looks like if you install ASP.NET MVC after Visual Studio 2010 it does not pick up as well. So currently the only way to create and work with a MVC project is to create a project in Visual Studio 2008 and then open it with Visual Studio 2010. If you do so, it will ask you to convert this project to 2010 version which is little strange to me since there are not many things to convert from 2008 to 2010 as of yet. Anyway, so as usual you have to go through a Conversion Wizard.

ConversionError

You will notice an error while it was performing conversion. It says that the project type is not supported by this installation. You will also find the Solution Explorer is unable to load the project.

SolutionExplorer

 

Solution

1. Now its the same procedure for Visual Studio 2008 if ASP.NET MVC is not installed. Right click on the project name from the Solution Explorer and Edit.

EditProject


2. You will see the project definition XML. Seek for the line below:

<ProjectTypeGuids>{603c0e0b-db56-11dc-be95-000d561079b0};{349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}</ProjectTypeGuids>

3. Remove the first GUID. Save and close the file.

4. Reload the project.

ReloadProject 


5. Conversion Wizard will pop up again, now with an additional dialog box whether we would like to upgrade to .NET Framework 4.0

WebSiteTargeting

 
6. Choose whather you want. Now that the conversion is complete, you will see the report is clean.

ConversionReport

 
You are ready to go. Note that even though you can get it working with Visual Studio 2010 in this way, but you still cannot have the functionality of adding new types from Context menu such as “Add View..” and so on.

There are two different ways to build controls for ASP.NET MVC as of now. The most common way is by HTML Helper extension methods. You will find such methods being used in numerous places inside Views. Such methods can take any complexity of parameters, yet return only standard HTML tags in string format as response. Our Menu control will render a basic structure of CSS Menu, which appearance can be controlled from the site’s CSS file. So no matter which look & feel you would like to see your menu reflects, you do not have to change your Menu control. You will only have to make changes into your CSS.

Menu       Menu1

The following TextBox method renders an input tag with name “username”:

<%= Html.TextBox("username") %>

This sort of extension methods often become handy when you need to render complex or reusable controls in different Views. Today I will talk about a simple Menu control I built which renders UL/LI tags, but can take parameters ranging from string to complex types such as MvcMenuItem. Let us see an example how we would like to see Menu control to be used:

   1:  <%
   2:      var list = new List<MvcMenuItem>();
   3:   
   4:      list.Add(new MvcMenuItem{ Text = "Home", ActionName = "Index" });
   5:      list.Add(new MvcMenuItem("About", "About", "Home"));
   6:      list.Add(new MvcMenuItem("Feedback", "alert('feedback');"));
   7:  %>
   8:   
   9:  <%= Html.Menu()
  10:      .ClientId("menu")
  11:      .AddRange(list)
  12:      .HtmlAttributes(new { style="color: Red" })
  13:      .Render()
  14:  %>


From the snippet you can see, the Menu control is taking a list of MvcMenuItem class which we will create later, is being invoked from Html class and supports method calls in chain which is known as Fluent Interface. Let us take a look at how many different ways we can add menu item to the Menu control:

MvcMenu Add(MvcMenuItem item)
MvcMenu Add(string text, string actionName, string controllerName, string clientCallbackMethod)
MvcMenu Add(string text, string actionName, string controllerName)
MvcMenu Add(string text, string clientCallbackMethod)
MvcMenu Add(string text)
MvcMenu AddRange(List<MvcMenuItem> items)


I am sure you noticed the return type in every method mentioned above. It is because we have to return the current instance of the class from within every method to support Fluent Interface. Let us also take a look at the class properties of MvcMenuItem which has several constructor overloads, helpful when initializing instances. You will notince ClientCallbackMethod property which indicates if this menu item is meant to be calling a client side method. You pass a JavaScript method/code block the menu item will execute it upon user’s click.

public class MvcMenuItem
{
    public string Text { get; set; }
    public string ActionName { get; set; }
    public string ControllerName { get; set; }
    public string ClientCallbackMethod { get; set; }
}


HtmlHelper
resides in a different assembly than we are building MVC control for. So how would we write extension for it? All we have to do is, reference the System.Web.Mvc assembly and we will write the class inside that namespace too:

namespace System.Web.Mvc
{
    public static class MvcMenuExtensions
    {
        public static MvcMenu Menu(this HtmlHelper helper)
        {
            return new MvcMenu(helper);
        }
    }
}


As we are writing extension method, you will notice we followed the convention of putting it in a static class, and the static method named Menu is taking the HtmlHelper which we will pass to the MvcMenu we will be building. Constructor of the MvcMenu class is important. It takes that HtmlHelper and store it to its private variable so that it can be reused later. We need it to be reused later since we are supporting Fluent Interface.

public MvcMenu(HtmlHelper helper)
{
    Helper = helper;
    Items = new List<MvcMenuItem>();
}


As you can understand MvcMenu is the class we are writing responsible for processing menu items, and rendering. Next part is easy and straight forward. We will implement Render method which will write Menu Items for us:

   1:  public MvcMenu HtmlAttributes(object dictionary)
   2:  {
   3:      HtmlProperties = new RouteValueDictionary(dictionary);
   4:      return this;
   5:  }
   6:   
   7:  public string Render()
   8:  {
   9:      var ulTag = new TagBuilder("ul");
  10:      ulTag.MergeAttribute("id", Id ?? string.Empty);
  11:      ulTag.MergeAttributes(HtmlProperties);
  12:   
  13:      foreach (var item in Items)
  14:      {
  15:          var liTag = new TagBuilder("li");
  16:   
  17:          if(!string.IsNullOrEmpty(item.ClientCallbackMethod))
  18:              liTag.InnerHtml = string.Format("<a href=\"javascript:;\" onclick=\"{1}\">{0}</a>", item.Text, item.ClientCallbackMethod);
  19:          else
  20:              liTag.InnerHtml = Html.LinkExtensions.ActionLink(Helper, item.Text, item.ActionName, item.ControllerName ?? string.Empty);
  21:          
  22:          ulTag.InnerHtml += liTag.ToString();
  23:      }
  24:   
  25:      return ulTag.ToString();
  26:  }


We used the ActionLink method which helps us generate valid URL from Action and Controller name. We will reach up to that point only if there is no ClientCallbackMethod is defined. You will also notice the TagBuilder class which is can render a standard HTML tag for you even with specified style. Also do not forget to add the namespace in web.config:

<namespaces>
	<add namespace="TanzimSaqib.Mvc.Menu"/>
	...
</namespaces>


Download the code. Enjoy.

More Posts Next page »