How to use URL parameters with router in Vaadin Fusion? - vaadin

I tried to follow the guide at
Question, which results in a
mobx.esm.js?4fd9:2362 [mobx] Encountered an uncaught exception that was thrown by a reaction or observer component, in: 'Reaction[MainView.update()]' TypeError: Expected "item" to be a string
My configuration is
path: 'item/:item',
component: 'item-view',
Is there an example, how to solve this? Do I need to handle this in the MainView (I follow the todo-tutorial on vaadin.com?

The problem is that main-view.ts in the starter project has logic which tries to list all navigation routes to generate links for them. Currently that logic is not smart enough to detect and skip routes that have non-optional route parameters.
The error is thrown from router.urlForPath(viewRoute.path) when the path has non-optional route parameters because here we are not specifying what the value for the route parameter should be (for the generated URL). For generating a URL for the path 'item/:item' it would need to do something like router.urlForPath('item/:item', { item: 'my-item' }).
The quick fix (to remove the title from the route definition) suggested by Marcus works because the main-view.ts has logic to skip all routes that don't have a title. You could change that logic to also skip by some other criteria or you could try to include values for the route parameters there (for specific routes) if you really want to generate working navigation links for those routes.
Other alternative would be to mark the route parameter as optional (in case you want the route also to be accessible without a parameter) by adding a question mark after it, then the link can be generated without specifying a value for the parameter.
{
path: 'item/:item?', // <- optional route parameter
component: 'item-view',
title: 'Hello World',
}

It seems you have run into an issue with the starter. In main-view.ts, it loops over the routes with a title and tries to create a link for each in the menu:
${this.getMenuRoutes().map(
(viewRoute) => html`
<vaadin-tab>
${viewRoute.title}
</vaadin-tab>
`
)}
However, router.urlForPath() breaks when there is a URL parameter definition instead of an actual parameter (items/:item instead of items/2).
A quick fix for this would be to remove the title property from your route configuration for any route that has a parameter.
{
path: 'item/:item',
component: 'item-view'
// make sure there is no title here
}

Related

Url.Action returning empty string in some environments

I have a Sitecore site and on 2 of my CD servers, Url.Action returns an empty string. This works locally and on 9 other servers ranging from dev to prod, CD and CM.
Deployment automation ensures that the exact same web.config is deployed to all environments; ditto for all other configs.
My controller inherits from SitecoreController properly. This is not isolated to a certain controller or action, this happens with all controllers and actions.
What would make Url.Action return an empty string in one environment and not others, with identical code?
What would make Url.Action return an empty string sometimes?
Specifically, route values that are derived from the current request.
Explanation
The Url.Action method is driven by the UrlHelper, which in turn is driven by routes. It uses route values to determine which route to use to build the URL. The routing framework attempts to match each route against the route values in the order they are registered until a match is found. If the routing framework reaches the end of the routing table and there is still no match, it returns an empty string (because there is no other reasonable default behavior).
On the other hand, if you call Url.Action and pass a route name, this narrows the possible matches to only 1 specific route (the named one). But the route values still need to match that route or you get the default empty string.
In general, all route values must match, but there are a couple of things that may make the behavior quirky:
Route values can be made optional. This means that the route value doesn't need to be present in order for the route to match.
If a route value is not supplied in the call to Url.Action, it may be supplied automatically if it exists in the current request.
This second quirk means that if Url.Action is put on a shared view and one request contains a route value to make it match a route, and another request doesn't contain that route value, in the latter case the URL may match another route or it may be an empty string.
Example
Say the routing configuration is setup like this:
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "AboutWith4RouteValues",
url: "test/home/about/{foo}/{bar}",
defaults: new { controller = "Home", action = "About" });
routes.MapRoute(
name: "ContactWith4RouteValues",
url: "test/home/contact/{foo}/{bar}",
defaults: new { controller = "Home", action = "Contact", bar = UrlParameter.Optional });
routes.MapRoute(
name: "Home",
url: "",
defaults: new { controller = "Home", action = "Index" }
);
}
}
Also, let's say there is a link on the _Layout.cshtml page:
<a href='#Url.Action("About", "Home")'>About</a>
If you go to the home page in the browser (/), the resulting link URL will be:
<a>About</a>
This is because foo and bar are not present in the route values of the request so it doesn't match any of the registered routes.
Actually, Url.Action returns an empty string, but Razor optimizes away the empty href='' attribute.
On the other hand, if you put this URL in the browser:
/test/home/contact/arg1/arg2
The link URL is generated as:
<a href='/test/home/about/arg1/arg2'>About</a>
This is because both {foo} (with value arg1) and {bar} (with value arg2) are available in the current request. Note that the incoming request matches the ContactWith4RouteValues route, but when the link URL is generated it uses the AboutWith4RouteValues route. Since both foo and bar are present in the request, they are carried over to the generation of the URL.
Now, if the URL in the browser is changed to:
/test/home/contact/arg1
It still matches the ContactWith4RouteValues route because the last value is optional. However, the URL that is generated is now:
<a>About</a>
This is because foo has a value in the request, but bar has no value, the Url.Action generation request does not match AboutWith4RouteValues because bar is a required value in order to make it match. And since it also doesn't match the Home route, we have reached the end of the route table and the only logical thing to return is empty string.
Workaround
The simplest workaround to avoid these quirks of the current request is to manually specify the route values when calling Url.Action or other UrlHelper based methods (such as ActionLink, RedirectToRoute, etc).
<a href='#Url.Action("About", "Home", new { foo = Model.Foo, bar = Model.Bar })'>About</a>
This ensures those values are always present when building the URL even if they don't happen to be part of the current request.
I had this exact same issue and for me the issue had to do with Case-Sensitivity in my Routing.
Deploying the same site to two different Applications on the same IIS Web Server.
I used the same web-config and same VS Web Publish Settings.
Yet, my Url.Action was returning blanks (empty-strings) in Prod, but not in Dev.
After reviewing the code, something caught my eye.
I have an Area called WorkBench (upper-case "B").
In a few places (where Url.Action returned blank), I was passing in Workbench (lower-case "b").
I have some fancy logic in my RouteConfig.cs and Area Registration (i.e. WorkBenchAreaRegistration.cs).
In those files, I had Conditional Logic to determine what Environment the Application was running in.
I use the same Project to share code, but only want some Areas accessible in different Environments.
Even though Prod and Dev are different Environments, to debug this issue,
I altered them so they would temporarily appear the same.
The issue was fixed once I capitalized the "b" to "B" everywhere.
I still do not know why it would work on my local machine and in Dev, but not in Prod.
It should have behaved the same way in both Environments.
Again, same server, same publish, same web.config, same iis config, same application code, etc...
Sorry I don't have an explanation for this, but at least this is a possible fix you could try.
Hope this helps someone out there.
Update 02/08/2019: I had this problem again with a different link and for this one I realized I had an option set to access all Actions within an Area when one of my Debug variables was set to true, or when running in Prod. I removed this conditional logic in my AreaRegistration.cs file and it fixed the problem when running in a Staging envrionment.
The Lesson Learned here is that anytime you see a blank Href, you gotta check those Route Configs.

