Skip to content
This repository has been archived by the owner on Dec 14, 2018. It is now read-only.

Complex model binding with RedirectToAction #5093

Closed
damienleroy opened this issue Jul 29, 2016 · 7 comments
Closed

Complex model binding with RedirectToAction #5093

damienleroy opened this issue Jul 29, 2016 · 7 comments

Comments

@damienleroy
Copy link

I recently update the framework version from RC1 to last version of Asp.Net core, and I have an issue with "RedirectToAction" method, with complex object to transfer.

RedirectToAction redirect my ViewModel to the action with GET parameters. This example :

    public class FirstViewModel
    {
        public SecondViewModel Second { get; set; }
    }
    public class SecondViewModel : Dictionary<int, string>
    {
        public override string ToString()
        {
            return String.Join("#", this.Select(d => d.Key + "|" + d.Value));
        }
    }

Into the controller :

    public IActionResult DoAction(FirstViewModel model)
    {
        return RedirectToAction("Index", "Second", model);
    }

With Asp Net RC1, this working perfectly. The redirect is done with the good parameter :
"myUrl?Second=1|one#2=two"
And I can get this value with a custom model binding.

But with last version of Asp .Net core, the redirect passing a wrong value parameter. I can see this :
"myUrl?Second=[1,one]&Second=[2,two]"
So the model binding don't work.
If possible, I want to keep my custom parameter defined by the override ToString() like RC1 version.

@Eilon
Copy link
Member

Eilon commented Jul 29, 2016

I think the problem here is a feature that was added to better support lists of data in URL generation. Because Dictionary<int, string> implements IEnumerable, it is treated as a list, so you get all those commas showing up.

I think you can explicitly convert the list of data to a string and then get the behavior you want:

    public IActionResult DoAction(FirstViewModel model)
    {
        return RedirectToAction("Index", "Second", model.ToString()); // Call ToString explicitly
    }

@damienleroy
Copy link
Author

Doesn't work for me. Call ToString explicitly like this call the ToString from "FirstViewModel", no "SecondViewModel".
The result of your example shown : "myUrl?Length=55"

@Bartmax
Copy link

Bartmax commented Jul 30, 2016

I guess @Eilon meant model.Second.ToString();

Still in your example, you are passing model but it looks it should be model.Second

try change this:

return RedirectToAction("Index", "Second", model);

to this:

return RedirectToAction("Index", "Second", model.Second);

@damienleroy
Copy link
Author

damienleroy commented Jul 30, 2016

It works if "Second" is the lonely property of "FirstViewModel". And sometimes this is not the case.
"FirstViewModel" could be have several properties. Sorry for the explanation, my bad.

The goal, have a way for create custom query value, from some complex objects, like RC1 worked.

@Eilon
Copy link
Member

Eilon commented Aug 1, 2016

@LeroyD ah, indeed, you are correct. I should have tried it before posting 😄

The reason this is happening is what I described before: starting in ASP.NET Core 1.0 RC2, the behavior of lists (IEnumerable) was updated to be comma-separated values. Because all dictionaries are lists, their behavior is changed.

To revert to the previous behavior, you can wrap the model in a model that has the behavior that you want for routing, e.g. add these wrapper classes:

    public class FirstViewModelRouteData
    {
        public FirstViewModelRouteData(FirstViewModel first)
        {
            Second = new SecondViewModelRouteData(first.Second);
        }

        public SecondViewModelRouteData Second { get; set; }
    }

    public class SecondViewModelRouteData
    {
        public SecondViewModelRouteData(SecondViewModel second)
        {
            SecondModel = second;
        }

        private SecondViewModel SecondModel { get; }

        public override string ToString()
        {
            return SecondModel.ToString();
        }
    }

And then call routing by wrapping the original model in the route model like this:

            return RedirectToAction("Index", "Second", new FirstViewModelRouteData(model));

And then I get the expected URL http://localhost:16896/Second?Second=1%7Cdog%232%7Ccat, which decodes to http://localhost:16896/Second?Second=1|dog#2|cat.

@Eilon Eilon closed this as completed Aug 1, 2016
@Eilon Eilon added the by design label Aug 1, 2016
@damienleroy
Copy link
Author

Great, it works with this solution.
Need to have a wrap model into an another model but I like that workaround.

Thanks. :)

@Eilon
Copy link
Member

Eilon commented Aug 1, 2016

@LeroyD great!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

3 participants