LINQ query cannot evaluate GET parameter in StartsWith - c#

I'm trying to make a query through a GET request (in Angular.js) using .NET Web API and Entity Framework using LINQ and MySQL db.
http://localhost/ProductsApp/api/clientes/GetByName/M
The problems is that the parameter passed by GET is not evaluated and I don't get any results (even though name is being set correctly to "M" according to the debugger).
However, if I hardcode a string, I get the expected result.
[ActionName("GetByName")]
public IEnumerable<cliente> GetByName(string name)
{
var query = from c in context.clientes where c.nome.StartsWith(name) select c;
var query2 = from c in context.clientes where c.nome.StartsWith("M") select c;
var query3 = context.clientes.Where(c => c.nome.StartsWith(name));
var query4 = context.clientes.Where(c => c.nome.StartsWith("M"));
return query.ToList();
}
My WebApiConfig.cs has the following lines:
config.Routes.MapHttpRoute(
name: "ActionApi",
routeTemplate: "api/{controller}/{action}/{name}",
defaults: null
);
I suppose that LINQ resolves the variables when it is executed, as hinted below (I removed the unimportant part of the query):
query.ToString()
"SELECT [...] WHERE `Extent1`.`nome` LIKE 'p__linq__0%'"
query2.ToString()
"SELECT [...] WHERE `Extent1`.`nome` LIKE 'M%'"
query3.ToString()
"SELECT [...] WHERE `Extent1`.`nome` LIKE 'p__linq__0%'"
query4.ToString()
"SELECT [...] WHERE `Extent1`.`nome` LIKE 'M%'"
Both query2 and query4 return correct values while query and query3 do not. Why? How can I make it work?

This is a reported bug with MySQL Entity Framework 6.9.5
Bug #74918 : Incorrect query result with Entity Framework 6:
https://bugs.mysql.com/bug.php?id=74918
It has been fixed in MySQL Connector/Net 6.7.7 / 6.8.5 / 6.9.6 releases.
Changelog:
With Entity Framework 6, passing in a string reference to the "StartWith"
clause would return incorrect results.
Alternatively, a workaround is to use .Substring(0) which forces Entity not to use LIKE (might affect performance).
var query = context.clientes.Where(c => c.nome.StartsWith(name.Substring(0)));

Your name parameter does not equal to "M", it is possible that this value is null. WebApi probably expects a "id" parameter and not "name"
Add a route attribute to make sure that the correct value is passed to the name parameter
[Route("api/clientes/GetByName/{name}")]
public IEnumerable<cliente> GetByName(string name)
{
....
}

