I am trying to change a URL in MVC 5 from "Master" to "Master-Franchise" & I thought the following would work but the URL is still just "Master".
// GET: Master-Fanchise
[Route("Master-Fanchise")]
public ActionResult Master()
{
return View();
}
Use ActionName attributes which allows you to give action name for controller method regardless of method name.
[ActionName("Master-Fanchise")]
public ActionResult Master()
{
return View();
}
Have you enabled attribute routing as it is not turned on by default
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
//Add this line of code
routes.MapMvcAttributeRoutes();
}
}
you may also need to change the controller
[Route("~/ControllerName/Master-Fanchise")]
Related
My Web API has two methods hooked up to a repository.
When I make a call to
"api/Cust/GetCustomers"
the full list of customers in my database is being returned. This is fine. As a heads up, i'm using Northwind so the IDs for a Customer are a group of letters. eg - ALFKI or ANTON
When I make a call to a specific CustomerID, for example
"api/Cust/GetCustomers/alfki"
I don't get an error, but the same list from above(containing all customers in the database) is returned. I'm finding this strange because my impression would be that i'd get a not found error if something is incorrect in my controller or repository.
Does anybody with experience know how something like this happens.
I have an already completed example to work off of, and in that example navigating to a specific will return records only for that customer, which is what i'm looking to do.
Here is the code in my api controller, which is almost identical
I'm thinking there must be something subtle in the routing configs that could cause this without causing an error
CustomersAPIController.cs
public class CustomersAPIController : ApiController
{
//
// GET: /CustomersAPI/
private INorthwindRepository _repo;
public CustomersAPIController(INorthwindRepository repo)
{
_repo = repo;
}
//This routing doesn't work, but if it is a possible issue,
the call for a specific customer wasn't working before I added it
[Route("api/Cust/GetOrders({id})")]
public IQueryable<Order> GetOrdersForCustID(string id)
{
return _repo.GetOrdersForCustID(id);
}
[Route("api/Cust/GetCustomers")]
public IQueryable<Customer> GetAllCustomers()
{
return _repo.GetCustomers();
}
[HttpGet]
[Route("api/Cust/GetCustomers/alfki")]
public Customer GetCustomerByID(string id)
{
Customer customer = _repo.GetCustomerByID(id);
return customer;
}
//===========================================
protected override void Dispose(bool disposing)
{
_repo.Dispose();
base.Dispose(disposing);
}
}
and here is my repo
repo.cs
public interface INorthwindRepository:IDisposable
{
//private northwndEntities _ctx = new northwndEntities();
IQueryable<Customer> GetCustomers();
IQueryable<Customer> TakeTenCustomers();
Customer GetCustomerByID(string id);
IQueryable<Order> GetOrders();
IQueryable<Order> GetOrdersForCustID(string id);
Order FetchOrderByID(int orderID);
}
public class NorthwindRepository : INorthwindRepository
{
northwndEntities _ctx = new northwndEntities();
public IQueryable<Customer> GetCustomers()
{
return _ctx.Customers.OrderBy(c => c.CustomerID);
}
public IQueryable<Customer> TakeTenCustomers()
{
var foo = (from t in _ctx.Customers
select t).Take(10);
return foo;
}
public IQueryable<Order> GetOrdersForCustID(string id)
{
var orders = _ctx.Orders.Where(x => x.CustomerID == id).OrderByDescending(x=>x.OrderDate).Take(4);
return orders;
}
public Customer GetCustomerByID(string id)
{
return _ctx.Customers.Find(id);
}
public void Dispose()
{
_ctx.Dispose();
}
Here is a link to a screenshot of the url in my example to work off of, working as intended and returning the records for a specific ID
http://postimg.org/image/oup88k83f/
In this second one, it is a link to my api that I have been basing on my example to work from.
http://postimg.org/image/858t1oph9/
As mentioned above, the code is nearly identical, except for some small changes to the routing and maybe the api controller names.
If anyone has any idea what is causing this, all suggestions are appreciated.
Thank you
*Update fixed a typo in my code
My routeconfig.cs (the same as the template provided my MVC4 API selection when creating a new project)
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
please, fixed the Route for the action GetCustomerById, look:
[Route("api/Cust/GetCustomers/{id}")]
public Customer GetCustomerByID(string id)
I've been searching for answers for this everywhere, but I can't seem to find any. I basically have an MVC application setup and I am using the built in AttributeRouting for my routes.
The folder structure looks like this;
Models
Views
Controllers
Areas
Member
MemberAreaRegistration.cs
Controllers
HomeController.cs
Views
Home
Account.cshtml
And then I wire up my routes in the global.asax like this;
public class Application : System.Web.HttpApplication {
protected void Application_Start(){
AreaRegistration.RegisterAllAreas();
// other web optimization stuff
RouteConfig.RegisterRoutes(RouteTable.Routes);
}
}
So then, MemberAreaRegistration.cs is simple.
namespace App.Web.Areas.Member {
public class MemberAreaRegistration: AreaRegistration {
public override string AreaName { get { return "Member"; } }
}
public override void RegisterArea( AreaRegistrationContext context){ }
}
And I try to wire it using the attributes...
/areas/member/controllers/homecontroller.cs
// ...
[Route("member/account")]
public ActionResult Account() { return View(); }
// ...
The problem is that this finds the route, but it cannot find the view. I get the following error;
The view 'Account' or its master was not found or no view engine
supports the searched locations. The following locations were
searched:
~/Views/Home/Account.aspx
~/Views/Home/Account.ascx
~/Views/Shared/Account.aspx
~/Views/Shared/Account.ascx
~/Views/Home/Account.cshtml
~/Views/Home/Account.vbhtml
~/Views/Shared/Account.cshtml
~/Views/Shared/Account.vbhtml
By all accounts, this should work fine - and if not, I expect the ~/area to at least be in the path it is trying to search. Do I have to wire something additional up to make this function?
I am using ASP.NET MVC 5.0
If I hardcode the absolute path of the view, it works. Obviously this is not a good situation though. I'd prefer it to find the view out of convention. But if I type return View("~/areas/member/views/home/account.cshtml"); I do get the view back - so I know it can access to file and that it is correct.
Here is my RouteConfig.cs per request
RouteConfig.cs
public class RouteConfig {
public static void RegisterRoutes(RouteCollection routes) {
// mvc attribute routing allows us to supersede normal routing mechanisms and
// declare our routes a bit more verbosely
routes.MapMvcAttributeRoutes();
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
namespaces: new[] { "App.Web.Controllers" }
);
}
}
That's because, once you are defining your route as an action's attribute, ASP.NET MVC doesn't know which area it is in, hence it doesn't know where to look for Views.
In the Controller the Account action is in, try to explicitly specify a RouteArea attribute.
I'm writing this off the top of my head, but it should look like:
[RouteArea("Member")]
[RoutePrefix("member")]
public class HomeController: Controller {
[Route("account")]
public ActionResult Account() { return View(); }
}
or, alternatively:
[RouteArea("Member")]
public class HomeController: Controller {
[Route("member/account")]
public ActionResult Account() { return View(); }
}
We have built an MVC app that publishes a complete website with hierarchal Folders, SubFolders and Pages. The resulting pages, are strictly HTML and are not published in our MVC app. Our customers are able to name their Folders and Pages with any compliant string they choose. So conceivably, once the site is hosted, they could end up with a URL such as:
someDomain.com/folder/subfolder1/subfolder2/page-slug. There is no limit to the number of nested subfolders.
We would like to replicate their sites in our MVC app, so that they are able to test them before they publish and perhaps so we can provide hosting ourselves if required.
The obvious problem, is how can we handle,
ourMVCApp.com/folder/subfolder1/subfolder2/page-slug in an MVC app?
If there was a way that we could set routing to handle such a thing, then we could easily get the content required for the request by splitting the url into an array by "/".
The last segment would be a page contained in the previous segment's folder. We could then search our DB using these strings to get the required content.
Your help is greatly appreciated.
FURTHER QUESTION:
In response to the answer provided by Tomi.
I added the code to my controller's class but I am receiving the following warning:
I am not sure what I am missing? Did I put the code in the place? Thanks again.
UPDATE 2. I realized I had not actually created the controller factory, so I followed a partial example I found here: http://develoq.net/2010/custom-controller-factory-in-asp-net-mvc/. And since implementing it, I no longer receive any build-errors, but when I run the the debug, it crashes the built-in IISEXPRESS without any error message.
Here is my controller factory code:
public class FolderControllerFactory : IControllerFactory
{
public IController CreateController(System.Web.Routing.RequestContext requestContext, string controllerName)
{
try
{
// Get the path
string path = requestContext.RouteData.Values["pathInfo"].ToString();
IController controller = new FolderController(path);
return controller;
}
catch
{
// Log routing error here and move on
return CreateController(requestContext, controllerName);
}
}
public void ReleaseController(IController controller)
{
var disposable = controller as IDisposable;
if (disposable != null)
{
disposable.Dispose();
}
}
public SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, string controllerName)
{
return SessionStateBehavior.Default;
}
}
Here is my global:
ControllerBuilder.Current.SetControllerFactory(typeof(ProofPixApp.Controllers.FolderControllerFactory));
And finally my controller:
public class FolderController : Controller
{
private string _path;
public FolderController(string path)
{
_path = path;
}
public ActionResult Index(string name)
{
ViewBag.Message = "Hello " + name;
return View("/Views/" + _path);
}
}
A couple of notes:
1. I removed the 'override' from public IController CreateController
because I kept receiving the initial error I posted.
2. I added public void ReleaseController and the public
SessionStateBehavior GetControllerSessionBehavior methods to the
CreateController class to avoid other build errors.
3. I removed 'base.' from the catch clause because it too was causing a
build error.
SOLUTION:
I was able to avoid the error by checking to see pathValue was not null in the createController method, like so:
public IController CreateController(System.Web.Routing.RequestContext requestContext, string controllerName)
{
// Get the path
string path = "";
if (requestContext.RouteData.Values["pathInfo"] != null)
{
path = requestContext.RouteData.Values["pathInfo"].ToString();
}
IController controller = new FolderController(path);
return controller;
}
I have no idea what page slug is but here's my solution on how to achieve the routing you requested.
I made a custom ControllerFactory which handles the url and passes it to controller. This ControllerFactory constructs the controller we use to handle folder-route requests. We get the path from routevalues and then pass it to the FolderController.
public override IController CreateController(System.Web.Routing.RequestContext requestContext, string controllerName)
{
try
{
// Get the path
string path = requestContext.RouteData.Values["pathInfo"].ToString();
IController controller = new FolderController(path);
return controller;
}
catch
{
// Log routing error here and move on
return base.CreateController(requestContext, controllerName);
}
}
Here's the controller. The actionmethod, which redirects to given path is called Index for now. The actionmethod returns view it finds from the url.
public class FolderController : Controller
{
private string _path;
public FolderController(string path)
{
_path = path;
}
public FolderController()
{
}
public ActionResult Index(string name)
{
ViewBag.Message = "Hello " + name;
return View("/Views/"+_path);
}
}
Last step is to write our own route and register the factory. Open up RouteConfig.cs. My new RegisterRoutes method looks like this:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Dynamic",
url: "{*pathInfo}",
defaults: new { controller = "Folder", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
In global.asax we need to register our FolderControllerFactory by adding this line into Application_Start method
ControllerBuilder.Current.SetControllerFactory(typeof(FolderControllerFactory));
And that's it! There's still much to be done, like handling improper urls and such. Also I don't think this supports plain html files, the files must be in .cshtml or asp format.
Here's the test:
My folder structure:
Url I request:
localhost:port/Mainfolder/Subfolder/Subfolder2/view.cshtml?name=Tomi
The result with Route Debugger plugin:
I'm doing my first steps in asp.net mvc trying to develop web api.
I have the following routing function:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "cdApiDefault",
url: "api/{controller}/{action}/{token}/{mid}/{id}",
defaults: new {
token = UrlParameter.Optional,
mid = UrlParameter.Optional,
id = RouteParameter.Optional }
);
}
and the following controller:
namespace cdapi.Controllers
{
public class PostsController : ApiController
{
// GET api/posts
public IEnumerable<string> Get()
{
return new string[] { "GET_value1", "GET_value2" };
}
// GET api/posts/5
public string Get(int id)
{
return "value!!!!!!!!!!!!!!!!!";
}
// POST api/posts
public void Post([FromBody]string value)
{
}
// PUT api/posts/5
public void Put(int id, [FromBody]string value)
{
}
// DELETE api/posts/5
public void Delete(int id)
{
}
public String GetTest(String token, String mid)
{
return token + " - " + mid;
}
}
}
the following call
hxxp://localhost:52628/api/posts/5
(in my browser) yields some result, i.e., the function GET is being called and return a value.
However, when I try
hxxp://localhost:52628/api/posts/GetTest/MyTestToken/myTestMid
comes back with 'the resource can not be found' error message.
I thought that the {Action} should contain the function to call and that the 'token' and 'mid' should contain the values I specify. What do I do wrong?
ApiControllers work differently than regular MVC Controllers. Here, method names (GET, POST, PUT, DELETE) represent HTTP VERBS, not url fragment. In your first call,
/api/posts/5
this invokes Get(int).
To do routing like you want, switch to standard MVC by inheriting from System.Web.Mvc.Controller instead of System.Web.Http.ApiController and modify your methods to return ActionResult
I must be dense. After asking several questions on StackOverflow, I am still at a loss when it comes to grasping the new routing engine provided with ASP.NET MVC. I think I've narrowed down the problem to a very simple one, which, if solved, would probably allow me to solve the rest of my routing issues. So here it is:
How would you register a route to support a Twitter-like URL for user profiles?
www.twitter.com/username
Assume the need to also support:
the default {controller}/{action}/{id} route.
URLs like:
www.twitter.com/login
www.twitter.com/register
Is this possible?
What about
routes.MapRoute(
"Profiles",
"{userName}",
new { controller = "Profiles", action = "ShowUser" }
);
and then, in ProfilesController, there would be a function
public ActionResult ShowUser(string userName)
{
...
In the function, if no user with the specified userName is found, you should redirect to the default {controller}/{action}/{id} (here, it would be just {controller}) route.
Urls like www.twitter.com/login should be registered before that one.
routes.MapRoute(
"Login",
"Login",
new { controller = "Security", action = "Login" }
);
The important thing to understand is that the routes are matched in the order they are registered. So you would need to register the most specific route first, and the most general last, or all requests matching the general route would never reach the more specific route.
For your problem i would register routing rules for each of the special pages, like "register" and "login" before the username rule.
You could handle that in the home controller, but the controller method would not be very elegant. I'm guessing something like this might work (not tested):
routes.MapRoute(
"Root",
"{controller}/{view}",
new { controller = "Home", action = "Index", view = "" }
);
Then in your HomeController:
public ActionResult Index(string view) {
switch (view) {
case "":
return View();
case "register":
return View("Register");
default:
// load user profile view
}
}
OK I haven't ever properly tried this, but have you tried to extend the RouteBase class for dealing with users. The docs for RouteBase suggest that the method GetRouteData should return null if it doesn't match the current request. You could use this to check that the request matches one of the usernames you have.
You can add a RouteBase subclass using:
routes.Add(new UserRouteBase());
When you register the routes.
Might be worth investigating.
i think your question is similar to mine. ASP.NET MVC Routing
this is what robert harvey answered.
routes.MapRoute( _
"SearchRoute", _
"{id}", _
New With {.controller = "User", .action = "Profile", .id = ""} _
)
Here is an alternative way to standar route registration:
1. Download RiaLibrary.Web.dll and reference it in your ASP.NET MVC website project
2. Decoreate controller methods with the [Url] Attributes:
public SiteController : Controller
{
[Url("")]
public ActionResult Home()
{
return View();
}
[Url("about")]
public ActionResult AboutUs()
{
return View();
}
[Url("store/{?category}")]
public ActionResult Products(string category = null)
{
return View();
}
}
BTW, '?' sign in '{?category}' parameter means that it's optional. You won't need to specify this explicitly in route defaults, which is equals to this:
routes.MapRoute("Store", "store/{category}",
new { controller = "Store", action = "Home", category = UrlParameter.Optional });
3. Update Global.asax.cs file
public class MvcApplication : System.Web.HttpApplication
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoutes(); // This do the trick
}
protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
}
}
How to set defaults and constraints? Example:
public SiteController : Controller
{
[Url("admin/articles/edit/{id}", Constraints = #"id=\d+")]
public ActionResult ArticlesEdit(int id)
{
return View();
}
[Url("articles/{category}/{date}_{title}", Constraints =
"date=(19|20)\d\d-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])")]
public ActionResult Article(string category, DateTime date, string title)
{
return View();
}
}
How to set ordering? Example:
[Url("forums/{?category}", Order = 2)]
public ActionResult Threads(string category)
{
return View();
}
[Url("forums/new", Order = 1)]
public ActionResult NewThread()
{
return View();
}