MVC 5 Web Api - Inheriting route from base controller - asp.net-mvc

Is it "legal" to have a controller inherit a route from its BaseController ? It seems it's not allowed for Attribute Routing , but how about normal route registration via RouteCollection?
The reason is I currently have a bunch of controllers, each representing some kind of file converter. Each of them has a common set of methods to upload the file to be converted. These method are endpoints on each controller not just private methods. I'd like for the following routes to be valid:
/api/controller1/uploadfile
/api/controller2/uploadfile
/api/controller3/uploadfile
Can I get an example how this could be done inside a BaseController and if it's not possible, an alternative.

Here's what works:
public abstract class BaseUploaderController : ApiController
{
[HttpGet, Route("uploadfile")] //Needs both because HttpGet("uploadfile") currently only supported in MVC attribute routing
public string UploadFile()
{
return "UploadFile";
}
}
[RoutePrefix("api/values")]
public class ValuesController : BaseUploaderController
{
[Route("{id:int}")]
public string Get(int id)
{
return "value";
}
}

Are you looking to place this UploadFile action in the base controller and other controllers inheriting from them should still be able to hit UploadFile from their respective routes like you mentioned in your post? If yes, you could create an abstract base api controller and place this UploadFile action in it and your requests to the individual controllers should work as expected.
Example:
public abstract class BaseApiController : ApiController
{
// POST /api/Values
// POST /api/Test
public string UploadFile()
{
return "UploadFile";
}
}
public class TestController : BaseApiController
{
// GET /api/test/10
public string GetSingle(int id)
{
return "Test.GetSingle";
}
}
public class ValuesController : BaseApiController
{
// GET /api/values/10
public string GetSingle(int id)
{
return "Values.GetSingle";
}
}

As per this answer https://stackoverflow.com/a/21610390/122507 attribute routes are not inherited.
I am currently debating between introducing unnecessary method in 30 controllers just so I can add an attribute route or add a fake parameter to the base class method to let the default routing disambiguate between Get(int id) and GetHistory(int id, bool history) where I don't need the second parameter.

Related

MVC Routes data not available

I have a controller called BaseController. In the BaseController, I have an Action method called Index which has some logic that involves querying the routes and building the URLs. Something on the lines of:
var link = Url.RouteUrl("myroute", new { id = 5 });
All this is well and fine until I create a controller NewController that extends the BaseController. In the constructor of NewController, I pass BaseController as a dependency.
public class NewController
{
private BaseController _baseController;
public NewController(BaseController baseController)
{
_baseController = baseController;
}
public ActionResult Index()
{
return _baseController.Index();
}
}
Reason why this was needed was because I need to override the view (some HTML and CSS changes). I didn't want to recreate the models and services and rewrite the business logic, so thought this would be the best and most time-effective approach.
Only issue is when the BaseController's Index Action is called, the Url is null obviously. Routes data is not available because the request was generated outside the base controller.
What is the best way to get around this?
Make BaseController.Index() virtual:
public class BaseController : Controller
{
public virtual ActionResult Index()
{
return View();
}
}
Then use inheritance:
public class NewController : BaseController
{
public override ActionResult Index()
{
var index = base.Index();
//do whatever
return index;
}
}
You are trying to call action method from another controller. Propably your constructor method gets baseController as a null. can you try to implement it like following
public ActionResult Index()
{
return new BaseController().Index(); // assume you call index action
}
Or you can call BaseController action from another controller like following
public ActionResult Index()
{
return RedirectToAction("Index", "Base"); // assume you call index action
}
You can also change Route url like following.
#Url.RouteUrl("myroute", new { controller = "Base", action = "Index", id = 5 })
I have another solution that requires a little bit of code design efforts.
Why don't you Abstract your business logic away from the two Controllers?
For example: RouteBuilder.cs a class that have the functions that contains the logic of building the routes.
And BaseClass.cs is a class that contains the Logic shared between the two Controllers.
Then:
public class BaseController
{
public ActionResult Index()
{``
//Instantiase BaseClass.cs and call the needed functions. Then RouteBuilder.cs and call functions.
return View();
}
}
public class NewController
{
public ActionResult Index()
{``
//Instantiase BaseClass.cs and call the needed functions.
return View();
}
}
Viola. Problem solved and clean code produced.

