How to pass parameters to the ViewModel in MVC

On a user account page, I had to display specific information regarding the logged in user. Things like Name, Age etc. I designed a ViewModel called UserAccount which takes in a UserId as a parameter to its contructor and based on that value, the VM pulls information from the database for that specific user. Something like this:


public class UserAccount

{

public string Name { get; set;}

public int Age { get; set; }

public UserAccount(int userId)

{

User user = GetUserFromRepository(userId);

Name = user.Name;

Age = user.Age;

}

}

All information I needed was provided by the VM but when it came to attaching this VM to the View, I got stuck. I need to mention I’m quite new to MVC but then I managed to find the solution to this:


public ActionResult MyAccount()
 {
 if (!Helper.IsUserLoggedIn())
 {
 return Redirect(Helper.GetLoginPageWithRedirect());
 }
 else
 {
 var vm = new Core.ViewModel.UserAccount(Helper.GetUserIdFromSession());

return View(vm);
 }
 }

What this does is first check whether the user is actually logged in. If not, it redirects the user to the login page because they should not  be able to access the account page without being signed in. Since you can actually pass in an object when returning a View, you can already initialise your object through constructor injection and then do “return View(YourAlreadyInitialisedObject”);” That saved me from writing extra lines of codes and it works brillantly.

Integrating Paypal button with IPN and PDT in ASP.net or MVC website

I was working on a project where a subscription is needed to access parts of the website (drivingtest.cleverdodo.mu). The idea was to have a Paypal button which would allow users to pay for 1 month subscription at a time and give them instant access. The standard button (the one you get from Paypal’s website) allows you to receive money but you’d have to manually check the payment and update the customer’s record to grant him access. If you wanted your system to be notified instantly, you’ll have to dive into Instant Payment Notification (IPN) and Payment Data Transfer (PDT).

Before I show you the C# codes for Paypal integration, you’ll need to understand what IPN and PDT are. IPN is a message service; when someone has made a payment to you, Paypal will send this message to your listener. It is the most reliable way to know whether you truly have received money because the message will be sent at some point or the other, whether the user does not return to your website after completing the payment or he loses internet connection or there’s congestion and the transaction notification is delayed. However you’ll need to have a listener to capture the message. PDT on the other hand happens when the user either clicks on the “Goc back to merchant website” link (your return url) or when the user is redirected to your website immediately after purchase. If for some reason or the other, the customer does not return to your website, you will receive no notification of a successful payment.

Therefore it is important that you have both IPN and PDT but the good news is that the codes for the two are not that much different after all.

Paypal Sandbox and the Developer account

You do not want to be testing against your live business/personal account. Therefore you’ll need to register for a developer account first to get access to Paypal Sandbox which allows you to test at your heart’s will.

Developer : https://developer.paypal.com/

Once registered, just create a test business account and a personal account so that you can check whether when you buy from the personal account, the money is transferred to your business account.

You’ll also need to sign in with your test business account to https://www.sandbox.paypal.com and go to Profile -> Website Payment Preferences and turn Auto Return and Payment Data Transfer to On so that you get an Identity Token to use for PDT. I had trouble finding my token because I was looking on the Developers url rather than the Sandbox one.

How it works in my project

When a user is logged in, he’s able to see a Paypal button in his dashboard (my account area). When he clicks the button, it takes him to an intermediate page which contains a form with all the necessary values to send off to Paypal. When this page is loaded, a record is created in the order table and the order id is inserted as a hidden form field. I need this value back from Paypal to update the corresponding user subscription record.

So this is an MVC view but the core principle remains the same for any other programming language


@{
 ViewBag.Title = "Paypal Checkout";
}

@section TestHeadContent {
<meta name="robots" content="noindex" />
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js"></script>
<script src="/scripts/jquery.countdown.min.js"></script>
<script type="text/javascript">
 $(document).ready(function () {
 $('#paypal-timer').countdown({ until: '+2s', format: 'MS', layout: '{sn}', onExpiry: SubmitTheForm });
 });

function SubmitTheForm() {
 $('#paypalform').submit();
 }
</script>
}

