Web Api Server Side Processing and DataTables parameters - c#

I'm trying to create an object that represents the values that datatables supplies to my web api call, which I'll then route to another api that actually returns the values (to separate the datatables nonsense from the api interface).
After researching a bit on the datatables wiki I ended up with the following objects defined:
public class DataTableParameters
{
public int draw { get; set; }
public int start { get; set; }
public int length { get; set; }
public order[] order { get; set; }
public column[] columns { get; set; }
}
public class order
{
public int column { get; set; }
public string dir { get; set; }
}
public class column
{
public string data { get; set; }
public string name { get; set; }
public bool searchable { get; set; }
public bool orderable { get; set; }
public search search { get; set; }
}
public class search
{
public string value { get; set; }
public bool regex { get; set; }
}
However, when I try to use them as arguments to the DataTables api controller, it comes out null:
public DataTableResult Get(DataTableParameters parameters) //parameters is null!
{
return new DataTableResult();
}
As far as I understood it, model binding should be reading the result and applying it to my object. This is an example call to the api from the front end:
Key Value
Request GET /MVC/api/DataTables?action=Get&draw=1&columns%5B0%5D%5Bdata%5D=0&columns%5B0%5D%5Bname%5D=&columns%5B0%5D%5Bsearchable%5D=true&columns%5B0%5D%5Borderable%5D=true&columns%5B0%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B0%5D%5Bsearch%5D%5Bregex%5D=false&columns%5B1%5D%5Bdata%5D=1&columns%5B1%5D%5Bname%5D=&columns%5B1%5D%5Bsearchable%5D=true&columns%5B1%5D%5Borderable%5D=true&columns%5B1%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B1%5D%5Bsearch%5D%5Bregex%5D=false&columns%5B2%5D%5Bdata%5D=2&columns%5B2%5D%5Bname%5D=&columns%5B2%5D%5Bsearchable%5D=true&columns%5B2%5D%5Borderable%5D=true&columns%5B2%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B2%5D%5Bsearch%5D%5Bregex%5D=false&columns%5B3%5D%5Bdata%5D=3&columns%5B3%5D%5Bname%5D=&columns%5B3%5D%5Bsearchable%5D=true&columns%5B3%5D%5Borderable%5D=true&columns%5B3%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B3%5D%5Bsearch%5D%5Bregex%5D=false&order%5B0%5D%5Bcolumn%5D=0&order%5B0%5D%5Bdir%5D=asc&start=0&length=10&search%5Bvalue%5D=&search%5Bregex%5D=false&_=1440437669357 HTTP/1.1
Why is my parameters object null, and how can I fix it?
Edit: I also attempted this:
public DataTableResult Get(int draw, int start, int length, column[] columns, order[] order)
{
return new DataTableResult();
}
But I get "Can't bind multiple parameters ('columns' and 'order') to the request's content."

UGH less than 10 minutes later, I need a [FromUri] attribute on the parameters.
public DataTableResult Get([FromUri]DataTableParameters parameters)
{
return new DataTableResult();
}

Related

Send a limited/reduced class to frontend

