Can i JSON serialize a object which contains a list? - c#

I've run into a issue when using JsonConvert.SerializeObject(addToBasketView) as it seems not to like tha list that i pass into it - until i passed in the list it was working fine - are you not able to pass a list?
Here is my code:
controller:
//We need a list of Automation scripts - this was in Apollo but now we need to pass it in teh message
var automationList = AutomationHelper(retailerId, productId);
var addToBasketView = new AddToBasketView
{
Url = retailerProduct.DeepLink,
Password = (password),
Username = (username),
RetailerProductJson = new RetailerProductJson(retailerProduct),
WidgetImpressionId = id,
Quantity = qty,
MessageId = messageId,
AutomationList = automationList
};
// now turn it into a string
var json = JsonConvert.SerializeObject(addToBasketView);
Addtobasketview.cs:
using System;
using System.Collections.Generic;
namespace WidgetData
{
/// <summary>
/// This is the view that we will give to the AddToBasketForm
/// </summary>
[Serializable]
public class AddToBasketView
{
/// <summary>
/// The Username for the retailer
/// </summary>
public String Username { get; set; }
/// <summary>
/// The password for the retailer
/// </summary>
public String Password { get; set; }
/// <summary>
/// The URl of the thing they want added to the site
/// </summary>
public String Url { get; set; }
/// <summary>
/// The retailer product selected - from this I can get the retailer and the product.
/// </summary>
public RetailerProductJson RetailerProductJson { get; set; }
/// <summary>
/// The widget impression id so that we can attach to the addTobaskets table for recording purposes
/// </summary>
public int WidgetImpressionId { get; set; }
/// <summary>
/// set the quantity
/// </summary>
public int Quantity { get; set; }
/// <summary>
/// The MessageId this is so we can identify it when waiting for a response
/// </summary>
public String MessageId { get; set; }
/// <summary>
/// Automation script list
/// </summary>
public List<AutomationStepScript> AutomationList { get; set; }
}
}
Edit:
AutomationStepScript.cs
using System;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
namespace WidgetData
{
/// <summary>
/// Extends the class created by Linq To SQL, so that we can control in our models section the validity of the data
/// </summary>
[MetadataType(typeof(AuotmationStepScriptValidation))]
public partial class AutomationStepScript
{
/// <summary>
/// Override the to string so that we can use in the delete method to capture details of this record before it is deleted
/// </summary>
/// <returns></returns>
public override string ToString()
{
return string.Format("AutomationStepScript Id = {0}, AutomationStepId = {1}, Sort Order = {2}, Description = {3}, Script = {4}, Evaluate = {5}", Id,AutomationStepId,SortOrder,Description,Script,Evaluate);
}
}
/// <summary>
/// Class to validate my AuotmationStepScript
/// </summary>
public class AuotmationStepScriptValidation
{
/// <summary>
/// AutomationId
/// </summary>
[Required(ErrorMessage = "AutomationStepId is a required field")]
[Range(1, int.MaxValue, ErrorMessage = "AutomationStepId must be valid")]
public int AutomationStepId { get; set; }
/// <summary>
/// Display SortOrder
/// </summary>
[Range(0, int.MaxValue, ErrorMessage = "SortOrder must be valid")]
public int SortOrder { get; set; }
/// <summary>
/// Description
/// </summary>
[StringLength(256, ErrorMessage = "Description maximum length is 256", MinimumLength = 1)]
public String Description { get; set; }
/// <summary>
/// Script
/// </summary>
[AllowHtml]
[StringLength(8000, ErrorMessage = "Script maximum length is 8000")]
public String Script { get; set; }
/// <summary>
/// Evaluate
/// </summary>
public int Evaluate { get; set; }
}
}
AutomationHelper (called in teh controller to get the list:
/// <summary>
/// The helper function to fetch the data so that we can get the scripts for each step for this automation (which we will find from the localParams)
/// </summary>
/// <param name="retailerId"> </param>
/// <param name="productId"> </param>
/// <returns>WE return a List of AutomationStepScripts, this object will be selected further with Lambda expression"></returns>
public List<AutomationStepScript> AutomationHelper(int retailerId, int productId)
{
List<AutomationStepScript> automationStepScripts = null;
var automationStepScriptRepository = new AutomationStepScriptRepository();
var productRepository = new ProductRepository();
var retailerCategoryRepository = new RetailerCategoryRepository();
var product = productRepository.GetProduct(productId);
var categoryId = product.CategoryId;
var retailerCategory = retailerCategoryRepository.GetRetailerCategoryByRetailerCategory(retailerId, categoryId);
// DO we have a retailer category?
if (retailerCategory != null)
{
// Yes, without a valid retailer category we cannot possibly find the automation
// Now here we have the RetailerCategory. The next question is Does this RetailerCategory HAVE an automation set up for it
if (retailerCategory.AutomationId != null)
{
// Yes, we have an automation. So lets get all of the Scripts for all of the steps for this automation
// Get All scripts for all steps for this automation.
automationStepScripts = automationStepScriptRepository.GetAllAutomationStepScriptsForAutomation((int)retailerCategory.AutomationId).ToList();
}
}
return automationStepScripts;
}
I am serializinf it as it bacomes teh body of a message which i am using in SQS which i then use later.
Please let me know if you need any more information.
Thanks.

Related

C# Net.Core Object.Equals() returning false even if both objects are the same

I am trying to return a list of recommended movies based on a client's favorite movies genres without including those that are already his favorites.
So, here is my Movie object
public class Movie
{
/// <summary>
/// Movie's title
/// </summary>
public string Title { get; set; }
/// <summary>
/// Movie's gengres list
/// </summary>
public List<string> Genres { get; set; }
private int HashCode
{
get
{
return this.Title.GetHashCode();
}
}
/// <summary>
/// Public default constructor. Initializes an empty Movie object
/// </summary>
public Movie()
{
Genres = new List<string>();
}
/// <summary>
/// Initializes a Movie object with a given title
/// </summary>
/// <param name="title">Movie title</param>
public Movie(string title)
{
Title = title;
Genres = new List<string>();
}
/// <summary>
/// Override ToString function
/// </summary>
/// <returns>Movie's title</returns>
public override string ToString()
{
return Title;
}
public override int GetHashCode()
{
return HashCode;
}
public override bool Equals(object obj)
{
if ((obj == null) || !this.GetType().Equals(obj.GetType()))
{
return false;
}
else
{
Movie movie = (Movie)obj;
return (HashCode == movie.HashCode);
}
}
}
And here is my Client object
public class Client
{
/// <summary>
/// Client's First Name
/// </summary>
public string FirstName { get; set; }
/// <summary>
/// Client's Last Name
/// </summary>
public string LastName { get; set; }
/// <summary>
/// Client's full name
/// </summary>
public string FullName
{
get
{
return $"{FirstName} {LastName}";
}
}
/// <summary>
/// Client's favorite movies
/// </summary>
public List<Movie> FavoriteMovies { get; set; }
/// <summary>
/// Public default constructor. Initializes an empty client
/// </summary>
public Client()
{
FavoriteMovies = new List<Movie>();
}
/// <summary>
/// Initializes a client object with a given name
/// </summary>
/// <param name="firstName">Client's first name</param>
/// <param name="lastName">Client's last name</param>
public Client(string firstName, string lastName)
{
FirstName = firstName;
FavoriteMovies = new List<Movie>();
}
/// <summary>
/// Initializes a client object with given name and list of favorite movies
/// </summary>
/// <param name="firstName">Client's first name</param>
/// <param name="lastName">Client's last name</param>
/// <param name="favoriteMovies">Client's list of favorite movies</param>
public Client(string firstName, string lastName, List<Movie> favoriteMovies)
{
FirstName = firstName;
LastName = lastName;
FavoriteMovies = favoriteMovies;
}
/// <summary>
/// Override ToString function that will return Client's full name
/// </summary>
/// <returns>Client's full name</returns>
public override string ToString()
{
return FullName;
}
Here's my movie list
Movies = new List<Movie>();
Movie movie1 = new Movie("Untitled Screenplay, adapted from the book, the Price of Fame, the biography of Dennis Price. Written by Eliane Parker and Gareth Owen.");
movie1.Genres.Add("Action");
Movies.Add(movie1);
Movie movie2 = new Movie("Cannery Row");
movie2.Genres.Add("Comedy");
movie2.Genres.Add("Drama");
movie2.Genres.Add("Romance");
Movies.Add(movie2);
Movie movie3 = new Movie("Body Melt");
movie3.Genres.Add("Comedy");
movie3.Genres.Add("Horror");
movie3.Genres.Add("Sci-Fi");
Movies.Add(movie3);
Movie movie4 = new Movie("Ghost Lab");
movie4.Genres.Add("Drama");
movie4.Genres.Add("Horror");
movie4.Genres.Add("Thriller");
Movies.Add(movie4);
Movie movie5 = new Movie("Biography: WWE Legends Biography: Booker T");
movie5.Genres.Add("Action");
Movies.Add(movie5);
Movie movie6 = new Movie("Zombie Wars");
movie6.Genres.Add("Action");
Movies.Add(movie6);
Movie movie7 = new Movie("Delitto in Formula Uno");
movie7.Genres.Add("Comedy");
movie7.Genres.Add("Crime");
movie7.Genres.Add("Thriller");
Movies.Add(movie7);
Movie movie8 = new Movie("The Long and Winding Road");
movie8.Genres.Add("Comedy");
movie8.Genres.Add("Drama");
Movies.Add(movie8);
And my client would be like this
Client client2 = new Client("Belinda", "Reed");
client2.FavoriteMovies.Add(movie2);
client2.FavoriteMovies.Add(movie7);
Clients.Add(client2);
That mean that client2 favorite movies titles Cannery Row (which is movie2) and Delitto in Formula Uno (which is movie7). Favorite genres list would be
"Comedy"
"Drama"
"Romance"
"Crime"
"Thriller"
And the function that will return the movie recommendations is
public static List<Movie> MovieRecommendationsByClient(Client client, List<Movie> moviesCatalog)
{
var recommendations = moviesCatalog
.Where(movie => client.FavoriteMovies
.Any(cm => (!cm.Equals(movie)
&& cm.Genres.Intersect(movie.Genres).Any()))).ToList();
return recommendations;
}
In theory, movie recommendations based on client's favorite movies genres list without including those that he already likes should be
"Body Melt"
"Ghost Lab"
"The Long and Winding Road"
But the assertion is failing because the return list is also including "Cannery Row" and "Delitto in Formula Uno"
I also tried in this way with the same results:
var recommendations = from movie in moviesCatalog
from clientMovie in client.FavoriteMovies
where !clientMovie.Equals(movie) && movie.Genres.Intersect(clientMovie.Genres).Any()
group movie by movie into moviesGroup
select moviesGroup.Key;
I've also tried comparing Movie Title properties orGetHashCode() function without any success.
When I am debugging the test, I can see that Equals(movie) returns the result that I am expecting, so I am thinking I am doing something wrong at the LINQ query.
Any help would be really appreciated
First you could to exclude movies you alread have in the client list and then filter by genres combination
It works:
var recommendations = moviesCatalog.Except(client.FavoriteMovies)
.Where(movie => client.FavoriteMovies
.Any(cm => cm.Genres.Intersect(movie.Genres).Any())).ToList();

ValidateModel does not work asp.net core 3.0

I trust you doing good, I am new to .net core API, I am doing a model validation, seems like it does not work,
Controller Action :
[HttpPost]
[ValidateModel]
[ProducesResponseType(StatusCodes.Status201Created, Type = typeof(ActionLog))]
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(ActionLog))]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
public async Task<IActionResult> CreateAsync([FromBody] ActionLog document)
{
try
{
if (ModelState.IsValid)
{
var result = await this.documentRepository.CreateAsync(document);
return this.StatusCode(StatusCodes.Status201Created,document.Id);
}
else
{
return this.StatusCode(StatusCodes.Status400BadRequest);
}
}
catch (Exception)
{
return this.StatusCode(StatusCodes.Status500InternalServerError, "Internal Server Error");
}
}
Model Class
public class ActionLog : ILogDocument
{
/// <summary>
/// Gets or sets the identifier.
/// </summary>
/// <value>
/// The identifier.
/// </value>
[BsonElement("id")]
[BsonId(IdGenerator = typeof(ObjectIdGenerator))]
public ObjectId Id { get; set; }
/// <summary>
/// Gets or sets the source identifier.
/// </summary>
/// <value>
/// The source identifier. Used to identify which source the log came from.
/// </value>
[BsonRequired]
[BsonElement("sourceId")]
public string SourceId { get; set; }
/// <summary>
/// Gets or sets the keywords.
/// </summary>
/// <value>
/// The keywords associated to the log.
/// Those keywords allow for faster searches compared to a full-text search on the payload.
/// This should be used as much as possible for common actions like: subject, type of action or target resource.
/// </value>
[BsonElement("keywords")]
public string[] Keywords { get; set; } = Array.Empty<string>();
/// <summary>
/// Gets or sets the payload of the action log (usually more detailed data).
/// </summary>
/// <value>
/// The payload.
/// </value>
[BsonElement("payload")]
public string Payload { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the date of event occurrence.
/// </summary>
/// <value>
/// The date.
/// </value>
[BsonDateTimeOptions(Kind = DateTimeKind.Utc)]
[BsonElement("date")]
public DateTime Date { get; set; } = DateTime.UtcNow;
}
Apart from that I have not coded any configurations in other classes or configuration, It returns 201 for every request.
reference : https://learn.microsoft.com/en-us/aspnet/web-api/overview/formats-and-model-binding/model-validation-in-aspnet-web-api

PartitionKey value must be supplied for this operation in cosmosdb delete operaiton

I am trying to delete a document from Cosmos DB
My code is like this:
public async Task<IHttpActionResult> DeletePartner(string id)
{
var telemetry = new TelemetryClient();
try
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var customers = await CosmosStoreHolder.Instance.CosmosStoreCustomer.Query().Where(x=> x.PartnerId == id).ToListAsync();
var userStore = CosmosStoreHolder.Instance.CosmosStoreUser;
var users = await userStore.Query().Where(x => x.PartnerId == id).ToListAsync(); ;
if (customers.Count> 0 || users.Count>0)
{
return BadRequest("You cant delete partners with existing customers or users");
}
else
{
var result = await CosmosStoreHolder.Instance.CosmosStorePartner.RemoveByIdAsync(id, "/CosmosEntityName");
return Ok(result);
}
}
catch (Exception ex)
{
string guid = Guid.NewGuid().ToString();
var dt = new Dictionary<string, string>
{
{ "Error Lulo: ", guid }
};
telemetry.TrackException(ex, dt);
return BadRequest("Error Lulo: " + guid);
}
}
}
[SharedCosmosCollection("shared")]
public class Partner : ISharedCosmosEntity
{
/// <summary>
/// Partner id
/// </summary>
[JsonProperty("Id")]
public string Id { get; set; }
/// <summary>
/// Partner name
/// </summary>
public string PartnerName { get; set; }
/// <summary>
/// Partner contact name
/// </summary>
public string PartnerContact { get; set; }
/// <summary>
/// Partner contact phone
/// </summary>
public string PartnerPhone { get; set; }
/// <summary>
/// Partner contact Office 365 domain
/// </summary>
public string PartnerDomain { get; set; }
/// <summary>
/// Partner type, silver, gold or platinum
/// </summary>
[ValidEnumValue]
public PartnerType PartnerType { get; set; }
/// <summary>
/// Partner start date
/// </summary>
public DateTime StartDate { get; set; }
/// <summary>
/// Partner end date
/// </summary>
public DateTime EndDate { get; set; }
/// <summary>
/// Parter enabled
/// </summary>
public bool Enabled { get; set; }
/// <summary>
/// CosmosEntityname
/// </summary>
[CosmosPartitionKey]
public string CosmosEntityName { get; set; }
}
/// <summary>
/// Partner type Enum
/// </summary>
public enum PartnerType
{
///Silver
Silver,
///Gold
Gold,
///Platinum
Platinum
}
But I got this error:
PartitionKey value must be supplied for this operation
I was trying to send as string "/CosmosEntityName" as second parameter, but it doesnt work
I am using Cosmonaut
You need to use the request options. For example, if your collection is partitioned by CosmosEntityName;
await this.documentClient.DeleteDocumentAsync(productDocument._self, new RequestOptions { PartitionKey = new Microsoft.Azure.Documents.PartitionKey(productDocument.CosmosEntityName) });
EDIT:
Here's what you need with Cosmonaut SDK
You need to provide the partition key value not the partition key
definition when you delete. Your delete request should look like this,
assuming the id is your partition key.
var deleted = await this._cosmonautClient.DeleteDocumentAsync(this._databaseName, collectionName, message.Id, new RequestOptions { PartitionKey = new PartitionKey(message.Id) });
You need to pass the value of the partition key of the element you want to delete as second parameter, not the path and attribute name.
var result = await CosmosStoreHolder.Instance.CosmosStorePartner.RemoveByIdAsync(id, "<partition key value for that id>");
Since the attribute you have defined as PK is CosmosEntityName, you need that attribute's value for that document.

