0

With the following model:

class Person
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public WorkingLevel WorkingLevel { get; set; }
    // ...
}

enum WorkingLevel
{
    Unknown,
    Level1,
    Level2,
    Level3
}

and the following view model:

public class PatchResourceVM
{
    public List<PropertyValue> PropertyValues { get; set; } = new();
}

public class PropertyValue
{
    public string PropertyName { get; set; }
    public string? Value { get; set; }
}

I have an ASP.NET controller action like this which is supposed to updated properties of a person resource:

[HttpPatch("{id:int}")]
public async Task<IActionResult> UpdatePerson(int id, PatchResourceVM model)
{
    var person = await _dbContext.Employees.FindAsync(id);

    if (person is null)
    {
        return NotFound();
    }

    foreach (var propVal in model.ProrpertyValues)
    {
        var propertyInfo = person.GetType().GetProperty(propVal.PropertyName);
        if (propertyInfo is null)
        {
            return BadRequest($"Property {propVal.PropertyName} does not exist");
        }

        var oldValue = propertyInfo.GetValue(person, null);

        try
        {
            Type t = Nullable.GetUnderlyingType(propertyInfo.PropertyType) ?? propertyInfo.PropertyType;
            object? safeValue = (propVal.Value == null) ? null : Convert.ChangeType(propVal.Value, t);
            propertyInfo.SetValue(person, safeValue, null);
        }
        catch (InvalidCastException)
        {
            return BadRequest($"{propVal.Value} is not a valid value for {propVal.PropertyName}");
        }
        // from this answer: https://stackoverflow.com/a/41466710/19363672

        var changeLog = new PersonChangeLog
        {
            PersonId = id,
            // ...
            PropertyName = propVal.PropertyName,
            OldValue = oldValue?.ToString(),
            NewValue = propVal.Value
        };

        _dbContext.Add(changeLog);
    }

    await _dbContext.SaveChangesAsync();
    return NoContent();
}

When I want to update an enum I get the InvalidCastException exception.

Payload:

{
  "propertyValues": [
    {
      "propertyName": "WorkingLevel",
      "value": "1"
    }
  ]
}

Response: "1 is not a valid value for WorkingLevel"

The code works fine for numeric and string types, but I cannot update the enum properties.

Is there any way to fix this?

Parsa99
  • 307
  • 1
  • 13

3 Answers3

1

When using reflection, the value of an enum can be set to its underlying type, so you can use ChangeType(Object, TypeCode).

object? safeValue = (propVal.Value == null) ? null : 
                    Convert.ChangeType(propVal.Value, Type.GetTypeCode(t));
propertyInfo.SetValue(person, safeValue, null);
shingo
  • 18,436
  • 5
  • 23
  • 42
0

For enums you can use static methods provided by Enum:

var value = "1";
if (type.IsEnum)
{
    var tryParse = Enum.TryParse(type, value, out var result); // todo analyze tryParse
    Console.WriteLine(result); // prints "Level1"
}

But in general I would suggest to look into reusing some tools for patching endpoints either using JSON Patch (JsonPatch.Net for System.Text.Json) approach or using tools like Automapper to map from dictionary for example (a bit similar to something done here)

Guru Stron
  • 102,774
  • 10
  • 95
  • 132
0

"Because enums aren't IConvertible, which is required for Convert.ChangeType: "For the conversion to succeed, value must implement the IConvertible interface, because the method simply wraps a call to an appropriate IConvertible method."

This will solve your enum issue:

object? safeValue = ( propVal.Value == null )
    ? null
    : ( t.IsEnum
        ? Enum.Parse (typeof ( string ), propVal.Value.ToString() )
        : Convert.ChangeType ( propVal.Value, t )
    );
Greg M
  • 676
  • 3
  • 3