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));
}
Related
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.
In asp.net core I would like to set up my API controller to do the following:
by default return View(model);
/api/id.json to return model; as json
/api/id.xml to return model; as xml
The second two can be achieved by using the [FormatFilter] see here
[FormatFilter]
public class ProductsController
{
[Route("[controller]/[action]/{id}.{format?}")]
public Product GetById(int id)
However this requires the method to return an object and not a View(object). Is there anyway to cleanly support also returning Views?
You cannot do both in the same action. However, you can factor out the common functionality into a private method and then implement two actions with minimal code duplication:
[Route("[controller]")]
[FormatFilter]
public class ProductsController : Controller
{
private Product GetByIdCore(int id)
{
// common code here, return product
}
[HttpGet("[action]/{id}")]
[ActionName("GetById")]
public IActionResult GetByIdView(int id) => View(GetByIdCore(id));
[HttpGet("[action]/{id}.{format}")]
public Product GetById(int id) => GetByIdCore(id);
}
It's necessary to use different action names here, because the method signatures cannot differ merely on return type. However, the [ActionName] attribute can be used as above to make them appear to have the same name for the purposes of URL generation and such.
You can actually achieve this just using the one action. Here's an example of how I got it to work:
[FormatFilter]
public class ProductsController : Controller
{
[Route("[controller]/[action]/{id}.{format?}")]
public IActionResult GetById(int id, string format)
{
var yourModel = ...;
if (string.IsNullOrWhiteSpace(format))
return View(yourModel);
return Ok(yourModel);
}
By using IActionResult as the return type, you can return either a ViewResult or an OkObjectResult. You can get access to the format value by taking it as a parameter in your action, check if it's empty and then react accordingly.
I also added Controller as the base class in order to access the convenience methods for creating the relevant results (View(...) and Ok(...)).
If you're going to be using this pattern a lot, to keep your controllers as clean as possible, you could create a base class that exposed a "FormatOrView" method:
[FormatFilter]
public abstract class FormatController : Controller
{
protected ActionResult FormatOrView(object model)
{
var filter = HttpContext.RequestServices.GetRequiredService<FormatFilter>();
if (filter.GetFormat(ControllerContext) == null)
{
return View(model);
}
else
{
return new ObjectResult(model);
}
}
}
And then your controller can inherit from this and use the FormatOrView method
public class ProductsController : FormatController
{
[Route("[controller]/[action]/{id}.{format?}")]
public ActionResult GetById(int id)
{
var product = new { Id = id };
return FormatOrView(product);
}
}
Edit to list final accepted answer by GreyCloud: Here is a generic slightly simplified method you can put into a controller (or make an extension method or put into an abstract base class as above). Note the ?. in case the service is not defined for some reason.
private ActionResult<T> FormatOrView<T>(T model) {
return HttpContext.RequestServices.GetRequiredService<FormatFilter>()?.GetFormat(ControllerContext) == null
? View(model)
: new ActionResult<T>(model);
}
The FormatFilter is part of the content negotiation of your app, in AspNetCore, you have the control to handle your input or output formatters also on the ConfigureServices where you have more control, even you can add more media types there
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
{
options .OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter());
options .InputFormatters.Add(new XmlDataContractSerializerInputFormatter(options ));
//more output formatters
var jsonOutputFormatter = options.OutputFormatters.OfType<JsonOutputFormatter>().FirstOrDefault();
if (jsonOutputFormatter != null)
{
jsonOutputFormatter.SupportedMediaTypes.Add("application/vnd.myvendormediatype");
}
}
}
But going back on the content negotiation in your controllers you can keep just one. The only thing is that you need to know the mediaType to return your View or your json content. Only be sure to pass an accept header with the content type you want. With the content type you are defining for an api or for an mvc application which is the content/format the client should expect
[HttpGet("[action]/{id}")]
public IActionResult public Product GetById(int id, [FromHeader(Name = "Accept")] string mediaType)
{
if (mediaType == "application/vnd.myvendormediatype")
{
var data = GetYourData(...)
return Json(data);
}
else return View("YourDefaultView");
}
My Question
Is it possible to somehow access the type argument T of a generic class from a method attribute that is attached to a method in this generic class?
An Example
For example say I have a generic Controller MyBaseController handling a certain resource type T that is extended by a child controller MyChildController.
public class MyChildController : MyBaseController<BlogPost>
{
// ...
}
Now I am using Swagger to document my API, more specifically Swashbuckle, hence, I am annotating my action with ProducesResponseType to specifiy the return type of the action. This where it get's complicated because ProducesResponseType expects a Type argument denoting the class of the value returned by the action.
public abstract class MyBaseController<T> : Controller where T : IResource
{
[HttpGet, ProducesResponseType(T)] // <-- DOESN'T WORK!
public async Task<IActionResult> doSomething()
{
// ...
}
}
In the example above I need T to somehow resolve to typeof(BlogPost)
Any ideas, or is this impossible?
Accessing generics in attributes is not available.
Consider an alternative approach
Reference Controller action return types in ASP.NET Core Web API
ASP.NET Core 2.1 introduces the ActionResult<T> return type for Web API controller actions. It enables you to return a type deriving from ActionResult or return a specific type.
public abstract class MyBaseController<T> : Controller where T : IResource {
[HttpGet]
public async Task<ActionResult<T>> doSomething() {
// ...
}
}
It was create for that very purpose
ActionResult<T> offers the following benefits over the
IActionResult type:
The [ProducesResponseType] attribute's Type property can be
excluded.
Implicit cast operators support the conversion of both
T and ActionResult to ActionResult<T>. T converts to
ObjectResult, which means return new ObjectResult(T); is
simplified to return T;.
I like the way in WebAPI how you just put a defined model as the parameter in a method of a controller. I'm switching to OData and every example I've found of working with a data structure is by defining it in the modelBuilder
var action = builder.Action("MyAction").ReturnsCollection<int>();
action.Parameter<int>("ProgramId");
action.Parameter<int>("BrandId");
action.Parameter<int>("StoreId");
action.Parameter<OperationStatus>("Status");
and then in the controller you must manually get those values from the ODataParameter dictionary. I find this tedious.
Also in the WebAPI way you can catch errors at compile time or by VisualStudio intelisense, and do validations with annotations.
Are there other ways to pass the parameters to an action? Something more like the WebAPI "perks"
You can have a custom DTO as a parameter for an OdataAction.
For example the code for registering would be like:
var action = builder.Action("MyAction");
action.Parameter<OperationStatus>("Status");
action.ReturnsCollection<int>("Results");
And the Json payload for the request would be like:
{
"Status": {
"Property1":"value",
"Property2":"value"
}
}
Action declaration in controller:
[HttpPost]
[ODataRoute("MyAction")]
public async Task<IHttpActionResult> MyAction(ODataActionParameters parameters)
{
OperationStatus status;
if (!parameters.TryGetValue("Status",out status))
{
return BadRequest("Missing parameter Status");
}
}
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.