Cleanest way to implement multiple parameters filters in a REST API - entity-framework

I am currently implementing a RESTFUL API that provides endpoints to interface with a database .
I want to implement filtering in my API , but I need to provide an endpoint that can provide a way to apply filtering on a table using all the table's columns.
I've found some patterns such as :
GET /api/ressource?param1=value1,param2=value2...paramN=valueN
param1,param2...param N being my table columns and the values.
I've also found another pattern that consists of send a JSON object that represents the query .
To filter on a field, simply add that field and its value to the query :
GET /app/items
{
"items": [
{
"param1": "value1",
"param2": "value",
"param N": "value N"
}
]
}
I'm looking for the best practice to achieve this .
I'm using EF Core with ASP.NET Core for implementing this.

Firstly be cautious about filtering on everything/anything. Base the available filters on what users will need and expand from that depending on demand. Less code to write, less complexity, fewer indexes needed on the DB side, better performance.
That said, the approach I use for pages that have a significant number of filters is to use an enumeration server side where my criteria fields are passed back their enumeration value (number) to provide on the request. So a filter field would comprise of a name, default or applicable values, and an enumeration value to use when passing an entered or selected value back to the search. The requesting code creates a JSON object with the applied filters and Base64's it to send in the request:
I.e.
{
p1: "Jake",
p2: "8"
}
The query string looks like:
.../api/customer/search?filters=XHgde0023GRw....
On the server side I extract the Base64 then parse it as a Dictionary<string,string> to feed to the filter parsing. For example given that the criteria was for searching for a child using name and age:
// this is the search filter keys, these (int) values are passed to the search client for each filter field.
public enum FilterKeys
{
None = 0,
Name,
Age,
ParentName
}
public JsonResult Search(string filters)
{
string filterJson = Encoding.UTF8.GetString(Convert.FromBase64String(filters));
var filterData = JsonConvert.DeserializeObject<Dictionary<string, string>>(filterJson);
using (var context = new TestDbContext())
{
var query = context.Children.AsQueryable();
foreach (var filter in filterData)
query = filterChildren(query, filter.Key, filter.Value);
var results = query.ToList(); //example fetch.
// TODO: Get the results, package up view models, and return...
}
}
private IQueryable<Child> filterChildren(IQueryable<Child> query, string key, string value)
{
var filterKey = parseFilterKey(key);
if (filterKey == FilterKeys.None)
return query;
switch (filterKey)
{
case FilterKeys.Name:
query = query.Where(x => x.Name == value);
break;
case FilterKeys.Age:
DateTime birthDateStart = DateTime.Today.AddYears((int.Parse(value) + 1) * -1);
DateTime birthDateEnd = birthDateStart.AddYears(1);
query = query.Where(x => x.BirthDate <= birthDateEnd && x.BirthDate >= birthDateStart);
break;
}
return query;
}
private FilterKeys parseFilterKey(string key)
{
FilterKeys filterKey = FilterKeys.None;
Enum.TryParse(key.Substring(1), out filterKey);
return filterKey;
}
You can use strings and constants to avoid the enum parsing, however I find enums are readable and keep the sent payload a little more compact. The above is a simplified example and obviously needs error checking. The implementation code for complex filter conditions such as the age to birth date above would better be suited as a separate method, but it should give you some ideas. You can search for children by name, and/or age, and/or parent's name for example.

I have invented and found it useful to combine a few filters into one type for example CommonFilters and make this type parseable from string:
[TypeConverter(typeof(CommonFiltersTypeConverter))]
public class CommonFilters
{
public PageOptions PageOptions { get; set; }
public Range<decimal> Amount { get; set; }
//... other filters
[JsonIgnore]
public bool HasAny => Amount.HasValue || PageOptions!=null;
public static bool TryParse(string str, out CommonFilters result)
{
result = new CommonFilters();
if (string.IsNullOrEmpty(str))
return false;
var parts = str.Split(new[] { ' ', ';' }, StringSplitOptions.RemoveEmptyEntries);
foreach (var part in parts)
{
if (part.StartsWith("amount:") && Range<decimal>.TryParse(part.Substring(7), out Range<decimal> amount))
{
result.Amount = amount;
continue;
}
if (part.StartsWith("page-options:") && PageOptions.TryParse(part.Substring(13), out PageOptions pageOptions))
{
result.PageOptions = pageOptions;
continue;
}
//etc.
}
return result.HasAny;
}
public static implicit operator CommonFilters(string str)
{
if (TryParse(str, out CommonFilters res))
return res;
return null;
}
}
public class CommonFiltersTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
public override object ConvertFrom(ITypeDescriptorContext context,
CultureInfo culture, object value)
{
if (value is string str)
{
if (CommonFilters.TryParse(str, out CommonFilters obj))
{
return obj;
}
}
return base.ConvertFrom(context, culture, value);
}
}
the request looks like this:
public class GetOrdersRequest
{
[DefaultValue("page-options:50;amount:0.001-1000;min-qty:10")]
public CommonFilters Filters { get; set; }
//...other stuff
}
In this way you reduce the number of input request parameters, especially when some queries don't care about all filters
If you use swagger map this type as string:
c.MapTypeAsString<CommonFilters>();
public static void MapTypeAsString<T>(this SwaggerGenOptions swaggerGenOptions)
{
swaggerGenOptions.MapType(typeof(T), () => new OpenApiSchema(){Type = "string"});
}

