How to put all my Controllers in a own classlibrary - asp.net-mvc

Can I configure MVC to directly look into a class library ("MyApp.Controllers" for example) instead of the foder Controllers?

Yes, you can. The easiest way is to add the namespace of your class library to the routers.MapRoute call in global.asax. This is how one of our configurations look like:
public static void RegisterRoutes(RouteCollection routes, IEnumerable<string> controllerNamespaces)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.IgnoreRoute("{resource}.ashx/{*pathInfo}");
routes.IgnoreRoute("{*favicon}", new { favicon = #"(.*/)?favicon.ico(/.*)?" });
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "" }, // Parameter defaults
controllerNamespaces.ToArray()
);
}
Notice that we use an overload of MapRoute that allows us to supply a string array of controller namespaces.
Another option is to implement a custom IControllerFactory, but that's more work and usually not necessary.

The ASP.NET MVC runtime knows nothing about a folder named 'Controllers', that is just a project structure convention. You can have your controllers on any assembly, on any namespace, and it should work without any special configuration.

Related

Attribute Routing not working in areas

Scenario: I have a Forms area in my ASP.NET MVC 5 site.
I'm trying to redirect to the Details Action which uses a custom route defined using the new Attribute Routing feature.
The RedirectToAction:
return RedirectToAction("Details", new { slug });
The action I'm redirecting to:
[HttpGet]
[Route("forms/{slug}")]
public ActionResult Details(string slug)
{
var form = FormRepository.Get(slug);
...
return View(model);
}
I would expect a redirect to http://localhost/forms/my-slug but instead the app is redirecting me to http://localhost/Forms/Details?slug=my-slug.
This means that the attribute routing is not working.
How can this be solved?
I have added the routes.MapMvcAttributeRoutes(); line to my RouteConfig:
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapMvcAttributeRoutes();
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
And here's my Application_Start():
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
You are probably combining convention based routing with attribute routing, and you should register your areas after you map the attribute routes.
The line
AreaRegistration.RegisterAllAreas();
should be called AFTER this line:
routes.MapMvcAttributeRoutes();
The explanation (from https://devblogs.microsoft.com/aspnet/attribute-routing-in-asp-net-mvc-5/):
If you are using both Areas with route attributes, and areas with convention based routes (set by an AreaRegistration class), then you need to make sure that area registration happen after MVC attribute routes are configured, however before the default convention-based route is set. The reason is that route registration should be ordered from the most specific (attributes) through more general (area registration) to the mist generic (the default route) to avoid generic routes from “hiding” more specific routes by matching incoming requests too early in the pipeline.
When you create a blank asp.net mvc website, add an area and start using attribute routing, you will encounter this problem because the "Add Area" action in visual studio adds the RegisterAllAreas call in your Application_Start, before the route configuration..
Alternative solution
Perhaps you do not intend to keep using convention based routing and prefer to only use attribute routing.
In this case you can just delete the FormsAreaRegistration.cs file.
Moving the AreaRegistration.RegisterAllAreas() to RouteConfig.cs wasn't enough for me. I also needed to use the AreaPrefix parameter for the RouteArea attibute:
//Use the named parameter "AreaPrefix"
[RouteArea("AreaName", AreaPrefix = "area-name-in-url")]
[RoutePrefix("controller-name-in-url")]
public class SampleController : Controller
{
[Route("{actionParameter}")]
public ActionResult Index(string actionParameter)
{
return View();
}
}
Edit: At some point, I came across a sample solution from Microsoft that nicely showed how to handle attribute routing. It also showed some nice examples of how to translate a SelectList into an array of input[type="radio"] items as well as doing the same with an array of input[type="checkbox"] items (if I recall). This sample solution is probably a better answer to this question--as well as giving some good examples on displaying radio buttons and checkbox items. If anyone knows of this sample solution, please add a comment with a link to it.

Can't access (404) ASPX from a View directory while HTML and (normal) ASPX are accessible

I can access Ping.HTML and Ping.ASPX but when I try to access the view from my MVC (4.0) project (deployed to the same server, the bogus one, by F5), I get 404.
It's a vanilla project created from the template for MVC 4 with a very default view and controller (no model).
Hints on how to resolved it? I'm out of ideas...
EDIT
My RouteConfig.cs is like this
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 }
);
}
}
Controllers folder contains only one, single file called ÄDefault1Controller.cs*. It only does this:
public class Default1Controller : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult Test()
{
return View();
}
}
EDIT
The exact URLs typed in (besides the server name alone, of course) are:
> http://localhost:49642/Index
> http://localhost:49642/Index.aspx
> http://localhost:49642/Home/Index
> http://localhost:49642/Home/Index.aspx
> http://localhost:49642/Default/Index
> http://localhost:49642/Default/Index.aspx
Based on the information you've given, it sounds like a routing problem. The URL you are requesting isn't firing a controller.
EDIT
MVC works by convention, so by naming your controller Default1Controller the matching URL would start with /Default1.
In the example you've given, you can only access the Test() method by navigating to http://localhost:49642/Default1/Test, which will return the view typically located at /Views/Default1/Test.aspx (or /Views/Default1/Test.cshtml for razor-based views).
Please check out the routing overview at ASP.NET for more information about how the route table maps to controllers and actions. I should point out that the link is for the older versions of MVC, but you should get the idea.
Let me know if I can help further.
Matt