action name with hyphens in asp.net mvc

I want to be able to call a page the in following way:
www.mydomain.com/articles/article-name-with-hyphens
I tries the following:
public class ArticlesController : Controller
{
[ActionName("article-name-with-hyphens")]
public ActionResult ArticleNameWithoutHyphens()
{
return View("~/Content/Views/Articles/ArticleNameWithoutHyphens.cshtml");
}
}
However, I receive this message:
The view '~/Content/Views/Articles/ArticleNameWithoutHyphens.cshtml'
or its master was not found or no view engine supports the searched
locations. The following locations were searched:
~/Content/Views/Articles/ArticleNameWithoutHyphens.cshtml
P.S. If I change the code to the regular way:
public class ArticlesController : Controller
{
public ActionResult ArticleNameWithoutHyphens()
{
return View("~/Content/Views/Articles/ArticleNameWithoutHyphens.cshtml");
}
}
And call:
www.mydomain.com/articles/ArticleNameWithoutHyphens
I get the desired page.
Where is my problem?
Did you try with Route attribute?
[RoutePrefix("Articles")]
public class ArticlesController : Controller
{
[Route("article-name-with-hyphens")]
public ActionResult ArticleNameWithoutHyphens()
{
return View("~/Content/Views/Articles/ArticleNameWithoutHyphens.cshtml");
}
}
Therefore you can query like -
localhost/MyApp/Articles/article-name-with-hyphens
This perfectly works with MVC5.

MVC Attribute Routing Controller issues

I have a URL https://localhost/api/Books/{value} using attribute routing , but whenever I pass {value} as null or empty string WEB API is not able to route it to the desired controller? How to go about this issue and resolve it.Error message details is : No action was found on the controller 'Books' that matches the request."
Assuming you've registered the default route, a request to...
http://localhost/api/books/34
will be handled by the followung controller/action without even using attribute routing...
public BooksController : ApiController
{
public IHttpActionResult Get(int id){...}
}
however, if you need attribute routing you decorate your action method with the following attribute...
[HttpGet, Route("api/books/{id}")]
public IHttpActionResult Get(int id){...}
You can make a URI parameter optional by adding a question mark to the route parameter. If a route parameter is optional, you must define a default value for the method parameter.
public class BooksController : ApiController
{
[Route("api/books/locale/{lcid:int?}")]
public IEnumerable<Book> GetBooksByLocale(int lcid = 1033) { ... }
}
Reference: http://www.asp.net/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2#optional
Or a better approach (if that works in your scenario) would be to have 2 different routes:
[Route("api/books/{id}")]
public BooksController : ApiController
{
public IHttpActionResult Get(int id){...}
}
[Route("api/books")]
public BooksController : ApiController
{
public IHttpActionResult Get(){...}
}

Inheriting from controller, overriding save method with different type

