Is Generic Controller Action Possible To Do? - c#

If so, how can I call it from my View?
The controller action that I have in mind is something like this:
public ActionResult UrunYonetimi<T>(int param = 0)
{
// doing stuff
}
Can we have an action like this? If so, how can I specify that T from my e.g #Url.Action() call?

I think I've come to an understanding for this issue:
I guess we need to declare our nice generic functions in the model rather than in the controller, and handle different types from there. We can and probably should have different actions and thus different URLs for every model type, but they will all call the same function from the model, only with different types.

Related

WebApi - UrlHelper.Route to GET with [FromUri] object parameter

I have an API action defined as the following:
[Route(Name="GetMembersTest"), HttpGet, ResponseType(typeof(MemberHeadersDto))]
public IHttpActionResult GetMembers[FromUri]MemberFilterDto filter, [FromUri]PagingOptionsDto paging)
This method works as expected, routing and all, requests are flowing through just fine. However, I'd like to supply a "NextUri" for paging so that the caller can just keep following NextUri until it is null to get all the results. I have to send back a uri to the same action, 1 page ahead, if that makes sense.
So I tried using UrlHelper.Route. This route is named "GetMembers" for the purpose of this example.
NextUri = Url.Route("GetMembers", new { filter, paging });
The problem is that instead of getting something like
/v1/members?filter.q=&filter.otherproperty=&paging.count=10&paging.startRow=11
I get
/v1/members?filter=WebApi.Models.MemberFilterDto&paging=WebApi.Models.PagingOptionsDto
It looks like UrlHelper.Route doesn't support complex types in the [FromUri] parameter of a GET Request. Is there anything I can do to get this functionality? My workaround right now is to take in all the Dto properties as individual parameters then build my Dtos from them on the server. This isn't ideal because if I ever add any more options I'd have to add more parameters to the action, which also makes the route value dictionary more fragile as well because it has to match with the method signature in UrlHelper.Route(routeName,routeValues).
Unfortunately, there is no way to pass in complex object to routing. Instead, you will need to pass in the simple properties individually.
I was not able to find a way to extend Url.Route, but that would be/have been your best option.

Convention for actions returning multiple View Types (html, json, partials)

I am building a website in ASP.NET MVC5. In my controllers, I have some actions that return the same data but in different formats mostly Views, partial views and Json.
For example, I would have a controller which display a list of items :
// some action returning a view
public ActionResult List()
{
var model= _repository.Items.ToViewModel();
return View(model)
}
// some action returning a partial view
[ChildActionOnly]
public ActionResult ListPartial()
{
var model= _repository.Items.ToViewModel();
return PartialView("_ListPartial", model)
}
// some action returning JSON
public ActionResult GetList()
{
var model= _repository.Items.ToViewModel();
return Json(model, JsonRequestBehavior.AllowGet);
}
How can I make a clear distinction between my actions ?
What kind of conventions should be followed to keep it clean?
Should the actions co-exist in the same controller ?
I'm not sure what you mean by "make a clear distinction between my actions". From a compiler perspective, the distinction between action methods will always depend on one of two things: the name of the action and the parameters that action takes. So, you could have two actions with the same name, but different parameters, or two actions with totally different names and the same or different parameters. From a developer perspective, you can add to the previous list: return value. Personally, I can look at any of the actions you have here and clearly see what they're doing based on that, i.e. the one that returns the model encoded as JSON, is obviously a JSON result.
As far as keeping things clean goes, again, that's a somewhat loaded question. What defines "clean"? Your methods look perfectly "clean" to me as-is. You've got a bit of code duplication, but I'd consider that acceptable in this scenario. All you're doing is retrieving a model in each. One could argue for leaving such duplicate code, as potentially each method could diverge over time, retrieving more or less parts of that model or including other related entities, etc.
However, if you want to remove the code duplication, you could factor that line out into an internal method on the controller, and then have each action call that internal method. That way, if you need to modify the line, you only need to do it in one place, but of course, if you need to make a modification just for one of these actions, you're back to where you were before.
Now, to should the actions co-exist in the same controller, this is a bit subjective as well. Since, they're so closely related in functionality, with just different return values, there's an easy argument for keeping them in the same controller. However, it's equally valid to suggest moving them into different controllers so, for example, you have all your JSON returning actions together. A lot of it boils down to personal preference.
Now, all that said, in this one particular scenario, since literally the only difference is the return value depending on the methodology being used to get the action, you can actually combine all of these into one action:
public ActionResult List()
{
var model= _repository.Items.ToViewModel();
if (Request.IsAjaxRequest())
{
return Json(model, JsonRequestBehavior.AllowGet);
}
if (ControllerContext.IsChildAction)
{
return PartialView("_ListPartial", model);
}
return View(model);
}
This is possible because your action's return value of ActionResult, is a base class that all of ViewResult, PartialViewResult and JsonResult inherit from.
ActionResult is an abstract class and all other result classes are derived from that.
Instead of user ActionReault for all method use derived class name. Like JsonResult, ViewResult , PartialViewResult

