Writing a simple OData client: how to query service? - c#

I am trying to adapt this example to create a simple OData client. Before that, I added a service reference in Visual Studio to "http://services.odata.org/Northwind/Northwind.svc/".
By this step I got many classes like "Alphabetical_list_of_product". But how do I get the alphabetical list of products, for example?
Specifically, in the example the author just starts with:
OdataClient.NorthwindOdataService.NorthwindEntities dc =
new OdataClient.NorthwindOdataService.NorthwindEntities(
new Uri("http://services.odata.org/Northwind/Northwind.svc/"));
But where did he get the OdataClient.NorthwindOdataService.NorthwindEntities class from?
I am new to web services and OData, so apologies if the question is vague.

Here is an example of how the service reference can be used after it has been added to the project:
// Create a service context object
// "NorthwindEntities" is the name of the class in the generated service reference that derives DataServiceContext
// The URI in should be the same URI you used to add the service reference
var context = new NorthwindEntities(new Uri("http://services.odata.org/Northwind/Northwind.svc/"));
// As Alphabetical_list_of_products is an entity set, it can be directly called from the context
// Call Execute() finally to send the request to the OData service and materialize the response got to "products"
var products = context.Alphabetical_list_of_products.Execute();
// Iterate through all the products and print "ProductName", which is the name of a property on "Alphabetical_list_of_product" entity
foreach (var product in products)
{
Console.WriteLine(product.ProductName);
}
As you are new to OData, it is recommended that you start from OData V4. Add Service Reference supports client side proxy generation of OData service up to OData V3. The OData V4 protocol on OASIS Comittee and the blog of the OData team of Microsoft can be referred to for details.

If you want a client for consuming OData service, a good choice should be OData code generator. You can start by reading the tutorial http://blogs.msdn.com/b/odatateam/archive/2014/03/12/how-to-use-odata-client-code-generator-to-generate-client-side-proxy-class.aspx .

Related

BreezeJs + EF + Angular capabilities with DTO's

