Multiple URLs same action method - asp.net

I need to share action methods between different controllers. Take for example the following 2 controllers:
public class AController : Controller
{
public ActionResult Index()
{
//print AController - Index
}
public ActionResult Test()
{
//print test
}
}
public class BController : Controller
{
public ActionResult Index()
{
//print BController - Index
}
}
Both controllers have an Index method which is different. The Test method however can be called from both controllers. So I want that when the following urls are entered the Test() method will execute:
AController/Test
BController/Test
I would appreciate any suggestions on how to achieve this.

Assuming the implementation of the Test() action is the same for both controllers, refactor it into a common service:
public interface ITestService {
string Test();
}
public TestService: ITestService {
public string Test() {
// common implementation
return "The test result";
}
}
Then set up Dependency Injection to acquire this service.
Your controllers then can use the common service.
public class AController : Controller {
private readonly ITestService _testService;
public AController(ITestService testservice) {
_testService = testservice;
}
public ActionResult Test() {
var vm = new TestViewModel();
vm.TestResult = _testService.Test();
return View("Test", vm);
}
}
public class BController : Controller {
private readonly ITestService _testService;
public BController(ITestService testservice) {
_testService = testservice;
}
public ActionResult Test() {
var vm = new TestViewModel();
vm.TestResult = _testService.Test();
return View("Test", vm);
}
}
Because the View Test.cshtml is rendered by both controllers, it should be placed in the Views\Shared\ folder.

You can define your own routes as described here: https://learn.microsoft.com/aspnet/core/mvc/controllers/routing
So you can define as many routes as you like to point to the "Test" method inside "AController" just like this:
routes.MapRoute("Atest", "AController/Test",
defaults: new { controller = "AController", action = "Test" });
routes.MapRoute("Btest", "BController/Test",
defaults: new { controller = "AController", action = "Test" });
But you have to define them before the "default" route because otherwise the entered URL will match the default route conditions and so it will enter that route.
It´s also possible to define the route directly in top of the method.
public class AController : Controller
{
[Route("/Some/Route")]
public ActionResult Test()
{
}
}

I want to throw in an alternative solution. Create a base controller class to be inherited by the other two. Whatever you have there will be part of the children.
public class BaseController : Controller
{
public ActionResult Index()
{
//print AController - Index
}
// Add more methods to be shared between the other controllers
}
public class AController : BaseController
{
// Has Index method already from parent
// Unique method for A
public ActionResult Test()
{
//print test 1
}
}
public class BController : BaseController
{
// Has Index method already from parent
// Unique method for B
public ActionResult Test()
{
//print test 2
}
}
This implements the actual functionality in a single place. We use this method for many projects with no issues.

Related

Unit Test derived Controller class using protected mediator in base controller in .NET Core 3.1

