ActionLink and relative path - c#

I can't find out how to solve this. I have two URLs. These are /my-url-1 and /my-url-2. Both going to different views.
The thing is that I have an ActionLink on /my-url-1's view which should make /my-url-2 and go to that view.
The problem is that ActionLink makes /my-url-1/my-url-2 as the URL and not just /my-url-2.
I was searching two days about how to fix it but couldn't find anything.
PD: I'm not using Areas so please don't tell me that I just should put the "area" parameter as a "".
These are two urls which goes to different controllers and different actions.
View which has the ActionLink (URL:/my-url-1) :
<div class="btn-index-container">
#Html.ActionLink("Url 2", "MyAction", "MyController")
</div>
This ActionLink should render:
Url 2
But it's rendering:
Url 2
where /my-url-1 is my current URL
Route Config
routes.MapRoute(
name: "route1",
url: "my-url-2", //without parameters
defaults: new { controller = "MyController", action = "MyAction" },
);
routes.MapRoute(
name: "route2",
url: "my-url-1", //without parameters too
defaults: new { controller = "MyController2", action = "MyAction2" }
);
So, when I go to localhost:port/my-url-1 it loads MyAction2 which renders a view. This view has inside an ActionLink(described above) which should render a /my-url-2 link.

Well, I've worked inside the MVC framework and I could told you about how Url.RouteUrl or Html.RouteLink works. At the end, the method which create the URL is GetVirtualPathForArea (this method is called before UrlUtil.GenerateClientUrl, which receive the VirtualPathData.cs created by GetVirtualPathForArea, as a parameter) from System.Web.Routing.RouteCollection.cs.
Here I left a link to the MVC source code:
https://github.com/aspnet/AspNetWebStack/blob/master/src/System.Web.Mvc
I found that, my Request.ApplicationPath was changing when I loaded /my-url-1. It was crazy because the application path was /.
At last, the problem was that the /my-url-1 was pointing to a virtual directory created on the IIS some time ago by error.
To know where your IIS configuration file is, please follow the link below:
remove virtual directory in IIS Express created in error
The solution was remove the .vs directory (which contains the config .vs\config\applicationhost.config) and rebuild
I think most of the Helpers that render URLs works more or less in the same way, so I hope it'll useful for all of you!

In your case, maybe no its necesary, just pass the parameters with null values, E.G.
#Html.ActionLink("EspaƱol", null, null, new { Lng = "es" }, null)
In this way, the parameters change, and the view is relative, depending on where you are.

Related

ASP.NET MVC routing aspx file

So I'm writing an ASP.NET MVC app and I have a little bit problem with routing aspx file - in general making this work.
Let's say I have a razor page and I want to, for example open specific row from database and show it, it's very simple and I just write in index.cshtml:
#Url.Action("Details", new { id = item.DB_Id })
And details page opens and I can see specific informations of this row in database
Code of routing:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home",
action = "Index",
id = UrlParameter.Optional
}
But when I want do the same but instead of opening details.cshtml file I'd like to do it with details.aspx (Web Form) appears a problem. Is controller has to be different, is code of routing has to be different? Or is it basically possible? And ideas or hints?
Url.Action helper doesn't create URL's to Web Forms pages because they aren't Actions. You'll need to do something like
#Url.Content("~/somefolder/Details.aspx?id=" + item.DB_Id)
Url.Content is meant for creating URL's to static files, but it also works well with Web Forms.
You could create your own helper that handles parameters more cleanly. I don't have time to do the implementation right now, but you could create something like:
#Url.WebFormsPage("~/somefolder/Details.aspx", new { id = item.DB_Id })
The helper could use reflection to generate the appropriate query string and append it to the URL.

MVC manipulate URL (routing), is it possible?

