aspnet / Mvc

[Archived] ASP.NET Core MVC is a model view controller framework for building dynamic web sites with clean separation of concerns, including the merged MVC, Web API, and Web Pages w/ Razor. Project moved to https://github.com/aspnet/AspNetCore
Apache License 2.0
5.61k stars 2.14k forks source link

Support expressions instead of magic strings where possible #2646

Closed ivaylokenov closed 6 years ago

ivaylokenov commented 9 years ago

Do you plan to support expressions instead of magic strings where possible?

For example:

Where values in the methods are resolved to their corresponding route values?

danroth27 commented 9 years ago

We don't plan to do this for the initial release of MVC 6, simply because of time and resource constraints. But it's certainly something we will consider in the future. Adding this to our backlog.

akamud commented 9 years ago

This would be a great addition. Magic strings are too unreliable.

ivaylokenov commented 8 years ago

@danroth27 @Eilon I want to contribute to MVC 6 by implementing this feature. It should be fairly easy task for me since last year I made a NuGet package for MVC 5, containing some extension methods adding the functionality: https://github.com/ivaylokenov/ASP.NET-MVC-Lambda-Expression-Helpers If you want to see how I write/design code, you can take a look at this repository: https://github.com/ivaylokenov/MyTested.WebApi. Of course, I will follow your conventions.

I have already read the guidelines and signed the needed document.

I can add Controller.Redirect, Url.Action, ModelState.AddModelError and all applicable HtmlHelper methods. If it is OK with you, I have couple of questions:

ivaylokenov commented 8 years ago

After running several performance tests, I came to the consclusion that this feature will bring little to no value in MVC 6 at all. Comparing lambda expression parameter resolving to PropertyHelper.ObjectToDictionary gave the following results:

The developer value these methods gave in MVC 5 was not relying on magic strings, when building URLs and redirects, but with the C# 6 nameof compile-time feature, it is a lot better to use RedirectToAction(nameof(Controller.Action), nameof(Controller), new { id }), instead of RedirectToAction<Controller>(c => c.Action(id)) (although the code looks prettier with lambda expressions in my opition).

If you have another opinion or suggestion, let me know. I will take a look at the other issues to find something I can help with. :)

Eilon commented 8 years ago

@ivaylokenov we're not currently looking at adding this feature to MVC 6, but if you have a prototype you'd like to share, we'd be happy to have a look at it. No pressure :smile:

ivaylokenov commented 8 years ago

@Eilon Well, since MVC is one of the finest technologies out there, I've always thought your team as big bad wolfs (some of the profile pictures confirm it). :wolf: Anyway, I will write again when there is something interesting to share. :+1:

Eilon commented 8 years ago

Naah we're mostly kittens and puppies if you look at our profile pics :smile:

rynowak commented 8 years ago

I've always thought your team as big bad wolfs (some of the profile pictures confirm it

I hope you don't mean me!

I wouldn't let performance dissuade you from building something like this. If you have an example of a site where link generation is a significant contributor to overall performance, I'd be interested to see how we can improve that.

A few thoughts on the topic that I'm not sure I've written up before...

The high performance option (that still uses the routing system) will always be Attribute Routing + generating links with Named Routes. Url.Route("CreateUser"). Named routes are 'direct' and we just do a simple dictionary lookup to find the right route.

We're going to do more work to optimize RouteValueDictionary for cases like Url.Action("Buy", "Products", new { id = 5 }). Today the reflection code used to map id into a dictionary is cached, going forward we're going to elide the dictionary allocation and just create a slim wrapper object that implements IDictionary.

Your example RedirectToAction(nameof(Controller.Action), nameof(Controller), new { id }) doesn't really work in a lot of cases. For instance, the nameof() doesn't know how to infer the name of the controller from the name of the case. Likewise the developer could use [ActionName(...)] to override an action name.

The issue that you quickly run into with an expression-based system is that expressions are created anew every time the page runs, which is very expensive. This is how the compiler works, and we have implemented some caching for trivial cases, but that likely will not include anything that would be used in link generation.


Now all of this is benchmark-level perf info. For most users an expression based might still be useful even if the perf is strictly worse.

ivaylokenov commented 8 years ago