Related

Flutter: find item in list of maps and update it

I have 3 nested collections (portfolios, rents and leases) and I get the last one like this:
getRentLeases() {
final portfolio = list.where((portfolio) {
return portfolio['documentID'].contains(currentPortfolioId)
? true
: false;
}).toList();
final rent = portfolio[0]['rents'].where((rent) {
return rent['documentID'].contains(currentRentId) ? true : false;
}).toList();
return rent[0]['leases'];
}
as you see I always have the ID for previous collection saved.
To find a specific lease I could follow similar approach:
var lease = getRentLeases().where((lease) {
return lease['documentID'].contains(newLease['documentID'])
? true
: false;
}).toList();
but how could I update it? Something like this doesn't work:
lease = newLease;
? true : false is not necessary as contains already gives you a boolean value.
If you only expect one item to be returned by the where method, then you should go for singleWhere as it directly gives you the item instead of the list. If none of these or multiple items satisfy the condition, you get an exception.
You should separate getRentLeases into two methods. In this case its even better to have methods returning the index of the relevant item.
Please consider that I only used dynamic because I don't have access to the data types. You should instead type the variables.
Code:
int getIndexOfPortFolio(String portfolioId) {
return list
.indexWhere(portfolio => portfolio['documentID'].contains(portfolioId));
}
int getIndexOfRent(dynamic portfolio, String rentId) {
return portfolio['rents']
.indexWhere(rent => rent['documentID'].toString().contains(rentId));
}
int getIndexOfLease(dynamic rent, String leaseId) {
return rent['leases']
.indexWhere(lease => lease['documentID'].toString().contains(leaseId));
}
And then you can update your object like this:
void updateLease(String portfolioId, String rentId, String oldLeaseId, dynamic newLease) {
int portFolioIndex = getIndexOfPortFolio(portfolioId);
var portFolio = list[portFolioIndex];
int rentIndex = getIndexOfRent(portFolio, rentId);
var rent = portFolio["rents"][rentIndex];
int leaseIndex = getIndexOfLease(rent, oldLeaseId);
rent["leases"][leaseIndex] = newLease;
}

Updating related Phone entities with custom tag helper

