4

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?

Gilles 'SO- stop being evil'
  • 104,111
  • 38
  • 209
  • 254
Hoecuspocus
  • 192
  • 3
  • 11
  • 1
    Have you considered passing around a category ID instead? – Kyeotic Dec 13 '11 at 00:26
  • You definitelly need to do a trim to the entered value. And not just check for spaces... some characters don't get displayed in a URL ("Ñ" or other non english chars)... remember there are other languages out there. So, to avoid all this... I suggest you to go for the category ID. – Romias Dec 13 '11 at 13:09

2 Answers2

0

As Tyrsius suggested it will be easier to pass the category ID around but if you want to pass the name then I would suggest using Url.Encode() every time you create a link or create a custom UrlHelper to do it for you. Once it hits your controllers action just do a Url.Decode() to get the original string back.

A more cleaner way would be to create your own route handler (implement IRouteHandler) to do it for you.

eth0
  • 4,977
  • 3
  • 34
  • 48
  • @Tyrsius Just wondering, like on stack overflow on this page - the name part of the URL seems to automatically be replaced / encoded: mvc-3-slashes-and-spaces-in-route how come this doesn't happen for mine - i.e: Projects-/-Lab or something similar? – Hoecuspocus Dec 13 '11 at 13:26
-1

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:

adding a category that I know will fail validation

I get this response automatically:

Failed response with validation

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.

Hoecuspocus
  • 192
  • 3
  • 11