<div id="area">
<div style="min-height:400px;">
<strong>Redirecting you to the Paypal website in <span id="paypal-timer"></span> second(s)...</strong>
<form name="paypalform" id="paypalform" action="@ViewData["PaypalUrl"]" method="post" enctype="application/x-www-form-urlencoded">
 <input type="hidden" name="cmd" value="_xclick" />
 <input type="hidden" name="business" value="@System.Configuration.ConfigurationManager.AppSettings["Business"]" />
 <input type="hidden" name="amount" value="4" />
 <input type="hidden" name="currency_code" value="USD" />
 <input type="hidden" name="notify_url" value="@String.Concat(System.Configuration.ConfigurationManager.AppSettings["WebsiteUrl"], "paypal/ipn")" />
 <input type="hidden" name="cancel" value="@String.Concat(System.Configuration.ConfigurationManager.AppSettings["WebsiteUrl"], "paypal/cancel")" />
 <input type="hidden" name="return" value="@String.Concat(System.Configuration.ConfigurationManager.AppSettings["WebsiteUrl"], "paypal/pdt")" />
 <input type="hidden" name="item_name" value="1 Month Access" />
 <input type="hidden" name="custom" value="@ViewData["OrderId"]" />
</form>
</div>
</div>

The form is populated with values from the AppSettings; Business is your business email address and notify_url is the URL of the IPN listener. The form action is set to either the sandbox or live Paypal url depending on a setting in the web.config file. After 2 seconds, the form is automatically submitted.

Note : You cannot issue a post command from code behind (ie programmatically) because you will need to redirect the user to that same page afterwards for him to enter his details. Therefore you will lose your post data. That is why we’re using a form to post the values and send the users to Paypal.

Code for IPN

Usually this code will go into your business logic layer (thin controllers, fat models) but this is just to simplify things.


public ActionResult IPN()
 {
 log.Info("IPN listener invoked");

try
 {
 var formVals = new Dictionary<string, string>();
 formVals.Add("cmd", "_notify-validate");

string response = GetPayPalResponse(formVals);

if (response == "VERIFIED")
 {
 string transactionId = Request["txn_id"];
 string sAmountPaid = Request["mc_gross"];
 string orderId = Request["custom"];

//_logger.Info("IPN Verified for order " + orderID);

//validate the order
 Decimal amountPaid = 0;
 Decimal.TryParse(sAmountPaid, out amountPaid);

Order order = new Gis.Quiz.Core.Repository.Repository<Order>(_session).GetById(Convert.ToInt32(orderId));

// check these first
 bool verified = true;

string businessEmail = HttpUtility.UrlDecode(Request["business"]);
 if (String.Compare(businessEmail, System.Configuration.ConfigurationManager.AppSettings["Business"], true) != 0)
 verified = false;

string currencyCode = HttpUtility.UrlDecode(Request["mc_currency"]);
 if (String.Compare(currencyCode, "USD", true) != 0)
 verified = false;

string paymentStatus = HttpUtility.UrlDecode(Request["payment_status"]);
 if (String.Compare(paymentStatus, "Completed", true) != 0)
 verified = false;

if (!AmountPaidIsValid(order, amountPaid))
 verified = false;

log.Info("Business : " + businessEmail);
 log.Info("currency : " + currencyCode);
 log.Info("payment status : " + paymentStatus);
 log.Info("amount valid : " + AmountPaidIsValid(order, amountPaid).ToString());

// process the transaction
 if (verified)
 {
 log.Info("Trying to process ipn transaction...");

// check that we have not already processed this transaction
 if (String.Compare(order.TransactionId, transactionId, true) != 0)
 {
 order.Firstname = HttpUtility.UrlDecode(Request["first_name"]);
 order.Lastname = HttpUtility.UrlDecode(Request["last_name"]);
 order.Email = HttpUtility.UrlDecode(Request["payer_email"]);
 order.Street = HttpUtility.UrlDecode(Request["address_street"]);
 order.City = HttpUtility.UrlDecode(Request["address_city"]);
 order.State = HttpUtility.UrlDecode(Request["address_state"]);
 order.Country = HttpUtility.UrlDecode(Request["address_country"]);
 order.Zip = HttpUtility.UrlDecode(Request["address_zip"]);
 order.TransactionId = transactionId;
 order.StatusId = 1;
 order.UpdateDate = Gis.Quiz.Core.Utilities.Helper.FormatNHibernateDateTime(DateTime.Now);

UserSubscription us = new Gis.Quiz.Core.Repository.Repository<UserSubscription>(_session).GetById(order.UserId);
 if (us == null)
 {
 // create new subscription record
 us = new UserSubscription();
 us.Id = order.UserId;
 us.CreateDate = Gis.Quiz.Core.Utilities.Helper.FormatNHibernateDateTime(DateTime.Now);
 us.UpdateDate = Gis.Quiz.Core.Utilities.Helper.FormatNHibernateDateTime(DateTime.Now);
 us.SubscriptionEndDate = Gis.Quiz.Core.Utilities.Helper.FormatNHibernateDateTime(DateTime.Now.AddDays(30));
 }
 else
 {
 // update subscription record
 us.UpdateDate = Gis.Quiz.Core.Utilities.Helper.FormatNHibernateDateTime(DateTime.Now);
 us.SubscriptionEndDate = Gis.Quiz.Core.Utilities.Helper.FormatNHibernateDateTime(DateTime.Now.AddDays(30));
 }

using (ITransaction tx = _session.BeginTransaction())
 {
 _session.Save(order); // save order
 _session.Save(us); // save subscription

tx.Commit();
 }
 }
 }
 else
 {
 //let fail - this is the IPN so there is no viewer
 log.Info("ipn is not valid...");
 }
 }
 else
 log.Info("ipn not verified...");
 }
 catch (Exception ex)
 {
 log.Error("Error caught in IPN : " + ex.ToString());
 }

return View();
 }

