9

I've looked around on SO, I can't find a sufficient answer to my question.

I have a wrapper class called Title defined like this

public class Title
{
    private readonly string _title;

    public Title (string title) {
        _title = title;
    }

    public static implicit operator Title(string title)
    {
        return new Title(title);
    }
}

I am using this class in an ASP MVC project. Right now I defined a controller like this:

public ActionResult Add(string title)
{
    //stuff
}

and this works fine. However, I wish to automatically bind the posted string value to the Title constructor, thus accepting a Title instead of a string as a parameter:

public ActionResult Add(Title title)
{
    //stuff
}

This however, does not work, as I will get the error: The parameters dictionary contains a null entry for parameter, meaning the model binder can't bind the string to the Title parameter.

The HTML responsible for posting the title data:

<form method="post" action="/Page/Add" id="add-page-form">                
    <div class="form-group">
        <label for="page-title">Page title</label>
        <input type="text" name="title" id="page-title">
    </div>
</form>

My question exists of two parts:

1. Why is it not possible to do this, I would expect the bodel binder to use the defined implicit operator to create a Title instance.

2. Is there a way to still accomplish getting the desired behavior, without explicitly creating a modelbinder?

Glubus
  • 2,819
  • 1
  • 12
  • 26
  • can you show your view? you have to set the model of your view to Title type not string for this to work – Ehsan Sajjad Apr 30 '16 at 15:19
  • The partial view sending the post to this action does not use a model, it's just a form with a textfield as input, I will add the html responsible for posting regardless. – Glubus Apr 30 '16 at 15:21

3 Answers3

7

Though I am late, just for the sake of covering all available options: you could implement your own TypeConverter, as follows:

public class TitleConverter : TypeConverter
{
    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
    {
        return sourceType == typeof(string) ? true : base.CanConvertFrom(context, sourceType);
    }

    public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
    {
        if (value is string)
            return new Title((string)value);

        return base.ConvertFrom(context, culture, value);
    }
}

[TypeConverter(typeof(TitleConverter))]
public class Title
{
    ...
}

This approach is especially useful if you need to instantiate your class from different types

Herman Kan
  • 2,253
  • 1
  • 25
  • 32
  • So what does this accomplish in the practical sense? Does this allow me to accept a `Title` object where the binder is trying to bind a string to a parameter? It feels like this is just something you'd do before the implicit operator was implemented. I understand this is mostly for completeness, I'm just wondering if this allows anything else than the already provided answers. – Glubus Sep 08 '16 at 12:50
  • 2
    Yes, it binds `string` to `Title`. Think of it as a replacement for the model binder that uses a totally different type-based mechanism. While it allows you to focus on the input **type**, in some scenarios that require controller or binding context you would still have to resort to the binder. – Herman Kan Sep 08 '16 at 15:02
  • Nifty! I might try it out sometime, thanks for leaving your answer despite of how long ago I posted this. – Glubus Sep 09 '16 at 08:08
5

As per you questions:

  1. The model binder is going to call new Title(). Which he can't. Then he would try to set a Title property. Which he can't find. No, the default binder does not call implicit conversions. The algorythm he uses is different.
  2. No, you don't need a custom binder, if you accept to change your Model, which is completely wrong in according to the behaviour of the default model binder.

The implicit conversion doesn't matter at all for Action binding.

The default model binder takes a big dictionary of values, gathered from the various parts of the request, and tries to insert them into properties.

So, if you want to use your Title as an Action parameter, your best bet is to make the Title class Binder-friendly, so to speak:

/* We call the class TitleModel in order not to clash
 * with the Title property.
 */
public class TitleModel
{
    public string Title { get; set; }

    /* If you really need the conversion for something else...
    public static implicit operator Title(string title)
    {
        return new Title { Title = title };
    }
    */
}

Everything should work as it is on the client side.

If you cannot (or don't want to) change your model class, then you can go for a custom model binder. But I don't think you really need it.

Alberto Chiesa
  • 7,022
  • 2
  • 26
  • 53
  • I wish to hide the given string to the Title instance, so creating a public property is not something I want to do. I guess I will create a custom binder then. It just feels to me like there should be a way to do this. Oh well, thank you. – Glubus Apr 30 '16 at 15:40
  • Private is overrated. Why should be a problem, IN THE REAL WORLD, that the Title property is not readonly? It's a messenger object anyway. Some theoretical bulls%%t doesn't count, sorry ;) I'm just kidding! But I encourage you to try to use the framework as it is intended, and don't reinvent the wheel. – Alberto Chiesa Apr 30 '16 at 15:47
  • It's a discipline. A title is something that is immutable. A book has a title, and the title of this book does never change (usually, clearly this is an abstraction). Now as to why not just represent this title as a string: A string tells you nothing about the semantics of it's value. A title instance clearly represents a title of something. This allows you to not mix up properties like Tttle and Author, properties you'd normally both represent as a string. – Glubus May 03 '16 at 14:04
  • Yes, but your Title class, in your example, is a request object coming from the client. You are not supposed to use this exact class for database operations. I would prefer to call this class AddPageRequest (because this is what this class is really about: a request for the AddPage action) and have a ToTitle method into the Request object. MVC really does not want you to act that way. Just my 2 cents. – Alberto Chiesa May 03 '16 at 16:30
  • But... how can the member name be the same as the enclosing type? – Stefan Jun 17 '19 at 20:54
  • @Stefan good catch: it cannot. Probably the class should just be called something along the lines of `TitleModel` or something like that. – Alberto Chiesa Jun 17 '19 at 21:22
1

The compiler won't help you there. This is a model binding issue. You can create a custom ModelBinder

Community
  • 1
  • 1
Bruno Garcia
  • 6,029
  • 3
  • 25
  • 38
  • Eh forgive my poor choice of terms, clearly the model binding is something done runtime. However, my question is as to why this cannot work, I know I can just create a custom model binder, and I state in my question that i do not wish to do this if tehre is a way around it using an implicit operator. – Glubus Apr 30 '16 at 15:32