Add OData to Web API 2 without coupling clients and server - c#

I want to add OData syntax for querying my application's data. I don't want to fully implement ODataController, but have ApiController and implement one GET action that will support OData queries, as described here: Supporting OData Query Options in ASP.NET Web API 2
Example of what I want to have:
public class LetterEntity
{
public int Id {get; set;}
public string Title {get; set;}
public string Content {get; set;}
public string Source {get; set;}
public DateTime SendingTime {get; set;}
public string AnotherWierdString {get; set;
...
}
public class LetterDTO
{
public int Id {get; set;}
public string Title {get; set;}
public string LetterContent {get; set;}
public string Source {get; set;}
public DateTime SendingTime {get; set;}
}
public class LetterInsideFolderDTO
{
public string Title {get; set;}
public string Source {get; set;}
}
public class LettersController : ApiController
{
// Is there a way to do something like the following?:
[HttpGet]
[Route("api/letters")]
[EnableQuery]
public IQueryable<LetterInsideFolderDTO> Get(ODataQueryOptions<LetterDTO> query)
{
IQueryable<Letter> letters = db.Letters;
var queryOnEntites = // Convert the query to work on the entities somehow? - This is where I need help!!
var afterQuery = query.ApplyTo(letters)
IQueryable<LetterInsideFolderDTO> dtos = afterQuery.ProjectTo<LetterInsideFolderDTO>(afterQuery)
return dtos;
}
}
Because of the fact that at the moment I take Entity model directly in the clients query, there is a strong coupling between clients and server.
For example if i want to query and get all the letters that has "abc" inside the Content field, I need to route to the following:
api/letters/?$filter=contains(Content,'abc')
If tomorrow I decide to change that property from "Content" to "LetterContent" all clients code will be broken.
How can I surpass it?

I have not tried it. This and this can help you to map the query url to actual controller by using router map and controller selector where you can map LetterDto to LetterEntity

This will work for Entity Framework (not sure for Nhibrenate but probably will) and will do a real SQL query not in memory filtering.
var queryOnEntites = db.Letters.Select(l=>new LetterDTO{Id =l.Id ... });
var afterQuery = query.ApplyTo(queryOnEntites);
But you shouldn't use DTOs with odata if you want to make some properties private to the API use builder.EntitySet<T1>("TEndpoint").EntityType.Ignore(o => o.SomeProp);
Now if you don't want the whole LetterEntity sent to client you have the $select=Id,... option for this purpose.
If you don't setup
config.MapODataServiceRoute(
routeName: "ODataRoute",
routePrefix: null,
model: builder.GetEdmModel());
you don't have an Odata endpoint and $metadata won't be available.
I haven't tested it but this way I don't think the client libraries(.net,java,js...) would work and you will have to make raw ajax requests to get the data.
ODataQueryOptions<> and translate it to
ODataQueryOptions<>
You can't do this. ProjectTo(AutoMapper) function dose the same thing as .Select(l=>new LetterDTO{Id =l.Id ... }); but you will have problems with this. This all is dependent on your back end since IQueryable is not the same as IEnumerable.
http://blog.ploeh.dk/2012/03/26/IQueryableTisTightCoupling/
This all depends on what level of LINQ your back end provides (NHibrenate tends to be worse then EF, and if you are using Mongo, Elastic, Cassandra... who knows what might go wrong when you use AutoMapper)
If tomorrow I decide to change that property from "Content" to
"LetterContent" all clients code will be broken.
You can setup Name property on an Entity set with oData. Remember Odata is data access level not BL. You should treat it the same way as changing SQL column name in the database.

Instead of exposing your entity models directly, create ViewModels corresponding to each entity model. They are nothing but simple classes having same or only required properties which we want to be exposed to the outer world. Having implemented this way, your entity model properties may change but ViewModels can remain unchanged. You are also addressing the security aspect of not exposing every property of your entity to end clients.
Mapping between entity model and ViewModel has to be done by yourself or by an object-object mapper like AutoMapper.

Related

Autosetting foreign key Ids for child objects in WebApi Rest actions bound for EFCore

I'm a little bit new to Dotnet core WebApi and Entity Framework so forgive me if I'm missing something super obvious.
I have web API PUT methods to update a model on its own, or as a child of a parent - by submitting the full model of the parent with a child.
The problem I've run into is that I have marked the foreign key ID as Required using DataAnnotations [Required] plus [Range] because, without it, a child level PUT would be worthless. But I would like to accept it without the parent ID specified if it's attached to the parent in the JSON.
For example:
For class
public class ParentData
{
public id id {get; set;}
public string parentProperty {get; set;}
public ChildData childData {get; set;}
}
public class ChildData
{
public int childId {get; set;}
[Required]
public int parentId {get; set;}
public string anotherAttribute{get; set;}
[JsonIgnore]
public ParentData parentData {get; set;}
}
This might be fine:
{
"childId":123,
"parentId":234,
"anotherAttribute":"Bar"
}
But this will fail because parentId is missing:
{
"id":234,
"parentProperty":"Foo",
"childData":
{
"childId":"123",
"anotherAttribute":"Bar"
}
}
Given that I have a fluent EF Map (entityTypeBuilder().HasOne.WithOne().HasForeignKey()) is there a way I can just have the parent ID set automatically allowing the model to be considered valid?
I was thinking I could do something inside the SET operation for the ChildData object which probably comes before the model validation in the pipeline, but that seems overwrought compared to EF Mapping and Data Annotation.
Am I doomed to either set the properties manually or build a custom validator and not use data annotations here?
Option 1
Maybe you should separate API Models and EF Models.
In API you can remove Id property for Create operations or you can make it optional. Also for Update operations, you can make it Required
So in your example you'll need several more models.
ParentData => implements CreateParentData and Id is required
CreateParentData => Id is optional
ChildData => implements CreateChildData and Id is required
CreateChildData => Id is optional
You can use AutoMapper to map to your EF Entities.
Option 2
Implementing IValidatableObject interface in your models is an option. You can type your own validator.
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var results = new List<ValidationResult>();
// Do validation here, if there are errors add to the results
return results;
}

