Action method overrides for HTTPPost .net core - c#

I have two Post method one is taking single object and one is taking list of object, how to do this overriding in .net core?
// taking list of object
[HttpPost("matchdob")]
public MatchDOBResponse MatchDOB(List<DOBMatchRequest> requestModel)
{
//
}
// taking single object
[HttpPost("matchdob")]
public MatchDOBResponse MatchDOB(DOBMatchRequest requestModel)
{
//
}
its not working, how to solve this issue?

You can't set the multiple action with the same template name. Each action should have a unique route name.
Try use different name, like this:
// taking list of object
[HttpPost("matchdmultipleob")]
public MatchDOBResponse MatchDOB(List<DOBMatchRequest> requestModel)
{
//
}
// taking single object
[HttpPost("matchdsingleob")]
public MatchDOBResponse MatchDOB(DOBMatchRequest requestModel)
{
//
}
Or you can use one action method and receive a list of object, then check the count:
[HttpPost("matchdob")]
public MatchDOBResponse MatchDOB(List<DOBMatchRequest> requestModel)
{
if (requestModel.Count == 1)
{
//upload single object
//do something
}
else
{
//upload list of object
}
//
return new MatchDOBResponse() { Status = "200", Message = "Success" };
}

Actually it is possible to implement what you are asking. It is necessary to use the route constraints.
For example, the following two action method will be called with the same action name Method depend on the data parameter type: the first when data is int, and the second when the data parameter is bool.
[HttpPost, Route("{data:int}")]
public IActionResult Method(int data)
{
return RedirectToAction("Method1", data);
}
[HttpPost, Route("{data:bool}")]
public IActionResult Method(bool data)
{
return RedirectToAction("Method2", data);
}
In your case the Custom route constraints should be implemented to use this feature: one class to support DOBMatchRequest type, and the second class for List<DOBMatchRequest> type.
For more information see:
Route constraints
Custom route constraints
Note: Yes... this is more likely overloading, not overriding.

Related

Web API Generic Action