Longer Route for ActionResult Method

Is it possible in MVC 4 to give a method a longer route than just /{controller}/{method}?
That is, how could I have a method "test", and have it's path be /{controller}/api/test?
You can register additional routes by modifying "RegisterRoutes()" in your project's Global.asax.cs file.
For example:
public static void RegisterRoutes(RouteCollection routes)
{
routes.MapRoute(
"YourNewRoute", // Route name
"{controller}/api/{action}", // URL with parameters
new { controller = "YourController", action = "Test" } // Parameter defaults
);
}
Now, if you enter:
YourController/api/Test
(Note the presence of "api" in the path)
it will call:
controller = YourController
action = Test
You can find more information about routes in this tutorial.
Yes it is. Simply define the api/test route before the general /{controller}/{method} so that the api/test route kicks in before the general route.
NOTE: When you define the /api/test, you'll need to specify the default action method.
Also, are you using WebAPI?

Controller action "Index" not invoked with default setting

I have a ASP.NET web application project, I implemented a WebApi controller and now proceeding with a MVC controller.
I've added a new MVC controller (eg. "Test") and selected to add it with "empty read and write actions".
The controller is created with various CRUD methods, however the Index action is behaving unexpectedly.
By default the Index action does not take any arguments:
public ActionResult Index()
{
return View();
}
Now if I try to invoke this method using url "/Test/Index", the method is not being called.
But if I type url "/Test/Index/1" the Index action is run.
It looks to me that its related to routing so I checked the RouteConfig class which looks like:
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 }
);
}
Id is set to optional and my understanding is I should not be required to provide a parameter value in Index method?
I created another project and checked this behavior and it works fine.
So wondering what in this project making MVC controller behaving differently?
Is mixing WebApi controllers with MVC controllers having some side effect (making it a special case)?
Let me know if you need any information to answer this question.
Update:
I did modify the WebApiConfig for making API controller work as per my requirement, it looks like this:
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "{controller}/{action}",
defaults: new { controller = "iclock", action = "cdata" }
);
}
I could not understand why it affect the MVC controller route? I got it now with answers provided.
Update 2:
The problem in this case is I can not (or can I?) prefix api in web api route as a third party device is sending GET and POST request using a fixed url, like following:
GET /iclock/cdata?SN=xxxxxx&options=all&pushver=2.0.2&language=XX
GET /iclock/cdata?SN=xxxxxx&type=time
POST /iclock/cdata?SN=123456&table=ATTLOG&Stamp=2011-04-05T17:40:20
I achieved above request handling functionality making changes into the WebApiConfig route. Now need a front end UI for which I am developing a MVC controller with CRUD operation.
What should be ideal approach in this case?
It could be when you navigate to Test/Index it doesn't know if it should use the web route where there is an optional "id" or the api route where there is no "id". If possible I would prefix your Web API routes, such as "api/{controller}/{action}" as described in the MVC Web API template.
For your second example, applications that access the API would simple prefix the URLs with "api/". If they are using the URLs you specified and cannot be changed, then I think you may be stuck because when the request comes in there is still the problem of not knowing which route to use.
If the URLs can be changed, as mentioned above, simple prefix with "api/".
EDIT: Updated my answer based on the extra information/code-snippet provided.
EDIT: Updated again
Did you look in WebApiConfig instead of RouteConfig in App_Start
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
You have a conflict in your routing table. Your Web API routing table is now the same as your standard MVC routing table. Your Web API routing table (the one that is initialized with MapHttpRoute should be declared as: routeTemplate: "api/{controller}/{id}". Also, you shouldn't have modified the Web API route. It is not cast in stone that it must be api/{controller}/{id}, but there is a certain convention that you should follow. Try reseting your routes to be like the following and then try again:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
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 }
);
}
}
So, first reset to normal routing configuration and test. After that make changes slowly and follow the result.

