Web Api json response $id variable - c#

in asp web api i have this controller that respond to ajax request and send back json data:
public IEnumerable<PersonaleDTO> GetPersonale()
{
var n = (from p in db.Personale
select new PersonaleDTO { Id = p.Id, Nome = p.Nome, Cognome = p.Cognome Cellulare = p.Cellulare, Email = p.Email, Attivo = (bool)p.Attivo }).ToList();
return n;
}
This method return seven object and it's correct.
Now when i receive back the data in the json format i see that i receive also and $id member along with the id, Nome, Cognome...
What is the $id variable? How can remove this from the json response?

Try this code in WebApiConfig
var json = config.Formatters.JsonFormatter;
json.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.None;
Edit : If for some reason you're using a custom ContractResolver then as per this post
The custom ContractResolver setting overrides the PreserveReferencesHandling setting.
In your implementation of DefaultContractResolver/IContractResolver, add this;
public override JsonContract ResolveContract(Type type) {
var contract = base.ResolveContract(type);
contract.IsReference = false;
return contract;
}
This behaves similarly to the PreserveReferencesHandling.None setting without a custom ContractResolver.

Related

Validate Requests in NSwag generated Client

I have a .NET 6 Webclient and REST Contract, that is generated from a YAML with NSwag.
The contract contains some validation properties. Is there any way to validate my request on the client side? I don't want to write the validation code by hand.
In the NSwag documentation I only found flags to generate validation attributes for generated controllers, but not for the web client.
YAML:
- name: anyField
in: query
description: Field with max value=20 and required
required: true
schema:
maximum: 20
type: integer
format: int32
Generated contract code:
/// <summary>
/// Field with max value=20 and required
/// </summary>
[Newtonsoft.Json.JsonProperty("anyField", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public int AnyField{ get; set; }
I could read the yaml with NSwag, get the json schema and use this for the validation:
public async Task<ICollection<ValidationError>> Validate(Request request) {
var yamlBytes = resx.yaml;
await using var yamlMs = new MemoryStream(yamlBytes);
using var yamlReader = new StreamReader(yamlMs);
var yamlText = await yamlReader.ReadToEndAsync();
var apiDocument = await NSwag.OpenApiYamlDocument.FromYamlAsync(yamlText);
var schema = apiDocument.Paths["/restPath"]["post"].RequestBody.Content["application/json"].Schema;
var jsonSettings = new JsonSettingsProvider().ProvideSettings;
var body = JsonConvert.SerializeObject(request, jsonSettings);
var errors = schema.Validate(body);
return errors;
}

Error "405 Method Not Allow" When Calling Put method in Postman with body parameter

I was trying to call the Put method through Postman and always getting error: "405 Method Not Allow" and "Message": "The requested resource does not support http method 'PUT'."
I'm using DocumentDB and C#. Here is my code:
[Route("multilanguage/Resources/{id}/{Language}")]
[HttpPut]
public async Task<IHttpActionResult> UpdateResource(string Id, string Language, string text)
{
client = new DocumentClient(new Uri(EndPoint), AuthKey);
var collectionLink = UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId);
var query = new SqlQuerySpec("SELECT * FROM MultiLanguage as m where m.id = #pmId",
new SqlParameterCollection(new SqlParameter[] { new SqlParameter { Name = "#pmId", Value = Id } }));
Document doc = client.CreateDocumentQuery<Document>(
collectionLink, query).AsEnumerable().FirstOrDefault();
List<Models.Translations> d = doc.GetPropertyValue<List<Models.Translations>>("Translations");
Models.Translations temp = d.Find(p => p.Language == Language);
temp.Content = text;
temp.LastModified = DateTimeOffset.Now;
temp.ModifiedBy = "admin";
doc.SetPropertyValue("Translations", d);
Document updated = await client.ReplaceDocumentAsync(doc);
return Ok();
}
When I call the Put method throught Postman, I call "http://localhost:XXXX/multilanguage/resources/2/En". "2" and "En" are the first two parameters in my code. And I also specify the "text" parameter value in the Postman request Body with x-www-form-urlencoded type: key = text, value = Test! This put method suppose to update the temp.Content value to "Test!". However, it always failed with the error I mentioned above.
Did I miss anything here?
The 405 error when performing a PUT request to web api is a well known topic. You can find many solutions in this or this SO question.
And for the design of you controller:
PUT are designed to have a body, just like POST and in your case
you should send all parameters in the body instead.
You should create a class which contains the objects you want to send to the server:
public class resourceClass
{
public string Id { get; set; }
public string Language { get; set; }
public string text { get; set; }
}
Then specify the route without the attribute routing and get the object from the request body
[Route("multilanguage/Resources/PutResource")]
[HttpPut]
public async Task<IHttpActionResult> UpdateResource([FromBody] resourceClass obj)
{
client = new DocumentClient(new Uri(EndPoint), AuthKey);
var collectionLink = UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId);
var query = new SqlQuerySpec("SELECT * FROM MultiLanguage as m where m.id = #pmId",
new SqlParameterCollection(new SqlParameter[] { new SqlParameter { Name = "#pmId", Value = Id } }));
Document doc = client.CreateDocumentQuery<Document>(
collectionLink, query).AsEnumerable().FirstOrDefault();
List<Models.Translations> d = doc.GetPropertyValue<List<Models.Translations>>("Translations");
Models.Translations temp = d.Find(p => p.Language == Language);
temp.Content = text;
temp.LastModified = DateTimeOffset.Now;
temp.ModifiedBy = "admin";
doc.SetPropertyValue("Translations", d);
Document updated = await client.ReplaceDocumentAsync(doc);
return Ok();
}
From the client you could add an object to the PUT request of Content-Type application/json like this
var data = {
Id: clientId,
Language: clientLanguage,
text: clientText
};
Don't forget to stringify the json when adding it to the http request
data: JSON.stringify(data),
The PUT controller will then be reached at "http://localhost:XXXX/multilanguage/resources/putresource".
Check the URL for which you are posting the data, in my case the URL was incorrect because of which I got these errors, also verify that in Body you should select raw and change the Text to JSON if you are passing a JSON as a data.