Populate navigation properties automatically?

I've this -simplified- model class:
public class Transaction
{
public string Id { get; set; }
public virtual UserResource Resource { get; set; }
}
And an Mvc Web Api Controller:
// POST api/values
[HttpPost]
public void Post([FromBody]Transaction transaction)
{
}
And my request body is like this:
{"Id":"5520AEB9-DAD8-4C85-80A7-3257931B9790","ResourceId":"118547FC-0B3A-4816-820C-93BF2BA1BF14"}
In the post method, transaction.Resource is null. And I don't know how to get it to be populated using the Resource Id I passed...
What I tried so far:
Add public string ResourceId {get; set;} and mark it as a ForeignKey in Transaction... -> no change
In the request change ResourceId param to "Resource":{Id:".."} -> transaction.Resource is not null any more but it hasn't queried Resource table to fill in anything...
Mark Resource as virtual or not (Lazy loading or Eager) -> No change
Ideas?
You are not passing the Resource object to your API, you must either pass it in or have the API figure out what it is, I don't suggest the latter.
So as it's Entity Framework 7, you can't use lazy loading at this point in time as it's not (yet) in the framework.
The current roadmap lists it as a high priority and they state:
These features are high priority but we think EF7 would be a compelling release for the vast majority of applications without them.
So instead, in your query you need to manually include all of your navigation properties, for example:
var data = context.Transactions
.Include(t => t.Resource)
.Where(t => t.Id == 1);

MVC ViewModel EntityFrameWork ICollection,IEnumerable virtual Class

I have gone through a lot of documents and found several ways of trying to make a CreateViewModel.
I want to know what does ICollection Does.
public Payment{
public int ID {get; set;}
public string Details {get; set;}
public virtual ICollection<Expense> Expenses {get; set;}
}
public Expense{
public int ID {get; set;}
public string MyDetails {get; set;}
}
So I guess in my View, I can Use just the plain class as virtual to make a Create Method. But if I want to use ViewModel to make a view with 2 or more DataModels for Creation. How will I go with that. Cause I guess I can always just make a
public virtual Payment payments {get; set;}
public virtual Expense expenses {get; set;}
But I am trying to ready this for Dynamically Having a Add Button Generating an Expense Details Input.
Not to mention, the IEnumerable as well, but I guess this needs an ID more suitable for Editing and Details for what I understand.
All you need is a PaymentViewModel class, where you'll use List<Expense> instead of ICollection<Expense>:
public class PaymentViewModel
{
// ID property unnecessary here because it doesn't need
// to be posted from the form
public string Details { get; set; }
// You may want to use something like `List<ExpenseViewModel>`
// based on your needs
public List<Expense> Expenses { get; set; }
}
With that, you add additional expense records to your form by making sure the input names are in the format of Expenses[N].MyDetails, where N is the index. Whatever JavaScript solution you use to add additional expense records to the form should create these inputs with properly indexed names. This would be a good place to use a JavaScript templating solution, or something that handles data-binding like Knockout.
For editing existing expenses, should you have the need, you just generate the fields like with any collection, but you need to use a for loop rather than the more traditional foreach:
#for (var i = 0; i < Model.Expenses.Count(); i++)
{
#Html.EditorFor(m => m.Expenses[i].MyDetails)
}
As a side note, since you asked, ICollection<T> is the required type for entity navigation properties with Entity Framework. These reasons are very low level, but has to do with the way Entity Framework handles one-to-many and many-to-many relationships at an object level and issues such as lazy loading. However, ICollection<T> doesn't work for actually submitting items through an HTML form, due mostly to the fact that it's not indexable. That's why a view model is so important in this scenario.

