How to properly send action parameter along with query in BreezeJs - breeze

Currently I am calling all data queries as showed on BreezeJs docs / examples:
getEntityList = function (predicate) {
var query = new entityModel.EntityQuery().from("EntityList");
if (predicate)
query = query.where(predicate);
return manager.executeQuery(query);
}
But I want to pass additional parameter to controller action before any queryable result is returned:
[AcceptVerbs("GET")]
public IQueryable<Entity> EntityList(string actionParam) {
//here goes logic that depends on actionParam
//and returns IQueryable<Entity>
}
As we know from documentation:
Breeze converts the query into an OData query string such as this one:
?$filter=IsArchived%20eq%20false&$orderby=CreatedAt
This is where the problem starts. How should I build query to pass param to controller action?
getEntityList = function (predicate, actionParam) {
var query = new entityModel.EntityQuery().from("EntityList");
if (predicate)
query = query.where(predicate);
if(actionParam)
// ???
return manager.executeQuery(query);
}
I already tried setting route to:
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{actionParam}",
defaults: new { query = RouteParameter.Optional }
);
and sending actionParam by applying it in a from section,
var query = new entityModel.EntityQuery()
.from("EntityList/" + encodeURIComponent(actionParam));
but encoding fails on some special chars and bad request is being thrown.
How can I properly send actionParam in such scenario? Please help.

As of v 0.76.1, you can use the EntityQuery.withParameters method to pass additional parameters to any service method. So you can now construct a query like the following that both passes parameters and uses breeze's IQueryable support.
EntityQuery.from("EmployeesFilteredByCountryAndBirthdate")
.withParameters({ BirthDate: "1/1/1960", Country: "USA" })
.where("LastName", "startsWith", "S")
.orderBy("BirthDate");
where your controller method would look something like this:
[HttpGet]
public IQueryable<Employee> EmployeesFilteredByCountryAndBirthdate(DateTime birthDate, string country) {
return ContextProvider.Context.Employees.Where(emp => emp.BirthDate >= birthDate && emp.Country == country);
}
The API docs have more information.

UPDATE: AS OF BREEZE v.0.76.1 THIS IS NO LONGER THE CORRECT ANSWER. BREEZE NOW SUPPORTS PARAMETERS ON QUERIES. SEE THE "withParameters" QUERY CLAUSE.
Support for parameterized queries was added to Breeze thanks in part to this question on SO. Thank you.
This answer used to describe a workaround which is no longer needed. I have revised my answer, eliminating my description of that workaround.

Related

How to spy on a Falcor Data Model constructor in Jasmine 1

I am trying to mock the constructor returned by require('falcor'); I have two routes and one calls the other route using var dataModel = new falcor({source: this});
Code looks like so
var falcor = require('falcor');
module.exports = {
route: 'items',
get: function (pathSet) {
var dataModel = new falcor({source: this});
var ids = '1';
dataModel.get('itemIds', ids).then(function (response) {
// Code I can't get to in Jasmine 1.x tests
});
}
}
I want the constructor to return a spy so I can call Promise.resolve and send back mock data for testing purposes. I'm not sure how to do this without moving the call into another module that I can mock separately. I think some questions that may help me here are
Where do I find the constructor functions defined by modules like falcor? I have tried looking into the 'global' object but have had no luck. If I did find this constructor, could I just replace it with a spyOn(global, 'falcor').andReturn(/* object with a mocked get method*/); ?
Is there a better way that makes testing easier to call a route from inside another route?
Thanks for any help.
To start w/ question 2: yes, to get data from another route, return refs to that route. Don't instantiate another model w/i the route. E.g.
const itemsRoute = {
route: 'items[{keys:indices}]',
get(pathSet) {
// map indices to item ids, likely via DB call
// in this case, SomeDataModel handles network requests to your data store and returns a promise
return SomeDataModel.getItemsByIndices(pathSet.indices)
.then(ids => ids.map((id, idx) => ({
path: ['items', pathSet.indices[idx]],
value: {
$type: 'ref',
value: ['itemById', id]
}
})));
}
};
const itemByIdRoute = {
route: 'itemById[{keys:ids}].name',
get(pathSet) {
return new Promise((resolve) => {
resolve(pathSet.idx.map(id => ({
path: ['itemById', id, 'name'],
value: {
$type: 'atom',
value: `I am item w/ id ${id}`
}
})));
});
}
};
When a request comes in for (e.g.) [items, 2, name], it will hit the first items route, resolve [items, 2] to [itemById, someId], and resolve the remaining name key in the itemsById route.
As for question 1: rather than mocking falcor, just mock whatever you are using to make the remote call to your data source. In the above case, just mock SomeDataModel