Is there a better way to reference a route by name in a link with AngularDart?

I have many named routes in my AngularDart app. I create links the old fashioned way, like this:
Go
That seems brittle. If I change the path or change the strategy away from hash change, I need to change all my links.
Can I do something like:
<a ng-link="activities">Go</a>
Where activities is the name of the route from my routes config.
For now you can use router to generate those URLs for you.
router.url('activities', {});
The second parameter (should probably be optional) is a map of parameter values. For example, if you have a path like /activity/:activityId then you can do:
router.url('activity', {'activityId', '12345'});
URL generator also honors current state of routes, so lets say you had an active route like foo.bar.baz, and foo was parameterized and you somehow got a hold of bar route (ex. via RouteProvider or queried router.root.getRoute('foo.bar')) then you don't need to know the values of the foo route parameters to generate the URL for baz, you can do:
Route bar = router.root.getRoute('foo.bar');
router.url('baz', {}, startingFrom: bar);
For now you will need to manually insert the generated URL into the template:
link

Grails glink optional params attribute not filtered based on custom urlMappings

Using the custom url mappings in grails the one would expect the following behavior:
all g.createLink and g.link by default do filtering of the optional attribute params based on Urlmappings.groovy logic. The rest of page params are ignored as irrelevant for the particular url.
Actually this doesn't happen. And the workaround is to provide all link params manually:
<g:link action="action" controller="controller" params="[duplicated entries from url mappings]">my custom params link mapped in UrlMappings</g:link>
Case the page params completelly passed as url params the one becomes a wierd url with custom mapping plus the rest of page params.
There is no duplication. You need to define what parameters are defined in your path (just placeholders). If you generate a link you need to provide the values for those parameters. The values cannot be automatically derived.
The only thing you can do, is to beautify the notation using namedMappings. The advantage of this is, that you get at least an error message, if you have forgotten to pass in some required variables.
Or you define your own link tag, which automatically filters from the global params map.

