WCF REST service dictionary serialization - wcf

I'm having trouble with serializing dictionary in my WCF service.
[DataContract]
public class UserInfo
{
[DataMember]
public Guid ID { get; set; }
[DataMember]
public string Name { get; set; }
[DataMember]
public IDictionary<string, List<string>> Permissions { get; set; } = new Dictionary<string, List<string>>();
}
This is example of current response
{
"ID": "1",
"Name": "admin",
"Permissions": [
{
"Key": "Users",
"Value": [
"Read",
"Edit"
]
},
{
"Key": "Management",
"Value": [
"Read"
]
}
]
}
and this is desired response
{
"ID": "1",
"Name": "admin",
"Permissions": {
"Users": ["Read", "Edit"],
"Management": ["Read"]
}
}
Is there way to implement this globally or on specific property?

I've solved it by using custom made dictionary as explained here.
Since I've created this type for serialization
AjaxDictionary<string, string[]>
I had to add
[ServiceKnownType(typeof(string[]))]
on my class that is ServiceContract for returning responses.

Related

System.Text.Json Deserialize and get Property Values [closed]

Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed 2 years ago.
Improve this question
I'm struggling to understand how to access the property values from a class which was derived by using the Visual Studio 'Paste Special' into a new C# Model. The JSON was copied from an API returning log events, in the example I've only included one log event but the API will usually return multiple events..
Below is JSON from the API:
{
"lastReadEventId": "event-1a29db8ad2c608d8079f040000000000",
"scannedEventCount": 1,
"eventEntities": [
{
"timestamp": "2021-02-16T21:59:15.7233546+00:00",
"properties": [
{
"name": "LogEventCategory",
"value": "System"
},
{
"name": "LogEventType",
"value": "Application Startup"
},
{
"name": "LogEventSource",
"value": "WebApp_RAZOR"
},
{
"name": "LogUserId",
"value": ""
},
{
"name": "LogUsername",
"value": ""
},
{
"name": "LogForename",
"value": ""
},
{
"name": "LogSurname",
"value": ""
},
{
"name": "LogData",
"value": "Application Starting Up"
},
{
"name": "MachineName",
"value": "DESKTOP-OS52032"
},
{
"name": "ProcessId",
"value": 16716
},
{
"name": "ThreadId",
"value": 1
},
{
"name": "ApplicationSource",
"value": "WebApp-RAZOR"
}
],
"messageTemplateTokens": [
{
"propertyName": "LogEventCategory",
"rawText": "{#LogEventCategory}"
},
{
"propertyName": "LogEventType",
"rawText": "{#LogEventType}"
},
{
"propertyName": "LogEventSource",
"rawText": "{#LogEventSource}"
},
{
"propertyName": "LogUserId",
"rawText": "{#LogUserId}"
},
{
"propertyName": "LogUsername",
"rawText": "{#LogUsername}"
},
{
"propertyName": "LogForename",
"rawText": "{#LogForename}"
},
{
"propertyName": "LogSurname",
"rawText": "{#LogSurname}"
},
{
"propertyName": "LogData",
"rawText": "{#LogData}"
}
],
"eventType": "$A970522D",
"level": "Information",
"renderedMessage": "SystemApplication StartupWebApp_RAZORApplication Starting Up",
"id": "event-1a29db8ad2c608d8079f040000000000",
"links": {
"Self": "api/events/event-1a29db8ad2c608d8079f040000000000{?download,render,clef}",
"Group": "api/events/resources"
}
}
]
}
Below is the model created from using the paste special control:
public class SeqLogEvents
{
public class Rootobject
{
public string LastReadEventId { get; set; }
public int ScannedEventCount { get; set; }
public Evententity[] EventEntities { get; set; }
}
public class Evententity
{
public DateTime Timestamp { get; set; }
public Property1[] Properties { get; set; }
public Messagetemplatetoken[] MessageTemplateTokens { get; set; }
public string EventType { get; set; }
public string Level { get; set; }
public string TenderedMessage { get; set; }
public string Id { get; set; }
public Links Links { get; set; }
}
public class Links
{
public string Self { get; set; }
public string Group { get; set; }
}
public class Property1
{
public string Name { get; set; }
public object Value { get; set; }
}
public class Messagetemplatetoken
{
public string PropertyName { get; set; }
public string RawText { get; set; }
}
}
I need to be able to access every property value in each section, included nested items as well as iterating over a list of the above items in a foreach loop. I've done this successfully in the past using Newtonsoft Json but I'm trying to achieve the same using system.text.json as the newer standard.
I can't even get access the first property value. I was trying to follow the MS instructions here:
How to read JSON as .NET objects (deserialize)
My stupid mistake, realised I was trying to reference the wrong class.
Should have referenced the Rootobject when deserializing:

