failing to bind string indexed query string parameters - c#

I'm attempting to bind some query string parameters that is indexed by string keys but i can't seem to be getting it to work
here are the values i was trying to bind
search[value]: Exception happ...
search[regex]: false
here is the model i'm trying to bind it with
getLogsAjax(DataTableAjaxPostModel model)
public class DataTableAjaxPostModel
{
public int draw { get; set; }
public int start { get; set; }
public int length { get; set; }
public List<Column> columns { get; set; }
public search search { get; set; }
public List<Order> order { get; set; }
}
public class search
{
public string value { get; set; }
public string regex { get; set; }
}
the rest of the model is being bind correctly except for the search class object, i tripled check that the request contains values for that object, what am i missing here?
p.s. the same code was supposedly working pre .net core

A little more background of the code would be helpful such as the code section that is actually doing the binding however here is a dotnetcore controller example with query parameter binding. Also common practice in C# are class names and fields are both uppercase FYI.
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
[Route("api/[controller]")]
public class SampleController : Controller
{
[HttpGet]
[Route("")]
public IActionResult ExampleGet([FromQuery] DataTableAjaxPostModel dataTableAjaxPostModel)
{
// You should be able to debug and see the value here
var result = dataTableAjaxPostModel.search;
return Ok();
}
public class DataTableAjaxPostModel
{
public int draw { get; set; }
public int start { get; set; }
public int length { get; set; }
public List<Column> columns { get; set; }
public search search { get; set; }
public List<Order> order { get; set; }
}
public class search
{
public string value { get; set; }
public string regex { get; set; }
}
}

You don't need to bind each field manually. Using reflection will make it easily.
Aslo, there's no need bind those outer model's properties (DataTableAjaxPostModel's properties) manually. That's because they will be done by the built-in model binder.
Implementation
create a custom binder QueryStringDictSyntaxBinder<TModel>:
internal class QueryStringDictSyntaxBinder<TModel> : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
throw new ArgumentNullException(nameof(bindingContext));
try
{
var result = Activator.CreateInstance<TModel>();
foreach(var pi in typeof(TModel).GetProperties())
{
var modelName = bindingContext.ModelName;
var qsFieldName = $"{modelName}[{pi.Name}]";
var field= bindingContext.HttpContext.Request.Query[qsFieldName].FirstOrDefault();
if(field != null){
pi.SetValue(result,field);
}
// do nothing if null , or add model binding failure messages if you like
}
bindingContext.Result = ModelBindingResult.Success(result);
}
catch
{
bindingContext.Result = ModelBindingResult.Failed();
}
return Task.CompletedTask;
}
}
And then decorate the search property with a [ModelBinder(typeof(QueryStringDictSyntaxBinder<search>))] :
public class DataTableAjaxPostModel
{
public int draw { get; set; }
public int start { get; set; }
public int length { get; set; }
public List columns { get; set; }
[ModelBinder(typeof(QueryStringDictSyntaxBinder<search>))]
public search search { get; set; }
public List order { get; set; }
}
Test Case:
I test it with the following requests, and it works fine for me:
?draw=1&search[value]=abc&search[regex]=(.*)&
?draw=1&sEarCh[value]=opq&Search[regex]=([^123]*)&
?draw=1&seaRch[value]=rst&Search[regex]=(.*)&
?draw=1&Search[value]=abc&
?draw=1&

seems like no one has an answer for this, so i took a different route and wrote my own custom binder, if a better answer came, ill accept it instead of this one, probably will refactor it later (hahaha IKR!)
public class DTModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
throw new ArgumentNullException(nameof(bindingContext));
try
{
var result = new DataTableAjaxPostModel();
if (bindingContext.HttpContext.Request.Query.Keys.Contains("draw"))
result.draw = int.Parse(bindingContext.ValueProvider.GetValue("draw").FirstValue);
if (bindingContext.HttpContext.Request.Query.Keys.Contains("search[value]") &&
bindingContext.HttpContext.Request.Query.Keys.Contains("search[regex]"))
result.search = new search()
{
regex = bindingContext.ValueProvider.GetValue("search[regex]").FirstValue,
value = bindingContext.ValueProvider.GetValue("search[value]").FirstValue
};
//...
bindingContext.Result = ModelBindingResult.Success(result);
}
catch
{
bindingContext.Result = ModelBindingResult.Failed();
}
return Task.CompletedTask;
}
}

Related

Why the controller response are setting model field names into lower case?