I have a website that use this pattern.
http://www.domain.com/product/...
My question is now, i need to create a subsite that going to be with this URL pattern, i have tried to change the routing without success.
http://www.domain.com/companyname/product/...
How can i inject the companyname in the URL without breaking my current routing?
Thanks
Niden
Three ways:
If it's relatively static, you can follow Andy's advice in the comments and publish the site in a virtual directory, companyname. Assuming you've properly used the UrlHelper extensions to generate URLs, instead of just hard-coding paths, then everything will just work.
You can create a "companyname" area. The default routing for an area is /area/controller/action. So that would get you the URL structure you want. However, areas are somewhat segregated, so you would need to copy controllers and views to the area's directory. Although, you could subclass controllers from the main app in the area to reuse code.
Just change the default route/add a new route:
routes.MapRoute(
"CompanyDefault",
"{company}/{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = "" }
);
// default route here

route not mapping mvc 3

I am new in mvc and I have a situation where I am convinced that I am mapping a route correctly although it is not.
it is a very basic login form with the option of passing in parameters.
this is the html
<li>Login</li>
and this is the action method in the 'Home' controller
public ViewResult LoginForm(string userName)
{
return View();
}
This is how is my attempt at mapping the route
routes.MapRoute(
null,
"Login/{userName}",
new { controller = "Home ", action = "LoginForm", UrlParameter.Optional }
);
The url is however displaying as follow
/Home/LoginForm?loginUser=user
my aim would be the following
Login/user
Advice perhaps as to why it is not mapping correctly. I have already registered a number of routes in the Global.asax.cs file. Could it have something to do with the order with which they were registered?
Try this:
<li>Login</li>
change the parameter loginUser to userName.
Use userName instead of loginUser
<li><a href='#Url.Action("LoginForm", "Home", new {userName="user"})'>Login</a></li>
You are hitting a different address than the one specified in MapRoute. The mapped route will not fire. Change both the parameter and the action name.
<li>Login</li>
You need to access /Home/Login not /Home/LoginForm. The routing is done automatically if the right address is accessed.
EDIT:
Following your address edit:
As far as I know, you cannot generate a link such as Login/{userName} using Url.Action; if you don't specify a controller, this defaults to Home controller
You can however access the Login/{userName} link directly from the browser (due to the mapped route)
You can create a "static" (i.e. classic) link, passing a hard-coded address:
<li>Login</li>
Please note that the userName added/removed per JavaScript.

Dynamic URL route

