views:

274

answers:

1

I have my application designed with Repository pattern implemented and my code prepared for optional dependency injection in future, if we need to support another datastore.

I want to create a custom validation attribute for my content objects. This attribute should perform some kind of datastore lookup. For instance, I need my content to have unique slugs. To check if a Slug already exist, I want to use custom DataAnnotation attribute in my Base content object (instead of manually checking if a slug exists each time in my controller's Insert actions). Attribute logic would do the validation.

So far I have come up with this:

public class UniqueSlugAttribute : ValidationAttribute
{
    private readonly IContentRepository _repository;

    public UniqueSlugAttribute(ContentType contentType)
    {
        _repository = new XmlContentRepository(contentType);
    }

    public override bool IsValid(object value)
    {
        if (string.IsNullOrWhiteSpace(value.ToString()))
        {
            return false;
        }

        string slug = value.ToString();
        if(_repository.IsUniqueSlug(slug))
            return true;

        return false;
    }
}

part of my Base content class:

...
        [DataMember]
        public ContentType ContentType1 { get; set; }

        [DataMember]
        [Required(ErrorMessageResourceType = typeof (Localize), ErrorMessageResourceName = "Validation_SlugIsBlank")]
        [UniqueSlug(ContentType1)]
        public string Slug
        {
            get { return _slug; }
            set
            {
                if (!string.IsNullOrEmpty(value))
                    _slug = Utility.RemoveIllegalCharacters(value);
            }
        }
...

There's an error in line

    [UniqueSlug(ContentType1)]

saying: "An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type."

Let me explain that I need to provide the ContentType1 parameter to the Constructor of UniqueSlug class because I use it in my data provider.

It is actually the same error that appears if you try do to this on the built-in Required attribute:

[Required(ErrorMessageResourceType = typeof (Localize), ErrorMessageResourceName = Resources.Localize.SlugRequired]

It does not allow us to set it to dynamic content. In the first case ContentType1 gets known at runtime, in the second case the Resources.Localize.SlugRequired also gets known at runtime (because the Culture settings are assigned at runtime).

This is really annoying and makes so many things and implementation scenarios impossible.

So, my first question is, how to get rid of this error? The second question I have, is whether you think that I should redesign my validation code in any way?

A: 

The only way to get rid of the error is to do what it says, and put static content in your attributes. Remember that the purpose of attributes is for metadata on your code that is specifically for the purposes of looking up information about your code at runtime. Making this dynamic would defeat this purpose.

It seems to me that if your slug is dynamic based on the content type, then the Slug property should be an object that is initialized with the content type. From the attribute code posted, there's no need to initialize your repository in the constructor anyway - so move it to the IsValid() method and do everything in there, and just do a bit of checking to make sure the the value casts to a Slug and that the ContentType property is set.

If you need to be doing a ton of dynamic implementation scenarios around attributes, this could be indicative of a design problem.

womp
I don't use ContentType in Slug generation. I just use ContentType to differentiate between content object types (Page, Section, TabGroup, Tab) and they have common base class BaseContentObject. The ContentType property is also used in my data provider to save content to appropriate subfolder in App_Data, like this ~/App_data/Page/page-slug.xml or ~/App_Data/TabGroup/tabgroup-slug.xml. Slugs are unique in their respective content folders but there can be duplicated accross folders. For instance, someslug.xml can exist in Page folder and in TabGroup folder. My dataprovider takes care of this.
mare