EF Core 3.1 property value returns default field value - asp.net-core

I have a class property of enum type LogLevel (using Microsoft.Extensions.Logging) being stored in my database and a class field that ins't mapped but stores the enum type value as such:
class ...
{
private LogLevel LevelName;
[Required]
public int Level { get { return (int)LevelName; } set { LevelName = (LogLevel)Level; } }
}
But since the field has to be initialized as default, every time I retrieve the value from the database I get back the default value of 0 for Level even though its really 3 or something. If the value is never being set, How can I initialize the correct default value?

You can use enums in Entity Framework Core models:
public class YourModel
{
public UserSearchStatus SearchStatus { get; set; }
}
with an enum like this.
public enum UserSearchStatus
{
StatusOne = 1,
StatusTwo = 2
}
If you don't want to use a default value, make the property nullable:
public class YourModel
{
public UserSearchStatus? SearchStatus { get; set; }
}

Related

ASPNET Core ActionResult property not serialize

I have this object
[DataContract]
public class FilterList<T> : List<T>
{
[DataMember]
public int Total { get; set; }
}
In my controller:
public ActionResult<FilterList<MyPOCO>> GetFilteredResult(string filter)
{
var l = new FilterList<MyPOCO>();
l.Total = 123456;
// Continue to add many MyPOCO objects into the list
return l;
}
I can get back the MyPOCO list at the client side, but the l.Total is NOT serialize. May I know what I had done wrongly?
Here is a workaround , you could try to use [JsonObject] attribute . But the items will not be serialized, because a JSON container can have properties, or items -- but not both. If you want both, you will need to add a synthetic list property to hold the items.
[JsonObject] will also cause base class properties such as Capacity to be serialized, which you likely do not want. To suppress base class properties, use MemberSerialization.OptIn. Thus your final class should look something like:
[JsonObject(MemberSerialization = MemberSerialization.OptIn)]
public class FilterList<T> : List<T>
{
[JsonProperty]
public int Total { get; set; }
[JsonProperty]
List<T> Items
{
get
{
return this.ToList();
}
set
{
if (value != null)
this.AddRange(value);
}
}
}
Result:

Validate Property as [Required], but don't validate the value itself. Is it possible with Attributes and conventions in .net core 2 or higher?

public class Post {
// ... other properties here
public Author { get; set; }
}
public class Author {
// ... other properties here
public List<Post> Posts { get; set; }
}
This is an overly-simplistic example. What I basically want is that the Author property in the Post class is never null, it MUST have a value. But because I'm pulling Author from a database, I am 100% sure that it's valid, so I want to skip the validation of the value of the Author property's value
In .net core 3 preview 9, recursive validation is causing unnecessarily long hold-ups where join entities for many-to-many relationships are involved.
I assume one workaround is to use [ValidateNever] in conjunction with the Validate() method just to check that the Author is not null. But I don't know if one of them will override the other.
But I am ideally looking to achieve this with Attributes and/or conventions
You could write your custom validation attribute.When the value is null, it returns an Validation error.Otherwise, it returns ValidationResult.Success.
public class CustomRequiredAttribute :ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if(value != null)
{
return ValidationResult.Success;
}
else
{
var propertyName = validationContext.DisplayName;
var type = validationContext.ObjectType;
return new ValidationResult("Could not be null for " + propertyName);
}
}
}
Model:
public class Post {
// ... other properties here
[CustomRequired]
public Author { get; set; }
}

FluentValidation NotNull on enum values

I have a model with enum property which based on 'int'.
I need to validate that this property is not empty. But NotEmpty forbids 0 value. And NotNull just doesn't work because enum property cannot be null.
I cannot make my property nullable.
How can I do such validation?
As long as the enum type is int you can do the following:
public class Status
{
public StatusType type { get; set; }
}
public enum StatusType
{
open = 1,
closed = 2
}
public class StatusValidator : AbstractValidator<Status>
{
public StatusValidator()
{
RuleFor(x => x.type).Must(x => x != 0);
}
}
If you can't avoid 0 you can define a workaround for the model as follow (source: Choosing the default value of an Enum type without having to change values):
[Note: you need to include using System.ComponentModel;]
public class Status
{
public StatusType type { get; set; }
}
[DefaultValue(_default)]
public enum StatusType
{
_default = -1,
test = 0,
open = 1,
closed = 2,
}
public static class Utilities
{
public static TEnum GetDefaultValue<TEnum>() where TEnum : struct
{
Type t = typeof(TEnum);
DefaultValueAttribute[] attributes = (DefaultValueAttribute[])t.GetCustomAttributes(typeof(DefaultValueAttribute), false);
if (attributes != null &&
attributes.Length > 0)
{
return (TEnum)attributes[0].Value;
}
else
{
return default(TEnum);
}
}
}
public class StatusValidator : AbstractValidator<Status>
{
public StatusValidator()
{
RuleFor(x => x.type).Must(x => x != Utilities.GetDefaultValue<StatusType>());
}
}
I suppose you want to validate the model in a mvc controller but you should be more clear about your utilization context.
I think the model should be as wide as possible in terms of types to fit any possible choice the user make at UI level, for example always using nullable types. When the model binding try to build the object it match property names to request keys/values and set the matching value into the property. When it dosen't find any match in the request it leaves the property to its default value(0 in case of int). In that case the only way you have to know if the user left the field empty or intentionally wrote a zero value in it is to check the model state. Well in the first case an error (field can not be null...etc etc) will be tracked in the model state and checking the model state you can be aware if the user set the value or not. Fluent validation comes into play after the model binding and it relies on the work of model binder itselfe and poor him can't really understand what zero really mean (empty/missing value or zero value).

