12

Okay, lets say I have a URL like so, which is mapped via HTTP verb GET to the controller action I have below:

GET /foo/bar?sort=asc&r=true

How can I bind this to my model Bar on my controller action I have below:

class Bar {
    string SortOrder { get; set; }
    bool Random { get; set; }
}

public ActionResult FooBar(Bar bar) {
    // Do something with bar
    return null;
}

Note that the property names won't and can't necessarily match the names of the URL parameters. Also, these are OPTIONAL url parameters.

tereško
  • 58,060
  • 25
  • 98
  • 150
DigitalZebra
  • 39,494
  • 39
  • 114
  • 146
  • 1
    I'd just add a class with the matching property names, but I'm interested to see if you can configure the model binder to bind to arbitrary property names. Maybe there is some attribute to use... – dotjoe Dec 14 '11 at 16:18
  • 1
    I can't get it, Why the properties can't match to the url parameters? – gdoron Dec 14 '11 at 16:29
  • 1
    @gdoron, they CAN, I would just strongly prefer that they DON'T match... I would prefer to have an attribute on the property to make it explicit as to which URL parameter is being bound to which property. – DigitalZebra Dec 14 '11 at 16:38
  • I updated the answer, you really should make them match if you can, and you can. – gdoron Dec 14 '11 at 16:54
  • 2
    @gdoron, Yes, I can make them match. HOWEVER, every other good web MVC framework has attributes/annotations which allow you to specify the parameter which should be bound to the property... this makes refactoring/maintenance more straightforward. I'm disappointed that ASP.NET MVC doesn't have this without having to create it yourself. – DigitalZebra Dec 14 '11 at 19:24
  • Hold your horses, I think there is a good reason why this functionality isn't built-in with the framework. The view must reflect the model. Please give ONE example how this cause maintenance problem. – gdoron Dec 14 '11 at 19:32
  • 2
    You have an external facing API/contract which implicitly relies on how an internal class is structured/named. Coding by convention is great and all, but the framework should give you the option to be explicit in your external contract. If Joe Nooby Developer comes in and renames/removes a property on the model, now my external contract has broken because of the implicit contract. – DigitalZebra Dec 14 '11 at 20:02
  • 1
    @Polaris878, so is if someone changes the attribute name, or the URL parameters names. It's coupled anyway! you just move the couple to the attribute... – gdoron Dec 15 '11 at 09:01

5 Answers5

13

It's not supported out of the box, but you could do this:

class BarQuery : Bar { 

   public string sort { get { return SortOrder; } set { SortOrder = value; } }
   public bool r { get { return Random; } set { Random = value; } }
}

public ActionResult FooBar(BarQuery bar) {
    // Do something with bar
}

You could implement a custom IModelBinder, but it's much easier to do manual mapping.


If you can change the Bar class you can use this attribute:
class FromQueryAttribute : CustomModelBinderAttribute, IModelBinder { 

   public string Name { get; set; }

   public FromQueryAttribute() { }

   public FromQueryAttribute(string name) { 
      this.Name = name;
   }

   public override IModelBinder GetModelBinder() { 
      return this;
   }

   public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) {
      return controllerContext.HttpContext.QueryString[this.Name ?? bindingContext.ModelName];
   }
}

class Bar {

    [FromQuery("sort")]
    string SortOrder { get; set; }

    [FromQuery("r")]
    bool Random { get; set; }
}

public ActionResult FooBar(Bar bar) {
    // Do something with bar
    return null;
}
Max Toro
  • 28,282
  • 11
  • 76
  • 114
  • I liked the Query attribute, but it's not the best practice (as you probably agree) – gdoron Dec 14 '11 at 17:15
  • @gdoron Don't know what you mean. – Max Toro Dec 14 '11 at 17:17
  • 2
    I mean, though it's a nice "hack" but MVC team really suggest to avoid writing custom Model Binder. "In general, we recommend folks don’t write custom model binders because they’re difficult to get right and they’re rarely needed" – gdoron Dec 14 '11 at 17:22
  • 7
    @gdoron You want to follow someone else's opinion instead of thinking fine, but don't call it a hack, because it's not. – Max Toro Dec 14 '11 at 17:26
  • 3
    It is not a hack. IMO it is stupid that ASP.NET MVC doesn't do this exact thing out of the box. – DigitalZebra Dec 14 '11 at 19:27
6

The model binder matches the parameters it gets from the view to the model you have in the action by the Names, so if they won't match the binding will not work.

options you got:

  1. Match the inputs names to the model properties names... but You said you can't do this, (for some unknown reason).
  2. Write custom Model Binder. *
  3. Use The Bind attribute with prefix - though it'll still force you to have input names close to the model properties names.

so basically, You can't do exactly what you want.


Update:

You wrote in a comment that the properties CAN match the parameters names, so instead of write custom attributes that maybe will succeeded to do the binding, just write a ViewModel (The VM fromMVC...) to adjust the url parameters names.

Writing custom model binder is not recommended by the MVC team:

In general, we recommend folks don’t write custom model binders because they’re difficult to get right and they’re rarely needed
from here

gdoron
  • 147,333
  • 58
  • 291
  • 367
3

I had the same problem a long time ago and used this stack solution by Andras Zoltan: Asp.Net MVC 2 - Bind a model's property to a different named value

I set the ModelBinder attribute on my class and the BindAlias on the property:

  [ModelBinder(typeof(DefaultModelBinderEx))]
  public class MyModel
  {
        [Required]
        [BindAlias("new")]
        public int? Amount { get; set; }

If you can't change or haven't access your model file to set the Attributes, you still can create a custom model binder, OR, make a specific object than you will map to your Model (AutoMapper is usefull)

Community
  • 1
  • 1
Loic El Thesea
  • 704
  • 7
  • 17
1

You could implement IModelBinder to map your incoming parameters to the object of your choosing provided the incoming parameter names are defined. Otherwise you would have to rely on parameter order and/or type to infer the proper binding which seems to be a very poor choice.

Shawn
  • 1,871
  • 2
  • 21
  • 36
1

There is no free MVC.Net Modelbinding feature that gives you what you want out of the box. When I need to do something like this, it really makes me think about my modeling which almost always makes me create a ViewModel for my view binding and a EntityModel for my repository storage.

I like to use AutoMapper to convert between these different types. Best part of using AutoMapper is that it drives you away from having to write the mapping logic yourself over and over for each Action in your controller. Just set it up once with AutoMapper in your initialization sections and just execute something like this in the future.

Mapper.Map<Bar>(barViewModel);