How to share controller actions/view with other controllers - c#

I would like to call the MainController.GetData() action from a few different URLs without a bunch of copied/pasted code. Is there a preferred way people tackle this? Seems like I might be able to do it with routing as well. I would just like to reuse the view and action since all that code would be the same if I made a version for the GetMyData() action.
**Example urls**
*/main/getdata
/other/getmydata
/diffferent/getothersdata?userid=3
public ActionResult MainController::GetData()
{
var data = GetData();
return View(collection);
}
public ActionResult OtherController::GetMyData()
{
var userId = GetCurrentUserId();
var data = GetData(userId);
return View("../main/getdata", collection);
}

Although controllers look like simple classes, their behavior inside the MVC framework is more specialized. You can't, or perhaps it's better to say you shouldn't, just call an action from one in an action for another, as if it's just any old method. Mostly, this is because controllers have context, and you have to instantiate them and set them up just right so that everything works. That's non-trivial to do inside of an action method and it's going to make your code ugly as hell.
You have two choices really. If you just want the result of the action, the best method is to utilize HttpClient and actually submit an HTTP request for it, just like any other request that would activate it.
However, based on your problem description, option two is probably more appropriate. You can create a base class and then inherit from that. For example, if you know two controllers are both going to need GetData, you can do something like:
public abstract class BaseController : Controller
{
protected IEnumerable<Data> QueryData(int? userId = null)
{
...
}
}
public class MainController : BaseController
{
public ActionResult GetData()
{
var data = QueryData();
return View(data);
}
}
public class OtherController : BaseController
{
public ActionResult GetMyData()
{
var userId = GetCurrentUserId();
var data = QueryData(userId);
return View(data);
}
}
In other words, you factor out the common functionality into something both the actions on both derived controllers can use.
This is all you needed for this scenario, but you can also implement entire actions on the base controller. For example, if you added a Foo action to BaseController, both MainController and OtherController would then responds to requests for Foo, without having to actually explicitly define that. You can then also override these actions if you need to, as well.

Related

One controller for different views MVC

