Delta<T> in PATCH actions not tracking primitive types - asp.net-web-api

I'm using Delta for patching an entity as outlined in "Partial Updates(PATCH request)" section outlined here
I have the following ProductDTO:
public class ProductDTO
{
public int ID { get; set; }
[Required]
public string Name { get; set; }
[UIHint("Date")]
[DataType(DataType.Date)]
public DateTime? ModifiedOn { get; set; }
public int Price { get; set; }
}
And the following action method defined:
public HttpResponseMessage Patch(int id, Delta<ProductDTO> delta)
{
return Request.CreateResponse(HttpStatusCode.NoContent);
}
If I pass in the following JSON (via Fiddler using the PATCH verb)
{"ID":1,"Name":"test","Price":"1000"}
The "delta" in the action method contains only the "Name" property and not the ID and Price properties. It appears that the delta is not containing values of types "int","decimal" and primitive types in general.
What am I missing here?
TIA
EDIT: Here is a link to a modded version of Delta that includes support for primitive JSON data types. Comments appreciated

Related

How can I LINQ select from two EF DbSets and group join - but using AutoMapper

Note: These classes are related, but not part of the same Aggregate (like PurchaseOrder and OrderLine) - so I do not have a navigation property from "One" to "Many".
=== Entities ===
public class One
{
public Guid Id { get; set; }
public string Name { get; set; }
}
public class Many
{
public Guid Id { get; set; }
public string Name { get; set; }
public Guid One { get; set; }
}
=== Contracts ===
public class OneWithMany
{
public Guid Id { get; set; }
public string Name { get; set; }
public IEnumerable<Many>? ManyRelatedObjects { get; set; }
}
I want to select all One objects and any related Many objects from DbSet/DbSet into OneWithMany.
To ensure I don't miss properties added in future I am using ProjectTo in AutoMapper - but I can't work out how to fit it into the equation.
Unfortunately, it seems Entity Framework does not support GroupJoin.
The solution is to do the projection and as much filtering as possible as two separate queries, and then combine them into a result in memory.
If you find EF related answers on the web related to GroupJoin make sure you check the example code to see if they are actually showing code working on arrays instead of DbSet.

Error deserializing JSON to a class with IEnumerable