@rynowak Thanks for the answer, I got your point. :+1: To continue your thought, I have never actually used the string-based link generation - always written expression based extensions (knowing they are slower) and never had performance issues. I just prefer writing high quality code and then optimize, if needed, but this is me. However, adding the feature to the core MVC framework may mislead some developers to use it without knowing fully what is going on behind the scenes and this made me think twice whether it is appropriate. Especially when the benchmark tests showed ~80 times slower execution time compared to PropertyHelper.ObjectToDictionary method. I have seen the expression caching in your code but it is intended for other usages. Frankly, I am not quite sure how the extracted argument values from such method call expresion c => c.Action(id) can be cached so that the second call to the same expression is not compiled again. Anyway, I am already implementing some of the features with performance in mind. Here are the methods with all applicable overloads:

These should respect ActionName, IRouteConstraint (like AreaAttribute), IActionModelConvention and IControllerModelConvention. Any other ideas? Maybe ViewComponents? Thanks!

rynowak commented 8 years ago

We're actually already taking a look at doing this for ViewComponents.

And you're right about c => c.Action(id) - in general that's not cacheable. We don't cache things like that in Razor.

ivaylokenov commented 8 years ago

@rynowak @Eilon I have implemented most of the core expression route values resolver and the IUrlHelper additional methods. They are located in this branch - LINK. Some minor things are still not added (like proper exception messages) but they will be soon enough. I would be happy, if the code gets reviewed, since all other helper methods (redirects, action links and forms) will depend on the ExpressionRouteHelper class, which should be fully functional and unit tested before I move on to them. Should I open a new Work In Progress pull request, where you can take a look and put comments and suggestions when you have the time to do so?

Additionally, I have two questions:

Thank you for your time! :hand:

Eilon commented 8 years ago

@ivaylokenov I would recommend trying to build this as a separate project so that it could potentially be published as an add-on to MVC, as opposed to making the changes in MVC itself. In theory you should be able to write everything as extension methods to IUrlHelper.

ivaylokenov commented 8 years ago

@Eilon Sorry, I could not understand you. You mean to extract the code from the fork, create a separate package and upload it as my own add-on in NuGet? Thank you for the reply. :)

Eilon commented 8 years ago

@ivaylokenov yes exactly, that's where I would start out.

ivaylokenov commented 8 years ago

Sure, will do! Thanks! :+1:

ivaylokenov commented 8 years ago

@Eilon @rynowak Implemented, tested and documented :smile: However, I have doubts developers will find this package and its features easily without heavy advertising on the Internet. Anyway, thank you guys for the marvelous open source code of MVC 6. I learnt a few tricks, while reading it and playing with it. :+1:

Eilon commented 8 years ago

@ivaylokenov very nice! We'd be happy to link to your project from the MVC repo's readme. Do you have a 1-line description we could use in a link to your repo?

ivaylokenov commented 8 years ago

@Eilon Thank you very much! I would be very grateful, if you provide a link to the repo! As for a 1-line description, I believe something like this: "Collection of extension methods providing strongly typed routing and link generation for ASP.NET MVC 6 projects."

Eilon commented 8 years ago

@ivaylokenov I added a link: https://github.com/aspnet/Mvc/blob/dev/README.md

Thanks!

ivaylokenov commented 8 years ago

No, @Eilon, thank you! You guys are truly awesome! :+1:

GraemeSMiller commented 8 years ago

Came to the issues looking to see if this feature existed in backlog. Great work @ivaylokenov. Death to magic strings - they have no place in the core framework.

ivaylokenov commented 8 years ago

@GraemeSMiller Thank you!

achmstein commented 8 years ago

Good job @ivaylokenov

danroth27 commented 8 years ago

See also http://www.strathweb.com/2016/06/introducing-strathweb-typedrouting-for-asp-net-mvc-core/ for an alternative implementation

ghost commented 7 years ago

I had a similar thing come up for me, where I'd like to use the new nameof() operator. So, I just created #5535.

Mardoxx commented 6 years ago

Any developments on this?

In the mean time, I am wondering how would you go about testing that:

return RedirectToAction("BarAction", "FooController");

is going to redirect to the controller and method?

Having a quick look at how RedirectToActionResult is handled, a URL is created regardless of if this controller/action pair is valid. Is there a service available that I can use to resolve a controller/action name to the method which will get invoked? I'd imagine this is the responsibility of the router, but I'm really having difficulty figuring this out.

pranavkm commented 6 years ago

@Mardoxx the recommendation here is to use @ivaylokenov's library - https://github.com/ivaylokenov/AspNet.Mvc.TypedRouting. Could you start a different thread for your question? I'd recommend starting Stackoverflow first, you generally get better traction there from the community.