I would like to know if it is best to use One controller for multiple views that load approximately the same data or use one controller for every view.
And if it is possible to use the controller like this :
[Route("api/[controller]")]
public class MultiplePagesController : Controller
{
[HttpGet]
public async Task<IActionResult> GetA()
{ ... return viewA }
[HttpGet]
public async Task<IActionResult> GetB()
{ ... return viewB }
}
And in my serviceViewA.ts :
getA() {
return this.http.get<InterfaceA>(`${this.baseUrl}/api/multiplepages/`);
}
My serviceViewB.ts :
getB() {
return this.http.get<InterfaceB>(`${this.baseUrl}/api/multiplepages/`);
}
Since let's say the viewA returns data of books and the viewB returns data of books and computers.
I am using MVC .NET CORE
It's in fact a great idea to use one controller (and method) if it does exactly the same thing, you reduce code this way, makes it easy to maintain and modify in just one place rather than all over the project. Think of it as a container for your functions. If you have a Book controller, you put all methods related to books there, same for User, you would put all user methods there.
As for your second question, that goes against polymorphism. Your function signatures are exactly the same, how will it be distinguished? You'll need to give it a different name or accept a parameter (different from the other function or different data type).
Worth noting that you can name your function differently to return views.
For example:
public async Task<IActionResult> GetViewA(){ // ... }
public async Task<IActionResult> GetViewB(){ // ... }
You'll call those views by entering: /Controller/GetViewB as your path.

Same code for each mvc actionresult in home controller

So I have some generic actionresults that link to various views for the time being. The layout page contains a call to adfs to populate a logged in user name that has to be for each page. Looks like this:
<div class="float-right">
<section id="login">
Hello, <span class="username">#ViewBag.GivenName #ViewBag.LastName</span>!
</section>
</div>
In the home controller, what makes this logged in name work is this code here:
public ActionResult Index()
{
ClaimsIdentity claimsIdentity = Thread.CurrentPrincipal.Identity as ClaimsIdentity;
Claim claimGivenName = claimsIdentity.FindFirst("http://sts.msft.net/user/FirstName");
Claim claimLastName = claimsIdentity.FindFirst("http://sts.msft.net/user/LastName");
if (claimGivenName == null || claimLastName == null)
{
ViewBag.GivenName = "#FAIL";
}
else
{
ViewBag.GivenName = claimGivenName.Value;
ViewBag.LastName = claimLastName.Value;
}
return View();
}
But as mentioned earlier, I need this to display when a user goes to each link (actionresult). Therefore, I am having to post all the code above into each actionresult in order to achieve this.
Is there some way I can have this apply to each actionresult as a whole rather than having to duplicate code from one action to another? I did try just to register into an actionresult for my _Layout.cshtml and make the call to that partialview, but that didn't give me favorable results. I am sure it is something simple that I am missing.
Hoping some of you can help. Thanks much.
We use an abstract controller and override its OnActionExecuting method to execute code before the actual action method gets invoked. With this abstract controller, all you have to do is make any other controllers inherit from it to gain it's functionality. We also use this base controller as a place to define other helper methods that other controllers which extend it can use, such as GetUsernameForAuthenticatedUser().
public abstract class AbstractAuthenticationController : Controller
{
private readonly IAuthenticationService _authService;
protected AbstractAuthenticationController()
{
_authService = AuthenticationServiceFactory.Create();
}
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
EnsureUserIsAuthenticated();
}
internal void EnsureUserIsAuthenticated()
{
if (!_authService.IsUserAuthenticated())
{
_authService.Login();
}
}
protected string GetUsernameForAuthenticatedUser()
{
var identityName = System.Web.HttpContext.Current.User.Identity.Name;
var username = _authService.GetUsername(identityName);
if (username == null) throw new UsernameNotFoundException("No Username for " + identityName);
return username;
}
}
This functionality could also be implemented in an Attribute class which allows you to decorate your controllers as opposed to using inheritance but the end result is the same. Here is an example of a custom controller attribute implementation.
You could create a base controller and make all the controllers inherit from it. Move the code that sets the given and last names into a separate, protected method and call it whenever you need. I think you could call the function in the Initialize method of the base controller. This way you won't need to call it directly into the actions.
You could also make a hierarchy of models and have GivenName and LastName as properties on the base model, instead of working with the ViewBag.
Another alternative to using OnActionExecuting as this is just for a set part of the template would be to give it its own action method that returns a partial and call #Html.Action()

ASP.NET MVC, 'Ticket Required' Attribute

I am attempting to build a system that allows users to perform certain actions, but their account must have a specific 'Ticket' per time they do it. For instance, suppose they wish to create a Product, they would need a CreateProductTicket.
I could simply do this with some 'if' statements, sure, but I want to try a bit more of a robust solution. My structure looks something like this...
interface ITicket<T> where T : ITicketable
{
}
My basic goal is to build an Attribute, perhaps like the following..
public class TicketRequiredAttribute : Attribute
{
public TicketRequiredAttribute(ITicket<T> ticket)
{
if(ticket == null)
return;
}
}
And to be able to decorate Controller or Repository Actions with this. So like ...
ProductsControlller
[TicketRequired(CreateProductTicket)]
public ActionResult CreateProduct(Product product)
{
// ... **I am unsure how to tell if TicketRequired was true or not**
}
Problem 1
I'm not familiar enough with attributes to know how to tell if TicketRequired was 'met' or not. Can anyone enlighten me on this?
Problem 2
The problem I am running into is with database querying. I want to be able to check the user (IMembershipRepository has a GetUser method), but I'm not entirely certain how to do that through an attribute.
Using Castle.Windsor, I have my Dependency Injection set up to inject repositories into controllers. I suppose I could pass the IMembershipRepository through the TicketRequired constructor, but I have a feeling that will become very messy - and extremely unstable. Is there a more logical way to approach this?
You're almost there. You can find more details at http://www.asp.net/mvc/tutorials/understanding-action-filters-cs
I would only use the attribute on the action since the website is where I do all my authorization.
Here is a possible solution. I have not tested this, but it should work. You'll need to verify the way I'm redirecting, not sure if that's the proper way.
public class TicketRequiredActionFilter : ActionFilterAttribute
{
private Type _ticketType;
public TicketRequiredAttribute(Type ticketType)
{
_ticketRequired = ticketType;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
UserServices userServices = GetUserServicesViaDIContainer(); // you'll need to figure out how to implement this
string userId = filterContext.HttpContext.User.Identity.Name
bool hasTicket = userServices.HasTicket(_ticketType, (int)userId); // again, you'll need to figure out the exact implementation
if(!hasTicket)
{
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary { { "controller", "Home" }, {"action", "NoPermission" } })
}
else
{
base.OnActionExecuting(filterContext);
}
}
}
In your controller:
[TicketRequiredActionFilter(typeof(CreateProductTicket))]
public ActionResult MyMethod()
{
// do stuff as if the person is authorized and has the ticket
}
If the user doesn't have the ticket, a redirect is issues;, otherwise, continue as normal.
This sounds very much like user roles.
How are you handling the user membership? If your using the built-in asp.net membership you can use roles. So each user will have a certain number of roles in your case one of the will be "CreateProductTicket" then you can decorate your action or controller with the Authorize attribute. Something like:
[Authorize(Roles="CreateProductTicket")]
public ActionResult CreateProduct(Product product)
If a user doesn't have the role or is not authorized then they can access the action.