Too much recursion when attempting to serialize object

I'm currently testing usage of YML in my application, to provide a more friendly means to editing and configuring e-mail downloads and imports for users.
For testing purposes, I wanted to see if my mocked-up YML is roughly equivalent to what the application would put out.
So, for testing I have this block of code, and it never fails to throw "Too much recursion".
// TEST SERIALIZE DATA
{
var _testDomain = default(Domain);
var _testSecureString = new SecureString();
"testP455w0rd".ToCharArray().ToList().ForEach(_testSecureString.AppendChar);
var _testConfig = new EmailBeamConfig() {
DefaultProtocol = ProtocolType.Imap,
Domains = new List<Domain> {
(_testDomain = new Domain {
Accounts = new List<EmailAccount> {
new EmailAccount {
Domain = _testDomain.DomainName,
InternalUid = "de.test_a_test-account",
Password = _testSecureString,
Username = "test-account#test.de"
},
new EmailAccount {
Domain = _testDomain.DomainName,
InternalUid = "de.test_a_test-account2",
Password = _testSecureString,
Username = "test-account2#test.de"
}
},
DomainName = "de.test",
EmailServer = "192.168.252.172",
UseEncryption = true
})
},
DownloadInterval = new TimeSpan(0, 10, 0),
DownloadRoot = mainSystemConfig.GetString("importRootDir")
};
var _testSerializer = new SerializerBuilder()
.WithNamingConvention(new UnderscoredNamingConvention())
.Build();
var _testSerializedData = _testSerializer.Serialize(_testConfig);
File.WriteAllText(cfgFile.FullName, _testSerializedData);
}
Here is the source to EmailBeamConfig:
internal class EmailBeamConfig {
private string _downloadRoot;
/// <summary>
/// Gets or sets the download root dir.
/// </summary>
public string DownloadRoot {
get => _downloadRoot;
set {
_downloadRoot = value;
DownloadRootObj = new DirectoryInfo(value);
}
}
/// <summary>
/// Gets an object representing the download root.
/// </summary>
public DirectoryInfo DownloadRootObj { get; private set; }
/// <summary>
/// Gets the interval for downloading e-mails.
/// </summary>
public TimeSpan DownloadInterval { get; set; }
/// <summary>
/// Gets or sets the default protocol to be used,
/// if a protocol is not defined for an individual user.
/// </summary>
public ProtocolType DefaultProtocol { get; set; }
public List<Domain> Domains { get; set; }
}
And EmailAccount:
/// <summary>
/// Defines an e-mail account for use in this plugin.
/// </summary>
internal struct EmailAccount {
private string _internalUid;
/// <summary>
/// Gets or sets the username for this e-mail account.
/// (Used for logging in, etc)
/// </summary>
internal string Username { get; set; }
/// <summary>
/// gets or sets the password for this account.
/// </summary>
internal SecureString Password { get; set; }
/// <summary>
/// Gets or sets the internal user identifier for this user account.
/// </summary>
internal string InternalUid {
get => _internalUid;
set {
if (value == "/" || value.Length < 10) {
_internalUid = $"{ Domain }_a_{ Username?.Split('#')[0] }";
} else _internalUid = value;
}
}
/// <summary>
/// Gets or sets the domain this e-mail account is associated with.
/// </summary>
internal string Domain { get; set; }
}
I'm completely oblivious to any source of infinite recursion at this point.
EDIT: Here is the Domain struct declaration:
internal struct Domain {
/// <summary>
/// Gets or sets the name of this domain.
/// </summary>
internal string DomainName { get; set; }
/// <summary>
/// Gets or sets the URL/IP address of the e-mail server.
/// </summary>
internal string EmailServer { get; set; }
/// <summary>
/// Gets or sets a value determining whether to attempt to use an encrypted connection to
/// the e-mail server.
/// </summary>
internal bool UseEncryption { get; set; }
/// <summary>
/// Gets the list of accounts associated with this domain
/// </summary>
internal List<EmailAccount> Accounts { get; set; }
}

