Model binding new Datatables 1.10 parameters - c#

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.

Related

Entity Framework 6 cannot select all columns but just a subset

I'm using c# .NET Framework 4.7.2, MySqlData and MySqlDataEntityFramework 8.0.22.0. This is my DbContext derived class:
/// <summary>
/// This is the class that maps the database table to the project's classes.
/// </summary>
[DbConfigurationType(typeof(MySqlEFConfiguration))]
class DatabaseContext : DbContext
{
/// <summary>
/// Session table.
/// </summary>
public DbSet<Session_table> Sessions { get; set; }
/// <summary>
/// Measure table.
/// </summary>
public DbSet<Measure_table> Measures { get; set; }
public DatabaseContext() : base()
{
//this.Configuration.LazyLoadingEnabled = false;
}
public DatabaseContext(System.Data.Common.DbConnection existingConnection, bool contextOwnConnection)
: base(existingConnection, contextOwnConnection)
{
}
///// <summary>
///// Measure Table.
///// </summary>
//public DbSet<Measure_table> Measures { get; set; }
/// <summary>
/// Override used to map class to database table.
/// </summary>
/// <param name="modelBuilder"></param>
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
#region Session Table
// session_id is the primary key
modelBuilder.Entity<Session_table>().HasKey(e => e.session_id);
// Specify the name of the table
modelBuilder.Entity<Session_table>().ToTable("session");
// Prevent MySql Server to auto-generate the value for primary key
modelBuilder.Entity<Session_table>().Property(a => a.session_id).HasDatabaseGeneratedOption(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.None);
// session_id: Specify name and mark as required
modelBuilder.Entity<Session_table>().Property(e => e.session_id).HasColumnName("session_id");
modelBuilder.Entity<Session_table>().Property(e => e.session_id).IsRequired();
// time_start: Specify name and mark as required
modelBuilder.Entity<Session_table>().Property(e => e.sessionTimeStart).HasColumnName("time_start");
modelBuilder.Entity<Session_table>().Property(e => e.sessionTimeStart).IsRequired();
// Time end: Specify name and mark as required
modelBuilder.Entity<Session_table>().Property(e => e.sessionTimeEnd).HasColumnName("time_end");
modelBuilder.Entity<Session_table>().Property(e => e.sessionTimeEnd).IsRequired();
// Description: Specify name and max length
modelBuilder.Entity<Session_table>().Property(e => e.description).HasColumnName("description");
modelBuilder.Entity<Session_table>().Property(e => e.description).HasMaxLength(255);
#endregion Session Table
#region Measure Table
var measureTable = modelBuilder.Entity<Measure_table>();
measureTable.HasKey(e => e.measure_id);
measureTable.HasIndex(a => a.session_id);
measureTable.HasRequired<Session_table>(e => e.session).WithMany().HasForeignKey(e => e.session_id);
measureTable.ToTable("measure");
// Measure Id
measureTable.Property(e => e.measure_id).HasColumnName("measure_id");
measureTable.Property(e => e.measure_id).IsRequired();
// Session Time
measureTable.Property(e => e.session_time).HasColumnName("session_time");
measureTable.Property(e => e.session_time).IsRequired();
// System Time
measureTable.Property(e => e.system_time).HasColumnName("system_time");
measureTable.Property(e => e.system_time).IsRequired();
// SubSystem
measureTable.Property(e => e.sub_system).HasColumnName("sub_system");
measureTable.Property(e => e.sub_system).IsRequired();
// Data Source
measureTable.Property(e => e.dataSource).HasColumnName("data_source");
measureTable.Property(e => e.dataSource).IsRequired();
// Raw
measureTable.Property(e => e.raw).HasColumnName("raw");
measureTable.Property(e => e.raw).IsRequired();
// Calib
measureTable.Property(e => e.calib).HasColumnName("calib");
measureTable.Property(e => e.calib).IsRequired();
// session id
measureTable.Property(e => e.session_id).HasColumnName("session_id");
measureTable.Property(e => e.session_id).IsRequired();
#endregion Measure Table
}
And here i have the two classes representing the database tables:
SESSION TABLE
/// <summary>
/// Session table
/// </summary>
public class Session_table
{
/// <summary>
/// Id of the session, primary key of the table.
/// </summary>
public int session_id { get; set; }
/// <summary>
/// Session datetime Start.
/// </summary>
public DateTime sessionTimeStart { get; set; } = DateTime.UtcNow;
/// <summary>
/// Session DateTime end.
/// </summary>
public DateTime sessionTimeEnd { get; set; } = DateTime.UtcNow;
/// <summary>
/// Session description.
/// Default: "No description"
/// </summary>
public string description { get; set; } = "No Description";
MEASURE TABLE (Has a foreign key for a one to many relationship: One session -> Many measures)
#region Enum
/// <summary>
/// Subsystem's type.
/// </summary>
public enum SubSystem
{
/// <summary>
/// Zerotype.
/// </summary>
ZERO,
/// <summary>
/// 1 type.
/// </summary>
FIRST,
/// <summary>
/// 2 type.
/// </summary>
SECOND
}
/// <summary>
/// Source of the measure.
/// </summary>
public enum DataSource
{
source1,
source_tbd
}
#endregion Enum
#region Properties
/// <summary>
/// Primary key (unique id) for the measure.
/// Default: 0
/// </summary>
public int measure_id { get; set; } = 0;
/// <summary>
/// DateTime of the session.
/// Default: DateTime min value.
/// </summary>
public DateTime session_time { get; set; } = DateTime.MinValue.ToUniversalTime();
/// <summary>
/// DateTime of the System.
/// Default: DateTime min value.
/// </summary>
public DateTime system_time { get; set; } = DateTime.MinValue.ToUniversalTime();
/// <summary>
/// SubSystem:
/// </summary>
public SubSystem sub_system { get; set; } = SubSystem.FIRST;
/// <summary>
/// Source of the measure.
/// </summary>
public DataSource dataSource { get; set; } = DataSource.source1;
/// <summary>
/// Raw value of the measure.
/// Default: 0
/// </summary>
public int raw { get; set; } = 0;
/// <summary>
/// Calibrated value of the measure.
/// Default: 0
/// </summary>
public double calib { get; set; } = 0;
/// <summary>
/// Foreign Key for One to Many relation.
/// </summary>
public virtual Session_table session { get; set; }
/// <summary>
/// Foreign key for the session table.
/// </summary>
public int session_id { get; set; } = 0;
Now i can connect to the database and insert an object (or list) to both table, i can also select some rows by using
var rows = context.Measures.Select(c => new { c.measure_id, c.raw, c.session_id, c.session.description }).Where(c => c.session_id == 666).ToList();
But when i try to get the all the column (the entire row) it fails in an exception "input string was not in the correct format", i've tried:
var rows = from t in context.Measures
where t.session_id == 666
select t;
var rows = (from t in context.Measures
where t.session_id == 666
select t).AsEnumerable<Measure_table>();
var rows = context.Measures.Where(a => a.session_id == 666).AsEnumerable<Measure_table>();
var rows = from a in context.Measures select a;
var rows = context.Measures;
But none of this works...for sure i'm missing something, but i cannot figure out what!
Thanks for any suggestion
I've also tried:
private void selectTest_2(string connet)
{
using (MySqlConnection connection = new MySqlConnection(connet))
{
try
{
connection.Open();
using (DatabaseContext context = new DatabaseContext(connection, false))
{
foreach (var row in context.Measures)
{
Console.WriteLine($"VALUE: {row.calib}" + Environment.NewLine);
System.Threading.Thread.Sleep(2000);
}
// Save to the database
context.SaveChanges();
}
}
catch (Exception exc)
{
Console.WriteLine($"Exception: {exc}");
}
}
}
But result in the same error. Now i'm sure that i'm missing something important...
I've found a workaround...it seems that the enum properties are the problem, by using a string field everything works, for the moment i'm using this "solution" but how can i safely and properly use the enums?
/// <summary>
/// SubSystem: ZERO, FIRST, SECOND
/// </summary>
public SubSystem sub_system_enum { get; set; } = SubSystem.FIRST;
/// <summary>
/// String properties for database compatibility
/// </summary>
public string sub_system
{
get
{
return this.sub_system_enum.ToString();
}
set
{
this.sub_system_enum = Enum.TryParse<SubSystem>(value, true, out SubSystem val) ? val : default(SubSystem);
}
}
And onModelCreating override method:
// IGnore enums
measureTable.Ignore(e => e.sub_system_enum);
measureTable.Ignore(e => e.dataSource_enum);
Database table:
enter image description here

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; }
}