As my application currently sits, each AppUser may (or may not) have 3 phone numbers (UserPhones). One of each type (Mobile, Home, Other).
The following Tag Helper works great (Thanks #itminus).
Calling code from Razor Page:
<user-phones phones="#Model.UserPhones"
asp-for="#Model.UserPhones"
prop-name-to-edit="PhoneNumber"
types-to-edit="new EnumPhoneType[] { EnumPhoneType.Mobile,
EnumPhoneType.Other }" />
Code:
public class UserPhonesTagHelper : TagHelper
{
private readonly IHtmlGenerator _htmlGenerator;
private const string ForAttributeName = "asp-for";
[HtmlAttributeName("expression-filter")]
public Func<string, string> ExpressionFilter { get; set; } = e => e;
public List<UserPhones> Phones { get; set; }
public EnumPhoneType[] TypesToEdit { get; set; }
public string PropNameToEdit { get; set; }
[ViewContext]
public ViewContext ViewContext { set; get; }
[HtmlAttributeName(ForAttributeName)]
public ModelExpression For { get; set; }
public UserPhonesTagHelper(IHtmlGenerator htmlGenerator)
{
_htmlGenerator = htmlGenerator;
}
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
output.TagName = null; //DO NOT WANT AN OUTTER HTML ELEMENT
for (int i = 0; i < Phones.Count(); i++)
{
var props = typeof(UserPhones).GetProperties();
var pType = props.Single(z => z.Name == "Type");
var pTypeVal = pType.GetValue(Phones[i]);
EnumPhoneType eType = (EnumPhoneType) Enum.Parse(typeof(EnumPhoneType), pTypeVal.ToString());
string lVal = null;
switch (eType)
{
case EnumPhoneType.Home:
lVal = "Home Phone";
break;
case EnumPhoneType.Mobile:
lVal = "Mobile Phone";
break;
case EnumPhoneType.Other:
lVal = "Other Phone";
break;
default:
break;
}
//LOOP ALL PROPERTIES
foreach (var pi in props)
{
var v = pi.GetValue(Phones[i]);
var expression = this.ExpressionFilter(For.Name + $"[{i}].{pi.Name}");
var explorer = For.ModelExplorer.GetExplorerForExpression(typeof(IList<UserPhones>), o => v);
//IF REQUESTED TYPE AND PROPERTY SPECIFIED
if (pi.Name.NormalizeString() == PropNameToEdit.NormalizeString() && TypesToEdit.Contains(eType))
{
TagBuilder gridItem = new TagBuilder("div");
gridItem.Attributes.Add("class", "rvt-grid__item");
gridItem.InnerHtml.AppendHtml(BuildLabel(explorer, expression, lVal));
gridItem.InnerHtml.AppendHtml(BuildTextBox(explorer, expression, v.ToString()));
output.Content.AppendHtml(gridItem);
}
else //ADD HIDDEN FIELD SO BOUND PROPERLY
output.Content.AppendHtml(BuildHidden(explorer, expression, v.ToString()));
}
}
}
private TagBuilder BuildTextBox(ModelExplorer explorer, string expression, string v)
{
return _htmlGenerator.GenerateTextBox(ViewContext, explorer, expression, v, null, new { #class = "form-control" });
}
public TagBuilder BuildHidden(ModelExplorer explorer, string expression, string v)
{
return _htmlGenerator.GenerateHidden(ViewContext, explorer, expression, v, false, new { });
}
public TagBuilder BuildLabel(ModelExplorer explorer, string expression, string v)
{
return _htmlGenerator.GenerateLabel(ViewContext, explorer, expression, v, new { });
}
}
My Question:
Lets assume this AppUser only has one related Mobile phone number listed currently. So AppUser.UserPhones (count = 1 of type Mobile). So the code above, as-is, will only render an input for Mobile phone.
Since types-to-edit calls for both Mobile and Other, I want both inputs to be rendered to the screen. And IF the user adds a phone number to the Other input, then it would be saved to the related UserPhones entity on the Razor Pages OnPostAsync method. If the user does NOT provide a number for the "Other" input, then the related UserPhones record of type "Other" should NOT be created.
Can you help?
Thanks again!!!!
TagHelper
As my application currently sits, each AppUser may (or may not) have 3 phone numbers (UserPhones). One of each type (Mobile, Home, Other).
If I understand correctly, an AppUser might have 3 phone numbers and the count of each phone type for every user will be zero or one.
If that's the case, we can simply use PhoneType as an index, in other words, there's no need to use a custom index to iterate through the Phones property, and the ProcessAsync() method could be :
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
output.TagName = null; //DO NOT WANT AN OUTTER HTML ELEMENT
var props = typeof(UserPhones).GetProperties();
// display editable tags for phones
foreach (var pt in this.TypesToEdit) {
var phone = Phones.SingleOrDefault(p=>p.Type == pt);
var index = (int) pt;
foreach (var pi in props)
{
// if phone==null , then the pv should be null too
var pv = phone==null? null: pi.GetValue(phone);
var tag = GenerateFieldForProperty(pi.Name, pv, index, pt);
output.Content.AppendHtml(tag);
}
}
// generate hidden input tags for phones
var phones= Phones.Where(p => !this.TypesToEdit.Contains((p.Type)));
foreach (var p in phones) {
var index = (int)p.Type;
foreach (var pi in props) {
var pv = pi.GetValue(p);
var tag = GenerateFieldForProperty(pi.Name,pv,index,p.Type);
output.Content.AppendHtml(tag);
}
}
}
Here the GenerateFieldForProperty is a simply helper method to generate tag builder for particular property:
private TagBuilder GenerateFieldForProperty(string propName,object propValue,int index, EnumPhoneType eType )
{
// whether current UserPhone is editable (check the PhoneType)
var editable = TypesToEdit.Contains(eType);
var expression = this.ExpressionFilter(For.Name + $"[{index}].{propName}");
var explorer = For.ModelExplorer.GetExplorerForExpression(typeof(IList<UserPhones>), o => propValue);
//IF REQUESTED TYPE AND PROPERTY SPECIFIED
if (pi.Name.NormalizeString() == PropNameToEdit.NormalizeString() && editable)
{
TagBuilder gridItem = new TagBuilder("div");
gridItem.Attributes.Add("class", "rvt-grid__item");
var labelText = this.GetLabelTextByPhoneType(eType);
gridItem.InnerHtml.AppendHtml(BuildLabel(explorer, expression, labelText));
gridItem.InnerHtml.AppendHtml(BuildTextBox(explorer, expression, propValue?.ToString()));
return gridItem;
}
else //ADD HIDDEN FIELD SO BOUND PROPERLY
return BuildHidden(explorer, expression, propValue?.ToString());
}
private string GetLabelTextByPhoneType(EnumPhoneType eType) {
string lVal = null;
switch (eType)
{
case EnumPhoneType.Home:
lVal = "Home Phone";
break;
case EnumPhoneType.Mobile:
lVal = "Mobile Phone";
break;
case EnumPhoneType.Other:
lVal = "Other Phone";
break;
default:
break;
}
return lVal;
}
When posted to server, if someone doesn't input a phone number for the other PhoneType, the actual payload will be something like:
AppUser.UserPhones[0].UserPhoneId=....&AppUser.UserPhones[0].PhoneNumber=911&....
&AppUser.UserPhones[2].UserPhoneId=&AppUser.UserPhones[2].PhoneNumber=&AppUser.UserPhones[2].Type=&AppUser.UserPhones[2].AppUserId=&AppUser.UserPhones[2].AppUser=
&AppUser.UserPhones[1].UserPhoneId=...&AppUser.UserPhones[1].PhoneNumber=119&....
Since we use phone type as the index, we can conclude that the UserPhones[0] will be used as an Mobile phone and the UserPhones[2] will be treated as an Home phone.
page handler or action method
And the model binder on server side will create a empty string for each UserPhone.
To remove those empty inputs and prevent overposting attack, we could use Linq to filter UserPhones so that we can create or update UserPhone records without empty Phones:
var editables = new[] {
EnumPhoneType.Mobile,
EnumPhoneType.Other,
};
AppUser.UserPhones = AppUser.UserPhones
.Where(p => !string.IsNullOrEmpty(p.PhoneNumber)) // remove empty inputs
.Where(p => editables.Contains(p.Type) ) // remove not editable inputs
.ToList();
// now the `UserPhones` will be clean for later use
// ... create or update user phones as you like
Let's say you want to create phones :
public IActionResult OnPostCreate() {
var editables = new[] {
EnumPhoneType.Mobile,
EnumPhoneType.Other,
};
AppUser.UserPhones = AppUser.UserPhones
.Where(p => !string.IsNullOrEmpty(p.PhoneNumber))
.Where(p => editables.Contains(p.Type) )
.Select(p => { // construct relationship for inputs
p.AppUser = AppUser;
p.AppUserId = AppUser.Id;
return p;
})
.ToList();
this._dbContext.Set<UserPhones>().AddRange(AppUser.UserPhones);
this._dbContext.SaveChanges();
return Page();
}
Test Case :
<form method="post">
<div class="row">
<user-phones
phones="#Model.AppUser.UserPhones"
asp-for="#Model.AppUser.UserPhones"
prop-name-to-edit="PhoneNumber"
types-to-edit="new EnumPhoneType[] { EnumPhoneType.Mobile, EnumPhoneType.Other}"
>
</user-phones>
</div>
<button type="submit">submit</button>
</form>
User1 who has Mobile phone and Home phone number:
User2 who wants to create a new Mobile phone number :