In .Net MVC, how do you access variables from URL Routing outside of the Action?

I'm building a controller that other controllers can inherit (provide base functionality across site without repeating code):
public abstract class ApplicationController : Controller
{
protected ApplicationController()
{
//site logic goes here
//what is the value of agentID from the Action below??
}
}
public class AgentController : ApplicationController
{
public ActionResult Index(string agentID)
{
return View();
}
}
The logic that applies to the entire site will go into the constructor of the ApplicationController class.
The problem is in that constructor I need to access the value in the parameter from the Action, in this case agentID (it will be the same across the entire site). Is there a way to read that value in?
Actions happen after constructors. The value doesn't exist (hasn't been bound) in a constructor. The route data might be known in the constructor, but the action data binding certainly will not have happened yet. You can't get this value with certainty until the action has been invoked.
Route data can be accessed inside the controller via:
ControllerContext.RouteData.Values
However, it is incorrect to suppose that agentID can only be bound to route data. In fact, it could come from a server variable, a form field, a query string parameter, etc. My advice would be to pass it explicitly wherever it is needed. If nothing else, it makes your unit tests better.
I figured out how to do it ... very similar to Craig Stuntz's answer, but the difference is in how you reach the RouteData.
Using ControllerContext.RouteData.Values does not work in a regular method used this way (it does from the original controller, but not from a base one like I built), but I did get to the RouteData by overriding the OnActionExecuting method:
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
string agentID = filterContext.RouteData.Values["agentID"].ToString();
OtherMethodCall(agentID);
}
There is, though you need to override the ControllerFactory. In your RegisterRoutes method in global.asax.cs, add this line:
public static void RegisterRoutes(RouteCollection routes) {
// Route code goes here
ControllerBuilder.Current.SetControllerFactory(typeof(MyControllerFactory));
}
And then define your MyControllerFactory class
public class MyControllerFactory : DefaultControllerFactory {
public override IController CreateController(RequestContext requestContext, string controllerName) {
// poke around the requestContext object here
return base.CreateController(requestContext, controllerName);
}
}
The requestContext object has all of the route data and values in it. You can use this to pass whatever you would like to the constructor of your controller.
Edit to add that this is how most of the popular Dependency Injectors (for example, StructureMap) work too.

Can you overload controller methods in ASP.NET MVC?

