C# Mongo serialization issue [duplicate] - c#

I've the following mongodb document schema;
{
"_id" : ObjectId("5c9d34ff781318afb9e8ab43"),
"name" : "Name",
"slug" : "slug",
"services" : {
"subservice" : {
"id" : NumberInt(37030)
}
}
}
and then i define my classes as;
public class MainModel
{
public ObjectId Id { get; set; }
[BsonElement("name")]
public string Name { get; set; }
[BsonElement("slug")]
public string Slug { get; set; }
[BsonElement("services")]
public ServicesDef Services { get; set; }
public class ServicesDef
{
[BsonElement("subservice")]
public SubServiceDef SubService{ get; set; }
public class SubServiceDef
{
[BsonElement("id")]
public int Id { get; set; }
}
}
}
But somehow when I query the document;
var result = await Repository.FindAsync(x => x.Slug == slug);
That services.subservice.id isn't properly registered and getting
Element 'id' does not match any field or property of class SubServiceDef.
Stuck here and looking for advice.
I think I'm having the same issue with cannot deserialize with the "Id" attribute but seems there is solution yet.

Long story short: it's all about conventions. MongoDB .NET driver exposes static class ConventionRegistry which allows you to register your own conventions (more here). Additionally there are two "built-in" conventions __defaults__ and __attributes__. Digging deeper (driver github) you can find that it registers one quite interesting convention:
new NamedIdMemberConvention(new [] { "Id", "id", "_id" })
Which means that id members will be considered as regular BSON _id elements.
How to fix that ?
You can get rid of default conventions
ConventionRegistry.Remove("__defaults__");
However automatically you will drop all the other driver conventions which is pretty risky. Alternatively you can create a fake property which will always be empty:
public class SubServiceDef
{
[BsonElement("id")]
public int Id { get; set; }
[BsonId]
public ObjectId FakeId { get; set; }
}
or you can just use BsonNoId attribute which
Specifies that the class's IdMember should be null.
[BsonNoId]
public class SubServiceDef
{
[BsonElement("id")]
public int Id { get; set; }
}
So the convention will be setting your id as IdMember in class map but then during postprocessing this attribute will force IdMember to be null and your class will get deserialized succesfully

I like the answer from #mickl. The issue i had is couldn't update model and add attributes. Also I needed the original Ids and not nulls after deserialization.
I tried BsonClassMap but i had so many sub models to update.
so, i ended up using your idea with removing default conventions.
public class MongoDbDefaultConventionPack : IConventionPack
{
// private static fields
private static readonly IConventionPack __defaultConventionPack = new MongoDbDefaultConventionPack();
// private fields
private readonly IEnumerable<IConvention> _conventions;
// constructors
/// <summary>
/// Initializes a new instance of the <see cref="MongoDbDefaultConventionPack" /> class.
/// </summary>
private MongoDbDefaultConventionPack()
{
_conventions = new List<IConvention>
{
new ReadWriteMemberFinderConvention(),
// new NamedIdMemberConvention(new [] { "Id", "id", "_id" }), changed to:
new NamedIdMemberConvention(),
new NamedExtraElementsMemberConvention(new [] { "ExtraElements" }),
// new IgnoreExtraElementsConvention(false), changed to:
new IgnoreExtraElementsConvention(true),
new ImmutableTypeClassMapConvention(),
new NamedParameterCreatorMapConvention(),
new StringObjectIdIdGeneratorConvention(), // should be before LookupIdGeneratorConvention
new LookupIdGeneratorConvention()
};
}
// public static properties
/// <summary>
/// Gets the instance.
/// </summary>
public static IConventionPack Instance
{
get { return __defaultConventionPack; }
}
// public properties
/// <summary>
/// Gets the conventions.
/// </summary>
public IEnumerable<IConvention> Conventions
{
get { return _conventions; }
}
}
and then replaced the config:
ConventionRegistry.Remove("__defaults__");
ConventionRegistry.Register("__defaults__", MongoDbDefaultConventionPack.Instance, t => true);
Worked great in my case as default convention. No more exceptions. Original Ids available

Related

Add default value to Swagger path parameters