MongoDB C# Combining Fields

The Plan:
So now what I basically want is to take my propertys out of the class, let the user pick some and then pull a List with ONLY those propertys out of MongoDB.
The Code:
here is where the method starts:
private void DoStuffExecute(object obj)
{
Class class= new Class();
ExtractClass(class);
if (propList != null)
{
var result = classService.DoStuff(propList);
}
}
in "ExtractClass()" the Propertys are being pulled out of the Class.
void ExtractClass(object obj)
{
foreach (var item in obj.GetType().GetProperties())
{
propList.Add(item.Name);
}
}
and finally in "classService.DoStuff()" i try to set the "fields".
public List<class> DoStuff(List<string> Props)
{
try
{
var filter = Builders<class>.Filter.Empty;
var fields = Builders<class>.Projection.Include(x => x.ID);
foreach (var item in Props)
{
string str = "x.";
str += item.ToString();
fields = Builders<class>.Projection.Include(x => str);
fields = Builders<class>.Projection.Include(x => item);
}
var result = MongoConnectionHandler.MongoCollection.Find(filter).Project<class>(fields).ToList();
return result;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
var result = new List<class>();
return result;
}
}
when i run the programm it gives me an "Unable to determine the serialization information for x=> value"... since im giving it a string.
The Question:
Does anyone have an Idea how to repair the code above or even make the plan work in another way?
thank you.
First of all: you are using such code lines as : var filter = Builders<class>.Filter.Empty; It is not possible, because class is a reserved keyword in c# (https://msdn.microsoft.com/en-us/library/x53a06bb.aspx) I assume, it's your Model, and i will speak about it as about Model class.
Include Filter needs Expression as a parameter, not a string, you should construct is as a expression. That's the second thing. Third, you should combine your includes as a chain, So your part of creating Include Filter from string List should look like:
var filter = Builders<Model>.Filter.Empty;
var fields = Builders<Model>.Projection.Include(x => x.Id);
foreach (var item in Props)
{
var par = Expression.Parameter(typeof(Model));
var prop = Expression.Property(par, item);
var cast = Expression.Convert(prop, typeof(object));
var lambda = Expression.Lambda(cast, par);
fields = fields.Include((Expression<Func<Model, object>>)lambda);
}
I have all expresiions separate for better understanding: first you create Parameter (x=>), than you add property (x=>x.Property1), than you should cast it to object, and after all create Lambda Expression from it.
And now the last part: You don't need all of it, Include function could get jsut a string as a parameter. So you could instead of all expression call write this:
fields = fields.Include(item);