In my .NET Core project, in the response of all controllers, the object fields are coming in lower case in the first one or two letters of the field name:
{
"iD_PARAM": "foo",
"cD_PROM": "bar",
"txT_OFFICER": "lorem",
"cN_NEW_PARAM": "fubá",
"iD_SITUATION": "XX",
"iD_NEW_USER": "ipsun",
}
It's strange, because the model has all fields in UPPER case:
public partial class MyModel {
public long ID_PARAM { get; set; }
public long CD_PROM { get; set; }
public string TXT_OFFICER { get; set; }
public int CN_NEW_PARAM { get; set; }
public int ID_SITUATION { get; set; }
public int ID_NEW_USER { get; set; }
}
For more detail, this is the controller where I set the values and the response:
[HttpPost("receive")]
public async Task<IActionResult> Get()
{
try
{
MyModel newParam = new MyModel ();
newParam.ID_PARAM = "foo";
newParam.CD_PROM = "foo";
newParam.TXT_OFFICER = "lorem";
newParam.CN_NEW_PARAM = "fubá";
newParam.ID_SITUATION = "XX";
newParam.ID_NEW_USER = "ipsun";
return Ok(newParam);
}
catch (Exception ex)
{
return BadRequest(ex);
}
}
Assuming you are using Newtonsoft Json, if you want your Json properties to be uppercase, try decorating your Model with JsonProperty like this to prevent the Serializer try to infer the property name :
public partial class MyModel {
[JsonProperty("ID_PARAM")]
public long ID_PARAM { get; set; }
[JsonProperty("CD_PROM")]
public long CD_PROM { get; set; }
[JsonProperty("TXT_OFFICER")]
public string TXT_OFFICER { get; set; }
[JsonProperty("CN_NEW_PARAM")]
public int CN_NEW_PARAM { get; set; }
[JsonProperty("ID_SITUATION")]
public int ID_SITUATION { get; set; }
[JsonProperty("ID_NEW_USER")]
public int ID_NEW_USER { get; set; }
}
You should change the ContractResolver, just add below code in startup ConfigurSservices
services.AddMvc().AddJsonOptions(options =>
{
options.SerializerSettings.ContractResolver = new DefaultContractResolver();
});
Refer to Lowercase property names from Json() in .Net core

Return DbSet based on selection

I'm writing an ASP.NET Core MVC web app that is a tool for handling a parts database. What I want is for the user to select a Part and then that will do some action, like delete that part from it's DB. However, I want this to be a generic action used by all the parts.
I have a class hierarchy which is:
Part
PartA
PartB
What I need is some method that I can call that will get the DbSet that my part belongs to. This is an example of what I'm looking to do:
Models
public class Part
{
public Nullable<int> ID { get; set; }
public string Brand { get; set; }
}
public class PartA : Part
{
public int Length { get; set; }
public List<Image> Images { get; set; }
}
public class PartB : Part
{
public int Durability { get; set; }
}
public class Image
{
public Nullable<int> ID { get; set; }
public string ImagePath { get; set; }
}
PartsDbContext
public class PartsDbContext : DbContext
{
public DbSet<PartA> PartAs { get; set; }
public DbSet<PartB> PartBs { get; set; }
}
PartsController
public IActionResult DeletePart (string partType, int id)
{
var partSet = GetDbSet(partType);
var part partSet.FirstOrDefault(e => e.ID == id);
if (part != null)
{
partSet.Remove(part);
_context.SaveChanges();
}
}
//function to find and return DbSet of the selected type
private DbSet<Part> GetDbSet (string partType)
{
switch (partType)
{
case "PartA":
return _context.PartAs;
case "PartB":
return _context.PartBs;
}
return null;
}
Now obviously this doesn't work because the compiler will complain that:
You can't convert type DbSet<PartA> to type DbSet<Part>
Anyone know how I might go about doing this?
This is really hacky, but sort of works.
public IActionResult DeletePart (string partType, int id)
{
Type type = GetTypeOfPart(partType);
var part = _context.Find(type, id);
var entry = _context.Entry(part);
entry.State = EntityState.Deleted;
_context.SaveChanges();
}
However, you really should just use polymorphism and generic abstract Controllers.
EDIT You can also use Explicit Loading for this.
private void LoadRelatedImages(IPart part)
{
_context.Entry(part)
.Collection(p => p.Images)
.Load();
}

Is there a shortcut to convert a model to a view model?

