ODataConventionModelBuilder with inherited entities - c#

I've an WebAPI OData v3 interface using ODataConventionModelBuilder. It contains some entities which are inherited, and also a model which has a collection of the abstract object:
public abstract class BaseObject
{
[Key]
public int Id { get; set; }
[ForeignKey("Object3")]
public int? ParentId { get; set; }
public virtual Object3 Parent { get; set; }
}
public class Object1: BaseObject
{
}
public class Object2: BaseObject
{
}
public class Object3
{
[Key]
public int Id { get; set; }
public ICollection<BaseObject> MyObjects { get; set; }
}
I'm calling the interface using Breeze with client side metadata, using expand:
http://example.com/api/Object3?$expand=MyObjects
The server response looks like this:
{
"odata.type":"MyNamespace.Object1",
"odata.id":"http://example.com/api/BaseObject(1)",
"Parent#odata.navigationLinkUrl":"http://example.com/api/BaseObject(1)/Parent",
"Id":1,
"ParentId":1
}
Breeze now recognizes this as an entity of type Object1. But if I modify the entity and save the changes it makes a POST request to http://example.com/api/BaseObject(1). To being able to handle the different concrete types I need the POST request to go to the specific controller http://example.com/api/Object(1).
What do I need to change so that Breeze makes to update POST calls to the concrete controller and not the controller of the base object?
UPDATE: After inspecting the Breeze source code, it seems like Breeze uses the odata.id as URI for the POST request. Is it somehow possible to have the OData API return the URI for the concrete object as odata.id instead of the base object?

I got this working with a nasty hack by removing extraMetadata from all entities before saving with breeze:
var entities = manager.getEntities(null, breeze.EntityState.Modified);
for (var i = 0; i < entities.length; i++) {
delete entities[i].entityAspect.extraMetadata;
}
It there are no extraMetadata (which contains the odata.id) are available, breeze calculates the URI to the controller of the concrete model.
I don't know if there's a better solution available, that the OData API sends the correct odata.id in the first place.

Related

Attributes added by custom ModelMetadataProvider are not being considered in model-binding

