I have a typical web API with a couple of PUT/UPDATE endpoints. These endpoints simply call the underlying service, and do the update.
The service layer, has the typical signature such as Object Update(Object object). What I then do is I basically run the following pseudo code:
var dbobject = _db.Object.Find(object.Id);
dbobject.Field1 = object.Field1;
dbobject.Field2 = object.Field2;
// continue for all fields
_db.SaveChanges();
return GetObjectById(object.Id);
However, this provides a challenge for me.
Lets say we have a consumer of our API. This consumer calls my PUT endpoint (/api/Object/{id}), and the payload is the updated Object.
However, lets say that the object we put don't know about example Field4, then this value would be NULL after the update has been run.
My question is:
What do you do about all those fields the payload does NOT contain?
How do you handle not setting values to NULL you don't expect to be
NULL afterwards?
As one of the possible ways, here can be used mix of NotifyPropertyChanged with automapper
The Idea is to store in DTO object which fields exactly was set, and which stays filled with default value. And use collected data in mapping.
For example DTO object will be
public class Dto
{
private List<string> Changed = new List<string>();
public bool IsChanged(string field) => Changed.Contains(field);
private int _age;
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
// IMPORTANT: field name should fit main object field name
Changed.Add("Name");
}
}
public int Age
{
get { return _age; }
set
{
_age = value;
Changed.Add("Age");
}
}
}
I used Next class for test
public class Human
{
public string Name { get; set; } = "DEFAULT";
public int Age { get; set; } = -1;
}
and automapper configuration will looks like
cfg.CreateMap<Dto, Human>()
.ForAllMembers(s=> s.Condition(d=>d.IsChanged(s.DestinationMember.Name)));
This is a simple example. But it still doesn't prevent to use function IsChanged for some complex/specific logic, use not just a strings but Expressions / MethodInfo, or add custom attributes and use them in automapper configuration (DestinationMember is MethodInfo)
Append
Instead of complex DTO object the information about passed field you can get from Request.Properties in your controller (key ms_querynamevaluepairs value of type Dictionary<string, string>).
Related
I have a Blazor component for creating graphs with ChartJs. Based on the models, C# creates a Json for the Chart configuration. Some configurations have defined values that I can use: for example, the PointStyle accepts only few values such as circle and cross.
For this reason, I want to have the constraint to select only the accepted values for this configuration. Then, in my project I created a class PointStyle to have a sort of enum with string.
public class PointStyle
{
private PointStyle(string value) { Value = value; }
public string Value { get; private set; }
public static PointStyle Circle { get { return new PointStyle("circle"); } }
public static PointStyle Cross { get { return new PointStyle("cross"); } }
}
To obtain the correct Json configuration, in the main model I created 2 properties: PointStyle and PointStyleString.
[JsonIgnore]
public PointStyle? PointStyle {
get => _pointStyle;
set
{
_pointStyle = value;
PointStyleString = _pointStyle.Value;
}
}
private PointStyle? _pointStyle;
[JsonPropertyName("pointStyle")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public string? PointStyleString { get; set; }
PointStyle accepts only the values in the defined list but this is ignored from the Json converter. For example, I can configure this like
PointStyle = PointStyle.Cross
Then, the public variable PointStyleString contains the string I have to serialize in the Json and this is updated when the PointStyle sets a new value.
Now, I have to read the property Value from the class PointStyle that contains the string I have to pass to the configuration. For this reason, I have a private variable _pointStyle that saves the value for PointStyle; so, when a new value is set, I also set the PointStyleString.
When the configuration is ready, I pass through JSRuntime the configuration like
await JSRuntime.InvokeVoidAsync("setup", Config.CanvasId, dotnet_ref, Config);
I don't know exactly how the Config is translated to the Json but the resulted Json is correct for the graph.
Everything I described above is working but it seems to me quite complicated for just setting a value. Do you think I can simplify this code?
The Microsoft code guidline forbids complex operations in a constructor ( see https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/constructor)
What's the proper way to validate the input data if not in the constructor. Here is an example:
class User{
User(string id){
Validate(id); //Looks up id in database or remote service
}
}
The validate should not be called in the constructor. But how can I make sure I don't have a User class with an invalid id?
Basically it depends on whether the operation is actually complex.
In case that the operation is complex, you can use properties and validate the values inside:
class User{
private string _id;
public int Id
{
get
{
return _id;
}
set
{
if (Validate(value))
{
_id = value;
}
}
}
User(){
}
}
Long Description: I'm writing a basic search by filter function from an entity, so I can do something like this:
public Entity GetEntityBy(Entity filter)
{ }
public IList<Entity> GetEntitiesBy(Entity filter)
{ }
The problem is with non nullable types (int, float, etc), and I don't want to "force" all properties to be written as nullables. I want to avoid any kinds of rules (such as applying attributes or implementing my own get/set) so I can write the entity just as usual and simply use this filter function.
The code looks like this:
public class Entity
{
public int EntityID { get; set; }
public string Name { get; set; }
public DateTime RegisterDate { get; set; }
//Other properties
}
public IList<Entity> GetEntitiesBy(Entity filter)
{
if (filter != null)
{
if (filter.EntityID > 0)
{
//Add criteria to filter by ID
//In this case it works because there shouldn't have any IDs with 0
}
//this won't work because DateTime can't be null
//I can't check the default value as well because there are some searchs using the default value and I don't want to ignore that
if (RegisterDate != null)
{
}
}
}
It's supposed to be a simple equal filter depending on the values found in the filter parameter, but as it is now I don't know when I should ignore the default values or not.
I already have a SCRUD manager sort of class, so I want to add a function call to the class that it belongs so I can check when a property has been read/written to.
Short Description: How do I add a function call before a property's get or set acessor is called on a dynamic class? Is this even possible?
I have the following property:
public virtual String Firstname { get; set; }
and i only want to be able to write to the field IF it is currently null (not set), it this possible to achieve through DataAnnotations?
Data annotations are metadata used for example for validation so you can create custom data annotation to validate property value but the validation cannot ensure that your property will not be set if it already has value. That is code which should be part of property's setter itself like:
private string _firstName;
public string FirstName
{
get
{
return _firstName;
}
set
{
if (_firstName != null) throw ...
_firstName = value;
}
}
If by data annotations you simply mean attributes then the answer is: It can be achieved with attributes BUT you need something which will implement some logic related to the attribute. This is usually done through Aspect oriented programming (AOP) where you will create marker attribute which will be used by some complex API. The API will based on that attribute wrap your class with custom code adding the if statement either at compile time (for example PostSharp) or at runtime (for example Unity, Spring.NET).
Another way to achive this, by me more elegant, do not implement set for the property, but only get
private string _firstName;
public string FirstName
{
get
{
return _firstName;
}
}
and have a function:
public void SetFirstName(string FirstName)
{
_firstName = FirstName;
}
So no exception, no return value handling. You have one property the only retrieve value, and one function, or constructor (why not, depends on your architecture, it's hard to deduct from post) that initialized it only once.
By me the API of your object will be more clear and straightforward in this way.
Regards.
There is also a specific DataAnnotation syntax to achieve this:
[DisplayFormat(NullDisplayText = "some string")]
public virtual String Firstname { get; set; }
It's an EntLib-Validator-issue again. I'm playing with EntLib 5.0 in C# and .Net 4.0 on XP pro.
I have some business objects (partial classes) generated by T4 templates. So I decided to put their validation attributes in buddy-classes by using MetadataTypeAttribute as definitely recommended by the documentation of entLib 5.0 (msdn).
But the Validator object I get from the ValidatorFactory doesn't know about the validation attributes, defined in the metadata-class.
The business object is defined like this:
[MetadataType(typeof(PatientMetadata))]
public partial class Patient
{
private string _Name;
private int _DiagnosisCount;
public int DiagnosisCount
{
get
{
return _DiagnosisCount;
}
set
{
if (value != _DiagnosisCount)
{
_DiagnosisCount = value;
}
}
}
public string Name
{
get
{
return _Name;
}
set
{
if (value != _Name)
{
_Name = value;
}
}
}
}
And the metadata class like this, according to documentation:
public class PatientMetadata
{
[RangeValidator(4)]
public int DiagnosisCount { get; set; }
[StringLengthValidator(64, ErrorMessage = "Name must not exceed 64 chars.")]
public string Name { get; set; }
}
If I know try to do validation this way:
var factory = ValidationFactory.DefaultCompositeValidatorFactory;
var validator = factory.CreateValidator<Patient>();
...then watching into validator (during debugging) already says, that it's just an AndCompositeValidator without any children validators.
Again, if I put the validation attributes right in the Patient class, it works perfectly.
By now, I have no real idea, what I'm missing here, since I think doing everything according to the docs.
Thanks in advance to you guys!
The property names of the metadata class must match the property names of the main class.
In your case your metadata class should look like:
public class PatientMetadata
{
[RangeValidator(0, RangeBoundaryType.Inclusive, 10, RangeBoundaryType.Ignore)]
public int DiagnosisCount { get; set; }
[StringLengthValidator(6, ErrorMessage = "Name must not exceed 6 chars.")]
public string Name { get; set; }
}
Also, the docs indicate the accepted approach is to declare all return types as object. However, the docs also talk about using properties but in their example use fields so take it under advisement. :)