I am new to c# and .NET. I am learning ASP.NET MVC 5. One thing that I am finding myself spending extra time doing in converting a model to a viewmodel.
Here is my model
public class Overview
{
public string chain_name { get; set; }
public int store_id { get; set; }
public int total_attempts { get; set; }
public int total_unique_number_called { get; set; }
public int total_callable { get; set; }
public int total_completed_interviews { get; set; }
}
and here is my view model
public class OverviewViewModel
{
public string chain_name { get; set; }
public int store_id { get; set; }
public int total_attempts { get; set; }
public int total_unique_number_called { get; set; }
public int total_callable { get; set; }
public int total_completed_interviews { get; set; }
public decimal? unique_number_per_complete { get; set; }
public OverviewViewModel()
{
unique_number_per_complete = 0;
}
}
as you can see both Model and ViewModel are identical except for over variable which is a calculation.
To populate my view model I do the following
var records = conn.Database.SqlQuery<Overview>(query).ToList();
var overView = new List<OverviewViewModel>();
foreach(var record in records){
var newRecord = new OverviewViewModel();
newRecord.store_id = record.store_id;
newRecord.chain_name = record.chain_name;
newRecord.total_attempts = record.total_attempts;
newRecord.total_callable = record.total_callable;
newRecord.total_completed_interviews = record.total_completed_interviews;
if (record.total_completed_interviews > 0) {
newRecord.total_unique_number_called = record.total_unique_number_called / record.total_completed_interviews;
}
overView.Add(newRecord);
}
The two issues that I am seeing with my approach is that
I have to do lots of extra coding especially of the view model is large or f I have multiple variables that I need to calculate.
I feel I am looping 1 extra time to convert my model to view mode.
Is there an easier way to do this in c#?
Is there a better approach with this procedure for a large application? My goal is to learn the better way to utilize my code time to the fullest.
I agree that you should look into automapper, but another way would be to create a constructor on your OverviewViewModel model that takes and Overview object and populates all the properties. Something like
public class OverviewViewModel {
public string chain_name { get; set; }
public int store_id { get; set; }
public int total_attempts { get; set; }
public int total_unique_number_called { get; set; }
public int total_callable { get; set; }
public int total_completed_interviews { get; set; }
public decimal? unique_number_per_complete { get; set; }
public OverviewViewModel()
{
unique_number_per_complete = 0;
}
public OverviewViewModel(Overview record)
{
store_id = record.store_id;
chain_name = record.chain_name;
total_attempts = record.total_attempts;
total_callable = record.total_callable;
//etc
}
}
Then your code would look like
var overView = new List<OverviewViewModel>();
foreach(var record in records){
overView.Add(new OverViewViewModel(record));
}
Yes, you should use Automapper, install package view Nuget. Automapper is very configurable as well.
http://automapper.org/
First, create this class:
public static class AutoMapperConfig
{
public static void RegisterMappings()
{
//Example here, creates "two way" for Overview & OverviewViewModel mapping
Mapper.CreateMap<Overview, OverviewViewModel>(); //<source, destination>
Mapper.CreateMap<OverviewViewModel, Overview>(); //<source, destination>
//..more mapping for other Models and ViewModels.
}
}
In Global.asax.ApplicationStart() add this line:
AutoMapperConfig.RegisterMappings()
Now your foreach example in your comments is nice and simple:
foreach (var record in records)
{
var newRecordOverviewViewModel = Mapper.Map<OverviewViewModel>(record); //<destination>(source)
overView.Add(newRecordOverviewViewModel);
}
Another alternative is to use Linq and extension methods as below
using System.Collections.Generic;
using System.Linq;
using App.Data.Entities;
using App.Business.Models;
namespace App.Business
{
public static partial class OverviewAdapter
{
public static OverviewViewModel ToOverviewViewModel(this Overview overview)
{
return new OverviewViewModel
{
chain_name = overview.chain_name,
store_id = overview.store_id,
total_attempts = overview.total_attempts,
total_unique_number_called = overview.total_unique_number_called,
total_callable = overview.total_callable,
total_completed_interviews = overview.total_completed_interviews,
unique_number_per_complete = 0
};
}
public static IEnumerable<OverviewViewModel> ToOverviewModelList(this IEnumerable<OverviewViewModel> overviewList)
{
return (overviewList != null) ? overviewList.Select(a => a.ToOverviewViewModel()) : new List<OverviewViewModel>();
}
// Reverse - ToOverview / ToOverviewList if needed...
}
}
Now, anytime your Business class is in scope, you have a discoverable method on the class and the list of class which can be added inline.
var records = conn.Database.SqlQuery<Overview>(query).ToOverviewModelList().ToList();
This more functional approach is intuitive, streamlined, and you have immediate feedback if an adapter has not yet been written.
It is a personal preference whether to return an IEnumerable or List from the list version. The broader IEnumerable is the more conventional solution, but I tend to be going from List<> to List<> all of the time and dropping down into IEnumerable seems superfluous.

