I have the following route defined in my Global.asax (MVC 3 web project):
routes.MapRoute(
"BlogCategory", // Route name
"Blog/Category/{*category}", // URL with parameters
new { controller = "Blog", action = "Index", category = "" } // Parameter defaults
);
And my action accepts a category parameter, looking like this:
public ViewResult Index(string category, int page = 1)
{
PostListViewModel viewModel;
if (string.IsNullOrEmpty(category))
{
....show all cats else show only the one passed in
This works fine and will pass the category through to the controller, filtering my results appropriately.
My problem is when one of the categories I created looks like this for it's category name:
Projects / Lab
(note the spaces and slash)
This creates a URL similar like this:
/Blog/Category/Projects%20/%20Lab
And when I follow the link, I get this error:
Description: HTTP 404. The resource you are looking for (or one of its dependencies) could have been removed, had its name changed, or is temporarily unavailable. Please review the following URL and make sure that it is spelled correctly.
Requested URL: /Blog/Category/Projects / Lab
It never reaches the index action when debugging.
My question is, how can I make this work, or must I do some input validation when creating category names to prevent this from occurring?
First off, thanks to Tyrsius, Romias and eth0 for their suggestions.
I decided that I didn't want to use the Id for a category and I didn't want to create a route handler as this isn't really solving the root issue.
Instead I create a validation attribute called "UsedAsUrl", and applied this to my Category.Name in my domain model. This has the benefits of inbuilt validation (good for the end user) and good re-usability from a developer perspective.
So my category model now looks like this (note the [UsedAsUrl] attribute):
public class Category
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
[UsedAsUrl]
[MaxLength(50, ErrorMessage = "One word or groups of words please")]
public string Name { get; set; }
public virtual List<Post> Posts { get; set; }
}
And the attribute I created looks like this:
using System;
using System.ComponentModel.DataAnnotations;
using System.Text.RegularExpressions;
namespace CommonWebsiteUtilities.Attributes
{
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class UsedAsUrlAttribute : ValidationAttribute
{
public UsedAsUrlAttribute() : base("Must not contain a slash with spaces either side") {}
public override bool IsValid(object value)
{
var input = value.ToString();
var result = true;
if (input.Contains("/"))
{
input = Regex.Replace(input, @"\s+", " ");
if (input.Contains(" /")) result = false;
if (input.Contains("/ ")) result = false;
if (input.Contains(" / ")) result = false;
}
return result;
}
}
}
Now when I go to add a category:
I get this response automatically:
JS doesn't yet work, but my controller can pick up on the model state and this is the response from it, so the attribute is working correctly.
This basically checks for a slash with spaces on either / any side. Please note that this is not an exhaustive URL validator, but it will do for my current project. If anyone has any improvements, please let me know and I'll amend this answer.
I'm not good with RegExp, so didn't derive from RegularExpressionAttribute, and also I didn't want to have to supply the error message from inside my model. This Attribute should really be built upon as more rules appear when using categories as URL parts.