This is my ViewModel:
public class EntityViewModel
{
[Required(ErrorMessage = "Title is required")]
[StringLength(255)]
[DisplayName("Title")]
public string Title { get; set; }
[Required(ErrorMessage = "Description is required")]
[DisplayName("Description")]
public string Description { get; set; }
[Required]
public DateTime StartTime { get; set; }
[Required]
public DateTime EndTime { get; set; }
[Required]
public Decimal InstantSellingPrice { get; set; }
public Nullable<Decimal> ShippingPrice { get; set; }
public Int64 Views { get; set; }
public Int32 UserId { get; set; }
public int RegionId { get; set; }
public short SelectCategoryId { get; set; }
public SelectList Categories { get; set; }
public IEnumerable<HttpPostedFileBase> Files { get; set; }
public Condition? Condition { get; set; }
}
public enum Condition
{
New=1,
Used=2
}
This is my Create Action in my Controller:
public ActionResult Create()
{
ViewBag.DropDownList = ReUzze.Helpers.EnumHelper.SelectListFor<Condition>();
var model = new ReUzze.Models.EntityViewModel
{
Categories = new SelectList(this.UnitOfWork.CategoryRepository.Get(), "Id", "Name")
};
return View(model);
}
In my Create View:
<div class="form-group">
#Html.LabelFor(model => model.Condition)
#Html.DropDownListFor(model => model.Condition, ViewBag.DropDownList as SelectList, null)
</div>
I am using the Enumhelper that you can find here.
But now I always get this error in my Create View on this rule:
#Html.DropDownListFor(model => model.Condition, ViewBag.DropDownList as SelectList, null)
The error:
Error 1 The call is ambiguous between the following methods or properties: 'System.Web.Mvc.Html.SelectExtensions.DropDownListFor(System.Web.Mvc.HtmlHelper, System.Linq.Expressions.Expression>, System.Collections.Generic.IEnumerable, string)' and 'System.Web.Mvc.Html.SelectExtensions.DropDownListFor(System.Web.Mvc.HtmlHelper, System.Linq.Expressions.Expression>, System.Collections.Generic.IEnumerable, System.Collections.Generic.IDictionary)' c:\Users\Niels\Documents\Visual Studio 2012\Projects\ReUzze\ReUzze\Views\Entity\Create.cshtml 57 30 ReUzze
I use code like this usually.
public static class Enums {
public static IList<SelectListItem> SelectListOf<TEnum>(bool empty = false)
{
var type = typeof(TEnum);
if (type.IsEnum)
{
var list = Enum.GetValues(type)
.Cast<TEnum>()
.OrderBy(x => x)
.Select(x => new SelectListItem { Text = GetDescription(x), Value = x.ToString() })
.ToList();
if (empty)
{
list.Insert(0, new SelectListItem());
}
return list;
}
return new List<SelectListItem>();
}
private static string GetDescription(object enumerator)
{
try
{
//get the enumerator type
Type type = enumerator.GetType();
//get the member info
MemberInfo[] memberInfo = type.GetMember(enumerator.ToString());
//if there is member information
if (memberInfo != null && memberInfo.Length > 0)
{
//we default to the first member info, as it's for the specific enum value
object[] attributes = memberInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false);
//return the description if it's found
if (attributes != null && attributes.Length > 0)
return ((DescriptionAttribute)attributes[0]).Description;
}
//if there's no description, return the string value of the enum
return enumerator.ToString();
}
catch (Exception e)
{
return string.Empty;
}
}
}
Then you can use it like this:
Conditions = Enums.SelectListOf<Condition>();
Check out my blog post on this very subject.
http://jnye.co/Posts/4/creating-a-dropdown-list-from-an-enum-in-mvc-and-c%23
Here is an enum helper I use that turns an enum into a select list. Note: If the enum has a description (using the DescriptionAttribute) it will use that as its display text
public static class EnumHelper
{
// Get the value of the description attribute if the
// enum has one, otherwise use the value.
public static string GetDescription<TEnum>(this TEnum value)
{
var fi = value.GetType().GetField(value.ToString());
if (fi != null)
{
var attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
if (attributes.Length > 0)
{
return attributes[0].Description;
}
}
return value.ToString();
}
/// <summary>
/// Build a select list for an enum
/// </summary>
public static SelectList SelectListFor<T>() where T : struct
{
Type t = typeof(T);
return !t.IsEnum ? null
: new SelectList(BuildSelectListItems(t), "Value", "Text");
}
/// <summary>
/// Build a select list for an enum with a particular value selected
/// </summary>
public static SelectList SelectListFor<T>(T selected) where T : struct
{
Type t = typeof(T);
return !t.IsEnum ? null
: new SelectList(BuildSelectListItems(t), "Value", "Text", selected.ToString());
}
private static IEnumerable<SelectListItem> BuildSelectListItems(Type t)
{
return Enum.GetValues(t)
.Cast<Enum>()
.Select(e => new SelectListItem { Value = e.ToString(), Text = e.GetDescription() });
}
}
Once you have this helper class in place you can do the following.
In your controller:
//If you don't have an enum value use the type
ViewBag.DropDownList = EnumHelper.SelectListFor<MyEnum>();
//If you do have an enum value use the value (the value will be marked as selected)
ViewBag.DropDownList = EnumHelper.SelectListFor(MyEnum.MyEnumValue);
In your View:
#Html.DropDownList("DropDownList")
#* OR *#
#Html.DropDownListFor(m => m.Property, ViewBag.DropDownList as SelectList, null)
if you want a quick workaround just for this one view you can do something like this. Although Khalid's approach is recommended.
<select id = 'myenumdropdown' class='something'>
#foreach(var item in Enum.GetValues(typeof('yourenumtype')))
{
<option value=item.ToHashCode()>item.ToString()</option>
}
</select>
Related
Platform:
Blazor, EF Core 6
When sending an OData filter query to my server, it works for some properties and fails for others, although they have the same type.
public partial class DocumentRequestDTO
{
public DocumentRequestDTO()
{ }
[Key]
public Guid ID { get; set; }
public string AuthorID { get; set; } = string.Empty;
public string Authorname { get; set; } = string.Empty;
public Guid DocumentID { get; set; } = Guid.Empty;
public string OwnerID { get; set; } = string.Empty;
public string Ownername { get; set; } = string.Empty;
public string Filename { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public string ReasonForRequest { get; set; } = string.Empty;
public string WatermarkText { get; set; } = string.Empty;
public bool IsForInternalUse { get; set; } = false;
public DateTime TimeStamp { get; set; } = DateTime.Now;
public string ReasonForDecision { get; set; } = string.Empty;
public string Statusname { get; set; } = string.Empty;
public List<ProxyTooltipData>? Proxies { get; set; } = null;
}
}
Controller code:
[EnableQuery]
public IQueryable<DocumentRequestDTO> GetForSelect(string userID, bool showByAuthor)
{
IQueryable<DocumentRequest> qBase = null!;
try
{
qBase =
_context.Requests
.Include(r => r.Document)
.ThenInclude(d => d.Owner)
.Include(r => r.Author)
.Where(r => r.TimeStamp.Date >= DateTime.Today.AddDays(-ServerGlobals.RequestLifespan))
;
}
catch (Exception e)
{
Console.WriteLine($"Error '{e.Message}' occurred while quyering requests.");
}
UserInfo user = _context.UserInfo.FirstOrDefault(e => e.AccountID == userID);
if ((user != null) && (user.AccessLevel < AccessLevels.Admin))
{
try
{
if ((user.AccessLevel == AccessLevels.Requester) || showByAuthor)
{
qBase = qBase.Where(x => x.AuthorID == user.AccountID);
}
else
{
List<string> proxiedOwners = new();
foreach (var p in _context.ProxyInfo.Where(p => p.ProxyID == user.AccountID).ToList())
proxiedOwners.Add(p.AccountID);
qBase = qBase.Where(x => proxiedOwners.Contains(user.AccountID));
}
}
catch (Exception e)
{
Console.WriteLine($"Error '{e.Message}' occurred while determining proxied owners.");
}
}
IQueryable<DocumentRequestDTO> qFinal = null!;
try
{
qFinal = qBase
.Select(
x => new DocumentRequestDTO()
{
ID = x.ID,
AuthorID = x.AuthorID,
TimeStamp = x.TimeStamp,
DocumentID = x.Document.ID,
Filename = (x.Document == null) ? "unknown" : x.Document.Filename,
OwnerID = x.Document.OwnerID,
Ownername = x.Document.Owner.Name,
Authorname = x.Author.Name,
Description = x.Document.Description,
WatermarkText = x.WatermarkText,
ReasonForRequest = x.ReasonForRequest,
ReasonForDecision = x.ReasonForDecision,
IsForInternalUse = x.IsForInternalUse,
ReviewStatus = x.ReviewStatus,
Statusname = DocumentRequest.StatusName(x.ReviewStatus),
Proxies = new()
});
}
catch (Exception e)
{
Console.WriteLine($"Error '{e.Message}' occurred while building request result.");
}
List<DocumentRequestDTO> l = null!;
try
{
l = qFinal.ToList();
}
catch (Exception e)
{
Console.WriteLine($"Error '{e.Message}' occurred while verifying request result.");
}
return qFinal;
}
StatusName code:
public enum RequestStatus
{
Pending,
Approved,
Declined,
NoDecisionNeeded
}
public class DocumentRequest : PropertyIndexer
{
public static string StatusName(RequestStatus status)
{
switch (status)
{
case RequestStatus.Pending:
return "Pending";
case RequestStatus.Approved:
return "Approved";
case RequestStatus.Declined:
return "Declined";
case RequestStatus.NoDecisionNeeded:
return "No decision needed";
default:
return "unknown";
}
}
}
Working request:
https://localhost:12345/TestServer/RequestDto
?$count=true&$orderby=Ownername&$select=Ownername&userID=0388&showByAuthor=False
Result:
{#odata.context: "https://localhost:44393/DocServer2/$metadata#RequestDto(Ownername)", #odata.count: 4,…}
#odata.context: "https://localhost:44393/DocServer2/$metadata#RequestDto(Ownername)"
#odata.count: 4
value: [{Ownername: "Baggins, Frodo"},
{Ownername: "Baggins, Frodo"},
{Ownername: "Wonka, Willy"},…]
0: {Ownername: "Baggins, Frodo"}
1: {Ownername: "Baggins, Frodo"}
2: {Ownername: "Wonka, Willy"}
3: {Ownername: "Wonka, Willy"}
Failed request:
https://localhost:12345/TestServer/RequestDto
?$count=true&$orderby=Filename&$select=Filename&userID=0388&showByAuthor=False
Result:
{"#odata.context":"https://localhost:44393/DocServer2/$metadata
#RequestDto(Filename)",
"#odata.count":4,"value":[
The underlying data from the controller: (from the list l)
The error occurs when selecting Filename or Statusname. I found that when assigning constant values to these properties ("Testfile.pdf" and "Test Status") the error does not appear. However, the data returned from my controller is complete and cointains valid filenames for all records returned, and there are no exceptions thrown during execution of my controller's code; Not even when getting a list of the data from the query result.
Regarding Statusname: If you check my below related code, you will notice that there will always valid string data assigned to it.
I found the problem myself: It is the assignment of
Statusname = DocumentRequest.StatusName(x.ReviewStatus)
in the controller.
When OData tries to retrieve data using the IQueryable my controller returns, it fails to invoke that static class member, because it expects a property from a database table here.
Now the question is whether there is a workaround for that not requring adding database table properties, because I would like to display a user readable version of the data's ReviewStatus property.
how can I deserialize below json structure using newtonsoft json.net in .net.
{
"users" : {
"parentname":"test",
"100034" : {
"name" : "tom",
"state" : "WA",
"id" : "cedf-c56f-18a4-4b1"
},
"10045" : {
"name" : "steve",
"state" : "NY",
"id" : "ebb2-92bf-3062-7774"
},
"12345" : {
"name" : "mike",
"state" : "MA",
"id" : "fb60-b34f-6dc8-aaf7"
}
}
}
I tried below code but its not working. I got error 'Error converting value "test" to type 'ConsoleApplication2.User'. Path 'users.parentname', line 5, position 35.'
class Program
{
static void Main(string[] args)
{
string json = #"
{
""users"": {
""parentname"":""test"",
""10045"": {
""name"": ""steve"",
""state"": ""NY"",
""id"": ""ebb2-92bf-3062-7774""
}
}
}";
RootObject root = JsonConvert.DeserializeObject<RootObject>(json);
}
}
class RootObject
{
public string ParentName { get; set; }
public Dictionary<string, User> users { get; set; }
}
class User
{
public string name { get; set; }
public string state { get; set; }
public string id { get; set; }
public string ParentName { get; set; }
}
Please suggest.
You have a couple problems:
Your JSON has an extra level of nesting, with the root object containing a single property "users":
{
"users" : { ... }
}
Your data model needs to reflect this.
Your "users" object has a mixture of known and unknown property names. The question Deserialize json with known and unknown fields addresses a similar situation, however in your case your unknown properties always have a fixed schema and their values should be deserialized into a dictionary of POCOs -- specifically the User class. Therefore the answers there don't quite meet your needs, nor does the build-in functionality [JsonExtensionData].
The following converter allows for unknown properties to be deserialized into a typed container, rather than into an dictionary of arbitrary types:
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
public class JsonTypedExtensionDataAttribute : Attribute
{
}
public class TypedExtensionDataConverter<TObject> : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(TObject).IsAssignableFrom(objectType);
}
JsonProperty GetExtensionJsonProperty(JsonObjectContract contract)
{
try
{
return contract.Properties.Where(p => p.AttributeProvider.GetAttributes(typeof(JsonTypedExtensionDataAttribute), false).Any()).Single();
}
catch (InvalidOperationException ex)
{
throw new JsonSerializationException(string.Format("Exactly one property with JsonTypedExtensionDataAttribute is required for type {0}", contract.UnderlyingType), ex);
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var jObj = JObject.Load(reader);
var contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(objectType);
var extensionJsonProperty = GetExtensionJsonProperty(contract);
var extensionJProperty = (JProperty)null;
for (int i = jObj.Count - 1; i >= 0; i--)
{
var property = (JProperty)jObj.AsList()[i];
if (contract.Properties.GetClosestMatchProperty(property.Name) == null)
{
if (extensionJProperty == null)
{
extensionJProperty = new JProperty(extensionJsonProperty.PropertyName, new JObject());
jObj.Add(extensionJProperty);
}
((JObject)extensionJProperty.Value).Add(property.RemoveFromLowestPossibleParent());
}
}
var value = existingValue ?? contract.DefaultCreator();
using (var subReader = jObj.CreateReader())
serializer.Populate(subReader, value);
return value;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(value.GetType());
var extensionJsonProperty = GetExtensionJsonProperty(contract);
JObject jObj;
using (new PushValue<bool>(true, () => Disabled, (canWrite) => Disabled = canWrite))
{
jObj = JObject.FromObject(value, serializer);
}
var extensionValue = (jObj[extensionJsonProperty.PropertyName] as JObject).RemoveFromLowestPossibleParent();
if (extensionValue != null)
{
for (int i = extensionValue.Count - 1; i >= 0; i--)
{
var property = (JProperty)extensionValue.AsList()[i];
jObj.Add(property.RemoveFromLowestPossibleParent());
}
}
jObj.WriteTo(writer);
}
[ThreadStatic]
static bool disabled;
// Disables the converter in a thread-safe manner.
bool Disabled { get { return disabled; } set { disabled = value; } }
public override bool CanWrite { get { return !Disabled; } }
public override bool CanRead { get { return !Disabled; } }
}
public struct PushValue<T> : IDisposable
{
Action<T> setValue;
T oldValue;
public PushValue(T value, Func<T> getValue, Action<T> setValue)
{
if (getValue == null || setValue == null)
throw new ArgumentNullException();
this.setValue = setValue;
this.oldValue = getValue();
setValue(value);
}
#region IDisposable Members
// By using a disposable struct we avoid the overhead of allocating and freeing an instance of a finalizable class.
public void Dispose()
{
if (setValue != null)
setValue(oldValue);
}
#endregion
}
public static class JsonExtensions
{
public static TJToken RemoveFromLowestPossibleParent<TJToken>(this TJToken node) where TJToken : JToken
{
if (node == null)
return null;
var contained = node.AncestorsAndSelf().Where(t => t.Parent is JContainer && t.Parent.Type != JTokenType.Property).FirstOrDefault();
if (contained != null)
contained.Remove();
// Also detach the node from its immediate containing property -- Remove() does not do this even though it seems like it should
if (node.Parent is JProperty)
((JProperty)node.Parent).Value = null;
return node;
}
public static IList<JToken> AsList(this IList<JToken> container) { return container; }
}
Then use it in your classes as follows:
class RootObject
{
[JsonProperty("users")]
public Users Users { get; set; }
}
[JsonConverter(typeof(TypedExtensionDataConverter<Users>))]
class Users
{
public Users()
{
this.UserTable = new Dictionary<string, User>();
}
[JsonProperty("parentname")]
public string ParentName { get; set; }
[JsonTypedExtensionData]
public Dictionary<string, User> UserTable { get; set; }
}
class User
{
public string name { get; set; }
public string state { get; set; }
public string id { get; set; }
}
I wrote the converter in a fairly general way so it can be reused. A converter that is hardcoded for the Users type would require less code.
Your Json has to look like this:
{
"ParentName":"test",
"users":{
"10045":{
"name":"steve",
"state":"NY",
"id":"ebb2-92bf-3062-7774",
"ParentName":"someOtherName"
}
}
}
In order to deserialize it with your given class structure:
class RootObject
{
public string ParentName { get; set; }
public Dictionary<string, User> users { get; set; }
}
class User
{
public string name { get; set; }
public string state { get; set; }
public string id { get; set; }
public string ParentName { get; set; }
}
Now you can deserialize the Json string with:
var root = JsonConvert.DeserializeObject<RootObject>(json);
I am playing around with Azure Mobile Services. Right now I am trying to store an object of my custom class in a table.
Here is a snippet from the class which represent the object which I want to store in Azure.
public class CustomItem : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string Id { get; set; }
[JsonProperty(PropertyName = "Categorie")]
public CategorieObject Categorie { get; set; }
[JsonProperty(PropertyName = "Subcategorie")]
public SubcategorieObject Subcategorie { get; set; }
[JsonProperty(PropertyName = "Name")]
public string Name { get; set; }
...
}
My question is how to store custom types like CategorieObject or SubCategorieObject. These classes contain a String for the name and many other properties, but I only need to store the name of the Categorie and SubcategorieObject.
Maybe you can give me a hint, to solve my problem.
Thanks!
The post at http://blogs.msdn.com/b/carlosfigueira/archive/2012/09/06/supporting-arbitrary-types-in-azure-mobile-services-managed-client-complex-types.aspx shows one way to support complex objects in azure mobile service. For your scenario, you can send the data to the server and in the insert/read/update scripts you "change" the data to only store what you need. For example, assuming that you have those types on the client:
public class CustomItem : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string Id { get; set; }
[JsonProperty(PropertyName = "Categorie")]
public CategorieObject Categorie { get; set; }
[JsonProperty(PropertyName = "Subcategorie")]
public SubcategorieObject Subcategorie { get; set; }
[JsonProperty(PropertyName = "Name")]
public string Name { get; set; }
}
public class CategorieObject
{
[JsonProperty(PropertyName = "Name")]
public string Name { get; set; }
[JsonProperty(PropertyName = "SomethingElse")]
public string SomethingElse { get; set; }
}
public class SubcategorieObject
{
[JsonProperty(PropertyName = "Name")]
public string Name { get; set; }
[JsonProperty(PropertyName = "SomethingElse")]
public string SomethingElse { get; set; }
}
You'd change your insert script to replace the complex object (categorie / subcategorie) with the name, which is what you want to store:
function insert(item, user, request) {
// Replace the complex object with their "Name" property.
// Omitting error / format checking here for conciseness.
item.Categorie = item.Categorie.Name;
item.Subcategorie = item.Subcategorie.Name;
request.execute({
success: function() {
// Recreate the original object
item.Categorie = { Name: item.Categorie };
item.Subcategorie = { Name: item.Subcategorie };
request.respond();
}
});
}
Likewise when updating an item you'd need to do the same:
function update(item, user, request) {
// Replace the complex object with their "Name" property.
// Omitting error / format checking here for conciseness.
item.Categorie = item.Categorie.Name;
item.Subcategorie = item.Subcategorie.Name;
request.execute({
success: function() {
// Recreate the original object
item.Categorie = { Name: item.Categorie };
item.Subcategorie = { Name: item.Subcategorie };
request.respond();
}
});
}
And the same for reading:
function read(query, user, request) {
request.execute({
success: function(results) {
results.forEach(function(item) {
item.Categorie = { Name: item.Categorie };
item.Subcategorie = { Name: item.Subcategorie };
});
request.respond();
}
});
}
Notice that any other properties in the subclasses will be lost when reading from the database (after all, you're not storing them).
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; }
I tried to figure this out using the Windows Phone sample from the Facebook C# SDK page, but have been unsuccessful.
Here's the main code:
private void GetPages()
{
var fb = new FacebookClient(_accessToken);
fb.GetCompleted += (o, e) =>
{
if (e.Error != null)
{
Dispatcher.BeginInvoke(() => MessageBox.Show(e.Error.Message));
return;
}
var result = (IDictionary<string, object>)e.GetResultData();
// returns data and paging from Facebook
Dispatcher.BeginInvoke(() =>
{
foreach (var item in result)
{
// Not sure if/how to use the custom classes here
//item has .Key and .Value
//.Key = data and .Value contains the key/value pais for each of the pages returned
}
});
};
fb.GetAsync("me/accounts");
}
// Custom Classes
public class FacebookPageCollection
{
[JsonProperty(PropertyName = "data")]
public FacebookPage[] data { get; set; }
[JsonProperty(PropertyName = "paging")]
public FacebookPagePaging paging { get; set; }
}
public class FacebookPage
{
[JsonProperty(PropertyName = "name")]
public string Name { get; set; }
[JsonProperty(PropertyName = "access_token")]
public string AccessToken { get; set; }
[JsonProperty(PropertyName = "category")]
public string Category { get; set; }
[JsonProperty(PropertyName = "id")]
public string Id { get; set; }
}
public class FacebookPagePaging
{
[JsonProperty(PropertyName = "previous")]
public Uri previous { get; set; }
[JsonProperty(PropertyName = "next")]
public Uri next { get; set; }
}
This is what the variable "result" returns:
{"data":[{"name":"value1","access_token":"value2","category":"value3","id":"value4","perms":["ADMINISTER","EDIT_PROFILE","CREATE_CONTENT","MODERATE_CONTENT","CREATE_ADS","BASIC_ADMIN"]},{"name":"value1","access_token":"value2","category":"value3","id":"value4","perms":["ADMINISTER","EDIT_PROFILE","CREATE_CONTENT","MODERATE_CONTENT","CREATE_ADS","BASIC_ADMIN"]}],"paging":{"next":"url"}}
What I'd like to do is retrieve and save details for each page.
I have been trying to figure this out and have looked over a number of other posts on here and elsewhere. I just don't have enough experience to figure it out.
Any help is appreciated.
Thank you.
Sri
Here is a trick to understanding how to work with json response in fb c# sdk.
Here is the mapping between Javascript JSON and C# JSON. (Notice there is no DateTime and another complex .net objects as it is not part of the JSON spec found in JSON.org)
JsonObject => keyvalue pairs => IDictionary<string, object> / IDictinary<string, dynamic>
JsonArray => array => IList<object> / IList<dynamic>
string => string
number => long/decimal
boolean => bool
Here is how you do the actual mapping.
var result = (IDictionary<string, object>)e.GetResultData();
var data = (IList<object>)result["data"];
foreach(var act in data) {
var account = (IDictionary<string,object>) act;
var name = (string)account["name"];
var accessToken = (string)account["access_token"];
var id = (string)account["id"];
// normalize to IList<string> permissions, so it is easier to work without casting.
var permissions = ((IList<object>)account["perms"]).Select(x => (string)x).ToList();
}