I am using .net core clean architecture along with jQuery datatable. Server-side search is enabled, but I cannot map that search param search[value] from datable to a model property in c#. I have tried the Newtonsoft JsonPropertyName attribute to map it but it fails. Below is my model code:
public class GetVotesByMeetingIdQuery : IRequest<PaginatedList<VoteCastDTO>>
{
public int PageNumber { get; set; } = 1;
public int PageSize { get; set; } = 10;
public Search Search { get; set; }
}
public class Search
{
[JsonProperty(PropertyName = "value")]
public string Value { set; get; }
[JsonProperty(PropertyName = "regex")]
public bool Regex { set; get; }
}
I am able to capture the param from the request in my controller.
[HttpGet("GetVotesByMeetingId")]
public async Task<ActionResult<PaginatedList<VoteCastDTO>>> GetVotesByMeetingId([FromQuery] GetVotesByMeetingIdQuery query)
{
var exist = Request.Query.TryGetValue("search[value]", out Microsoft.Extensions.Primitives.StringValues val);
query.Search = exist ? val.ToString() : string.Empty;
return await Mediator.Send(query);
}
but I don't want to do this as I want to keep my controller clean. Is there anyway to sort out this issue?
You can use [FromQuery] attribute on the property. It will map the parameter to the property accordingly. Also, you need to change the property type to string as you are getting the param value in string. Below is the example:
public class GetVotesByMeetingIdQuery : IRequest<PaginatedList<VoteCastDTO>>
{
public int PageNumber { get; set; } = 1;
public int PageSize { get; set; } = 10;
[FromQuery(Name = "search[value]")]
public string Search { get; set; }
}
Related
I have a class Article with several properties.
I want to know whether it is possible to override the ToString method for the bool and DateTime properties so that for the booleans they print as "Yes/No" and the DateTime as custom text.
Idea is that when these properties are printed in a ListView with GridViewColumn bound to every property they don't print standard ToString values.
public class Article
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
public string Title { get; set; }
public string Author { get; set; }
public string Content { get; set; }
public int NumberOfWords { get; set; }
public string Category { get; set; }
public bool CanBePublished { get; set; }
public bool Published { get; set; }
public int Length { get; set; }
public DateTime CreationDate { get; set; }
public Article() { }
public Article(string title, string author, string content, int numberOfWords, string category, bool canBePublished, int length)
{
Title = title;
Author = author;
Content = content;
NumberOfWords = numberOfWords;
Category = category;
CanBePublished = canBePublished;
Length = length;
Published = false;
CreationDate = DateTime.Now;
}
}
You can define get method to get the formatted value from those fields as shown below. Create a view model class for that and do it in this way by difining a get method. and use that property to read the data. Like Article_ViewModelObject.CreationDateVal
public class Article_ViewModel: Article
{
public string CreationDateVal
{
get
{
return CreationDate.ToString();
}
}
public string CanBePublishedVal
{
get
{
return CanBePublished ? "Yes" : "No";
}
}
}
It is not a good idea to modify your model data class to alter what you see in a view. Most platforms that display model data objects have a way of modifying what you are seeing, based on the raw data.
Depending on the stack you are using, search for modifying the view based on raw data instead. If you can show your UI code, we can be of further help.
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;
}
}
I have the followings classes:
public class TextFilter
{
public string Value { get; set; } = "";
public EnumTextFilterSearchMethod SearchMethod { get; set; } = EnumTextFilterSearchMethod.EQ;
}
public class ContractsFilter
{
public TextFilter ContractNumber { get; set; } = new TextFilter();
public TextFilter OrderNumber { get; set; } = new TextFilter();
}
and controller:
[HttpGet("")]
public IActionResult Contracts([FromQuery] ContractsFilter filter = null)
the query string appears as:
http://localhost:63553/contracts?ContractNumber=my.namespace.TextFilter&OrderNumber=my.namespace.TextFilter
But I would need that the query string is formed as following:
http://localhost:63553/contracts?ContractNumber.Value=any_contract_number&ContractNumber.SearchMethod=EQ&OrderNumber.Value=any_order_number&OrderNumber.SearchMethod=EQ
If I enter so the query string by hand, the controller can it parse properly.
How can I make the class ContractsFilter appears in the query string with all properties of class TextFilter?
Try the [FromUri] attribute before your in-parameter in the same way you’ve tried with the [FromBody] attribute.
I have a model that I'm loading into a table within a form. The records are retrieved from an Oracle DB using EF6 and loaded into the model.
I also want the user to be able to select records to delete from the database via a checkbox in each row in the form.
The function to retrieve the Attendees:
public List<WebinarAttendeesList> getAttendees(string webinarKey)
{
string connectionString = "Password=password;User Id=user;Data Source=Oracle";
List<WebinarAttendeesList> r = null;
using (webinarAttendeesListDbContext context = new webinarAttendeesListDbContext(connectionString))
{
var result = from w in context.WebinarAttendeesList
where w.webinarKey == webinarKey
orderby w.FirstPollCount, w.SecondPollCount
select w;
r = result.ToList();
}
return r;
}
Here is the model:
[Table("WEBINARATTENDEESLIST")]
public class WebinarAttendeesList {
[Key, Column("WAL_ID")]
public int wa_id { get; set; }
[Column("WAL_CLI_RID")]
public int ParticipantID { get; set; }
[Column("WAL_FULLNAME")]
public string FullName { get; set; }
[Column("WAL_EMAIL")]
public string Email { get; set; }
[Column("WAL_JOINTIME")]
public string JoinTime { get; set; }
[Column("WAL_TIMEINSESSION")]
public string TimeInSession { get; set; }
[Column("WAL_LEAVETIME")]
public string LeaveTime { get; set; }
[Column("WAL_FIRSTPOLLCOUNT")]
public int FirstPollCount { get; set; }
[Column("WAL_SECONDPOLLCOUNT")]
public int SecondPollCount { get; set; }
[Column("WAL_ATTENDEDWEBINAR")]
public int AttendedWebinar { get; set; }
[Column("WAL_MAKEUP")]
public int Makeup { get; set; }
[Column("WAL_COMMENTS")]
public string Comments { get; set; }
[Column("WAL_REGISTRANTKEY")]
public string RegistrantKey { get; set; }
[Column("WAL_WEBINARKEY")]
public string webinarKey { get; set; }
}
When the form is submitted, I am passing the model to a function to store the records in EF6.
public ActionResult PostAttendees(ICollection<WebinarAttendeesList> attendees)
{
foreach (WebinarAttendeesList attendee in attendees)
{
UpdateAttendee(attendee);
}
}
How would I edit the model to allow this delete the records that are selected and update the ones that don't have the checkbox selected?
If I put an int delete property on the model that has no Column attribute I get this exception:
ORA-00904: "Extent1"."delete": invalid identifier
I found this tutorial but I'm NOT using any helpers in the creation of the form and do not have any ViewModels and it also doesn't explain how to handle doing different things to the different records based on the checkbox: http://johnatten.com/2014/01/05/asp-net-mvc-display-an-html-table-with-checkboxes-to-select-row-items/
Is there a better way to do this?
Yes. All models properties in EF are suppose to be column. You should use NotMapped attribute if you don't want property to be treated as a 'column' in database.
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.