You need to check you haven’t processed the transaction yet because you could end up doing it twice as Paypal could send you a notification through both PDT and IPN.

The PDT Code


public ActionResult PDT()
 {
 log.Info("PDT called...");

try
 {
 //_logger.Info("PDT Invoked");
 string transactionId = Request.QueryString["tx"];
 string sAmountPaid = Request.QueryString["amt"];
 string orderId = Request.QueryString["cm"];

Dictionary<string, string> formVals = new Dictionary<string, string>();
 formVals.Add("cmd", "_notify-synch");
 formVals.Add("at", System.Configuration.ConfigurationManager.AppSettings["PayPalPDTToken"]);
 formVals.Add("tx", transactionId);

string response = GetPayPalResponse(formVals);
 //_logger.Info("PDT Response received: " + response);
 if (response.StartsWith("SUCCESS"))
 {
 //_logger.Info("PDT Response received for order " + orderID);

//validate the order
 Decimal amountPaid = 0;
 Decimal.TryParse(sAmountPaid, out amountPaid);

Order order = new Gis.Quiz.Core.Repository.Repository<Order>(_session).GetById(Convert.ToInt32(orderId));

//check the amount paid
 if (AmountPaidIsValid(order, amountPaid))
 {
 // check that we have not already processed this transaction
 if (String.Compare(order.TransactionId, transactionId, true) != 0)
 {
 order.Firstname = GetPDTValue(response, "first_name");
 order.Lastname = GetPDTValue(response, "last_name");
 order.Email = GetPDTValue(response, "payer_email");
 order.Street = GetPDTValue(response, "address_street");
 order.City = GetPDTValue(response, "address_city");
 order.State = GetPDTValue(response, "address_state");
 order.Country = GetPDTValue(response, "address_country");
 order.Zip = GetPDTValue(response, "address_zip");
 order.TransactionId = transactionId;
 order.StatusId = 1;
 order.UpdateDate = Gis.Quiz.Core.Utilities.Helper.FormatNHibernateDateTime(DateTime.Now);

UserSubscription us = new Gis.Quiz.Core.Repository.Repository<UserSubscription>(_session).GetById(order.UserId);
 if (us == null)
 {
 // create new subscription record
 us = new UserSubscription();
 us.Id = order.UserId;
 us.CreateDate = Gis.Quiz.Core.Utilities.Helper.FormatNHibernateDateTime(DateTime.Now);
 us.UpdateDate = Gis.Quiz.Core.Utilities.Helper.FormatNHibernateDateTime(DateTime.Now);
 us.SubscriptionEndDate = Gis.Quiz.Core.Utilities.Helper.FormatNHibernateDateTime(DateTime.Now.AddDays(30));
 }
 else
 {
 // update subscription record
 us.UpdateDate = Gis.Quiz.Core.Utilities.Helper.FormatNHibernateDateTime(DateTime.Now);
 us.SubscriptionEndDate = Gis.Quiz.Core.Utilities.Helper.FormatNHibernateDateTime(DateTime.Now.AddDays(30));
 }

using (ITransaction tx = _session.BeginTransaction())
 {
 _session.Save(order); // save order
 _session.Save(us); // save subscription

tx.Commit();
 }

ViewData["fb"] = "Thank you for your payment. Your transaction has been completed and a receipt for your purchase has been emailed to you. You can now access the paid members area of our website by <a href=\"/myaccount\">clicking here</a>.";
 }
 else
 {
 ViewData["fb"] = "This order has already been processed.";
 }
 }
 else
 {
 //Payment amount is off, this can happen if you have a Gift cert at PayPal, be careful of this!
 ViewData["fb"] = "Payment amount received from paypal does not match order total.";
 }
 }
 else
 {
 ViewData["fb"] = "Your payment was not successful with PayPal";
 }
 }
 catch (Exception ex)
 {
 log.Error("Error caught in PDT : " + ex.ToString());
 }

return View();
 }