mvc route random number of parameters

I am trying to construct a route that routes to a specific product page.
The product exists in a category. Categories may also exist in categories. I am trying to construct the URL like
my-site.com/products/category/category/category/product
The amount of categories is able to change but the last parameter will always be the name of the product.
Is there any way to construct a route for this?
A solution exactly for you
I faced the same problem in the past and solved it a bit differently to what others will advise you here. Most of solutions will talk about *catch-all parameter. In your case this means that you'd have to parse the product out yourself. Manually. Because catch-all parameter may only be the last parameter in route definition.
Catch-all anywhere in the route
If you think of it carefully, you can actually realise that catch all parameter can actually be defined anywhere in the route as long as you have all other segments present. So I've written such route class that does all that and successfully runs in production on a heavy traffic website.
My blog post has all the information about it as well as all the code that will solve your problem:
Custom Asp.net MVC route class with catch-all segment anywhere in the URL
This makes it possible for you to define your route as:
products/{*categories}/{productId}
If you think of the catch-all parameter even further you could also get to the point that a single route definition could have several catch-all parameters as long as at least one segment between them is static. But my class isn't able to do this, because your scenario with just one arbitrary segment set is much more common.

ASP.NET MVC AnchorLink not using route values?

I'm using AnchorLink on a very simple site with just two routes defined, one standard route and another area route for admin/{controller}/{action}/{id}. I'm at a URL like:
/admin/release/push/255
In that view I'm using:
#Html.AnchorLink("Confirm", "Confirm")
AnchorLink is rendering a link without the current request {id} included, ie. /admin/release/confirm! If I inspect the RouteValues I can see {id} is in there. If I explicity pass in the route values from teh current request, as in:
#Html.AnchorLink("Confirm this release", "Confirm", Url.RequestContext.RouteData.Values)
Then it works, I get the URL /admin/release/confirm/255. Why does the explicit version where I pass in the current request route values work, but the implicit one, without the route values argument, which I thought would pick up the current request route values, doesn't? I know the above is a solution, but it's ugly and there's some underlying reason why the AnchorLink without the route values isn't working as I expect?!
MVC is doing exactly the right thing here. It's not to know you require the Id parameter for your anchor link or not -- in other words its not trying anything clever to pre-parse and examine the link. It will only do that when its being rendered. And in your case without the id parameter specified somewhere its going to use the default route.
If you find yourself writing that same code all over the place you could easily extract it out into a static helper class. That can get rid of the ugliness.

Resources