I have a collection of BsonDocuments, for example:
MongoCollection<BsonDocument> products;
When I do inserts into the collection, I want the member name to always be lowercase. After reading the documentation, it appears that ConventionPack is the way to go. So, I've defined one like this:
public class LowerCaseElementNameConvention : IMemberMapConvention
{
public void Apply(BsonMemberMap memberMap)
{
memberMap.SetElementName(memberMap.MemberName.ToLower());
}
public string Name
{
get { throw new NotImplementedException(); }
}
}
And right after I get my collection instance I register the convention like this:
var pack = new ConventionPack();
pack.Add(new LowerCaseElementNameConvention());
ConventionRegistry.Register(
"Product Catalog Conventions",
pack,
t => true);
Unfortunately, this has zero effect on what is stored in my collection. I debugged it and found that the Apply method is never called.
What do I need to do differently to get my convention to work?
In order to use IMemeberMapConvention, you must make sure to declare your conventions before the mapping process takes place. Or optionally drop existing mappings and create new ones.
For example, the following is the correct order to apply a convention:
// first: create the conventions
var myConventions = new ConventionPack();
myConventions.Add(new FooConvention());
ConventionRegistry.Register(
"My Custom Conventions",
myConventions,
t => true);
// only then apply the mapping
BsonClassMap.RegisterClassMap<Foo>(cm =>
{
cm.AutoMap();
});
// finally save
collection.RemoveAll();
collection.InsertBatch(new Foo[]
{
new Foo() {Text = "Hello world!"},
new Foo() {Text = "Hello world!"},
new Foo() {Text = "Hello world!"},
});
Here's how this sample convention was defined:
public class FooConvention : IMemberMapConvention
private string _name = "FooConvention";
#region Implementation of IConvention
public string Name
{
get { return _name; }
private set { _name = value; }
}
public void Apply(BsonMemberMap memberMap)
{
if (memberMap.MemberName == "Text")
{
memberMap.SetElementName("NotText");
}
}
#endregion
}
These are the results that came out when I ran this sample. You could see the Text property ended up being saved as "NotText":
If I understand correctly, conventions are only applied when auto-mapping. If you have a classmap, you need to explicitly call AutoMap() to use conventions. Then you can modify the automapping, e.g.:
public class MyThingyMap : BsonClassMap<MyThingy>
{
public MyThingyMap()
{
// Use conventions to auto-map
AutoMap();
// Customize automapping for specific cases
GetMemberMap(x => x.SomeProperty).SetElementName("sp");
UnmapMember(x => x.SomePropertyToIgnore);
}
}
If you don't include a class map, I think the default is to just use automapping, in which case your convention should apply. Make sure you're registering the convention before calling GetCollection<T>.
You can define ConventionPack which is also part of their official document on Serialization. Like below which stores are property names as camel case. You can place while Configuring services/repositories
Official link
https://mongodb.github.io/mongo-csharp-driver/1.11/serialization/[Mongo Db Serialization C#]1
// For MongoDb Conventions
var pack = new ConventionPack
{
new CamelCaseElementNameConvention()
};
ConventionRegistry.Register(nameof(CamelCaseElementNameConvention), pack, _ => true);
Related
I am currently trying to set up a Non-Entity Framework environment to access data via REST/JSON:API in ASP.NET Core 3.1 with https://github.com/json-api-dotnet/JsonApiDotNetCore
I followed the example as shown in: https://github.com/json-api-dotnet/JsonApiDotNetCore/blob/master/src/Examples/NoEntityFrameworkExample/Services/WorkItemService.cs
So here is my sample method:
public class DepartmentResourceService : IResourceService<Department>
{
public Task<IReadOnlyCollection<Department>> GetAsync(CancellationToken cancellationToken)
{
IReadOnlyCollection<Department> departments = new List<Department>{
new Department{ Id = 1, Name = "SE", Contact = "se#someaddress.at" },
new Department{ Id = 2, Name = "SD", Contact = "sd#someaddress.at" }
}.AsReadOnly();
return Task.FromResult(departments);
}
...
}
My example works pretty well, but I haven't figured out how I can access the given JSON:API fields-filter.
However: The filters do apply automatically somehow and only the given fields are sent which were defined in the query string, but the filter is applied after the object was generated.
When using EF, I can see that the sql-queries are already limited to the defined JSON:API fields-filter list, therefore the object is only filled up with informationen that was requested and nothing else.
I would like to do the same without EF, but I am missing that filter information in order to do so.
I could figure out, that there is an Interface called ITargetedFields (https://github.com/json-api-dotnet/JsonApiDotNetCore/blob/master/src/JsonApiDotNetCore/Resources/TargetedFields.cs/) and I thought maybe this could be injected into the constructor like so:
public class DepartmentResourceService : IResourceService<Department>
{
private readonly ITargetedFields targetedFields;
public DepartmentResourceService(ITargetedFields targetedFields)
{
this.targetedFields = targetedFields;
}
...
}
But the properties Attributes and Relationships Collections of ITargetedFields are always zero-length.
I couldn't find something in the docs or examples.
Any ideas?
I finally found a way, how to access query information, such as fields, pagination, includes in IResourceService<TResource>:
There are several IQueryConstraintProvider service providers registered in JsonApiApplicationBuilder in the method AddQueryStringLayer() with a Scope-Lifetime:
IIncludeQueryStringParameterReader
IFilterQueryStringParameterReader
ISortQueryStringParameterReader
ISparseFieldSetQueryStringParameterReader
IPaginationQueryStringParameterReader
IResourceDefinitionQueryableParameterReader
They are injected by DI in the constructor of your IResourceService<TResource> implementation, here is an example:
public class DepartmentResourceService : IResourceService<Department>
{
private readonly ISparseFieldSetQueryStringParameterReader _sparseFieldSetQueryStringParameterReader;
private readonly IIncludeQueryStringParameterReader _includeQueryStringParameterReader;
private readonly ISortQueryStringParameterReader _sortQueryStringParameterReader;
private readonly IPaginationQueryStringParameterReader _paginationQueryStringParameterReader;
public DepartmentResourceService(
ISparseFieldSetQueryStringParameterReader sparseFieldSetQueryStringParameterReader,
IIncludeQueryStringParameterReader includeQueryStringParameterReader,
ISortQueryStringParameterReader sortQueryStringParameterReader,
IPaginationQueryStringParameterReader paginationQueryStringParameterReader
)
{
_sparseFieldSetQueryStringParameterReader = sparseFieldSetQueryStringParameterReader;
_includeQueryStringParameterReader = includeQueryStringParameterReader;
_sortQueryStringParameterReader = sortQueryStringParameterReader;
_paginationQueryStringParameterReader = paginationQueryStringParameterReader;
}
public Task<IReadOnlyCollection<Department>> GetAsync(CancellationToken cancellationToken)
{
// Accessing all provided information:
IReadOnlyCollection<ExpressionInScope> constraints = _sparseFieldSetQueryStringParameterReader.GetConstraints();
IReadOnlyCollection<ExpressionInScope> includes = _includeQueryStringParameterReader.GetConstraints();
IReadOnlyCollection<ExpressionInScope> sortQuery = _sortQueryStringParameterReader.GetConstraints();
IReadOnlyCollection<ExpressionInScope> pagination = _paginationQueryStringParameterReader.GetConstraints();
// ***************************************************************
// Do what ever you need to do, with the information provided here
// ***************************************************************
// Return something
IReadOnlyCollection<Department> departments = new List<Department>{
new Department{ Id = 1, Name = "SE", Contact = "se#someaddress.at" },
new Department{ Id = 2, Name = "SD", Contact = "sd#someaddress.at" }
}.AsReadOnly();
return Task.FromResult(departments);
}
...
}
I am trying to load bindings in a class for a project. I am using 3rd party extensions for Caching and the class I need to load looks like below using c# and .net framework 472 .
public class CouchbaseCache : ICouchbaseCache, IDistributedCache
{
public CouchbaseCache(ICouchbaseCacheBucketProvider provider, IOptions<CouchbaseCacheOptions> options);
public IBucket Bucket { get; }
public CouchbaseCacheOptions Options { get; }
}
usually, If I have to load, I would use something like
Bind().To().InSingletonScope();
But how would I do it for the above class by giving the bucket info and Options as values while loading it? I could not get my head around it.
Also, ICouchbaseCachebucketProvider is an interface derived from INamedbucketProvider and derived class looks like
public interface INamedBucketProvider
{
string BucketName { get; }
IBucket GetBucket();
}
So far, I was able to get CouchbaseClientDefinition set up like this
Bind<ICouchbaseClientDefinition>().ToMethod(ctx =>
{
var options = new CouchbaseClientDefinition
{
Servers = new List<Uri>
{
new Uri("http://couchbase.com/")
}
};
return options;
}).InSingletonScope();
I need to give Uri for couchbase and also bucket name and the logic is all over the place. Any knowledge sharing will be greatly appreciated.
if the argument for the constructor of the CouchbaseCache is identical for the whole application life time then you can bind it with the use of binding with constructor arguments something like this where you are loading:
var options = new CouchbaseClientDefinition
{
Servers = new List<Uri>
{
new Uri("http://couchbase.com/")
}
};
var couchbaseCacheBucketProvider= new CouchbaseCacheBucketProvider
{
...
};
Bind<ICouchbaseClientDefinition().To<CouchbaseCache >()
.WithConstructorArgument(couchbaseCacheBucketProvider, options);
but you have to provide the couchbaseCacheBucketProvider.
if the arguments are different but they are limited for example if you have two version of the arguments you can use the named binding like this
var options1 = new CouchbaseClientDefinition
{
...
};
var options2 = new CouchbaseClientDefinition
{
...
};
var couchbaseCacheBucketProvider1= new CouchbaseCacheBucketProvider
{
...
};
var couchbaseCacheBucketProvider2= new CouchbaseCacheBucketProvider
{
...
};
Bind < ICouchbaseClientDefinition().To<CouchbaseCache>().WithConstructorArgument(couchbaseCacheBucketProvider, options1).Named("FirstBinding");
Bind < ICouchbaseClientDefinition().To<CouchbaseCache>().WithConstructorArgument(couchbaseCacheBucketProvider, options2).Named("SecondBinding");
However another Alternative is to use the FactoryPattern/Singleton to create your CouchbaseCache object. Then you need just to inject the Factory Class that you created and you can use the Factory Class to get the required CouchbaseCache object whenever it is required.
Note: I'm using the MongoDB C# Driver 2.0
I would like to replicate the behaviour of the BsonConstructor attribute but using the BsonClassMap API.
Something like this:
BsonClassMap.RegisterClassMap<Person>(cm =>
{
cm.AutoMap();
cm.MapCreator(p => new Person(p.FirstName, p.LastName));
});
but without having to specify each argument.
The reason I want to do it this way is that I don't want to "pollute" my domain model with implementation concerns.
I have found this (SetCreator)
BsonClassMap.RegisterClassMap<Person>(cm =>
{
cm.AutoMap();
cm.SetCreator(what goes here?);
});
but I don't know how to use the SetCreator function, and if it does what I think it does...
I achived the same result using the conventions instead of BsonClassMap
Here is an example (reading (serialization) from read only public properties and writing (deserialization) to the constructor)
public class MongoMappingConvention : IClassMapConvention
{
public string Name
{
get { return "No use for a name"; }
}
public void Apply(BsonClassMap classMap)
{
var nonPublicCtors = classMap.ClassType.GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance);
var longestCtor = nonPublicCtors.OrderByDescending(ctor => ctor.GetParameters().Length).FirstOrDefault();
classMap.MapConstructor(longestCtor);
var publicProperties = classMap.ClassType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => p.CanRead);
foreach (var publicProperty in publicProperties)
{
classMap.MapMember(publicProperty);
}
}
}
Old question, I know, but something like this worked for me with MongoDB driver 2.10.4:
var mapper = new BsonClassMap(type);
mapper.AutoMap();
var constructorInfo = type.GetConstructor(...); // Find the constructor you want to use
mapper.MapConstructor(constructorInfo, new[] {"FirstName", "LastName"});
Note that the array passed to MapConstructor has the property names, not the constructor argument names. As I understand, it goes by the order of constructor arguments, but they may have different names, e.g.
public Person(string givenName, string surname)
{
FirstName = givenName;
LastName = surname;
}
I have a feeling this is possible but I can't seem to find it. I'd like to configure my mongo driver to make any DateTime object stored as a BsonDocument.
The mongo c# driver lets you set certain conventions globally so you don't need to annotate everything, is this also possible for date time options?
For example, I'd like to remove the following annotation:
[BsonDateTimeOptions(Representation = BsonType.Document)]
From all of my DateTime properties. Can anyone point me in the right direction?
When I tried to verify that the answer provided by devshorts worked I got a compile time error (because the Add method of ConventionPack, which is being called by the collection initializer syntax, expects an IConvention).
The suggested solution was almost right, and only a slight modification was needed:
ConventionRegistry.Register(
"dates as documents",
new ConventionPack
{
new DelegateMemberMapConvention("dates as documents", memberMap =>
{
if (memberMap .MemberType == typeof(DateTime))
{
memberMap .SetSerializationOptions(new DateTimeSerializationOptions(DateTimeKind.Utc, BsonType.Document));
}
}),
},
t => true);
If we needed to use this convention in more than one place we could package it up in a class, as so:
public class DateTimeSerializationOptionsConvention : ConventionBase, IMemberMapConvention
{
private readonly DateTimeKind _kind;
private readonly BsonType _representation;
public DateTimeSerializationOptionsConvention(DateTimeKind kind, BsonType representation)
{
_kind = kind;
_representation = representation;
}
public void Apply(BsonMemberMap memberMap)
{
if (memberMap.MemberType == typeof(DateTime))
{
memberMap.SetSerializationOptions(new DateTimeSerializationOptions(_kind, _representation));
}
}
}
And then use it like this:
ConventionRegistry.Register(
"dates as documents",
new ConventionPack
{
new DateTimeSerializationOptionsConvention(DateTimeKind.Utc, BsonType.Document)
},
t => true);
Long overdue, but the answer is to use a convention pack and set
ConventionRegistry.Register(
"Dates as utc documents",
new ConventionPack
{
new MemberSerializationOptionsConvention(typeof(DateTime), new DateTimeSerializationOptions(DateTimeKind.Utc, BsonType.Document)),
},
t => true);
Given a validator class that looks like this
public class SomeValidator : AbstractValidator<SomeObject>
{
public SomeValidator(){
RuleSet("First",
() => {
RuleFor(so => so.SomeMember).SetValidator(new SomeMemberValidator())
});
RuleSet("Second",
() => ... Code Does Not Matter ... );
RuleSet("Third",
() => ... Code Does Not Matter ... );
}
}
And another to do the inner member validation
public class SomeMemberValidator: AbstractValidator<SomeMember>
{
public SomeValidator(){
RuleSet("Fourth",
() => {
... Code Does Not Matter ...
});
}
}
Question is, I want to run specific rulesets: "First", "Second", and "Fourth". I don't want "Third" to run.
Given the Validate method signature only takes a single ruleset argument I don't see any way to do this. There is "*", but I don't want to run all the rules.
Please help.
You could use validator constructor instead of RuleSet as a workaround for this problem.
Just create enum inside of validator class and then use its value when creating validator.
I this way correct rules will be activated depending on what Mode is set in constructor.
public class UserValidator : AbstractValidator<User>
{
public enum Mode
{
Create,
Edit
}
public UserValidator()
{
// Default rules...
}
public UserValidator(UserValidator.Mode mode)
: this()
{
if (mode == Mode.Edit)
{
// Rules for Edit...
RuleFor(so => so.SomeMember)
.SetValidator(
new SomeMemberValidator(SomeMemberValidator.Mode.SomeMode))
}
if (mode == Mode.Create)
{
// Rules for Create...
RuleFor(so => so.SomeMember)
.SetValidator(
new SomeMemberValidator())
}
}
}
I think it's actually more flexible method than using RuleSet.
There is only one small problem regarding FluentValidation MVC integration:
User class can't have attribute [Validator(typeof(UserValidator))] because UserValidator will be then created using default constructor, before you can do anything in controller method.
Validator must be created and called manually. Like that for example:
public class UserController : Controller
{
[HttpPost]
public ActionResult Create(User userData)
{
var validator = new UserValidator(UserValidator.Mode.Create);
if (ValidateWrapper(validator, userData, this.ModelState))
{
// Put userData in database...
}
else
{
// ValidateWrapper added errors from UserValidator to ModelState.
return View();
}
}
private static bool ValidateWrapper<T>(FluentValidation.AbstractValidator<T> validator, T data, ModelStateDictionary modelState)
{
var validationResult = validator.Validate(data);
if (!validationResult.IsValid)
{
foreach (var error in validationResult.Errors)
modelState.AddModelError(error.PropertyName, error.ErrorMessage);
return false;
}
return true;
}
}
You can execute more than one RuleSet, but I don't think you can execute the inner RuleSet.
validator.Validate(new ValidationContext<SomeObject>(person, new PropertyChain(), new RulesetValidatorSelector("First", "Second", "Fourth")));
Source
The other option is to investigate the source code and try to think a way of doing it. The third parameter of the ValidationContext is an interface, IValidatorSelector, maybe you can have some luck with a custom class.