PayPalPDTToken is the Identity Token you got in an earlier step above.

The helper methods


/// <summary>
 /// Utility method for handling PayPal Responses
 /// </summary>
 string GetPayPalResponse(Dictionary<string, string> formVals)
 {
 bool useSandbox = Convert.ToBoolean(System.Configuration.ConfigurationManager.AppSettings["IsLocal"]);

string paypalUrl = useSandbox ? "https://www.sandbox.paypal.com/cgi-bin/webscr"
 : "https://www.paypal.com/cgi-bin/webscr";

 HttpWebRequest req = (HttpWebRequest)WebRequest.Create(paypalUrl);

// Set values for the request back
 req.Method = "POST";
 req.ContentType = "application/x-www-form-urlencoded";

byte[] param = Request.BinaryRead(Request.ContentLength);
 string strRequest = Encoding.ASCII.GetString(param);

StringBuilder sb = new StringBuilder();
 sb.Append(strRequest);

foreach (string key in formVals.Keys)
 {
 sb.AppendFormat("&{0}={1}", key, formVals[key]);
 }
 strRequest += sb.ToString();
 req.ContentLength = strRequest.Length;

//for proxy
 //WebProxy proxy = new WebProxy(new Uri("http://urlort#");
 //req.Proxy = proxy;
 //Send the request to PayPal and get the response
 string response = "";
 using (StreamWriter streamOut = new StreamWriter(req.GetRequestStream(), System.Text.Encoding.ASCII))
 {

streamOut.Write(strRequest);
 streamOut.Close();
 using (StreamReader streamIn = new StreamReader(req.GetResponse().GetResponseStream()))
 {
 response = streamIn.ReadToEnd();
 }
 }

return response;
 }

bool AmountPaidIsValid(Order order, decimal amountPaid)
 {
 bool result = true;

if (order != null)
 {
 if (order.Total > amountPaid)
 {
 //_logger.Warn("Invalid order amount to PDT/IPN: " + order.ID + "; Actual: " + amountPaid.ToString("C") + "; Should be: " + order.Total.ToString("C") + "user IP is " + Request.UserHostAddress);
 result = false;
 }
 }
 else
 {
 //_logger.Warn("Invalid order ID passed to PDT/IPN; user IP is " + Request.UserHostAddress);
 }
 return result;
 }

string GetPDTValue(string pdt, string key)
 {
 string[] keys = pdt.Split('\n');
 string thisVal = "";
 string thisKey = "";
 foreach (string s in keys)
 {
 string[] bits = s.Split('=');
 if (bits.Length > 1)
 {
 thisVal = HttpUtility.UrlDecode(bits[1]); // values are urlencoded, not htmlencoded
 thisKey = bits[0];
 if (thisKey.Equals(key, StringComparison.InvariantCultureIgnoreCase))
 break;
 }
 }
 return thisVal;
 }

Important things to remember

