asp.net mvc routing "view was not found" - c#

very simple basic question
I have only route:
routes.MapRoute(
"Widget", // Route name
"Widget/Frame/{postUrl}", // URL with parameters
new { controller = "Widget", action = "Index", postUrl = UrlParameter.Optional } // Parameter defaults
);
And when i try to open following url:"http://localhost:50250/Widget/Frame/qwerty"
I have an error:
The view 'qwerty' or its master was not found or no view engine
supports the searched locations. The following locations were
searched:
Well...why?
Controller code:
public class WidgetController : Controller
{
//
// GET: /Widget/
public ActionResult Index(string postUrl, int? blogEngineType)
{
return View(postUrl);
}
}

You are returning a View with
return View(postUrl);
Since there is no name of the view (in this overload), the method uses the Action name as view name and looks for it. You probably meant to do
return Redirect(postURL);

I would hazard a guess and say it's because it's actually trying to use the action name of Index(), since that's the default action that you've specified. You're not passing an {action} parameter through the url, so where else will it get the action from?
Can you change your url pattern to Widget/{action}/{postUrl} and see if it works then?
Either that, or set the default value of action to Frame instead. Basically, it has no way of knowing that you're looking for the Frame action, so it fails.
Edit: I see what you're doing now - the action's name is actually Index, right? In that case, I'm not sure, we need to see your controller code. I'll leave the above answer in case it's useful.
Edit 2: You're passing the value "qwerty" as the view name - do you have a view named "qwerty" in the views folder?
If you intend for it to be the model, and for the view name to be "Index", you should call return View((object)postUrl); instead, so that it doesn't get confused.

It's because your return statement is return View(postUrl); and when you pass a string to the View() method it is interpreted as the name of the view to use. So it looks for a view called qwerty since that's what's in that variable. If you want to hand postUrl as a model to the view of your Index action, you'll have to change your return to be return View("Index", postUrl)

Are you sure there is a View called 'qwerty' in the Shared or Widget folder within the Views parent folder?
Otherwise you probably want to use return RedirectToAction(postURL);

Related

Getting controller name from razor

I seem to be having a difficult getting something that should be easy. From within my view, using Razor, I'd like to get the name of the current controller. For example, if I'm here:
http://www.example.com/MyController/Index
How can I get the controller name, MyController from a Razor expression:
#* Obviously this next line doesn't work
#Controller.Name
*#
I'm new to MVC, so if this is an obvious answer, don't attack me to bad.
#{
var controllerName = this.ViewContext.RouteData.Values["controller"].ToString();
}
OR
#{
var controllerName = HttpContext.Current.Request.RequestContext.RouteData.Values["controller"].ToString();
}
An addendum to Koti Panga's answer: the two examples he provided are not equivalent.
This will return the name of the controller handling the view where this code is executed:
var handlingController = this.ViewContext.RouteData.Values["controller"].ToString();
And this will return the name of the controller requested in the URL:
var requestedController = HttpContext.Current.Request.RequestContext.RouteData.Values["controller"].ToString();
While these will certainly be the same in most cases, there are some cases where you might be inside a partial view belonging to a different controller and want to get the name of the controller "higher-up" in the chain, in which case the second method is required.
For example, imagine you have a partial view responsible for rendering the website's menu links. So for every page in your website, the links are prepared and passed to the view from an action called SiteMenuPartial in LayoutController.
So when you load up /Home/Index, the layout page is retrieved, the SiteMenuPartial method is called by the layout page, and the SiteMenuPartial.cshtml partial view is returned. If, in that partial view, you were to execute the following two lines of code, they would return the values shown:
/* Executes at the top of SiteMenuPartial.cshtml */
#{
// returns "Layout"
string handlingController = this.ViewContext.RouteData.Values["controller"].ToString();
// returns "Home"
string requestedController = HttpContext.Current.Request.RequestContext.RouteData.Values["controller"].ToString();
}
#HttpContext.Current.Request.RequestContext.RouteData.Values["controller"].ToString();
MVC 3
#ViewContext.Controller.ValueProvider.GetValue("controller").RawValue
MVC 4.5 Or MVC 5
#ViewContext.RouteData.Values["controller"].ToString();
To remove need for ToString() call use
#ViewContext.RouteData.GetRequiredString("controller")
Also if you want to get the full controller's name (with "Controller" ending) you can use:
ViewContext.Controller.GetType().Name
I want to add some more information to the previous answers. Maybe someone need.
In order to find Area Name, Controller Name and Action Name in razor view engine you can use:
#{
var areaName = ViewContext.RouteData.Values["area"].ToString();
var controllerName = ViewContext.RouteData.Values["controller"].ToString();
var actionName = ViewContext.RouteData.Values["action"].ToString();
}
#ViewContext.RouteData.Values["controller"].ToString();