What I want
I want to send a limited/reduced class/object to frontend (as JSON). I use .NET Core 5.
What I have
I have a model class like this:
namespace Tasks.Models
{
public class Resources
{
public Guid Id { get; set; }
public string Type { get; set; }
public string Url { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public DateTime? Createdon { get; set; }
public Guid Userid { get; set; }
public Guid Taskid { get; set; }
public int Clicked { get; set; }
public byte Active { get; set; }
+++ many more properties
}
}
Now depending on the which controller that calls this model I want to have different "kind" of models. So if the resource is file I maybe want the properties Id,Type,Name. But if the resource is URL I want Id, Url, Name.
I tried setting up a method that "initialized the fields I wanted, but that also returned all properties
public static Responses FileResponse()
{
var response = new Responses()
{
Id = new Guid(),
Name = "",
Type = "File",
};
return response;
}
Now, when I call the Resources class or this method I get all properties, and returning it to the view presents all properties, but mostly as null, because I only set the three fields in the method.
What is the recommended way of solving this?
If you want to remove the field if it's null instead of showing in json with null value.
public class Resources
{
public Guid Id { get; set; }
public string Type { get; set; }
// if null, dont show it in JSON output
[JsonIgnoreAttribute(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string Url { get; set; }
// if null, dont show it in JSON output
[JsonIgnoreAttribute(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string Name { get; set; }
public string Description { get; set; }
public DateTime? Createdon { get; set; }
public Guid Userid { get; set; }
public Guid Taskid { get; set; }
public int Clicked { get; set; }
public byte Active { get; set; }
}
PS: Fiddle https://dotnetfiddle.net/hiMAci
It is just limiting the Resource class I am not able to do
Yep, side effect of C# being strongly typed, with object X definitely having properties Y and Z. You need differently shaped objects - either full on classes or records - that name the reduced set of properties because the serializer is going to look a tthe object and ser every property it can find.
You could make a new class for every variation - quick and easy with records, and easy to pass around inside your C#:
public record FileThing(string Id, string Type, string Name);
//make a new one and return it
new FileThing(someResources.Id, someResources.Type, someResources.Name);
Or can consider using an anonymous type if you're literally looking to put a few properties into some json, down a socket to a consuming front end (I can't quite decide what you mean by "view" - it doesn't seem to be an MVC View) that only cares about a few props out of many
So if the resource is file I maybe want the properties Id,Type,Name. But if the resource is URL I want Id, Url, Name.
public ActionResult SomeControllerMethod(){
if(isFile)
return Ok(new { someResources.Id, someResources.Type, someResources.Name });
else if(isUrl)
return Ok(new { someResources.Id, someResources.Url, someResources.Name });
}
Anonymous types are a bit harder to work with because the compiler writes the class for you, so it's tricky to do things like declare return types from methods if the method is returning an AT.. But if you're using it as some fill-in all within one method, such as a "make this and serialize it", they work well..
I think your approach is not the right one here. I tend to follow more general OO guidelines in this situation (note, some consider these a bit dated, and other solutions exist. But they are still commonly used)
You write against an interface. So let's see what you want... A guid, type and name. All other deatils aren't important.
public interface IResourceDetails
{
public Guid Id { get; }
public string Name { get; }
public string Type { get; }
}
And you can have multiple of these interfaces.
You could then implement the interfaces per type. But I would probably combine them in a base class
public abstract class ResourceBase : IResourceDetails
{
public Guid Id { get; } = new ();
public string Name { get; init; }
public string Type { get; }
public ResourceBase(string type)
{
Type = type;
}
}
Each resource type would have it's own implementation
public class FileResource : ResourceBase
{
public FileResource() : base("File") { }
// File-specific properties.
public string Description { get; init; }
public DateTime? Createdon { get; init; }
}
The response method then could be made generic and look like this
public static IActionResult Response(IResourceDetails resource)
{
return Ok(new
{
resource.Id,
resource.Name,
resource.Type,
});
}

C# DTO List that consists of 2 other lists

Firstly, apologies if this seems basic, I am new to C#/dotnet and if the answer to this questions is somewhere obvious please point me in the right direction.
I have a DTO class with the following code
public class LessonDetailView : BaseResult
{
public long Id { get; set; }
public string Title { get; set; }
public List<LessonImagesListView> LessonImages { get; set; }
public List<LessonInstructionCardListView> InstructionCards { get; set; }
}
public class LessonImagesListView
{
public long Id { get; set; }
public string Title { get; set; }
public ImageDetailView Image { get; set; }
public LessonImagesListView()
{
Image = new ImageDetailView();
}
}
public class LessonInstructionCardListView
{
public long Id { get; set; }
public string Instructions { get; set; }
}
So I have 2 distinct types of object that I attach to the lesson and send to the frontend.
I will add that in the future I might have 6 different types of object.
These Images, or Instructions are also going to be displayed in a certain order on the front end so instead of sending them all separately I wanted to combine them all and send them in a new List LessonAssetsListView for example.
How can i create Lists in a DTO that combine 2 other lists ?
OR ... is this something I even need to do here ... and can i just do all this in my service.
Help appreciated.
You could simply define a type that composes both your existing and send a List of them
public class LessonAsset
{
public LessonImagesListView Image {get;set; }
public LessonInstructionCardListView Instruction {get;set;}
}
and then
public class LessonDetailView : BaseResult
{
public long Id { get; set; }
public string Title { get; set; }
public List<LessonAsset> LessonAssets { get; set; }
}

FromQuery ModelBinding fails, FromBody works

I use a standard set of classes for binding server side DataTables.net grids to data sources.
public class DataTable
{
// Grid properties
public int draw { get; set; }
public List<DataTableColumn> columns { get; set; }
public List<DataTableOrder> order { get; set; }
public int start { get; set; }
public int length { get; set; }
public DataTableColumnSearch search { get; set; }
}
public class DataTableColumn
{
public string data { get; set; }
public string name { get; set; }
public bool searchable { get; set; }
public bool orderable { get; set; }
public DataTableColumnSearch search { get; set; }
}
public class DataTableColumnSearch
{
public string value { get; set; }
public bool regex { get; set; }
}
public class DataTableOrder
{
public int column { get; set; }
public string dir { get; set; }
}
public class Grid
{
public int start { get; set; }
public int recordsFiltered { get; set; }
public int recordsTotal { get; set; }
public object data { get; set; }
public void Setup<T>(IQueryable<T> r, DataTable p)
{
// Filter, sort and bind the data data to this.data
}
}
Then, in MVC 5 I was able to use the QueryString model binding:
public async Task<JsonResult> GetDataTable(DataTable p)
Everything was bound, and the column, search and order objects were all set.
I'm setting up a new project in .NET Core and this now only works if you accept [HttpPost] and post the DataTables.net request to the controller.
If I use the [HttpGet] method, then the DataTable object binds correctly, but no child items (DataTableColumn, DataTableSearch or DataTableOrder) bind.
The Form Data and QueryString data are the same:
draw:1
columns[0][data]:Col1
columns[0][name]:
columns[0][searchable]:true
columns[0][orderable]:true
columns[0][search][value]:
columns[0][search][regex]:false
columns[1][data]:Col2
columns[1][name]:
columns[1][searchable]:true
columns[1][orderable]:true
columns[1][search][value]:
columns[1][search][regex]:false
order[0][column]:0
order[0][dir]:asc
start:0
length:10
search[value]:v6
search[regex]:false
I'd prefer to use a GET request, as it is getting data, so shouldn't need to post. As said, this was working in MVC 5, so I'm not sure what has changed?
EDIT TO ADD
As per the comments, it's just using the standard model binder:
public class OutboundCaims: Controller
{
//[HttpGet]
[HttpPost]
[Route("Claims")]
public async Task<JsonResult> GetClaims(DataTable p) { }
}
If using POST, the DataTable class is bound correctly by the model binder, using GET it isn't. Both POST and GET produce the same output as above when viewed in Chrome dev tools, so pretty sure it's not that.
The request is made using the standard set of jQuery ajax requests built into DataTables.net.
Edit to add: REQUEST URL using a GET method:
http://localhost:55319/api/DataTable?draw=1&columns%5B0%5D%5Bdata%5D=col1&columns%5B0%5D%5Bname%5D=&columns%5B0%5D%5Bsearchable%5D=true&columns%5B0%5D%5Borderable%5D=true&columns%5B0%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B0%5D%5Bsearch%5D%5Bregex%5D=false&columns%5B1%5D%5Bdata%5D=col2&columns%5B1%5D%5Bname%5D=&columns%5B1%5D%5Bsearchable%5D=true&columns%5B1%5D%5Borderable%5D=true&columns%5B1%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B1%5D%5Bsearch%5D%5Bregex%5D=false&columns%5B2%5D%5Bdata%5D=contract&columns%5B2%5D%5Bname%5D=&columns%5B2%5D%5Bsearchable%5D=true&columns%5B2%5D%5Borderable%5D=true&columns%5B2%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B2%5D%5Bsearch%5D%5Bregex%5D=false&columns%5B3%5D%5Bdata%5D=manufacturer&columns%5B3%5D%5Bname%5D=&columns%5B3%5D%5Bsearchable%5D=true&columns%5B3%5D%5Borderable%5D=true&columns%5B3%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B3%5D%5Bsearch%5D%5Bregex%5D=false&columns%5B4%5D%5Bdata%5D=owner&columns%5B4%5D%5Bname%5D=&columns%5B4%5D%5Bsearchable%5D=true&columns%5B4%5D%5Borderable%5D=true&columns%5B4%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B4%5D%5Bsearch%5D%5Bregex%5D=false&order%5B0%5D%5Bcolumn%5D=0&order%5B0%5D%5Bdir%5D=asc&start=0&length=10&search%5Bvalue%5D=&search%5Bregex%5D=false&_=1478804216098
This maps to the same as the POST request, just the model binder doesn't work.

how to setup mvc net model with ILists

I am passing back a List of objects to a webapi controller. i need to add 2 more properties, they are both Lists. I am stuck on how to setup my classes.
model/apicontroller
public class DataPoints
{
public string sqft { get; set; }
public string price { get; set; }
}
public class Products
{
public string product { get; set; }
}
public class Data
{
public string Color { get; set; }
public string Name { get; set; }
public IList<DataPoints> DataPoints { get; set; }
public IList<Products> Products { get; set; }
}
public class ExportLegendController : ApiController
{
// POST: api/ExportLegend
[HttpPost]
public PDF Post([FromBody]List<Data> data)
{
here is what I am passing back
This should work as long as your JSON object has properties called dataPoints and products - whereas at the moment they're called data and product (or vice versa of course, you could rename your .NET model.)
I also find IEnumerables are more reliably deserialised.

jQuery datatable in MVC (server-side)

https://datatables.net/usage/server-side
On the page above, there are parameters that you need to receive to make server-side datatable work.
I have a helper class
public class TableParameter
{
public string sEcho { get; set; }
public int iDisplayStart { get; set; }
public int iDisplayLength { get; set; }
public int iSortingCols { get; set; }
}
But in order to sort columns I need to receive
string sSortDir_(int)
How do I do that? I know (int) represents column ID that needs to be sorted, but I just can't catch it in my controller.
The datatable will post one or more sSortDir_x parameters to your controller, depending on how many columns are sorted on simultaneously in the table.
The specific columns that the table is sorted by are sent in the iSortCol_ parameters (again, one or more).
public class TableParameter
{
public string sEcho { get; set; }
public int iDisplayStart { get; set; }
public int iDisplayLength { get; set; }
public int iSortingCols { get; set; }
public int iSortCol_0 { get; set; } // the first (and usually only) column to be sorted by
public string sSortDir_0 { get; set; } // the direction of the first column sort (asc/desc)
public int iSortCol_1 { get; set; } // the second column to be sorted by
public string sSortDir_1 { get; set; } // the direction of the second column sort
// etc
}
For receiveing a column name in action, that is used for one-column sorting:
public ActionResult SomeMethod(FormCollection coll)
{
var sortingColumnNumber = Convert.ToInt32(coll["iSortCol_0"]);
var sortingColumnName = coll[string.Format("mDataProp_{0}", sortingColumnNumber)];
var propertyInfo = typeof(SomeObject).GetProperty(sortingColumnName);
//..get List<SomeObject> sortedObjects
sortedObjects = sortedObjects.OrderBy(x => propertyInfo.GetValue(x, null)).ToList();
//...
}

Categories