Here are some thing worth noting:

  • You need to be logged into your sandbox account on the same browser that you’re sending off the request to Paypal to make a sucessful transaction in testing mode.
  • You can only test IPN on a URL that’s publicly accessible. So localhost will not work.

Paypal integration is not complicated but I had to read lots on different forums/blogs and StackOverflow and I’m grateful to Kona.Web from which I’ve borrowed a lot of code.

Displaying your recent posts in Tumblr

Tumblr does not provide an easy way to show your latest posts on your blog. The infinite scrolling is nice but you want your visitors to be able to see say the last 5 posts you’ve made so they can jump straight to a specific post instead of having to read through all posts until they reach something worth reading. So to achieve this functionality, I’ve used Jquery/JavaScript.

There are 3 things to accomplish this:

  1. If you’re not already using the jquery library, you need to include it in your theme.
  2. Insert an html ul tag where you want the posts to render.
  3. Add the jQuery code to your theme.

Some themes already come with jquery and therefore there’s no need to add it again. However the library needs to appear before the jQuery code for it to work. Check for the word jquery in your theme’s html and if you can’t find any instances of it, then add it in the head section, that is within these tags <head></head>. Then right after it, place the jQuery code. So you will have the following:


<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script>

<script type="text/javascript">
$(function() {
var url = 'http://staff.tumblr.com/api/read?start=0&num=5';
var $list = $('#recent-posts');
$.ajax({
 url: url,
 type: 'GET',
 dataType: 'xml',
 success: function(data) {
 var posts = $(data).find('post');
 posts.each( function() {
 var post = $(this);
 var link = post.attr('url-with-slug');
 var title = post.children('regular-title').text();
 if (link && title) {
 $list.append('<li><a href="' + link + '">' + title + '</a></li>');
 }
 });
 }
});
});
</script>

Replace staff.tumblr.com with the hostname of your blog and if you want to change how many posts are displayed, then change the number 5 at the end of the url eg http://staff.tumblr.com/api/read?start=0&num=20. The latter will retrieve the last 20 blog posts.

Now you need to include the following somewhere in your theme where you want the posts to be displayed:


<ul id="recent-posts">
 </ul>

You might want to place that in the sidebar so that it’s prominent. And that’s it really.

Any problems, let me know 🙂

Creating an event in MySql

If you want an action to happen at a specific time or at regular intervals, events in MySql is the way to go. Triggers are only good as a reaction to something that happens to the tables in your database, eg a new column added. In my case, I needed to clear off session data and I decided to create an event as follows:


CREATE EVENT SessionRemover
 ON SCHEDULE
 EVERY 5 MINUTE
 DO
 DELETE FROM Session WHERE Expires < NOW();

The statement failed for me because I did not have the event privilege. So I had to grant that to the user I was using for connecting to the database:


GRANT EVENT ON *.* TO someone@"%" IDENTIFIED BY ‘password’ WITH GRANT OPTION;

The event was created but it never ran. When I investigated the problem, it turned out that the event scheduler was not running. So I had to run this sql as well:

SET GLOBAL event_scheduler = ON;

Everything worked as expected until the server got restarted and my session count got up to 8000!!! I checked whether the event scheduler was running with this sql:

SHOW PROCESSLIST

But I did not find a user with the name ‘event_scheduler’ in the list. To make the event scheduler run event when the server restarts, you have to add it to the configuration file. I’m using Windows 2008 and I had to add the following line to ini file located at YourMySqlInstallationDir/MySql Server 5.1/my.ini
event_scheduler=ON
I couldn’t find any entry for this statement, so I added it to the end of the my.ini file.

How to map another file extension to the php parser through htaccess

I was in the process of cleaning up my web hosting accounts when I came across one plan which had 2 sites on it. One of them was a static website (pure html) and the other one was written in classic asp. So I decided to move both sites to another hosting plan because it was just a waste of money as the sites did not have much traffic and there was no justification why the hosting was needed.

Since windows hosting plans are more expensive, I wanted to move the sites to a linux server and I knew I wouldn’t have any problems with the html website but I wondered how much trouble it would be to move the classic asp website. The code for the dynamic website was not using a lot of asp code as in only the files ended with an .asp extension but a php parser could very well serve the webpage. So after much googling, I found that adding a line to the .htaccess file would map any file extension to the php and that would do the trick for me.

Mapping asp to php parser