MVC c# Comments Listing / Display

I am trying to find the most efficient way to display a set of nested comments from a database schema and into a c# object that i can convert to a serialised JSON object.
My Schema is as follows:
As you can see from the screenshot there is an Update which has a parent issue id (not relevant here) and a Comment. There is also a ParentUpdateID which enables the table to store nested replies for each reply and so on.
I have created a class within c# which i want to convert the results from my Entity Framework call;
/// <summary>
/// The resource view model.
/// </summary>
public class Comment
{
/// <summary>
/// Gets or sets the id.
/// </summary>
public Guid Id { get; set; }
/// <summary>
/// Gets or sets the body.
/// </summary>
public string Body { get; set; }
/// <summary>
/// Gets or sets the modified by.
/// </summary>
public string ModifiedBy { get; set; }
/// <summary>
/// Gets or sets the modified date.
/// </summary>
public DateTime ModifiedDate { get; set; }
/// <summary>
/// Gets or sets the created by.
/// </summary>
public string CreatedBy { get; set; }
/// <summary>
/// Gets or sets the created date.
/// </summary>
public DateTime CreatedDate { get; set; }
/// <summary>
/// Gets or sets the parent comment id.
/// </summary>
public Guid? ParentCommentId { get; set; }
/// <summary>
/// Gets or sets the children.
/// </summary>
public List<Comment> Children { get; set; }
}
And here is how i am retrieving the List of Comments / Updates from the database.
List<ClientIssuesUpdate> updates = this.db.ClientIssuesUpdates.Where(i => i.IssueId == issueId).ToList();
Is there a clever way through either a loop or using lync that can order all of the comments showing most recent first, but then display in date order each comments children and so on. (e.g. All comments without a ParentUpdateId would be to level comments)
Any guidance would be helpful so i can learn for the future.
Many thanks
This should order them by date created.
Depending on how deep your comments children gets nested, you would have to add more loops.
List<ClientIssuesUpdate> updates = this.db.ClientIssuesUpdates.Where(i => i.IssueId == issueId).ToList().OrderByDescending(p => p.CreatedDate);
foreach (var comment in updates.Where(comment => comment.Children != null))
{
// order the children
comment.Children = new List<Comment>(comment.Children.OrderByDescending(c => c.CreatedDate));
}
*cant remember if you should use OrderBy<> or OrderByDescending<>

Can i JSON serialize a object which contains a list?

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.

Categories