Introduction
You're setting up your ASP.NET MVC project and you realize that one of the parameters in your web.config file is a URL. Wouldn't it be cool if your application could validate that parameter as a URL directly? Yes, it would and here's how.
Background
When we wrote (and continue to write) Revalee—an open source project used to schedule callbacks for web applications—we needed to write a number of internal tools, or “widgets”, that would support the project. One of these was a UrlValidator
. By no means was this validator a core component of the project, but it's inclusion certainly did make our lives just a bit easier during development and deployment.
Disclaimer: Revalee is a free, open source project written by the development team that I am a part of. It is freely available on GitHub and is covered by the MIT License. If you are interested, download it and check it out.
UrlValidator
Let's see why we might want such a widget. Suppose, for example, that your web.config includes a custom configuration section, like:
<revalee>
<clientSettings serviceBaseUri="http://localhost:46200" authorizationKey="YOUR_SECRET_KEY" />
<recurringTasks callbackBaseUri="http://localhost:64646">
<task periodicity="daily" hour="00" minute="00" url="/RevaleeTest/DailyRecurring" />
<task periodicity="hourly" minute="30" url="/RevaleeTest/HourlyRecurring" />
</recurringTasks>
</revalee>
Now let's focus on the <clientSettings>
element, specifically the serviceBaseUri
attribute. That's it: no more, no less.
Why should we care about validating (or not) one measly little attribute, you ask? Well, consider the following: as an application installed outside of your control, you have no way of predicting who is going to enter what as a value for the serviceBaseUri
attribute. So getting this detail right now, means that it won't come back and bite you in... er, haunt you later.
(To be perfectly clear, the web.config snippet above utilizes the UrlValidator
four (4) different times: once for the serviceBaseUri
attribute of the <clientSettings>
element, once for the callbackBaseUri
attribute of the <recurringTasks>
element, and once each for url
attribute of the two defined <task>
elements. I only bring this up to highlight the importance of code reuse, but I digress...)
Web.config
Let's take a look at the serviceBaseUri
attribute in the <clientSettings>
element again:
serviceBaseUri="http://localhost:46200"
It's clearly meant to be a URL: it's got a scheme, a host, and even, in this example, a port number. We could import this as a string
, but why do something in two steps (that is, import it as a string
and then convert it to a Uri
elsewhere in the code) when you can accomplish the same thing in one step? We want our attribute to validate as a URL when the application loads, more specifically as a Uri
. If the attribute's value isn't a valid URL, then our application is misconfigured and can't run. Period.
ConfigurationElement
Since we started with the web.config file, let's work inwards from there so that we'll cover the UrlValidator
last. That means we need to define a custom ConfigurationElement
, called ClientSettingsConfigurationElement
in this example. This class
looks like:
using System;
using System.ComponentModel;
using System.Configuration;
namespace Revalee.Client.Configuration
{
internal class ClientSettingsConfigurationElement : ConfigurationElement
{
[ConfigurationProperty("serviceBaseUri", IsKey = false, IsRequired = false)]
[UrlValidator(AllowAbsolute = true, AllowRelative = false)]
public Uri ServiceBaseUri
{
get
{
return (Uri)this["serviceBaseUri"];
}
}
}
}
You'll notice two details. First, that the property
(that is, ServiceBaseUri
) is defined as a Uri
(and also, that this["serviceBaseUri"]
is cast as Uri
). And second, that ServiceBaseUri
is marked up with the following custom attribute (more on this in a moment):
[UrlValidator(AllowAbsolute = true, AllowRelative = false)]
So now we see how we could use such an custom attribute. How do we go about defining one? Well...
ConfigurationValidatorAttribute
The custom UrlValidator
attribute (shown above) includes two properties: AllowAbsolute
and AllowRelative
. We'll define these two bool
properties now when we create our custom ConfigurationValidatorAttribute
.
Property |
Type |
Definition |
Example |
AllowAbsolute |
bool |
Allows (or prohibits) the use of an absolute Uri |
http://localhost:46200/Absolute/Path |
AllowRelative |
bool |
Allows (or prohibits) the use of a relative Uri |
/Relative/Path |
Since properties of this custom attribute are both optional (notice that the constructor
's signature has no parameters: public UrlValidatorAttribute()
), we'll assign each property to have a default value of true
. The final code for this new, custom ConfigurationValidatorAttribute
looks like this:
using System;
using System.Configuration;
namespace Revalee.Client.Configuration
{
internal sealed class UrlValidatorAttribute : ConfigurationValidatorAttribute
{
private bool _AllowAbsolute = true;
private bool _AllowRelative = true;
public UrlValidatorAttribute()
{
}
public bool AllowAbsolute
{
get
{
return _AllowAbsolute;
}
set
{
_AllowAbsolute = value;
}
}
public bool AllowRelative
{
get
{
return _AllowRelative;
}
set
{
_AllowRelative = value;
}
}
public override ConfigurationValidatorBase ValidatorInstance
{
get
{
return new UrlValidator(_AllowAbsolute, _AllowRelative);
}
}
}
}
Above, we see that the ValidatorInstance
property of the ConfigurationValidatorAttribute
base class has been overridden and returns a new UrlValidator()
of type
: ConfigurationValidatorBase
. That private class
would be defined inline where the comment is located (above). Instead, we'll review that code separately now.
ConfigurationValidatorBase
There are other details defined the UrlValidator
, but the Validate()
method of the ConfigurationValidatorBase
class is where all of the custom validation work happens. Since that's the essence of this endeavor, let's focus on that.
As you may have already guessed, based on the groundwork laid earlier in this article, we are interested in supporting both absolute and relative URLs (or only one or the other). Additionally, in this specific implementation, we are only interested in an absolute Uri
that sports either the http
or the https
scheme; if a Uri
is relative, then we don't have to worry about its scheme. So without further ado, the code:
private class UrlValidator : ConfigurationValidatorBase
{
private bool _AllowAbsolute = true;
private bool _AllowRelative = true;
public UrlValidator(bool allowAbsolute, bool allowRelative)
{
_AllowAbsolute = allowAbsolute;
_AllowRelative = allowRelative;
}
public override bool CanValidate(Type type)
{
return type == typeof(Uri);
}
public override void Validate(object value)
{
if (value == null)
{
return;
}
if (value.GetType() != typeof(Uri))
{
throw new ArgumentException("The URL attribute is invalid.");
}
Uri url = value as Uri;
if (!_AllowAbsolute && url.IsAbsoluteUri)
{
throw new ArgumentException("The URL attribute cannot contain an absolute URL.");
}
if (!_AllowRelative && !url.IsAbsoluteUri)
{
throw new ArgumentException("The URL attribute cannot contain a relative URL.");
}
if (url.IsAbsoluteUri && url.Scheme != Uri.UriSchemeHttp && url.Scheme != Uri.UriSchemeHttps)
{
throw new ArgumentException(string.Format("The URL attribute only supports {0} and {1}.",
Uri.UriSchemeHttp,
Uri.UriSchemeHttps)
);
}
}
}
...And that's it. A handful of one-line if
statements, really the last three in the Validate()
method, are what makes this particular UrlValidator
tick. Perhaps yours will be more complex.
Conclusion
This article reviewed the specifics of how a UrlValidator
might be implemented. In Revalee, usage of this widget made setting up and configuring the project much, much easier. Hopefully, it can do the same for your project.
Further Reading
History
- [2014.May.22] Initial post.