The line below will tell the php parser to treat files ending with .asp extension as if they were a php file.

AddType application/x-httpd-php .asp

How the above line did not work for me and I had a lot of problems trying to map the classic asp code before I found the solution. It has something to do with the version of php you’re using. Mine was 5.12.53 I think. I added the following lines in my .htaccess file and it worked:

AddType application/x-httpd-php4 .asp
AddType application/x-httpd-php5 .asp

Both lines are required. There’s something else that you need to do if you can’t get it to work. You can replace AddType with AddHandler (that depends if you’re running Apache as a module or CGI script). And sometimes you might need to remove the application word and have only this:

AddHandler x-httpd-php5 .asp

If you wanted to map another extension, you would do this:

AddType application/x-httpd-php5 .html .htm .me .newextension

Files ending with .html, .htm, .me and .newextension will then be run through the php parser and it would allow you to serve dynamic content through .html files or hide (disguise) the programming language you’re using on your website or make it easy to move from one coding language to another because if you don’t use a file extension reserved for a particular programming language, then you can easily switch to another programming language or server.

While mapping another extension, I also did two other things:

Define a default document (so that index.asp would be recognised as a default webpage for the folder/directory)

DirectoryIndex index.html index.htm index.php index.asp

Disable directory browsing (so that folders/directories without a default webpage are not listed)

Options -Indexes

Well so far so good, now just waiting for DNS propagation to complete before I can cancel the hosting plan.

 

Fixing the problem with Static Compression Module frequentHitThreshold

While trying to speed up my website, I struggled with getting the StaticCompressionModule to work on my local machine. By default, the static compression module is enabled so you would think that all your static files like javascript and css will be gzipped before being sent to your browser. However this is not necessarily the case as I found out with Chrome Developer Tools and the Firefox YSlow plugin.

Although the module is enabled, static files are only going to be compressed if they meet a certain criteria determined by the frequency of access controlled by two parameters (frequentHitThreshold and frequentHitTimePeriod). The first parameter is set to 2 and the second one to 10 seconds by default. This means that if a period of 10s, if you get 2 request for your static files then they will be sent as gzip. However this is not what I wanted and I needed to correct this behaviour.

It is possible to change the values for these parameters but when I put it in the web.config file, I got the following error:

This configuration section cannot be used at this path. This happens when the section is locked at a parent level. Locking is either by default (overrideModeDefault=”Deny”), or set explicitly by a location tag with overrideMode=”Deny” or the legacy allowOverride=”false”.

I googled the problem and thought I’d give the command line a try:

%windir%\system32\inetsrv\appcmd.exe set config -section:system.webServer/serverRuntime -frequentHitThreshold:1

Substitute your windir accordingly.

That worked great but I wanted to know why the web.config gave an error and it turned out that the config needed to be within a location tag  as follows:


<location path="Default Website">
 <system.webServer>
 <serverRuntime enabled="true" frequentHitThreshold="1" frequentHitTimePeriod="00:00:10" />
 </system.webServer>
 </location>

Once the value of the frequentHitThreshold was changed to 1, I was able to see the content compressed before being sent out from the server to the browser. Sweet!

Output caching in ASP.NET with text substitution and QueryString criteria

To speed up a dynamic website that I maintain, I decided to use the output caching mechanism built into ASP.NET. I wanted to bypass the page lifecycle and database roundtrips to achieve a faster loading webpage. However I was faced with a couple problems which are worth noting.

Memory is more expensive than disk space so it was important that I cache the webpages to disk rather than in-memory. I therefore hook up a custom output cache provider by leveraging the features of ASP.NET 4.0 and tailored it for disk caching. This worked fine for the caching portion but I also needed to evict certain pages based on user actions.

Deleting output cache for a single page based on QueryString

Delete the cache version of a page like “Product.aspx” is easy but if you wanted to evict “Product.aspx?id=4” from the cache, then there’s no easy solution. There is only one method to delete cache : HttpResponse.RemoveOutputCacheItem which takes a virtual absolute path to work. When I looked at the code through reflection, I saw that this method was taking in parameters like the path to the webpage, the HttpVerb (GET or POST), the VaryBy parameters to generate a cache key for that particular webpage. To evict an item from the cache programmatically, you would need to know the cache key and it’s actually difficult to recompute the key without making your own method which will be an overkill.