NSwag generates base class even though JsonSchemaFlattenAttribute is set

I am using NSwag for my ASP.NET Core web api project and just can't get it to work. What I'm trying to do is to exclude some of the base properties of IdentityUser. Here is my custom user class:
[DataContract]
[JsonSchemaFlattenAttribute]
public class User : IdentityUser
{
// Overridden variables
[DataMember( Name = "id" )]
[PersonalData]
public new string Id { get; set; }
[DataMember( Name = "email" )]
[ProtectedPersonalData]
public new string Email { get; set; }
[IgnoreDataMember]
[JsonSchemaIgnoreAttribute]
public new DateTimeOffset? LockoutEnd { get; set; }
[PersonalData]
[IgnoreDataMember]
[JsonSchemaIgnoreAttribute]
public new bool TwoFactorEnabled { get; set; }
[PersonalData]
[IgnoreDataMember]
[JsonSchemaIgnoreAttribute]
public new bool PhoneNumberConfirmed { get; set; }
[ProtectedPersonalData]
[IgnoreDataMember]
[JsonSchemaIgnoreAttribute]
public new string PhoneNumber { get; set; }
[IgnoreDataMember]
[JsonSchemaIgnoreAttribute]
public new string ConcurrencyStamp { get; set; }
[IgnoreDataMember]
[JsonSchemaIgnoreAttribute]
public new string SecurityStamp { get; set; }
[IgnoreDataMember]
[JsonSchemaIgnoreAttribute]
public new string PasswordHash { get; set; }
[PersonalData]
[IgnoreDataMember]
[JsonSchemaIgnoreAttribute]
public new bool EmailConfirmed { get; set; }
[IgnoreDataMember]
[JsonSchemaIgnoreAttribute]
public new string NormalizedEmail { get; set; }
[IgnoreDataMember]
[JsonSchemaIgnoreAttribute]
public new string NormalizedUserName { get; set; }
[ProtectedPersonalData]
[IgnoreDataMember]
[JsonSchemaIgnoreAttribute]
public new string UserName { get; set; }
[IgnoreDataMember]
[JsonSchemaIgnoreAttribute]
public new bool LockoutEnabled { get; set; }
[IgnoreDataMember]
[JsonSchemaIgnoreAttribute]
public new int AccessFailedCount { get; set; }
// Custom variables
[DataMember( Name = "date_joined_utc" )]
public DateTime DateJoinedUtc { get; set; }
[IgnoreDataMember]
[JsonSchemaIgnoreAttribute]
public virtual ICollection<RefreshToken> RefreshTokens { get; set; }
// Optimistic concurrency
[Timestamp]
[IgnoreDataMember]
[JsonSchemaIgnoreAttribute]
public byte[] RowVersion { get; set; }
}
Which generates:
"User": {
"type": "object",
"required": [
"dateJoinedUtc"
],
"properties": {
"id": {
"type": "string"
},
"email": {
"type": "string"
},
"dateJoinedUtc": {
"type": "string",
"format": "date-time"
}
},
"allOf": [
{
"$ref": "#/definitions/IdentityUserOfString"
},
{}
]
},
"IdentityUserOfString": {
"type": "object",
"required": [
"emailConfirmed",
"phoneNumberConfirmed",
"twoFactorEnabled",
"lockoutEnabled",
"accessFailedCount"
],
"properties": {
"id": {
"type": "string"
},
"userName": {
"type": "string"
},
"normalizedUserName": {
"type": "string"
},
"email": {
"type": "string"
},
"normalizedEmail": {
"type": "string"
},
"emailConfirmed": {
"type": "boolean"
},
"passwordHash": {
"type": "string"
},
"securityStamp": {
"type": "string"
},
"concurrencyStamp": {
"type": "string"
},
"phoneNumber": {
"type": "string"
},
"phoneNumberConfirmed": {
"type": "boolean"
},
"twoFactorEnabled": {
"type": "boolean"
},
"lockoutEnd": {
"type": "string",
"format": "date-time"
},
"lockoutEnabled": {
"type": "boolean"
},
"accessFailedCount": {
"type": "integer",
"format": "int32"
}
}
},
My understanding of JsonSchemaFlattenAttribute is that it should exclude the base classes and put every property in the derived class. So why is the class IdentityUserOfString generated? What am I missing?
For some reason setting OpenApiIgnore on the User class solved it:
[DataContract]
//[JsonSchemaFlattenAttribute]
[OpenApiIgnore]
public class User : IdentityUser
Generates:
"User": {
"type": "object",
"required": [
"dateJoinedUtc"
],
"properties": {
"id": {
"type": "string"
},
"email": {
"type": "string"
},
"dateJoinedUtc": {
"type": "string",
"format": "date-time"
}
}
},

