I'm trying to consume existing WCF services (basic binding) and I'm facing some issues related with the deserialization of the received message. Let me start by showing a snippet of the message:
<s:Body>
<ObtemUtilizadoresResponse xmlns="http://xxx. pt/Mercados"><ObtemUtilizadoresResult xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<util>
<Id>123</Id>
<ver>AAAAAACL5j4=</ver>
<im>-2</im>
<n>User 123</n>
</util>
....
</s:Body>
Initially, I've start by creating a new DTO for performing the deserialization which looks like this:
[DataContract(Namespace = "http://xxx.pt/Mercados", Name = "util")]
public class Utilizador {
[field: DataMember(Name = "Id")]
public int Id { get; set; }
[field: DataMember(Name = "ver")]
private byte[] Version { get; set; }
[field: DataMember(Name = "n")]
public string Nome { get; set; }
[field: DataMember(Name = "im")]
public int IdMercado { get; set; }
}
Even though the instance is created, it will only fill the Id and Version properties. If I remove the Version property, then the remaining properties are filled. In order to get all the properties filled, I had to move Version to a base class:
[DataContract(Namespace = "http://xxx. pt/Mercados", Name = "vb")]
public class Base {
[field: DataMember(Name = "ver")]
private byte[] Version { get; set; }
}
[DataContract(Namespace = "http://xxx.pt/Mercados", Name = "util")]
public class Utilizador:Base {
... //removed Version property
Does anyone know why this is happening?
Thanks.
After some digging, it seems like there's an explanation for this behavior. When you use the DataContractSerializer, the default serialization order inside the type is alphabetical, and if you're using a class hierarchy, the order is top down. If there's a mismatch in the serialization order, the members will be initialized to their default values. That's why everything worked out fine when I introduced the base class (because the service uses a base class for the type being serialized).
So, if you want to flatten the hierarchy on the client side, you'll need to resort to the Order property of the DataMemberAttribute. You can apply the correct order position to each property (so that it mimics what's being serialized on the server) or you can "group" them by giving the same value to the properties that belong to the each level of the hierarchy (and rely on the the default alphabetical order for the properties that have the same order value - which, btw, is base on the Name property, if you're also setting it):
[DataContract(Namespace = "http://xxx.pt/Mercados", Name = "util")]
public class Utilizador {
[field: DataMember(Name = "Id", Order = 1)]
public int Id { get; set; }
[field: DataMember(Name = "ver", Order = 1)]
private byte[] Version { get; set; }
[field: DataMember(Name = "n", Order = 2)]
public string Nome { get; set; }
[field: DataMember(Name = "im", Order = 2)]
public int IdMercado { get; set; }
}
Related
I have a model Blueprint that requires a reference to an IdentityUser. When I run Add-Migration CreateBlueprintSchema the following error is thrown:
No suitable constructor was found for entity type 'Blueprint'. The following constructors had parameters that could not be bound to properties of the entity type: cannot bind 'author' in 'Blueprint(IdentityUser author, string name, string data)'.
How do I resolve this issue?
Blueprint.cs
using Microsoft.AspNetCore.Identity;
using System.ComponentModel.DataAnnotations;
using System.Security.Cryptography;
using System.Text;
namespace FactorioStudio.Models
{
public class Blueprint
{
[MaxLength(40)]
public string Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public string Data { get; set; }
[Required]
public IdentityUser Author { get; set; }
[Required]
public DateTime CreationDate { get; set; }
public Blueprint? ParentBlueprint { get; set; }
public Blueprint(IdentityUser author, string name, string data)
{
Author = author;
Name = name;
Data = data;
CreationDate = DateTime.UtcNow;
string hashSource = Author.Id +
Name +
CreationDate.ToString("s", System.Globalization.CultureInfo.InvariantCulture) +
Data;
using SHA1 sha1 = SHA1.Create();
byte[] hashBytes = sha1.ComputeHash(Encoding.UTF8.GetBytes(hashSource));
string hash = BitConverter.ToString(hashBytes).Replace("-", String.Empty).ToLower();
Id = hash;
}
}
}
change your model like this:
public class Blueprint
{
[MaxLength(40)]
public string Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public string Data { get; set; }
[Required]
public IdentityUser Author { get; set; } = new IdentityUser();
//.......
}
or just add a non-parameter constructor in your model.
You can refer to this;
Edit=======================
For testing convenience, I manually added a foreign key(If I don't add Fk by myself, EF core will create shadow foreign key property, But i can't use it directly), Then I create a Blueprint without referring to an existing IdentityUser, You can see it will report a SqlException that show error in FK.
As I wrote in the comment, EF Core cannot set navigation properties using a constructor. Instead, use a custom value generator to create the hash for the ID when saving the entity.
public class BlueprintHashGenerator : ValueGenerator<string>
{
public override bool GeneratesTemporaryValues => false;
public override string Next(EntityEntry entry)
{
if (entry.Entity is not Blueprint bp)
{
throw new ApplicationException("Unexpected entity");
}
string hashSource = bp.Author.Id +
bp.Name +
bp.CreationDate.ToString("s", System.Globalization.CultureInfo.InvariantCulture) +
bp.Data;
using SHA1 sha1 = SHA1.Create();
byte[] hashBytes = sha1.ComputeHash(Encoding.UTF8.GetBytes(hashSource));
return BitConverter.ToString(hashBytes).Replace("-", String.Empty).ToLower();
}
}
then in model builder
builder.Entity<Blueprint>().Property(bp => bp.Id).HasValueGenerator<BlueprintHashGenerator>().ValueGeneratedNever();
It will generate the value on SaveChanges, so ensure all properties are set before calling save (Author, Name, CreationDate, Data).
I've a situation where entity framework core (2.0) is performing additional work to a parent table when I update a row in a child table. I've pin-pointed the cause to a value not being set in the unflattened object tree produced by AutoMapper (I'm not saying it is an error in AutoMapper; it's probably more to do with my code).
I'm using ASP.NET Core 2.0, C#, EF Core 2.0 and AutoMapper for the API development side. The database already exists and the EF classes scaffolded from it.
To keep it short, the child table is Note and the parent table is NoteType. The EF classes (extraneous columns removed) are as follows :
//Entity classes
public partial class Note
{
public int NoteBookId { get; set; }
public short NoteSeqNo { get; set; }
public short NoteTypeId { get; set; }
public string NoteText { get; set; }
public NoteBook NoteBook { get; set; }
public NoteType NoteType { get; set; }
}
public partial class NoteType
{
public NoteType() { Note = new HashSet<Note>(); }
public short NoteTypeId { get; set; }
public string NoteTypeDesc { get; set; }
public ICollection<Note> Note { get; set; }
}
//DTO class
public class NoteDto
{
public int NoteBookId { get; set; }
public short NoteSeqNo { get; set; }
public short NoteTypeId { get; set; }
public string NoteTypeNoteTypeDesc { get; set; }
public string NoteText { get; set; }
}
public class NoteTypeDto
{
public short NoteTypeId { get; set; }
public string NoteTypeDesc { get; set; }
}
(NoteBookId + NoteSeqNo) is Note's primary key.
NoteTypeId is the NoteType's primary key.
Configuration
This is the AutoMapper configuration:
// config in startup.cs
config.CreateMap<Note,NoteDto>().ReverseMap();
config.CreateMap<NoteType,NoteTypeDto>().ReverseMap();
Read the data
As a result of data retrieval I get the expected result and the parent note type description is populated.
// EF get note in repository
return await _dbcontext.Note
.Where(n => n.NoteId == noteId && n.NoteSeqNo == noteSeqNo)
.Include(n => n.NoteType)
.FirstOrDefaultAsync();
// Get note function in API controller
Note note = await _repository.GetNoteAsync(id, seq);
NoteDto noteDto = Mapper.Map<NoteDto>(note);
Example JSON result:
{
"noteBookId": 29,
"noteSeqNo": 19,
"noteTypeId": 18,
"noteTypenoteTypeDesc": "ABCDE",
"noteText": "My notes here."
}
Update the data
When the process is reversed during an update, the API controller maps the dto to the entity
Mapper.Map<Note>(noteDto)
Then when it is passed to EF by the repository code, EF tries to add a NoteType row with id 0. The unflattened object tree looks like this:
Note
NoteBookId = 29
NoteSeqNo = 19
NoteTypeId = 18
NoteTypeNoteTypeDesc = "ABCDE"
NoteText = "My notes updated."
NoteType.NoteTypeDesc = "ABCDE"
NoteType.NoteTypeId = 0
The parent id column (NoteType.NoteTypeId) value is 0 and is not assigned the value of 18 which is what I expected.
(During debugging I manually set NoteType.NoteTypeId to 18 to ensure EF did nothing with it).
To work around this at the moment I nullify the NoteType in the Note in the repository code.
Should I expected AutoMapper to populate all the parent properties with setters or have I missed some configuration? Perhaps there is a glaring flaw in my approach?
When AutoMapper reverses the mapping, it has to collect all information for nested objects from the flat object. Your DTO only carries a value for the mapping NoteType -> NoteTypeDesc. Not for NoteType -> NoteTypeId, so AM really doesn't have any idea where to get that value from.
If you want to rely on flattening only, the only way to change that is to add a flattened NoteTypeId to the DTO besides the unflattened one:
public class NoteDto
{
public int NoteBookId { get; set; }
public short NoteSeqNo { get; set; }
public short NoteTypeId { get; set; } // Not flattened
public short NoteTypeNoteTypeId { get; set; } // Flattened
public string NoteTypeNoteTypeDesc { get; set; }
public string NoteText { get; set; }
}
The alternative is to add this to your mapping:
config.CreateMap<Note, NoteDto>()
.ForMember(dest => dest.NoteTypeId,
e => e.MapFrom(src => src.NoteType.NoteTypeId))
.ReverseMap();
MapFrom-s (including the default unflattening) are reversed now. You can drop ReverseMap and create the maps, ignore Note.NoteType or ignore the offending path, Note.NoteType.NoteTypeDesc.
I am using Hot towel template and extended functionality of it by using breeze. I have used breeze.partial-entities.js file to conver breeze entities to proper dtos that can be used by knockout observables as shown below.
function dtoToEntityMapper(dto) {
var keyValue = dto[keyName];
var entity = manager.getEntityByKey(entityName, keyValue);
if (!entity) {
// We don't have it, so create it as a partial
extendWith = $.extend({ }, extendWith || defaultExtension);
extendWith[keyName] = keyValue;
entity = manager.createEntity(entityName, extendWith);
}
mapToEntity(entity, dto);
entity.entityAspect.setUnchanged();
return entity;
}
For few of the entities it is working properly and getting breeze data converted to entities but for one of the entity implementation is failing. Model for the same is given as below.
public class StandardResourceProperty
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public int StandardResourceId{ get; set; }
public int InputTypeId{ get; set; }
public int ListGroupId{ get; set; }
public string Format{ get; set; }
public string Calculation{ get; set; }
public bool Required{ get; set; }
public int MinSize{ get; set; }
public int MaxSize{ get; set; }
public string DefaultValue{ get; set; }
public string Comment { get; set; }
public virtual StandardResource AssociatedStandardResource { get; set; }
public virtual List AssociatedList { get; set; }
}
The error i am getting is
TypeError: this[propertyName] is not a function
[Break On This Error]
thispropertyName;
breeze.debug.js (line 13157)
]
with code
proto.setProperty = function(propertyName, value) {
this[propertyName](value);
// allow set property chaining.
return this;
};
Please let me know . What can be possible issue with the implementation also , it would be great if i can get more suggestion on how to debug and trace such issues.
Let's back up. I do not understand what you mean by "convert breeze entities to proper dtos that can be used by knockout observables". Breeze entities are already configured as KO observables (assuming you are using the default Breeze model library configuration). What are you trying to do?
I suspect you are following along with the Code Camper Jumpstart course where it does a getSessionPartials projection query. That query (like all projections) returns DTOs - not entities - and maps them with the dtoToEntityMapper method into Session entities.
The CCJS dtoToEntityMapper method cannot be used with entities. It is for converting from a DTO to an Entity and takes DTOs - not entities - as input.
Goodbye to dtoEntityMapper
The dtoToEntityMapper method pre-dates the ability of Breeze to automate projection-to-entity mapping by adding .toType('StandardResourceProperty') to your projection query.
Here is what the CCJS getSessionPartials query could look like now:
var query = EntityQuery
.from('Sessions')
.select('id, title, code, speakerId, trackId, timeSlotId, roomId, level, tags')
.orderBy(orderBy.session)
.toType('Session');
If you go this way, be sure to set the default state of the isPartial flag to true in the custom constructor (see model.js)
metadataStore.registerEntityTypeCtor(
'Session', function () { this.isPartial = true; }, sessionInitializer);
Note that this.isPartial = true is the reverse of the CCJS example where the default was false.
Make sure that you set isPartial(false) when you query or create a full entity. In CCJS there are two places to do that: in the success-callback of getSessionById AND in createSession which would become:
var createSession = function () {
return manager.createEntity(entityNames.session, {isPartial: false});
};
I have a couple of classes (for now) and I'm trying to clear up a circular reference between the two since it is killing WCF's serialization.
I am using EF with POCOs in a WCF REST service is that helps. I have simplified my problem down to bare bones for an easy example here:
[ServiceContract]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class Groups
{
[WebGet(UriTemplate = "")]
public Message GetCollection()
{
var message = new Message { Body = "Test message" };
var group = new Group { Title = "Title of group" };
message.Group = group;
group.Messages = new List<Message> { message };
return message;
}
}
public class Message
{
public string Body { get; set; }
public Group Group { get; set; }
}
[DataContract(IsReference = true)]
public class Group
{
public string Title { get; set; }
public ICollection<Message> Messages { get; set; }
}
I have added the [DataContract(IsReference = true)] to the Group class so that the circular reference is cleaned up however my returned results end up like this:
<Message xmlns="http://schemas.datacontract.org/2004/07/LmApi" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<Body>Test message</Body>
<Group z:Id="i1" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/"/>
</Message>
Where are the properties of the Group and how can I get them?
BritishDeveloper,
There are no properties associated with Group. That's why all you see is the ID of 1.
The reason is that as soon as you annotate the Group class with [DataContract(IsReference = true)], you are telling the DataContract serializer that it's no longer a POCO type. It's a DataContract type.
So, to serialize Group with properties, you now need to go ahead and annotate the Title and Message properties with DataMemberAttribute.
An alternative would be use the "preserveObjectReferences", which you can pass as a parameter to DataContractSerializer, DataContractSerializerOperationBehavior, and other classes.
Hope this helps!
I decided to make my own smaller classes that have a constructor that takes an entity and sets all of this lighterweight properties correctly.
Basically it is a very small copy of the class that has just the properties needed in the payload. (Obviously I have excluded the problem navigation properties)
This is similar to Circular References and WCF Here is my answer modified for this case
I had the same problem and resolved it by excluding the navigation property back to the parent from the DataContract
[DataContract]
public partial class Message
{
[DataMember]
public virtual string Body { get; set; }
[DataMember]
public virtual Group Group { get; set; }
}
[DataContract]
public partial class Group
{
[DataMember]
public virtual string Title { get; set; }
public virtual ICollection<Message> Messages {get; set;}
}
Problem:
I have a WCF service setup to be an endpoint for a call from an external system. The call is sending plain xml. I am testing the system by sending calls into the service from Fiddler using the RequestBuilder.
The issue is that all of my fields are being deserialized with the exception of two fields. price_retail and price_wholesale.
What am I missing? All of the other fields deserialize without an issue - the service responds. It is just these fields.
XML Message:
<widget_conclusion>
<list_criteria_id>123</list_criteria_id>
<list_type>consumer</list_type>
<qty>500</qty>
<price_retail>50.00</price_retail>
<price_wholesale>40.00</price_wholesale>
<session_id>123456789</session_id>
</widget_conclusion>
Service Method:
public string WidgetConclusion(ConclusionMessage message)
{
var priceRetail = message.PriceRetail;
}
Message class:
[DataContract(Name = "widget_conclusion", Namespace = "")]
public class ConclusionMessage
{
[DataMember(Name = "list_criteria_id")]
public int CriteriaId { get; set;}
[DataMember(Name = "list_type")]
public string ListType { get; set; }
[DataMember(Name = "qty")]
public int ListQuantity { get; set; }
[DataMember(Name = "price_retail")]
public decimal PriceRetail { get; set; }
[DataMember(Name = "price_wholesale")]
public decimal PriceWholesale { get; set; }
[DataMember(Name = "session_id")]
public string SessionId { get; set; }
}
Fields are in the wrong order for your message. DataContracts default to Alphabetical ordering and not order of declaration; and expects XML elements to arrive in that order; Out of order elements are discarded usually.
Either fix your contract to specify the right order explicitly (using the Order property of the DataMemberAttribute) or make sure your client sends them in the right one.
You can try to use XmlSerializer instead of DataContractSerializer.
In my case, I need to change default engine in global.asax file:
var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
xml.UseXmlSerializer = true;
Do this carefully because some XML can become not valid, for example - namespaces, with XmlSerializer should be determined like:
[XmlNamespaceDeclarations]
private XmlSerializerNamespaces xmlns
{
get {
var xns = new XmlSerializerNamespaces();
xns.Add("i", "http://www.w3.org/2001/XMLSchema-instance");
return xns;
}
set { }
}
Or u can set XmlSerializerFormatAtrribute to You class (not work for me).
Look in url head "Manually Switching to the XmlSerializer"