Returning view via a path name

I have a site that has basic functionality, but can be overriden based on different clients and different partners. The Routing is set up to handle the client name and the partner name as part of the route:
routes.MapRoute(
"DefaultRoute", // Route name
"{client}/{portal}/{controller}/{action}/{id}", // URL with parameters
new { client="UNKNOWN", portal="UNKNOWN", controller = "Home", action = "Index", id = UrlParameter.Optional }, // Parameter defaults
new string[] { "Enterprise.Portal.Controllers" }
);
I have a helper class to determine if a view exists that will supercede the normal view. The site has different clients and each client has different partners. These clients can provide HTML if they do not want the default views, and the partners can do the same. I keep these alternate views in a folder. The helper class takes the information and if an alternate view exists, returns the file path to this view. If it returns null or empty string, the normal view is used.
public static string ViewPath(string basePath, string client, string partner, string controller, string viewname)
// This returns something like C:\Sites\Portal\UI\ClientName\PartnerName\ControllerName\View.cshtml
In my controller, if this returns a non null or empty value, How do I provide that view to be used. Here is what I did, which doesn't work:
if (String.IsNullOrEmpty(this.model.CurrentViewLocation))
{
return View(model);
}
else
{
return View(this.model.CurrentViewLocation, model);
}
I am getting the following error, because obviously the return View() constructor can not use path names, only View names. Is there a way to accomplish this? I can convert the paths to Virtual Web Paths if needed like "~\UI\Client\Partner\Controller\View.cshtml".
Server Error in '/' Application
The view 'C:\Sites\Portal\UI\ClientName\PartnerName\Account\LogOn.cshtml' or its master was not found or no view engine supports the searched locations. The following locations were searched:
~/Views/Account/C:\Sites\Portal\UI\ClientName\PartnerName\Account\LogOn.cshtml.aspx
~/Views/Account/C:\Sites\Portal\UI\ClientName\PartnerName\Account\LogOn.cshtml.ascx
~/Views/Shared/C:\Sites\Portal\UI\ClientName\PartnerName\Account\LogOn.cshtml.aspx
~/Views/Shared/C:\Sites\Portal\UI\ClientName\PartnerName\Account\LogOn.cshtml.ascx
~/Views/Account/C:\Sites\Portal\UI\ClientName\PartnerName\Account\LogOn.cshtml.cshtml
~/Views/Account/C:\Sites\Portal\UI\ClientName\PartnerName\Account\LogOn.cshtml.vbhtml
~/Views/Shared/C:\Sites\Portal\UI\ClientName\PartnerName\Account\LogOn.cshtml.cshtml
~/Views/Shared/C:\Sites\Portal\UI\ClientName\PartnerName\Account\LogOn.cshtml.vbhtml
I'm guessing a better way to do this would be to add the Client folder and the Partner folder to the Location Formats of the view engine which are used searched for the views. But the format string only includes {0} for the controller and {1} for the viewname. I would need to override it to pass the Client and the partner as well, which are both passed via the route.
I can convert the paths to Virtual Web Paths if needed like
"~\UI\Client\Partner\Controller\View.cshtml".
Yes, that's precisely what you should do because that's what the View method expects - a relative path to the root of the website:
return View("~/UI/Client/Partner/Controller/View.cshtml", someViewModel);

MVC Changing a URL route

I just watched the Pluralsight video on URL routing in ASP.NET MVC 3. I'm still a bit confused.
That image shows how my views are set up. I have a controller for each view. Perhaps I misunderstood something but when I did this I thought by adding the Portfolio view and controller instead of going to /home/portfolio it would just take me to /portfolio but it didn't. Now when I click on the portfolio link it takes me to /portfolio/portfolio.
Am I misunderstanding something about the way routing works in ASP.NET or am I just forgetting something?
It takes you to Portfolio/Portfolio because that is how you named the setup. The first one is the name of your controller (without controller in the name) PortfolioController. The second one is the name of your ActionResult, Portfolio which returns Portfolio.cshtml. If you wish to only see /Portfolio you could always have the PortfolioController use
public ActionResult Index(){ return View(); }
and then rename Portfolio.cshtml to Index.cshtml and you should be good to go.
when you have localhost/portfolio you that will call default action of portfolio which will be Index by default and you will see that view ,since you don't have view for that and you can make one
you can access your portfolio action in your portfolio controller by /portfolio/portfolio
because it follows the default route which is in routConfig.cs in app_start folder
if you want to get the same result for /portfolio/portfolio just with /portfolio you can add a route like this in your routeconfig
routes.MapRoute(
name: "portfolio",
url: "portfolio/{action}",
defaults: new { Controller = "portfolio", Action = "portfolio" }
);
be careful to write that before default route,because when it match the first route it would not check for other
sorry my English is not good
Rename the action and view to Index.