I have a project that has a 'core' version, and a 'customised' version.
They are separate projects.
'customised' inherits functionality from 'core' and in some case overrides methods.
For example:
I have a user model that looks like this:
public class User
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
Then, in a separate assembly,
public class User : Core.User
{
public string CustomProperty { get; set; }
}
I then have a controller (in my 'core' assembly)
public class UserController : Controller
{
[HttpPost]
public ActionResult SaveUser(User user)
{
}
}
In my other project, I have a UserController that inherits from Core.UserController:
public class UserController : Core.UserController
{
[HttpPost]
public ActionResult SaveUser(Custom.User user)
{
}
}
Obviously, in my Global.asax I have the controller namespaces mapped
However, when I hit the SaveUser method, I get
The current request for action SaveUser on controller type
UserController is ambiguous between the following action methods
While I understand the problem, is there any way around this?
In a nutshell:
I want to use Core.UserController methods most of the time, but in this instance, I need to use my Custom.UserController SaveUser method (since it takes my Custom.User type)
Polymorphism?
public class UserController : Controller
{
[HttpPost]
public virtual ActionResult SaveUser(User user)
{
}
}
public class UserController : Core.UserController
{
[HttpPost]
public override ActionResult SaveUser(User user)
{
var customUser = user as Custom.User;
if(customUser != null)
{
//Your code here ...
}
}
}
Another possible workaround if the polymorphism solution doesn't work or isn't acceptable, would be to either rename your UserController or its action method to something slightly different:
public class CustomUserController : Core.UserController
{
[HttpPost]
public ActionResult SaveUser(Custom.User user)
{
}
}
public class UserController : Core.UserController
{
[HttpPost]
public ActionResult SaveCustomUser(Custom.User user)
{
}
}
If you wanted to keep the routes consistent with the other project, you would just have to create a custom route for this.
I encountered the same problem in my own project today and came across your post.
In my case, while I didn't want to alter the way the core controller's logic functioned, I was able to make changes to its code, and thus its modifier keywords. After adding virtual to the base controller's actions, and override to my derived controller's actions. The original controller's actions still function, my derived controller uses my customized actions, no more ambiguous errors.
I realize you may not be able to modify your Core controller, and if this is the case, then you need to differentiate your actions using some other means. Action name, parameters or some other solution such as a custom implementation of ActionMethodSelectorAttribute. That was my first attempt at this problem, but before I got too far down that path of how to implement it, I discovered the virtual/override solution. So I don't have code to share on that route unfortunately.

Can I use method hiding on an ActionResult that has the same signature as the base controller?

I have 2 controllers, one inheriting the other. I need to override an ActionResult from the base controller because I need to change to code to implement pagination in the plugin for nopCommerce. However, I get an AmbiguousMatchException because of the new ActionResult.
Base Controller:
public class BaseController : Controller {
public ActionResult Category(int categoryId, CatalogPagingFilteringModel command)
{
//original action result code
}
}
Customer Controller w/ inheritance
public class CustomController : BaseController {
public new ActionResult Category(int categoryId, CatalogPagingFilteringModel command)
{
// my pagination code with different model/view
}
}
Route Info:
Here I remove the route for the base controller and add a new route to use the CustomCatalog controller.
routes.Remove(routes["OriginalCategory"]);
routes.MapLocalizedRoute(
"OriginalCategory",
"Category/{categoryId}/{SeName}",
new { controller = "CustomCatalog", action = "Category", SeName = UrlParameter.Optional },
new { categoryId = #"\d+" },
new[] { "Nop.Plugin.Common.Web.Controllers" });
I then get an AmbiguousMatchException
[AmbiguousMatchException: The current request for action 'Category' on
controller type 'CustomCatalogController' is ambiguous between the
following action methods: System.Web.Mvc.ActionResult Category(Int32,
Nop.Web.Models.Catalog.CatalogPagingFilteringModel) on type
Nop.Plugin.Common.Web.Controllers.CustomCatalogController
System.Web.Mvc.ActionResult Category(Int32,
Nop.Web.Models.Catalog.CatalogPagingFilteringModel) on type
Nop.Web.Controllers.CatalogController]
EDIT
The base controller resides in the core of the application where as the CustomController is in the plugin meaning that I cannot modify the base controller's type.
How about using virtual in the base controller and override in the derived controller instead of new?
base:
public virtual ActionResult Category(...) { }
derived:
public override ActionResult Category(...) { }
You cannot override a method which has not been declared virtual.
You can either define a new method with a different signature or you can encapsulate the functionality of the original class by maintaining a private reference to it in a wrapper class. It helps if you have an interface that you can implement from the base library (because this allows you to substitute the wrapper class where the base class that implements the same interface would be used), but you can do it without the interface, too.
// Base class source code is not modifiable
class BaseClass {
public ActionResult Category(...) {}
public ActionResult Other() {}
}
// Wrapper class can modify the behavior
class Wrapper {
private BaseClass baseClass = new BaseClass(); // Instantiate appropriately
public ActionResult Category(...) {
// do some stuff
}
public ActionResult Other() {
return baseClass.Other();
}
}

Resources