This app has several routes configured in RouteConfig.cs. For instance, I have the two following routes defined:
routes.MapRoute(
name: "MyPage-Demo",
url: "pages/page-title/demo",
defaults: new { controller = "Root", action = "PageDemo" }
);
routes.MapRoute(
name: "MyPage",
url: "pages/page-title/{resource}",
defaults: new { controller = "Root", action = "Page", resource = UrlParameter.Optional }
);
Each page someone visits has a link to a "demo". A page could be accessed by visiting http://localhost/pages/page-title. This works fine.
When a user clicks the "demo" link, they are redirected to a page located at http://localhost/pages/page-title/demo. This works fine.
My problem is the demo page may reference a complex nested structure. The structure consists of JavaScript, css, images, etc. Content used for the purpose of the demo. None of these nested resources can be found. However, I'm not sure how to setup my routing to account for these nested files.
I'm confident I'm going to need to update my controller's PageDemo action. However, I'm not sure
a) how to do so in a way that will allow for differing structures and
b) how to update my route configuration to account for these nested structures.
Is there a way to do this? In reality, I'm going to have multiple pages and multiple demos. For that reason, I want to have something a little more reusable than a hard-coded approach.
If you just need to serve files physically stored in a path, you should be able to just ignore the route, e.g.:
routes.IgnoreRoute("pages/page-title/demo/resources/{*resource}");
That will bypass MVC trying to route the request to a controller.
Or you could go by file extension:
routes.IgnoreRoute("{file}.js");
routes.IgnoreRoute("{file}.css");
(Code is untested, but it looks like you're trying to do something similar here :)
https://stackoverflow.com/a/3112192/486620
IF I understand:
The problem seems to be that your MyPage-Demo route:
routes.MapRoute(
name: "MyPage-Demo",
url: "pages/page-title/demo",
defaults: new { controller = "Root", action = "PageDemo" }
);
is NOT {resource} specific, while your MyPage route IS.
If you change your route to take a {resource}
routes.MapRoute(
name: "MyPage-Demo",
url: "pages/page-title/demo/{resource}",
defaults: new { controller = "Root",
action = "PageDemo", resource = UrlParameter.Optional });
Then your action method can
return specific Views with proper resource settings
set a Viewbag property with path to your specific resource
If this is inline with your intent, these routes can be consolidated into
routes.MapRoute(
name: "MyPage-Demo",
url: "pages/{action}/{resource}",
defaults: new { controller = "Root",
action = "PageDemo", resource = UrlParameter.Optional });
/pages/PageDemo/{resource} resolves to Controller=pages, action = PageDemo
/pages/demo/{resource} resolves to Controller=pages, action = demo.
This convention allows you flexibility to create more {resource} dependant links
In the Browser, Right Click Demo page => Choose View Page Source.
Here, you have the link for the CSS and Js files in your Demo page. Click on those js/css file links. Check if there are redirecting you to the correct/expected location. Otherwise you could make the Css/Js file URL accordingly Because, as per the demo page each PageDemo will have its own unique structure of JS/Images/css, etc
How are you referencing your JS and CSS files ?
If you use the tilde character like : ~/Content/Styles/Site.css you won't have any problem no matter where you are in your virtual path.
Also not 100% sure I am directly answering your question, but making the assumption that the resources you are trying to access are nested in a folder structure that mirrors the page structure - and the issue you are having is how to ignore the routes to these without having to know what they might be in advance?
This does a good job of explaining that: https://stackoverflow.com/a/30551/1803682
I would ask:
As #PKKG notes in his answer - do the links in the page source match what you expect?
How is this per-demo content served: e.g. by a service and not a static file?
this answer contains two approaches. the second one may be more suitable for your scenario. the first may be more suitable for a general mvc project
approach one
i suggest creating a organized structure in your content folder to store the scripts and css files, ie
/Content/Demos/Page-Title-1/
/Content/Demos/Page-Title-2/
/Content/Demos/Page-Title-3/
and
/Content/Demos/Common/
and then make a bundle to render the scripts and css files for each page title
ie.
bundles.Add(new StyleBundle("~/Demo/page-title/css").Include(
"~/Content/Demos/Page-Title-1/csscontent1.css",
"~/Content/Demos/Page-Title-1/csscontent2.css",
"~/Content/Demos/Page-Title-1/csscontent3.css",
"~/Content/Demos/Page-Title-1/csscontent4.css"));
bundles.Add(new StyleBundle("~/Demo/page-title/js").Include(
"~/Content/Demos/Page-Title-1/jscontent1.css",
"~/Content/Demos/Page-Title-1/jscontent2.css",
"~/Content/Demos/Page-Title-1/jscontent3.css",
"~/Content/Demos/Page-Title-1/jscontent4.css"));
this will allow you to render the scripts on the demo page using a few line approach, ie.
#Styles.Render("~/Demo/page-title/css");
#Scripts.Render("~/Demo/page-title/jss");
#Styles.Render("~/Demo/common/css");
#Scripts.Render("~/Demo/common/css");
you will have to update the files in global .asax as you change the files in your /Content/Demos/Page-Title/ folder.
there is the benefit that if you choose, you may bundle and minify the files to save bandwidth and load time for the first page load.
approach two.
(still use the following folder structure
/Content/Demos/Common/
and
/Content/Demos/Page-Title-1/
/Content/Demos/Page-Title-2/
/Content/Demos/Page-Title-3/)
make an html helper to reference all the scripts & contents in a folder
its usage would be
#Asset.RenderAssets( '~/folderdirectory')
and the helper would do something like
#helper RenderAssets (stirng directory){
#* scrape the directory for all script files*
var scripts = find all scripts in the directory
#* include the script files *#
for each script
<script src=" ... .js"></script>
#* scrape the directory for all cssfiles*
var styles = all css in the directory
#* include the css files *#
for each style
<link rel='stylesheet' type="text/css" href=" ... .css">
}
this would be a few line usage in each demo view
#Asset.RenderAssets( '~/Content/Demos/Common')
#Asset.RenderAssets( '~/Content/Demos/Page-Title')
you may or may not need to pair this with an extra few line or two in your global.asax or RouteConfig.cs file (see source 3)
routes.IgnoreRoute("/Content/Demos/{page}/{script}.js");
routes.IgnoreRoute("/Content/Demos/{page}/{style}.css");
relevant sources
to create html helpers see
http://weblogs.asp.net/scottgu/archive/2011/05/12/asp-net-mvc-3-and-the-helper-syntax-within-razor.aspx
to use bundling and minifcation (the scripts.render approach) see
http://www.asp.net/mvc/tutorials/mvc-4/bundling-and-minification
phill haakk says may not need to pair this with an ignore route!
https://stackoverflow.com/a/30551/1778606
commentary and edits are encouraged.
All static content (.js, .css, .html, .png) is not seen by MVC (unless modules/runAllManagedModulesForAllRequests is set to true in web.config). Static content extensions are defined in IIS configuration "module mapping", and is using the StaticFileHandler module (and not the .NET module).
So static content must be referenced by its physical path relative to the current path (the path of the current html page).
The best solution is to use absolute link from the root of the website. Like /content/demo1/demo1.html, put all js,css in /content/demo1/, and in demo1.html use path relative to the /content/demo1/ folder (where the .html is). Ie: with demo1.css being in the same folder.
The link to demo1.html would be demo 1