I need to return a collection with nested optional collections from my WebAPI and convert it back to objects in my Web UI, however I'm getting the following error:
Cannot create and populate list type System.Linq.IQueryable`1[MyNamespace.MyClass].
I'm using Json.Net.
Here is a sample of my code:
public class ClassA
{
public Int64 Id { get; set; }
public String Description { get; set; }
public IEnumerable<ClassB> { get; set; }
}
public class ClassB
{
public Int64 Id { get; set; }
public String Description { get; set; }
}
I've saw some questions here in stackoverflow, but I don't have access to serialization options (it is handled internally by our library).
UPDATE
I've forgot to mention that ClassA is returned as an IQueryable in my Web API.
Changing the return type of my Web API method to IEnumerable instead of IQueryable solved the problem.

Problems with Model binding and validation attributes with asp.net Web API

I am writing a Web API with ASP.NET Web API, and make use of the following View Model.
I seem to be having a problem with the data binding when there are two validation attributes on a particular property (i.e. [Required] and [StringLength(10)]).
When posting a JSON value from a client to a controller action of the form:
// POST api/list
public void Post([FromBody] TaskViewModel taskVM)
I observe the following:
If I remove one of the multiple attributes everything is bound OK;
If I leave in the multiple attributes, the client recieves a 500 internal server error and the body of the Post method is never reached.
Any ideas why this happens?
Cheers
public class TaskViewModel
{
//Default Constructor
public TaskViewModel() { }
public static TaskViewModel MakeTaskViewModel(Task task)
{
return new TaskViewModel(task);
}
//Constructor
private TaskViewModel(Task task)
{
this.TaskId = task.TaskID;
this.Description = task.Description;
this.StartDate = task.StartDate;
this.Status = task.Status;
this.ListID = task.ListID;
}
public Guid TaskId { get; set; }
[Required]
[StringLength(10)]
public string Description { get; set; }
[Required]
[DataType(DataType.DateTime)]
public System.DateTime StartDate { get; set; }
[Required]
public string Status { get; set; }
public System.Guid ListID { get; set; }
}
You need to inspect what is inside in the 500 internal server
make sure that you turn customerror off in your web.config
If you selfhost web.API you need to set GlobalConfiguration.Configuration.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
use your browser development console's network tab (in IE, Chrome you can get the console with F12) or if you are using FireFox then use FireBug or a thrid party tool like Fiddler.
Then you can see what went wrong on the server and go further to solve your problem.
In your case this is in the response:
"Message":"An error has occurred.","ExceptionMessage":"Property
'StartDate' on type 'MvcApplication3.Controllers.TaskViewModel' is
invalid. Value-typed properties marked as [Required] must also be
marked with [DataMember(IsRequired=true)] to be recognized as
required. Consider attributing the declaring type with [DataContract]
and the property with
[DataMember(IsRequired=true)].","ExceptionType":"System.InvalidOperationException"
So your problem is not that you have two attributes but that you've marked your properties with [Required] to solve this the exception tells you what to do.
You need to add [DataMember(IsRequired=true)] to your required properties where the property type is a value type (e.g int, datatime, etc.):
So change your TaskViewModel to:
[DataContract]
public class TaskViewModel
{
//Default Constructor
public TaskViewModel() { }
[DataMember]
public Guid TaskId { get; set; }
[Required]
[DataMember]
[StringLength(10)]
public string Description { get; set; }
[Required]
[DataMember(IsRequired = true)]
[DataType(DataType.DateTime)]
public System.DateTime StartDate { get; set; }
[Required]
[DataMember]
public string Status { get; set; }
[DataMember]
public System.Guid ListID { get; set; }
}
Some side notes:
You need to reference the System.Runtime.Serialization dll in order to use the DataMemberAttribute
You need to mark your class with [DataContract] and you need to mark all of its properties with [DataMember] not just the required ones.

How can I mark properties required to show up different in intellisense

Let's say we have a class like so:
public class Plan
{
public string PlanCode { get; set; } //Required
public string Name { get; private set; }
public string Description { get; set; }
public string SuccessUrl { get; set; }
}
Is there a way to make intellisense show required fields/properties different, such as italicized or a shade of red?
One would be able to determine what properties would be required quickly if calling a Create() method for instance.
Note: When I say show up different in intellisense I don't mean the tool-tip text that you see when the member is highlighted. I specifically mean the text of the member itself.
No, I don't believe you can. You should design your objects with required fields in mind, i.e. constructors.
So any fields that the object requires should be parameters in a constructor.
public class Plan
{
public string PlanCode { get; set; } //Required
public string Name { get; private set; }
public string Description { get; set; }
public string SuccessUrl { get; set; }
public Plan(string planCode)
{
PlanCode = planCode;
}
}
This is allows your code to clearly express its intention. Any developer using the Plan class will know that PlanCode is required, as it is enforced by the constructor.

Using Multiple Interfaces with MVC DataAnnotations and MetaDataType

I am applying validation using DataAnnotations to an MVC ViewModel which is a composite of several entity framework objects and some custom logic. The validation is already defined for the entity objects in interfaces, but how can I apply this validation to the ViewModel?
My initial idea was to combine the interfaces into one and apply the combined interface to the ViewModel, but this didn't work. Here's some sample code demonstrating what I mean:
// interfaces containing DataAnnotations implemented by entity framework classes
public interface IPerson
{
[Required]
[Display(Name = "First Name")]
string FirstName { get; set; }
[Required]
[Display(Name = "Last Name")]
string LastName { get; set; }
[Required]
int Age { get; set; }
}
public interface IAddress
{
[Required]
[Display(Name = "Street")]
string Street1 { get; set; }
[Display(Name = "")]
string Street2 { get; set; }
[Required]
string City { get; set; }
[Required]
string State { get; set; }
[Required]
string Country { get; set; }
}
// partial entity framework classes to specify interfaces
public partial class Person : IPerson {}
public partial class Address : IAddress {}
// combined interface
public interface IPersonViewModel : IPerson, IAddress {}
// ViewModel flattening a Person with Address for use in View
[MetadataType(typeof(IPersonViewModel))] // <--- This does not work.
public class PersonViewModel : IPersonViewModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
public string Street1 { get; set; }
public string Street2 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Country { get; set; }
}
My real-world problem involves about 150 properties on the ViewModel, so it's not as trivial as the sample and retyping all the properties seems like a horrible violation of DRY.
Any ideas on how to accomplish this?
In order for this to work you will need to manually associate the interfaces as metadata for your concrete classes.
I expected to be able to add multiple MetadataType attributes but that is not permitted.
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] // Notice AllowMultiple
public sealed class MetadataTypeAttribute : Attribute
Therefore, this gives a compilation error:
[MetadataType(typeof(IPerson))]
[MetadataType(typeof(IAddress))] // <--- Duplicate 'MetadataType' attribute
public class PersonViewModel : IPersonViewModel
However, it works if you only have one interface. So my solution to this was to simply associate the interfaces using a AssociatedMetadataTypeTypeDescriptionProvider and wrap that in another attribute.
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class MetadataTypeBuddyAttribute : Attribute
{
public MetadataTypeBuddyAttribute(Type modelType, Type buddyType)
{
TypeDescriptor.AddProviderTransparent(
new AssociatedMetadataTypeTypeDescriptionProvider(
modelType,
buddyType
),
modelType);
}
}
In my situation (MVC4) the data annotation attributes on my interfaces already worked. This is because my models directly implement the interfaces instead of having multi-level inheritance. However custom validation attributes implemented at the interface level do not work.
Only when manually associating the interfaces all the custom validations work accordingly. If I understand your case correctly this is also a solution for your problem.
[MetadataTypeBuddy(typeof(PersonViewModel), typeof(IPerson))]
[MetadataTypeBuddy(typeof(PersonViewModel), typeof(IAddress))]
public class PersonViewModel : IPersonViewModel
based on answer here, I couldn't somehow make that MetadataTypeBuddy attribute works. I'm sure that we must set somewhere that MVC should be calling that attribute. I managed to get it work when I run that attribute manually in Application_Start() like this
new MetadataTypeBuddyAttribute(typeof(PersonViewModel), typeof(IPerson));
new MetadataTypeBuddyAttribute(typeof(PersonViewModel), typeof(IAddress));
The MetadataTypeBuddy attribute did not work for me.
BUT adding "new" MetadataTypeBuddyAttribute in the "Startup" did work BUT it can lead to complex code where the developer is not aware to add this in the "Startup" for any new classes.
NOTE: You only need to call the AddProviderTransparent once at the startup of the app per class.
Here is a thread safe way of adding multiple Metadata types for a class.
[AttributeUsage(AttributeTargets.Class)]
public class MetadataTypeMultiAttribute : Attribute
{
private static bool _added = false;
private static readonly object padlock = new object();
public MetadataTypeMultiAttribute(Type modelType, params Type[] metaDataTypes)
{
lock (padlock)
{
if (_added == false)
{
foreach (Type metaDataType in metaDataTypes)
{
System.ComponentModel.TypeDescriptor.AddProviderTransparent(
new AssociatedMetadataTypeTypeDescriptionProvider(
modelType,
metaDataType
),
modelType);
}
_added = true;
}
}
}
}

Resources