Deserialize object passed as abstract class by SignalR

I have a WebAPI server that has a Hub that replies to Subscribe requests by publishing an Dictionary<long, List<SpecialParam>> object.
The list of SpecialParam contains items of type SpecialParamA & SpecialParamB which both inherit SpecialParam.
When I try to capture the publish on the client:
hubProxy.On<Dictionary<long, List<SpecialParam>>>(hubMethod, res =>
{
DoStuff();
});
The DoStuff() method isn't called. If I change the publish return value to string, and change the proxy to receive a string value, the DoStuff() method is called. Therefore, the problem is with the deserialization of the SpecialParam Item.
I tried configuring on the server-side:
var serializer = JsonSerializer.Create();
serializer.TypeNameHandling = TypeNameHandling.All;
var hubConfig = new HubConfiguration();
hubConfig.Resolver.Register(typeof(JsonSerializer), () => serializer);
GlobalHost.DependencyResolver.Register(typeof(JsonSerializer), () => serializer);
But it didn't help.
I also tried adding to the client:
HubConnection hubConnection = new HubConnection(hubPath);
hubConnection.JsonSerializer.TypeNameHandling = Newtonsoft.Json.TypeNameHandling.Auto;
hubProxy = hubConnection.CreateHubProxy(hubName);
hubProxy.JsonSerializer.TypeNameHandling = Newtonsoft.Json.TypeNameHandling.Auto;
And it also didn't help.
In other solutions I found that people defined a new IParameterResolver, but it is only called when the server receives the input to the hub method, and not when the output is published from the hub.
Please help!
UPDATE
Here is what I caught with fidler:
{"$type":"Microsoft.AspNet.SignalR.Hubs.HubResponse, Microsoft.AspNet.SignalR.Core","I":"0"}
This is what the server replies to the client.
UPDATE 2
I'm still trying to figure out how to receive it already deserialized as Dictionary<long, List<SpecialParam>>.
I solved it by setting in the service:
public static void ConfigureApp(IAppBuilder appBuilder)
{
...
var service = (JsonSerializer)GlobalHost.DependencyResolver.GetService(typeof(Newtonsoft.Json.JsonSerializer));
service.TypeNameHandling = TypeNameHandling.All;
...
}
and removing the expected type in the client:
hubProxy.On(hubMethod, res =>
{
DoStuff();
});
I get the response in json form and I deserialize it with:
var serializer = new JsonSerializer();
serializer.TypeNameHandling = TypeNameHandling.All;
JObject jObject = resp as JObject;
var specialParams = jObject.ToObject<Dictionary<long, List<SpecialParam>>>(serializer);
I still don't know how to make my client receive it already deserialized.