I am facing issue in mocking mediator that is defined in base class controller as protected property.
I would like to test method of derived class controller, which is using base class property.
Find code details for reference:
[ApiController]
public class BaseController : ControllerBase
{
private IMediator _mediator;
private IUrlHelper _urlHelper;
protected virtual IMediator Mediator => _mediator ?? (_mediator = HttpContext.RequestServices.GetService<IMediator>());
public BaseController()
{
}
}
[Route("api/[controller]")]
[ApiController]
public class ArchiveFileController : BaseController
{
public ArchiveFileController()
{
}
[HttpPost]
public async Task<ActionResult> Post([FromBody]UploadArchiveCommand model)
{
try
{
var response = await Mediator.Send(new UploadArchiveCommand(model));
return CreatedAtAction("Get", new { id = response.Id }, response);
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
}
}
UnitTest :
[Fact]
public async Task PostArchiveFileTest_Returns_CreatedArchiveFileInformation()
{
// Arrange
var mockUploadArchiveCommand = fixture.Create<UploadArchiveCommand>();
//Action
_mediatorMock.Setup(x => x.Send(It.IsAny<UploadArchiveCommand>(), new CancellationToken())).
ReturnsAsync(new UploadArchiveViewModel { Identifier = mockUploadArchiveCommand.Identifier, FileName = mockUploadArchiveCommand.FileName });
var _archiveFileController = new ArchiveFileController();
var result = await _archiveFileController.Post(mockUploadArchiveCommand);
var archiveFileInfo = ((CreatedAtActionResult)result).Value as UploadArchiveViewModel;
// Assert
Assert.Equal(archiveFileInfo.Identifier, mockUploadArchiveCommand.Identifier);
}
Here Mediator is coming as Null.
A couple things:
Don't create a base class just for common dependencies. A base class here does not add any value.
Use dependency injection, not service location, for dependencies.
Remove the base class, and just inject the dependencies in your controller class through its constructor. Then you'll be able to mock those dependencies in your unit test.

How to show endpoint documentation when my method inherits a base controller

I have multiple classes (more than 100) which inherit from my base class BaseController. All my classes are their logics and models but format's response (200, 404, 500, ...) are always the same.
But when I inherit from my base class, in my swagger documentation I see my endpoints but the details of the response are not there. How can I do this?
public class BaseController : Controller
{
public BaseController() {}
[Produces("application/json")]
[SwaggerResponse(StatusCodes.Status200OK)]
[SwaggerResponse(StatusCodes.Status404NotFound)]
protected async Task<IActionResult> Get(int id)
{
...
}
}
public class MyController : BaseController
{
[HttpGet("{id}")]
public async Task<IActionResult> Get(int id)
{
return await base.Get(id).ConfigureAwait(false);
}
}
As #Helder Sepulveda Said, this goes beyond swashbuckle.
I Think you can use IActionModelConvention to simulate inherit the action attributes.
Use Action.Filters like this
public class ActionMethodConvention : IActionModelConvention
{
public void Apply(ActionModel action)
{
var actonBaseResponses = new List<SwaggerResponseAttribute>();//some code to get baseAction reflections
foreach (var attr in actonBaseResponses)
{
action.Filters.Add(new Microsoft.AspNetCore.Mvc.ProducesResponseTypeAttribute(actonBaseResponses.StatusCode));
}
}

Is there a way to get the current controller instance in ASP.NET 5?

Is there a way to do this using DI? I tried IScopedInstance<Controller> but this gives me null. Poked around aspnet's source code but didn't win. Any ideas?
I have a controller that accepts different IPaymentMethods. The IPaymentMethod can be a ViewComponent that can render Views. If the IPaymentMethod is a ViewComponent, I want it to use MVC's built-in model binding on post back.
public class XController : Controller
{
// ctor, props, ...
public IActionResult Checkout()
{
return View(new Model
{
PaymentMethodId = 1,
PaymentMethodType = typeof(MyPaymentMethod) // The razor file will use this type to render it as a ViewComponent
});
}
[HttpPost]
public IActionResult Checkout(Model model)
{
var paymentMethod = _paymentService.GetPaymentMethodById(model.PaymentMethodId);
paymentMethod.ProcessPayment();
// ..
}
}
This is where I need the controller to be injected. I wanted to make use of the built-in MVC validation and model binding.
public class MyPaymentMethod : IPaymentMethod
{
private Controller _currentController;
public MyPaymentMethod(IScopedInstance<Controller> controller)
{
_currentController = controller.Value;
}
public void ProcessPayment()
{
var model = new PaymentModel();
_currentController.TryUpdateModel(model, typeof(PaymentModel), null);
if (!_currentController.ModelState.IsValid)
{
return; // or exception
}
// Process Payment using model
}
public Task<IViewComponentResult> InvokeAsync()
{
// returns View
}
}
public interface IPaymentMethod
{
void ProcessPayment();
}
Since the model instance is required in the ProcessPayment method, why not simply pass it as a parameter?
[HttpPost]
public IActionResult Checkout(PaymentModel model)
{
var paymentMethod = _paymentService.GetPaymentMethodById(model.PaymentMethodId);
if (!ModelState.IsValid)
{
return; // or exception
}
paymentMethod.ProcessPayment(model);
// ..
}
public void ProcessPayment(PaymentModel model)
{
// Process Payment using model
}
Your service is taking on responsibilities that belong to the controller - namely checking ModelState.IsValid.
public interface IPaymentMethod
{
void ProcessPayment(PaymentModel model);
}
You may wish to also pass just the properties that are needed from the payment model, or you may wish to make an IPaymentModel interface to decouple your model from your PaymentService. In that case, your IPaymentModel would go into a shared layer.
public interface IPaymentMethod
{
void ProcessPayment(IPaymentModel model);
}
This no longer works with beta7
At this time of writing (beta6), this probably isn't supported and there is a good reason for it: Controllers in ASP.NET 5 does not need to inherit from the Controller class. I have, however, found a way for this to work using ActionFilters.
public class ScopeControllerActionFilterAttribute : ActionFilterAttribute
{
private readonly IScopedInstance<Controller> _controller;
public ScopeControllerActionFilterAttribute(IScopedInstance<Controller> controller)
{
_controller = controller;
}
public override void OnActionExecuting(ActionExecutingContext context)
{
if (_controller.Value == null)
{
_controller.Value = context.Controller as Controller;
}
}
}
Note that depending on the stage of the http request lifecycle, the Value of IScopedInstance<Controller> may still be empty.

ASP.NET MVC 5 Common actions for more controllers

I have some controllers (and will be more) which share some actions like those:
public ActionResult DeleteConfirmed(int id)
{
Supplier s = db.Suppliers.Find(id);
s.Deleted = true;
db.SaveChanges();
return RedirectToAction("Index");
}
public ActionResult RestoreConfirmed(int id)
{
Supplier s = db.Suppliers.Find(id);
s.Deleted = false;
db.SaveChanges();
return RedirectToAction("Index");
}
Those action are part of SuppliersController. What this does is that when I delete or restore an object, it marks the object in the database as true for deleted field (and false when it is restored).
The same behavior is shared by many other controllers like CurrenciesController, ProductsController, etc...
In the code I showed you should see that my database entity is clearly specified (Supplier) and also the repository (Suppliers).
I want to find a way to this in a generic way. I want to create a custom controller and all other controllers that shares the same behavior will extended it. In this case ProductsController will extend my DeleteRestoreController.
How can I do this in a "generic" way?
db is a DbContext
public partial class LE: DbContext
{
public LE()
: base("name=LE")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public virtual DbSet<Category> Categories { get; set; }
public virtual DbSet<CategoryText> CategoryTexts { get; set; }
...
}
Categories also share the same behavior.
To go one step further
public abstract class DeleteRestoreController<T> : Controller
{
public virtual Action DeleteConfirmed(int id)
{
var dbset = db.Set<T>();
var s = dbset.Find(id);
s.Deleted = true;
db.SaveChanges();
return RedirectToAction("Index");
}
}
then when defining your controller add the entity type
public class ProductsController : DeleteRestoreController<Supplier>
{
////blah
}
You can implement your DeleteRestoreController as an abstract class.
public abstract class DeleteRestoreController : Controller
{
private IRepository : Repository;
public DeleteRestoreController() { ... }
public DeleteRestoreController(IRepository Repository) { ... }
public virtual Action DeleteConfirmed(int id)
{
Supplier s = db.Suppliers.Find(id);
s.Deleted = true;
db.SaveChanges();
return RedirectToAction("Index");
}
}
If you need to differ from that behaviour in your ProductsController you can simply override that method.
public class ProductsController : DeleteRestoreController
{
public override void DeleteConfirmed()
{
//override the logic
}
}
You could always go one step further and implement a generic repository as well, but I've never gone beyond 6-8 controllers in my applications and didn't create one once.
EDIT I've just read in the comments, that the entities would change from Suppliers in the controllers, so implementing a base controller wouldn't make much sense, if you do not implement a generic interface as well. Robert Harvey has made a great point in stating the complexity has to go somewhere.

How to get roles array from Authorize Attribute of certain action in ASP.NET MVC?

Suppose I have following Controller and action with authorization Attribute:
public class IndexController : Controller
{
//
// GET: /Index/
[Authorize(Roles="Registered")]
public ActionResult Index()
{
return View();
}
}
I've searched over the entire Internet and not found an answer for this simple question: how to get the roles annotated to an especific Action/Controller? In this case: Index Action has: string[] = {"Registered"}
Finally I found the solution! Was more easy than I thought! ahahha I need extend a class from AuthorizeAttribute and use it in actions. The information I need is the attribute "Roles" of the inherited class:
public class CustomAuthorizationAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
var roles = this.Roles;
base.OnAuthorization(filterContext);
}
}
And on Index Controller:
public class IndexController : Controller
{
//
// GET: /Index/
[CustomAuthorizationAttribute(Roles = "Registered")]
public ActionResult Index()
{
return View();
}
}

Resources