I have a simple WebAPI2 service that uses OData (System.Web.Http.OData, 5.1.0.0). Users can hit /odata/$metadata to get the available entities and properties. I'm looking for a way to extend this metadata with additional information, such as adding a "display name" value to a property.
I found information about "annotations" that sounds like it is what I want, but I cannot find anything explanining how to use this in my scenario or if it is even possible. I was trying to do something like the following:
model.SetAnnotationValue(((IEdmEntityType)m.FindType("My.Thing")).FindProperty("SomeProperty"),
namespaceName:"MyNamespace",
localName: "SomeLocalName",
value: "THINGS");
The type/property names are correct and the call succeeds, but the OData EDMX document does not contain this annotation. Is there some way to expose these annotations or otherwise do what I want?
Update:
Still at it. I've been looking at ODataMediaTypeFormatters as a possible way to handle this. There is an ASP.NET sample project that shows how to add instance annotations to entities. Close, but not exactly what I want, so now I'm trying to find a way to extend whatever generates the metadata document in a similar way.
I figured out a way to do this. The code below adds a custom namespace prefix "myns" and then adds an annotation on a model property:
const string namespaceName = "http://my.org/schema";
var type = "My.Domain.Person";
const string localName = "MyCustomAttribute";
// this registers a "myns" namespace on the model
model.SetNamespacePrefixMappings(new [] { new KeyValuePair<string, string>("myns", namespaceName), });
// set a simple string as the value of the "MyCustomAttribute" annotation on the "RevisionDate" property
var stringType = EdmCoreModel.Instance.GetString(true);
var value = new EdmStringConstant(stringType, "BUTTONS!!!");
m.SetAnnotationValue(((IEdmEntityType) m.FindType(type)).FindProperty("RevisionDate"),
namespaceName, localName, value);
Requesting the OData metadata document should give you something like this:
<edmx:Edmx Version="1.0">
<edmx:DataServices m:DataServiceVersion="3.0" m:MaxDataServiceVersion="3.0">
<Schema Namespace="My.Domain">
<EntityType Name="Person">
<Key><PropertyRef Name="PersonId"/></Key>
<Property Name="RevisionDate" Type="Edm.Int32" Nullable="false" myns:MyCustomAttribute="BUTTONS!!!"/>
</Schema>
</edmx:DataServices>
</edmx:Edmx>
You can set a custom property to any IEdmEntityType, thus also for . Simply modify kenchilada's code as follows:
m.SetAnnotationValue(m.FindType(type), namespaceName, localName, value);
Related
I am using Spring for GraphQL (version 2.7.0-M1).
In my domain model, a lot of properties return an object Foo. This object must be serialized to a String based on data available from GraphQlContext. So the schema looks like:
type Parent {
code: String!
foo: String
...
}
It is easy to do this with #SchemaMapping for a specific parent type.
#SchemaMapping(typeName = "Parent", field = "foo")
public String foo(Parent parent, DataFetchingEnvironment env) {
var context = env.getGraphQlContext();
return ...
However, this is not very DRY. I am looking for a way to have this code at one place, like a custom scalar.
Is there a way to do this with spring-graphql / graphql-java?
Example
An example is a Localized<T> object we use. For instance a Product instance has Localized<String> properties for title and description (and more).
For the GraphQL query we can set the context, part of the context is the Locale. For all Localized property values the value can be converted to the string value for the locale. We are looking for a way to do this automagically. Otherwise it creates a lot of boiler plate code
Would #ContextValue help here? This would remove a bit of boilerplate from your controller handlers.
#SchemaMapping(typeName = "Parent", field = "foo")
public String foo(Parent parent, #ContextValue Foo foo) {
If you'd like something more involved, I think you should elaborate on the exact relationship between an attribute of one or multiple types in your schema, and some random value in the context.
Maybe you could come up with some concrete example here?
I have the following entity, that references another entity.
class Foo {
String id;
String name supplierName;
**#DBRef** TemplateSchema templateSchema;
...
}
I want to be able to use the following JSON (or similar) to create a new entity.
{
"supplierName": "Stormkind",
"templateSchema": "572878138b749120341e6cbf"
}
...but it looks like Spring forces you to use a URI like this:
{
"supplierName": "Stormkind",
"templateSchema": "/template-schema/572878138b749120341e6cbf"
}
Is there a way to create the DBRef by posting an ID instead of a URI?
Thanks!
In REST, the only form of ID's that exist are URIs (hence the name Unique Resource Identifier). Something like 572878138b749120341e6cbf does not identify a resource, /template-schema/572878138b749120341e6cbf does.
On the HTTP level, entities do not exist, only resources identified by URIs. That's why Spring Data REST expects you to use URIs as identifiers.
I've seen in OData documentation that there are Edm types Date and Time. Currently in my database there are many DATE fields being represented in EF as DateTimes, so the ODataConventionModelBuilder is declaring them as Edm.DateTime. How can I change them to Edm.Date?
Was hoping I could do this:
entityType.Property(p => p.AgreementDate).EdmType = EdmType.Date;
The corresponding Edm type of certain property is mapped from the CLR type and can't be overrided by ODataConventionModelBuilder.
If you want to change the Edm type, you can ignore these properties within the ODataConventionModelBuilder.
After getting the Edm model by calling GetEdmModel on the ODataConventionModelBuilder, you can add these properties with Edm.Date to the Edm model by calling OData APIs.
Here's my answer in case someone is interested in the details of how to implement Feng Zhao's suggestion. I didn't find the API too discoverable, so I wanted to share.
First, build your EDM model as usual with the ODataConventionModelBuilder but ignore the date property:
...
entitType.Ignore(p => p.AgreementDate);
...
IEdmModel model = builder.GetEdmModel();
Then add the date property "manually":
var myType = (EdmStructuredType)model.FindDeclaredType(typeof(MyType).FullName);
var dateType = (IEdmPrimitiveType)model.FindType("Edm.Date");
myType.AddProperty(new EdmStructuralProperty(myType, "AgreementDate", new EdmPrimitiveTypeReference(dateType, false)));
That's it.
I have a situation in AutoMapper where I need to create a mapping with an interface destination. This is not a problem, and when using the normal Mapper.Map, it returns a proxy class as expected. However, when I try to do something similar with .Project().To(), which I need to use because an ORM is involved, I have issues. Here is the exact code that is failing which I replicated in a unit test:
AutoMapper.Mapper.CreateMap<RoleDto, IRole>(); //both just have Id/Name
IQueryable<RoleDto> roleDtos = new List<RoleDto>
{
new RoleDto { Id = 1, Name = "Role1" },
new RoleDto { Id = 2, Name = "Role2" }
}.AsQueryable();
//this works:
List<IRole> roles = roleDtos.Select(
roleDto => AutoMapper.Mapper.Map<IRole>(roleDto)
).ToList();
//this does not work:
List<IRole> roles2 = roleDtos.Project().To<IRole>().ToList();
I'm getting ArgumentException:
ArgumentException: Type 'UnitTestProject5.IRole' does not have a default constructor
In my real implementation the .Select is being performed on an Entity Framework collection, which is why I need to stick with .Project().To().
I have no issues with .Project().To() if the destination is not an interface. Additionally, I have no issues with the interface destination if I use .Map().
How can I get the interface destination and .Project.To() to work at the same time? Why is .Project.To() not giving me proxy classes like .Map() is? Any ideas?
Thanks!
Mapper.Map() takes the linq-to-objects route to materialize objects. As you said, AutoMapper is capable of creating types on the fly if the mapped target is an interface.
Project().To() is a way to translate the whole query, including the mapping, into SQL. Which is great, because only the properties that are required for the target object are included in the SQL query. However, the things AutoMapper does for creating types on the fly (undoubtedly some Refection voodoo) can never be part of an expression tree that can be converted into SQL. That's why Project.To simply tries to new up an object, even if it's an interface.
You'll have to use a concrete type as a mapping target. Of course, this type can implement an interface, so you can keep the independence you want.
if (settings.Contains("myDetailsObject"))
{
settings["myDetailsObject"] = myDetails;
}
else
{
settings.Add("myDetailsObject", myDetails);
}
settings.Save();
Tried doing the below, however it gave me error. those save values are in strings and is a custom object. tried even saving an integer instead and is still not working
Type 'SharedLibary.Object.MyDetailsObject' cannot be serialized. Consider marking it with the DataContractAttribute attribute, and marking all of its members you want serialized with the DataMemberAttribute attribute.
Add the attribute [DataMember] on all properties that you want to serialize in your MyDetailsObject class.
Mark class with [DataContractAttribute] attribute and all members that you want to serialize with [DataMemberAttribute]. Note, that marked properties must be public.
Also, don't forget to add reference to System.Runtime.Serialization