I'm curious to see if you can overload controller methods in ASP.NET MVC. Whenever I try, I get the error below. The two methods accept different arguments. Is this something that cannot be done?
The current request for action 'MyMethod' on controller type 'MyController' is ambiguous between the following action methods:
You can use the attribute if you want your code to do overloading.
[ActionName("MyOverloadedName")]
But, you'll have to use a different action name for the same http method (as others have said). So it's just semantics at that point. Would you rather have the name in your code or your attribute?
Phil has an article related to this: http://haacked.com/archive/2008/08/29/how-a-method-becomes-an-action.aspx
Yes. I've been able to do this by setting the HttpGet/HttpPost (or equivalent AcceptVerbs attribute) for each controller method to something distinct, i.e., HttpGet or HttpPost, but not both. That way it can tell based on the type of request which method to use.
[HttpGet]
public ActionResult Show()
{
...
}
[HttpPost]
public ActionResult Show( string userName )
{
...
}
One suggestion I have is that, for a case like this, would be to have a private implementation that both of your public Action methods rely on to avoid duplicating code.
Here's something else you could do... you want a method that is able to have a parameter and not.
Why not try this...
public ActionResult Show( string username = null )
{
...
}
This has worked for me... and in this one method, you can actually test to see if you have the incoming parameter.
Updated to remove the invalid nullable syntax on string and use a default parameter value.
No,No and No. Go and try the controller code below where we have the "LoadCustomer" overloaded.
public class CustomerController : Controller
{
//
// GET: /Customer/
public ActionResult LoadCustomer()
{
return Content("LoadCustomer");
}
public ActionResult LoadCustomer(string str)
{
return Content("LoadCustomer with a string");
}
}
If you try to invoke the "LoadCustomer" action you will get error as shown in the below figure.
Polymorphism is a part of C# programming while HTTP is a protocol. HTTP does not understand polymorphism. HTTP works on the concept's or URL and URL can only have unique name's. So HTTP does not implement polymorphism.
In order to fix the same we need to use "ActionName" attribute.
public class CustomerController : Controller
{
//
// GET: /Customer/
public ActionResult LoadCustomer()
{
return Content("LoadCustomer");
}
[ActionName("LoadCustomerbyName")]
public ActionResult LoadCustomer(string str)
{
return Content("LoadCustomer with a string");
}
}
So now if you make a call to URL "Customer/LoadCustomer" the "LoadCustomer" action will be invoked and with URL structure "Customer/LoadCustomerByName" the "LoadCustomer(string str)" will be invoked.
The above answer i have taken from this codeproject article --> MVC Action overloading
To overcome this problem you can write an ActionMethodSelectorAttribute that examines the MethodInfo for each action and compares it to the posted Form values and then rejects any method for which the form values don't match (excluding the button name, of course).
Here's an example:- http://blog.abodit.com/2010/02/asp-net-mvc-ambiguous-match/
BUT, this isn't a good idea.
As far as I know you can only have the same method when using different http methods.
i.e.
[AcceptVerbs("GET")]
public ActionResult MyAction()
{
}
[AcceptVerbs("POST")]
public ActionResult MyAction(FormResult fm)
{
}
I have achieved this with the help of Attribute Routing in MVC5. Admittedly I am new to MVC coming from a decade of web development using WebForms, but the following has worked for me. Unlike the accepted answer this allows all the overloaded actions to be rendered by the same view file.
First enable Attribute Routing in App_Start/RouteConfig.cs.
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 }
);
}
}
Optionally decorate your controller class with a default route prefix.
[RoutePrefix("Returns")]
public class ReturnsController : BaseController
{
//.......
Then decorate your controller actions that overload each other with a common route and parameters to suit. Using type constrained parameters you can use the same URI format with IDs of different types.
[HttpGet]
// Returns
public ActionResult Index()
{
//.....
}
[HttpGet]
[Route("View")]
// Returns/View
public ActionResult View()
{
// I wouldn't really do this but it proves the concept.
int id = 7026;
return View(id);
}
[HttpGet]
[Route("View/{id:int}")]
// Returns/View/7003
public ActionResult View(int id)
{
//.....
}
[HttpGet]
[Route("View/{id:Guid}")]
// Returns/View/99300046-0ba4-47db-81bf-ba6e3ac3cf01
public ActionResult View(Guid id)
{
//.....
}
Hope this helps and is not leading somebody down the wrong path. :-)
You could use a single ActionResult to deal with both Post and Get:
public ActionResult Example() {
if (Request.HttpMethod.ToUpperInvariant() == "GET") {
// GET
}
else if (Request.HttpMethod.ToUpperInvariant() == "POST") {
// Post
}
}
Useful if your Get and Post methods have matching signatures.
I've just come across this question and, even though it's quite old now, it's still very relevant. Ironically, the one correct comment in this thread was posted by a self-confessed beginner in MVC when he wrote the post. Even the ASP.NET docs are not entirely correct. I have a large project and I successfully overload action methods.
If one understands routing, beyond the simple {controller}/{action}/{id} default route pattern, it might be obvious that controller actions can be mapped using any unique pattern. Someone here talked about polymorphism and said: "HTTP does not understand polymorphism", but routing has nothing to do with HTTP. It is, simply put, a mechanism for string pattern matching.
The best way to make this work is to use the routing attributes, for example:
[RoutePrefix("cars/{country:length(3)}")]
public class CarHireController
{
[Route("{location}/{page:int=1}", Name = "CarHireLocation")]
public ActionResult Index(string country, string location, int page)
{
return Index(country, location, null, page);
}
[Route("{location}/{subLocation}/{page:int=1}", Name = "CarHireSubLocation")]
public ActionResult Index(string country, string location, string subLocation, int page)
{
//The main work goes here
}
}
These actions will take care of urls like /cars/usa/new-york and /cars/usa/texas/dallas, which will map to the first and second Index actions respectively.
Examining this example controller it's evident that it goes beyond the default route pattern mentioned above. The default works well if your url structure exactly matches your code naming conventions, but this is not always the case. Code should be descriptive of the domain, but urls often need to go further because their content should be based on other criteria, such as SEO requirements.
The benefit of the default routing pattern is that it automatically creates unique routes. This is enforced by the compiler since urls will match unique controller types and members. Rolling your own route patterns will require careful thought to ensure uniqueness and that they work.
Important note The one drawback is that using routing to generate urls for overloaded actions does not work when based on an action name, e.g., when using UrlHelper.Action. But it does work if one uses named routes, e.g., UrlHelper.RouteUrl. And using named routes is, according to well respected sources, the way to go anyhow (http://haacked.com/archive/2010/11/21/named-routes-to-the-rescue.aspx/).
Good luck!
You can use [ActionName("NewActionName")] to use the same method with a different name:
public class HomeController : Controller
{
public ActionResult GetEmpName()
{
return Content("This is the test Message");
}
[ActionName("GetEmpWithCode")]
public ActionResult GetEmpName(string EmpCode)
{
return Content("This is the test Messagewith Overloaded");
}
}
I needed an overload for:
public ActionResult Index(string i);
public ActionResult Index(int groupId, int itemId);
There were few enough arguments where I ended up doing this:
public ActionResult Index(string i, int? groupId, int? itemId)
{
if (!string.IsNullOrWhitespace(i))
{
// parse i for the id
}
else if (groupId.HasValue && itemId.HasValue)
{
// use groupId and itemId for the id
}
}
It's not a perfect solution, especially if you have a lot of arguments, but it works well for me.
I have faced same issue in my application too. Without Modifiyig any Method information, I have provided [ActionName("SomeMeaningfulName")] on Action head. issue resolved
[ActionName("_EmployeeDetailsByModel")]
public PartialViewResult _EmployeeDetails(Employee model)
{
// Some Operation
return PartialView(model);
}
}
[ActionName("_EmployeeDetailsByModelWithPagination")]
public PartialViewResult _EmployeeDetails(Employee model,int Page,int PageSize)
{
// Some Operation
return PartialView(model);
}
Create the base method as virtual
public virtual ActionResult Index()
Create the overridden method as override
public override ActionResult Index()
Edit: This obviously applies only if the override method is in a derived class which appears not to have been the OP's intention.
I like this answer posted in another thread
This is mainly used if you inherit from another controller and want to override an acction from the base controller
ASP.NET MVC - Overriding an action with differing parameters
There is only one public signature allowed for each controller method. If you try to overload it, it will compile, but you're getting the run-time error you've experienced.
If you're not willing to use different verbs (like the [HttpGet] and [HttpPost] attributes) to differentiate overloaded methods (which will work), or change the routing, then what remains is that you can either provide another method with a different name, or you can dispatch inside of the existing method. Here's how I did it:
I once came into a situation where I had to maintain backwards compatibility. The original method expected two parameters, but the new one had only one. Overloading the way I expected did not work because MVC didn't find the entry point any more.
To solve that, I did the following:
Changed the 2 overloaded action methods from public to private
Created one new public method which contained "just" 2 string parameters. That one acted as a dispatcher, i.e.:
public ActionResult DoSomething(string param1, string param2)
{
if (string.IsNullOrEmpty(param2))
{
return DoSomething(ProductName: param1);
}
else
{
int oldId = int.Parse(param1);
return DoSomething(OldParam: param1, OldId: oldId);
}
}
private ActionResult DoSomething(string OldParam, int OldId)
{
// some code here
return Json(result);
}
private ActionResult DoSomething(string ProductName)
{
// some code here
return Json(result);
}
Of course, this is a hack and should be refactored later. But for the time being, it worked for me.
You can also create a dispatcher like:
public ActionResult DoSomething(string action, string param1, string param2)
{
switch (action)
{
case "update":
return UpdateAction(param1, param2);
case "remove":
return DeleteAction(param1);
}
}
You can see, that UpdateAction needs 2 parameters, while DeleteAction just needs one.
Sorry for the delay. I was with the same problem and I found a link with good answers, could that will help new guys
All credits for BinaryIntellect web site and the authors
Basically, there are four situations: using differents verbs, using routing, overload marking with [NoAction] attribute and change the action attribute name with [ActionName]
So, depends that's your requiriments and your situation.
Howsoever, follow the link:
Link:
http://www.binaryintellect.net/articles/8f9d9a8f-7abf-4df6-be8a-9895882ab562.aspx
This answer for those who struggling with the same issue. You can
implement your own custom filter based on
ActionMethodSelectorAttribute. Here I found the best solution
for solving your question. Works fine on .net 5 project.
If you try to implement the same logic as was in web api controllers then use Microsoft.AspNetCore.Mvc.WebApiCompatShim. This nuget package provides compatibility in ASP.NET Core MVC with ASP.NET Web API 2 to simplify migration of existing Web API implementations. Please check this answer but consider that
starting with ASP.NET Core 3.0, the Microsoft.AspNetCore.Mvc.WebApiCompatShim package is no longer available.
If this is an attempt to use one GET action for several views that POST to several actions with different models, then try add a GET action for each POST action that redirects to the first GET to prevent 404 on refresh.
Long shot but common scenario.

Categories

Resources