Content Negotiation in ASP.NET Web API

I'm migrating a web service to ASP.NET Web Api 2, and hitting trouble at almost the first hurdle.
I want to do this:
public class SomeController : ApiController
{
[Route("some\url")]
public object Get()
{
return { Message = "Hello" };
}
}
And be able to ask the service for either "application/json" or "application/xml" (or indeed any other potential format, such as Message Pack), and get a serialized response. But it seems it only works for JSON.
I've read this and seen the documentation which states clearly that the framework cannot handle serialization of anonymous types into XML (seriously) and that the solution is to not use XML (seriously).
When I attempt to call this and request XML as response type, I get
The 'ObjectContent`1' type failed to serialize the response body for content type 'application/xml; charset=utf-8'.
I'm not removing support for clients wanting to ask for XML - but I genuinely can't find a work around for this - what can I do?
Edit
I've added these:
System.Web.Http.GlobalConfiguration.Configuration.Formatters.XmlFormatter.SupportedMediaTypes.Clear();
config.Formatters.Insert(0, new System.Net.Http.Formatting.JsonMediaTypeFormatter());
config.Formatters.Insert(0, new System.Net.Http.Formatting.XmlMediaTypeFormatter());
as per Dalorzo's answer, but it made no difference.
For clarification, the service works absolutely fine when I call it using an accept header of application/json, but bombs when I call it with an accept header of application/xml.
You have 3 options:
Create a class with a proper name and return the object instead of an anonymous type.
Or if you want to return the anonymous instance, you should remove XML formatter, because anonymous types are not supported by XML Formatter
Create your own formatter inheriting from MediaTypeFormatter or BufferedMediaTypeFormatter
You can do it by following code :
public HttpResponseMessage GetTestData()
{
var testdata = (from u in context.TestRepository.Get().ToList()
select
new Message
{
msgText = u.msgText
});
return ActionContext.Request.CreateResponse(HttpStatusCode.OK, testdata);
}
// This Code Is Used To Change Contents In Api
public HttpResponseMessage GetAllcarDetails( string formate)
{
CarModel ST = new CarModel();
CarModel ST1 = new CarModel();
List<CarModel> li = new List<CarModel>();
ST.CarName = "Maruti Waganor";
ST.CarPrice = 400000;
ST.CarModeles = "VXI";
ST.CarColor = "Brown";
ST1.CarName = "Maruti Swift";
ST1.CarPrice = 500000;
ST1.CarModeles = "VXI";
ST1.CarColor = "RED";
li.Add(ST);
li.Add(ST1);
// return li;
this.Request.Headers.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/xml"));
//For Json Use "application/json"
IContentNegotiator negotiator =
this.Configuration.Services.GetContentNegotiator();
ContentNegotiationResult result = negotiator.Negotiate(
typeof(List<CarModel>), this.Request, this.Configuration.Formatters);
if (result == null) {
var response = new HttpResponseMessage(HttpStatusCode.NotAcceptable);
throw new HttpResponseException(response);
}
return new HttpResponseMessage() {
Content = new ObjectContent<List<CarModel>>(
li, // What we are serializing
result.Formatter, // The media formatter
result.MediaType.MediaType // The MIME type
)
};
}
Please browse your API route on Chrome. Chrome, by default shows output in XML format. If that doesn't happen, it means that your service is preventing XML format using media formatting.
And in that case, you should search your WebApiConfig. If nothing is present there, add this file to your project
using System.Net.Http.Formatting;
using System.Collections.Generic;
using System.Net.Http;
using System;
using System.Linq;
using System.Net.Http.Headers;
namespace ExampleApp.Infrastructure
{
public class CustomNegotiator : DefaultContentNegotiator
{
public override ContentNegotiationResult Negotiate(Type type, HttpRequestMessage request, IEnumerable<MediaTypeFormatter> formatters)
{
if(request.Headers.UserAgent.Where(x=>x.Product!=null&& x.Product.Name.ToLower().Equals("chrome")).Count() > 0)
{
return new ContentNegotiationResult(new JsonMediaTypeFormatter(), new MediaTypeHeaderValue("application/xml"));
}
else
{
return base.Negotiate(type, request, formatters);
}
}
}
}
and, in WebApiConfig.cs, add:
config.Services.Replace(typeof(IContentNegotiator), new CustomNegotiator());