I have made a WebAPI in .NET CORE 6.
I have a controller class like this:
[ApiController]
[Route("{culture:culture}/[controller]")]
[SwaggerDefaultValue("culture", "en-US")]
[Produces("application/json")]
public class AccountsController : BaseController
{
...
}
As you can see I defined a parameter in the route like this {culture:culture}.
I would like to have a default value for this parameter in my Swagger.
I defined an attribute class like this:
[AttributeUsage(AttributeTargets.Class )]
public class SwaggerDefaultValueAttribute:Attribute
{
public string Name { get; set; }
public string Value { get; set; }
public SwaggerDefaultValueAttribute(string name, string value)
{
Name = name;
Value = value;
}
}
And a filter class like this:
public class SwaggerDefaultValueFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
// needs some code
}
}
And added the filter to my Swagger service too.
options.OperationFilter<SwaggerDefaultValueFilter>();
However, the problem is most of the code samples that I found are related to the old versions of Swagger and most of their methods are deprecated (like this one).
The question is, how can I modify this SwaggerDefaultValueFilter class to show a default value in my path parameter:
FYI: I am using <PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="6.2.3" />.
I found this sample too, however, it does not set the default values of the path parameters, it seems it works for model attributes.
To assign the default value for the parameter, you can do something like this,
internal static class OperationFilterContextExtensions
{
public static IEnumerable<T> GetControllerAndActionAttributes<T>(this OperationFilterContext context) where T : Attribute
{
var controllerAttributes = context.MethodInfo.DeclaringType.GetTypeInfo().GetCustomAttributes<T>();
var actionAttributes = context.MethodInfo.GetCustomAttributes<T>();
var result = new List<T>(controllerAttributes);
result.AddRange(actionAttributes);
return result;
}
}
public class SwaggerDefaultValueFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
var defaultAttributes = context.GetControllerAndActionAttributes<SwaggerDefaultValueAttribute>();
if (defaultAttributes.Any())
{
foreach (var defaultAttribute in defaultAttributes)
{
var parameter = operation.Parameters.FirstOrDefault(d => d.Name == defaultAttribute.Name);
if (parameter != null)
{
version.Schema.Default = new OpenApiString(defaultAttribute.Value);
}
}
}
}
}
Not Swashbuckle.AspNetCore but this might send you on the right path:
Controller:
https://github.com/heldersepu/Swagger-Net-Test/blob/master/Swagger_Test/Controllers/CompanyController.cs#L35-L39
[Route("Get2")]
public Company Get2([FromUri] Company c)
{
return c;
}
Model:
https://github.com/heldersepu/Swagger-Net-Test/blob/master/Swagger_Test/Models/Company.cs
public class Company
{
/// <summary>The Unique Company ID</summary>
/// <example>123</example>
[Required]
[DefaultValue(456)]
public int Id { get; set; }
[Required]
[DefaultValue(null)]
public int? MyId { get; set; }
/// <summary>The Company Name</summary>
/// <example>Acme co</example>
[DefaultValue("My Company")]
public string Name { get; set; }
Live code:
http://swagger-net-test.azurewebsites.net/swagger/ui/index?filter=Company#/Company/Company_Get2

WebApi returns empty JSON instead of serialized object

So simple yet I know not why it fails. In a WebAPI 2.0 ASP.NET MVC (pre-core) controller method, I have this:
[Route("GetItem")]
[HttpGet]
public ItemVM GetItem() {
var item = new ItemVM(); // Constructor initializes
return item;
}
When I run the code, the debugger shows this in item:
item {ViewModels.ItemVMs.ItemVM}
firstItem {ViewModels.ItemVMs.FirstItemVM}
id 0
archived false
name null
Yet WebAPI returns only this:
{}
I have tried suggestions like Newtonsoft json serializer returns empty object but Visual Studio 2017 says CreateProperties does not exist to override.
Any help would be much appreciated.
EDIT:
LOL I told you it is a simple answer. Here's the class:
public class ItemVM {
FirstItemVM firstItem { get; set; }
public ItemVM() {
this.firstItem = FirstItemVM.ToMap( new Entities.Item());
}
}
public class FirstItemVM {
public int id { get; set; }
public bool archived { get; set; }
public string name { get; set; }
public static readonly Expression<Func<Entities.FirstItem, FirstItemVM>>
Map (e) => new FirstItemVM {
id = e.id,
archived = e.archived,
name = e.name
};
public static readonly Func<Entities.FirstItem, FirstItemVM>
ToMap = FirstItemVM.Map.Compile();
}
Most likely ItemVM.firstItem is not public (for example, internal), and JSON serializer will only serialize public properties by default (unless you non-public property explicitly to be serialized).

AutoMapper - Map Derived Class To Dto

Im trying to map a Class which inherits from a base class to a dto.
public class LaunchConfiguration : Document
{
public string Brand { get; set; }
public string SettingName{ get; set; }
}
public class LaunchConfigurationDto
{
public string Brand { get; set; }
public string SettingName{ get; set; }
}
The point of the dto is to hide the fields of the base document when it gets returned to the user. This is my Map configuration
public class DtoProfile : Profile
{
public DtoProfile()
{
CreateMap<LaunchConfiguration,LaunchConfigurationDto>();
}
};
The problem im having is that auto mapper complains about the base class properties which are not mapped . "Unmapped members were found." The properties are the ones on the base class. I have tried specifying this to be ignored in the profile to no avail . Can anyone specify the correct way to do this ?
My ConfigureServices Method incase anyone is wondering :
public void ConfigureServices(IServiceCollection services)
{
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info { Title = Configuration["ApiInformation:Name"], Version = Configuration["ApiInformation:Version"] });
c.DescribeAllEnumsAsStrings();
});
services.AddAutoMapper(mc =>
{
mc.AddProfile(new DtoProfile());
});
services.AddMvc().AddJsonOptions(options =>
{
options.SerializerSettings.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter());
options.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore;
});
}
My Base Class :
public class Document : IDocument, IDocument<Guid>
{
public Document()
{
this.Id = Guid.NewGuid();
this.AddedAtUtc = DateTime.UtcNow;
}
/// <summary>The Id of the document</summary>
[BsonId]
public Guid Id { get; set; }
/// <summary>The datetime in UTC at which the document was added.</summary>
public DateTime AddedAtUtc { get; set; }
/// <summary>The version of the schema of the document</summary>
public int Version { get; set; }
}
My implementation where _mapper is my Injected mapper and _repo My Injected Repo. Exception Occurs on Map Method call
Task ILaunchConfigurationService<LaunchConfigurationDto >.InsertLaunchConfiguration(LaunchConfigurationDto model)
{
var mapped = _mapper.Map<LaunchConfiguration >(model);
return _repo.AddOneAsync(mapped);
}
Your problem should be solved by simply adding ReverseMap() to CreateMap call:
public class DtoProfile : Profile
{
public DtoProfile()
{
CreateMap<LaunchConfiguration, LaunchConfigurationDto>().ReverseMap();
}
};
Automapper creates one way map by default. ReverseMap is just a sugar for creating reverse map in case there are no peculiar mappings in one way. You could also do it like this:
public class DtoProfile : Profile
{
public DtoProfile()
{
CreateMap<LaunchConfiguration, LaunchConfigurationDto>();
CreateMap<LaunchConfigurationDto, LaunchConfiguration>();
}
};
You can read more about this in documentation
However I cannot guarantee you that you will not experience exceptions from database with your current implementation on commiting changes.

