I'm trying to get an auto decompile configuration for DelegateDecompiler to work, as shown here: http://daveaglick.com/posts/computed-properties-and-entity-framework
But it doesn't work :(
Not sure what I'm doing wrong.
Here's the class that has a computed value.
public class Person
{
public int Id { get; set; }
public string First { get; set; }
public string Last { get; set; }
[NotMapped]
[Computed]
public string Full { get { return First + " " + Last; } }
}
This is the configuration.
public class DelegateDecompilerConfiguration : DefaultConfiguration
{
public override bool ShouldDecompile(MemberInfo memberInfo)
{
// Automatically decompile all NotMapped members
return base.ShouldDecompile(memberInfo) || memberInfo.GetCustomAttributes(typeof(NotMappedAttribute), true).Length > 0;
}
}
I also tried removing the [NotMapped] and then changed typeof(NotMappedAttribute) to typeof(ComputedAttribute) in the above configuration.
Then I register it like so
DelegateDecompiler.Configuration.Configure(new DelegateDecompilerConfiguration());
In Startup.cs. I also tried putting it directly into my action.
public ActionResult Test()
{
DelegateDecompiler.Configuration.Configure(new DelegateDecompilerConfiguration());
var ctx = new ApplicationDbContext();
var result = ctx.People.Where(x => x.Full.Contains("foo bar")).ToList();
return View();
}
Neither work :(
If I put .Decompile() on the query then it works as expected. So the DelegateDecompiler is working, but not the configuration.
As you discovered in your GitHub issue, you always have to call .Decompile() within your LINQ query. The extra configuration just eliminates the need to decorate all of your computed properties with the Computed attribute, instead relying on the Entity Framework NotMapped attribute.
As Dave says it requires Decompile()
So you don't have to remember to call it you can create a mapper extension that wraps what you are doing.
public static class MapperExtensions
{
/// <summary>
/// Creates a list from automapper projection. Also wraps delegate decompiler to supprt Computed Domain properties
/// </summary>
/// <typeparam name="TDestination"></typeparam>
/// <param name="projectionExpression"></param>
/// <returns></returns>
public static List<TDestination>
ToList<TDestination>(this IProjectionExpression projectionExpression)
{
return projectionExpression.To<TDestination>().Decompile().ToList();
}
}
Related
If there is any wrong property (For example if I send the payload data, Person_ instead of Person), model fully gets as null (Post([FromBody] Request data))
public class Person
{
public Guid Id { get; set; }
public string? Firstname { get; set; }
public string? Lastname { get; set; }
}
public class Request
{
public Guid Id { get; set; }
public Guid? Personid { get; set; }
public virtual Person? Person { get; set; }
}
public IActionResult Post([FromBody] Request data)
{
...
}
curl --location --request POST 'https://localhost:7124/v2/request?$expand=Person($select=Id,Firstname,Lastname)/Request&#odata.context=%27https://localhost:7124/v2/$metadata' \
--header 'Content-Type: application/json' \
--data-raw '{
"Id": "a436677a-fa4b-465e-8e70-211a1a3de8e9",
"Personid": "be9b53ad-4dfb-4db5-b269-32669f7c4e2d",
"Person_" : {
"Firstname": "JOHN",
"Lastname": "SMITH",
}
}'
I need to get the model even though some properties not correct according to model schema.
What could be the reason for it being null?
One of the main issues is that the type argument forms a strong contract that the OData subsystem tries to enforce. If the Deserializer cannot match the expected type fully, then it returns null, not a partially constructed object, or an empty object if none of the properties matched.
What you are expecting was a lazy implementation that we often took for granted in previous versions of OData and JSON.Net, but the OData Entity Serializer doesn't work this way any more.
When the argument is null, the ModelState should provide detailed information on the reason for the failure.
OData has support for allowing additional members, it is called Open-Type Support. Similar to the catch all solutions in other deserialization methods, we designate a dictionary to route all un-mapped properties so that you can inspect them after deserialization. This was a good walkthrough in .Net FX but basically we add the property:
public class Request
{
public Guid Id { get; set; }
public Guid? Personid { get; set; }
public virtual Person? Person { get; set; }
public IDictionary<string, object> DynamicProperties { get; set; }
}
Then in your model builder you need to declare the type as open:
builder.Entity<Request>("request").EntityType.IsOpen();
This is alone is still going to be hard to use though because your additional member is a complex type, so the type cannot be easily resolved automatically.
You could implement your own deserializer, but that is a lot more universal to all of your controllers and endpoints, you should take a little bit more care because it really opens a back door and cancels out a lot of functionality if you don't do it right. In your example the _person is omitted entirely, which might not be your intention.
Other solutions are a bit more permanent and messy, like adding additional properties to your model to capture the input and re-assign it internally. The best advice however is to respond to the client with an adequate error message so that they update the call.
There is another way that we can also cheat by using the JToken type, instead of the expected concrete type. This will universally ingest the payload from the request, then we can use good old JSON.Net to resolve the object:
/// <summary>
/// Inserts a new item into this collection
/// </summary>
/// <param name="item">The item to insert</param>
/// <returns>CreatedODataResult</returns>
[EnableQuery(AllowedQueryOptions = AllowedQueryOptions.Format | AllowedQueryOptions.Select)]
public virtual async Task<IActionResult> Post([FromBody] Newtonsoft.Json.Linq.JToken x)
//public virtual async Task<IActionResult> Post(TEntity item)
{
TEntity item = x.ToObject<TEntity>();
... insert custom logic to resolve the badly formed properties
// Tell the client that the request is invalid, if it is still invalid.
if (!ModelState.IsValid)
return BadRequest(ModelState);
//EvalSecurityPolicy(item, Operation.Insert);
await ApplyPost(item);
//UpdateRelationships(item, Operation.Insert);
await _db.SaveChangesAsync();
return Created(item);
}
/// <summary>
/// Inheriting classes can override this method to apply custom fields or other properties from dynamic members in the posted item, base class will apply TenantId only if it has not already been applied
/// </summary>
/// <remarks>This process is called after the usual validation overriding just this process means you do not have to replicate the existing internal logic for the afore mentioned tasks.</remarks>
/// <param name="item">The new item that has been uploaded</param>
/// <returns>Promise to add the item to the underlying table store</returns>
public virtual Task ApplyPost(TEntity item)
{
GetEntitySet().Add(item);
return Task.CompletedTask;
}
This is a base class implementation of ODataController Inheriting controller classes only override ApplyPost if needed. I've commented out some more advanced logic routines to give you other hints on how you might use this pattern.
Is a good practice? I'm undecided but it works and will allow your API to be resilient to schema changes that the client hasn't yet been updated to support, you can also inspect and handle the invalid ModelState in your controller before you return to the caller, or can easily add your own custom mapping logic if needed.
The problem is the declaration of Person in the class Request, It should be public Person Person_ { get; set; }.
You can declare it as public virtual Person? Person_ { get; set; } also if you don't want to change the declaration.
The only catch here is the suffix underscore before Person.
If you don't want to change the declaration then you can use JsonProperty
[JsonProperty("Person_")]
public virtual Person? Person { get; set; }
I have found a solution. I have used a custom ODataResourceDeserializer to handle the exception of doesn't exist properties and, included a try/catch block in the ApplyNestedProperty method's content. So the web service cannot throw an exception for not exists properties while deserialization process.
public class CustomResourceDeserializer : ODataResourceDeserializer
{
public CustomResourceDeserializer(IODataDeserializerProvider deserializerProvider) : base(deserializerProvider)
{
}
public override void ApplyNestedProperty(object resource, ODataNestedResourceInfoWrapper resourceInfoWrapper, IEdmStructuredTypeReference structuredType, ODataDeserializerContext readContext)
{
try
{
base.ApplyNestedProperty(resource, resourceInfoWrapper, structuredType, readContext);
}
catch (System.Exception)
{
}
}
}
I have an API and I want swashbuckle to autogenerate all the swagger documentation for me.
I have an endpoint that returns a class with a dictionary property but the swagger generated example contains "additionalProp1, additionalProp2" etc instead of example values. Is there a way to instead use example values specified in the SimpleClass class?
The class with the example for swagger (that doesn't work).
public class SimpleClass
{
/// <example>"{"age":31,"height":234}"</example>
public Dictionary<string, int> DictionaryProperty { get; set; }
/// <example>The cow jumped over the moon</example>
public string someProperty { get; set; }
}
The controller
[HttpGet]
[Route("/testexample")]
[ProducesResponseType(typeof(SimpleClass), StatusCodes.Status200OK)]
public async Task<IActionResult> TestExample()
{
return Ok();
}
The result in swagger:
Take out the quotes inside the XML example value:
/// <example>{"age":31,"height":234}</example>
I have the following code:
The structure of my project is like this:
My classes (relevant for this bug)
SizeEntity.cs
namespace DataObjects.EntityFramework
{
public class SizeEntity
{
public int Id { get; set; }
public string SizeName { get; set; }
}
}
Size.cs (on business objects class library)
namespace BusinessObjects
{
// Product business object
// ** Enterprise Design Pattern: Domain Model, Identity Field, Foreign key mapping
public class Size : BusinessObject
{
// ** Enterprise Design Pattern: Identity field pattern
public Size()
{
// establish business rules
AddRule(new ValidateRequired("SizeName"));
AddRule(new ValidateLength("SizeName", 1, 3));
}
public int Id { get; set; }
public string SizeName { get; set; }
}
}
SizeDao.cs
public class SizeDao : ISizeDao
{
/// <summary>
/// Constructor to initialize AutoMapper
/// </summary>
static SizeDao()
{
Mapper.Initialize(cfg => cfg.CreateMap<SizeEntity, Size>());
Mapper.Initialize(cfg => cfg.CreateMap<List<SizeEntity>, List<Size>>());
}
/// <summary>
/// Inserts size into database
/// </summary>
/// <param name="size"></param>
public void InsertSize(Size size)
{
using (var context = new ExamContext())
{
var entity = Mapper.Map<Size, SizeEntity>(size);
context.Sizes.Add(entity);
context.SaveChanges();
// update business object with new id
size.Id = entity.Id;
}
}
/// <summary>
/// Gets all size from database
/// </summary>
/// <returns>Returns a list of Sizes</returns>
public List<Size> GetSizes()
{
using (var context = new ExamContext())
{
var sizes = context.Sizes.ToList();
return Mapper.Map<List<SizeEntity>, List<Size>>(sizes);
}
}
}
I am getting the mapping error on the last line of this code.
return Mapper.Map, List>(sizes);
What am I missing here?
Error message is in the title:
AutoMapperMappingException: Missing type map configuration or unsupported mapping, Mapping List of Types
Update 1:
I removed that line suggest and still get:
Missing type map configuration or unsupported mapping.
Mapping types:
SizeEntity -> Size
DataObjects.EntityFramework.SizeEntity -> BusinessObjects.Size
For first you should remove the initialization of the mapping where you specify the List of objects, you just need to define the mapping with the base objects. That initialization would lead to an empty result when you call Map method.
static SizeDao()
{
Mapper.Initialize(cfg => cfg.CreateMap<SizeEntity, Size>());
// This is not needed.
// Mapper.Initialize(cfg => cfg.CreateMap<List<SizeEntity>, List<Size>>());
}
Another thing, as suggested #CodeCaster, don't put your mapping definition in the static constructor, but, for example, in the entry point of the whole application and verify that those lines get called before you invoke the Map method.
Because Automapper didnt work, then I had to do the following:
/// <summary>
/// Gets all size from database
/// </summary>
/// <returns>Returns a list of Sizes</returns>
public List<Size> GetSizes()
{
using (var context = new ExamContext())
{
var sizes = context.SizeEntities.ToList();
//Convert SizeEntities list to Size (Business Objects) list
var targetList = sizes
.Select(x => new Size() { Id = x.Id, SizeName = x.SizeName})
.ToList();
return targetList;
//return Mapper.Map<List<SizeEntity>, List<Size>>(sizes);
}
}
When attempting to use a property in the WithMessage part of a fluent validation rule, the string property isn't used, and instead it just outputs true. I have used validation in other areas of the application using collections (which is less straight-forward), and I could perform this task without issues. The only difference here is that it's a single object with a base class.
Here is my validator:
public class MultiCulturalControlValidator : AbstractValidator<TitleMultiCulturalControlProperty>
{
public MultiCulturalControlValidator()
{
RuleFor(x => x.EnglishValue).NotEmpty().WithMessage("test error {0}", x => x.DisplayName);
}
}
My viewmodel, with all the irrelevant properties stripped out:
[Validator(typeof(MultiCulturalControlValidator))]
[DataContract]
public class TitleMultiCulturalControlProperty : MultiCulturalControlProperty
{
public TitleMultiCulturalControlProperty()
{
}
/// <summary>
/// Gets or sets the name of these culture table values.
/// </summary>
[DataMember]
public string DisplayName { get; set; }
/// <summary>
/// Gets or sets the required english value.
/// </summary>
// ReSharper disable once LocalizableElement
[Display(Name = "English")]
[StringLength(255)]
[DataMember]
public override string EnglishValue { get; set; }
}
As you can see, the required English value is overridden. Is that the issue? The rule still runs correctly, though, and it's just the message that isn't correct.
The message that displays when the rule doesn't pass:
"test error true"
'true' should be the DisplayName string. I checked, and the name isn't null/empty when the data is posted. I've checked all over for help and I couldn't find anything covering this issue.
Thanks
I know this is ancient, but I just found this question while searching for the solution to similar problem. Here is the syntax I ended up using (reworked for the original question).
public class MultiCulturalControlValidator : AbstractValidator<TitleMultiCulturalControlProperty>
{
public MultiCulturalControlValidator()
{
RuleFor(x => x.EnglishValue).NotEmpty().WithMessage(x => string.Format("test error {0}", x.DisplayName));
}
}
Way too late of an answer, but I wonder if the fact you are inheriting from "MultiCulturalControlProperty" was the issue.
I'm currently having an issue with explicit loading in Entity Framework.
I've disabled Proxy Creation and Lazy Loading in my datacontext.
public DataContext()
: base("")
{
Configuration.ProxyCreationEnabled = false;
Configuration.LazyLoadingEnabled = false;
}
This has been done because I need to be able to serialize my entity framework objects, which I cannot do when using virtual properties because this creates a virtual property.
I would like to retrieve the entity 'ContentType':
[Table("T_CONTENT_TYPES")]
public class ContentType : Entity
{
#region Properties
/// <summary>
/// Gets or sets the name of the content type.
/// </summary>
[Required]
[Column(Order = 1)]
public string Name { get; set; }
/// <summary>
/// Gets or sets the <see cref="Folder"/> in which this item belongs.
/// If this field is empty, it means that this item is stored in no particular folder, which means it's stored in the root.
/// </summary>
[Column(Order = 100)]
public Folder Folder { get; set; }
#endregion
}
This is done by using this code:
var contentType = UnitOfWork.ContentTypeRepository.GetById(Id);
As you see, my entity has a reference to a Folder. Since LazyLoading & Proxy Creation is disabled, I use the following code to retrieve the folder:
((DataContext) UnitOfWork.DataContext).Entry(contentType).Reference(x => x.Folder).Load();
My complete method is then:
public ContentType GetById(int Id)
{
var contentType = UnitOfWork.ContentTypeRepository.GetById(Id);
((DataContext) UnitOfWork.DataContext).Entry(contentType).Reference(x => x.Folder).Load();
return contentType;
}
The GetById method looks like the following:
public TEntity GetById(int id)
{
if (GetEntityById != null)
{ GetEntityById(this, new EventArgs()); }
var returnData = dbSet.FirstOrDefault(x => x.Id == id);
if (GetEntityByIdFound != null)
{ GetEntityByIdFound(this, new EventArgs()); }
return returnData;
}
However, the 'Folder' property is still null.
Anyone has an idea why this is not working?
If somebody knows a good working alternative for serializing lazy loaded entities, I'm prepared to integrate that one.