Why do you need a route defined for Html.Action?

I have created an otherwise empty ASP.NET MVC 3 application with 2 controllers, HomeController and OtherController.
HomeController.cs looks like this:
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
}
Index.cshtml looks like this:
#Html.Action("Index", "Other")
And, of course, Othercontroller.cs:
public class OtherController : Controller
{
[ChildActionOnly]
public ActionResult Index()
{
return Content("OK!");
}
}
So far, so good. I run the app, and it tells me everything is OK!
Now, I take the default RegisterRoutes from Global.asax.cs:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
}
And I crumple it up, so that no routes match OtherController:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute("Default", "", new { controller = "Home", action = "Index" });
}
Now, when I run the page, it crashes with this error message:
System.InvalidOperationException: No route in the route table matches the supplied values.
Source Error:
Line 1: #Html.Action("Index", "Other")
I specified the controller name and action name in the call to .Action. No URLs are being generated, and no requests are being made. Why does routing even need to get involved?
I think that this blog post will help you understand a little more:
http://blogs.charteris.com/blogs/gopalk/archive/2009/01/20/how-does-asp-net-mvc-work.aspx.
Essentially, routing is involved in determining which controller to 'fire up' to handle the request and appropriate action to invoke based on the parameters that you sent and the MVCRouteHandler uses those to make a decision. Just because you tell it which controller in your action does not make it magically ignore the routing table, go straight to that controller class and bypass all the other MVC goodness that happens in the back-end. Remember, these #HTML.Action methods can take a whole lot of overloads which could influence which route in the routing table to use (think URL structure for instance).
The MVC paths are not to static content and as such, have to be parsed through the URLRoutingModule which uses the routing table to decide on what to do. Since you have no matching route - you get an error.
EDIT
In my diatribe, I did not actually address your final statement. You're right, no URL was generated, but a request to the application was generated. HTML.Action still will use routing to determine what controller, action, area, parameters to use. I think it be fair to say in simple terms that it's like generating an ActionLink and clicking it for you.
Routing got involved by using Html.Action on a controller. When the engine couldn't find the "Other" HtmlHelper with an action of "Index" it defaulted to executing that path which means passing through routing. No route matched so it threw an error.
InvalidOperationException The required virtual path data cannot be found.
http://msdn.microsoft.com/en-us/library/ee721266.aspx
The solution for me was as follows:
I had the following line that was giving me an error:
#{Html.RenderAction("ActionName", "ControllerName");}
Adding a thrid parameter solved the error:
#{Html.RenderAction("ActionName", "ControllerName", new { area = string.Empty });}

Resources