Disclaimer: this is a long one. Unreasonable knowledge of ASP.NET Core MVC internals is almost certainly required. Here be dragons.
Background
I am trying to implement a method for augmenting type metadata in ASP.NET MVC Core.
The reason I want to do this is that my data models are used by multiple projects, so I've shared them by placing them in a NuGet package:
// defined in NuGet package
public class MyModel
{
public int MyProperty { get; set; }
public MyNestedModel NestedModel { get; set; }
}
public class MyNestedModel
{
public bool NestedProperty { get; set; }
}
^ Listing 1
However, some of the projects will need to apply additional metadata to the model types - for example, in the case of an ASP.NET Core project, these models will be used as inputs and therefore participate in model-binding, so they require FromQuery, FromHeader etc. attributes applied. I obviously cannot do this in the package, as different consumer projects will need to apply different attributes depending on their use cases.
The simplest and mostly guaranteed-to-work way is to modify all model properties to be virtual and then have subclasses that override those properties as necessary to add attributes:
// defined in NuGet package
public class MyModel
{
public virtual int MyProperty { get; set; }
public virtual MyNestedModel NestedModel { get; set; }
}
public class MyNestedModel
{
public virtual bool NestedProperty { get; set; }
}
// defined in ASP.NET MVC project consuming above package
public class MyViewModel : MyModel
{
[FromQuery(Name = "foo")]
public override int MyProperty { get; set; }
public new // can't use override, as type is different
MyNestedViewModel NestedModel { get; set; }
}
[Bind(Prefix = "")]
public class MyNestedViewModel : MyNestedModel
{
[FromQuery(Name = "bar")]
public override bool NestedProperty { get; set; }
}
^ Listing 2
I don't want to do this, because in every model that has a child model property, that child model has to be subclassed, and then it cannot be used as an override but has to be re-declared with new - and the semantics of new won't work for me here. Also, I don't really intend for the model types to be subclassed.
I'm aware of ModelMetadataTypeAttribute but the intended usage of that attribute is on the model type to be augmented, not the type doing the augmenting. Since in my case there's no way to know ahead of time what the actual metadata types will be (as they're defined in the consuming projects), I cannot use ModelMetadataTypeAttribute. Subclasses and partial classes will also not work - ModelMetadataTypeAttribute only applies to the class specified, partial cannot span across assemblies.
Solution
Essentially, a "reverse" version of ModelMetadataTypeAttribute that is applied on the type doing the augmenting, pointing back to the model type to be augmented:
// defined in ASP.NET MVC project consuming package from Listing 1
[ReverseModelMetadataType(typeof(MyModel))]
public class MyModelMetadata
{
[FromQuery(Name = "foo")]
public int MyProperty { get; set; }
public MyNestedModel NestedModel { get; set; }
}
[ReverseModelMetadataType(typeof(NestedModel))]
[Bind(Prefix = "")]
public class NestedModelMetadata
{
[FromQuery(Name = "bar")]
public bool NestedProperty { get; set; }
}
^ Listing 3
The intention is that at runtime, the model-binding infrastructure will pick up that MyModel.MyProperty should be bound using [FromQuery(Name = "foo")], i.e. from the query string as a variable named foo. Similarly, MyModel.NestedModel.NestedProperty should be simply bound as bar from the query string.
In order to make this work I've implemented a custom ModelMetadataProvider that inherits DefaultModelMetadataProvider and overrides the CreatePropertyDetails method:
// defined in same ASP.NET Core MVC project as Listing 3
public class MyModelMetadataProvider : DefaultModelMetadataProvider
{
protected override DefaultMetadataDetails[] CreatePropertyDetails(ModelMetadataIdentity key)
{
var defaultPropertyDetails = base.CreatePropertyDetails(key);
// check if the key.ModelType should be augmented
// if so, mutate the relevant element(s) of the defaultPropertyDetails array to do the augmentation
return defaultPropertyDetails;
}
}
The mutation part effectively changes the relevant DefaultMetadataDetails.ModelAttributes member to have the additional attributes applied to the properties of the type(s) decorated with ReverseModelMetadataTypeAttribute. I then get ASP.NET MVC Core to use my provider instead of its DefaultModelMetadataProvider via the following in Startup.cs#ConfigureServices:
services.AddSingleton<IModelMetadataProvider, MyModelMetadataProvider>();
I have verified that provider is registered and the mutation code works correctly: MyModelMetadataProvider.CreatePropertyDetails is hit and the defaultPropertyDetails returned do contain the extra attributes applied by my *ModelMetadata classes from Listing 3.
Problem
Model binding does not work: ASP.NET Core MVC essentially behaves as if the attributes added via Listing 3 do not exist, and I don't know why. As far as I'm aware, the results of the calls to the various IModelMetadataProvider methods are cached then used for all subsequent lookup of metadata that's required, including for the purposes of binding models.
I'm hoping that somebody can provide some advice on how to get this to work, before I have to go through the pain of stepping through the ASP.NET Core source myself.

Mapping a User entity into a User DTO in .NET Core

I'm developing a web app that contains a User entity that is derived from .NET Core's IdentityUser. Lets suppose there is another entity called Comment which has a relation to a user (the user who posted the comment):
public class User : IdentityUser
{
public string SomeExtraField { get; set; }
}
public class Comment
{
//Owner (Creator) of the feedback
public User User { get; set; }
//body of the comment
public string Body { get; set; }
}
Now suppose I have an API endpoint that returns all of the comments in the system. If I query for all comments and include the User relation, when the object gets serialized, everything in the User class is serialized and sent to the client (including the users hashed password, etc). Obviously I don't want this. So I've created a CommentService layer that grabs the Comments from a CommentRepository. From my understanding, the service layer should do the job of mapping the raw Comment object into a Comment DTO, which only contains data that should be sent to the client. I've defined a comment and user DTO like this:
public class UserOutput
{
public string Id { get; set; }
public string SomeExtraField { get; set; }
}
public class CommentOutput
{
public UserOutput User { get; set; }
public string Body { get; set; }
}
Then in my service layer I have something like the following:
//Fetch all comments
var list = await _repository.ListAsync();
//Map comments to DTO
var result = list.Select(x => new CommentOutput
{
Body = x.Body,
User = new UserOutput
{
Id = x.User.Id,
SomeExtraField = x.User.SomeExtraField,
}
});
This all seems to work great. However I can foresee one problem. Lets say I have a large system with Comments, Posts, Likes, Private Messages, etc. I can map them all in a similar fashion above. Then one day I decide to add another field to the UserOutput DTO. Now I have to go through potentially hundreds of mapping code like the sample above to map the new field properly, and whats worse is the compiler wont tell me if I've missed anything. I would like to have a function somewhere that maps a User to a UserOutput but I don't know where it should go.
I've seen some suggestions to put a constructor to the DTO that does the mapping:
public class UserOutput
{
public UserOutput(User user)
{
Id = user.Id;
SomeExtraField = user.SomeExtraField
}
public string Id { get; set; }
public string SomeExtraField { get; set; }
}
but I've seen people against this because it tightly couples the DTO with the Entity. I've also seen suggestions of using Auto Mapper but is also seems an equal amount of people are against it.
Where should I place code that can perform these DTO->entity and entity->DTO mappings so I don't repeat myself all over the place?
Try to check out AutoMapper.
This library will help you to map the Entity Class into the ViewModel.
The way to use it is pretty straightforward.

Dynamically change a type with C#

I am very new to C# and ServiceStack and I am working on a small project that consists on calling a third party API and loading the data I get back from the API into a relational database via ServiceStack's ORMLite.
The idea is to have each endpoint of the API have a reusable model that determines how it should be received in the API's response, and how it should be inserted into the database.
So I have something like the following:
[Route("/api/{ApiEndpoint}", "POST")]
public class ApiRequest : IReturn<ApiResponse>
{
public Int32 OrderId { get; set; }
public DateTime PurchaseDate { get; set; }
public String ApiEndpoint { get; set; }
}
public class ApiResponse
{
public Endpoint1[] Data { get; set; }
public String ErrorCode { get; set; }
public Int32 ErrorNumber { get; set; }
public String ErrorDesc { get; set; }
}
public class Endpoint1
{
[AutoIncrement]
public Int32 Id { get; set; }
[CustomField("DATETIME2(7)")]
public String PurchaseDate { get; set; }
[CustomField("NVARCHAR(50)")]
public String Customer { get; set; }
[CustomField("NVARCHAR(20)")]
public String PhoneNumber { get; set; }
public Int32 Amount { get; set; }
}
My first class represents the API's request with its route, the second class represents the API's response. The API's response is the same for all endpoints, but the only thing that varies is the structure of the Data field that comes back from that endpoint. I've defined the structure of one of my endpoints in my Endpoint1 class, and I am using it in my API's response class. As you can see, I am also defining a few attributes on my Endpoint1 class to help the ORM make better decisions later when inserting the data.
Ok, so the issue is that I have about 15 endpoints and I don't want to create 15 ApiResponse classes when I know the only thing that changes is that first Data field in the class.
So I made something like this:
public class DataModels
{
public Type getModel(String endpoint)
{
Dictionary<String, Type> models = new Dictionary<String, Type>();
models.Add("Endpoint1", typeof(Endpoint1));
// models.Add("Endpoint2", typeof(Endpoint2));
// models.Add("Endpoint3", typeof(Endpoint3));
// and so forth...
return models[endpoint];
}
}
I would like for getModel() to be called when the request is made so that I can pass in the ApiEndpoint field in the ApiRequest class and store the type that I want my Data field to have so that I can dynamically change it in my ApiResponse class.
In addition, there is the ORM part where I iterate over every endpoint and create a different table using the model/type of each endpoint. Something like this:
endpoints.ForEach(
(endpoint) =>
{
db.CreateTableIfNotExists<Endpoint1>();
// inserting data, doing other work etc
}
);
But again, I'd like to be able to call getModel() in here and with that define the model of the specific endpoint I am iterating on.
I've attempted calling getModel() on both places but I always get errors back like cannot use variable as a typeand others... so I am definitely doing something wrong.
Feel free to suggest a different approach to getModel(). This is just what I came up with but I might be ignoring a much simpler approach.
When I DID understand you correctly, you have different API-Calls which all return the same object. The only difference is, that the field "Data" can have different types.
Then you can simply change the type of data to object:
public object Data { get; set; }
And later simply cast this to the required object:
var data1=(Endpoint1[]) response.Data;
You're going to have a very tough time trying to dynamically create .NET types dynamically which requires advanced usage of Reflection.Emit. It's self-defeating trying to dynamically create Request DTOs with ServiceStack since the client and metadata services needs the concrete Types to be able to call the Service with a Typed API.
I can't really follow your example but my initial approach would be whether you can use a single Service (i.e. instead of trying to dynamically create multiple of them). Likewise with OrmLite if the Schema of the POCOs is the same, it sounds like you would be able to flatten your DataModel and use a single database table.
AutoQuery is an example of a feature which dynamically creates Service Implementations from just a concrete Request DTO, which is effectively the minimum Type you need.
So whilst it's highly recommended to have explict DTOs for each Service you can use inheritance to reuse the common properties, e.g:
[Route("/api/{ApiEndpoint}/1", "POST")]
public ApiRequest1 : ApiRequestBase<Endpoint1> {}
[Route("/api/{ApiEndpoint}/2", "POST")]
public ApiRequest2 : ApiRequestBase<Endpoint1> {}
public abstract class ApiRequestBase<T> : IReturn<ApiResponse<T>>
{
public int OrderId { get; set; }
public DateTime PurchaseDate { get; set; }
public string ApiEndpoint { get; set; }
}
And your Services can return the same generic Response DTO:
public class ApiResponse<T>
{
public T[] Data { get; set; }
public String ErrorCode { get; set; }
public Int32 ErrorNumber { get; set; }
public String ErrorDesc { get; set; }
}
I can't really understand the purpose of what you're trying to do so the API design is going to need modifications to suit your use-case.
You're going to have similar issues with OrmLite which is a Typed code-first POCO ORM where you're going to run into friction trying to use dynamic types which don't exist at Runtime where you'll likely have an easier time executing Dynamic SQL since it's far easier to generate a string than a .NET Type.
With that said GenericTableExpressions.cs shows an example of changing the Table Name that OrmLite saves a POCO to at runtime:
const string tableName = "Entity1";
using (var db = OpenDbConnection())
{
db.DropAndCreateTable<GenericEntity>(tableName);
db.Insert(tableName, new GenericEntity { Id = 1, ColumnA = "A" });
var rows = db.Select(tableName, db.From<GenericEntity>()
.Where(x => x.ColumnA == "A"));
Assert.That(rows.Count, Is.EqualTo(1));
db.Update(tableName, new GenericEntity { ColumnA = "B" },
where: q => q.ColumnA == "A");
rows = db.Select(tableName, db.From<GenericEntity>()
.Where(x => x.ColumnA == "B"));
Assert.That(rows.Count, Is.EqualTo(1));
}
Which uses these extension methods:
public static class GenericTableExtensions
{
static object ExecWithAlias<T>(string table, Func<object> fn)
{
var modelDef = typeof(T).GetModelMetadata();
lock (modelDef)
{
var hold = modelDef.Alias;
try
{
modelDef.Alias = table;
return fn();
}
finally
{
modelDef.Alias = hold;
}
}
}
public static void DropAndCreateTable<T>(this IDbConnection db, string table)
{
ExecWithAlias<T>(table, () => {
db.DropAndCreateTable<T>();
return null;
});
}
public static long Insert<T>(this IDbConnection db, string table, T obj, bool selectIdentity = false)
{
return (long)ExecWithAlias<T>(table, () => db.Insert(obj, selectIdentity));
}
public static List<T> Select<T>(this IDbConnection db, string table, SqlExpression<T> expression)
{
return (List<T>)ExecWithAlias<T>(table, () => db.Select(expression));
}
public static int Update<T>(this IDbConnection db, string table, T item, Expression<Func<T, bool>> where)
{
return (int)ExecWithAlias<T>(table, () => db.Update(item, where));
}
}
But it's not an approach I'd take personally, if I absolutely needed (and I'm struggling to think of a valid use-case outside of table-based Multitenancy or sharding) to save the same schema in multiple tables I'd just be using inheritance again, e.g:
public class Table1 : TableBase {}
public class Table2 : TableBase {}
public class Table3 : TableBase {}

serialized property to complex type in dto with linq and automapper

I'm having a hard time figuring something out that seems as a "easy" problem.
I'm working with Microsoft Azure mobile apps .Net backend, a MSSQL database, Entity Framework code-first and AutoMapper.
So i have the following objects:
public class Route
{
public string Id { get; set; }
[...] //some other properties
public string SerializedGoogleRoute { get; set; }
}
public class DtoRoute
{
public string Id { get; set; }
[...]
public DtoGoogleRoute GoogleRoute { get; set; }
}
public class DtoGoogleRoute
{
[...] //only strings, ints,...
}
So what I want to do is: In the database save the GoogleRoute as a serialized string because it consists of many properties and I don't need them in different columns - I just want it as a serialized string in one column on the route entity.
When the Route object is projected to the DtoRoute object I want the GoogleRoute to be serialized and vice versa.
Because I'm working with LINQ / queryables I am limited to a few AutoMapper mapping options (see AutoMapper wiki). And with none of these I can't get it to work.
The problems I'm facing/what I tried:
I can't serialize/deserialize the string to the DtoGoogleRoute on mapping (with MapFrom or ConstructProjectionUsing) because LINQ obviously cannot transform the JsonConvert.Serialize/Deserialize methods to SQL statements.
I tried having a DtoGoogleRoute property in the Route object and a string property in the DtoRoute object with getters/setters doing the (de)serialization. This works almost perfectly in a custom API controller but because of the OData query filter the azure mobile app .Net backend uses in the tablecontrollers again only the serialized string property gets returned to the client (because OData/LINQ does not know of the other property).
Another option was making a complex type out of DtoGoogleRoute with Entity Framework - this works fine but not with AutoMapper because AutoMapper can't handle complex types.
For now I'm working with a custom API controller and this works. But it would be better to use the tablecontrollers because they support offline sync.
I can't imagine such a simple thing (at least I thought it was a simple thing) can't be done or is so hard to do. But maybe the problem is all the components (tablecontroller, OData, LINQ, EF, AutoMapper) involved.
I would really be thankful if someone could help.
[EDIT]: I think the fact that it works with a normal api controller and not with a tablecontroller has something to do with OData. I tried putting the same code in a tablecontroller method and in an API controller method. when calling the API controller method I can see on the server that it just calls this function and returns all the right properties to the client (checked with fiddler). But when calling the tablecontroller method the tablecontroller method "rewrites" the URL to a OData URL --> I think this is because of some of the EnableQuery or other OData attributes. Because here (although not AutoMapper but it seems like a similar project from Microsoft) it says that the EnableQuery attribute is called twice - also when the request leaves the server. And I think it cuts of the GoogleRoute property because it does not know about this property in the OData metadata or something like that.
You can achieve it like this -
internal class RouteToDtoConverter : TypeConverter<Route, DtoRoute>
{
protected override DtoRoute ConvertCore(Route source)
{
return new DtoRoute
{
Id = source.Id,
GoogleRoute = JsonConvert.DeserializeObject<DtoGoogleRoute>(source.SerializedGoogleRoute)
};
}
}
internal class DtoToRouteConverter : TypeConverter<DtoRoute, Route>
{
protected override Route ConvertCore(DtoRoute source)
{
return new Route
{
Id = source.Id,
SerializedGoogleRoute = JsonConvert.SerializeObject(source.GoogleRoute)
};
}
}
public class Route
{
public string Id { get; set; }
public string SerializedGoogleRoute { get; set; }
}
public class DtoRoute
{
public string Id { get; set; }
public DtoGoogleRoute GoogleRoute { get; set; }
}
public class DtoGoogleRoute
{
public int MyProperty { get; set; }
public int MyProperty2 { get; set; }
}
AutoMapper.Mapper.CreateMap<Route, DtoRoute>()
.ConvertUsing(new RouteToDtoConverter());
AutoMapper.Mapper.CreateMap<DtoRoute, Route>()
.ConvertUsing(new DtoToRouteConverter());
var res = Mapper.Map<DtoRoute>(new Route
{
Id = "101",
SerializedGoogleRoute = "{'MyProperty':'90','MyProperty2':'09'}"
});
var org = Mapper.Map<Route>(res); //pass

How to get the request object from wcf message?

I have a WCf service with Contracts shown below.
[MessageContract]
public class ServiceRequest
{
[MessageBodyMember]
public int RequestId { get; set; }
[MessageBodyMember]
public OrderDetails OrderDetails { get; set; }
}
[DataContract]
public class OrderDetails
{
[IsLogRequired]
public int OrderId { get; set; }
[IsLogRequired]
public int Quantity { get; set; }
public string CustomerName { get; set; }
}
[IsLogRequired] is custom Attribute.
We need to get all properties in the request which have "[IsLogRequired]" attribute when the request is received. We want to do it as generic solution so that it can be plugged into all services.
We thought of using "MessageInspector" to do this implementing "IDispatchMessageInspector".
How do i get the actual request object from "System.ServiceModel.Channels.Message" parameter of IDispatchMessageInspector.AfterReceiveRequest() method?
Please correct me if i am using a wrong interface or wrong method. Any other solution to this?
I am assuming that "[IsLogRequired] is custom property." means a custom attribute...
Simple answer is that there is no solution to transfer custom attributes that are decorating the data contract as you described it.
Data contracts should be pure and not encumbered by business logic. The know how about the what should be done with various fields belongs to a service implementation.
Possible approach could look like this:
public class OrderService : IOrderService
{
private void ProcessOrder(Order order)
{
var ra = new AuditMetadataResourceAccess();
MethodInfo[] fieldsToLog = ra.GetLoggingFields(typeof(OrderDetal));
if (fieldsToLog.Any())
{
var logger = new LogingEngine();
logger.Log(fieldsToLog, order.OrderDetails);
}
}
}
You could move this implementation inside message inspector or operation invoker. Carlos Figueira has extensive description of each WCF extensibility point.
"How do i get the actual request object from "System.ServiceModel.Channels.Message" parameter of IDispatchMessageInspector.AfterReceiveRequest() method?"
I am assuming you are referring to Web request. WebOperationContext.Current but you need to have ASP.NET Compatibility Mode turned on.

Categories