MVC routing a repeatable pattern?

A design goal for a website I'm working on is to keep the URL in the browser in a state where the user can copy it, and the link can be used from another browser/user/machine to return to the spot that the url was copied. (The actual changes will happen via AJAX, but the URL will change to reflect where they are.)
Example: If you were on the customer page looking at customer 123, and had details pulled up on their order #456, and full details on line 6 of this order, your url could simply be /customer/123/456/6
The challenge comes with a second feature: Users can add UI columns (analogous to adding a new tab in a tab view, or a new document in an MDI app) Each column can easily generate a routable url, but I need the url to reflect one or more columns. (E.G. User has both /customer/123/456/6 and /customer/333/55/2 in two side by side columns)
In a perfect world, I'd like the url to be /customer/123/456/6/customer/333/55/2 for the above scenario, but I don't know if MVC routing can handle repetitive patterns, or, if so, how it is done.
Can this be done via routing? If not is there a way to get this type of one-or-more functionality from Url?
You could create a custom route handler (see my previous answer) or derive from a RouteBase like NightOwl888 suggested. Another approach would be to simply use a model binder and a model binder attribute.
public class CustomerInvoiceLineAttribute : CustomModelBinderAttribute
{
public override IModelBinder GetBinder()
{
return new CustomerInvoiceLineModelBinder();
}
}
public class CustomerInvoiceLineModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var path = (string)bindingContext.ValueProvider.GetValue(bindingContext.ModelName).AttemptedValue;
var data = path.Split(new[] { "/customer/" }, StringSplitOptions.RemoveEmptyEntries);
return data.Select(d =>
{
var rawInfo = d.Split('/');
return new CustomerInvoiceLine
{
CustomerId = int.Parse(rawInfo[0]),
InvoiceId = int.Parse(rawInfo[1]),
Line = int.Parse(rawInfo[2])
};
});
}
}
You define your route by specifying a star route data. This mean that the route parameter will contains everything following the action
routes.MapRoute(
name: "CustomerViewer",
url: "customer/{*customerInfo}",
defaults: new { controller = "Customer", action = "Index" });
Then in your controller, you bind your parameter with the same name as the star route parameter using the custom model binder defined above:
public ActionResult Index([CustomerInvoiceLine] IEnumerable<CustomerInvoiceLine> customerInfo)
{
return View();
}
You will need to add validation during the parsing and probably security too, so that a customer cannot read the invoice of other customers.
Also know that URL have a maximum length of 2000 characters.
You can do this with the built-in routing as long as you don't anticipate that any of your patterns will repeat or have optional parameters that don't appear in the same segment of the URL as other optional parameters.
It is possible to use routing with optional parameters by factoring out all of the permutations, but if you ask me it is much simpler to use the query string for this purpose.
NOTE: By definition, a URL must be unique. So you must manually ensure your URLs don't have any collisions. The simplest way to do this is by matching the page with the path (route) and adding this extra information as query string values. That way you don't have to concern yourself with accidentally making routes that are exactly the same.
However, if you insist on using a route for this purpose, you should probably put your URLs in a database in a field with a unique constraint to ensure they are unique.
For the most advanced customization of routing, subclass RouteBase or Route. This allows you to map any URL to a set of route values and map the route values back to the same URL, which lets you use it in an ActionLink or RouteLink to build the URLs for your views and controllers.
public class CustomPageRoute : RouteBase
{
// This matches the incoming URL and translates it into RouteData
// (typically a set of key value pairs in the RouteData.Values dictionary)
public override RouteData GetRouteData(HttpContextBase httpContext)
{
RouteData result = null;
// Trim the leading slash
var path = httpContext.Request.Path.Substring(1);
if (/* the path matches your route logic */)
{
result = new RouteData(this, new MvcRouteHandler());
result.Values["controller"] = "MyController";
result.Values["action"] = "MyAction";
// Any other route values to match your action...
}
// IMPORTANT: Always return null if there is no match.
// This tells .NET routing to check the next route that is registered.
return result;
}
// This builds the URL for ActionLink and RouteLink
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
VirtualPathData result = null;
if (/* all of the expected route values match the request (the values parameter) */)
{
result = new VirtualPathData(this, page.VirtualPath);
}
// IMPORTANT: Always return null if there is no match.
// This tells .NET routing to check the next route that is registered.
return result;
}
}
Usage
routes.Add(
name: "CustomPage",
item: new CustomPageRoute());
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);

Asp.net mvc sql query as parameter to controller action method

