I've got this controller with 2 actions, both GETs, the same name.
[ApiVersion("2")]
[Route("v{version:apiVersion}/[controller]")]
public class FooController : BaseController
And the two actions are like:
public IActionResult Get([FromServices]IBarService barService)
public IActionResult Get([FromServices]IBarService barService, string someParameter)
But I do need to distinguish those two actions as different operations with inputs and even outputs.
Also I cannot change the behaviour of the current API, it means, the final user should have access to the following listed paths:
GET v2/Foo
GET v2/Foo?someParameter={someParameter}
In the first place I had to add this line to make it work:
o.ResolveConflictingActions((apiDescriptions) => apiDescriptions.First());
It works, but when the OpenApiOperation are listed it had only the first one (for obvious reasons)
How can I add the query string parameters to make them work like 2 different paths?
Here is an alternative:
[RoutePrefix("foo/widgets")]
public class RoutePrefixController : ApiController
{
[Route("")]
public string Get()
{
return "value";
}
[Route("{id}")]
public string GetById(string id)
{
return "value " + id;
}
[Route("{id}/{name}")]
public string GetByIdByName(string id, string name)
{
return "value " + id + " " + name;
}
That uses RoutePrefix and Route your final enpoints are:
GET /foo/widgets
GET /foo/widgets/{id}
GET /foo/widgets/{id}/{name}
Here is how that looks like on swagger:
http://swagger-net-test.azurewebsites.net/swagger/ui/index?filter=RoutePrefix
Related
I have an ASP.NET Core web API controller with (among others) two methods that have the same signature.
Shortened down, this looks as follows:
[Route("my/route")]
public class MyApiController : ApiController
{
[HttpGet("{*id}", Order = 2)]
[Route("{*id}", Order = 2)]
public MyObject Load([FromUri] String id) => new MyObject();
[HttpDelete("{*id}", Order = 1)]
[Route("{*id}", Order = 1)]
public void Delete([FromUri] String id)
{
}
}
Now, I am issuing a call:
GET my/route/123/456
Shockingly, this call ends up in the Delete method. I literally have a breapoint in the first line of my (in real life, non-empty) Delete method, and the Immediate window in VS tells me HttpContext.Request.Method is "GET", yet I end up in the method explicitly marked as HttpDelete.
What is going on here? Luckily, my call happened from within an automated test to test the web API, but if someone had issued that call to retrieve actual data from the DB, they would have ended up deleting that data instead. Is there any misunderstanding on my side with respect to the [HttpDelete] attribute?
You don't have to use route attribute and order parameter. It might be cause this situation.
[Route("my/route")]
public class MyApiController : ApiController
{
[HttpGet("{*id}")]
public MyObject Load([FromUri] String id) => new MyObject();
[HttpDelete("{*id}")]
public void Delete([FromUri] String id)
{
}
}
If you have an [ApiController] attribute, you have to remove it since you will need full explicit route attribute. Or it is much better to use explicit routes
[Route("my/[action]")]
public class MyApiController : ApiController
{
[HttpGet("my/load/{id}")]
public MyObject Load(string id) => new MyObject();
[HttpDelete("my/delete/{id}")]
[HttpGet("my/delete/{id}")]
public void Delete(string id)
{
}
}
Problem: We are upgrading from a legacy system, so solutions are constrained. I am trying to route to an unauthorized controller if a specific query string is present. If it is not present, the user is routed to the authorized controller. This is on ASP.Net Core 2.1.
Is it possible to set the controller to route based on query string? I've tried
[/home/[action]?query={query}] -> Leads to runtime error due to '?'
[/home/[action]/{query}] - > maps to /home/index/1 (not what I need)
Thanks for any help!
Edit: Alternatively, is it possible to have a separate controller Action that depends on the query parameter?
public IActionResult Index(){}
public IActionResult Index([FromQuery]string query){}
Routing doesn't seem to distinguish between these two.
You can use IActionConstraint and IParameterModelConvention interfaces for that. In short, create an IActionConstraint like this:
public class RequiredFromQueryActionConstraint : IActionConstraint
{
private readonly string _parameter;
public RequiredFromQueryActionConstraint(string parameter)
{
_parameter = parameter;
}
public int Order => 999;
public bool Accept(ActionConstraintContext context)
{
if (!context.RouteContext.HttpContext.Request.Query.ContainsKey(_parameter))
{
return false;
}
return true;
}
}
If a matching parameter is not found on the request's query string, then it will return false from the Accept method.
Than create RequiredFromQueryAttribute class like this:
public class RequiredFromQueryAttribute : FromQueryAttribute, IParameterModelConvention
{
public void Apply(ParameterModel parameter)
{
if (parameter.Action.Selectors != null && parameter.Action.Selectors.Any())
{
parameter.Action.Selectors.Last().ActionConstraints.Add(new RequiredFromQueryActionConstraint(parameter.BindingInfo?.BinderModelName ?? parameter.ParameterName));
}
}
}
Than you could decorate your mandatory query string parameters with this attribute:
[Route("api/[controller]")]
public class ValuesController : Controller
{
[HttpGet("{id}")]
public string Get(int id, [RequiredFromQuery]string foo, [RequiredFromQuery]string bar)
{
return id + " " + foo + " " + bar;
}
}
From now than, only the following URL GET api/values/5?foo=a&bar=b would lead into the action above, all other combinations of parameters would result in response with status 404, which you can eventually replace with what you want.
You can find more info at this link https://www.strathweb.com/2016/09/required-query-string-parameters-in-asp-net-core-mvc/
I can do this by using this code :
[HttpPost("SampleRoute1")]
public JsonResult Post([FromBody]SampleModel1 value)
{
.....Functionone.....
return Json("");
}
[HttpPost("SampleRoute2")]
public JsonResult Post([FromBody]SampleModel2 value)
{
.....Functiontwo.....
return Json("");
}
but i cant do this :
[HttpPost("SampleRoute1")]
public JsonResult Post([FromBody]SampleModel1 value)
{
.....Functionone.....
return Json("");
}
[HttpPost("SampleRoute2")]
public JsonResult Post([FromBody]SampleModel1 value)
{
.....Functiontwo.....
return Json("");
}
it gives error "Type 'Controller1' already defines a member called 'Post' with the same parameter types"
so is there any way that i can make two Post in one controller with same paramter but with different route?
like this :
Posting(SampleModel1) => "Controller1\SampleRoute1" => Doing Function1
Posting(SampleModel1) => "Controller1\SampleRoute2" => Doing Function2
Yes, you can do that. Problem is that you're trying to have two methods in a class that have same name & parameters and that's not possible. You should change name of your methods to something different.
Note that the action name & Post request type are already specified in the HttpPost attribute so you don't have to rely on the method name.
[HttpPost("SampleRoute1")]
public JsonResult Aaa([FromBody]SampleModel1 value)
{
.....Functionone.....
return Json("");
}
[HttpPost("SampleRoute2")]
public JsonResult Bbb([FromBody]SampleModel1 value)
{
.....Functiontwo.....
return Json("");
}
You are getting the error because you have 2 methods that are identical. How would you know which one to execute? Are you basing this on the routes that you defined?
If I gave you 2 identical red apples to eat, there is no difference between the 2 apples, and I told you to eat the correct apple, would you know which is the correct apple?
You are going to have to change your method names so that they are unique and identifiable.
[HttpPost("SampleRoute1")]
public ActionResult Function1(SampleModel1 model)
{
return Json("");
}
[HttpPost("SampleRoute2")]
public ActionResult Function2(SampleModel1 model)
{
return Json("");
}
So based on the above, the following will happen:
So now when posting SampleModel1, using route Controller1\SampleRoute1 will execute action method Function1
So now when posting SampleModel2, using route Controller1\SampleRoute2 will execute action method Function2.
I want to log the each action method parameter name and its
corresponding values in the database as key value pair. As part of
this, I am using OnActionExecuting ActionFilterAttribute, since it
will be the right place (OnActionExecuting method will get invoke for
all controller action methods call) to get Action Executing context.
I am getting the value for .Net types (string, int, bool). But I am
unable to get the value of the User defined types (custom types).
(ex: Login model). My model might have some other nested user
defined types as well.
I was trying to get the values of the user defined types but I am
getting the only class name as string. I hope we can do in
reflection.
Could you please anyone assist to resolve the issue. since I am new
to reflection. It will helpful to me. Thanks in Advance.
I need to get the name and value of these types in OnActionExecuting.
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
ActionParameter = new SerializableDictionary<string,string>();
if(filterContext.ActionParameter != null)
{
foreach(var paramter in filterContext.ActionParameter)
{
//able to get returnUrl value
//unable to get model values
ActionParameter.Add(paramter.Key, paramter.Value);
}
}
}
public ActionResult Login(LoginModel model, string returnUrl)
{
return View(model);
}
User defined type
public class LoginModel
{
public string UserName {get;set;}
public string Password {get;set;}
//User defined type
public UserRequestBase Request {get;set;}
}
//User defined type
public class UserRequestBase
{
public string ApplicationName {get;set;}
}
I am able to get the value of the returnUrl (login method param) in OnActionExecuting but not for model (login method param). I am able to see the values, but don't know how to access it, I used typeof even though I am unable to get it, but I need generic because i have 20 methods in controller so I could not only for LoginModel.
This answer isn't exactly what you want - based on your question - but I think it will work better for what want to accomplish. Quick aside...
Playing around with reflection and nested classes in this instance, lead to some SO (a propos?) errors for me...
So, a better path, maybe? Rather than trying to get/cast the property names, values (types?) from 'context.ActionParameters,` I found it was much easier to let a Json serialization do the work for me. You can then persist the Json object, then deserialize... pretty easy.
Anyway, here's the code:
using Newtonsoft.Json; // <-- or some other serialization entity
//...
public class LogActions : ActionFilterAttribute, IActionFilter
{
// Using the example -- LoginModel, UserRequestBase objects and Login controller...
void IActionFilter.OnActionExecuting(ActionExecutingContext context)
{
var param = (Dictionary<String, Object>)context.ActionParameters;
foreach (var item in param.Values)
{
string itemName = item.GetType().Name.ToString();
string itemToJson = JsonConvert.SerializeObject(item);
// Save JsonObject along with whatever other values you need (route, etc)
}
}
}
Then when you retrieve the Json object from the database you just have to deserialize / cast it.
LoginModel model = (LoginModel)JsonConvert.DeserializeObject(itemToJson, typeof(LoginModel));
From example:
public class LoginModel
{
public string UserName {get;set;}
public string Password {get;set;}
//User defined type
public UserRequestBase Request {get;set;}
}
//User defined type
public class UserRequestBase
{
public string ApplicationName {get;set;}
}
Controller used in example:
public ActionResult Login(LoginModel model, string returnUrl)
{
return View(model);
}
Hope this helps. If there are further issues with this please let me know and I will try to help.
I'm new to Web API, and I'm stuck at getting multiple values for Get(). What I'm trying to do is to pass in many values through the query string. Instead of having a Get(string .., string .., so on), I decided to go MVC style and do something like Get(RequestModel m). This returns a NullRef exception. e.g.:
For my 'web request', I've created a class:
RequestModel
{
public string Req1 {get;set;}
public string Req2 {get;set;}
public string Req3 {get;set;}
}
My Get function in the controller:
public ValuesController : ApiController
{
public Get(RequestModel m)
{
return m.Req1;
}
}
My url is:
http://localhost/api/values?Req1=test
Is this possible? If not, what's the best way to do this? The only thing i can think of as an alternative is ParseQueryString().
You would need to explicitly set [FromUri] attribute like below:
public Get([FromUri] RequestModel m)