If you have more than one GET method with parameter so you have to change your WebApiConfig.cs
config.Routes.MapHttpRoute(
name: "ActionApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional });
or another way to do this you have to hardcode your controller and action in WebApiConfig.cs like this
config.Routes.MapHttpRoute(
name: "GetApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { action = "GetByName", controller = "Your Controller" });
Your Action:
[HttpGet]
public IEnumerable<cliente> GetByName(string name)
{
}
Call it like this
$.ajax({
type: 'GET',
url: 'localhost/ProductsApp/api/clientes/GetByName?Name='+ M,
data: JSON.stringify({}),
contentType: 'application/json',
dataType: 'json',
success: function (data) {
}
}
});

Related

MVC Route Parameters Are Null

I'm receiving the following error that my default route parameters are null. I've used this same code on a Controller Action that didn't have any parameters in the URL and it worked fine. I know that my custom route is being called but I don't understand why startIndex and pageSize are showing up null in the action.
Error:
The parameters dictionary contains a null entry for parameter 'startIndex' of non-nullable type 'System.Int32' for method 'System.Web.Mvc.ActionResult ViewVcByStatus(System.String, Int32, Int32)' in 'AEO.WorkOrder.WebUI.Controllers.VendorComplianceController'. An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter.
Parameter name: parameters
Controller:
public ActionResult ViewVcByStatus(string status, int startIndex, int pageSize) { ... }
Route:
routes.MapRoute("ViewVcByStatus", "ViewVcByStatus/{status}",
new
{
controller = "VendorCompliance",
action = "ViewVcByStatus",
startIndex = 0,
pageSize = WebConfigurationManager.AppSettings["PageSize"],
});
Link:
<a href="VendorCompliance/ViewVcByStatus?status=PROCESSED">
Also tried this link which produces the same error:
<a href="VendorCompliance/ViewVcByStatus/PROCESSED">
Try this.
public ActionResult ViewVcByStatus(string status, int? pageSize, int?startIndex)
{
return View();
}
Route.config
routes.MapRoute(
name: "ViewVcByStatus",
url: "ViewVcByStatus/{status}",
defaults: new { controller = "VendorCompliance", action = "ViewVcByStatus", startIndex = UrlParameter.Optional, pageSize = UrlParameter.Optional });
optional parameters should be declared optional in routeconfig and mark them int? in your action method, This will do the work for you. Hope this helps.This solution will work with your url pattern in your question "http://localhost:53290/VendorCompliance/ViewVcByStatus?status=PROCESSED".
Send the startIndex and pageSize with the link(I hardcoded it, use parameters instead), your actionresult is expecting all parameters that the link needs to provide, and the MapRoute will probably fall through to default Route because it can´t match it with any other route matching the one parameter you provided
<a href="VendorCompliance/ViewVcByStatus?status=PROCESSED&startIndex=0&pageSize=0">

C# MVC - Passing URL parameters to methods [duplicate]

This question already has an answer here:
how to pass parameter with #url.action to controller
(1 answer)
Closed 6 years ago.
Having trouble passing url paramaters to my method in my MVC project.
Currently experiencing the following error:
The parameters dictionary contains a null entry for parameter 'id' of non-nullable type 'System.Int32' for method 'System.Web.Mvc.ActionResult SinglePost(Int32)' in 'mybloggywog.Controllers.PostController'. An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter.
Call is below:
#{
foreach (var article in ViewBag.DisplayPosts)
{
<a class="ArticleLink" href="#Url.Action("SinglePost", "Post", article.Id)">#article.Title</a>
<p>#article.CreatedOn</p>
<br/>
}
}
Routeconfig:
routes.MapRoute(
"Post",
"post/{id}",
new { controller = "Post", action = "SinglePost", id = UrlParameter.Optional }
);
And method:
public ActionResult SinglePost(int id)
{
var entities = new blogEntities();
var post = entities.Set<Post>();
var postToDisplay = (from p in post
where p.Id == id
select p);
ViewBag.SinglePost = postToDisplay;
return View();
}
Try changing it to an anonymous object:
#Url.Action("SinglePost", "Post", new { id = article.Id })

Multiple optional parameters routing

