I'm trying to convert
dynamic AdvanceEvent = JsonSerializer.Deserialize<dynamic>(File);
to model class that is AdvanceEventsDto
public class AdvanceEventsDto
{
/// <summary>
/// The Event List
/// </summary>
public EventsDto[] Events { get;set;}
/// <summary>
/// The Winner
/// </summary>
public string Winner { get; set; }
}
and EventDto calss is
public class EventsDto
{
/// <summary>
/// The Events
/// </summary>
public string Events { get; set; }
}
json is
{
"Events" : [
"France",
"Netherlands",
"Argentina",
"Brazil",
"France",
"Argentina",
"Qatar",
"Brazil",
"Germany",
"Japan",
"Brazil",
"Portugal",
"Japan",
"Japan",
"Brazil"
],
"Winner" : "Brazil"
}
when i'm trying to get values like follows
string FilePath = System.IO.Path.GetFullPath(#"..\..\Data\AdvanceEvents.json");
string File = System.IO.File.ReadAllText( FilePath);
dynamic AdvanceEvent = JsonSerializer.Deserialize<dynamic>( File);
var events = AdvanceEvent[0];
but i'm getting following error
The requested operation requires an element of type 'Array', but the target element has type 'Object'.
I'm trying to map AdvanceEvents.json values to class AdvanceEventsDto and class EventsDto
fix the class
public class AdvanceEventsDto
{
/// <summary>
/// The Event List
/// </summary>
public string[] Events { get;set;}
/// <summary>
/// The Winner
/// </summary>
public string Winner { get; set; }
}
and use this code
AdvanceEventsDto advanceEvent = JsonSerializer.Deserialize<AdvanceEventsDto>(File);
string[] events = advanceEvent.Events;
Related
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.
Here i have a Class Like
Public Class Employee
{
[Required]
public string FName { get; set; }
[Required]
public bool Married { get; set; }
public string WifeName { get; set; }
public string Wife_dOB { get; set; }
}
Here WifeName And Wife_Dob is Required only When Married Is True
Please Help me How can i Resolve this Problem
Im using Here MVC
You can use a custom validation attribute.
You can see bellow a solution that I used a few time ago:
/// <summary>
/// Provides conditional validation based on related property value.
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public sealed class RequiredIfAttribute : ValidationAttribute
{
#region Properties
/// <summary>
/// Gets or sets the other property name that will be used during validation.
/// </summary>
/// <value>
/// The other property name.
/// </value>
public string OtherProperty { get; private set; }
/// <summary>
/// Gets or sets the display name of the other property.
/// </summary>
/// <value>
/// The display name of the other property.
/// </value>
public string OtherPropertyDisplayName { get; set; }
/// <summary>
/// Gets or sets the other property value that will be relevant for validation.
/// </summary>
/// <value>
/// The other property value.
/// </value>
public object OtherPropertyValue { get; private set; }
/// <summary>
/// Gets or sets a value indicating whether other property's value should match or differ from provided other property's value (default is <c>false</c>).
/// </summary>
/// <value>
/// <c>true</c> if other property's value validation should be inverted; otherwise, <c>false</c>.
/// </value>
/// <remarks>
/// How this works
/// - true: validated property is required when other property doesn't equal provided value
/// - false: validated property is required when other property matches provided value
/// </remarks>
public bool IsInverted { get; set; }
/// <summary>
/// Gets a value that indicates whether the attribute requires validation context.
/// </summary>
/// <returns><c>true</c> if the attribute requires validation context; otherwise, <c>false</c>.</returns>
public override bool RequiresValidationContext
{
get { return true; }
}
#endregion
#region Constructor
/// <summary>
/// Initializes a new instance of the <see cref="RequiredIfAttribute"/> class.
/// </summary>
/// <param name="otherProperty">The other property.</param>
/// <param name="otherPropertyValue">The other property value.</param>
public RequiredIfAttribute(string otherProperty, object otherPropertyValue)
: base("'{0}' is required because '{1}' has a value {3}'{2}'.")
{
this.OtherProperty = otherProperty;
this.OtherPropertyValue = otherPropertyValue;
this.IsInverted = false;
}
#endregion
/// <summary>
/// Applies formatting to an error message, based on the data field where the error occurred.
/// </summary>
/// <param name="name">The name to include in the formatted message.</param>
/// <returns>
/// An instance of the formatted error message.
/// </returns>
public override string FormatErrorMessage(string name)
{
return string.Format(
CultureInfo.CurrentCulture,
base.ErrorMessageString,
name,
this.OtherPropertyDisplayName ?? this.OtherProperty,
this.OtherPropertyValue,
this.IsInverted ? "other than " : "of ");
}
/// <summary>
/// Validates the specified value with respect to the current validation attribute.
/// </summary>
/// <param name="value">The value to validate.</param>
/// <param name="validationContext">The context information about the validation operation.</param>
/// <returns>
/// An instance of the <see cref="T:System.ComponentModel.DataAnnotations.ValidationResult" /> class.
/// </returns>
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (validationContext == null)
{
throw new ArgumentNullException("validationContext");
}
PropertyInfo otherProperty = validationContext.ObjectType.GetProperty(this.OtherProperty);
if (otherProperty == null)
{
return new ValidationResult(
string.Format(CultureInfo.CurrentCulture, "Could not find a property named '{0}'.", this.OtherProperty));
}
object otherValue = otherProperty.GetValue(validationContext.ObjectInstance);
// check if this value is actually required and validate it
if (!this.IsInverted && object.Equals(otherValue, this.OtherPropertyValue) ||
this.IsInverted && !object.Equals(otherValue, this.OtherPropertyValue))
{
if (value == null)
{
return new ValidationResult(this.FormatErrorMessage(validationContext.DisplayName));
}
// additional check for strings so they're not empty
string val = value as string;
if (val != null && val.Trim().Length == 0)
{
return new ValidationResult(this.FormatErrorMessage(validationContext.DisplayName));
}
}
return ValidationResult.Success;
}
FluentValidation might be a good choice for your problem. With FluentValidation it would be easy to create an EmployeeValidator class to perform as much or as little validation as needed.
Note: The examples below are written using the standard FluentValidation package since I don't use MVC. There appears to be nuget packages for several versions of MVC in addition to the standard nuget package and your mileage may vary using those implementations.
Here is a very simple example of how to use FluentValidation:
void Main()
{
var emp = new Employee { FName = "John Smith", Married = true };
var val = new EmployeeValidator();
val.ValidateAndThrow(emp);
}
public class Employee
{
public string FName { get; set; }
public bool Married { get; set; }
public string WifeName { get; set; }
public string Wife_dOB { get; set; }
}
public class EmployeeValidator : AbstractValidator<Employee>
{
public EmployeeValidator()
{
RuleFor(e => e.WifeName).NotEmpty().When(e => e.Married);
RuleFor(e => e.Wife_dOB).NotEmpty().When(e => e.Married);
}
}
EDIT
I forgot to include an example of the output from the validation code above.
Example output:
ValidationException: Validation failed:
-- WifeName: 'Wife Name' must not be empty.
-- Wife_dOB: 'Wife_d OB' must not be empty.
An alternative approach, and in my opinion a better one, would be something like this that requires no validation, at least for the Married property:
public class Person
{
public Person(string firstName, string surname, DateTime dateOfBirth)
{
FirstName = firstName;
Surname = surname;
DateOfBirth = dateOfBirth;
}
public string FirstName { get; set; }
public string Surname { get; set; }
public string FullName => $"{FirstName} {Surname}";
public DateTime DateOfBirth { get; }
}
public class Employee : Person
{
public Employee(string firstName, string lastName, DateTime dateOfBirth, DateTime dateOfHire)
: base(firstName, lastName, dateOfBirth)
=> DateOfHire = dateOfHire;
public DateTime DateOfHire { get; }
public Person Spouse { get; set; }
public bool IsMarried => Spouse != null;
}
With this implementation the IsMarried property simply projects whether or not the Spouse property is set. This is purely for convenience but can often be helpful.
Some validation that might make sense for these objects could be the following:
public class PersonValidator : AbstractValidator<IPerson>
{
public PersonValidator()
{
RuleFor(p => p.FirstName).NotEmpty();
RuleFor(p => p.Surname).NotEmpty();
RuleFor(p => p.DateOfBirth).SetValidator(new DateOfBirthValidator());
}
}
public class EmployeeValidator : AbstractValidator<IEmployee>
{
private static readonly DateTime CompanyInceptionDate
= DateTime.Today.Subtract(TimeSpan.FromDays(365.25d * 10d));
public EmployeeValidator()
{
// Person rules
RuleFor(p => p.FirstName).NotEmpty();
RuleFor(p => p.Surname).NotEmpty();
RuleFor(p => p.DateOfBirth).SetValidator(new DateOfBirthValidator());
// Employee rules
RuleFor(e => e.DateOfHire).SetValidator(
// Can't be in the future nor older than the company
new DateRangeValidator(CompanyInceptionDate, DateTime.Today));
}
}
// This class really isn't necessary
// The built-in InclusiveBetween/ExclusiveBetween validators would work just as well
public class DateRangeValidator : PropertyValidator
{
protected const double DaysInAYear = 365.25d;
public DateRangeValidator(DateTime from, DateTime to)
: base($"{{PropertyName}} out of range. Expected between {from:yyyy-MM-dd} and {to:yyyy-MM-dd}.")
{
From = from;
To = to;
}
public DateTime From { get; }
public DateTime To { get; }
protected override bool IsValid(PropertyValidatorContext context)
=> context.PropertyValue is DateTime date
? date >= From && date <= To
: false;
}
public class DateOfBirthValidator : DateRangeValidator
{
private static readonly TimeSpan OneHundredAndFiftyYears
= TimeSpan.FromDays(DaysInAYear * 150d);
public DateOfBirthValidator()
// Can't be in the future nor older than 150 years
: base(DateTime.Today.Subtract(OneHundredAndFiftyYears), DateTime.Today) { }
}
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.
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.
I have the following ServiceContract:
[ServiceContract(Namespace = "http://WebAdmin.Services.AdministrativeService", Name = "AdministrativeService")]
public interface IAdministrativeService
{
/// <summary>
/// Add the group based on the provided criteria
/// </summary>
/// <example>
/// <code>
/// service.AddWebGroup
/// (new User {
/// SequenceNumber = 1,
/// },
/// new Group
/// {
/// GroupName = "Example Group"
/// },
/// new Application
/// {
/// ApplicationCode = "DS"
/// }
/// );
/// </code>
/// </example>
/// <param name="webUser"><see cref="WebUser"/>The user a</param>
/// <param name="group"><see cref="Group"/></param>
/// <param name="application"><see cref="Application"/></param>
/// <returns><see cref="Group"/></returns>
[OperationContract]
[TransactionFlow(TransactionFlowOption.Mandatory)]
[FaultContract(typeof(ErrorFault))]
[PrincipalPermission(SecurityAction.Demand, Role = "WEB_ADMIN_SVC")]
Group AddWebGroup(WebUser webUser, Group group, Application application);
}
With the following DataContracts:
[DataContract]
public class WebUser
{
[DataMember]
public long? SequenceNumber { get; set; }
[DataMember]
public List<UserRole> Roles { get; set; }
}
[DataContract]
public class UserRole
{
[DataMember]
public long? RoleID { get; set; }
[DataMember]
public string Name { get; set; }
[DataMember]
public Application Application { get; set; }
[DataMember]
public Group Group { get; set; }
}
{
[DataContract]
public class Application
{
[DataMember]
public string ApplicationCode { get; set; }
[DataMember]
public string ApplicationName { get; set; }
[DataMember]
public string ContactEmailAddress { get; set; }
[DataMember]
public string DivisionCode { get; set; }
}
[DataContract]
public class Group
{
[DataMember]
public long? GroupID { get; set; }
[DataMember]
public string GroupName { get; set; }
}
With the following implementation:
[ServiceBehavior(
ConcurrencyMode = ConcurrencyMode.Single,
InstanceContextMode = InstanceContextMode.PerSession,
ReleaseServiceInstanceOnTransactionComplete = false,
TransactionIsolationLevel = System.Transactions.IsolationLevel.Serializable
)]
public class AdministrativeService : ServiceBase, IAdministrativeService
{
#region Implementation of IAdministrativeService
/// <summary>
/// Add the group based on the provided criteria
/// </summary>
/// <example>
/// <code>
/// service.AddWebGroup
/// (new User {
/// SequenceNumber = 1,
/// },
/// new Group
/// {
/// GroupName = "Example Group"
/// },
/// new Application
/// {
/// ApplicationCode = "DS"
/// }
/// );
/// </code>
/// </example>
/// <param name="webUser"><see cref="WebUser"/>The user adding the group</param>
/// <param name="group"><see cref="Group"/>The Group being added</param>
/// <param name="application"><see cref="Application"/>The associated application</param>
/// <returns><see cref="Group"/> With the ID set</returns>
[OperationBehavior(TransactionAutoComplete = true, TransactionScopeRequired = true)]
public Group AddWebGroup(WebUser webUser, Group group, Application application)
{
if (webUser == null)
throw new ArgumentException("user");
if (webUser.SequenceNumber == long.MinValue)
throw new ArgumentException("user sequence number");
if (group == null || string.IsNullOrWhiteSpace(group.GroupName))
throw new ArgumentNullException("group");
if (application == null || string.IsNullOrWhiteSpace(application.ApplicationCode))
throw new ArgumentException("application");
var service = Resolve<IWebGroupService>();
var criteria = new GroupCriteriaEntity
{
ApplicationCode = application.ApplicationCode,
Group = new GroupEntity{GroupName = group.GroupName,},
User = new UserEntity{SequenceNumber = webUser.SequenceNumber,}
};
GroupEntity result = null;
try
{
result = service.InsertWebGroup(criteria);
}
catch (Exception ex)
{
ex.Data.Add("result",criteria);
ExceptionPolicy.HandleException(ex, "Service Policy");
}
var groupResult = new Group
{
GroupID = result.SequenceNumber
};
return groupResult;
}
The service model binding is using wsHttpBinding with TransportMessageCredential...
All of this excellent stuff works in a dev environment. Transactions commit and roll back as expected. The user credentials allow and prevent the user from executing the codes and all in all everyone is happy happy happy.
The test environment is supposed to be an identical setup as the dev environment. However, when the proxy client invokes the service I get the following exception:
HandlingInstanceID: 44a7c5ae-17fc-4d14-b55e-1aa53bb156e0
An exception of type 'System.ArgumentException' occurred and was caught.
06/13/2012 15:39:11
Type : System.ArgumentException, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
Message : Argument passed in is not serializable.
Parameter name: value
Source : mscorlib
Help link :
ParamName : value
Data : System.Collections.ListDictionaryInternal
TargetSite : Void Add(System.Object, System.Object)
Stack Trace : at System.Collections.ListDictionaryInternal.Add(Object key, Object value)
at Administrative.Service.AdministrativeService.AddWebGroup(WebUser webUser, Group group, Application application)
at SyncInvokeAddWebGroup(Object , Object[] , Object[] )
at System.ServiceModel.Dispatcher.SyncMethodInvoker.Invoke(Object instance, Object[] inputs, Object[]& outputs)
at System.ServiceModel.Dispatcher.DispatchOperationRuntime.InvokeBegin(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage5(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage31(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.MessageRpc.Process(Boolean isOperationContextSet)
Any ideas on why this would occur on one machine but not another? Why would I be getting an error that I am passing a non-serializable object when all objects are being passed are objects gnerated in the proxy client and are tagged with DataContract?
It looks like the exception is happening at the exception handler where you do: ex.Data.Add("result",criteria);, but I don't see in your data contracts (the ones you showed) where GroupCriteriaEntity is defined, is that one also serializable?