I'm trying to post a JSON to NancyFx. The JSON is the following:
{
"prop1": 1,
"entries":{
"Entry1": 1,
"entry2": 2
}
}
On server side I created a corresponding model:
public class Model
{
public int Prop1 { get; set; }
public IDictionary<string, object> Entries { get; set; }
}
entries field in JSON has a dynamic structure, and because of that IDictionary<string, object> is used in the model.
And then I bind the model:
this.Bind<Model>();
Model is created successfully but problem is that in Entries dictionary both keys are in capital case. For me case is very important and I expect second key to be entry2, not Entry2.
I also tried to use JavaScriptConverter and JavaScriptPrimitiveConverter but in Deserialize method I get already capitalized data.
Any ideas oh how to fix that?
For me this was solved by configuring JavascriptSerializer to retain casing.
Unfortunately I couldn't figure out a clean way to do this, but here's the hack I'm using for now.
public class Model
{
public IDictionary<string, object> Entries { get; set; }
}
public class CustomModelBinder : IModelBinder
{
public bool CanBind(Type modelType)
{
return modelType == typeof(Model);
}
public object Bind(NancyContext context, Type modelType, object instance, BindingConfig configuration, params string[] blackList)
{
using (var sr = new StreamReader(context.Request.Body))
{
return (new JavaScriptSerializer() { RetainCasing = true }).Deserialize<Model>(sr.ReadToEnd());
}
}
}
Nancy will pick up this binder at runtime, no need to explicitly register anything.
This solution is not ideal because it ignores some Nancy features like blacklists and possibly other binding configuration settings.
A better option is to set JsonSettings from Bootstrapper
public class MyBootstrapper : DefaultNancyBootstrapper
{
public MyBootstrapper () : base()
{
JsonSettings.RetainCasing = true;
}
}
Implementing IModelBinder works, but it messes up other default binding settings.
Related
I have maybe 60-70 classes that all have various Id columns that I would like to exclude when I return JSON data from the Web API. Internally I join on Id, but anything front-facing uses a Guid. So my primary key is the Id (int) and then there is a Guid there for the outside world to use to make things more secure.
Typically you just add [JsonIgnore] over the property and it takes care of it, but I have a lot of classes that may get updated from time to time. Whenever I scaffold everything and force an overwrite, it's going to remove my changes.
Instead of manually adding [JsonIgnore] to every Id column I want to exclude, it seems more logical to just handle this in OnModelCreating. I am able to loop through properties and use .Ignore, but that removes the property from everything else as well. I just don't want it to serialize and return any of the columns named "Id" and any foreign keys (which are also Ids).
So here is an example from one class
[JsonIgnore]
public int Id { get; set; }
public Guid Guid { get; set; }
public string Name { get; set; }
public bool? Active { get; set; }
[JsonIgnore]
public int HoldTypeId { get; set; }
public DateTime CreateDateTime { get; set; }
public DateTime UpdateDateTime { get; set; }
I can "make it work" the hard way, but I'm hoping there is a quick and easy way to achieve the same results so I can spend time on the important pieces.
EDIT:
Here is what is returning the data to the user.
// GET: api/Distributors
[HttpGet]
public async Task<ActionResult<IEnumerable<Distributor>>> GetDistributor()
{
return await _context.Distributor.ToListAsync();
}
You could write your own DefaultContractResolver to exclude any property that you want on serialization process.
Below there is an example for it:
public class PropertyIgnoringContractResolver : DefaultContractResolver
{
private readonly Dictionary<Type, string[]> _ignoredPropertiesContainer = new Dictionary<Type, string[]>
{
// for type student, we would like to ignore Id and SchooldId properties.
{ typeof(Student), new string[] { "Id", "SchoolId" } }
};
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty property = base.CreateProperty(member, memberSerialization);
string[] ignoredPropertiesOfType;
if (this._ignoredPropertiesContainer.TryGetValue(member.DeclaringType, out ignoredPropertiesOfType))
{
if (ignoredPropertiesOfType.Contains(member.Name))
{
property.ShouldSerialize = instance => false;
// Also you could add ShouldDeserialize here as well if you want.
return property;
}
}
return property;
}
}
then you should configure this in your Startup.cs in ConfigureServices like below
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc()
.AddJsonOptions(options => options.SerializerSettings.ContractResolver = new PropertyIgnoringContractResolver());
}
However what i actually would do is that i would create response DTO's to match the needs of my API responses. Instead of returning raw entity types. Like;
[HttpGet]
public async Task<ActionResult<IEnumerable<Distributor>>> GetDistributor()
{
return await _context.Distributor.Select(dist => new DistributorDTO
{
Name = dist.Name,
// so on..
}).ToListAsync();
}
By implementing something like this, you would also optimize your database queries as well by only selecting the properties that the API response requires.
I am trying to use a custom attribute on a Entity class generated automatically by the Entity Framework.
The problem is how to add an property attribute on an existing field?
Here the point where I am right now:
// the custom attribute class
public class MyCustomAttribute : Attribute
{
public String Key { get; set; }
}
// Entity Framework class generated automatically
public partial class EntityClass
{
public String Existent { get; set; }
//...
}
// set a metadata class for my entity
[MetadataType(typeof(EntityClassMetaData))]
public partial class EntityClass
{
// if I add a new property to the entity, it works. This attribute will be read
[MyCustomAttribute(Key = "KeyOne" )]
public int newProp { get; set; }
}
public class EntityClassMetaData
{
// adding the custom attribute to the existing property
[MyCustomAttribute(Key = "keyMeta") ]
public String Existent { get; set; }
}
Running this test:
[TestMethod]
public void test1()
{
foreach (var prop in typeof(EntityClass).GetProperties())
{
var att = prop.GetCustomAttribute<MyCustomAttribute>();
if (att != null)
{
Console.WriteLine($"Found {att.Key}");
}
}
}
will produce:
Found KeyOne
Or the Metadata class store the attribute in a different way or only works for data annotations.
I am stuck here, how can I set and read custom attributes of the generated class without having to edit the generated file?
I came across this same problem today. I figured EF magic would do the trick and map the attribute across to each model property. Turns out it does, but only for EF data annotations and I couldn't find an answered solution to pull out custom attributes so made this function. Hope it helps dude.
private object[] GetMetadataCustomAttributes(Type T, string propName)
{
if (Attribute.IsDefined(T, typeof(MetadataTypeAttribute)))
{
var metadataClassType =
(T.GetCustomAttributes(typeof(MetadataTypeAttribute), true).FirstOrDefault() as
MetadataTypeAttribute).MetadataClassType;
var metaDataClassProperty = metadataClassType.GetProperty(propName);
if (metaDataClassProperty != null)
{
return metaDataClassProperty.GetCustomAttributes(true);
}
}
return null;
}
I believe if you want to set an attribute in the metadata class, you have to use this syntax:
public class EntityClassMetaData
{
// adding the custom attribute to the existing property
[MyCustomAttribute(Key = "keyMeta") ]
public String Existent;
}
You must not have { get; set; } on your pre-existing property - just the property with the correct name and datatype.
I am creating a new C# OData4 Web API with a class named Call that has dynamic properties, which OData 4 allows via "Open Types". I believe I've set everything up and configured it correctly but the serialized response does not include the dynamic properties.
Did I configure something wrong?
public partial class Call
{
public int Id { get; set; }
public string Email { get; set; }
public IDictionary<string, object> DynamicProperties { get; }
}
public class CallController : ODataController
{
[EnableQuery]
public IQueryable<Call> GetCall([FromODataUri] int key)
{
return _context.Call.GetAll();
}
}
public static partial class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
AllowUriOperations(config);
ODataModelBuilder builder = new ODataConventionModelBuilder();
builder.ComplexType<Call>();
var model = builder.GetEdmModel();
config.MapODataServiceRoute(RoutePrefix.OData4, RoutePrefix.OData4, model);
}
private static void AllowUriOperations(HttpConfiguration config)
{
config.Count();
config.Filter();
config.OrderBy();
config.Expand();
config.Select();
}
}
You can enable serialization of null valued dynamic properties in OData open types by adding the following line (in my code in WebApiConfig.cs in method Register(HttpConfiguration config))
config.Properties.AddOrUpdate("System.Web.OData.NullDynamicPropertyKey", val=>true, (oldVal,newVal)=>true);
Then my dynamic properties with nulls start to serialize.
Or configuration.SetSerializeNullDynamicProperty(true); can be used, which is more cleaner and self-explanatory.
Can you check in your metadata, on the type Call do you have OpenType="true"? If not, try making it an EntitySet changing this line:
builder.ComplexType<Call>();
to this
builder.EntitySet<Call>("Calls");
If you do have OpenType="true" in your metadata, check that you definitely have some entries in your DynamicProperties collection
If the value in a key pair is null the property is simply not serialized. I was expecting it to be serialized to
"key" : null
Here are some additional examples
DynamicProperties.Add("somekey", 1);
"somekey" : 1
DynamicProperties.Add("somekey", "1");
"somekey" : "1"
DynamicProperties.Add("somekey", null);
I have a simple JavaScript string and object:
var name = "Scarlett Johansson";
var args = { arg1: "foo", arg2: "bar" };
And I want to pass them via $.ajax to a Web API controller:
public string Get([FromUri]TestClass input) {
// would like Args model-bound
}
And my TestClass is:
public class TestClass
{
public string Name { get; set; }
public Dictionary<string, string> Args { get; set; }
}
The Name property is bound as expected, but I haven't found a way to bind Args. I've tried JSON.stringify(args), $.param(args), using a List<KeyValuePair<string,string>> on TestClass instead of Dictionary, nothing has worked.
I was hoping I could achieve this via model binding instead of manually de-serializing the JSON. Is this possible?
Clarification: the number of keys/values would vary in "args" from call to call, hence my need for a Dictionary.
the default model binding wont work like that, it attempts to bind to public properties on objects. in this example, you would need a class containing like :
public class ArgClass
{
public string Arg1 { get; set; }
public string Arg2 { get; set; }
}
public class TestClass
{
public string Name { get; set; }
public List<ArgClass> Args { get; set; }
}
the alternative, which seems like you would want to do, is write a custom model binder, or a quick google search turns up this DefaultDictionaryBinder someone seems to have implemented already
https://github.com/loune/MVCStuff/blob/master/Extensions/DefaultDictionaryBinder.cs
Update: just realized you are using web api, which is i guess slightly different. Here's a blog post explaining how the binding works for web api: http://blogs.msdn.com/b/jmstall/archive/2012/04/16/how-webapi-does-parameter-binding.aspx
Let's extend your method with implementation (to see the result of what we've passed) like this:
public HttpResponseMessage Get([FromUri]TestClass input)
{
return Request.CreateResponse<TestClass>(HttpStatusCode.OK, input);
}
And if we would like to see this:
{
"Name":"MyName",
"Args":
{
"FirstKey":"FirstValue",
"SecondKey":"SecondValue"
}
}
Other words the testClass.Name == "MyName" and testClass.Args["FirstKey"] == "FirstValue"... we can call the API like this:
api/MyService/?name=MyName&args[0].key=FirstKey&args[0].value=FirstValue&args[1].key=SecondKey&args[1].value=SecondValue
The params on separated lines, just for clarity (URI will be without line breaks!):
api/MyService/
?name=MyName
&args[0].key=FirstKey
&args[0].value=FirstValue
&args[1].key=SecondKey
&args[1].value=SecondValue
I'm building an ASP.NET MVC 2 application in C# and i am successfully using Automapper to map values back and forth between ViewModels and business objects.
In addition to several explicit properties, my business objects wrap a dictionary as a catch all for properties that aren't explicitly defined. Something similar to:
public class MyBusinessObject {
public void SetExtraPropertyValue<T>(string key, T value) {
// ...
}
public T GetExtraPropertyValue<T>(string key, T defaultValue) {
// ...
}
}
In my ViewModel, I have the freedom to create any properties I want, but I cannot modify the business objects.
So let's say I create a view model like this:
class MyViewModel {
public string CustomProp { get; set; }
}
and the value I want to store and retrieve will need to use
businessModelInstance.SetExtraPropertyValue("CustomProp", newVal);
and
businessModelInstance.GetExtraPropertyValue("CustomProp");
I have problems going both directions.
First, when going from the MyBusinessObject to the MyViewModel, I thought it should be simple to do in my custom Automapper profile:
CreateMap<MyBusinessObject, MyViewModel>()
.ForMember(dest => dest.CustomProp,
opt => opt.MapFrom(s => s.GetExtraPropertyValue("CustomProp", "")));
However, MyBusinessObject.CustomProp is never populated, though other properties are.
Secondly, I don't know how to configure getting a value from MyViewModel.CustomProp to calling MyBusinessObject.SetExtraPropertyValue.
Is there a way to accomplish this
mapping with Automapper?
Is there a completely different attack that I
should be trying?
Do I have to resort to manual mapping in my controller? For example, MyBusinessObject.SetExtraPropertyValue("CustomProp",
MyViewModel.CustomProp)
UPDATE: Here is my solution based on #Patrick Steele's suggestions:
I added a custom attribute to the view model properties that i wanted to map to extra property keys. A custom TypeConverter uses reflection to find these attributes and map properties appropriately.
public class ItemExtraPropertyConverter : ITypeConverter<MyViewModel, MyBusinessObject>
{
public MyBusinessObject Convert(ResolutionContext context)
{
var destination = context.DestinationValue as MyBusinessObject;
if (destination == null )
throw new InvalidOperationException("Destination type is not of type MyBusinessObject");
foreach (var property in context.SourceType.GetProperties())
foreach (var attribute in property.GetCustomAttributes(true).OfType<ExtraPropertyAttribute>())
{
var key = attribute.Key;
if (string.IsNullOrEmpty(key))
key = property.Name;
destination.SetExtraPropertyValue(key, property.GetValue(context.SourceValue, null));
}
return destination;
}
}
public class ExtraPropertyAttribute : Attribute
{
public ExtraPropertyAttribute()
{
}
public ExtraPropertyAttribute(string key)
{
Key = key;
}
public string Key { get; set; }
}
public class MyViewModel
{
[ExtraProperty]
public string CustomProp { get; set; }
[ExtraProperty("OtherPropertyValue")]
public string CustomProp2 { get; set; }
}
In the custom profile class's configure method:
CreateMap<MyViewModel, MyBusinessObject>()
.ConvertUsing<ItemExtraPropertyConverter>();
My guess is that something is wrong with your GetExtraPropertyValue and SetExtraPropertyValue implementations. I threw together a quick test and the mapping you provided above worked as expected. Here's the implementation I used for the test:
public class MyBusinessObject
{
private readonly Dictionary<string, object> extraProperties = new Dictionary<string, object>();
public int Id { get; set; }
public string Name { get; set; }
public void SetExtraPropertyValue<T>(string key, T value)
{
extraProperties.Add(key, value);
}
public T GetExtraPropertyValue<T>(string key, T defaultValue)
{
if (extraProperties.ContainsKey(key))
{
return (T)extraProperties[key];
}
return defaultValue;
}
}