Is it possible to have extra (ignored) properties in C#?

I have a repository for a DocumentDb database. My documents all have a set of common properties so all documents implement the IDocumentEntity interface.
public interface IDocumentEntity {
[JsonProperty("id")]
Guid Id { get; set; }
[JsonProperty("documentClassification")]
DocumentClassification DocumentClassification { get; set; }
}
public class KnownDocument : IDocumentEntity {
[JsonProperty("id")]
Guid Id { get; set; }
[JsonProperty("documentClassification")]
DocumentClassification DocumentClassification { get; set; }
[JsonProperty("knownProperty")]
string KnownProperty { get; set; }
}
public class BaseDocumentRepository<T> where T : IDocumentEntity {
public Set(T entity) {
// ... stuff
}
}
This works fine with a KnownDocument where I know all of the properties. But, of course, what's great about a Document Db is that I don't need to know all of the properties (and in many cases I won't).
So my client submits something like this-
{unknownProperty1: 1, unknownProperty2: 2}
And I want to upsert this using my document repository.
public OtherDocumentService() {
_otherDocumentService = new OtherDocumentRepository();
}
public UpsertDocument(dynamic entity) {
entity.id = new Guid();
entity.documentClassification = DocumentClassification.Other;
_otherDocumentRepository.Set(entity);
}
But I get an InvalidCastException from dynamic to IDocumentEntity. I assume it's because of the extra properties that exist on the dynamic object but not on the IDocumentEntity interface?
What I'm trying to do is leave my document entities open to be dynamic, but rely on a few properties being there to maintain them.
Entity parameter passed to the UpsertDocument should explicitly implement IDocumentEntity in order do make the code works, it is not enough just have a Id property.
Some options:
1) Proxy may be applied:
public class ProxyDocumentEntity : IDocumentEntity
{
public dynamic Content { get; private set; }
public ProxyDocumentEntity(dynamic #content)
{
Content = #content;
}
public Guid Id
{
get { return Content.Id; }
set { Content.Id = value; }
}
}
... using
public void UpsertDocument(dynamic entity)
{
entity.Id = new Guid();
repo.Set(new ProxyDocumentEntity(entity));
}
The stored document will have nested Object property, which may be not acceptable
2)There is a lib https://github.com/ekonbenefits/impromptu-interface which creates a proxy dynamically
and does not make extra property like solution above.
Drawback will be in performance.
Technically it could be 2 methods:
public void UpsertDocument(IDocumentEntity entity){...}
public void UpsertDocument(dynamic entity){...}
so the first (fast) will work for the objects which implement IDocumentEntity and second(slow) for the rest of the objects.
But this is a speculation a bit , as I dunno the details of the whole code base of the project you have.
If you have some flexibility as to how to name those dynamic properties, you could stuff them into a Dictionary property on your object:
public Dictionary<string, dynamic> extra { get; set; }

