Thursday 28 February 2013

Mocking HttpContext using Fakes framework

It is a common problem when unit testing a MVC controller action which uses HttpContext in some capacity. By default the HttpContext.Current returns null during the execution of test case as the execution takes place outside a webserver and hence results in the expected "Object reference not set to an instance of an object" exception.

So how do we avoid it? Well, simply mock it. If you are using .NET 4.5, then you can use the newly introduced Fakes framework by Microsoft. Since System.Web is a framework assembly, we need to create "Shim" behavior for methods or properties that we expect to be used in the controller's action.

For example - assume that "Index" action of HomeController is implemented like following:

        public ActionResult Index()
        {
            ViewBag.Message = System.Web.HttpContext.Current.Request.Url;
            return View();
        }
 
You will need to add Fakes for System.Web assembly which you can do by simply "Right-Click"ing on the System.Web assembly in references of the unit test project and selecting "Add Fakes Assembly" option. Once the fakes implementation is added, the unit test of the action can be written like following:
 
        [TestMethod]
        public void Index()
        {
            using (ShimsContext.Create())
            {
                System.Web.Fakes.ShimHttpContext.AllInstances.RequestGet =
                (e) =>
                {
                    return new System.Web.Fakes.ShimHttpRequest()
                    {
                        UrlGet = () =>
                        {
                            return new Uri(http://localhost/);
                        }
                    };
                };
 
                System.Web.Fakes.ShimHttpContext.CurrentGet =
                    () =>
                    {
                        return new System.Web.Fakes.ShimHttpContext();
                    };
 
                HomeController controller = new HomeController();
                ViewResult result = controller.Index() as ViewResult;
                Assert.AreEqual("http://localhost/", result.ViewBag.Message.ToString());
            }
        }
 
Like they say - Fake it till you make it :)

Sunday 24 February 2013

URL Rewriting for WCF service

How do you consume a IIS hosted WCF service without referring to the .svc file in the URL? Say, a service URL is http://localhost/Test/Test.svc and I want to access it using http://localhost/Test

One of the solutions is described here :

1. Enable file less activation in WCF service.
2. Host the WCF service in aspNetCompatibility mode
3. Add Global.asax file to the project
4. Add routing handler to web.config by referring System.Web.Routing
5. Add a route mapping in Application_Start method of Global.asax using ServiceRoute class present in System.ServiceModel.Activation

I came across a similar problem where I had to access my WCF service using a URL that followed a pattern like - http://localhost/Test/X where X is not a constant value and is added by the caller at runtime. Also, the value of X is expected to the used inside the implementation of the invoked WCF method.

The above mentioned approach did not work directly for my case owing to a known limitation. So instead of using the ASP.NET routing as a means, I looked at IIS routing. Since our WCF service is going to be hosted in Integrated Mode in IIS, thus there were not many roadblocks. So here is what I did:

1. Installed Web Platform Installer on my machine. It is an extremely cool utility that allows me to search for useful components, applications and products that can be consumed readily.
2. Installed "IIS URL Rewrite Module 2" by selecting URL Rewrite 2.0 in web platform installer. More details about the capabilities of the modules are available here
 

3. Modify the web.config of the WCF service application to include a rewrite rule. The rule uses a regular expression to convert the part of URL present after the application name into a querystring.
 
With this rule in place the URL http://localhost/Test/X to http://localhost/Test/Test.svc?q=X

4. To access the querystring values in the WCF service method, I enabled aspNetCompatibilityMode also.

And we are done.
 
UPDATE (4/2/2013): One more example can be when you want URL rewrite module to handle clean urls like "products/category/10/product/20" and translate to "products.aspx?category=10&product=20". You can use following rule for that -
 
 
 
 
You can try advanced scenario by launching the URL rewrite module from IIS - It has a pretty helpful wizard and makes url rewriting a breeze.

Tuesday 12 February 2013

Faster DELETE operation on SQL Server Tables

SQL Server is a tricky fellow. Just when you start feeling that whatever you have done in a T-SQL script makes perfect sense, SQL Server starts to show unwanted characteristics such as slowness in query execution time, unnecessary waits etc. One such situation arises when you write a script to delete rows from a table.

If the number of candidate rows are small then the standard DELETE command with a WHERE clause that covers all the rows works like a charm. But try increasing the number of candidate rows to a mildly big number like one million (1000, 000) rows and you will start to notice considerable slowness in DELETE operation. Can we do the intended operation any faster? Yes :)

If the candidate rows include all the rows in the table and you do not care if the identity column in table are reseeded, then go ahead with TRUNCATE TABLE command. It is one of the fastest way of getting rid of data present in the table.

If the situation requires you to perform DELETE on selective rows (but a large number of rows), then you can use try to perform DELETE operation in relatively smaller batches along with WITH (TABLOCKX) query hint. TABLOCKX takes an exclusive lock on table for the lifetime of a transaction. If the table is going to be part of transactions run by other queries then try using WITH (TABLOCK) query hint which suggests a "shared" lock on the table. Below is a sample scenario that I created to test the approach:

Create a simple table with 10 data columns.

Create Table TestTableWith10Columns
(
 Id bigint not null identity (1,1) PRIMARY KEY,
 col1 int not null,
 col2 int not null,
 col3 int not null,
 col4 int not null,
 col5 int not null,
 col6 int not null,
 col7 int not null,
 col8 int not null,
 col9 int not null,
 col10 int not null,
)
Populate it with random data. In this case I inserted 1,048,576 rows -

SET NOCOUNT ON
INSERT INTO TestTableWith10Columns
(col1, col2, col3, col4, col5, col6, col7, col8, col9, col10)
VALUES (1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
DECLARE @counter INT = 0
DECLARE @cutOff INT = 1000000
WHILE (@counter < @cutOff)
BEGIN
INSERT INTO TestTableWith10Columns
(col1, col2, col3, col4, col5, col6, col7, col8, col9, col10)
select col1, col2, col3, col4, col5, col6, col7, col8, col9, col10
from TestTableWith10Columns
SET @counter = @counter + @@ROWCOUNT
END
SELECT count(1) FROM TestTableWith10Columns -- 1,048,576 rows inserted by now.
SET NOCOUNT OFF

Now try running a simple DELETE operation like following. It takes about 9 seconds.

DELETE FROM TestTableWith10Columns -- 9 seconds

Re-run the query to populate the same number of rows in the table use Truncate operation like following. It finishes in less than one second.

TRUNCATE TABLE TestTableWith10Columns -- less than a second

Re-run the query to populate the table again and use following query to delete the rows.

SET NOCOUNT ON
DECLARE @rowsDeleted INT;
DECLARE @rowsDeletedCutOffPerIteration INT
SET @rowsDeleted = 1
SET @rowsDeletedCutOffPerIteration = 100000
WHILE (@rowsDeleted > 0)
BEGIN
 DELETE TOP (@rowsDeletedCutOffPerIteration)
 FROM TestTableWith10Columns

 SET @rowsDeleted = @@ROWCOUNT
END
SET NOCOUNT OFF

The query takes about 3 seconds.


* all the numbers are measured on my machine. Also, one should look for alternative options when the simplest of options (in this case a DELETE statement) seems to run slowly consistently i.e. the query is found to be running slowly over a period of days at multiple occasions. Sometimes query might be running slow because some other process is hogging system resources.