How do I translate complex objects in ServiceStack?

Suppose I have two objects:
class Order
{
string Name {get; set;}
Customer Customer {get; set;}
Item[] Items {get; set;}
}
and
class OrderDTO
{
string Name {get; set;}
CustomerDTO Customer {get; set;}
ItemDTO[] Items {get; set;}
}
If I receive an object orderDTO that is fully populated and do orderDTO.TranslateTo<Order>() the result will only have Name populated, not Customer or Items. Is there a way to do a recursive translation or the only option is to translate Customer and each of the Items manually?
I would wrap this in a re-usable extension method, e.g:
public static OrderDTO ToDto(this Order from)
{
return new OrderDTO {
Name = from.Name,
Customer = from.ConvertTo<CustomerDTO>(),
Items = from.Items.Map(x => x.ConvertTo<ItemDTO>()).ToArray(),
}
}
Which can then be called whenever you need to map to an Order DTO, e.g:
return order.ToDto();
Some more examples of ServiceStack's Auto Mapping is available on the wiki.
ServiceStack's party-line is if your mapping requires more than the default conventional behavior that's inferable from an automated mapper then you should wrap it behind a DRY extension method so the extensions and customizations required by your mapping are cleanly expressed and easily maintained in code. This is recommended for many things in ServiceStack, e.g. maintain un-conventional and complex IOC binding in code rather than relying on an obscure heavy-weight IOC feature.
If you prefer it, you can of course adopt a 3rd party tool like AutoMapper.
You are going to have to handle complex mapping explicitly yourself. Here are some unit tests from ServiceStack src that show how complex types are currently handled.
You can see that the the Car object is serialized into JSON.
var user = new User() {
FirstName = "Demis",
LastName = "Bellot",
Car = new Car() { Name = "BMW X6", Age = 3 }
};
var userDto = user.TranslateTo<UserDto>();
Assert.That(userDto.FirstName, Is.EqualTo(user.FirstName));
Assert.That(userDto.LastName, Is.EqualTo(user.LastName));
Assert.That(userDto.Car, Is.EqualTo("{Name:BMW X6,Age:3}"));
I agree with Trust Me - I'm a Doctor that Automapper is worth using. The built in Translating was designed to reduce dependencies.

Issues with Dynamic Search Expressions in EF

I currently am using a data structures similar to the following:
public class Individual
{
//Other properties omitted for brevity sake
public List<IndividualName> IndividualNames {get; set;}
}
and
public class IndividualName
{
public string FamilyName {get; set;}
public string GivenName {get; set;}
public string MiddleName {get; set;}
}
I am attempting to use some Dynamic search expressions to pass from my presentation layer to repository level (to actually apply the search).
However, I have run into some issues due to the fact that an Individual can have 1-M Individual Names, and I am trying to use LINQ to grab all of an Individual's IndividualNames so they can be queried.
For example's sake - this is what the expression currently looks like:
searchExpressions.Add(new SearchExpression("Individual
.IndividualNames
.Select(GivenName)
.FirstOrDefault()"
, ComparisonOperator.Contains, "Test");
This will currently only determine if the GivenName in the first IndividualName instance Contains "Test". The above works as it should - however I am a bit stuck in terms of how I would be able to determine if Any of the IndividualNames contained the string.
Any help would be appreciated - as I have tried several things without any luck.
I think you would be looking for....
searchExpressions.Add(new SearchExpression("Individual
.IndividualNames
.Select(GivenName)",
ComparisonOperator.Contains, "Test");
You will also need to add a Contains Aggregate Method to the Dynamic Linq library. How to do this can be found here.
http://blog.walteralmeida.com/2010/05/advanced-linq-dynamic-linq-library-add-support-for-contains-extension-.html
im not sure this is aplicable in your case, but maybe this lambda?
Individual.IndividualNames.Where(x => x.GivenName == "Test")

Categories

Resources