Entity Framework 6 won't update record unless I breakpoint my code

Edit: There's no issue here and I don't know whether or not to delete the question... As per this comment, the whole thing was my bad. In the meantime, below are some nice drawings of my database and EntityFramework configuration. And some database tables.
I have the following two tables in my database (among others, these are the relevant ones):
It's possible that the GroupId field in SignalMetaData is null until the GroupMetaData entry is received by my application. With that said, here is my code to update a SignalMetaData record to provide its GroupId field once it's been received. this._entities is my EF object.
/// <summary>
/// Maps a signal to its group in the database
/// </summary>
/// <param name="signalId">The SignalId</param>
/// <param name="groupId">The identifier of the group that SignalId belongs to.</param>
/// <returns></returns>
public bool InsertSignalGroupMapping(Guid signalId, Guid groupId)
{
try
{
var sig = this._entities.SignalMetaDatas
.SingleOrDefault(signal => signal.SignalId == signalId);
if (sig != null)
{
sig.GroupId = groupId;
this._entities.SaveChanges();
}
}
catch (Exception ex)
{
this._logger.Log(LogSeverity.Error, ex);
return false;
}
return true;
}
The this._entities.SaveChanges(); call doesn't seem to do anything when I test my application. I query the database and the record is not updated. It's like the method was never called. If I breakpoint the line with sig.GroupId = groupId; and then step through the SaveChanges() call, it has no problem. The record is updated and everything works fine.
I've tried adding this line between the assignment of GroupId and the SaveChanges() call:
this._entities.Entry(sig).State = EntityState.Modified;
with the same outcome. I've also tried using this call to update the record:
this._entities.SignalMetaDatas.AddOrUpdate(sig);
to no avail. I've added Console writes in the method and gone through without breakpoints and the Console writes appear. The method is being called but it's just doing nothing unless I breakpoint and step through manually.
One other thing I should mention: both signalId and groupId passed to the InsertSignalGroupMapping method are not null or empty guids, verified with Console.Writeline.
I've also tried assigning the GroupMetaData navigation property on the sig object to a fully qualified group object containing the ID passed to InsertSignalGroupMapping with the same outcome.
Per request, here is the model classes for the two objects.
using System;
using System.Collections.Generic;
public partial class GroupMetaData
{
public GroupMetaData()
{
this.GroupHierarchiesParentId = new HashSet<GroupHierarchy>();
this.GroupHierarchiesGroupId = new HashSet<GroupHierarchy>();
this.SignalMetaDatas = new HashSet<SignalMetaData>();
}
public System.Guid GroupId { get; set; }
public string GroupName { get; set; }
public virtual ICollection<GroupHierarchy> GroupHierarchiesParentId { get; set; }
public virtual ICollection<GroupHierarchy> GroupHierarchiesGroupId { get; set; }
public virtual ICollection<SignalMetaData> SignalMetaDatas { get; set; }
}
and
using System;
using System.Collections.Generic;
public partial class SignalMetaData
{
public SignalMetaData()
{
this.SignalHierarchiesSignalId = new HashSet<SignalHierarchy>();
this.SignalHierarchiesParentId = new HashSet<SignalHierarchy>();
this.SignalValues = new HashSet<SignalValue>();
}
public System.Guid SignalId { get; set; }
public string SignalName { get; set; }
public Nullable<System.Guid> GroupId { get; set; }
public virtual GroupMetaData GroupMetaData { get; set; }
public virtual ICollection<SignalHierarchy> SignalHierarchiesSignalId { get; set; }
public virtual ICollection<SignalHierarchy> SignalHierarchiesParentId { get; set; }
public virtual ICollection<SignalValue> SignalValues { get; set; }
}
and the EF Config:
It's possible that you have a watch or something else in your debugging environment that is manipulating the context in a way that causes it to "work".
The standard method is to set the navigation property instead of the ID. Something like:
var sig = this._entities.SignalMetaDatas
.SingleOrDefault(signal => signal.SignalId == signalId);
var group = this._entities.Groups
.SingleOrDefault(g => g.groupId == groupId);
if (sig != null)
{
sig.Group = group;
this._entities.SaveChanges();
}

Categories