A solution to missing XML files and Swagger

You use Swagger, and include XML comment files, which are copied during build from a few different projects to the root of your API application. Then one day, you realize that the build didn’t copy one of the XML files, so this line threw an exception:

options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, "MyProject.Domain.xml"));

Yes, you can fix the build to resolve the error, but if you treat XML comments as content (not code), you can do this:

foreach (var file in Directory.GetFiles(AppContext.BaseDirectory, "*.xml"))
{
    options.IncludeXmlComments(file);
}

It appears that Swagger doesn’t break when a non-XML comment file are included, which is beneficial. With this method, only XML files that exist are included, you don’t risk breaking your app when they are missing.

Extending DbSet for easy filter expressions

Databases often have entities which are often by a repeated expression. A common one is filtering something by a UserId. In SQL, this looks like:

select * 
from orders
where userid = @userid;

Using Entity Framework, we may write something like this:

dataContext.Orders.Where(x => x.UserId == userId);

But I’d really like to make it more expressive and consistent, like this:

dataContext.Orders.ForUser(userId);

Fortunately, it is possible, with an interface and an extension method.

The interface, which I will call IUserEntity, will expose the common filtered expressions.

public interface IUserEntity
{
	int UserId { get; set; }
	User User { get; set; }
}

Any class that can be filtered by users should inherit this class.

public class Order : IUserEntity
{
	public int Id { get; set; }
	public int UserId { get; set; }
	public User User { get; set; } = null!;
}

Then our expression method will extend any DbSet with a type of IUserEntity to include our extension method, which simply returns a filtered DbSet.

public static class DbSetExtensions
{
	public static IQueryable<T> ForUser<T>(this DbSet<T> userEntities, int? userId) where T : class, IUserEntity
	{
		if (userId.HasValue)
		{
			return userEntities.Where(x => x.UserId == userId.Value);
		}
		return userEntities;
	}
}

Note how in the above I made the userId nullable — this is not required, but it does give you a bit more flexibility.

This can be replicated for any other common filter, which will make your code more expressive and easy to read. If you wanted similar extensions on other elements, such as Lists, you could just make a copy of the extension method for that object type, and the appropriate return types.

Damn the documentation: FtpWebRequest.Timeout default value is NOT Infinite

For the past few weeks, an FTP upload has been failing, rather religiously, with the following .Net error:

System.Net.WebException: The underlying connection was closed: An unexpected error occurred on a receive.

For the FTP upload, I was using the .Net FtpWebRequest class, in a rather simple code snippet:

FtpWebRequest request = (FtpWebRequest)WebRequest.Create(config.Uri);
request.UsePassive = config.UsePassive;
request.Method = WebRequestMethods.Ftp.UploadFile;
request.Credentials = new NetworkCredential(config.UserName, config.Password);

The FTP connection was dropping, but why? The transfer file was rather large (over 50MB), but reading the MSDN documentation for the FtpWebRequest.Timeout property, it was pretty clear that the .Net Framework wasn’t timing out, because:

FtpWebRequest.Timeout Property

Type: System.Int32
An Int32 value that contains the number of milliseconds to wait before a request times out. The default value is Infinite.

I tried active and passive connections; both failed. I checked the firewall; nothing unusual. I tried other external servers; they all failed. What gives?

Finally, exasperated, I tried forcing the timeout:

FtpWebRequest request = (FtpWebRequest)WebRequest.Create(config.Uri);
request.UsePassive = config.UsePassive;
request.Method = WebRequestMethods.Ftp.UploadFile;
request.Credentials = new NetworkCredential(config.UserName, config.Password);
request.Timeout = -1;

Viola! The FTP upload did not fail! Stepping through the code, I found that, contrary to the documentation, the default value for FtpWebReqeust.Timeout is not infinite:

That is right, my friends: contrary to what you read in the documentation, the default value is 100,000 milliseconds, or about 1 minute, 40 seconds.

Lessons learned:

  1. Don’t believe everything you read.
  2. Trust, but verify.
  3. Infinite is not infinite when it is not infinite.