Entity Framework matching a subset of an int

I am implementing a search where I would like to partially match an entity's primary key. For example, if I would search for "123" it would return entities which have primary keys like:
12345
67123
91234
If this were a string, I'd attack it like this:
public PartialViewResult QuickSearch(string searchTerm)
{
var results = db.MyEntities.Where(x => x.myProperty.Contains(searchTerm));
return PartialView("QuickSearch_Results", results);
}
However I'm at a loss on the best way to do this for an int. I'm looking for something like this:
public PartialViewResult QuickSearch(int id)
{
var results = db.MyEntities.Where(x => x.myPropertyId.Contains(int));
return PartialView("QuickSearch_Results", results);
}
But obviously contains is not the right way to go. What would be a correct way of implementing this?
Totally wild and untested guess using the StringConvert method:
public PartialViewResult QuickSearch(int id)
{
string sId = id.ToString();
var results =
from x in db.MyEntities
where SqlFunctions.StringConvert((double)x.myPropertyId)
.Contains(sId)
select x;
return PartialView("QuickSearch_Results", results);
}
db.MyEntities.Where(x => x.myPropertyId.ToString().Contains(id.ToString()));

How to create and return an Expression<Func

I Use entity Framework 4.
I would like to be able to create a function that return an Expression func that will be use in a lambda expression.
var ViewModel = _db.Suppliers.Select(model => new {
model,SupType = model.SupplierType.SupplierTypeTexts.Where( st => st.LangID == 1)
});
I would like to make this call like that
var ViewModel = _db.Suppliers.Select(model => new {
model,SupType = model.SupplierType.GetText()
});
My Partial class is:
public partial class SupplierType
{
public Expression<Func<SupplierTypeText, bool>> GetText()
{
return p => p.LangID == 1;
}
How can i perform this.
Easy. For example, Let's assume you have a Product table that is mapped to Products EntitySet in your context, now you want to pass a predicate and select a Product:
Expression<Func<Product, bool>> GetPredicate(int id) {
return (p => p.ProductID == id);
}
You can call GetPredicate() with a Product ID to filter based on that:
var query = ctx.Products.Where(GetPredicate(1)).First();
The point really is that you can always pass a Lambda Expression to where an Expression<T> is needed.
EDIT:
You should change your code like this:
var ViewModel = _db.Suppliers.Select(model => new {
model,
SupType = model.SupplierType.SupplierTypeTexts.Where(GetText())
});
public Expression<Func<SupplierTypeText, bool>> GetText() {
return (stt => stt.LangID == 1);
}
If you want to dynamically create compiled Expression at runtime (as opposed to ones hardcoded against a particular data model at compile time) you need to use the static methods on the Expression class.