9

I'm trying to build an Attribute that validates a certain instance of a type.

In order to do this I have to cast the ObjectInstance to that type.

And I need to set the attribute on the member of that type.

So we need to resort to the and keyword for the circular definition.

However in the following case I get the error that

A custom attribute must invoke an object constructor

On the line marked below.

namespace Test

open System
open System.ComponentModel.DataAnnotations

[<AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)>]
type MyAttribute() =
    class
    inherit ValidationAttribute ()

    override this.IsValid (value: Object, validationContext: ValidationContext) =
        match validationContext.ObjectInstance with
        | :? MyClass as item ->
            // TODO more validation
            ValidationResult.Success
        | _ ->
            new ValidationResult("No no no")
    end
and MyClass(someValue) =
    [<Required>]
    [<Range(1, 7)>]
  //vvvvvvvvvvvvvvv
    [<MyAttribute>]
  //^^^^^^^^^^^^^^^
    member this.SomeValue : int = someValue

I tried manually invoking the constructor, such as:

[<MyAttribute()>]
// or
[<new MyAttribute()>]

But none of them are accepted by the system.

Can an F# guru help me out here?

Rob
  • 26,989
  • 16
  • 82
  • 98
Anemoia
  • 7,928
  • 7
  • 46
  • 71

3 Answers3

7

Interesting one. It seems that the type inference is really not getting that right. The correct syntax to use here is [<MyAttribute()>], but despite you using the and keyword, the MyAttribute class is not yet known.

Here is a workaround: First check that the object to validate is really of the right type, then use reflection to invoke a validation method:

[<AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)>]
type MyAttribute() =
    inherit ValidationAttribute ()

    override this.IsValid (value: Object, validationContext: ValidationContext) =
        let t = validationContext.ObjectInstance.GetType()
        if t.FullName = "Test.MyClass" then
            let p = t.GetMethod("IsValid")
            if p.Invoke(validationContext.ObjectInstance, [| |]) |> unbox<bool> then
                ValidationResult.Success
            else
                ValidationResult("failed")
        else
            new ValidationResult("No no no")

type MyClass(someValue: int) =
    [<Required>]
    [<Range(1, 7)>]
    [<MyAttribute()>]
    member this.SomeValue = someValue

    member this.IsValid() = someValue <= 7

Edit: to make that slightly cleaner, you could add an interface, that you use in your validation attribute, and later implement in your class.

type IIsValid =
    abstract member IsValid: unit -> bool

Your IsValid method then becomes

    override this.IsValid (value: Object, validationContext: ValidationContext) =

        match validationContext.ObjectInstance with
        | :? IIsValid as i -> 
            if i.IsValid() then
                ValidationResult.Success
            else
                ValidationResult("failed")
        | _ ->
            ValidationResult("No no no")

in your class, this looks like:

type MyClass(someValue: int) =
    [<Required>]
    [<Range(1, 7)>]
    [<MyAttribute()>]
    member this.SomeValue = someValue

    interface IIsValid with
        member this.IsValid() = someValue <= 7
Anton Schwaighofer
  • 3,119
  • 11
  • 24
  • That is a possible solution, but it is quite (understatement) dirty. Thank you! – Anemoia Apr 13 '16 at 13:40
  • Not claiming this is the cleanest solution :-) Let me add another one. – Anton Schwaighofer Apr 13 '16 at 14:00
  • I like your new workaround better. I leave it open a little longer, maybe somebody comes with a real solution. If not, the points are yours. I also filed a bug on the FSharp project on GitHub: https://github.com/fsharp/fsharp/issues/565 – Anemoia Apr 13 '16 at 14:18
  • I like this idea - you'll only have a single attribute type + a generally useful interface to implement, instead of creating loads of single-use attribute types. ComponentModel is a mess. – scrwtp Apr 13 '16 at 20:22
  • Don Syme just confirmed that this is in fact a compiler bug, see @Snake's report on [GitHub](https://github.com/fsharp/fsharp/issues/565) – Anton Schwaighofer Apr 14 '16 at 08:39
3

One thing that you can do to get rid of mutual recursion is to break up MyClass definition into two and use type augmentation to add the members you want to mark with the attribute.

type MyClass(someValue: int) =
    member internal this.InternalSomeValue = someValue

type MyAttribute() = 
    inherit ValidationAttribute()
    (* you can refer to MyClass here *)

type MyClass with
    [<MyAttribute()>]
    member this.SomeValue = this.InternalSomeValue

That's closer to what you're asking for, but I like the interface idea better.

scrwtp
  • 13,437
  • 2
  • 26
  • 30
3

One solution would be to first describe your types in a signature files.

Since the attribute is specified in the signature file, you don't need to add it again in the implementation file:

Foo.fsi:

namespace Foo

open System

[<AttributeUsage(AttributeTargets.Property)>]
type MyAttribute =
    inherit System.Attribute

    new : unit -> MyAttribute

    member Foo : unit -> MyClass

and MyClass =
    new : someValue : int -> MyClass

    [<MyAttribute()>]
    member SomeValue : int

Foo.fs:

namespace Foo

open System

[<AttributeUsage(AttributeTargets.Property)>]
type MyAttribute() =
    inherit Attribute()

    member this.Foo () =
        new MyClass(1)

and MyClass(someValue) =
    // [<MyAttribute()>] -> specified in the fsi, still appears in compiled code
    member this.SomeValue : int = someValue

See https://msdn.microsoft.com/en-us/library/dd233196.aspx for reference