Persisting Part Record to Database

Working on creating my first Orchard Module and I am running into issues getting the form data saved back to the database. I have everything registered correctly as far as I can tell from looking at a lot of samples so I must be missing something minor.
I am able to get the Apartment form to show under the new menu, validation is working but when I fill the form completly and hit save I get:
Your Apartment has been created.
Checking the database the record is not in the table and checking the logs shows:
2013-12-19 09:15:23,416 [19]
NHibernate.Transaction.ITransactionFactory - DTC transaction prepre
phase failed NHibernate.Exceptions.GenericADOException: could not
execute batch command.[SQL: SQL not available] --->
System.Data.SqlClient.SqlException: Cannot insert the value NULL into
column 'FloorPlanName', table
'Orchard.dbo.CommunityWebsiteSolutions_ApartmentPartRecord';
column does not allow nulls. INSERT fails.
Running SQL Profiler shows an insert with all columns being set to NULL.
Migrations.cs
SchemaBuilder.CreateTable(typeof(ApartmentPartRecord).Name, table => table
.ContentPartRecord()
.Column<string>("FloorPlanName", c => c.WithLength(25).NotNull())
.Column<string>("FullAddress", c => c.WithLength(256).NotNull()))
.Column<string>("ShortDescription", c => c.WithLength(150).NotNull())
.Column("NumberOfBedrooms", DbType.Int32, c => c.NotNull())
.Column("NumberOfBathrooms", DbType.Int32, c => c.NotNull())
.Column("SquareFootage", DbType.Int32, c => c.NotNull())
.Column("WhenAvailable", DbType.DateTime)
.Column("RentAmount", DbType.Decimal)
);
ContentDefinitionManager.AlterPartDefinition(typeof (ApartmentPart).Name, part => part.Attachable());
ApartmentPart
public class ApartmentPartRecord : ContentPartRecord {
public virtual string FloorPlanName { get; set; }
public virtual string ShortDescription { get; set; }
public virtual string FullAddress { get; set; }
public virtual int? NumberOfBedrooms { get; set; }
public virtual int? NumberOfBathrooms { get; set; }
public virtual int? SquareFootage { get; set; }
public virtual DateTime? WhenAvailable { get; set; }
public virtual decimal? RentAmount { get; set; }
}
public class ApartmentPart : ContentPart<ApartmentPartRecord> {
[Required, StringLength(256)]
[Display(Name = "Address / Unit Number")]
public string FullAddress {
get { return Record.FullAddress; }
set { Record.FullAddress = value; }
}
[Required, StringLength(25)]
[Display(Name = "Floor Plan")]
public string FloorPlanName {
get { return Record.FloorPlanName; }
set { Record.FloorPlanName = value; }
}
[Required, StringLength(150)]
[Display(Name = "Sales Description")]
public string ShortDescription {
get { return Record.ShortDescription; }
set { Record.ShortDescription = value; }
}
[Required]
[Display(Name = "Bedroom Count")]
public int? NumberOfBedrooms {
get { return Record.NumberOfBedrooms; }
set { Record.NumberOfBedrooms = value; }
}
[Required]
[Display(Name = "Bathroom Count")]
public int? NumberOfBathrooms {
get { return Record.NumberOfBathrooms; }
set { Record.NumberOfBathrooms = value; }
}
[Required]
[Display(Name = "Square Footage")]
public int? SquareFootage {
get { return Record.SquareFootage; }
set { Record.SquareFootage = value; }
}
[Display(Name = "First Availability")]
public DateTime? WhenAvailable {
get { return Record.WhenAvailable; }
set { Record.WhenAvailable = value; }
}
[Display(Name = "Rent Amount")]
public decimal? RentAmount {
get { return Record.RentAmount; }
set { Record.RentAmount = value; }
}
}
Driver
public class ApartmentPartDriver : ContentPartDriver<ApartmentPart>
{
protected override string Prefix
{
get { return "Apartment"; }
}
//GET
protected override DriverResult Editor(ApartmentPart part, dynamic shapeHelper)
{
return ContentShape("Parts_Apartment_Edit",
() => shapeHelper.EditorTemplate(
TemplateName: "Parts/Apartment",
Model: part,
Prefix: Prefix));
}
//POST
protected override DriverResult Editor(ApartmentPart part, IUpdateModel updater, dynamic shapeHelper)
{
updater.TryUpdateModel(part, Prefix, null, null);
return Editor(part, shapeHelper);
}
}
Handler
public class ApartmentPartHandler : ContentHandler {
public ApartmentPartHandler(IRepository<ApartmentPartRecord> repository)
{
Filters.Add(StorageFilter.For(repository));
}
}
Your error message explains this pretty clearly:
System.Data.SqlClient.SqlException: Cannot insert the value NULL into column 'FloorPlanName', table 'Orchard.dbo.CommunityWebsiteSolutions_ApartmentPartRecord'; column does not allow nulls. INSERT fails.
Your problem occurs because:
You are using nullable types such as string and int? types in your Record class, which means you want to allow nulls.
Yet, you are specifying in your DB migration that you want to disallow nulls.
And when C# instantiates your Record class, it initializes the fields using the default value, which is null for nullable types.
You can do one of the following:
Make your DB columns nullable (remove NotNull)
Make your Record class use non-nullable types (for example, int instead of int?). Note that this is not an option for reference types such as string.
Give non-null default values to the fields of your Record class by giving the class a constructor. This is arguably bad practice since you will be calling virtual properties in a base class, but seems to be ok in NHibernate.
Give non-null default values to the fields of your Record class by giving your part an OnInitializing handler, which would be placed in your Handler class.
UPDATE
You commented that you are expecting the fields to be filled in by the TryUpdateModel in the Editor function of your driver class. This does eventually happen, but the actual sequence of events that occurs is this (you can see this in the CreatePOST method of Orchard.Core.Contents.Controllers.AdminController):
ContentManager.New() with the content type ID to create content item in memory. This step calls OnInitializing for the appropriate content parts for the content type, which are defined in handlers.
ContentManager.Create() with the content item in Draft Mode. This step actually tries to persist the item to the DB once.
ContentManager.UpdateEditor(). This is the call that actually calls Editor of the appropriate driver for the content type.
Check the ModelState and roll back the transaction if anything has failed.
Step 2 will fail if you have NULL values in columns marked NotNull, because the fields have default values at that point. For these columns, you have to fill them in before step 2 by using OnInitializing or by using a constructor on your Record part.
In other words, TryUpdateModel in your driver is actually applying changes directly to the entity that has already been Created and is now attached to the NHibernate session.

