ASP.Net Model Binder - Ignore Not Mapped Properties - asp.net-core

I am working with ASP.Net Core 5, Entity Framework Core 6, and using ODATA controllers with an EDM model. The model I am working with has several properties, including a [NotMapped] property. I have a React SPA that gets an entity from the controller, including the [NotMapped] property. When I post this entity back to my controller from the react app. The controller uses [FromBody] attribute to deserialize the JSON into a complex .NET object. However, the model fails to bind because of the [NotMapped]` property. If I remove this not mapped property in the React app on the client side before posting to the controller, the model binder works fine. However, this is not an acceptable solution, since this means my code will break anytime I add a not mapped property to my models without updating the React client to deal with this. The React client has no understanding of which properties are mapped or not mapped. Is there a way to get the model binder to just ignore properties that do not exist in the model, instead of having the model fail to bind completely?
Here is my model:
public class Person
{
public Person() { }
[Key]
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
[NotMapped]
public string FullName => FirstName + " " + LastName;
}
And here is my controller Get action:
[ODataRoute("{id}")]
public Person Get([FromODataUri] int id)
{ return db.Persons.Find(id); }
The controller successfully gets the Person, including the [NotMapped] FullName property from the model and returns the model to the React client.
When I update the model on the React client and want to update Person, I call the Put method of this controller:
[ODataRoute("{id}")]
public async Task<IActionResult> Put([FromODataUri] int id, [FromBody] Person person)
{
if (!ModelState.IsValid) return BadRequest(ModelState);
if (id != person.Id) return BadRequest();
db.Entry(person).State = EntityState.Modified;
await db.SaveChangesAsync();
return Updated(person);
}
However, the Person object is null in the controller because it does not recognize the [NotMapped] FullName property of Person. I presume this, because if I remove this property from the JSON before POST'ing the data to the controller the model binder properly populate the Person object.
Is there a simple way to tell the model binder to simple ignore extra properties that do not exist as mapped properties of the model it is trying to bind, or do I have to write a complete custom model binder to do this?
Edit
Here is an example JSON payload that results in the model not binding, and the Person entity in my controller is null:
{
"#odata.context": "https://localhost:44304/rest/$metadata#Persons/$entity",
"Id": 1,
"FirstName": "Jimmy",
"LastName": "Johnson",
"FullName": "Jimmy Johnson",
}
Note the FullName property is [NotMapped]. When included in the JSON payload in my controller, it will not deserserialize. The person object is Null. Also notice there #odata.context does not interfere with the deserialization, even though it is not in the model. I assume the Odata inputformatter knows to expect that and ignores it.
If I send the same payload as above, but omit the FullName property, the JSON deserializes and the person object is populated.
I would like to figure out how to make it so that FullName or any [NotMapped] property is ignored, rather than causing the deserialization to fail. I am hoping for a solution that doesn't make me have a custom inputformatter for each entity type, and one that does not require me to make changes for every new [NotMapped] property I add to any of my models. It seems like there should just be an option in System.Text.Json options that tells the deserializer to ignore properties that don't exists for the model, just like #Odata.context which is ignored. However, I have not seen such an option.

Related

Instantiating ModelExpression directly

Let's say I have the following input tag which utilizes the built-in tag helper:
#model ProductViewModel
<label asp-for="Product.Id"></label>
In my case, this expands into the following:
<label for="Product_Id">Id</label>
I see that asp-for is expecting a ModelExpression:
In tag helper implementations, I often see a property like the following:
public ModelExpression For { get; set; }
It appears that this is automatically populated when the tag helper is used.
Is there a way to instantiate a ModelExpression directly in C#?
I.e. something like this:
var exp = new ModelExpression("Product.Id",...)
I'd like to be able to generate "Product_Id" and "Id" from Product.Id as the input tag helper did.
As far as I know, you can specify that your property is to be set to the name of some property on the View's Model object by declaring your property with the ModelExpression type. This will enable any developer using your property to get IntelliSense support for entering a property name from the Model object. More importantly, your code will be passed the value of that property through the ModelExpression's Model property.
Sample code as below:
[HtmlTargetElement("employee-details")]
public class EmployeeDetailTagHelper : TagHelper
{
[HtmlAttributeName("for-name")]
public ModelExpression EmployeeName { get; set; }
[HtmlAttributeName("for-designation")]
public ModelExpression Designation { get; set; }
public override void Process(TagHelperContext context, TagHelperOutput output)
{
output.TagName = "EmployeeDetails";
output.TagMode = TagMode.StartTagAndEndTag;
var sb = new StringBuilder();
sb.AppendFormat("<span>Name: {0}</span> <br/>", this.EmployeeName.Model);
sb.AppendFormat("<span>Designation: {0}</span>", this.Designation.Model);
output.PreContent.SetHtmlContent(sb.ToString());
}
}
Code in the View page:
#model WebApplication7.Models.EmployeeViewModel
<div class="row">
<employee-details for-name="Name" for-designation="Designation"></employee-details>
</div>
Code in the Model
public class EmployeeViewModel
{
public string Name { get; set; }
public string Designation { get; set; }
}
From above code, you can see that we could custom the attribute name. More detail information about using the ModelExpression, check the following links:
Creating Custom Tag Helpers With ASP.NET Core MVC
Expression names
I'd like to be able to generate "Product_Id" and "Id" from Product.Id
as the input tag helper did.
Besides, do you mean you want to change the Product. Id to Product_Id, in my opinion, I'm not suggesting you change it, because generally we can use "_" as a separator in the property name. So, if we are using Product.Id, it means the Product's Id property, and the Product_Id means there have a Product_Id property.
To answer the question:
Is there a way to instantiate a ModelExpression directly in C#"
Yes you can, through IModelExpressionProvider and its CreateModelExpression method. You can get an instance of this interface through DI.
Now, if you're already in your view and working with tag helpers, Zhi Lv's answer is all you need, as the functionality is built-in and much easier to use. You only need IModelExpressionProvider for when you're in your Razor Page, Controller, or perhaps some custom middleware. Personally, I find this functionality useful for my Ajax handlers that need to return one of my ViewComponents that has a ModelExpression argument (so that I can easily call it from my Pages/Views too.)
To call CreateModelExpression, you'll need a strongly-typed instance of ViewData. In Razor Pages, this is as easy as casting the ViewData property to the strongly-typed instance of your PageModel's type (presuming you don't have a page model hierarchy):
var viewData = (ViewDataDictionary<IndexModel>)ViewData;
If you're using MVC and you're in the controller, that won't exist yet. Best you can do is make your own instance.
var viewData = new ViewDataDictionary<ErrorViewModel>(new EmptyModelMetadataProvider(),
new ModelStateDictionary());
Once you get your strongly-typed ViewData instance, you can obtain your desired ModelExpression like this, just like using a lambda expression in your views:
var myPropertyEx = _modelExpressionProvider.CreateModelExpression(viewData,
m => m.MyProperty);

EF Core 2.0 Trouble 'Cascading' Inserts for Related Entities When Updating Principle Entity

ASP.NET Core 2 Web application using a REST API. Currently using sqlite3 for development database. (Also tried migrating to SQL Server and got same results as below).
I'm sending an entity to web client, the client makes changes to the entity that involve adding a new related entity and then that updated principle entity gets sent back as json in body of PUT a request.
I was hoping the new related entity would get created automatically, but this is not happening. The simple properties on the principle entity are updated properly, but not reference properties. I'm not getting any exceptions or anything - it just seems to be ignoring the reference properties.
Simplified Classes (I removed other properties that shouldn't affect the relationship):
public partial class DashboardItem {
public int Id { get; set; }
public int? DataObjectId { get; set; }
public DataObject DataObject { get; set; }
}
public partial class DataObject {
public int Id { get; set; }
}
Portion of DbContext Fluent API for associated property:
modelBuilder.Entity<DashboardItem>(entity => {
entity.HasOne(p => p.DataObject)
.WithMany()
.HasForeignKey(p => p.DataObjectId);
});
Controller Method for PUT:
[HttpPut("{id}")]
public async Task<IActionResult> PutDashboardItem([FromRoute] int id, [FromBody] DashboardItem entity)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (id != entity.Id)
{
return BadRequest();
}
_context.Entry(entity).State = EntityState.Modified;
try{
await _context.SaveChangesAsync();
}catch (DbUpdateConcurrencyException)
{
if (!DashboardItemExists(id)){
return NotFound();
}else {
throw;
}
}
return NoContent();
}
The simplified json (without all the other properties) would look like this (I've tried different variations of have the foreign key "DataObjectId" removed from the json, set to null, or set to zero in case that might be interferring.):
{
Id:1,
DataObjectId:null,
DataObject:{
Id: 0
}
}
When debugging in the controller action method, the existing "DashboardItem" principle entity created from the request body has the reference property "DataObject" populated before getting added to the DbContext, but the new DataObject never gets created in the database. There is only a SQL UPDATE statement issued for DashboardItem and no INSERT for DataObject.
I've also tried making the controller method synchronous instead of async, using DbContext.SaveChanges() instead of .SaveChangesAsync(), since there used to be a problem with that in earlier versions of EF Core related to creating related entities, even though I'm using 2.0 which already has a fix for that. Same result.
This EFCore Doc sounds like it should just work out of the box.
This has worked for me in a prior project. What am I missing here?
Basically, my mistake was in assuming the process of updating data was much simpler than it actually is when sending the updated data from a client in a web application.
After digging a lot more, it seems that the following line in my controller method for handling the PUT request is the problem:
_context.Entry(entity).State = EntityState.Modified;
Setting the entity entry state to Modified in this way results in Entity Framework Core ignoring the reference properties for the related objects - the SQL UPDATE generated will only address the columns in the entity table.
This simple summary eventually got me started down the right path.
Summarizing what I've now learned:
This controller method is dealing with a 'detached' entity that was edited and sent back from the client. The DbContext is not yet tracking this entity since I get a new instance of the context with each http request (hence the entity is considered 'detached'). Because it is not being tracked yet, when it is added to the DbContext, the context needs to be told whether this entity has been changed and how to treat it.
There are several ways to tell the DbContext how to handle the detached entity. Among those:
(1) setting the entity state to EntityState.Modified will result in ALL properties being included in the SQL update (whether they've actually changed or not), EXCEPT for the reference properties for related entities:
_context.Entry(entity).State = EntityState.Modified;
(2) adding the entity with a call to DbContext.Update will do the same as above, but will include the reference properties, also include ALL properties on those entities in the update, whether they've changed or not:
_context.Update(entity)
Approach #2 got things working for me, where I was just trying to get the new related child entity to be created in the Update to its parent.
Beyond that, DbContext.Attach() and DbContext.TrackGraph sound like thy provide more find-grained control over specifying what specific properties or related entities to include in the update.

Unwanted loading of relations in EF Core

Hello,
I have problem with EF Core feature - It automatically binds related entities together when the entities are somewhere independently attached to current dbCotnext.
Let's assume following two entities:
public class Seller {
public Guid Id {get;set;}
public List<Product> Products {get;set;}
}
public class Product {
public Guid Id {get;set;}
public Guid SellerId {get;set;}
public Seller Seller {get;set;}
}
And some code in the controller (just for imagination):
var seller = DbContext.Sellers.FirstOrDefault(e => e.Id == someId);
var products = DbContext.Products.All(t => t.SellerId == someId);
return StatusCode(200, products);
The returned JSON will be like
[
{
"id": "1234",
"sellerId": "5678",
"seller": {
"id" : "5678",
"products": ["(muted reference loop exception from json converter here.)"]
}
}
]
But I don't want the Seller to be included in each Product. If I did, I'd call Products.Include(...) for that or something else.
I don't want to crawl through entities and null the navigation properties.
I don't want to hide it with [JsonIgnore] because sometimes the relation must be included.
I also don't want to manually detach every entity all the time when this happens.
The question is, is there any way to disable or work around this behaviour?
Thanks
No, you can't/shouldn't. You need separate dto class(es).
Newtonsoft.Json is responsible for object serialization, it decides which properties must [not] be serialized. You can control it's behavior only using it's attributes. You can't control it from EF :)
And as soon as you wish sometimes to include property and sometimes not - you need two different classes (each with correct attributes). Everything other is a hack. DTO, Automapper and all this stuff - you are welcome.
BTW, having different class(es) for external API and internal data storage allows you to easily change one without breaking other (in future).
Have you tried this configuration on Startup class:
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc().AddJsonOptions(a => a.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);
// other code
}
You have to change model class and using nullable type.
public class Product {
public Guid Id {get;set;}
public Guid SellerId {get;set;}
public Seller? Seller {get;set;}
}

Problems with EF-Agnostic design consumed by WCF service.

I am trying to set up EF to work on WCF and keeping the domain class models EF Agnostic.
The code is organized into 3 projects. (I am taking a stab a DDD - I am very new to it but am looking forward t learning more)
Project: QA - Domain Layer. Contains the DataContract models/entities.
References
QA.Data
Project: QA.Data - Data Layer. Contains the context and EDMX (code generation stragtegy = "none")
References
Entity Framework/System.Data.Entity
Project: QA.Repository - Data Access/Repository. Contains the repository classes
References
QA [Domain Layer]
QA.Data [Data Layer]
Entity Frame/System.DataEntity
My understanding is that the domain layer can reference the data layer but the data layer should never reference the domain. The problem that this presents is that my Domain Models/Classes are defined in the Domain layer but the Context which creates and returns them is in the Data layer. In order for my context to know to return a "Widget" object it would need a reference to the Domain layer which defined the "Widget"
My (failed) solution : My solution was to create interfaces for each Domain Model and place them in the data layer. The context would return ... IdbSet ... These interfaces would, in turn, be implemented by the Domain Models, therefore keeping my data layer from directly needing to reference my domain (which causes illegal circular references anyway). The domain models were originally contructed using "ADO.NET DbContext Generator w/WCF Support" T4 templates. This process resulted in the inclusion of the [KnownType(typeof(IWidgetPiece))] at the beginning of of the widget class defin ition. (A Widget has a navigation property ... ICollection ...)
The problem appears when I attempt to access the service, I get the following error
'QA.Data.IWidgetPiece' cannot be added to list of known types since
another type 'System.Object' with the same data contract name
'http://www.w3.org/2001/XMLSchema:anyType' is already present. If
there are different collections of a particular type - for example,
List and Test[], they cannot both be added as known types.
Consider specifying only one of these types for addition to the known
types list.
I can change these to the concrete implementations ... [KnownType(typeof(WidgetPiece))] ... but I continue to get this error because the navigation property they are referring to is still returning an IWidgetPiece interface type which it MUST do in order to satify the interface implementation.
I am trying to figure out how to keep things appropriately divided and still have the context returning what it should. the context returning Interfaces still doesn't "sit" right with me for this and other reasons but I cannot think of another way to do this, and even this is presenting the aforementioned issue. HELP!
Some code to hopefully clarify my previous ramblings ...
namespace QA.Data
{
public interface IWidgetPiece
{
String ID { get; set; }
}
public interface IWidget
{
String ID { get; set; }
ICollection<IWidgetPiece> Pieces;
}
public partial class WidgetEntities : DbContext
{
IDbSet<IWidget> Widgets { get; set; }
IDbSet<IWidgetPiece> WidgetPieces { get; set; }
}
}
namespace QA
{
[KnownType(typeof(IWidgetPiece))]
// [KnownType(typeof(WidgetPiece))]
[DataContract(IsReference = true)]
public partial class Widget : QA.Data.IWidget
{
[DataMember]
public String ID { get; set; }
[DataMember]
public virtual ICollection<IWidgetPiece> Pieces { get; set; }
}
[DataContract(IsReference = true)]
public partial class WidgetPiece : QA.Data.IWidgetPiece
{
[DataMember]
public string ID { get; set; }
}
}
namespace QA.Repository
{
public class WidgetRepository
{
public List<Widget> GetWidgetbyID(String sId)
{
WidgetEntities context = new WidgetEntities();
List<IWidget> objs = context.Widgets.Where(b => b.ID == "78").ToList();
List<Widget> widgetList = new List<Widget>();
foreach (var iwidget in widgetList)
widgetList((Widget)iwidget);
return widgetList;
}
}
}
Do you really want / need two separate models i.e. your data access layer model (edmx) and your "real" domain model? The whole point of an ORM framework like EF is so you can map your domain model to your database tables, using mappings between the physical (database) conceptual model.
Since EF4.1, you can construct your domain model and then in your data access layer map that to your database directly using a fluent API. You can also elect to reverse-engineer your POCO domain model from a database if you want to quickly get up an running.
It just seems a bit of unnecessary complexity to create an entire EF class model, only to then have to map it again into another class model (which will most likely be fairly close to the EF-generated one).

nHibernate mapping to custom types

I have a Oracle database and one of the fields is a date range field. It is basically just stored in the database as a VARCHAR(40) in the format YYYY/MM/DD-YYYY/MM/DD. I want to map it in nHibernate to a custom class I have created like this
public class DateTimeRange
{
public DateTimeRange(DateTime fromTime, DateTime toTime)
{
FromTime = fromTime;
ToTime = toTime;
}
public override string ToString()
{
return String.Format("{0} to {1}", FromTime.ToString("HH:mm:ss"), ToTime.ToString("HH:mm:ss"));
}
public DateTime FromTime { get; set; }
public DateTime ToTime { get; set; }
}
How can I map to custom classes like this?
You need to implement your own IUserType.
See this blog post for details. I'll also paste the relevant section below in case the blog disappears.
In NHibernate, a custom mapping type is a class that derives from either the IUserType or ICompositeUserType interfaces. These interfaces contain several methods that must be implemented, but for our purposes here, we’re going to focus on 2 of them. Consider the following.
public class TypeClassUserType : IUserType
{
object IUserType.NullSafeGet(IDataReader rs,
string[] names,
object owner) {
string name = NHibernateUtil.String.NullSafeGet(rs,
names[0]) as string;
TypeClassFactory factory = new TypeClassFactory();
TypeClass typeobj = factory.GetTypeClass(name);
return typeobj;
}
void IUserType.NullSafeSet(IDbCommand cmd,
object value,
int index) {
string name = ((TypeClass)value).Name;
NHibernateUtil.String.NullSafeSet(cmd, name, index);
}
}
Having created this class, I can now explicitly map the association between ActualClass and TypeClass as a simple property on the ActualClass mapping.
<property
name="Type"
column="TypeName"
type="Samples.NHibernate.DataAccess.TypeClassUserType,
Samples.NHibernate.DataAccess" />
As NHibernate is in the process of saving an instance of ActualType, it will load and create a new instance of TypeClassUserType and call the NullSafeSet method. As you can see from the method body, I am simply extracting the name from the mapped property (passed in as the value parameter) and setting the extracted name as the value of the parameter to be set in the database. The net result is that although the Type property of ActualClass is TypeClass in the domain model, only the Name property of the TypeClass object gets stored in the database. The converse is also true. When NHibernate is loading an instance of ActualType from the database and the finds a property of my custom mapping type, it loads my custom type and calls the NullSafeGet method. As you can see, my method gets the name from the returned data, calls my flyweight factory to get the correct instance of TypeClass, and then actually returns that instance. The type resolution process happens transparently to my data access classes (and even to NHibernate itself for that matter).