To overcome the problem, I changed the custom cache provider and had a static RemoveItemFromCache method which takes in a cached key and deletes the file. I analysed the cached key that was being generated (eg a2/product.aspxHQNidV4FCDE) and realised that after HQN it was the QueryString parameter that was inserted and before FCDE it was the value of the QueryString. I could therefore delete a particular page by entering the cache key. Note that when the cached files are saved to disk, the “/” in the file path are replaced with an underscore “_”.

Session is not accessible when output caching is enabled

Full page caching is better than caching multiple parts of the webpage through user controls as you will see a better performance with the former. However I needed to have login links for anonymous users (users which are not logged in yet) and logout links for those who are currently signed in. There’s a substitution control which you can use for that matter :

<asp:Substitution ID=”MySubstitutionControl” MethodName=”” runat=”server” />

The MethodName that you specify will return a string which will be inserted in the cached page. This is known as donut caching as you’re caching the whole page but have holes in it (placeholders) where you can insert dynamic text.  You can also use Response.WriteSubstitution in your code behind if you don’t like the idea of declarative syntax.

The problem with the substitution control however is that the Session object is not accessible. I was storing the username of the currently logged in user in the session but with the output caching, I was not able to retrieve this info from the session as it was always null. It makes sense though because when using output caching you’re bypassing the asp.net page life cycle and the session object is not populated and the page is rather retrieved from cache. For me, it was important to display the username with a logout link though and I had to find a solution to it.

My workaround was to use a cookie to store the username when people sign in. Since the cookies collection is available (as well as the request object), I was able to display the logout link with the username properly.

AdRotator does not do post cache substitution when the AdCreated method has been overridden

The ASP.NET team says that the AdRotator control has built in post cache substitution and while this is true, you will encounter problems if you override the AdCreated Event. By doing this, you’re losing the post cache features and you’ll notice that an advertisement banner that was shown on the first view of a page before it is cached will be shown on subsequent views of that page because substitution will not occur. To fix the problem, you’ll have to come with your own post caching mechanism for this control or build your own custom control to display dynamic banners which is not a tough task.

Server.Transfer will not cache out page response

On one particular page, I was using a Default.aspx page in the root to transfer to another page where the logic is run. This is because I wanted a particular folder to display the same content (varied by querystring) from a different page. The server.transfer method worked fine except when I enabled caching on the transferred to page. So if Page A is transferred to Page B and Page B has the output cache, you’ll find that Page A won’t be output cached.

The solution to this problem is to use Server.Execute instead of Server.Transfer and Page A will be cached.

Server.Execute(“~/MyPageA.aspx”, Response.Output);

Programmatically using output cache in code behind

I found that using Response.Cache with multiple VaryByParam parameters does not work well. So if you use this:


Response.Cache.SetExpires(DateTime.Now.AddSeconds(36000));
Response.Cache.SetCacheability(HttpCacheability.Server);
Response.Cache.VaryByParams["p"] = true;
Response.Cache.VaryByParams["s"] = true;
Response.Cache.VaryByParams["t"] = true;
Response.Cache.SetVaryByCustom("RawUrl");
Response.Cache.SetValidUntilExpires(true);

You’ll have to use this instead :

Response.Cache.SetExpires(DateTime.Now.AddSeconds(36000));
Response.Cache.SetCacheability(HttpCacheability.Server);
Response.Cache.VaryByParams["*"] = true;
Response.Cache.SetVaryByCustom("RawUrl");
Response.Cache.SetValidUntilExpires(true);

The wildcard (*) will cache your pages properly.

Configuring Windows Firewall to use IP range to restrict access to your server

To keep your Windows 2008 server secure, it is important to restrict access to who is allowed to connect to your server. You may have programs like FileZilla, SQL Server/MySql and Remote Desktop and just having a strong password for those services is not enough. You should therefore create Windows Firewall inbound rules to prevent unauthorised access to your server.

My previous ISP gave me an IP address which lasted at least 6 months before being renewed. So I could add my IP address to the Windows Firewall and deny access to all others. That worked fine until I changed ISP and everytime I connect I was given a new IP address. This caused a huge problem for me and I needed to find a solution. I didn’t want to remove that Windows Firewall rule and just have the password as a method of defence against hackers or opportunists.

