When I make mvc ajax json post applicaiton, there is a trouble to convert json dynamic object to entity.
In my app, movie is a business entity, json object has row status property than movie entity. When json data is posted to mvc server side, it can be converted to dynamic object, everyting is ok in this stage. But after handling some logic to each row status, it is needed to convert dynamic object to movie business entity, then begin database transaction logic. But there is a troulbe even I try different method to cast the object.
please did someone use the same cast method? thanks your advice or reply.
public class movie
{
public int id
{
get;
set;
}
public string title
{
get;
set;
}
}
/// <summary>
/// Convert Json Object to Entity
/// </summary>
/// <param name="id">ajax post value
/// format: {"id": "{\"id\": 1, \"title\": \"sharlock\", \"RowStatus\": \"deleted\"}"}
/// </param>
[AllowAnonymous]
[HttpPost]
public void DoJsonSimple(string id)
{
string title;
var entity = Newtonsoft.Json.JsonConvert.DeserializeObject<dynamic>(id);
//*** entity is dynamic object
//*** entity.id, entity.title and entity.RowStauts can be accessed.
int first = entity.id;
var status = entity.RowStatus;
if (status == "deleted")
{
//*** m1 is null
//*** m1.title can not be accessed
movie m1 = entity as movie;
title = m1.title;
//*** m2 is an empty object
//*** m2.id is 0, m2.title is null
var m2 = AutoMapperHelper<dynamic, movie>.AutoConvertDynamic(entity);
title = m2.title;
//*** Exception: Object must implement IConvertible.
var m3 = EmitMapper.EMConvert.ChangeTypeGeneric<dynamic, movie>(entity);
title = m3.title;
}
}
Just create another class for the rows.
public class Request
{
[JsonProperty("id")]
public string Json { get; set; }
}
public class Movie
{
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("title")]
public string Title { get; set; }
}
// either this for variant 1...
public class Row
{
public string RowStatus { get; set; }
}
// or that for variant 2...
public class MovieRow : Movie
{
public string RowStatus { get; set; }
}
[AllowAnonymous]
[HttpPost]
public void DoJsonSimple_Variant1(string id)
{
var json = JsonConvert.DeserializeObject<Request>(id).Json;
var entity = JsonConvert.DeserializeObject<MovieRow>(json);
var row = JsonConvert.DeserializeObject<Row>(json);
switch (row.RowStatus)
{
case "deleted":
// delete entity
break;
// ...
}
// ...
}
[AllowAnonymous]
[HttpPost]
public void DoJsonSimple_Variant2(string id)
{
var json = JsonConvert.DeserializeObject<Request>(id).Json;
var row = JsonConvert.DeserializeObject<MovieRow>(json);
var entity = (Movie)row;
switch (row.RowStatus)
{
case "deleted":
// delete entity
break;
// ...
}
// ...
}
Related
I'm trying to consume a soccer API (SportMonks) in my ASP.NET MVC Project. I'm new to this so I'm facing a problem of sending deserialized JSON from Controller to View. Here is the code:-
JSON Response:
{"data":[{"id":1,"name":"Europe"},{"id":2,"name":"Asia"},{"id":3,"name":"Africa"},{"id":4,"name":"Oceania"},{"id":5,"name":"Antarctica"},{"id":6,"name":"North America"},{"id":7,"name":"South America"}],"meta":{"subscription":{"started_at":{"date":"2017-03-20 21:02:56.000000","timezone_type":3,"timezone":"UTC"},"trial_ends_at":null,"ends_at":null},"plan":{"name":"Free Plan","price":"0.00","request_limit":"3,1"},"sports":[{"id":1,"name":"Soccer","current":true},{"id":6,"name":"Cricket","current":false}]}}
Models:-
public class SAPI_Continent
{
public int id { get; set; }
public string name { get; set; }
}
public class SoccerAPI
{
public IList<SAPI_Continent> continents { get; set; }
}
Contorller
[HttpGet]
public ActionResult ConsumeExternalAPI()
{
var webClient = new WebClient();
var json = webClient.DownloadString(#"https://soccer.sportmonks.com/api/v2.0/continents?api_token=nAfVFSbRn3x7tP7b840mitMVli48deY0sVndIB6zpae8MquprEivAuH7zplu");
SoccerAPI continents = JsonConvert.DeserializeObject<SoccerAPI>(json);
return View(continents);
}
continents is always null although var json is returning the data normally
View
#if (Model != null)
{
<ul>
#foreach (var con in Model.continents)
{
<li>#con.name</li>
}
</ul>
}
else
{
<p>Error</p>
}
How Can I get the values from the `data` part only `id & name` and why the deserialised variable is null?
Referring to your "null" question. Your continents attribute should be changed to data, as this is the name returned from json.
I have an api controller in my webcore application:
[Route("api/[controller]")]
public class DataController : Controller
{
protected ApplicationDbContext dbContext;
public DataController(ApplicationDbContext dc)
{
dbContext = dc;
}
[HttpGet("Categories")]
public List<Category> GetCategories()
{
var l = dbContext.Categories.OrderBy(c => c.Name).ToList();
return l;
}
}
And the class:
public class Category
{
public long Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
When I Invoke the controller action to get the categories, in the response the name of properties are all decapitalized. That is:
Id becomes id,
Name becomes name,
Description becomes description.
**Edit:
I have tried also:
[HttpGet("Test")]
public IActionResult Test()
{
var l = dbContext.Categories.OrderBy(c => c.Name).ToList();
return Json(l);
}
And still the properties are all decapitalized
/// <summary>
/// Welcome Note Message
/// </summary>
/// <returns>In a Json Format</returns>
public JsonResult WelcomeNote()
{
Category cs = new Category();
cs.Id = 123456;
cs.Name = "ExampleName";
cs.Description = "Abcd";
return Json(cs, JsonRequestBehavior.AllowGet);
}
This, I am getting from above code which you want I guess
Refer this for more good Examples
I managed to create an ApiController retrieving data from my repositories, and populating a grid in my view through Bootgrid with Ajax. This is an example of request data sent to Api's Action, given by their Docs here (look for POST Body Request tab):
current=1&rowCount=10&sort[sender]=asc&searchPhrase=&id=b0df282a-0d67-40e5-8558-c9e93b7befed
Here is an example URL:
http://localhost/api/SomeController?current=1&rowCount=10&sort%5BName%5D=asc&searchPhrase=&id=b0df282a-0d67-40e5-8558-c9e93b7befed
I created two Helper classes to handle data I must return as response, and sort data (as it's an array):
public class SortData
{
public string Field { get; set; } // FIeld Name
public string Type { get; set; } // ASC or DESC
}
public class BootgridResponseData<T> where T: class
{
public int current { get; set; } // current page
public int rowCount { get; set; } // rows per page
public IEnumerable<T> rows { get; set; } // items
public int total { get; set; } // total rows for whole query
}
Therefore, my action is as follow:
public BootgridResponseData<SomeViewModel> Get(int current, int rowCount, List<SortData> sort, string searchPhrase, string id)
{
// get items and return a bootgrid response data with them...
}
The method is invoked and all parameters come with data properly, except sort, which is always null.
What kind of parameter should I expect for this? I also tried to put object but it comes null anyway.
After learning a bit more, I saw Bootgrid has a requestHandler setting which allows you to manipulate data sent to server.
I did it in my javascript like this:
var grid = $("#my-grid").bootgrid({
ajax: true,
rowCount: 10,
ajaxSettings: {
method: "POST",
cache: true
},
requestHandler: function (request) {
// Scan the original sort object from Bootgrid...
// and populate an array of "SortData"...
request.sortItems = [];
if (request.sort == null)
return request;
for (var property in request.sort) {
if (request.sort.hasOwnProperty(property)) {
request.sortItems.push({ Field: property, Type: request.sort[property] });
}
}
return request;
},
url: "/api/FooApi"
});
Then I created my post action in API like this:
public class FooApiController : ApiController
{
[HttpPost]
public BootgridResponseData<FooModel> Get(BootgridRequestData model)
{
// This would come from some data store, using the request params...
// I use PagedList to make pagination easier...
IPagedList<FooModel> itemsPaged = store.GetPagedFoo();
// Then return the response with data...
return new BootgridResponseData<FooModel>()
{
current = model.current,
rowCount = model.rowCount,
rows = itemsPaged,
total = itemsPaged.TotalItemCount
};
}
}
The BootgridResponseData has already been shown in my question. I just added a BootgridRequestData which the following structure:
public class BootgridRequestData
{
public int current { get; set; }
public int rowCount { get; set; }
public string searchPhrase { get; set; }
public IEnumerable<SortData> sortItems { get; set; }
}
Then I could even use my original SortData helper class:
public class SortData
{
public string Field { get; set; } // FIeld Name
public string Type { get; set; } // ASC or DESC
}
I've struggled with this as well. You are overthinking it. It's nice to create simple models to handle the post call from jquery-bootgrid, but you can also just use simple parameters in the post method. As for the sort, it looks like a Key-Value pair, but that doesn't serialize properly.
I ended up trying a Dictionary object and it works.
Here is my signature:
[HttpPost]
public async Task<ActionResult> GetActiveDogs(int? current, int? rowCount,
Dictionary<string, string> sort, string searchPhrase = null)
I had the same problem passing the sort options to my webservice. The Dictionary object did not solve my problem either.
To solve it, I created a class holding string properties for each field I wanted to pass through the bootgrid sort options. See code excerpt
class TestSort
{
public string field1 { get; set; }
public string field2 { get; set; }
...
}
I use this class as the sort options parameter in my webservice. All fields in this class that are referred to by the bootgrid options are set to "ASC" or "DESC". The others remain null.
I added an 'orderBy' property to this class that returns an orderby clause for the fields that are not null.
Approch1.
consider you have table with columns "col1, col2, col3, ...".
you can use:
public ActionType Get(int current, int rowCount, Sort sort, string searchPhrase) {
//sort.col1 == 'asc' (consider sorted by col1 in ascending order)
}
public class Sort
{
public string col1 { get; set; }
public string col2 { get; set; }
public string col3 { get; set; }
//... other columns
}
Approach 2.
You can use remove you parameters and parse request data manually. i used post here instead of get.
[HttpPost]
public object Post(){
string jsonContent = Request.Content.ReadAsStringAsync().Result;
Dictionary<string, string> keyvalues = new Dictionary<string, string>();
string[] keyvalue_strings = jsonContent.Split('&');
string sort_column = "";
string sort_direction = "";
for (var i = 0; i< keyvalue_strings.Length; i++)
{
var a = keyvalue_strings[i].Split('=');
a[0] = a[0].Replace("%5B", "[").Replace("%5D", "]");
keyvalues.Add(a[0], (a[1]));
if (a[0].Contains("sort"))
{
sort_column = a[0].Replace("sort[", "").Replace("]", "");
sort_direction = a[1];
}
}
//now you have keyvalues, sort_column, sort_direction.
//...
}
I want my Json data to look like this:
{"Person": {
"PersonId": "1"
"Name":"Brad",
"Age":"45",
...
}
where "Person" would be the type of Object returned.
When I see XML format it always returns data with Object Type specified
<Person>
<PersonId>1</PersonId>
<Name>Brad</Name>
<Age>45</Age>
</Person>
Where Person is type of Object.
so is there any way we get the Json data as specified in the format above ?
May be:
public class Person
{
public int PersonId { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
public class PersonController : ApiController
{
/*{"ArrayOfPerson":[{"PersonId":1,"Name":"Brad","Age":45},{"PersonId":2,"Name":"Mary","Age":30}]}*/
[HttpGet]
public HttpResponseMessage Get()
{
Person person1 = new Person { PersonId = 1, Name = "Brad", Age = 45 };
Person person2 = new Person { PersonId = 2, Name = "Mary", Age = 30 };
IEnumerable<Person> data = new List<Person> { person1, person2 };
JToken json = MyHelper.ToJson(data);
return Request.CreateResponse<JToken>(json);
}
//{"Person":{"PersonId":1,"Name":"Brad1","Age":45}}
[HttpGet]
public HttpResponseMessage Get(int id)
{
Person data = new Person { PersonId = id, Name = string.Concat("Brad", id), Age = 45 };
JToken json = MyHelper.ToJson<Person>(data);
return Request.CreateResponse<JToken>(json);
}
}
public static class MyHelper
{
public static JToken ToJson<T>(T source)
{
JObject result = new JObject();
Type type = typeof(T);
string key = type.Name;
if (typeof(IEnumerable).IsAssignableFrom(type))
key = string.Format("ArrayOf{0}", GetEnumeratedType(type).Name);
JToken value = (source != null) ? JToken.FromObject(source) : JValue.CreateNull();//Newtonsoft.Json 6.0.6
result.Add(key, value);
return result;
}
/// <summary>
/// Use http://stackoverflow.com/questions/4129831/how-do-i-get-the-array-item-type-from-array-type-in-net
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public static Type GetEnumeratedType(Type type)
{
// provided by Array
var elType = type.GetElementType();
if (null != elType) return elType;
// otherwise provided by collection
var elTypes = type.GetGenericArguments();
if (elTypes.Length > 0) return elTypes[0];
// otherwise is not an 'enumerated' type
return null;
}
}
I have following JSON string which is received from an external party.
{
"team":[
{
"v1":"",
"attributes":{
"eighty_min_score":"",
"home_or_away":"home",
"score":"22",
"team_id":"500"
}
},
{
"v1":"",
"attributes":{
"eighty_min_score":"",
"home_or_away":"away",
"score":"30",
"team_id":"600"
}
}
]
}
My mapping classes:
public class Attributes
{
public string eighty_min_score { get; set; }
public string home_or_away { get; set; }
public string score { get; set; }
public string team_id { get; set; }
}
public class Team
{
public string v1 { get; set; }
public Attributes attributes { get; set; }
}
public class RootObject
{
public List<Team> team { get; set; }
}
The question is that I don't like the Attributes class name and the attributes field names in the Team class. Instead, I want it to be named TeamScore and also to remove _ from the field names and give proper names.
JsonConvert.DeserializeObject<RootObject>(jsonText);
I can rename Attributes to TeamScore, but if I change the field name (attributes in the Team class), it won't deserialize properly and gives me null. How can I overcome this?
Json.NET - Newtonsoft has a JsonPropertyAttribute which allows you to specify the name of a JSON property, so your code should be:
public class TeamScore
{
[JsonProperty("eighty_min_score")]
public string EightyMinScore { get; set; }
[JsonProperty("home_or_away")]
public string HomeOrAway { get; set; }
[JsonProperty("score ")]
public string Score { get; set; }
[JsonProperty("team_id")]
public string TeamId { get; set; }
}
public class Team
{
public string v1 { get; set; }
[JsonProperty("attributes")]
public TeamScore TeamScores { get; set; }
}
public class RootObject
{
public List<Team> Team { get; set; }
}
Documentation: Serialization Attributes
If you'd like to use dynamic mapping, and don't want to clutter up your model with attributes, this approach worked for me
Usage:
var settings = new JsonSerializerSettings();
settings.DateFormatString = "YYYY-MM-DD";
settings.ContractResolver = new CustomContractResolver();
this.DataContext = JsonConvert.DeserializeObject<CountResponse>(jsonString, settings);
Logic:
public class CustomContractResolver : DefaultContractResolver
{
private Dictionary<string, string> PropertyMappings { get; set; }
public CustomContractResolver()
{
this.PropertyMappings = new Dictionary<string, string>
{
{"Meta", "meta"},
{"LastUpdated", "last_updated"},
{"Disclaimer", "disclaimer"},
{"License", "license"},
{"CountResults", "results"},
{"Term", "term"},
{"Count", "count"},
};
}
protected override string ResolvePropertyName(string propertyName)
{
string resolvedName = null;
var resolved = this.PropertyMappings.TryGetValue(propertyName, out resolvedName);
return (resolved) ? resolvedName : base.ResolvePropertyName(propertyName);
}
}
Adding to Jacks solution. I need to Deserialize using the JsonProperty and Serialize while ignoring the JsonProperty (or vice versa). ReflectionHelper and Attribute Helper are just helper classes that get a list of properties or attributes for a property. I can include if anyone actually cares. Using the example below you can serialize the viewmodel and get "Amount" even though the JsonProperty is "RecurringPrice".
/// <summary>
/// Ignore the Json Property attribute. This is usefule when you want to serialize or deserialize differently and not
/// let the JsonProperty control everything.
/// </summary>
/// <typeparam name="T"></typeparam>
public class IgnoreJsonPropertyResolver<T> : DefaultContractResolver
{
private Dictionary<string, string> PropertyMappings { get; set; }
public IgnoreJsonPropertyResolver()
{
this.PropertyMappings = new Dictionary<string, string>();
var properties = ReflectionHelper<T>.GetGetProperties(false)();
foreach (var propertyInfo in properties)
{
var jsonProperty = AttributeHelper.GetAttribute<JsonPropertyAttribute>(propertyInfo);
if (jsonProperty != null)
{
PropertyMappings.Add(jsonProperty.PropertyName, propertyInfo.Name);
}
}
}
protected override string ResolvePropertyName(string propertyName)
{
string resolvedName = null;
var resolved = this.PropertyMappings.TryGetValue(propertyName, out resolvedName);
return (resolved) ? resolvedName : base.ResolvePropertyName(propertyName);
}
}
Usage:
var settings = new JsonSerializerSettings();
settings.DateFormatString = "YYYY-MM-DD";
settings.ContractResolver = new IgnoreJsonPropertyResolver<PlanViewModel>();
var model = new PlanViewModel() {Amount = 100};
var strModel = JsonConvert.SerializeObject(model,settings);
Model:
public class PlanViewModel
{
/// <summary>
/// The customer is charged an amount over an interval for the subscription.
/// </summary>
[JsonProperty(PropertyName = "RecurringPrice")]
public double Amount { get; set; }
/// <summary>
/// Indicates the number of intervals between each billing. If interval=2, the customer would be billed every two
/// months or years depending on the value for interval_unit.
/// </summary>
public int Interval { get; set; } = 1;
/// <summary>
/// Number of free trial days that can be granted when a customer is subscribed to this plan.
/// </summary>
public int TrialPeriod { get; set; } = 30;
/// <summary>
/// This indicates a one-time fee charged upfront while creating a subscription for this plan.
/// </summary>
[JsonProperty(PropertyName = "SetupFee")]
public double SetupAmount { get; set; } = 0;
/// <summary>
/// String representing the type id, usually a lookup value, for the record.
/// </summary>
[JsonProperty(PropertyName = "TypeId")]
public string Type { get; set; }
/// <summary>
/// Billing Frequency
/// </summary>
[JsonProperty(PropertyName = "BillingFrequency")]
public string Period { get; set; }
/// <summary>
/// String representing the type id, usually a lookup value, for the record.
/// </summary>
[JsonProperty(PropertyName = "PlanUseType")]
public string Purpose { get; set; }
}
Expanding Rentering.com's answer, in scenarios where a whole graph of many types is to be taken care of, and you're looking for a strongly typed solution, this class can help, see usage (fluent) below. It operates as either a black-list or white-list per type. A type cannot be both (Gist - also contains global ignore list).
public class PropertyFilterResolver : DefaultContractResolver
{
const string _Err = "A type can be either in the include list or the ignore list.";
Dictionary<Type, IEnumerable<string>> _IgnorePropertiesMap = new Dictionary<Type, IEnumerable<string>>();
Dictionary<Type, IEnumerable<string>> _IncludePropertiesMap = new Dictionary<Type, IEnumerable<string>>();
public PropertyFilterResolver SetIgnoredProperties<T>(params Expression<Func<T, object>>[] propertyAccessors)
{
if (propertyAccessors == null) return this;
if (_IncludePropertiesMap.ContainsKey(typeof(T))) throw new ArgumentException(_Err);
var properties = propertyAccessors.Select(GetPropertyName);
_IgnorePropertiesMap[typeof(T)] = properties.ToArray();
return this;
}
public PropertyFilterResolver SetIncludedProperties<T>(params Expression<Func<T, object>>[] propertyAccessors)
{
if (propertyAccessors == null)
return this;
if (_IgnorePropertiesMap.ContainsKey(typeof(T))) throw new ArgumentException(_Err);
var properties = propertyAccessors.Select(GetPropertyName);
_IncludePropertiesMap[typeof(T)] = properties.ToArray();
return this;
}
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
var properties = base.CreateProperties(type, memberSerialization);
var isIgnoreList = _IgnorePropertiesMap.TryGetValue(type, out IEnumerable<string> map);
if (!isIgnoreList && !_IncludePropertiesMap.TryGetValue(type, out map))
return properties;
Func<JsonProperty, bool> predicate = jp => map.Contains(jp.PropertyName) == !isIgnoreList;
return properties.Where(predicate).ToArray();
}
string GetPropertyName<TSource, TProperty>(
Expression<Func<TSource, TProperty>> propertyLambda)
{
if (!(propertyLambda.Body is MemberExpression member))
throw new ArgumentException($"Expression '{propertyLambda}' refers to a method, not a property.");
if (!(member.Member is PropertyInfo propInfo))
throw new ArgumentException($"Expression '{propertyLambda}' refers to a field, not a property.");
var type = typeof(TSource);
if (!type.GetTypeInfo().IsAssignableFrom(propInfo.DeclaringType.GetTypeInfo()))
throw new ArgumentException($"Expresion '{propertyLambda}' refers to a property that is not from type '{type}'.");
return propInfo.Name;
}
}
Usage:
var resolver = new PropertyFilterResolver()
.SetIncludedProperties<User>(
u => u.Id,
u => u.UnitId)
.SetIgnoredProperties<Person>(
r => r.Responders)
.SetIncludedProperties<Blog>(
b => b.Id)
.Ignore(nameof(IChangeTracking.IsChanged)); //see gist
I am using JsonProperty attributes when serializing but ignoring them when deserializing using this ContractResolver:
public class IgnoreJsonPropertyContractResolver: DefaultContractResolver
{
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
var properties = base.CreateProperties(type, memberSerialization);
foreach (var p in properties) { p.PropertyName = p.UnderlyingName; }
return properties;
}
}
The ContractResolver just sets every property back to the class property name (simplified from Shimmy's solution). Usage:
var airplane= JsonConvert.DeserializeObject<Airplane>(json,
new JsonSerializerSettings { ContractResolver = new IgnoreJsonPropertyContractResolver() });
Also if you want to ignore something use this
[JsonIgnore]
public int Id { get; set; }
[JsonProperty("id")]
Public string real_id { get; set; }