17

I've been using T4MVC (FYI: v2.6.62) for quite some time, and I've been slowly moving over our code to this way of working (less reliance on magic strings).

But I've had to stop because, for some reason, T4MVC is unable to translate objects into urls, and only seems to be able to work on primitive types (int/string/etc).

Here is an example:

Route breakdown:

/MyController/MyAction/{Number}/{SomeText}

Class:

namespace MyNamespace
{
  public class MyClass
  {
    public int Number { get; set; }
    public string SomeText { get; set; }
  }
}

Controller:

public class MyController
{
  public virtual ActionResult MyAction(MyClass myClass)
  {
    return View();
  }
}

View:

<%= Html.Action(
  T4MVC.MyController.Actions.MyAction(
    new MyClass()
    {
      Number = 1,
      SomeText = "ABC"
    }
 ) %>

The end result is this:

/MyController/MyAction?myClass=MyNamespace.MyClass

and not

/MyController/MyAction/1/ABC

Does anyone else have this problem? Are T4MVC urls like this available?

Question also asked at the ASP.NET Forum.

Dan Atkinson
  • 11,391
  • 14
  • 81
  • 114

2 Answers2

17

Update (10/11/2012): the recently added support for Model Unbinders (see section 3.1 in the doc) should hopefully cover a lot of these cases.

Original answer:

Copying my reply from the forum thread:

Hmmm, I don't think this has come up yet. Maybe in most cases that people have Action methods that take an object, the object's values come from posted form data, rather than being passed on the URL? In such scenario, the question doesn't arise.

I think in theory T4MVC could be changed to support this. It would just need to promote all the object's top level properties as route values rather than try to use the object itself (obviously, the current behavior is bogus, and is a result of just calling ToString() blindly).

Have others run into this and think it's worth addressing?

David Ebbo
  • 42,443
  • 8
  • 103
  • 117
  • 1
    Hi David. Thanks for the reply. I'm guessing that, given the 5 upvotes in the brief period it's been up means that this is probably desirable functionality. :-) – Dan Atkinson Mar 04 '10 at 20:39
  • Ok, I'll put it on the TODO list! :) – David Ebbo Mar 04 '10 at 21:15
  • Thank you! I would find it especially useful as I've created a few ActionResults, such as PermanentRedirectResult which utilise the T4MVC style actions, and this sort of functionality would be great, not just for using in views! – Dan Atkinson Mar 05 '10 at 09:00
  • Hi David. Is there any update on this? I would really love to see this in other projects that I use T4MVC with, but because they use complex types, I need to write it manually. – Dan Atkinson Dec 02 '10 at 11:48
  • Sorry, no update on this one. But note that the design is not straightforward here. It seems arbitrary to just take the object's properties and promote them to route values. What if there were nested properties as well? The same issue would re-occur at that level. So it comes down to trying to serialize arbitrary object graphs as route values, and I'm not sure if there is a generally accepted 'correct' way of doing this. – David Ebbo Dec 06 '10 at 02:22
  • Isn't it just a matter of merging the objects and letting MVC worry about arbitrary serializations? I would think `MVC.Controller.Action(new { Number = 1, SomeText = "ABC", SomeObject = new Finklestein() })` would exactly translate into `new { controller = "Controller", action = "Action", Number = 1, SomeText = "ABC", SomeObject = new Finklestein() })` ... or maybe the `RouteValueDictionary` equivalent since it makes it so easy to merge properties like that. Or am I missing something obvious? – Anthony Mills Mar 23 '11 at 11:54
  • I've found that I'm willing to go to the trouble to create an action method overload which takes all primitives and creates what would otherwise be a model-bound object to pass to the *real* action method, in the interest of keeping magic strings at bay. One can imagine cases in which this might get out of hand, but it should suffice for 90% of cases. (or, 100% of the real world cases I have encountered so far) –  Oct 11 '12 at 15:44
  • I just added an update to my answer to point to the new Model Unbinder feature, which should cover this. – David Ebbo Oct 11 '12 at 18:19
  • @DavidEbbo Why would it be necessary to write explicit unbinders? I don't have to write explicit binder's either, so why isn't this merely a job of per-member handling in the same spirit the default binder handles unknown non-collection types per member? – John Nov 04 '14 at 11:51
4

If I've understood the problem correctly then the following syntax should allow you to work around the problem.

<%= Html.ActionLink("test", MVC.MyController.MyAction().AddRouteValues(new MyClass() { Number = 5, SomeText = "Hello" })) %>

I think the answer to make the syntax nicer would be to wrap each non value type parameter in a RouteValueDictionary in each generated action result method

Edit: (Response to comment as not enough chars)

Ah ok I managed to recreate the simple example above using this method to give: /MyController/MyAction/5/Hello as the url. I'm not quite sure how nested complex types would pan out in practice. You could use some recursion to dive down the into the top-level object and reflect over the values to add them but then you open up a new set of issues, such as how to cope with a child property name that is identical to the parent property name. This seems like it could be a complex problem to solve, in a manner that would work for everyone. Perhaps some kind of adapter pattern would be most useful to transform a complex object into route values. In the simplest case this might be to declare an extension method ToRouteDictionary that acts on your complex type and transforms it using your knowledge of how it should work. Just thinking out loud as I'm obviously not aware of your use cases

Dan Atkinson
  • 11,391
  • 14
  • 81
  • 114
PabloBlamirez
  • 2,692
  • 3
  • 18
  • 14
  • I'm afraid this doesn't work. It returns the route values, but they url isn't formed correctly. Also, if it is fixed using this method of wrapping non value type, you'll need to do so recursively, because there you could be using a complex type that contains other complex types... – Dan Atkinson Mar 09 '10 at 10:10