serializing form data to a web api

is there a better way of implementing this code:
public Payment SendPaymentToSagePay(Payment paymentModel)
{
var webClient = new WebClient()
{
BaseAddress = "http://localhost:64317/api/PaymentStart"
};
string price = paymentModel.price.ToString();
string productId = paymentModel.productId.ToString();
string paymentMode = paymentModel.paymentMode.ToString();
string context = paymentModel.context.ToString();
var collection = new NameValueCollection();
collection.Add("title", paymentModel.title);
collection.Add("firstName", paymentModel.firstName);
collection.Add("lastName", paymentModel.lastName);
collection.Add("email", paymentModel.email);
collection.Add("context", context);
collection.Add("programmeName", paymentModel.programmeName);
collection.Add("productId", productId);
collection.Add("price", price);
collection.Add("cardHolderTitle", paymentModel.cardHolderTitle);
collection.Add("cardHolderLastName", paymentModel.cardHolderLastName);
collection.Add("cardHolderFirstName,", paymentModel.cardHolderFirstName);
collection.Add("cardHolderEmail", paymentModel.cardHolderEmail);
collection.Add("selfAddress1", paymentModel.selfAddress1);
collection.Add("selfAddress2", paymentModel.selfAddress2);
collection.Add("selfCity", paymentModel.selfCity);
collection.Add("selfCountry", paymentModel.selfCountry);
collection.Add("selfState", paymentModel.selfState);
collection.Add("selfPhone", paymentModel.selfPhone);
collection.Add("selfPostCode", paymentModel.selfPostCode);
collection.Add("otherAddress1", paymentModel.otherAddress1);
collection.Add("otherAddress2", paymentModel.otherAddress2);
collection.Add("otherCity", paymentModel.otherCity);
collection.Add("otherCountry", paymentModel.otherCountry);
collection.Add("otherState", paymentModel.otherState);
collection.Add("otherPhone", paymentModel.otherPhone);
collection.Add("otherPostCode", paymentModel.otherPostCode);
collection.Add("ipAddress", paymentModel.ipAddress);
collection.Add("additionalInfo", paymentModel.additionalInfo);
collection.Add("paymentMode", paymentMode);
byte[] responseBytes = webClient.UploadValues("", "POST", collection);
string response = Encoding.UTF8.GetString(responseBytes);
string decodedResponse = HttpUtility.UrlDecode(response);
JavaScriptSerializer js = new JavaScriptSerializer();
var payment = js.Deserialize<Payment>(decodedResponse);
return payment;
}
I am using this method to serialize my payment model and then send it to an external web api which then adds this data to various repositories.
Is there a more succinct method of doing this?
Ideally I want a solution that's more MVC4 and Dependency Injection (I am currently using the Unity container) friendly, I created this serializer as an interim solution whilst I researched other methods, so far this is the only one I have been able to successfully implement.
Any advice would be very much appreciated.
Can you post JSON instead of a form collection? If you can, you can use the same JavaScriptSerializer you're using to deserialize the response to serialize your Payment:
string serializedPayment = js.Serialize(paymentModel);

Categories