Two HttpGet on the same route in one controller [duplicate] - c#

I want to build truly RESTful web service so don't want to leverage RPC-style, so have currently this:
[HttpGet]
[ActionName(nameof(GetByParticipant))]
public async Task<IActionResult> GetByParticipant([FromQuery]string participantId, [FromQuery]string participantType, [FromQuery]string programName)
{
}
[HttpGet]
[ActionName(nameof(GetByProgram))]
public async Task<IActionResult> GetByProgram([FromQuery]string programName)
{
}
And I believe that would work in ASP.NET Web API. But I'm getting an exception:
AmbiguousActionException: Multiple actions matched. The following actions matched route data and had all constraints satisfied:
TermsController.GetByParticipant (ParticipantTerms.Api)
TermsController.GetByProgram (ParticipantTerms.Api)
Neither of the attributes actually help:
[HttpGet]
[ActionName]
[FromQuery]

You can do this using an IActionConstraint.
Here is an example:
public class ExactQueryParamAttribute : Attribute, IActionConstraint
{
private readonly string[] keys;
public ExactQueryParamAttribute(params string[] keys)
{
this.keys = keys;
}
public int Order => 0;
public bool Accept(ActionConstraintContext context)
{
var query = context.RouteContext.HttpContext.Request.Query;
return query.Count == keys.Length && keys.All(key => query.ContainsKey(key));
}
}
[HttpGet]
[ActionName(nameof(GetByParticipant))]
[ExactQueryParam("participantId", "participantType", "programName")]
public async Task<IActionResult> GetByParticipant([FromQuery]string participantId, [FromQuery]string participantType, [FromQuery]string programName)
{
}
[HttpGet]
[ActionName(nameof(GetByProgram))]
[ExactQueryParam("programName")]
public async Task<IActionResult> GetByProgram([FromQuery]string programName)
{
}

When using from query you need to uniquely differentiate the actions' routes otherwise you will get the ambiguous action exception. Reason being api/action?participantId=1&participantType=2 is the same as api/action?programName=x
Suggestion:
public class ParticipantQuery {
public string participantId { get; set; }
public string participantType { get; set; }
public string programName { get; set; }
}
[Route("api/[controller]")]
public class TermsController : Controller {
[HttpGet("participants")] //GET api/terms/participants?participantId=123&....
[ActionName(nameof(GetByParticipant))]
public async Task<IActionResult> GetByParticipant([FromQuery]ParticipantQuery model) {
//...
}
[HttpGet("programs/{programName}")]//GET api/terms/programs/name
[ActionName(nameof(GetByProgram))]
public async Task<IActionResult> GetByProgram(string programName) {
//...
}
}
Or you can use one action that encapsulates the available parameters and branch the result based on the provided members
public class GetTermsQuery {
public string participantId { get; set; }
public string participantType { get; set; }
public string programName { get; set; }
}
[Route("api/[controller]")]
public class TermsController : Controller {
[HttpGet] //GET api/terms?participantId=123&....
public async Task<IActionResult> Get([FromQuery]GetTermsQuery model) {
//...
}
}

I've spent all day trying to do this and Andrew Radford's solution and this was perfect!
[HttpGet]
[ExactQueryParam("active")]
public IEnumerable<UserSelectAllSprocResult.DataRow> GetAll(
[FromQuery] bool active)
{
return ...
}
[HttpGet]
[ExactQueryParam("active", "companyId")]
public IEnumerable<UserSelectAllByCompanySprocResult.DataRow> GetByCompanyId(
[FromQuery] bool active, [FromQuery] int companyId)
{
return ...;
}

Related

How to check if client send me empty json array with .NET Mvc validation?

I have the following view model:
public class EventViewModel
{
[Required(ErrorMessage = "type can not be empty")]
[JsonProperty("type")]
[DisplayName("type")]
public string Type { get; set; }
[Required(ErrorMessage = "date can not be empty")]
[JsonProperty("date")]
[DisplayName("date")]
public int Timestamp { get; set; }
[JsonProperty("data")]
public JObject Data { get; set; }
}
and the following controller action:
[Route("/api/v1.0/events")]
[HttpPost]
public async Task<IActionResult> Events([FromBody] List<EventViewModel> viewModel)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
I expect the following JSON:
[
{
"type" : "open",
"date" : 1567673607,
"data" : {
"message_id" : "lalalalala"
}
}
]
But what if a client will send me just:
[]
empty array?
I would like to show validation message. How I can do that? Is it possible?
Update
Maybe the explanation of the problem is not so clear or I don't understand something. I'll try to fix it.
If I have this kind of JSON:
{
"items" :[
{
...
},
...
]
}
Then it's ok. Not problem. I can write:
public class EventViewModelList
{
[Required]
List<EventViewModel> EventViewModels {get; set;}
}
But my JSON is just array of objects. SO, I can't.
And I can to do something like:
public async Task<IActionResult> Events([Required][FromBody] List<EventViewModel> viewModel)
Because it does not work.
Put validation into the controller? No (( Controller - is controller. Validation - is validation.
I think that I need attribute. But controller action level attribute. Like:
[Route("/api/v1.0/events")]
[NotEmptyListOfViewModels]
[HttpPost]
public async Task<IActionResult> Events([FromBody] List<EventViewModel> viewModel)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
Am I right?
You could write a custom model binder like below:
NotEmptyListOfViewModels
public class NotEmptyListOfViewModels:IModelBinder
{
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
throw new ArgumentNullException(nameof(bindingContext));
//Get command model payload (JSON) from the body
String valueFromBody;
using (var streamReader = new StreamReader(bindingContext.HttpContext.Request.Body))
{
valueFromBody =await streamReader.ReadToEndAsync();
}
var modelInstance = JsonConvert.DeserializeObject<List<EventViewModel>>(valueFromBody);
if(modelInstance.Count==0)
{
bindingContext.ModelState.AddModelError("JsonData", "The json is null !");
}
bindingContext.Result = ModelBindingResult.Success(modelInstance);
}
}
Events action
[Route("/api/v1.0/events")]
[HttpPost]
public async Task<IActionResult> Events([ModelBinder(BinderType = typeof(NotEmptyListOfViewModels))]List<EventViewModel> viewModel)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
return Ok();
}
You can do as follows:
[Route("/api/v1.0/events")]
[HttpPost]
public async Task<IActionResult> Events([FromBody] List<EventViewModel> viewModel)
{
if(viewModel == null || viewModel.Count == 0)
{
ModelState.AddModelError("","List can not be empty or null");
}
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
..............
}

Multiple GET() methods with a single parameter result in AmbiguousMatchException: The request matched multiple endpoints

[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
}
[HttpGet("{id}")]
public ActionResult<string> Get([FromRoute]int id)
{
}
[HttpGet]
public ActionResult<IEnumerable<string>> Get()([FromQuery]DateTime dateTime)
{
}
I can reach the second with:
https://localhost:44341/api/Orders/3
But for the first and third:
https://localhost:44341/api/Orders
https://localhost:44341/api/Orders?dateTime=2019-11-01T00:00:00
Both of these return the error:
AmbiguousMatchException
Core 2.2, if it matters.
I ended up just creating a different endpoint for the GetByDate method.
[HttpGet]
public ActionResult<string> Get()
{
//
}
[HttpGet("{id}")]
public ActionResult<string> Get(int id)
{
//
}
[HttpGet("ByDate/{date}")]
public ActionResult<string> ByDate(DateTime date)
{
//
}
They can be called as follows:
https://localhost:44341/api/controller
https://localhost:44341/api/controller/1
https://localhost:44341/api/controller/getbydate/2019-11-01
We can have a route at the controller level and handle the string input to parse to int or date as below
[Route("api/[controller]/{id}")]
[ApiController]
public class ValuesController : ControllerBase
{
// GET api/values/5
[HttpGet()]
public ActionResult<string> Get(string id)
{
if (int.TryParse(id, out int result))
{
return Ok(id);
}
else if (DateTime.TryParse(id, out DateTime result1))
{
return Ok(id);
}
else
return Ok("Failed");
}
}
How about having Id and date as optional parameters? You can still handle the actual search in a seprate methods if you want, but you have one GET method.
[HttpGet("{id}")]
public IActionResult Get([FromRoute]int? id [FromQuery]DateTime? dateTime)
{
if(id.HasValue)
{
//Do Something with the id
}
else if (dateTime.HasValue)
{
//Do Something with the date
}
else
{
//return all
}
}

getting failed to parse JSON/YAML response using swagger when inheriting from custom base controller class

I am getting the error failed to parse JSON/YAML response. This occurs only if my controller inherits from a BaseController class. If I Remove the BaseController it works fine.
[Route("v1/helloworld")]
public class ValuesController : BaseController
{
private SystemManager _mgr { get; }
public ValuesController(SystemManager mgr):base(mgr)
{
_mgr = mgr;
}
// GET: api/values
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/values/5
[HttpGet("{id}")]
public string Get(int id)
{
return "value";
}
// POST api/values
[HttpPost]
public void Post([FromBody]string value)
{
}
// PUT api/values/5
[HttpPut("{id}")]
public void Put(int id, [FromBody]string value)
{
}
// DELETE api/values/5
[HttpDelete("{id}")]
public void Delete(int id)
{
}
}
BaseController.cs
public abstract class BaseController : Controller
{
public SystemManager SysMgr { get; }
public LogWriter Logger
{
get { return SysMgr.Logger; }
}
public BaseController(SystemManager sysMgr)
{
SysMgr = sysMgr;
}
public async Task LogAsync(Exception ex)
{
await LogAsync(JsonConvert.SerializeObject(ex, Formatting.Indented));
}
public async Task LogAsync(string message)
{
var le = new LogEntry()
{
Message = message
};
await Logger.LogAsync(le);
}
public string GetUsername() => User.Claims.Where(a => a.Type == "Username").FirstOrDefault().Value;
public string GetIPAddress() => HttpContext.Features.Get<IHttpConnectionFeature>()?.RemoteIpAddress?.ToString() ?? string.Empty;
}
Was fixed by setting all method to protected in base controller class.