MVC Routes... How do I set up my controller to deal with /configuration/building/add

I have an architecture where I have numerous objects I can configure. Examples of URLs I want are as follows:
/configuration/building/add
/configuration/building/edit/1
/configuration/group/add
/configuration/group/edit/1
I have a Configuration controller but how do I intercept or deal with building/add and building/edit/1 etc... If it were AddBuilding I could simply add an AddBuilding() function, and similarily how do I get it to work for configuration/building/edit/
Here's what you can do for the first one - open up the Global.asax.cs file of your site and put this in RegisterRoutes before the standard MVC catch-all route (the one that uses the route "{controller}/{action}/{id}"):
routes.MapRoute("AddBuilding", "configuration/building/add",
new { controller = "Configuration", action = "AddBuilding" });
The others will be the same, but different names (first parameter) and action, whislt the edit routes but would include an {id} route placeholder and route parameter (but not optional - unlike the MVC default route):
routes.MapRoute("EditBuilding", "configuration/building/edit/{id}",
new { controller = "Configuration", action = "EditBuilding" });
By leaving the id off the route defaults we make it required. I'm assuming this, because I'm guessing the Url /Building/Edit doesn't logically map to anything.
As a side node - including verbs in your urls isn't really in keeping with REST methodology, however you're not the first to do it by a long way (I include myself in that too). That said - trying to keep to it usually makes your life a lot easier, as you'll find your controllers will be cleaner, as will your route table, and your site's URL space will be a lot smaller and more obviously hierarchical. This last point is - handy for zooming around the site at dev time, but more importantly it's crucial for SEO.
So (I've commented this code heavily, hopefully enough to provide some nuggets of knowledge!):
public class ConfigurationController{
////HTTP GET /Buildings
/// DISPLAYS BUILDINGS
public ActionResult Buildings(){
//get model and return view that shows all buildings with perhaps a
//partial in that for creating a new one (or you can use another action)
//The HTML form on that view will POST to the URL handled by the method below.
}
////HTTP POST /Buildings
/// CREATES A NEW BUILDING
//use ActionName here to make this and the method above accessible through
//the same URL
[ActionName("Buildings")]
[HttpPost]
public ActionResult CreateBuilding(BuildingModel model){
//validate the model, create the object and return the same
//view as the Buildings() method above (after re-loading all the
//buildings. Or, you can issue a redirect, effectively transferring
//control back to the method above.
}
////HTTP GET /Configuration/Building/id
///DISPLAYS A BUILDING
public ActionResult Building(int id){
//get building and return view, which also contains Edit functionality
}
////HTTP POST /Configuration/Building/id
///EDITS A BUILDING
[HttpPost]
public ActionResult Building(int id, BuildingModel model){
//very similar to the CreateBuilding method - and again you might
//either simply return a building view at the end, or redirect
//to the method above.
//Note that we don't need [ActionName] here because this and the
//get method can have the same method names, because they are overloads
//i.e. since they have different method signatures we can call them the same
//thing in code.
}
}
I've left off the group stuff to keep it short, and hopefully you'll be able to see how to do it from there.
With this in place, we only need at most two routes in Global.asax.cs - although I think the order will be important:
//handles both GET and POST to this URL, i.e. our view & edit operations
routes.MapRoute("IndividualBuilding", "/configuration/buildings/{id}",
new { controller = "Configuration", action = "Building" });
routes.MapRoute("Buildings", "/configuration/buildings",
new { controller = "Configuration", action = "Buildings" });
Now we are using the HTTP verbs to signify what we intend to do with a particular request, and our URLs have become more 'logical'.
Another refactor
If you want to be 'clever' you can lump both buildings and groups under two routes
//handles both GET and POST to this URL, i.e. our view & edit operations
routes.MapRoute("Individual", "/configuration/{controller}/{id}",
new { controller = "Configuration", action = "List" });
//again, handles GET and POST
routes.MapRoute("Collection", "/configuration/{controller}",
new { controller = "Configuration", action = "Single" });
Now you do both buildings and groups controllers as I showed above, but replace Buildings (remembering the ActionName attribute on the second method) with List and Building with Single.
One final thing to consider is that because of the default MVC route:
routes.MapRoute("Default", "{controller}/{action}/{id}",
new { controller = "Default", action="Home", id = UrlParameter.Optional });
Both of your two controllers can still be routed via /Buildings/Single/1 or /Groups for example. This is a minor issue (dupe content isn't great SEO) but it can be something that people can use to sniff your site.
If you absolutely want to prevent this other url format; you can take out the default route, meaning you'd have to explicitly route other stuff that might already work (not a great issue).
Or you can use a little trick that will make it far harder: use explicit [ActionName] attributes with characters in the route name that won't be allowed through IIS - e.g. ":Single" or ":List", and then adjust our two routes from a couple of code blocks back accordingly.
So firstly you can create a controller action called AddBuilding() as you have hinted.
Then in your Global.asax file in the RegisterRoutes method you can add a route like so:
routes.MapRoute(
"AddBuilding", // Route name
"configuration/building/add", // URL with parameters
new { controller = "Configuration", action = "AddBuilding" }
);
You should not though that you will likely still be able to access the page using "/configuration/addbuilding" because of your default route mapping.
You edit one will be similar expect you will want to map the ID value for this:
routes.MapRoute(
"EditBuilding", // Route name
"configuration/building/edit/{id}", // URL with parameters
new { controller = "Configuration", action = "AddBuilding", id = UrlParameter.Optional }
);
I think you will need to add this code with the default MapRoute setup to ensure that one does not take priority
Another approach would be to create a Configuration MVC area, and then have a building and group controller in that Area.
You can do that by Attribute Routing in ASP.NET MVC 5. Something like following;
// eg: /reviews
[Route(“reviews”)]
public ActionResult Index() { … }
// eg: /reviews/5
[Route(“reviews/{reviewId}”)]
public ActionResult Show(int reviewId) { … }
// eg: /reviews/5/edit
[Route(“reviews/{reviewId}/edit”)]
public ActionResult Edit(int reviewId) { … }
You can add multiple route for the same controller as well. For details please check here

Can't find view even though it's there, it's not looking for the correct extension

I'm confused about this one. I'm using razor with MVC 3 and I have a problem with one view on one action.
[HttpGet]
[ActionName("Unsubscribe")]
public ActionResult UnsubscribeGet(string maskedId)
{
return View("Unsubscribe", maskedId);
}
The Unsubscribe.cshtml view is in the correct view folder.
The route is
routes.MapRoute(
"", // Route name
"Unsubscribe/{maskedId}", // URL with parameters
new { controller = "Account", action = "Unsubscribe" });
When going to the following url "/Unsubscribe/db94fddb", the action fires and then the following error is shown when it tries to find the view.
The view 'Unsubscribe' or its master was not found or no view engine supports the searched locations. The following locations were searched:
~/Views/Account/Unsubscribe.aspx
~/Views/Account/Unsubscribe.ascx
~/Views/Shared/Unsubscribe.aspx
~/Views/Shared/Unsubscribe.ascx
~/Views/Account/db94fddb.master
~/Views/Shared/db94fddb.master
~/Views/Account/db94fddb.cshtml
~/Views/Account/db94fddb.vbhtml
~/Views/Shared/db94fddb.cshtml
~/Views/Shared/db94fddb.vbhtml
Notice it doesn't look for the cshtml extension when looking for Unsubscribe but does when looking for db94fddb
This is only happening with this view, completely at a loss on how to fix it.
** EDIT **
Found it,
I was using System.String as the model. For some reason that was causing it to blow up.
I swapped it to #model UnsubscribeViewModel
[HttpGet]
[ActionName("Unsubscribe")]
public ActionResult UnsubscribeGet(string maskedId)
{
return View("Unsubscribe", new UnsubscribeViewModel { MaskedId = maskedId } );
}
Although this now works I still have no idea why it didn't work before. I have my views set to compile on build so there were no compiler errors.
I would love to know what's happening here.
The overload of the View method you were using takes two strings. It doesn't interpret the second string as a model, it interprets it as the master page name:
protected internal ViewResult View(
string viewName,
string masterName
)
If you instead called it like this, it would work:
return View("Unsubscribe", (object) maskedId);

Categories

Resources