MVC non-area route problems (alongside areas)

How should I configure the following non area routes?
/foo/{controller}/{action}/{id}
maps to controllers in namespace myapp.foo.
/{controller}/{action}/{id}
maps to controllers in namespace myapp.
I also have 2 areas, bar and baz, they are registered with registeraAllAreas.
My current setup
This is my current setup. It gives the problem below when I use the url /Home/Index.
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.IgnoreRoute("myapp/elmah.axd/{*pathInfo}");
AreaRegistration.RegisterAllAreas();
routes.MapRoute(
"foo", // Route name
"foo/{controller}/{action}/{id}", // URL with parameters
new { action = "Index", id = UrlParameter.Optional }, // Parameter defaults
new string[] { "myapp.Controllers.foo" }
);
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional }, // Parameter defaults
new string[] { "myapp.Controllers" }
);
Multiple types were found that match the controller named 'Menu'. This
can happen if the route that services this request
('foo/{controller}/{action}/{id}') does not specify namespaces to
search for a controller that matches the request.
The request for 'Menu' has found the following matching controllers:
myapp.Controllers.MenuController
myapp.Areas.bar.Controllers.MenuController
myapp.Areas.baz.Controllers.MenuController
Clearly there's something I'm doing the wrong way.
Update
I also get the wrong adress generated when I use:
<% using (Ajax.BeginForm("SaveSomething", "Home", ...
It renders <form target="/foo/Home/SaveSomething"
I'm guessing that one cannot reliably use {controller} in two routes in the same area.
Update 2
It seems to work much better when I put the /foo route registration at the bottom.
This raises the question, what is considered a/the default route? (As the default route is reccomended to be put at the very end.)
You have two controllers that has the name MenuController so MVC doesn't know which one to use if you don't give it more information. In you areas you probably have a files named something like <YourAreaName>AreaRegistration. Open those files and update the RegisterArea method so you route the request to the right controller.
From your error message it seems like the route is getting mapped to foo/{controller}/{action}/{id}, which doesn't have a MenuController. My guess is that you have a action link on a page under foo something something. That will generate an incorrect link if you don't specify the area for the link.
Try this to use the default route with ActionLink:
#Html.ActionLink("Some text", "action", "controller", new { area = "" }, null)
If you want the request to go to a specific area just write it down in the call.
UPDATE: The problem is that when you write something like Ajax.BeginForm("SaveSomething", "Home",...) it will match the first route. You can't solve this by putting the area in the BeginForm statement as I suggested before since the foo route is not an area. You have two options, 1: move the foo part to an area, 2: put the foo route after the default route. If you put the default route before the foo route you will get a hard time rendering urls as long as you have foo in the same area as the default route (the default area), since the route engine will always find the default one first. However, you will be able to catch request to the foo route. So my best suggestion is to put the foo route in an area.

Categories