Complex array in ServiceStack request

I am sending the following request parameters to my service; among which, is the filter parameter which is a multidimensional array:
filter[0][field]:homeCountry
filter[0][data][type]:string
filter[0][data][value]:united s
page:2
start:200
limit:200
sort:homeCountry
dir:ASC
The querystring is encoded like so:
paymentratetrip.json?filter%5B0%5D%5Bfield%5D=homeCountry&filter%5B0%5D%5Bdata%5D%5Btype%5D=string&filter%5B0%5D%5Bdata%5D%5Bvalue%5D=united%20s&page=2&start=200&limit=200&sort=homeCountry&dir=AS
Currently, my C# request object looks like this:
public class PaymentRateTripRequest
{
public int start { get; set; }
public int limit { get; set; }
public string sort { get; set; }
public string dir { get; set; }
}
How can I modify my request object to receive the filter parameter which could be a multidimensional array?
Note: I am using ServiceStack.
The only way I can think is to send the entire request object as a parameter to my method like so:
public object Get(PaymentRateTripRequest req)
{
return _repository.GetAllRates(req.start, req.limit, req.sort, req.dir, this.Request.OriginalRequest);
}
But, this doesn't seem like the best solution.
Edit: this.Request.QueryString
this.Request.QueryString
{filter%5b0%5d%5bfield%5d=homeCountry&filter%5b0%5d%5bdata%5d%5btype%5d=string&filter%5b0%5d%5bdata%5d%5bvalue%5d=united+s&page=2&start=200&limit=200&sort=homeCountry&dir=ASC}
[System.Web.HttpValueCollection]: {filter%5b0%5d%5bfield%5d=homeCountry&filter%5b0%5d%5bdata%5d%5btype%5d=string&filter%5b0%5d%5bdata%5d%5bvalue%5d=united+s&page=2&start=200&limit=200&sort=homeCountry&dir=ASC}
base {System.Collections.Specialized.NameObjectCollectionBase}: {filter%5b0%5d%5bfield%5d=homeCountry&filter%5b0%5d%5bdata%5d%5btype%5d=string&filter%5b0%5d%5bdata%5d%5bvalue%5d=united+s&page=2&start=200&limit=200&sort=homeCountry&dir=ASC}
_all: null
_allKeys: {string[8]}
AllKeys: {string[8]}
Edit: filter is still empty.
This is an alternative solution that requires no changes to your client and therefore will accept the query string in the format you have currently:
paymentratetrip.json?filter%5B0%5D%5Bfield%5D=homeCountry&filter%5B0%5D%5Bdata%5D%5Btype%5D=string&filter%5B0%5D%5Bdata%5D%5Bvalue%5D=united%20s&page=2&start=200&limit=200&sort=homeCountry&dir=AS
The disadvantage of this method is that it's more code to maintain. The JSV method is simpler.
Request attribute to populate the filter from the querystring:
We can use a ServiceStack filter to intercept the query string before it reaches the action method. It can then parse the custom filter format and populate the filter object of the DTO.
public class FilterAttribute : Attribute, IHasRequestFilter
{
IHasRequestFilter IHasRequestFilter.Copy()
{
return this;
}
public int Priority { get { return int.MinValue; } }
FilterField CreateOrUpdateField(ref Dictionary<string, FilterField> filter, string id)
{
if(filter.ContainsKey(id))
return filter[id];
var field = new FilterField { Data = new Dictionary<string, object>() };
filter.Add(id, field);
return field;
}
public void RequestFilter(IRequest req, IResponse res, object requestDto)
{
var filteredDto = requestDto as IFilter;
if(filteredDto == null)
return;
const string fieldPattern = #"filter\[([A-Za-z0-9]+)\]\[field\]";
const string dataPattern = #"filter\[([A-Za-z0-9]+)\]\[data\]\[([A-Za-z0-9]+)\]";
Dictionary<string, FilterField> filter = new Dictionary<string, FilterField>();
foreach(var property in req.QueryString.AllKeys)
{
Match match = Regex.Match(property, fieldPattern, RegexOptions.IgnoreCase);
if(match.Success)
{
// Field
var id = match.Groups[1].Value;
var field = CreateOrUpdateField(ref filter, id);
field.Field = req.QueryString[property];
} else {
match = Regex.Match(property, dataPattern, RegexOptions.IgnoreCase);
if(match.Success)
{
// Data value
var id = match.Groups[1].Value;
var keyName = match.Groups[2].Value;
var field = CreateOrUpdateField(ref filter, id);
if(!field.Data.ContainsKey(keyName))
field.Data.Add(keyName, req.QueryString[property]);
}
}
}
filteredDto.Filter = filter.Values.ToArray();
}
}
You will also need to add this interface and FilterField class:
public class FilterField
{
public string Field { get; set; }
public Dictionary<string,object> Data { get; set; }
}
public interface IFilter
{
FilterField[] filter { get; set; }
}
Then you simply need to update your DTO so it looks like this:
[Route("/paymentratetrip", "GET"]
[Filter]
public class PaymentRateTripRequest : IFilter
{
public int page { get; set; }
public int start { get; set; }
public int limit { get; set; }
public string sort { get; set; }
public string dir { get; set; }
public FilterField[] filter { get; set; }
}
You should add a property with the filter to your DTO, such as below:
public class PaymentRateTripRequest
{
public int page { get; set; }
public int start { get; set; }
public int limit { get; set; }
public string sort { get; set; }
public string dir { get; set; }
public FilterField[] filter { get; set; }
}
public class FilterField
{
public string field { get; set; }
public Dictionary<string,object> data { get; set; }
}
This will allow you to add any number of fields to filter by, and by making the data property of the FilterField a Dictionary<string, object> you can add as many data properties as needed.
Then you can populate the filter parameter in your PaymentRateTripRequest using JSV format. You can learn about JSV format here. JSV Format (i.e. JSON-like Separated Values) is a JSON inspired format that uses CSV-style escaping for the least overhead and optimal performance.
paymentratetrip.json?filter=[{field:homeCountry,data:{type:string,value:"united s"}},{field:other,data:{type:int,value:34,special:true}}]&page=2&start=200&limit=200&sort=homeCountry&dir=ASC
Then you can access the filter as a regular property on your request.
Hope this helps.

ASP.Net MVC request ViewModel not parsing

I have a request like this:
filter[logic]:and
filter[filters][0][value]:a
filter[filters][0][operator]:startswith
filter[filters][0][field]:result
filter[filters][0][ignoreCase]:true
I need to receive it on the Controller but I don't know exactly how. I have tried this view model:
{
public class SearchFilterViewModel
{
public string logic { get; set; }
public List<SearchFilterFiltersViewModel> filters { get; set; }
}
public class SearchFilterFiltersViewModel
{
public string value { get; set; }
//public string operator { get; set; }
public string field { get; set; }
public bool ignoreCase { get; set; }
}
}
But the Controller receives it all null. operator property is commented because operator is a reserved keyword, I don't know how to make Asp.Net to use it. And I don't know if this is the cause of the problem.
Note that I can't change the request body pattern because it comes from this Kendo Widget.
This is my Controller(test version):
public ActionResult Text(SearchFilterViewModel filter)
{
return Json("", JsonRequestBehavior.AllowGet);
}
Here is working solution
Model:
public class SearchFilterViewModel
{
public string logic { get; set; }
public List<SearchFilterFiltersViewModel> filter { get; set; }
}
public class SearchFilterFiltersViewModel
{
public string value { get; set; }
public string oper { get; set; }
public string field { get; set; }
public bool ignoreCase { get; set; }
}
Then you can write custom IValueProvider where you can override usual parsing mechanism like this:
public class KendoValueProvider : NameValueCollectionValueProvider
{
public KendoValueProvider(NameValueCollection originalCollection)
: base(UpdateCollection(originalCollection), CultureInfo.InvariantCulture)
{
}
private static NameValueCollection UpdateCollection(NameValueCollection collection)
{
NameValueCollection result = new NameValueCollection();
foreach (string key in collection.Keys)
{
// ignore all other request
if (!key.StartsWith("filter"))
return null;
var newKey = key
.Replace("[filters]", string.Empty)
.Replace("filter[logic]", "logic")
.Replace("[value]", ".value")
.Replace("[operator]", ".oper")
.Replace("[field]", ".field")
.Replace("[ignoreCase]", ".ignoreCase");
var value = collection[key];
result.Add(newKey, value);
}
return result;
}
}
Then you need to write ValueProviderFactory that will register this ValueProvider like this:
public class KendoValueProviderFactory : ValueProviderFactory
{
public override IValueProvider GetValueProvider(ControllerContext controllerContext)
{
return new KendoValueProvider(controllerContext.HttpContext.Request.QueryString);
}
}
And the last step is just register it in Global.asax file
ValueProviderFactories.Factories.Add(new KendoValueProviderFactory());
And sample Action
[HttpGet]
public ActionResult Index(SearchFilterViewModel model)
{
return Json(model, JsonRequestBehavior.AllowGet);
}

Categories