I have the following route definition in my webapi project. I have problem one of the parameter is not passed. eg;
when i call /Controller/Action/param2/startdate/enddate the value i passed for param2 is taken for param1 and vice versa.The problem is, the RoutingModule can not detect that the provided route value is for param2 not param1
It works if i use querystring in the url but doesn't want to use querystring. Appreciate your help.
Is there any way to achieve what i expect?
config.Routes.MapHttpRoute(
name: "RetrieveHistory",
routeTemplate: "{controller}/{action}/{param1}/{param2}/{startDate}/{endDate}",
defaults: new
{
controller = "Vend",
action = "RetrieveUtrnHistory",
param1 = RouteParameter.Optional,
param2 = RouteParameter.Optional,
starDate = RouteParameter.Optional,
endDate = RouteParameter.Optional
});
Thanks
To solve your problem you must take into account this things:
you can register more than one route. The first registered route that can handle an URL, will handle it.
you can use something apart from slash / as separator, to make parts of a route distinguishable
you can use parameter constraints, usually regular expressions, to make it easier to discover if a parameter is of one or other kind
you can specify default values for your parameters, and, if you do so, the action method must have default values for them (unless MVC, that only requires them to be nullable or of reference type)
As you didn't tell how your URL looks like I'll show you my own examples.
Let's suppose that you have a TestController Web API Controller class with an action like this:
// GET api/Test/TestAction/ ...
[HttpGet]
public object TestAction(int param1, DateTime startDate, DateTime endDate,
int? param2 = null)
{
return new
{
param1 = param1,
param2 = param2,
startDate = startDate,
endDate = endDate
}.ToString();
}
NOTE: with the default routes a Web API controller's method named GetXxx is available to HTTP GET, a method named PostXxx is available to HTTP POST and so on. However, once you include Controller and Action in the URL template, you must use the [HttpXxx] attributes to make your method available to the required HTTP method.
Optional parameter(s) in the middle
In this first example, I suppose that both param1, and param2 are integer numbers, and stardDate and endDate are dates:
http://myhost/api/Mycontroller/Myaction/12/22/2014-12-01/2014-12-31
http://myhost/api/Mycontroller/Myaction/22/2014-12-01/2014-12-31
If you want the first URL to match parameters like these:
param1 = 12; param2 = 22; startDate = 2014-12-01; endData = 2014-12-31
and the second like these:
param1 = 12; param2 = null; startDate = 2014-12-01; endData = 2014-12-31
You need to register two routes, one that will match each possible URL structure, i.e.
// for the 1st
routeTemplate: "api/{controller}/{action}/{param1}/{param2}/{startDate}/{endDate}"
// for the 2nd
routeTemplate: "api/{controller}/{action}/{param1}/{startDate}/{endDate}"
Note that, in this case, both routes are mutually exclusive, i.e. a single URL can match only one of the routes, so you can register them in any other.
However, you must notice that the second URL doesn't define a value for param2, and the TestAction method requires it. This wouldn't work: you must include a default value for this parameter, both in the controler's method and in the route registration:
action parameter int? param2 = null (C# requires optional parameter to be the last ones).
the route must include the default: defaults: new { param2 = RouteParameter.Optional }
This is the way to solve the optional parameter in the middle problem. In general, you'll need to define several routes, depending on how many optional parameters there are, and declare this parameters, with default values, in the Web API action method.
NOTE: as I wrote above, in MVC you don't need to specify a default value in the method parameter for this to work
Parameter constraints
Specifying constrains for a route parameter has two consequences:
There's a warranty that the parameter value has the expected format
Most importantly, the route will only handle the URL if the format is the expected one. So this helps you make your URL more selective, thus making it more flexible.
You simply need to add a constraint parameter on the route registration, like this:
config.Routes.MapHttpRoute(
name: "Multiparam2",
routeTemplate: "api/{controller}/{action}/{param1}/{param2}/{startDate}/{endDate}",
constraints: new
{
startDate = #"20\d\d-[0-1]?\d-[0-3]?\d", // regex
endDate = #"20\d\d-[0-1]?\d-[0-3]?\d" // regex
},
defaults: new object { }
);
Note that it's necessary to specify a defaults parameter, even if it's empty.
NOTE: the constraints in this case are a regex that only matches dates in the year 20XX, the month expressed as a single digit, or as 0x or 1x, and the date as a single digit or 0x, 1x, 2x or 3x, separated by dashes. So this regex will match 2012-1-1 or 2015-12-30, but not 1920-12-30. You should adapt the regex to your needs.
Optional parameters at the end
By this time I've explained how to support optional parameters, and how to specify formats (constraints) for them, to match a route template.
The usual way to define optional parameters is to do it at the end of the URL template, and, in this case, if there are missing params in a route, they must be all at the end of the route. (Compare this with optional in the middle: they require different routes).
In this example, if you want to make optional the param2, and the startDate and endDate, you must define them in the route registration, and set default parameter values in the action method.
The final code would look like this:
[HttpGet]
public object TestAction(int param1, int? param2 = null, DateTime? startDate = null,
DateTime? endDate = null)
{
return new
{
param1 = param1,
param2 = param2,
startDate = startDate,
endDate = endDate
}.ToString();
}
config.Routes.MapHttpRoute(
name: "Multiparam1",
routeTemplate: "api/{controller}/{action}/{param1}/{startDate}/{endDate}",
constraints: new
{
startDate = #"20\d\d-[0-1]?\d-[0-3]?\d",
endDate = #"20\d\d-[0-1]?\d-[0-3]?\d"
},
defaults: new
{
param2 = RouteParameter.Optional,
startDate = RouteParameter.Optional,
endDate = RouteParameter.Optional
}
);
config.Routes.MapHttpRoute(
name: "Multiparam2",
routeTemplate: "api/{controller}/{action}/{param1}/{param2}/{startDate}/{endDate}",
constraints: new
{
startDate = #"(20\d\d-[0-1]?\d-[0-3]?\d)?",
endDate = #"(20\d\d-[0-1]?\d-[0-3]?\d)?"
},
defaults: new
{
startDate = RouteParameter.Optional,
endDate = RouteParameter.Optional
}
);
Note, that, in this case:
the routes could be mismatched, so they must be registered in the right order, as shown. If you registered first the Multiparam2 route, it would erroneously handle an URL like this: http://localhost:1179/api/test/testaction/1/2014-12-12/2015-1-1, with param1=1; param2="2014-12-12"; startDate="2015-1-1". (You could avoid this with an additional constraint on param2 that only accepts numbers, like param2=#"\d+")
the action must have default values for startDate and endDate.
Conclusions
You can handle default parameters in different positions by carefully:
registering routes in the right order
define default parameters in the route, and also default values in the controller's action
use constraints
If you plan carefully how your routes look like, you can get what you need with a few routes and optional parameters.
JotaBe answer was nice and complete. Just you have to consider if parameters are optional you have to write routeTemplate with the order from lowest parameters to highest.
Just like :
// for the 1st
routeTemplate: "api/{controller}/{action}/{param1}/{startDate}/{endDate}"
// for the 2nd
routeTemplate: "api/{controller}/{action}/{param1}/{param2}/{startDate}/{endDate}"

MVC Routing System

What means the operator : when calling methods for example ? I see that it maps the name of the parameter's name inside the method. What means name : "Default" how to interpret it ? What is the name of this kind of calling method ?
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
That is called "named arguments", and I would not call it an operator. (There is a ?: operator though)
Named and Optional Arguments (C# Programming Guide)

Web.API MapHttpRoute parameters

I'm having problems with my Web.API routing. I have the following two routes:
config.Routes.MapHttpRoute(
name: "MethodOne",
routeTemplate: "api/{controller}/{action}/{id}/{type}",
defaults: new { id = RouteParameter.Optional, type = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "MethodTwo",
routeTemplate: "api/{controller}/{action}/{directory}/{report}",
defaults: new { directory = RouteParameter.Optional, report = RouteParameter.Optional }
);
And in my controller these two methods:
[HttpGet]
[ActionName("methodone")]
public string MethodOne(string id, string type)
{
return string.Empty;
}
[HttpGet]
[ActionName("methodtwo")]
public string MethodTwo(string directory, string report)
{
return string.Empty;
}
These two seemingly cannot live side by side. If I comment out the MethodOne route in WebApiConfig, the MethodTwo route works. Commenting MethodTwo route allows MethodOne to work. Leaving both uncommented, MethodOne works, but not MethodTwo.
I was hoping to use one route for both of these then it seems they would have to have the same parameter names. Who writes methods with generic parameter names? Bad. I really don't want my methods to have the same parameter names (like p1, p2, p3), so I thought I could create a route just for the new method. But even this doesn't seem to work.
I really miss the WebGet(UriTemplate="") from WCF rest.
I have one controller that has many methods, some with 1, 2, 3 or even more parameters. I can't believe I cant use meaningful parameter names with the MapHttpRoute approach.
I could comment that stuff out entirely and use WebGet() … but before I got there I wanted to see if I'm missing something.
The reason you are seeing this problem is because your first route will match both requests. The id and type token in the URL will match both requests because when the route is being run, it will try parse the URL and match each segment against your URL.
So your first route will happily match both requests as follows.
~/methodone/1/mytype => action = methodone, id = 1, and type = mytype
~/methodtwo/directory/report => action = methodtwo, id = directory, and type = report
To work around this, you should use route like
config.Routes.MapHttpRoute(
name: "MethodOne",
routeTemplate: "api/{controller}/methodone/{id}/{type}",
defaults: new { id = RouteParameter.Optional, type = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "MethodTwo",
routeTemplate: "api/{controller}/methodtwo/{directory}/{report}",
defaults: new { directory = RouteParameter.Optional, report = RouteParameter.Optional }
);
Even if you use WebGet, you might need to do something similarly to disambiguous those two methods I believe.
You can choose to pass the parameters in the query string like /MethodTwo?directory=a&report=b, but if you'd rather they show up in the path, this looks like a good candidate for attribute-based routing. Filip has a great post about it here:
http://www.strathweb.com/2012/05/attribute-based-routing-in-asp-net-web-api/
From http://www.asp.net/web-api/overview/web-api-routing-and-actions/routing-and-action-selection
You can also provide constraints, which restrict how a URI segment can
match a placeholder:
constraints: new { id = #"\d+" } // Only matches if "id" is one or
more digits.
Adding this constraint to "MethodOne" (api/{controller}/{action}/{id}/{type}) would mean that numbers it only matches if {id} is a number otherwise it would match "MethodTwo" ("api/{controller}/{action}/{directory}/{report}").

Categories

Resources