I am trying to send a query like below from UI to controller.
name='abc' and title='def'
I am trying to use lambda expression on controller to filter this query. But I am struggling hard to pass ui query to controller and make it as lambda expression.
Could you please throw some idea with example where i can pass sql query(string) as parameter and use it in controller action method as lambda expression. Any link or logic should be fine for me to try further.
[HttpGet]
public virtual ActionResult QueriedProjects(string builtQuery)
{
var Helpera = new Helpera(true);
var myProjectDetails = Helpera.myProjectDetails (null);
var myProjectDetails = new myProjectDetails ()
{ GetMyProjectDetails = myProjectDetails };
return View(myProjectDeails)
}
UI
on button click I am generating a query as string with entered values in query builder
Generated String: name='abc' and title= 'def'
If you pass values like this:
Contoller/QueriedProjects?name=abc&title=def
You need to have 2 parameters in your controller method like:
public virtual ActionResult QueriedProjects(string name, string title)

MVC query string with ICollection or array

My MVC 4 web service has a new use case. I need to pass a list of arguments on the query string to a Web API, e.g.,
http://host/SomeWebApi?arg=x&arg=y
I previously did this simply and easily in my Web Site controller using ICollection<string> arg as a parameter in the controller. That is working now, but it is a Web page, as opposed to an API.
Now I am knocking my head against the wall trying to get a Web API version of the same thing to work. I've made a simple test interface, below, and the collection arg is always null. I've tried List<string> and string[] as types as well. What am I overlooking?
Route register:
config.Routes.MapHttpRoute(
name: "Experiment",
routeTemplate: "Args",
defaults: new
{
controller = "Storage",
action = "GetArgTest",
suite = UrlParameter.Optional,
},
constraints: new
{
httpMethod = new HttpMethodConstraint(new HttpMethod[] { new HttpMethod("GET") })
}
);
Web API controller code:
public string GetArgTest(ICollection<string> suite)
{
if (suite == null)
{
return "suite is NULL";
}
else
{
return "suite is NON-NULL";
}
}
Test query string that results in "suite is NULL":
http://localhost:5101/Args?suite=1&suite=2
I came across this answer ApiController Action Failing to parse array from querystring and discovered that to resolve this you need to put in the FromUriAttribute. So in your example:
public string GetArgTest([FromUri]ICollection<string> suite)
{
}
This makes sense I guess, typically with an API controller you'd expect to be doing more POST and PUT requests for which you'd typically include post data rather than a QueryString.

How to get GET parameters with ASP.NET MVC ApiController

I feel a bit absurd asking this but I can't find a way to get parameters for a get request at
/api/foo?sort=name for instance.
In the ApiController class, I gave a public string Get(). Putting Get(string sort) makes /api/foo a bad request. Request instance in the ApiController is of type System.Net.Http.HttpRequestMessage. It doesn't have a QueryString or Parameters property or anything.
The ApiController is designed to work without the HttpContext object (making it portable, and allowing it to be hosted outside of IIS).
You can still access the query string parameters, but it is done through the following property:
Request.GetQueryNameValuePairs()
Here's an example loop through all the values:
foreach (var parameter in Request.GetQueryNameValuePairs())
{
var key = parameter.Key;
var value = parameter.Value;
}
You could just use
HttpContext.Current.Request.QueryString
Here's an example that gets the querystring q from the request and uses it to query accounts:
var q = Request.GetQueryNameValuePairs().Where(nv => nv.Key =="q").Select(nv => nv.Value).FirstOrDefault();
if (q != null && q != string.Empty)
{
var result = accounts.Where(a=>a.Name.ToLower().StartsWith(q.ToLower()));
return result;
}
else
{
throw new Exception("Please specify a search query");
}
This can be called then like this:
url/api/Accounts?q=p
Get all querystring name/value pairs into a variable:
IEnumerable<KeyValuePair<string, string>> queryString = request.GetQueryNameValuePairs();
Then extract a specified querystring parameter
string value = queryString.Where(nv => nv.Key == "parameterNameGoesHere").Select(nv => nv.Value).FirstOrDefault();
You can also use the following
var value = request.GetQueryNameValuePairs().Where(m => m.Key == "paramName").SingleOrDefault().Value;
if we have a proper model for that request
for example
public class JustModel
{
public int Id {get;set;}
public int Age {gets;set;}
}
and query like this
/api/foo?id=1&Age=10
You could just use [FromUri] attribute
For example
public IHttpActionResult GetAge([FromUri] JustModel model){}
You're trying to build an OData webservice? If so, just return an IQueryable, and the Web API will do the rest.
Adding a default value does the job:
public string Get(string sort="")

Resources