i'm evaluating Breeze.Js for a large enterprise, data oriented, Angular 5 application in order to take advantage of the following features that are missing in the vanilla Angular framework:
client side data store
client side model state tracking
client side model validation rules
bulk data persistence (SaveChanges() method to persist all entities).
For test purposes i've written the following simple BreezeController in my ASP.NET WebApi + EntityFramework server side:
[EnableCors(origins: "*", headers: "*", methods: "*")]
[BreezeController]
public class PeopleController : ApiController
{
private AdventureWorksDbContext db = new AdventureWorksDbContext();
#region "Breeze"
readonly EFContextProvider<AdventureWorksDbContext> _contextProvider =
new EFContextProvider<AdventureWorksDbContext>();
// ~/breeze/todos/Metadata
[HttpGet]
public string Metadata()
{
return System.Text.Encoding.UTF8.GetString(AdventureWorks.WebApi.Properties.Resources.WebApiMetadata);
}
// ~/breeze/todos/Todos
// ~/breeze/todos/Todos?$filter=IsArchived eq false&$orderby=CreatedAt
[HttpGet]
public IQueryable<PersonDTO> GetPeople()
{
return db.People.ProjectTo<PersonDTO>();
}
// ~/breeze/todos/SaveChanges
[HttpPost]
public SaveResult SaveChanges(Newtonsoft.Json.Linq.JObject saveBundle)
{
return _contextProvider.SaveChanges(saveBundle);
}
#endregion
}
As you can see in my example (it uses AdventureWorks DB) i've done the following modifications:
1) "GetPeople()" endpoint returns a queryable of DTO ("ProjectTo" extension is provided by Automapper). I need to do this in order to shape the model in a usable way for the client, avoid recursions, deep dive in the schema, big fields serialization and so on.
2) "Metadata()" endpoint returns a string resource that represents metadata of the DTO class. I builded it using "PocoMetadata" tool of the "Breeze Tooling Suite" (https://github.com/Breeze/breeze.tooling). This is needed because i can't return the _contextProvider.Metadata() result as long as i'm using DTO's and not EF POCO class.
Now, if in my Angular 5 client i issue an ODATA query like the following i can see that executeQuery() method actually works:
export class BreezeDataStoreComponent implements OnInit {
private _em: EntityManager;
constructor() {
this._em = new EntityManager({
serviceName: 'http://localhost:31328/breeze/People'
});
}
ngOnInit() {
const query = EntityQuery.from('GetPeople')
.where('FirstName', FilterQueryOp.StartsWith, 'F')
.orderBy('LastName', true);
this._em.executeQuery(query).then(res => {
// Here i can get all People instances.
// Now i try to get the first, edit the name and saveChanges.
(res.results[0] as any).FirstName = 'Franklino';
this._em.saveChanges().then(saveResult => {
const test = saveResult.entities;
});
});
}
}
Unfortunately problems comes with SaveChanges().
When the Angular client calls that method, in my server side i get the following error:
System.InvalidOperationException: Sequence contains no matching
element
I think it's due to the fact that i'm calling SaveChanges() over an EF context provider passing a JObject bundle referred to DTO instead of POCO class.
So my question is:
Is it possible to use BreezeJs query and bulk persistence (SaveChanges() method) using DTO's? It's a pretty common need in big data-centric enterprise applications since i think it's a bad practice exposing EF POCOs on WebApi.
should i rely instead over a classic WebApi that respond to the POST\PUT\DELETE HTTP verbs? In that case, how to configure Breeze client in order to contact those endpoints instead of "SaveChanges" when persisting data?
If Breeze is not suitable for this needs are there other technolgies that provides the 4 abovementioned points?
Thank you very much.
To make SaveChanges work with your DTOs, you would need to either
Write your own method to unpack the JObject saveBundle, or
Use the BeforeSaveChanges method to modify the dictionary of DTOs and replace them with entities that EF understands.
Number 2 seems like the better choice. If you do not have a 1:1 match between entities and DTOs, some logic would be required when doing the mapping.

Convert a WPF app from using EntityFramework to using WCF RESTful services

I've followed these two walkthroughs:
WPF: EntityFramework MVVM Walk Through 1 Sample
Walkthrough: Binding WPF Controls to a WCF Data Service
I'm now trying to implement RESTful services in the first walkthrough code. My attempt is at this BitBucket repository (bitbucket.org/Sryn/wpf_entityframework_wt01).
I'm stuck at the stage where I have to change the existing Linq style dB access code to using REST services, I think. I thought it was a simple process of just changing the 'source origin' of the dB, but my understanding is that the Linq statements seem to be aware of where the data is coming from and I don't think it works with REST services. However, I just came upon this article (msdn.microsoft.com/en-us/library/dd203052.aspx), which I just skimmed to a particular section, entitled 'Consuming ADO.NET Data Services'. In there it has code that looks like this:
BookmarkService bookmarkService = new BookmarkService(
new Uri("http://localhost:55555/Bookmarks.svc/"));
// this generates the following URL:
// http://localhost:55555/Bookmarks.svc/Bookmarks()?$filter=substringof('WCF',Tags)&
// $orderby=LastModified
var bookmarks = from b in bookmarkService.Bookmarks
where b.Tags.Contains("WCF")
orderby b.LastModified
select b;
foreach (Bookmark bm in bookmarks)
Console.WriteLine("{0}\r\n{1}", bm.Title, bm.Url);
.. which has both the REST service Uri and Linq statements. However, I don't know how to test/implement it.

Programmatically Generate Asp Net Web Api OData Route Metadata

Is there are a way to programmatically generate the /$metadata response returned from an ASP.Net Web Api OData controller route in a way that can be serialized to XML?
The reason I want to do this is that I'm using breeze to access the Web API using the OData adapter and would like to pre-load the Breeze MetadataStore with the metadata, like in this http://breeze.github.io/doc-js/metadata-load-from-script.htmlexample.
But this example does not seem to work with the OData adapter as it uses different metadata.
If I understand your question, you are trying to simulate GET /$metadata on the server so you can store the results in a file. In ASP.NET OData, $metadata is represented by an object that implements IEdmModel (e.g., the result of calling ODataModelBuilder.GetEdmModel). The problem then becomes how to serialize that model to XML.
The following helper will write service metadata to the given stream. For the model and config parameters, you should pass the same objects you used for your service configuration.
public class MetadataHelper
{
public static Task WriteMetadataAsync(Stream stream, IEdmModel model, HttpConfiguration config, string odataRouteName)
{
var request = new HttpRequestMessage(HttpMethod.Get, "/$metadata");
request.ODataProperties().Model = model;
request.ODataProperties().RouteName = odataRouteName;
request.SetConfiguration(config);
var payloadKinds = new List<ODataPayloadKind> { ODataPayloadKind.MetadataDocument };
var xmlMediaType = new MediaTypeHeaderValue("application/xml");
var formatter = new ODataMediaTypeFormatter(payloadKinds).GetPerRequestFormatterInstance(model.GetType(), request, xmlMediaType);
var content = new StringContent(String.Empty);
content.Headers.ContentType = xmlMediaType;
return formatter.WriteToStreamAsync(model.GetType(), model, stream, content, null);
}
}
True, the OData metadata is handled correctly by Breeze only when reading an OData response; the MetadataStore doesn't import/export it directly.
I think the easiest way to handle this is to create a separate bit of client-side code that will
Create an EntityManager that will
Fetch the metadata from the OData server
Export the metadata from the MetadataStore
Log the metadata so you can capture it and store it in a file
Not elegant, but it gets the job done.
Some future version of breeze.server.net will do the OData-to-Breeze metadata conversion on the server, so we won't have this problem.

Using ASMX Web Service Entities in WCF Service

We have a good old .asmx web service (let's call it "Message" Web Service) which we have to preserve for backward compatibility.
The .asmx service exposes this method:
[WebMethod(Description = "Do Something")]
public int DoSomething(Entity1 e)
{
...
}
This web service uses some entities referenced from a DLL, for example:
namespace Software.Project.Entities
{
[DataContract]
public class Entity1
{
[DataMember]
public string property1{ get; set; }
// Lots of other properties...
}
}
This DLL is also used by a brand-new WCF service. Now, I have to call the old .asmx method from WCF. To do so, in the WCF project, I added a reference to the .asmx project, using the "Add service reference" wizard (Advanced - Add Web Reference).
Now, great! It is possible for me to call the DoSomething method from WCF, this way:
Entity1 e1 = new Entity1();
Software.Project.WCFService.ServiceReferenceName.Message m = new Software.Project.WCFService.ServiceReferenceName.Message();
m.Url = ConfigurationManager.AppSettings["MessageWebServiceURL"];
int r = m.DoSomething(e1);
Unfortunately, doing so won't work: I get a compiler error like if Entity1 in WCF is not good as argument for method DoSomething. What I have to do is:
Entity1 e2 = new Software.Project.WCFService.ServiceReferenceName.Entity1();
Software.Project.WCFService.ServiceReferenceName.Message m = new Software.Project.WCFService.ServiceReferenceName.Message();
m.Url = ConfigurationManager.AppSettings["MessageWebServiceURL"];
int r = m.DoSomething(e2);
By doing so, the compiler accepts the call; the problem is that Entity1 in my WCF service is full of fields and data and I would have to copy all the data to the new entity.
I also tried adding the reference to the .asmx as a Service Reference, and flagging "Reuse types in reference assembly", but the result was exactly the same.
I can't believe that there isn't a way to make it understand that Entity1 is exactly the same entity! Is that really impossible?
I am sorry, but i think I have bad news.
You can try to use xml serialization instead of data contract serialization, since asmx does not know about it.
Also, this post say this possible but not so easy: .NET 3.5 ASMX Web Service - Invoked via .NET 3.5 Service Reference - Common Class Reuse
Probably you'll find easier to add your translator class.

GET operation difference in normal web service and REST web service

I have a doubt on GET operation of normal and REST web services. I understand REST services are based on the HTTP VERBS. So, for a entity, if there are couple of GET methods, how would it differentiate.
Below is the example of basic service
public class CustomerService
{
public List<Customer> GetCustomers()
{
//returns all customers
}
public List<Customer> GetCustomersWhoHaveOrdersAndOtherFilterCriteria(int orderid,string name)
{
//returns filtered customers
}
}
If it is normal web service, it can be called via CustomerService/GetCustomers or CustomerService/GetCustomersWhoHaveOrdersAndOtherFilterCriteria/23 but how about REST web service, I assume there should be one GET operation.
1) In "normal" web service - if you mean SOAP you are never using GET - all requests are wrapped in POST
2) REST Url shall contain reference to resource - e.g. Customer not to operation so the result url could be CustomerService/Customers for http method GET
3) For orderid and name parameters there are more options:
CustomerService/Customers/orderid/123/name/MyName001
CustomerService/Customers?orderid=123&name=MyName001
and more
You are right as you said that Rest services are based on HTTP verbs. But there is one more thing that is Rest services are basically Resource based and Resources are nothing but the Url.
So what you can do is you can create different uri template for accessing different services
CustomerApi/Customer -- HTTP GET -- GET All Customers
CustomerApi/Customer/FilterID -- HTTP GET -- Get filter customer

Categories