Using the WebApiContrib ProtoBufFormatter on the Client Side - asp.net-web-api

I'm using the WebApiContrib.Formatting.ProtoBuf NuGet package (http://nuget.org/packages/WebApiContrib.Formatting.ProtoBuf/0.9.5.0) to add support for Protocol Buffers in my Web API project.
The server side seems to work great, but I've been unable to get the Web API Client libraries to deserialize a server response.
System.InvalidOperationException: Type is not expected, and no contract can be inferred: System.Collections.Generic.IEnumerable`1[[WebAPITest1.Protocol.Messages.Product, WebAPITest1.Protocol.Messages, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]
Here is my client code:
var httpClient = new HttpClient();
httpClient.BaseAddress = new Uri(#"http://localhost:60500/");
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/x-protobuf"));
var response = httpClient.GetAsync("/api/products?$orderby=Name").Result;
Assert.IsTrue(response.IsSuccessStatusCode);
var products = response.Content.ReadAsAsync<IEnumerable<Product>>(new[]{new ProtoBufFormatter()}).Result;
Assert.IsTrue(products.Count() > 0);
Ideas?

Have you tried with something other than IEnumerable<Product>? In particular, maybe a top-level object that has a set of Products ? For example:
[ProtoContract]
public class NeedsBetterName {
[ProtoMember(1)]
public List<Product> Products {get;set;}
}
That might be happier. Actually, I added better support for "naked enumerables" earlier this month (in r629) - however, I have not yet deployed this build anywhere.

Related

Xamarin.Form HttpClient for WebAPI failed in iOS

I'm creating Xamarin.Forms for Android and iOS using WebAPI as the web service. The whole thing went well in Android but I hit error in iOS, particularly when doing "JsonConvert.DeserializeObject". Below is the sample code
Model
public class WsObjTest
{
public string name { get; set; }
public string code { get; set; }
public string age { get; set; }
}
WebAPI
[HttpGet]
public WsObjTest HelloWorld()
{
WsObjTest wsObjTtest = new WsObjTest();
wsObjTtest.name = "John Doe";
wsObjTtest.code = "ABC123";
wsObjTtest.age = "18";
return wsObjTtest ;
}
In my Xamarin.Forms, this is how I call and deserialize the response
HttpClient client = new HttpClient();
var response = await client.GetStringAsync(apiURL.Replace("##action##", "HelloWorld"));
return JsonConvert.DeserializeObject<WsObjTest>(response); //ERROR HERE
I got the response as below, but error thrown at the return statement.
"{\"code\":\"ABC123\",\"name\":\"John Doe\",\"age\":\"18\"}"
The error message is below
Unhandled Exception:
System.MemberAccessException: Cannot create an abstract class:
System.Reflection.Emit.DynamicMethod occurred
I think it's because iOS doesn't support JIT compilation or dynamic methods of some sort? May I know if there is anyway to overcome this error? Thanks.
Based on https://learn.microsoft.com/en-us/xamarin/ios/internals/limitations, it is not possible to use any facilities that require code generation at runtime in Xamarin.iOS because code on the iPhone is statically compiled ahead of time instead of being compiled on demand by a JIT compiler.
What I have to do now is read and create the object manually as below. Should anybody got better solution please share with me.
HttpClient client = new HttpClient();
var response = await client.GetStringAsync(apiURL.Replace("##action##", "HelloWorld"));
JObject jObject = (JObject) JsonConvert.DeserializeObject(response);
WsObjTest wsObjTest = new WsObjTest();
wsObjParent.name = jObject["name"].ToString();
wsObjParent.code = jObject["code"].ToString();
wsObjParent.age = jObject["age"].ToString();
return wsObjTest;
Extracted from the link
Since the iOS kernel prevents an
application from generating code dynamically, Xamarin.iOS does not
support any form of dynamic code generation. These include:
The System.Reflection.Emit is not available.
No support for
System.Runtime.Remoting.
No support for creating types dynamically (no
Type.GetType ("MyType`1")), although looking up existing types
(Type.GetType ("System.String") for example, works just fine).
Reverse
callbacks must be registered with the runtime at compile time.
So the System.Reflection.Emit thus the error that I received.

When creating an IServiceManagement for Dynamics 365, why does the authentication endpoint respond with an HTML sign in page?

