Extension methods in .NET part 3: mixture of useful methods

Introduction

So far in this series we’ve looked at the foundations of extension methods in C#. We’ve also seen a number of examples and special cases. We’ll continue our discussion of with a mixture of useful applications of extension methods.

Exceptions

Extending exceptions can be useful if you’d like to get a user-friendly output of wrapped exceptions. You normally don’t want to show the full stacktrace of the top-level and wrapped exceptions to a user. The following extension method collects the exception message from the inner exceptions as long as they are not null:

public static string UserFriendlyFullMessage(this Exception exception)
{
	StringBuilder exceptionBuilder = new StringBuilder();
	while (exception != null)
	{
		exceptionBuilder.AppendLine(exception.Message);
		exception = exception.InnerException;
	}
	return exceptionBuilder.ToString();
}

Let’s say you have the following method:

private static void TestExceptionExtension(int divideBy)
{
	try
	{
		int res = 10 / divideBy;
	}
	catch (Exception ex)
	{
		InvalidOperationException ioex = new InvalidOperationException("What are you doing???", ex);
		string errorMessage = string.Concat("Division failed. Tried to divide by ", divideBy);
		throw new ApplicationException(errorMessage, ioex);
	}
}

Call the method as follows and print the exception messages on the console window:

try
{
	TestExceptionExtension(0);
}
catch (Exception ex)
{
	String messages = ex.UserFriendlyFullMessage();
	Console.WriteLine(messages);
}

The output is:

Division failed. Tried to divide by 0
What are you doing???
Attempted to divide by zero.

This is a cleaner presentation of the exceptions to a user who is not familiar with stacktraces and should not even be able to view them.

Enumerations

Consider the following enumeration:

public enum Difficulty
{
	Easy
	, Medium
	, Hard
	, Master
}

We can extend enumerations the same way as other objects. The following extension method will hide the ugly Enum.GetName call behind a friendly method signature:

public static class EnumExtensions
{
	public static string ToUserFriendlyString(this Enum value)
	{
		return Enum.GetName(value.GetType(), value);
	}
}

You can call this extension method on any enumeration:

string val = Difficulty.Easy.ToUserFriendlyString();

You can add an extended description to a specific enum value as follows:

public enum Difficulty
{
	[Description("This is the easy level")]
	Easy
	, Medium
	, Hard
	, Master
}

…where the Description attribute is found in the System.ComponentModel namespace so you’ll need to reference the correct library.

You can read the detailed description using Reflection. We’ll hide the ugly details behind another extension method:

public static string GetDetailedDescription(this Enum value)
{
	FieldInfo fieldInfo = value.GetType().GetField(value.ToUserFriendlyString());
	DescriptionAttribute descriptionAttribute = fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false).FirstOrDefault() as DescriptionAttribute;
	return descriptionAttribute == null ? value.ToUserFriendlyString() : descriptionAttribute.Description;
}

We check if there’s a Description attribute on the enumeration value. If that’s the case then we return the description. Otherwise we’ll use the extension method defined above to return the default value.

You can call these methods as follows:

string val = Difficulty.Easy.ToUserFriendlyString();
string desc = Difficulty.Easy.GetDetailedDescription();

Web API

If Web API doesn’t tell you much then check out its home page. It is a great technology to create RESTful web services. According to RFC 2616, i.e. the HTTP protocol specifications, if a new resource is created with a POST or PUT request then the service should respond with a 201 status code – meaning Created – and a Location header specifying the URL of the newly created resource. Example: “Location: http://api.mysite.com/products/10”.

It’s customary for Web API controllers to return a HttpResponseMessage object to the client. It can respond with other objects, such as a string, but the HttpResponseMessage object is very rich. You can set the status code, the content body, the HTTP response headers etc.

The solution will therefore extend the HttpResponseMessage object. It will also accept an HttpRequestMessage object which includes the details about the original incoming request. The request is necessary so that we can find the original URI that was used for the POST/PUT operation. We’ll follow the convention that individual resources can be located by an ID parameter in the URL like in the example above: /products/10.

For the solution to work the Controller that processes the POST/PUT request must also be able to process the GET. This is so that URL in the POST/PUT request can be used to extract the URL for the GET in the Location header. Also, the Controller must have a GET method that returns a resource by its id. The following extension method will do the job:

public static void InsertLocationHeader(this HttpResponseMessage httpResponseMessage, 
			HttpRequestMessage httpRequestMessage, int idOfNewResource)
{
	string uri = string.Concat(httpRequestMessage.RequestUri, "/", idOfNewResource);
	httpResponseMessage.Headers.Location = new Uri(uri);
}

We don’t need to return anything from this method. We just take advantage of the fact that objects are reference types and therefore we can modify the state of the httpResponseMessage object we’re extending.

The method can be called as follows:

HttpResponseMessage httpResp = //build the response message;
httpResp.InsertLocationHeader(Request, 20);

The original request can be acquired using the built-in Request property.

Epoch datetime

There’s no built in method in .NET to convert DateTime objects to UNIX timestamps, i.e. to the seconds passed since the midnight of Jan 01 1970. Nothing could be simpler with an extension method:

public static long ToUnixTimeStamp(this DateTime dateTimeUtc)
{
      DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0);
      TimeSpan timeSpan = dateTimeUtc - epoch;
      return Convert.ToInt64(timeSpan.TotalSeconds);
}

We construct a DateTime with the starting point for the Unix timestamp. We get the time span between ‘now’ and the start date. Finally we extract the seconds passed during that time span.

SecureString

The SecureString object can be used to keep confidential text securely in memory. The following extension method will convert a string to a SecureString:

public static SecureString ToSecureString(this string s)
{
        if (s == null)
             return null;

        SecureString psw = new SecureString();
        foreach (char c in s.ToCharArray())
        {
            psw.AppendChar(c);
        }
        return psw;
}

Various

The list of useful extension methods could go on and on. By now you’ve probably got the general idea behind extension methods so you can construct your own. To finish off this short series here’s a couple more methods that you can use in your projects.

Check if integer is within some interval:

public static bool IsBetween(this int theNumber, int lower, int higher)
{
	return (theNumber >= lower) && (theNumber <= higher);
}

Remove some prefix or suffix from a string:

public static string TrimPrefix(this string str, string prefix)
{
        if (!String.IsNullOrEmpty(str) && !String.IsNullOrEmpty(prefix) && str.StartsWith(prefix))
	{
		 return str.Substring(prefix.Length);
	}
	return str;
}

public static string TrimSuffix(this string str, string suffix)
{
        if (!String.IsNullOrEmpty(str) && !String.IsNullOrEmpty(suffix) && str.EndsWith(suffix))
	{
		 return str.Remove(str.Length - suffix.Length);
	}
	return str;
}

Extract the base64 encoded username and password from the Basic Auth header of a HttpRequestMessage object in Web API:

public static Tuple<string, string>
            ExtractUsernameAndPasswordFromHttpHeader(this HttpRequestMessage requestMessage)
{
            Dictionary<string, string> headers = requestMessage.ReadHeaders();
            if (headers.ContainsKey("Authorization"))
            {
                string authHeaderValue = headers["Authorization"];
                authHeaderValue = authHeaderValue.Trim();
                string encodedCredentials = authHeaderValue.Substring(6);
                byte[] decodedBytes = Convert.FromBase64String(encodedCredentials);
                string s = new ASCIIEncoding().GetString(decodedBytes);
                string[] userPass = s.Split(new char[] { ':' });
                string username = userPass[0];
                string password = userPass[1];
                return new Tuple<string, string>(username, password);
            }
            else
            {
                throw new ArgumentNullException("Username and password couldn't be extracted from the HttpHeaders collection.");
            }
}

Extract all headers from a HttpRequestMessage into a Dictionary:

public static Dictionary<string, string>
            ReadHeaders(this HttpRequestMessage requestMessage)
{
      return ExtractHeaders(requestMessage.Headers);
}

private static Dictionary<string, string> ExtractHeaders(HttpHeaders headers)
{
            Dictionary<string, string> dict = new Dictionary<string, string>();
            foreach (var kvp in headers.ToList())
            {
                if (kvp.Value != null)
                {
                    string header = string.Empty;
                    foreach (var j in kvp.Value)
                    {
                        header += j + " ";
                    }
                    dict.Add(kvp.Key, header);
                }
            }
            return dict;
}

Check if an IEnumerable of T is empty:

public static bool IsEmpty<T>(this IEnumerable<T> list)
{
	if (list == null)
		return true;

	if (list is ICollection<T>) 
        	return ((ICollection<T>)list).Count == 0;
	else
		return !list.Any();
}
Advertisements

About Andras Nemes
I'm a .NET/Java developer living and working in Stockholm, Sweden.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

ultimatemindsettoday

A great WordPress.com site

Elliot Balynn's Blog

A directory of wonderful thoughts

Softwarearchitektur in der Praxis

Wissenswertes zu Webentwicklung, Domain-Driven Design und Microservices

Technology Talks

on Microsoft technologies, Web, Android and others

Software Engineering

Web development

Disparate Opinions

Various tidbits

chsakell's Blog

WEB APPLICATION DEVELOPMENT BEST PRACTICES WITH MICROSOFT STACK & ANGULAR

Cyber Matters

Bite-size insight on Cyber Security for the not too technical.

Guru N Guns's

OneSolution To dOTnET.

Johnny Zraiby

Measuring programming progress by lines of code is like measuring aircraft building progress by weight.

%d bloggers like this: