Wrong value binding for asp.net web API controller action paramters - asp.net-mvc-4

I have an asp.net web api controller action (RESTful) that accepts 2 string parameters. Such a parameter can be empty. The web api action is consumed from AngularJS codes (client side Javascript) in a asp.net Razor view page.
The problem of that web api action is case 4 (see below) is never hit. In details, case 4 is supposed to run when paramter1 is passed with an empty string, and paramter2 is passed with a non-empty string. However, on running for this case and by using debugger, I find the value of paramter1 is bound to the value of parameter2, and the value of parameter2 becomes null or empty. So there is a wrong data binding with that web api action, and I do not know how to solve it. Please help. Thank you.
The web API controller action looks like:
[HttpGet]
[AllowAnonymous]
public HttpResponseMessage GetProductByParamter1AndParameter2(string paramter1, string paramter2)
{
if (string.IsNullOrWhiteSpace(paramter1) && string.IsNullOrWhiteSpace(paramter2))
{
// case 1: do something 1 ...
}
else if (!string.IsNullOrWhiteSpace(paramter1) && !string.IsNullOrWhiteSpace(paramter2))
{
// case 2: do something 2 ...
}
else
{
if (!string.IsNullOrWhiteSpace(paramter1) && string.IsNullOrWhiteSpace(paramter2))
{
// case 3: do something 3 ...
}
else // when paramter1 is empty and paramter2 is not empty
{
// case 4: do something 4 ... but this is never hit
}
}
And the custom route for that web API controller action looks like:
config.Routes.MapHttpRoute(
name: "ProductApi_GetProductByParamter1AndParameter2",
routeTemplate: "api/ProductApi/GetProductByParamter1AndParameter2/{parameter1}/{parameter2}",
defaults: new
{
controller = "ProductApi",
action = "GetProductByParamter1AndParameter2",
parameter1 = "",
parameter2 = ""
}
);
In the cshtml view page, on the client side AngularJS (Javascript codes) to consume that web API, I am coding things like:
myApp.factory('ListProductFactory', function ($http, $q) {
return {
getProducts: function (par1, par2) {
var url = _baseUrl + '/ProductApi/GetProductByParamter1AndParameter2/' + par1 + '/' + par2;
return $http({
method: 'GET',
url: url
})
}
};
});

On the controller you set the 'routeTemplate' as api/ProductApi/**GetSourceColumns**/{parameter1}/{parameter2}, it should be the name of the action GetProductByParamter1AndParameter2.
It doesn't even make sense that the url defined as something like: /ProductApi/GetProductByParamter1AndParameter2/{parameter1}/{parameter2} is still reaching the route that have been defined.
You probably have read these, but if you haven't, check out these links that explain key features of Web API
Model Validation: http://www.asp.net/web-api/overview/formats-and-model-binding/model-validation-in-aspnet-web-api
Routing and Action Selection: http://www.asp.net/web-api/overview/web-api-routing-and-actions/routing-in-aspnet-web-api

To solve this problem, I use query-string approach instead of segment (/) approach:
var url = _baseUrl + '/ProductApi/GetProductByParamter1AndParameter2?parameter1=' + par1 + '&parameter2=' + par2;
I spent 10 days to figure out the answer for myself. It is painful.

Related

Cannot redirect another page in ASP.NET Core MVC project Controller

I would like to press a button to connect a GET api, do something and redirect the page.
const Http = new XMLHttpRequest();
Http.open("GET", '/Startup/Action/Test');
Http.send();
Http.onreadystatechange = (e) => {
console.log(Http.responseText)
}
Then in my Controller:
[HttpGet("Startup/Action/{Handle}")]
public IActionResult Action(string Handle)
{
this.ViewData["Result"] = Handle;
return this.Ok(Handle);
// return LocalRedirect("~/Startup/");
// return Redirect("http://localhost:50429/");
// return View("~/Views/Startup/Index.cshtml");
// return Redirect("/");
// return RedirectToAction("Index", "Startup");
}
I get response and see it in console written by the javascript XMLHttpRequest event. However I cannot redirect to just another page. I tried all options commented above as return value.
With AJAX you don't perform the redirect server-side, you perform it client-side after the result has been received. Something like this:
Http.onreadystatechange = (e) => {
if(xhr.readyState === 4 && xhr.status === 200) {
window.location.href = '#Url.Action("Index", "Startup")';
} else {
// non-success reaponse? do something else?
}
}
The above code would redirect following a successful AJAX response. Note that this JavaScript code would need to be on a view itself in order to use the Url.Action() helper. If the JavaScript code is in a separate file, one option could be to return as a string in the AJAX response the URL to which you are redirecting.
Note however that what you are doing is a bit different here:
this.ViewData["Result"] = Handle;
I could be wrong, but I suspect this won't carry over on a redirect. TempData might? But ultimately the question then becomes... What exactly are you trying to accomplish? You are sending a value to the server and then trying to redirect the user to another page to include that value. Well, the client already knows that value, and since it's AJAX the client needs to perform the redirect, so is this AJAX operation even necessary at all?
It's not entirely clear to me what the overall goal here is, and I suspect the overall setup could be simplified significantly. But it looks like AJAX is needed at all here. You can revert your server-side code to performing the redirect and, instead of using AJAX, just redirect the client to that server-side action entirely:
window.location.href = '/Startup/Action/Test';
or:
winfow.location.href = '#Url.Action("Action", "Startup", new { Handle = "Test" })';
And then redirect in your server-side action as you originally tried:
[HttpGet("Startup/Action/{Handle}")]
public IActionResult Action(string Handle)
{
this.ViewData["Result"] = Handle;
return RedirectToAction("Index", "Startup");
}
It's still possible, and again I'm not certain, that ViewData isn't correct here and you may want to use TempData on a redirect. Though, again, it's equally likely that whatever you're trying to accomplish can be done so more effectively in the first place. Such as skipping this Action action method entirely and just passing the Handle value directly to the Index action, whatever it does it with that value.

Extbase UriBuilder and RealUrl on Ajax Requests

I'm developing a TYPO3 plugin, which outputs a list of records in VueJS.
Therefor I created a controller action which returns requested records as json.
Every record has a property "uri", which holds the uri to its detail page. I generate this uri with the Extbase uriBuilder.
The first records are loaded directly within my list action, where I assign this set of records to the VueJs application directly in the frontend (v-bind:items="my_json_objects").
The next set of records will be loaded on demand by calling my API which returns the same type of records.
Problem: The uri built by uriBuilder returns a rewritten url only in the first case, when objects assigned directly to VueJS. For all items loaded by ajax calls, uribuilder returns the non-rewritten url.
Both actions calls the same method to build the uri:
$item['uri'] = $this->buildShowUri($item);
The method to build the uri:
return $this->uriBuilder
->reset()
->setTargetPageUid(56) // currently static, for testing
->setCreateAbsoluteUri(true)
->uriFor(
'show',
[
'item' => $item,
]
);
Is there a way to trigger url rewriting in this way? Do I need to register the uri somewhere to realurl?
Any hints much appreciated.
How stupid. The uribuilder works, but I forgot to enable realurl in the page type which delivers the json output.
json = PAGE
json {
config {
linkVars = L(0-4)
**tx_realurl_enable = 1**
sys_language_mode = strict
disableAllHeaderCode = 1
debug = 0
no_cache = 1
additionalHeaders {
10 {
header = Content-Type: application/json
replace = 1
}
}
}
typeNum = 129912
10 = USER
10 {
userFunc = TYPO3\CMS\Extbase\Core\Bootstrap->run
extensionName = MyExt
pluginName = Plug
vendorName = Myself
controller = Events
action = apiList
switchableControllerActions {
Event {
1 = apiList
}
}
}
}

ASP. NET Web Api 2 issue - The requested resource does not support HTTP method 'GET'

I am not sure why I am getting a "404 Not Found" on the following GET call to my api (using PostMan)
http://localhost:53840/api/v1/MessageLog/SomeStuff/3
The method in the Controller is as follows
[System.Web.Http.HttpGet]
public string SomeStuff(int s)
{
return "Received input !";
}
The Register method in the WebApiConfig class has the only route as follows :
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/v1/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
But when I change the code to
[System.Web.Http.HttpGet]
public string SomeStuff()
{
return "Received input !";
}
The call http://localhost:53840/api/v1/LogMessage/SomeStuff works and POSTMAN displays the "Recieved input !" string in the response body.
Is there a specific calling convention for passing in int/string etc. (I tried using a [FromUri] without much success) ? I have another POST method in the controlled which takes a JObject and that seems to be working perfectly fine.
It should be something like:
[System.Web.Http.HttpGet]
public string SomeStuff(int id)
{
return "Received input !";
}
Web API matches the parameter by name. In your route template, it is defined as {id} so the action parameter name must match that.
The reason the second one works is because the id is optional and the action matches the template.

OData Routing with Optional Parameter

I have an OData (v3) Web API 2 project that is a wrapper to another wcf web service. The intended client for this odata connection is SharePoint 2013. I am creating CRUD operations within this wrapper and noticed that when sharepoint is asked to delete something it send a request in this format: /Entity(Identity=XX) instead of it's normal /Entity(XX) that i have working normally. I need to be able to handle that request without breaking the other one. Here is my code:
public IHttpActionResult GetSchool([FromODataUri] int key, ODataQueryOptions<School> queryOptions)
{
// validate the query.
try
{
queryOptions.Validate(_validationSettings);
}
catch (ODataException ex)
{
return BadRequest(ex.Message);
}
SchoolDataSource data = new SchoolDataSource();
var result = data.GetByID(key);
return Ok<School>(result);
//return StatusCode(HttpStatusCode.NotImplemented);
}
This works fine for a request for /Schools(1), but not for /Schools(ID=1). i have tried adding:
[Route("Schools(ID={key}")]
And this makes the /Schools(ID=1) route work, but breaks pretty much everything else (406 Errors). i tried adding the above attribute and
[Route("Schools({key})")]to see if i can get them both working, but it doesn't function correctly either. I am very new to this, and was hoping to at least get some direction. Here is my WebApiConfig:
config.MapHttpAttributeRoutes();
config.EnableQuerySupport();
config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
// Web API configuration and services
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<School>("Schools");
builder.DataServiceVersion = new Version("2.0");
config.Routes.MapODataRoute("odata", null, builder.GetEdmModel());
// Web API routes
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Errors i get:
406 if i have the route attribute set. 500 if i dont have the route attribute set. it seems as though my service has no idea how to handle the parameter unless i specify it, but if i do, all calls get 406 errors.
may not be the best approach, but made it work with this class:
public class SharePointRoutingConvention : EntitySetRoutingConvention
{
public override string SelectAction(ODataPath odataPath, HttpControllerContext context,
ILookup<string, HttpActionDescriptor> actionMap)
{
//Gets the entity type
IEdmEntityType entityType = odataPath.EdmType as IEdmEntityType;
//makes sure the format is correct
if (odataPath.PathTemplate == "~/entityset/key")
{
//parses out the path segment (Identity=X)
KeyValuePathSegment segment = odataPath.Segments[1] as KeyValuePathSegment;
//Gets the verb from the request header
string actionName = context.Request.Method.ToString();
// Add keys to route data, so they will bind to action parameters.
KeyValuePathSegment keyValueSegment = odataPath.Segments[1] as KeyValuePathSegment;
//Checks to see if the "Identity=" part is in the url
if (keyValueSegment.Value.Contains("Identity="))
{
//removes the extra text
context.RouteData.Values[ODataRouteConstants.Key] = keyValueSegment.Value.Replace("Identity=", "");
}
else
{
//parses it normally
context.RouteData.Values[ODataRouteConstants.Key] = keyValueSegment.Value;
}
//returns the verb
return actionName;
}
// Not a match.
return null;
}
}
and make the change to the webapiconfig:
var conventions = ODataRoutingConventions.CreateDefault();
//adding the custom odata routing convention
conventions.Insert(0, new SharePointRoutingConvention());
config.Routes.MapODataRoute(
routeName: "odata",
routePrefix: null,//this is so that you can type the base url and get metadata back (http://localhost/)
model: builder.GetEdmModel(),
pathHandler: new DefaultODataPathHandler(),
routingConventions: conventions //this assigns the conventions to the route
);

Web API Routing error from RC to RTM with model binding

Upgrading an rc to rtm web api project
Default parameter binding for simple type parameters is now [FromUri]: In previous releases of ASP.NET Web API the default parameter binding for simple type parameters used model binding. The default parameter binding for simple type parameters is now [FromUri].
I believe is the change that is causing me greif.
Well now I'm not so sure. StrathWeb seems to make me thing it should just work as is.
Given this endpoint
[HttpGet]
public HttpResponseMessage Method(string a, string b)
{
...
}
I generate a url on the client using
#Url.RouteUrl("route", new { httproute = "", controller = "Controller", version = "1" })">
to get it to generate the url for this route.
routes.MapHttpRoute(
name: "route",
routeTemplate: "api/v{version}/{controller}/Method",
defaults: new
{
action = "Method",
controller = "Controller",
version = "1"
});
It creates the url fine. The urls looks like
.../api/v1/Controller/Method?optional=z
.../api/v1/Controller/Method?a=x&b=y&optional=z
It throws a 404 when requested. If I remove the parameters a and b in the api controller then it enters the method just fine.
What is the correct way to make these bind?
if you need 'a' and 'b' to be optional, then you would need to make them optional parameters:
public HttpResponseMessage Method(string a = null, string b = null)