I have some integration code that intends to use the Organization Service via the CRM SDK.
On one environment, creating an IServiceManagement<IOrganizationService>:
IServiceManagement<IOrganizationService> orgServiceManagement = ServiceConfigurationFactory.CreateManagement<IOrganizationService>(new Uri("dynamics uri")));
and then authenticating with service account credentials:
AuthenticationCredentials authCredentials = new AuthenticationCredentials();
authCredentials.ClientCredentials.UserName.UserName = _config.GetValue<string>("Dynamics:Username");
authCredentials.ClientCredentials.UserName.Password = _config.GetValue<string>("Dynamics:Password");
AuthenticationCredentials tokenCredentials = orgServiceManagement.Authenticate(authCredentials);
works fine.
On another Dynamics environment, the call to GetServiceManagement fails with the following error message:
System.InvalidOperationException
HResult=0x80131509
Message=Metadata contains a reference that cannot be resolved: 'https://login.microsoftonline.com/[guid]/oauth2/authorize?client_id=[some client id]&response_mode=form_post&response_type=code+id_token&scope=openid+profile&state=OpenIdConnect.AuthenticationProperties%[some base-64]RedirectTo%3dhttps%253a%252f%252ftst-success.crm4.dynamics.com%252f&nonce=[some nonce]&redirect_uri=https:%2f%2fcloudredirector.crm4.dynamics.com%2fG%2fAuthRedirect%2fIndex.aspx&max_age=86400'.
Source=System.ServiceModel
StackTrace:
at System.ServiceModel.Description.MetadataExchangeClient.MetadataRetriever.Retrieve(TimeoutHelper timeoutHelper)
at System.ServiceModel.Description.MetadataExchangeClient.ResolveNext(ResolveCallState resolveCallState)
at System.ServiceModel.Description.MetadataExchangeClient.GetMetadata(MetadataRetriever retriever)
at System.ServiceModel.Description.MetadataExchangeClient.GetMetadata(Uri address, MetadataExchangeClientMode mode)
at Microsoft.Xrm.Sdk.Client.ServiceMetadataUtility.RetrieveServiceEndpointMetadata(Type contractType, Uri serviceUri, Boolean checkForSecondary)
at Microsoft.Xrm.Sdk.Client.ServiceConfiguration`1..ctor(Uri serviceUri, Boolean checkForSecondary)
at Microsoft.Xrm.Sdk.Client.ServiceConfigurationFactory.CreateConfiguration[TService](Uri serviceUri, Boolean enableProxyTypes, Assembly assembly)
at Microsoft.Xrm.Sdk.Client.ServiceConfigurationFactory.CreateConfiguration[TService](Uri serviceUri)
at CrmAuthTest.Program.Main(String[] args) in c:\users\t.wolverson\Source\Repos\CrmAuthTest\CrmAuthTest\Program.cs:line 18
Inner Exception 1:
XmlException: CData elements not valid at top level of an XML document. Line 1, position 3.
(I have masked the bits which look identifying or cryptographic)
POSTing to this URL in PostMan yields the HTML for a browser login page, which explains the failure; this isn't what the ServiceConfigurationFactory expects. The scenario is not user-interactive, so this would never make sense, there is no browser and no user able to interact with it.
What do I have to change in Dynamics CRM Online to stop it doing this, and make it just work normally?
Do you instantiate your OrganizationServiceProxy depending on the AuthenticationProviderType right after the lines of code you have posted? Like this
var orgServiceManagement = ServiceConfigurationFactory.CreateManagement<IOrganizationService>(new Uri(ConfigurationManager.AppSettings["CrmUrlService"]));
var authCredentials = new AuthenticationCredentials();
authCredentials.ClientCredentials.UserName.UserName = ConfigurationManager.AppSettings["CrmUserName"];
authCredentials.ClientCredentials.UserName.Password = ConfigurationManager.AppSettings["CrmPassword"];
var tokenCredentials = orgServiceManagement.Authenticate(authCredentials);
IOrganizationService _service;
switch (orgServiceManagement.AuthenticationType)
{
case AuthenticationProviderType.ActiveDirectory:
_service = new OrganizationServiceProxy(orgServiceManagement, tokenCredentials.ClientCredentials);
break;
default:
_service = new OrganizationServiceProxy(orgServiceManagement, tokenCredentials.SecurityTokenResponse);
break;
}
Even if this solves your problem, I recommend that you use CrmServiceClient instead. This class can be found in Microsoft.Xrm.Tooling.Connector dll. It is the go to authentication class when building Windows client applications that connect to Microsoft Dynamics 365. More information on this can be found here
Here is an example on how to initialize CrmServiceClient when connecting to Dynamics 365 online using Office 365:
var myConnectionString = "Url=https://[YourOrganization].crm4.dynamics.com;Username=[YourUser];Password=[YourPassword];AuthType=Office365;";
var crmClient = new CrmServiceClient(myConnectionString);
//Do your stuff
var response = crmClient.Execute(new WhoAmIRequest());
If you need other authentication methods in Dynamics Online check how to build your connection string here.
For on-premises check how to build your connection string here.

FaultException when using ExecuteTransactionRequest (CRM 2015)

I'm doing a bit of a technical investigation into the ExecuteTransactionRequest. It's not something I've ever used before so I knocked up a very quick experiment just to see how it works. However, when sending off the request the OrganizationService is throwing back a FaultException (below). What I believe is happening is that my version of CRM doesn't support that OrganizationRequest. Although I'm pretty sure I have the right assemblies and version.
Can anyone please shed some light on what I'm missing?
CRM Deployment Version: 7.0.1.129
Organization Version: 7.0.2.53
Microsoft.Xrm Assembly Version: 7.0.0.0 (Also happened with 8.0.0.0)
An unhandled exception of type 'System.ServiceModel.FaultException'
occurred in Microsoft.Xrm.Sdk.dll
Additional information: The formatter threw an exception while trying
to deserialize the message: There was an error while trying to
deserialize parameter
http://schemas.microsoft.com/xrm/2011/Contracts/Services:request. The
InnerException message was 'Error in line 1 position 451. Element
'http://schemas.microsoft.com/xrm/2011/Contracts/Services:request'
contains data from a type that maps to the name
'http://schemas.microsoft.com/xrm/2011/Contracts:ExecuteTransactionRequest'.
The deserializer has no knowledge of any type that maps to this name.
Consider changing the implementation of the ResolveName method on your
DataContractResolver to return a non-null value for name
'ExecuteTransactionRequest' and namespace
'http://schemas.microsoft.com/xrm/2011/Contracts'.'. Please see
InnerException for more details.
CrmConnection connection = CrmConnection.Parse(GetCrmConnectionString("unmanaged"));
IOrganizationService orgService = new OrganizationService(connection);
ExecuteTransactionRequest transactionRequest = new ExecuteTransactionRequest()
{
ReturnResponses = true,
Requests = new OrganizationRequestCollection()
};
Entity newContact = new Entity("contact");
newContact["firstname"] = "Stack";
newContact["lastname"] = "Overflow";
CreateRequest createRequest = new CreateRequest()
{
Target = newContact
};
transactionRequest.Requests.Add(createRequest);
ExecuteTransactionResponse transactionResponse = (ExecuteTransactionResponse)orgService.Execute(transactionRequest);
Update
Quick look at your code, looked like it was because of the CreateRequest not being added to the collection. After your comments and double checking the crm organization version, you are on CRM 2015 (not on update 1). ExecuteTransactionRequest is only supported by CRM 2015 update 1 (version 7.1.XXX) and up (version 8.0.XXX) organizations. So unfortunately, your query won't work until at least the 2015 update is applied to the organization.
You did not add your create request to the ExecuteTransactionRequest - Requests collection. An empty request collection is causing the exceptions most likely.
ExecuteTransactionRequest transactionRequest = new ExecuteTransactionRequest()
{
ReturnResponses = true,
Requests = new OrganizationRequestCollection()
};
Entity newContact = new Entity("contact");
newContact["firstname"] = "Stack";
newContact["lastname"] = "Overflow";
CreateRequest createRequest = new CreateRequest()
{
Target = newContact
};
transactionRequest.Requests.Add(createRequest); //missing
ExecuteTransactionResponse transactionResponse = (ExecuteTransactionResponse)orgService.Execute(transactionRequest);

What is the correct way to call patch from an OData client in Web Api 2

Following the OData samples created by the web api team, my controller has the following for supporting Patch:
public HttpResponseMessage Patch([FromODataUri] int key, Delta<Foo> item)
{
var dbVersion = myDb.GetById(key);
if(dbVersion == null)
throw Request.EntityNotFound();
item.Patch(dbVersion);
myDb.Update(dbVersion);
return Request.CreateResponse(HttpStatusCode.NoContent);
}
and using the auto-generated client (derived from DataServiceContext), I submit a patch request like this:
var foo = svcContainer.Foos.Where (f => f.Id == 1).SingleOrDefault();
foo.Description = "Updated Description";
svcContainer.UpdateObject(foo);
svcContainer.SaveChanges(SaveChangesOptions.PatchOnUpdate);
However, tracing the call in fiddler, I see that all other properties of Foo are serialized and sent to the service. Is that the correct behavior? I expected only the Id and Description to be sent over the wire. Also, if I debug the service method and call
GetChangedPropertyNames on item, all its property names are returned.
Should I be creating some sort of Delta instance on the client?
I understand the disconnected nature of the service and thus the service side does not have a context for tracking changes, but it seems to me the api team added support for patch for a reason, so I'd like to know if the client ought to be invoking the update in a different manner.
Update
The link YiDing provided explains how to create a true PATCH request from the client (using the Microsoft.OData.Client.DataServiceContext created by the Microsoft.OData.Client 6.2.0 and above.
For convenience, here is the code snippet:
var svcContainer = new Default.Container(<svcUri>);
var changeTracker = new DataServiceCollection<Foo>(svcContainer.Foos.Where(f => f.Id == 1));
changeTracker[0].Description = "Patched Description";
svcContainer.SaveChanges();
The DataServiceCollection implements property tracking, and using this pattern, only the updated properties are sent to the service.
Without using DataServiceCollection and simply using
svcContainer.UpdateObject(foo);
svcContainer.SaveChanges();
all properties are still sent over the wire despite documentation to the contrary, at least as of Microsoft.OData.Client 6.7.0
The client side property tracking is now supported from Microsoft.OData.Client version 6.2.0. It will detect only the modified properties of an entity and send the update request as PATCH instead of PUT to meet the requirement of your scenario. Please refer to this blog post for more details:
https://devblogs.microsoft.com/odata/tutorial-sample-client-property-tracking-for-patch/

WebApi Odata Windows Store App EndSaveChanges exception

I am trying to create a Windows Store App using a WebApi Odata controller. After some effort I have all the Get requests working, I am now moving onto the CRUD methods, and am getting the following Exception on the EndSaveChanges of the Data Service Context.
<?xml version="1.0" encoding="utf-8"?>
<m:error xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata">
<m:code />
<m:message xml:lang="en-US">No HTTP resource was found that matches the request URI 'http://localhost:56317/odata/ESFClients(guid'f04ad636-f896-4de4-816c-388106cd39ce')'.</m:message>
<m:innererror>
<m:message>No routing convention was found to select an action for the OData path with template '~/entityset/key'.</m:message>
<m:type></m:type>
<m:stacktrace></m:stacktrace>
</m:innererror>
</m:error>
Now I think this is a bug in WebApi from this http://aspnetwebstack.codeplex.com/workitem/822 and its hiding the actual error. To make sure it wasn't my Odata Endpoint I created a quick console app to get an entry, update it and Patch it back, which worked all ok. My WebApi Odata Controller derives from ODataController with
public HttpResponseMessage Patch([FromODataUri] Guid key, Delta<ESFClient> patch)
As the method.
In my windows application I have a extension method on the DataServiceContext for the Save Changes.
public static async Task<DataServiceResponse> SaveChangesAsync(this DataServiceContext context, SaveChangesOptions options)
{
var queryTask = Task.Factory.FromAsync<DataServiceResponse>(context.BeginSaveChanges(options, null, null),
queryAsyncResult =>
{
var results = context.EndSaveChanges(queryAsyncResult);
return results;
});
return await queryTask;
}
And calling the update like so from a blank Windows Store XAML page.
public async Task UpdateWeekNo()
{
var container = new ESFOdataService.Container(new Uri("http://localhost:56317/odata/"));
var clients = (DataServiceQuery<ESFClient>)from p in container.ESFClients where p.UserID == new Guid("f04ad636-f896-4de4-816c-388106cd39ce") select p;
var result = await clients.ExecuteAsync();
var updatedClient = result.Single();
if (updatedClient != null)
{
updatedClient.WeekNo = 19;
container.UpdateObject(updatedClient);
await container.SaveChangesAsync(SaveChangesOptions.PatchOnUpdate); // Use PATCH not MERGE.
}
}
So does anyone come across the same issue, or know how I can find out the actual error. One interesting point is that if I debug the controller while running the Windows App, the patch method does not get called.
Ok, so I have finally solved this. Just a recap for those who could experience the same issue. I have an Odata WebApi controller, Windows 8 Store Application using WCF Client Library, with the reference created from Add Service Reference. When trying to update (patch) a record an exception was being thrown at the EndSaveChanges. This is because for some reason Post Tunneling is enabled by default on my context. Setting this to false allowed everything to work.
Context.UsePostTunneling = false;
Context.IgnoreResourceNotFoundException = true;

Resources