So I started analysing the IP addresses that were assigned to me by my new ISP every day or so. From the data I gathered, I found a pattern which shows that the first 2 digits were fixed, the 3rd one varied between just two consecutive numbers and the fourth one was dynamic (as in it could be any number). With that information, I knew exactly what I had to do. Instead of listing a static IP in Windows Firewall, I could set an IP range which could connect to the server.

Let’s suppose, the lowest IP address was 140.192.45.0 and the highest was 140.192.46.255. You could put that in the Firewall inbound rule easily as the starting from and upto range as follows:

IP address range

However there’s a more elegant way of doing that – by specifying a subnet mask. You could try type in 140.192.45.0/23 as follows:

ip address subnet mask

I’ve used the subnet calculator to correctly assign the range : http://www.subnet-calculator.com/cidr.php

Note that although this configuration will allow a lot of IP addresses within that range to access the server, it’s better than leaving it open to the whole world. The combination of the IP range along with a strong password makes it more secure than having no IP restriction at all.

How to do a one-to-one mapping in Fluent nHibernate

In most circumstances, you will use a many-to-one mapping and that’s easily created with fluent nHibernate using the “References” relationship. However if you need to perform a 1 to 1 mapping, then things get tricky. Say for example, you have a user record and wanted to have the user preferences in another table. Each user will have a record in the preferences table so it’s a direct one to one mapping. You will then have to use the HasOne relationship in fluent nhibernate.

public class User
{
    public virtual int Id { get; set; }
    public virtual string Username { get; set; }
    public virtual UserPreference Preference { get; set; }
}

public class UserMap : ClassMap<User>
{
    public UserMap()
    {
        Id(x => x.Id);
        Map(x => x.Username);

        HasOne(x => x.Preference);
    }
}

public class UserPreference
{
    public virtual int UserId { get; set; }
    public virtual bool Newsletter { get; set; }
}

public class UserPreferenceMap : ClassMap<UserPreference>
{
    public UserPreferenceMap()
    {
        Id(x => x.UserId).GeneratedBy.Assigned();
        Map(x => x.Newsletter);
    }
}

So we have a User table with Id set as the primary key and we have a second table UserPreference with UserId as the primary key. The Id on the user table is autogenerated by the database but when a user entity has already been created, we pass this object to the UserPreference entity so that it can get the UserId. That’s why we need to specify in the mapping class for UserPreferenceMap that the Id is Assigned.

Enum conversion in C#

Just for reference, here are some examples on how enums can be converted in C#:

public class MyAnimalClass
{
    public enum Animal
    {
        Cat = 1,
        Dog = 2,
        Bird = 3
    }

    // enum to string
    public void Hey()
    {
        Animal animal = Animal.Bird;
        string animalText = animal.ToString(); // Bird

        HttpContext.Current.Response.Write("<br />enum to string : " + animalText);
    }

    // string to enum
    public void Hey2()
    {
        string animalText = "cat";
        Animal animal;
        // Enum.TryParse will return false if the string value cannot be converted to the enum type
        if (!Enum.TryParse<Animal>(animalText, out animal))
            HttpContext.Current.Response.Write("<br />string to enum is null for : " + animalText);
        else
            HttpContext.Current.Response.Write("<br />string to enum: " + animal.ToString()); // Animal.Cat

        Animal animal2 = (Animal)Enum.Parse(typeof(Animal), animalText, true); // Animal.Cat (case insensitive comparison)

        HttpContext.Current.Response.Write("<br />string to enum insensitive: " + animal2.ToString());
    }

    // int to enum
    public void Hey3()
    {
        int animalInt = 2;
        Animal animal = (Animal)animalInt;

        HttpContext.Current.Response.Write("<br />int to enum : " + animal.ToString());
    }

    // enum to int
    public void Hey4()
    {
        Animal animal = Animal.Bird;
        int animalInt = (int)animal;

        HttpContext.Current.Response.Write("<br />enum to int : " + animalInt.ToString());
    }
}

Using enums when coding makes life easier as you can easily tell what sort of object you’re dealing with rather than having to guess what the numbers (1,2,3, etc) mean really.