MVC4 WebAPI reject invalid enum values

How can I make JSON.NET / MVC 4 WebAPI reject integer values for which the enum has no member? Eg:
If I have this model:
public enum Colour { Red = 1 };
public class Model
{
public Colour Colour { get; set; }
}
Model Post(Model model)
{
// model.Colour could be 99, 34234234, 0 etc, etc
}
If I post { Color: 9999 }, I end up with a model where model.Color = 999 and I want to return a Bad Request status code instead.
It turns out the EnumDataTypeAttribute, which comes with the out-of-the-box ValidationAttributes in the System.ComponentModel.DataAnnotations namespace, does an Enum.Defined check.
Once I applied this attribute to my view model, out-of-range integer values failed validation:
public enum Color {Red = 1, Blue = 2}
public class Car
{
[EnumDataType(typeof(Color))]
public Color Color { get; set; }
}
Note: values that can be parsed into integers that are defined on the enum will still pass validation due to the default behavior of enum model binding. This means, for example, true will be parsed as 1, which would be valid for this enum. I assume characters that can be mapped to integers will also work.
If you only want one flavor of enum parsing to work, be it string or integer, consider using that specific type on your view model, and then write a custom ValidationAttribute that takes in the enum type, validating that the string or integer on your view model matches a value in the enum.
One option is to write a validator:
public class ValidEnumValueAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
Type enumType = value.GetType();
bool valid = Enum.IsDefined(enumType, value);
if (!valid)
{
return new ValidationResult(String.Format("{0} is not a valid value for type {1}", value, enumType.Name));
}
return ValidationResult.Success;
}
}
Use as:
public enum Color {Red = 1, Blue = 2}
public class Car
{
[ValidEnumValue]
public Color Color { get; set; }
}
In the controller, ModelState.IsValid would be false.
You can also throw a ValidationException, if you really want to fail the request, but I'm not quite sure that is how they should be used.