I have started using RestSharp to call an webapi proejct as it seems pretty easy to use.
I am wanting to build a helper class for all of my crud actions.
I have this so far for a simple PUT request.
public static IRestResponse Update(object objectToUpdate,string apiEndPoint)
{
var client = new RestClient(CreateBaseUrl(null))
{
Authenticator = new HttpBasicAuthenticator("user", "Password1")
};
var request = new RestRequest(apiEndPoint, Method.PUT);
request.AddObject(objectToUpdate);
var response = client.Execute<MyViewModel>(request);
//var response = client.ExecuteDynamic(request);
return response;
}
So the above code works however I have had to hardcode my viewmodel into it
var response = client.Execute<MyViewModel>(request);
How can I change this so I dont need to know the type of model I am expecting?
I tried using var response = client.ExecuteDynamic(request);
however this throws an exception of
Unable to cast object of type 'RestSharp.RestResponse' to type 'RestSharp.RestResponse`1[System.Object
Im not sure how I am meant to cast my object correctly
I'm not familiar with RestSharp. However, it sounds like generics could help you here. Either your class or method needs to accept a type. For example, the signature of your method would change to
public static IRestResponse Update<T>(object objectToUpdate,string apiEndPoint)
This would allow you to call the method as:
Update<MyViewModel>(objectToUpdate, apiEndPoint);
Your implementation would change from your concrete type to:
var response = client.Execute<T>(request);
Overall you could modify your code to something like this:
public static IRestResponse Update<T>(object objectToUpdate,string apiEndPoint)
{
var client = new RestClient(CreateBaseUrl(null))
{
Authenticator = new HttpBasicAuthenticator("user", "Password1")
};
var request = new RestRequest(apiEndPoint, Method.PUT);
request.AddObject(objectToUpdate);
var response = client.Execute<T>(request);
//var response = client.ExecuteDynamic(request);
return response;
}
Documentation on C# Generics can be found here: http://msdn.microsoft.com/en-us/library/ms379564(v=vs.80).aspx
Related
I have created a custom action that takes in an input parameter called paramsInput (string).
I want to send to said action a stringified JSON object so that it may be deserialized inside the action.
Snippet of action code:
public void Execute(IServiceProvider serviceProvider)
{
try
{
_context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
IOrganizationServiceFactory factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
_crmService = factory.CreateOrganizationService(_context.UserId);
string paramsInput = _context.InputParameters.Contains("paramsInput") ? _context.InputParameters["paramsInput"].ToString() : string.Empty;
if (paramsInput != string.Empty)
{
// Deserialize to concrete class
InputParams inputParameters = JsonConvert.DeserializeObject<InputParams>(paramsInput);
// logic here...
}
}
catch (Exception ex)
{
// handle exception...
}
}
I have also created a generic function that receives the action's name and the parameters to send and calls the action via OData:
public bool CallMessage(string action, JObject parameters, out string reason)
{
RegenerateAccess(); // Microsoft Online
string urlAPI = "/api/data/v9.1/" + action;
using (HttpClient client = new HttpClient())
{
client.BaseAddress = new Uri(_serviceUrl);
client.Timeout = new TimeSpan(0, 2, 0); //2 minutes
client.DefaultRequestHeaders.Add("OData-MaxVersion", "4.0");
client.DefaultRequestHeaders.Add("OData-Version", "4.0");
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, urlAPI);
/*StringBuilder sb = new StringBuilder();
using (StringWriter sw = new StringWriter(sb))
using (JsonTextWriter writer = new JsonTextWriter(sw))
{
writer.QuoteChar = '\'';
JsonSerializer ser = new JsonSerializer();
ser.Serialize(writer, parameters);
}*/
var jsonObj = JsonConvert.SerializeObject(parameters);
//request.Content = new StringContent(sb.ToString(), Encoding.UTF8, "application/json");
request.Content = new StringContent(jsonObj, Encoding.UTF8, "application/json");
request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json");
//Set the access token
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _authResult.AccessToken);
HttpResponseMessage response = client.SendAsync(request).Result;
reason = response.Content.ReadAsStringAsync().Result;
return response.IsSuccessStatusCode;
}
}
My issue is that I'm getting a Bad Request 400 back, and the response reads - Microsoft.OData.ODataException: An unexpected 'StartObject' node was found for property named 'paramsInput' when reading from the JSON reader. A 'PrimitiveValue' node was expected.
My parameters JObject is structured as follows:
{
"paramsInput" : {
"id" : "xxxx-xxxx...", // some guid
"code" : 1 // int
}
}
or via code:
Guid guid = Guid.NewGuid();
JObject parameters = new JObject();
parameters.Add("id", guid);
parameters.Add("code", 1);
JObject wrapper = new JObject();
wrapper.Add("paramsInput", parameters);
// send wrapper JObject to CallMessage
I don't want to change the structure of CallMessage function as it is a generic function that may be used multiple times by other sources.
The action's parameter paramsInput (containing the JSON) is a string parameter. Therefore, when this parameter is constructed, it must be stringified and your last code snippet would need to look like this:
Guid guid = Guid.NewGuid();
JObject parameters = new JObject();
parameters.Add("id", guid);
parameters.Add("code", 1);
JObject wrapper = new JObject();
wrapper.Add("paramsInput", JsonConvert.SerializeObject(parameters));
// send wrapper JObject to CallMessage
A few side notes:
you decided to create your code executing Dataverse actions from scratch using HttpClient, which in itself is fine, but doing so you need to do all plumbing yourself. Another option would be to use the OrganizationRequest from the MS SDK libraries.
The Action is a bit of legacy nowadays and has been replaced by the Custom API. A Custom API pretty much works in the same way and as a bonus you do not need to create custom plugin steps.
Henk's answer is correct, You told the API to accept a string, thus you must send it the serialized version of your JSON in the string parameter.
I cannot add a comment to Henk's answer due to length restrictions, but I wanted to add a point of clarification here:
The Action is a bit of legacy nowadays and has been replaced by the Custom API. A Custom API pretty much works in the same way and as a bonus you do not need to create custom plugin steps.
You don't need to create a plugin supporting it if you're going to process it somewhere else ( Like PowerAutomate). This is technically called an "Event" in the Power Platform. You also don't need to create a plugin to support a custom action.
The fundamental difference is that "Custom Action" was implemented as a Workflow Operation, where "CustomAPI" is not. You gain a bit of perf as the system does not need to invoke the workflow runtime to run your operation.
Originally, 'Custom Action' was intended to be used as a custom workflow implementation (Think the way you use an HTTP trigger for PowerAutomate today.) However, folks quickly realized that you could wire up a Plugin to the SDK message to get it to act like a 'custom api' inside Dataverse.
Carrying that forward, the CustomAPI Definition is used for both Events and Custom Operations.
As Henk Said, 'Custom Action' is the older process for creating a callable interface for this.
I'm building a class library that connects to a client's API, and collects the response from a given endpoint into a class. In other words, I'll hit the client's Donuts endpoint, and using JsonConvert to deserialize the response into my Donuts class, which has fields that match the attributes in the JSON object. This is all well and good, but they have a lot of endpoints, and I have many methods which repeat the same code over and over. In the interests of DRYness and separation of concerns, I'd like to have a separate namespace that makes the rest call, no matter the endpoint, and returns the correct result to the main class. Here's an example of what it does now (I'm using RestSharp to configure the request and response):
public List<T> Index<T>(string Endpoint)
{
List<T> Results = new List<T>();
RestClient Client = new RestClient();
RestRequest Req = ConfigureGetRequest(Endpoint);
IRestResponse Resp = Client.Execute(Req);
if (Resp.StatusCode == System.Net.HttpStatusCode.OK)
{
Results = JsonConvert.DeserializeObject<List<T>>(Resp.Content, DeserializationSettings);
}
return Results;
}
}
There's other code in the if statement (it's actually an if-else for error handling), but it's not really relevant to this question. I have this same code in multiple methods, with the only difference being that it returns List<Bagel>, or List<Cruller>, etc... What I'd like to do is extract this code into it's own method, but I'm not sure how. Right now, I'm trying to deserialize into List<object>, and then convert it to List<Donut> later, but that doesn't seem to be something I can do. My other thought is to pass in the endpoint name as a parameter (public List<Donut> GetDonuts(string Endpoint)), and then somehow use that to determine what type of object to deserialize into, if that makes sense? Anyhoo, if anyone has any insight on a good way to do this, it would be appreciated!
EDIT
Awesome responses, thank you folks. I've refactored the method according to Matt Dillard's answer below. The next step is to convert the List to List. The method that calls the method above looks like this:
public List<Donut> GetDonuts()
{
List<Donut> Results = new List<Donut>();
MODULE MakeCall = new GET();
return Results = MakeCall.Index("donuts")();
}
My Intellisense tells me that "The type arguments for method 'MODULE.Index(string)' cannot be inferred from the usage. Try specifying the type arguments explicitly". I tried casting like so: (List)MakeCall.Index("donuts")();
but that didn't work out. Is there a way to cast this so that I can have methods GetDonuts, GetCrullers, etc... that all call Index effectively?
Generics:
public List<T> GetStuff<T>(string endpoint)
{
List<T> Results = new List<T>();
RestClient Client = new RestClient();
RestRequest Req = ConfigureGetRequest(endpoint);
IRestResponse Resp = Client.Execute(Req);
if (Resp.StatusCode == System.Net.HttpStatusCode.OK)
{
Results = JsonConvert.DeserializeObject<List<T>>(Resp.Content, DeserializationSettings);
}
return Results;
}
}
Bascially replace all references to Donut with T.
Presumably the endpoint address would change, so you can add that as a parameter.
Call something like this:
var donuts = GetStuff<Donut>("http://dunkin.com/api/allthedonuts");
You probably would like to have it as generic method which accepts a type T and return a list of such like
public List<T> GetDonuts<T>() where T : class, new()
{
I figured out what needed to happen. I can't deserialize a generic list, but I can deserialize a generic, and place it in a list. The final code looks like this:
public List<Donut> GetDonuts<T>()
{
GET MakeCall = new GET();
List<Donut> Results = new List<Donut>();
Results = MakeCall.Index<Donut>();
return Results;
}
public List<T> Index<T>()
{
T Result;
List<T> ResultList = new List<T>();
RestClient Client = new RestClient();
RestRequest Req = ConfigureGetRequest(Endpoint);
IRestResponse Resp = Client.Execute(Req);
if (Resp.StatusCode == System.Net.HttpStatusCode.OK)
{
Result = JsonConvert.DeserializeObject<T>(Resp.Content, DeserializationSettings);
ResultList.Add(Result);
}
return ResultList;
}
This allows me to use GetDonuts, GetCrullers, etc... to be lean and DRY. Thanks for all the help!
I am making of System.Net.Http.HttpClient class to call an end point. This endpoint is expect certain input and returns a List of user defined object of type Employee ( List<Employee>).
This is the code that I am using.
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:7792/");
client.DefaultRequestHeaders.Accept.Clear();
FilterModel payload = new FilterModel();
payload.employeeId= 97050001;
payload.Level= "Manager";
// New code:
HttpResponseMessage response = client.PostAsJsonAsync("api/employee", payload).Result;
if (response.IsSuccessStatusCode)
{
var employee= response.Content.ReadAsStringAsync();
//HOW DO I CONVERT THE OUTPUT INTO LIST<EMPLOYEE>?
Console.Write("---DONE---");
}
Console.ReadKey();
}
I know this is not the ideal way to call an end point & I must use asyc await. I just need the data, the call can be sync or async & I want to type cast the result into List.
Currently I get a string back which I need to deserialize. Please help
You can use JsonConvert as below:
var jsonString= response.Content.ReadAsStringAsync();
var employees = JsonConvert.DeserializeObject<List<Employee>>(jsonString);
Hope this help!
UPDATE 2020:
Use some thing like this:
return await response.Content.ReadFromJsonAsync<List<Employee>>();
I am using HttpClient PCL Class library. But When First Time I Get the JSON result It return correct data. After that HttpClient returns the same JSON result again and again for one URL till I close the application and start it again. My code looks like that
public class HttpService : IHttpService
{
public async Task<TResponseType> GetAsync<TResponseType>(string method, string parameters = null) where TResponseType : class
{
var uri = new Uri(string.Format(Constants.ServerUrl + method + parameters));
using (var client=new HttpClient())
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic",
"c2Rzb2w6c2Rzb2w5OQ==");
var response = await client.GetAsync(uri);
var result = response.Content.ReadAsStringAsync().Result;
return JsonConvert.DeserializeObject<TResponseType>(result);
}
}
}
As stated in an answer for a different question, the solution is to set the IfModifiedSince property to prevent the default caching behaviour like this:
httpClient.DefaultRequestHeaders.IfModifiedSince = DateTime.Now;
You could also check on MSDN for more information.
I have a custom complex type that I want to work with using Web API.
public class Widget
{
public int ID { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
And here is my web API controller method. I want to post this object like so:
public class TestController : ApiController
{
// POST /api/test
public HttpResponseMessage<Widget> Post(Widget widget)
{
widget.ID = 1; // hardcoded for now. TODO: Save to db and return newly created ID
var response = new HttpResponseMessage<Widget>(widget, HttpStatusCode.Created);
response.Headers.Location = new Uri(Request.RequestUri, "/api/test/" + widget.ID.ToString());
return response;
}
}
And now I'd like to use System.Net.HttpClient to make the call to the method. However, I'm unsure of what type of object to pass into the PostAsync method, and how to construct it. Here is some sample client code.
var client = new HttpClient();
HttpContent content = new StringContent("???"); // how do I construct the Widget to post?
client.PostAsync("http://localhost:44268/api/test", content).ContinueWith(
(postTask) =>
{
postTask.Result.EnsureSuccessStatusCode();
});
How do I create the HttpContent object in a way that web API will understand it?
The generic HttpRequestMessage<T> has been removed. This :
new HttpRequestMessage<Widget>(widget)
will no longer work.
Instead, from this post, the ASP.NET team has included some new calls to support this functionality:
HttpClient.PostAsJsonAsync<T>(T value) sends “application/json”
HttpClient.PostAsXmlAsync<T>(T value) sends “application/xml”
So, the new code (from dunston) becomes:
Widget widget = new Widget()
widget.Name = "test"
widget.Price = 1;
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("http://localhost:44268");
client.PostAsJsonAsync("api/test", widget)
.ContinueWith((postTask) => postTask.Result.EnsureSuccessStatusCode() );
You should use the SendAsync method instead, this is a generic method, that serializes the input to the service
Widget widget = new Widget()
widget.Name = "test"
widget.Price = 1;
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("http://localhost:44268/api/test");
client.SendAsync(new HttpRequestMessage<Widget>(widget))
.ContinueWith((postTask) => postTask.Result.EnsureSuccessStatusCode() );
If you don't want to create the concrete class, you can make it with the FormUrlEncodedContent class
var client = new HttpClient();
// This is the postdata
var postData = new List<KeyValuePair<string, string>>();
postData.Add(new KeyValuePair<string, string>("Name", "test"));
postData.Add(new KeyValuePair<string, string>("Price ", "100"));
HttpContent content = new FormUrlEncodedContent(postData);
client.PostAsync("http://localhost:44268/api/test", content).ContinueWith(
(postTask) =>
{
postTask.Result.EnsureSuccessStatusCode();
});
Note: you need to make your id to a nullable int (int?)
Note that if you are using a Portable Class Library, HttpClient will not have PostAsJsonAsync method.
To post a content as JSON using a Portable Class Library, you will have to do this:
HttpClient client = new HttpClient();
HttpContent contentPost = new StringContent(argsAsJson, Encoding.UTF8,
"application/json");
await client.PostAsync(new Uri(wsUrl), contentPost).ContinueWith(
(postTask) => postTask.Result.EnsureSuccessStatusCode());
If you want the types of convenience methods mentioned in other answers but need portability (or even if you don't), you might want to check out Flurl [disclosure: I'm the author]. It (thinly) wraps HttpClient and Json.NET and adds some fluent sugar and other goodies, including some baked-in testing helpers.
Post as JSON:
var resp = await "http://localhost:44268/api/test".PostJsonAsync(widget);
or URL-encoded:
var resp = await "http://localhost:44268/api/test".PostUrlEncodedAsync(widget);
Both examples above return an HttpResponseMessage, but Flurl includes extension methods for returning other things if you just want to cut to the chase:
T poco = await url.PostJsonAsync(data).ReceiveJson<T>();
dynamic d = await url.PostUrlEncodedAsync(data).ReceiveJson();
string s = await url.PostUrlEncodedAsync(data).ReceiveString();
Flurl is available on NuGet:
PM> Install-Package Flurl.Http
After investigating lots of alternatives, I have come across another approach, suitable for the API 2.0 version.
(VB.NET is my favorite, sooo...)
Public Async Function APIPut_Response(ID as Integer, MyWidget as Widget) as Task(Of HttpResponseMessage)
Dim DesiredContent as HttpContent = New StringContent(JsonConvert.SerializeObject(MyWidget))
Return Await APIClient.PutAsync(String.Format("api/widget/{0}", ID), DesiredContent)
End Function
Good luck! For me this worked out (in the end!).
Regards,
Peter
I think you can do this:
var client = new HttpClient();
HttpContent content = new Widget();
client.PostAsync<Widget>("http://localhost:44268/api/test", content, new FormUrlEncodedMediaTypeFormatter())
.ContinueWith((postTask) => { postTask.Result.EnsureSuccessStatusCode(); });
In case someone like me didn't really understand what all above are talking about, I give an easy example which is working for me.
If you have a web api which url is "http://somesite.com/verifyAddress", it is a post method and it need you to pass it an address object. You want to call this api in your code. Here what you can do.
public Address verifyAddress(Address address)
{
this.client = new HttpClient();
client.BaseAddress = new Uri("http://somesite.com/");
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var urlParm = URL + "verifyAddress";
response = client.PostAsJsonAsync(urlParm,address).Result;
var dataObjects = response.IsSuccessStatusCode ? response.Content.ReadAsAsync<Address>().Result : null;
return dataObjects;
}
This is the code I wound up with, based upon the other answers here. This is for an HttpPost that receives and responds with complex types:
Task<HttpResponseMessage> response = httpClient.PostAsJsonAsync(
strMyHttpPostURL,
new MyComplexObject { Param1 = param1, Param2 = param2}).ContinueWith((postTask) => postTask.Result.EnsureSuccessStatusCode());
//debug:
//String s = response.Result.Content.ReadAsStringAsync().Result;
MyOtherComplexType moct = (MyOtherComplexType)JsonConvert.DeserializeObject(response.Result.Content.ReadAsStringAsync().Result, typeof(MyOtherComplexType));
Make a service call like this:
public async void SaveActivationCode(ActivationCodes objAC)
{
var client = new HttpClient();
client.BaseAddress = new Uri(baseAddress);
HttpResponseMessage response = await client.PutAsJsonAsync(serviceAddress + "/SaveActivationCode" + "?apiKey=445-65-1216", objAC);
}
And Service method like this:
public HttpResponseMessage PutSaveActivationCode(ActivationCodes objAC)
{
}
PutAsJsonAsync takes care of Serialization and deserialization over the network