QnAMaker QueryResult Missing Context Property

I'm working on adding follow up prompts to QnAMaker responses.
var answers = await qnaService.GetAnswersAsync(stepContext.Context, null, null);
var prompts = answers[0].Context?.morecodehere() <--- Context not defined.
answers is resolved as QueryResult[]
The documentation, samples, and SO question indicate that there is a Context property on the QueryResult object; however, my project's QnAResult from the Virtual Assistant Template does not.
I'd like to be able to follow the samples's way of accessing follow-up prompts using adaptive cards.
My question is whether or not I have something outdated or if it is more likely that I have a configuration issue. Could this be as simple as creating a new QueryResult model to use?
Sample from Microsoft's sample library:
...
var query = inputActivity.Text;
var qnaResult = await _qnaService.QueryQnAServiceAsync(query, (QnABotState)oldState);
var qnaAnswer = qnaResult[0].Answer;
var prompts = qnaResult[0].Context?.Prompts;
if (prompts == null || prompts.Length < 1)
{
outputActivity = MessageFactory.Text(qnaAnswer);
}
...
QueryResult Class from Metadata Locally
namespace Microsoft.Bot.Builder.AI.QnA
{
public class QueryResult
{
public QueryResult();
[JsonProperty("questions")]
public string[] Questions { get; set; }
[JsonProperty("answer")]
public string Answer { get; set; }
[JsonProperty("score")]
public float Score { get; set; }
[JsonProperty(PropertyName = "metadata")]
public Metadata[] Metadata { get; set; }
[JsonProperty(PropertyName = "source")]
public string Source { get; set; }
[JsonProperty(PropertyName = "id")]
public int Id { get; set; }
}
}
Sample from Faq.qna Json
"qnaDocuments": [
{
"id": 46,
"answer": "Answer Text [Redacted Link](https://link)\n.",
"source": "sourcefile.docx",
"questions": [
"Sample question text"
],
"metadata": [],
"alternateQuestions": "",
"alternateQuestionClusters": [],
"context": {
"isContextOnly": false,
"prompts": [
{
"displayOrder": 0,
"qnaId": 51,
"qna": {
"id": 51,
"answer": "[Redacted Link](https://linkhere)",
"source": "Editorial",
"questions": [
"More Information",
"Troubleshooting Information"
],
"metadata": [],
"alternateQuestions": "",
"alternateQuestionClusters": [],
"context": {
"isContextOnly": true,
"prompts": []
}
},
"displayText": "More Information"
}
]
}
},
...

How to display Swashbuckle parameter object only with fields that should actually be sent?

I'm starting to work with Swagger using the Swashbuckle library for AspNetCore.
And when putting in my API an object with references it presents as if it were necessary to send all the fields of the references, when only the identifier (Id)
Here's an example:
Model Structure:
public class Cidade
{
public long Id { get; set; }
public string Nome { get; set; }
public Uf Uf { get; set; }
}
public class Uf
{
public long Id { get; set; }
public string Nome { get; set; }
public Pais Pais { get; set; }
}
public class Pais
{
public long Id { get; set; }
public string Nome { get; set; }
}
And the following API:
[Produces("application/json")]
[Route("api/Cidade")]
public class CidadeController : Controller
{
// POST: api/Cidade
[HttpPost]
public void Post([FromBody]Cidade value)
{
}
}
The result in Swagger is as follows:
And what I would like is the following (only up to uf.id):
How can I get this result?
I followed the logic of #HelderSepu answer, to get my solution, which would be as follows:
I built a Schema filter to add an example to the reference properties (Ref), which has a property called "Id":
public class ApplySchemaRefIdExtensions : ISchemaFilter
{
public void Apply(Schema schema, SchemaFilterContext context)
{
if (schema.Properties != null)
{
foreach (var p in schema.Properties)
{
if (p.Value.Example == null && p.Value.Ref != null)
{
var reference = context.SystemType.GetProperty(p.Value.Ref.Split("/").LastOrDefault());
if (reference != null)
{
var id = reference.PropertyType.GetProperty("Id");
if (id != null)
{
p.Value.Example = new
{
Id = 123
};
p.Value.Ref = null;
}
}
}
}
}
}
}
On Startup.cs:
services.AddSwaggerGen(c =>
{
// ...
c.SchemaFilter<ApplySchemaRefIdExtensions>();
});
Result for the same example of the question:
I was looking on my samples and I think I found something you can use:
http://swagger-net-test.azurewebsites.net/swagger/ui/index?filter=P#/PolygonVolume/PolygonVolume_Post
On my case I'm adding more, you need less, but still what you need is just a custom example...
the JSON looks like this:
"PolygonVolumeInsideParameter": {
"properties": {
"Points": {
"items": {
"$ref": "#/definitions/Location"
},
"xml": {
"name": "Location",
"wrapped": true
},
"example": [
{
"Lat": 1.0,
"Lon": 2.0
},
{
"Lat": 5.0,
"Lon": 6.0
}
],
"type": "array"
},
"PlanId": {
"type": "string"
}
},
"xml": {
"name": "PolygonVolumeInsideParameter"
},
"type": "object"
},
And on swashbuckle I added the example it with an ISchemaFilter my code is here:
https://github.com/heldersepu/Swagger-Net-Test/blob/master/Swagger_Test/App_Start/SwaggerConfig.cs#L891

Web API Getting Http 500 error

Here is my MVC Controller and everything is fine:
private UnitOfWork UOW;
public InventoryController()
{
UOW = new UnitOfWork();
}
// GET: /Inventory/
public ActionResult Index()
{
var products = UOW.ProductRepository.GetAll().ToList();
return View(products);
}
Same method call in API Controller gives me an Http 500 Error:
private UnitOfWork _unitOfWork;
public TestController()
{
_unitOfWork = new UnitOfWork();
}
public IEnumerable<Product> Get()
{
var products = _unitOfWork.ProductRepository.GetAll().ToList();
return products;
}
Debugging shows that indeed there is data being returned in both controllers' UOW calls. I then added a customer configuration in Global:
public static void CustomizeConfig(HttpConfiguration config)
{
config.Formatters.Remove(config.Formatters.XmlFormatter);
var json = config.Formatters.JsonFormatter;
json.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
}
I am still receiving an Http 500 in API Controller ONLY and at a loss as to why. Any ideas?
UPDATE:
It appears using lazy loading caused the problem. When I set the associated properties to NON-VIRTUAL the Test API provided the necessary JSON string. However, whereas before I had the Vendor class included, I only have VendorId. I really wanted to included the associated classes. Any ideas? I know there are alot of smart people out there. Anyone?
Problem Solved:
The issue was not Lazy Loading after all. The issue was that while I correctly had an association of Vendor in product, I also had a collection of products in Vendor, presumably causing something circular:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public int VendorId { get; set; }
public virtual Vendor Vendor { get; set; }
}
public class Vendor
{
public int Id { get; set; }
//public Vendor()
//{
// Products = new List<Product>();
//}
public string CompanyName { get; set; }
// public ICollection<Product> Products { get; set; }
}
Omitting the collection in Vendors and reinstating the virtual in Products did the trick.
"vendor": {
"id": 4,
"companyName": "Vendor 3"
},
"id": 1,
"name": "Product 1",
"vendorId": 4
},
{
"vendor": {
"id": 2,
"companyName": "Vendor 4"
},
"id": 2,
"name": "Product 2",
"vendorId": 2
},
{
"vendor": {
"id": 3,
"companyName": "Vendor 2"
},
"id": 3,
"name": "Product 3",
"vendorId": 3
},
{
"vendor": {
"id": 1,
"companyName": "Vendor 1"
},
"id": 4,
"name": "Product 4",
"vendorId": 1
}]