Model binding new Datatables 1.10 parameters

In Datatables 1.10 the ajax server side parameters changed from
public class DataTableParamModel
{
public string sEcho{ get; set; }
public string sSearch{ get; set; }
public int iDisplayLength{ get; set; }
public int iDisplayStart{ get; set; }
public int iColumns{ get; set; }
public int iSortingCols{ get; set; }
public string sColumns{ get; set; }
}
to (API Here http://datatables.net/manual/server-side)
columns[i][data]
columns[i][name]
columns[i][orderable]
columns[i][search][regex]
columns[i][search][value]
columns[i][searchable]
...
draw
length
order[i][column]
order[i][dir]
...
search[regex]
search[value]
start
Some are easy to bind
public class DataTableParamModel
{
public string draw { get; set; }
public int length{ get; set; }
public int start { get; set; }
}
But the new array format looks tricky.
What is the new appropriate model to map the new parameter format?
Here is a model binder and class that will bind these new parameters...
Nuget Package: https://www.nuget.org/packages/Wetware.DataTables
Parameter Model:
[ModelBinder(typeof(DTModelBinder))]
public class DTParameterModel
{
/// <summary>
/// Draw counter. This is used by DataTables to ensure that the Ajax returns from
/// server-side processing requests are drawn in sequence by DataTables
/// </summary>
public int Draw { get; set; }
/// <summary>
/// Paging first record indicator. This is the start point in the current data set
/// (0 index based - i.e. 0 is the first record)
/// </summary>
public int Start { get; set; }
/// <summary>
/// Number of records that the table can display in the current draw. It is expected
/// that the number of records returned will be equal to this number, unless the
/// server has fewer records to return. Note that this can be -1 to indicate that
/// all records should be returned (although that negates any benefits of
/// server-side processing!)
/// </summary>
public int Length { get; set; }
/// <summary>
/// Global Search for the table
/// </summary>
public DTSearch Search { get; set; }
/// <summary>
/// Collection of all column indexes and their sort directions
/// </summary>
public IEnumerable<DTOrder> Order { get; set; }
/// <summary>
/// Collection of all columns in the table
/// </summary>
public IEnumerable<DTColumn> Columns { get; set; }
}
/// <summary>
/// Represents search values entered into the table
/// </summary>
public sealed class DTSearch
{
/// <summary>
/// Global search value. To be applied to all columns which have searchable as true
/// </summary>
public string Value { get; set; }
/// <summary>
/// true if the global filter should be treated as a regular expression for advanced
/// searching, false otherwise. Note that normally server-side processing scripts
/// will not perform regular expression searching for performance reasons on large
/// data sets, but it is technically possible and at the discretion of your script
/// </summary>
public bool Regex { get; set; }
}
/// <summary>
/// Represents a column and it's order direction
/// </summary>
public sealed class DTOrder
{
/// <summary>
/// Column to which ordering should be applied. This is an index reference to the
/// columns array of information that is also submitted to the server
/// </summary>
public int Column { get; set; }
/// <summary>
/// Ordering direction for this column. It will be asc or desc to indicate ascending
/// ordering or descending ordering, respectively
/// </summary>
public string Dir { get; set; }
}
/// <summary>
/// Represents an individual column in the table
/// </summary>
public sealed class DTColumn
{
/// <summary>
/// Column's data source
/// </summary>
public string Data { get; set; }
/// <summary>
/// Column's name
/// </summary>
public string Name { get; set; }
/// <summary>
/// Flag to indicate if this column is orderable (true) or not (false)
/// </summary>
public bool Orderable { get; set; }
/// <summary>
/// Flag to indicate if this column is searchable (true) or not (false)
/// </summary>
public bool Searchable { get; set; }
/// <summary>
/// Search to apply to this specific column.
/// </summary>
public DTSearch Search { get; set; }
}
Model Binder:
/// <summary>
/// Model Binder for DTParameterModel (DataTables)
/// </summary>
public class DTModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
base.BindModel(controllerContext, bindingContext);
var request = controllerContext.HttpContext.Request;
// Retrieve request data
var draw = Convert.ToInt32(request["draw"]);
var start = Convert.ToInt32(request["start"]);
var length = Convert.ToInt32(request["length"]);
// Search
var search = new DTSearch
{
Value = request["search[value]"],
Regex = Convert.ToBoolean(request["search[regex]"])
};
// Order
var o = 0;
var order = new List<DTOrder>();
while (request["order[" + o + "][column]"] != null)
{
order.Add(new DTOrder
{
Column = Convert.ToInt32(request["order[" + o + "][column]"]),
Dir = request["order[" + o + "][dir]"]
});
o++;
}
// Columns
var c = 0;
var columns = new List<DTColumn>();
while (request["columns[" + c + "][name]"] != null)
{
columns.Add(new DTColumn
{
Data = request["columns[" + c + "][data]"],
Name = request["columns[" + c + "][name]"],
Orderable = Convert.ToBoolean(request["columns[" + c + "][orderable]"]),
Searchable = Convert.ToBoolean(request["columns[" + c + "][searchable]"]),
Search = new DTSearch
{
Value = request["columns[" + c + "][search][value]"],
Regex = Convert.ToBoolean(request["columns[" + c + "][search][regex]"])
}
});
c++;
}
return new DTParameterModel
{
Draw = draw,
Start = start,
Length = length,
Search = search,
Order = order,
Columns = columns
};
}
}
Usage:
MyController.cs
public JsonResult DataTablesList(DTParameterModel model)
{
...
}
MVC6
If you're going to MVC6 you no longer need the model binder as MVC6 includes a JQueryFormValueProvider into the default model binder that can bind these values.
The model classes themselves may still be useful, however.
There is a bug to be fixed in 2.1.0 that doesn't allow binding for HttpGet but still works for HttpPost
Give this a try #shoe: datatables-mvc project: https://github.com/ALMMa/datatables-mvc
I changed my javascript to use the legacy ajax params option which uses the old parameters to send to the server. This is done through $.fn.dataTable.ext.legacy.ajax = true; so now my code becomes something like...
$.fn.dataTable.ext.legacy.ajax = true;
var datatable = $('#data-table').DataTable({
"processing": true,
"serverSide": true,
"ajax": "MyController/AjaxHandlerPaging",
"pageLength": 25,
"order": [[2, 'desc']],
"columns": []
});
Know this post is 2 years old but to those who want to use this with ASP.Net Core MVC 6. This is the converted/ upgraded answer provided by #Shoe
Model Binder:
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace TrackingAndTraining.Models
{
/// <summary>
/// Model Binder for DTParameterModel (DataTables)
/// </summary>
public class DTModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var request = bindingContext.ActionContext.HttpContext.Request.Form;
// Retrieve request data
var draw = Convert.ToInt32(request["draw"]);
var start = Convert.ToInt32(request["start"]);
var length = Convert.ToInt32(request["length"]);
// Search
var search = new DTSearch
{
Value = request["search[value]"],
Regex = Convert.ToBoolean(request["search[regex]"])
};
// Order
var o = 0;
var order = new List<DTOrder>();
while (!string.IsNullOrEmpty(request["order[" + o + "][column]"]))
{
order.Add(new DTOrder
{
Column = Convert.ToInt32(request["order[" + o + "][column]"]),
Dir = request["order[" + o + "][dir]"]
});
o++;
}
// Columns
var c = 0;
var columns = new List<DTColumn>();
while (!string.IsNullOrEmpty(request["columns[" + c + "][name]"]))
{
columns.Add(new DTColumn
{
Data = request["columns[" + c + "][data]"],
Name = request["columns[" + c + "][name]"],
Orderable = Convert.ToBoolean(request["columns[" + c + "][orderable]"]),
Searchable = Convert.ToBoolean(request["columns[" + c + "][searchable]"]),
Search = new DTSearch
{
Value = request["columns[" + c + "][search][value]"],
Regex = Convert.ToBoolean(request["columns[" + c + "][search][regex]"])
}
});
c++;
}
var result = new DTParameterModel
{
Draw = draw,
Start = start,
Length = length,
Search = search,
Order = order,
Columns = columns
};
bindingContext.Result = ModelBindingResult.Success(result);
return TaskCache.CompletedTask;
}
}
}
Parameter Model:
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace TrackingAndTraining.Models
{
[ModelBinder(BinderType = typeof(DTModelBinder))]
public class DTParameterModel
{
/// <summary>
/// Draw counter. This is used by DataTables to ensure that the Ajax returns from
/// server-side processing requests are drawn in sequence by DataTables
/// </summary>
public int Draw { get; set; }
/// <summary>
/// Paging first record indicator. This is the start point in the current data set
/// (0 index based - i.e. 0 is the first record)
/// </summary>
public int Start { get; set; }
/// <summary>
/// Number of records that the table can display in the current draw. It is expected
/// that the number of records returned will be equal to this number, unless the
/// server has fewer records to return. Note that this can be -1 to indicate that
/// all records should be returned (although that negates any benefits of
/// server-side processing!)
/// </summary>
public int Length { get; set; }
/// <summary>
/// Global Search for the table
/// </summary>
public DTSearch Search { get; set; }
/// <summary>
/// Collection of all column indexes and their sort directions
/// </summary>
public List<DTOrder> Order { get; set; }
/// <summary>
/// Collection of all columns in the table
/// </summary>
public List<DTColumn> Columns { get; set; }
}
/// <summary>
/// Represents search values entered into the table
/// </summary>
public sealed class DTSearch
{
/// <summary>
/// Global search value. To be applied to all columns which have searchable as true
/// </summary>
public string Value { get; set; }
/// <summary>
/// true if the global filter should be treated as a regular expression for advanced
/// searching, false otherwise. Note that normally server-side processing scripts
/// will not perform regular expression searching for performance reasons on large
/// data sets, but it is technically possible and at the discretion of your script
/// </summary>
public bool Regex { get; set; }
}
/// <summary>
/// Represents a column and it's order direction
/// </summary>
public sealed class DTOrder
{
/// <summary>
/// Column to which ordering should be applied. This is an index reference to the
/// columns array of information that is also submitted to the server
/// </summary>
public int Column { get; set; }
/// <summary>
/// Ordering direction for this column. It will be asc or desc to indicate ascending
/// ordering or descending ordering, respectively
/// </summary>
public string Dir { get; set; }
}
/// <summary>
/// Represents an individual column in the table
/// </summary>
public sealed class DTColumn
{
/// <summary>
/// Column's data source
/// </summary>
public string Data { get; set; }
/// <summary>
/// Column's name
/// </summary>
public string Name { get; set; }
/// <summary>
/// Flag to indicate if this column is orderable (true) or not (false)
/// </summary>
public bool Orderable { get; set; }
/// <summary>
/// Flag to indicate if this column is searchable (true) or not (false)
/// </summary>
public bool Searchable { get; set; }
/// <summary>
/// Search to apply to this specific column.
/// </summary>
public DTSearch Search { get; set; }
}
}
Again all credit goes to #Shoe for original post.
I ran into the same issue when moving to 1.10. Basically, I changed my parameter class like this (getting rid of the unsupported parameters):
public class jQueryDataTableParamModel
{
/// <summary>
/// Request sequence number sent by DataTable,
/// same value must be returned in response
/// </summary>
public string draw { get; set; }
/// <summary>
/// Number of records that should be shown in table
/// </summary>
public int length { get; set; }
/// <summary>
/// First record that should be shown(used for paging)
/// </summary>
public int start { get; set; }
}
In my controller, I get the search value, sort order, and sort column like this:
var searchString = Request["search[value]"];
var sortColumnIndex = Convert.ToInt32(Request["order[0][column]"]);
var sortDirection = Request["order[0][dir]"]; // asc or desc
The full server side binding implementation can be found here. I ran into a similar issue and this was how I went about solving it.

Categories