Is it possible to have multiple GETs that vary only by parameters in ASP.NET Core?

I want to build truly RESTful web service so don't want to leverage RPC-style, so have currently this:
[HttpGet]
[ActionName(nameof(GetByParticipant))]
public async Task<IActionResult> GetByParticipant([FromQuery]string participantId, [FromQuery]string participantType, [FromQuery]string programName)
{
}
[HttpGet]
[ActionName(nameof(GetByProgram))]
public async Task<IActionResult> GetByProgram([FromQuery]string programName)
{
}
And I believe that would work in ASP.NET Web API. But I'm getting an exception:
AmbiguousActionException: Multiple actions matched. The following actions matched route data and had all constraints satisfied:
TermsController.GetByParticipant (ParticipantTerms.Api)
TermsController.GetByProgram (ParticipantTerms.Api)
Neither of the attributes actually help:
[HttpGet]
[ActionName]
[FromQuery]
You can do this using an IActionConstraint.
Here is an example:
public class ExactQueryParamAttribute : Attribute, IActionConstraint
{
private readonly string[] keys;
public ExactQueryParamAttribute(params string[] keys)
{
this.keys = keys;
}
public int Order => 0;
public bool Accept(ActionConstraintContext context)
{
var query = context.RouteContext.HttpContext.Request.Query;
return query.Count == keys.Length && keys.All(key => query.ContainsKey(key));
}
}
[HttpGet]
[ActionName(nameof(GetByParticipant))]
[ExactQueryParam("participantId", "participantType", "programName")]
public async Task<IActionResult> GetByParticipant([FromQuery]string participantId, [FromQuery]string participantType, [FromQuery]string programName)
{
}
[HttpGet]
[ActionName(nameof(GetByProgram))]
[ExactQueryParam("programName")]
public async Task<IActionResult> GetByProgram([FromQuery]string programName)
{
}
When using from query you need to uniquely differentiate the actions' routes otherwise you will get the ambiguous action exception. Reason being api/action?participantId=1&participantType=2 is the same as api/action?programName=x
Suggestion:
public class ParticipantQuery {
public string participantId { get; set; }
public string participantType { get; set; }
public string programName { get; set; }
}
[Route("api/[controller]")]
public class TermsController : Controller {
[HttpGet("participants")] //GET api/terms/participants?participantId=123&....
[ActionName(nameof(GetByParticipant))]
public async Task<IActionResult> GetByParticipant([FromQuery]ParticipantQuery model) {
//...
}
[HttpGet("programs/{programName}")]//GET api/terms/programs/name
[ActionName(nameof(GetByProgram))]
public async Task<IActionResult> GetByProgram(string programName) {
//...
}
}
Or you can use one action that encapsulates the available parameters and branch the result based on the provided members
public class GetTermsQuery {
public string participantId { get; set; }
public string participantType { get; set; }
public string programName { get; set; }
}
[Route("api/[controller]")]
public class TermsController : Controller {
[HttpGet] //GET api/terms?participantId=123&....
public async Task<IActionResult> Get([FromQuery]GetTermsQuery model) {
//...
}
}
I've spent all day trying to do this and Andrew Radford's solution and this was perfect!
[HttpGet]
[ExactQueryParam("active")]
public IEnumerable<UserSelectAllSprocResult.DataRow> GetAll(
[FromQuery] bool active)
{
return ...
}
[HttpGet]
[ExactQueryParam("active", "companyId")]
public IEnumerable<UserSelectAllByCompanySprocResult.DataRow> GetByCompanyId(
[FromQuery] bool active, [FromQuery] int companyId)
{
return ...;
}

How do i enable cache in my RestAPI in c#?

This is my EmployeeDetailsController.cs
namespace EmpApi.Controllers
{
[RoutePrefix("")]
public class EmployeeDetailsController : ApiController
{
[HttpGet]
[Route("Employees")]
public IEnumerable<Employee> Employees()
{
}
[HttpGet]
[Route("Details/{id}")]
public IEnumerable<Details> Details(int id)
{
}
[HttpGet]
[Route("TeamInfo/{id}")]
public IEnumerable<Team> TeamInfo(int id)
{
}
[HttpGet]
[Route("DetailsForTeam/{id}")]
public IEnumerable<Details> DetailsForTeam(int id)
{
;
}
[HttpPost]
[Route("PostEmp")]
public void PostEmp([FromBody] Employee cs)
{
}
[HttpPut]
[Route("PutEmp/{id}")]
public void PutEmp(int id, [FromBody]Employee cs)
{
}
[HttpDelete]
[Route("DeleteEmp/{id}")]
public void DeleteEmp(int id)
{
}
}
}
I made an API which has various services.
Suppose i call api/Employees,after i call api/Details/12 and then when i click GoBack button in browser, api/Employees should not be triggered.
How do i enable cache for my API.Please tell me the steps as I am new in WebApI.
Thanks in advance..
Add this code before your controller declaration as follows:
[OutputCache(VaryByParam = "*", Duration = 0, NoStore = true)]
public class EmployeeDetailsController : ApiController
{
...
}

Categories