I have a http Action that will needs to take in two different object model. The Implementation looks at the two model object and know what to do at that point. Can I use a generic object?
[HttpPost]
public IHttpActionResult InsertData(string accessKey, [FromBody] T content)
{
try
{
MobileAppService ms = new MobileAppService();
ResultStatus resultStatus = ms.ProcessAppLogging(t);
return Ok(resultStatus.ResultCode);
}
catch (Exception e)
{
Elmah.ErrorSignal.FromCurrentContext().Raise(e);
}
}
Controller having a generic action method
With default implementations of the framework, No, you cannot have such action:
public IHttpActionResult InsertData([FromBody] T model)
This is the exception which you receive:
Cannot call action method 'XXXX' on controller
'XXXX' because
the action method is a generic method.
But the framework is very extensible and if you get a very good understanding of Routing and Action Selection in ASP.NET Web API and learn how routing, controller selection, action selection, parameter binding and action invocation work, then you can implement some customization for the framework to support generic action method selection and invocation.
In your custom logic, to be able to execute the action in run-time, you need to resolve T at run-time. To do so, you can rely on attributes to limit it to some known types or you can rely on some context information like route data, header values, some special values like $type in body an so on.
Providing a custom implementation to handle generic action method is too broad for this post. That said, let me share other solutions.
Derive from a generic base controller
You can have a base generic controller then having two non-generic controllers derived from the base controller and handle the request by a single method which is implemented in the base controller. I assume you have seen this post which has already suggested the same solution:
public class MyBaseController<T> : ApiController
{
public IHttpActionResult InsertData([FromBody] T model)
{
//Write the generic code here, for example:
dbContext.Set<T>().Add(model);
dbContext.SaveChanges();
return some value;
}
}
Then:
public class ProductController : MyBaseController<Product> { }
public class CustomerController : MyBaseController<Customer> { }
Rely on dynamic and resolve the type later based on context information
Another option is having the following action method:
public IHttpActionResult InsertData([FromBody] dynamic model)
Then based on some context information like route values, header values, some special values like $type in body an so on, you can resolve model type and call your private generic method:
public IHttpActionResult InsertData([FromBody] dynamic model)
{
Type t = resolve type base on context information
object data = create an instance of t base on the model values;
var method = this.GetType().GetMethod(nameof(InsertDataPrivate),
BindingFlags.NonPublic | BindingFlags.Instance);
var result = (int)method.MakeGenericMethod(t)
.Invoke(this, new object[] { data });
return Ok(result);
}
private int InsertDataPrivate<T>(T model) where T
{
//Write the generic code here, for example:
dbContext.Set<T>().Add(model);
dbContext.SaveChanges();
return some value;
}
Don't mess up with all the objects, just do a cast.
Content-Type: application/json
[ApiController]
public class AdWordsController : ControllerBase
{
[HttpPost("/Metrics/")]
[EnableCors("CorsPolicy")]
public IActionResult GetMetrics(dynamic data)
{
return new JsonResult(Metrics((String)data.customerId, (String)data.from, (String)data.to));
}

MVC implicit redirect to another JsonResult action possible?

I have one action, and depending on the value of foo I want to redirect to a particular action:
public JsonResult FirstAction(string foo) // foo == "SecondAction"
{
return RedirectToAction(foo);
}
public JsonResult SecondAction() { }
This doesn't work, since RedirectToAction doesn't return a JsonResult and there is no way of converting (as far as I'm aware).
I'm aware I could use some condition here i.e. a switch on foo in order to determine which method to invoke, but I'd like to keep it as implicit as possible.
RedirectToAction responds with an Http 302, which instructs the user's browser to request a different URI.
You simply need to change the return type of your method to ActionResult and your code should work as expected.
So if I understand you correctly you want to execute different actions and different logic based on the value of the parameter passed to the method.
One way you could do this is using the routing system, but I would only advise you opting to this if the possible values of the parameter is of a manageable size.
Then in your RouteConfig.cs file:
public static void RegisterRoutes(RouteCollection routes)
{
// ... other routes
string[] possibleValueSet1 = new[] { ... };
string[] possibleValueSet2 = new[] { ... };
for (int i = 0; i < possibleValueSet1.Length; ++i)
{
routes.MapRoute(
name: "YourActionWithValueSet1_" + i.ToString(),
url: "ControllerName/FirstAction/" + possibleValueSet1[i],
defaults: new { controller = "ControllerName", action = "FirstAction", foo = possibleValueSet1[i] }
);
}
for (int i = 0; i < possibleValueSet2.Length; ++i)
{
routes.MapRoute(
name: "YourActionWithValueSet2_" + i.ToString(),
url: "ControllerName/SecondAction/" + possibleValueSet2[i],
defaults: new { controller = "ControllerName", action = "SecondAction", foo = possibleValueSet2[i] }
);
}
}
This way each possible value of foo, which should be handled by logic #1, would have an own URL, the same applies to the values which should be handled by logic #2.
I think you can already see that this goes far and whereas implicit, it is in no way more maintainable or generally better than simply implementing a decision inside the action what you pointed out you wanted to avoid. What you are asking for sounds like a "value-based overload" which C# does not allow, the code above is kind of a dirty workaround to let the routing engine do it for you, though it is not an actual overload, it is just mapping to different actions based on hardcoded possible values.
Instead of doing RedirectToAction you should return the SecondAction itself.
public JsonResult FirstAction(string foo) // foo == "SecondAction"
{
return SecondAction();
}
public JsonResult SecondAction() { }
Apart from the route-based approach I've described above, you should also take a look at the ActionMethodSelectorAttribute attribute. This is an attribute on which, among others, HttpPost and HttpGet are based and lets you control whether or not the action is valid by returning a boolean value. This is the method you need to override:
public abstract bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo);
Then you could implement something like this:
public class ValidBasedOnFoo
{
public int MethodNum { get; set; }
public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo)
{
var foo = controllerContext.HttpContext.Request["foo"] as string;
if (MethodNum == 1)
return YourDecisionLogicForFirstAction(foo);
else if (MethodNum == 2)
return YourDecisionLogicForSecondAction(foo);
else throw new InvalidOperationException("Unknown method number.");
}
}
Then in your controller, the actions are as such:
[ValidBasedOnFoo(1)]
public JsonResult FirstAction(string foo) { ... }
[ValidBasedOnFoo(2)]
public JsonResult SecondAction(string foo) { ... }
And this will allow the action to be executed if the value is proper for the action. The number used in the attribute is to tell which decision logic to use, you could just as well implement one attribute for each action if that is more clear for you.

Can anyone explain CreatedAtRoute() to me?