How can I make a form which properly calls an action which uses generics?

In ASP.NET MVC3 C#, I want to have an action with this signature:
public ActionResult Restore<T>(int entityId, string redirect) where T : class
I cannot figure out how to call this action properly from the view through a user action. Is it possible to pass the type like that from a view?
Perhaps with something similar to:
#using (Html.BeginForm("Restore", "Global", new { T = #m }))
Perhaps the signature could change while retaining the functionality?
No, it's not possible to do this out of the box. What it boils down to is that the routing code is not generic type aware. Generic types are stored internally much different than they are represented in code, as such method names are not compared the same way.
It could be possible if you wrote your own extension points, but there could be other problems with code that doesn't expect this behavior. You would also have to write many of your own helper methods and not use existing ones.
In short, it's not worth trying to do in my opinion.

Routing by posted content type in ASP.NET MVC

I have a fixedURL to which I'd like to post different types of xml message, deserialized using DataContracts. Depending on the type of the deserialized message, I'd like to route to:
overloaded methods, e.g.
void Process(ContractType1 request) {}
void Process(ContractType2 request) {}
So at some point I need to deserialize this message and hopefully allow the default routing rules to match the correct method. Which extensibility point should I use for this? Or even better, can I make this work out of the box?!
If it makes any difference, I'm using MVC 3.
ASP NET MVC does not respect the overload if they are not decorated for different HTTP methods - e.g. one for POST, other for GET.
You need to use [ActionName(Name = "Process2")] to change the route name. And you will have to use different routes to access (if the HTTP methods are the same)
Have a look here.
Apart from the technical workaround, passing different contracts to the same URL is against the REST principles. Data could be in different format (XML, JSON, ...) but it must be the same. The URI defines a unique intention. Now it is possible to have a common dumpster where documents are all dumped to the same URI but then ASP NET MVC default model binder would not be able to help you and you need to create your own model binder.
Contrary to the other answer I say this is possible
Asp.net MVC is a great platform that can be easily extended. And so basically I've written a special action method selector that makes it possible to write overloads that can serve the same HTTP method but defer in parameters. By default you'd get runtime error that action method can't be resolved. But when you use this action method selector you get rid of this error.
Basically if your parameter classes have distinct parameter names, you can actually select methods by that.
Action method selector is called RequiresRouteValuesAttribute and a typical usage scenario would be with default route where id is optional in:
{controller}/{action}/{id}
This means that you either have to write
public ActionResult Index(int? id)
{
if (id.HasValue)
{
// display details view
}
else
{
// display master view
}
}
but by using my action method selector you can easily write two action methods:
public ActionResult Index()
{
// display master view
}
[RequiresRouteValues("id")]
public ActionResult Index(int id)
{
// display details view
}
The same could be applied to your action methods as long as your custom types have distinct property names or methods use different parameter names. So in your case it could be something like:
[RequiresRouteValues("first.Id")] // when you provide prefix with your form
// or
[RequiresRouteValues("Some.ContractType1.Distict.Property.Name")]
public ActionResult Process(ContractType1 first)
{
// do what's appropriate
}
[RequiresRouteValues("second.Id")] // when you provide prefix with your form
// or
[RequiresRouteValues("Some.ContractType2.Distict.Property.Name")]
public ActionResult Process(ContractType2 second)
{
// do what's appropriate
}
Read all the details about this action method selector and get the code as well.

Call an action from another controller

I have two different objects: contracts, and task orders. My requirements specify that in order to view the Details for either object, the Url should be "http://.../Contract/Details" or "http://.../TaskOrder/Details" depending on which type. They are both very similar and the details pages are almost identical, so I made a class that can either be a contract or a task order, and has a variable "objectTypeID" that says which type it is. I wrote the action "Details" in the task order controller, but now I want to call that from the contract controller instead of recopying the code.
So is there any way to have the url still say ".../Contract/Details" but call the action in the TaskOrder controller instead? I tried using
TaskOrderController TOController = new TaskOrderController();
TOController.Details(id);
This would have worked except that I can't use the HttpContext.Session anymore, which I used several times in the action.
Why are you calling a controller from a controller? A controller action should be called via a route and return a view.
If you have common code used by two separate controllers then you should be looking to abstract this code to another class.
RedirectToAction("Details","To");
In addition, add routing parameters if you need to.
Also, maybe you need a BaseController class which these two controllers inherit from and which implement the same Details action, but based on the objectTypeID do slightly different things.
Create a base class for the controller. Like DetailsController
Put your details code in there, and have it accept an typeId.
Then have your two controllers derive from that base class, and have their Details action call the base class passing in the id
Thanks David, I should be calling it from the view.
All I needed was the following line in my Contract/Details.aspx page:
<%= Html.Action("Details", "TaskOrder", new { id = ViewData["id"] })%>

Categories