From the template for Web API 2, a post method is always like this:
[ResponseType(typeof(MyDTO))]
public IHttpActionResult PostmyObject(MyDTO myObject)
{
...
return CreatedAtRoute("DefaultApi", new { id = myObject.Id }, myObject);
}
I don't understand this CreatedAtRoute() method. Can anyone explain it to me?
The CreatedAtRoute method is intended to return a URI to the newly created resource when you invoke a POST method to store some new object.
So if you POST an order item for instance, you might return a route like 'api/order/11' (11 being the id of the order obviously).
BTW I agree that the MSDN article is of no use in understanding this. The route you actually return will naturally depend on your routing setup.
When you use CreatedAtRoute, the first argument is the route name of the GET to the resource. The trick that is not so obvious is that, even with the correct method name specified, you must thus use the Name param on the HttpGet attribute for it to work.
So if the return in your POST is this:
return CreatedAtRoute("Get", routeValues: new { id = model.Id }, value: model);
Then your Get method attribute should look like this even if your method is named Get:
[HttpGet("{id}", Name = "Get")]
Calls to your Post method will not only return the new object (normally as JSON), it will set the Location header on the response to the URI that would get that resource.
NOTE the field names in the routeValues field names need to match the binding names in the target route, i.e. there needs to be a field named id to match the {id} in HttpGet("{id}"
Finally, in some cases, it should be mentioned that the CreatedAtAction helper can be a more direct solution.
In .net core WebAPI, you use this method to return a 201 code, which means that the object was created.
[Microsoft.AspNetCore.Mvc.NonAction]
public virtual Microsoft.AspNetCore.Mvc.CreatedAtRouteResult CreatedAtRoute (string routeName, object routeValues, object content);
As you can see above, the CreatedAtRoute can receive 3 parameters:
routeName
Is the name that you must put on the method that will be the URI that would get that resource after created.
routeValues
It's the object containing the values that will be passed to the GET method at the named route. It will be used to return the created object
content
It's the object that was created.
The above example shows the implementation of two methods of a simple controller with a simple GET method with the bonded name and the POST method that creates a new object.
[Route("api/[controller]")]
[ApiController]
public class CompanyController : Controller
{
private ICompanyRepository _companyRepository;
public CompanyController(ICompanyRepository companyRepository)
{
_companyRepository = companyRepository;
}
[HttpGet("{id}", Name="GetCompany")]
public IActionResult GetById(int id)
{
Company company = _companyRepository.Find(id);
if (company == null)
{
return NotFound();
}
return new ObjectResult(company);
}
[HttpPost]
public IActionResult Create([FromBody] Company company)
{
if (company == null)
{
return BadRequest();
}
_companyRepository.Add(company);
return CreatedAtRoute(
"GetCompany",
new { id = company.CompanyID },
company);
}
}
IMPORTANT
Notice that the first parameter at CreatedAtRoute (routeName), must be the same at the definition of the Name at the Get method.
The object on the second parameter will need to have the necessary fields that you use to retrieve the resource on the Get method, you can say that it's a subset of the object created itself
The last parameter is the company object received in the body request in it's full form.
FINALY
As final result, when the Post to create a new company got made to this API, you will you return a route like 'api/company/{id}' that will return to you the newly created resource

ActionResult cast parameter base class to derived class

I've written a base class and some classes which derive from it.
I want to use these classes in one ActionResult, but if I'm trying to cast PSBase to PS1 I'm getting a System.InvalidCastException that type PSBase can not be converted to PS1.
Classes:
public class PSBase
{
public int stationId { get; set; }
public string name { get; set; }
}
public class PS1 : PSBase
{
public string reference { get; set; }
}
public class PS2 : PSBase
{
}
ActionResult:
[HttpPost]
public ActionResult ProductionStep(PSBase ps)
{
if (ModelState.IsValid)
{
var product = db.Product.FirstOrDefault(.........);
switch (ps.stationId )
{
case 1:
{
product.Reference = ((PS1)ps).reference;
db.SaveChanges();
break;
}
}
}
return View();
}
As I don't want to have for each class a own ActionResult (repeating much of the same code many times) I wanted put all this to one ActionResult. Any Ideas how I could implement this?
What you are trying to do will never work without custom ModelBinder (and even then it will be a huge mess I'd not recommend to implement), sorry.
Only when you are passing a model from Controller to View it remembers what type it was originally (including inheritance, etc.) because at that point you are still on the server side of the page and you are merely passing an object.
Once you enter a view and submit a form all that does it creates some POST request with body containing list of values based on input names.
In your case if you have a form based on PS1 and used all the fields as inputs, you would get something like:
POST:
stationId = some value
name = some value
reference = some value
(there is no mention of the original type, controller, method, etc.)
Now, what MVC does is that it checks what argument you are using in the header of the method (in your case ProductionStep(PSBase ps)).
Based on the argument it calls a model binder. What the default model binder does is that it creates new instance of that class (in your case PSBase) and goes via reflection through all the properties of that class and tries to get them from the POST body.
If there are some extra values in the POST body those are forgotten.
Unless you write a custom model binder for this default MVC implementation can't help you there.
I'd recommend creating two separate methods, one of each accepting different implementation of PSBase.
If you want to read more on Model Binders check this out http://msdn.microsoft.com/en-us/magazine/hh781022.aspx
EDIT:
By creating two separate methods I mean something like this:
[HttpPost]
public ActionResult ProductionStepA(PS1 ps)
{
if (ModelState.IsValid)
{
}
return View();
}
[HttpPost]
public ActionResult ProductionStepB(PS2 ps)
{
if (ModelState.IsValid)
{
}
return View();
}
then you have to distinguish between them in the view via different form action.

ASP.net c#, Same name method that takes different argument type [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Can you overload controller methods in ASP.Net MVC?
I need to 2 methods that takes different type of argument. so I tried this,
[HttpPost]
public ActionResult ItemUpdate(ORDER ln)
{
// do something
}
[HttpPost]
public ActionResult ItemUpdate(List<ORDER> lns)
{
// Do Something
}
but it does not work.
No error while compiling, but when run, it makes an error.
How I write the code to make that works?
Thanks!
[Edit]
[HttpGet]
public ActionResult ItemUpdate(string name)
{
return Content(name);
}
[HttpGet]
public ActionResult ItemUpdate(int num)
{
return Content(num.ToString());
}
and when I call /Test/ItemUpdate/
it make an error,
Server Error in '/' Application.
The current request for action 'ItemUpdate' on controller type 'OrderController' is ambiguous between the following action methods:
System.Web.Mvc.ActionResult ItemUpdate(System.String) on type Ecom.WebUI.Controllers.OrderController
System.Web.Mvc.ActionResult ItemUpdate(Int32) on type Ecom.WebUI.Controllers.OrderController
[EDIT]
It does not match with ORDER even single parameter.
if (lns.GetType() == typeof(ORDER))
{
// always false
}else{
// also I can not cast the object.
ORDER ln = (ORDER)lns; //Unable to cast object of type 'System.Object' to type 'ORDER'
}
Overloaded actions are not supported in MVC. The dispatcher can not tell the difference between the two Actions. You can get around this by giving one of your actions the [HttpGet] attribute and the other one the [HttpPost] attribute.
If that isn't an option (or if you have three or more overloads), you can always dispatch the Action yourself, by using an object parameter and using run time type identification to select the correct function to call. E.g.:
[HttpPost]
public ActionResult ItemUpdate(object arg)
{
if (arg.GetType() == typeof(ORDER))
{
return ItemUpdateOrder((Order)arg);
}
else if (arg.GetType() == typeof(List<ORDER>))
{
return ItemUpdateList((List<Order>)arg);
}
}
public ActionResult ItemUpdateOrder(ORDER ln)
{
//...
}
public ActionResult ItemUpdateList(List<ORDER> lns)
{
//...
}
You can't do that in a controller. You will have to change the second method name.
[HttpPost]
public ActionResult ItemUpdates(List<ORDER> lns)
{
// Do Something
}
public ActionResult ItemUpdates(object myInputValue)
{
if (myInputValue.GetType() == typeof(string)
// Do something
else if (myInputValue.GetType() == typeof(List<ORDER>))
// Do something else
}
You can then cast the object to your type of choice and manipulate normally.
In ASP.NET it's not possible to have overloaded methods without an ActionFilter attribute to distinguish these actions. The reason for this is that the ActionInvoker (a class used inside of the Controller base class to invoke actiosn) cannot determine which method to call since it would need to "ask" a ModelBinder (which are responsible to construct action argument objects) for every overload if the ModelBinder could construct that argument object from the HTTP request passed. For simple scenarios this would work but in more complex scenarios this would fail because the ModelBinder would succeed in binding arguments of multiple overloads. Not to allow overloads in ASP.NET MVC is quite clever design decision.
To solve your problems you can fix ItemUpdate HTTP GET action by just leaving the second action away and having just one action, because a ModelBinder does not mind if a value that is passed as URL parameter for example is a string or an int.
[HttpGet]
public ActionResult ItemUpdate(string name)
{
return Content(name);
}
For the ItemUpdate HTTP POST version I'd recommend to rename one of these actions or to have only one action, the list version because updating a single ORDER is only a specific case of updating multiple ORDER objects.

Categories