Fluent Validation - Custom Guid Validator not triggering - asp.net

I have a custom FleuntValidation validationrule that checks if a Guid is valid;
public static class GuidValidator
{
private static Regex isGuid = new Regex(#"^(\{){0,1}[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}(\}){0,1}$", RegexOptions.Compiled);
public static bool IsGuid(string candidate)
{
if (candidate != null)
{
if (isGuid.IsMatch(candidate))
{
return true;
}
}
return false;
}
}
I want to use this to check a Guid Property for a valid Guid and then return a custom error message.
RuleFor(x => x.ShiftId).Must(guid => GuidValidator.IsGuid(guid.ToString())).WithMessage("StopShift.ShiftId.GuidNotValid()");
However, my rule does not get hit, because i guess the Guid is not valid and some built in check runs before it. How would i disable the built-in check so my custom rule gets reached?

It depends on what your stack looks like. A Guid will not be instantiated with an "invalid" value.
If you want to cater for a use case where the value in question can either be a valid or invalid Guid I suggest you model it as a string.
e.g.
[Validator(typeof(FooRequestValidator))]
public class FooRequest
{
public string Bar { get; set; }
}
public class FooRequestValidator : AbstractValidator<FooRequest>
{
public FooRequestValidator()
{
RuleFor(x => x.Bar)
.Must(ValidateBar).WithErrorCode("Not a guid");
}
private bool ValidateBar(string bar)
{
return Guid.TryParse(bar, out var result);
}
}

Property in the class is a non-nullable Guid.
public Guid ProductId {get;set;}
Here is what I use
RuleFor(product => product.ProductId).Must(BeAValidGuid).When(product => product.ProductId != null);
With the Custom Rule
private bool BeAValidGuid(Guid unValidatedGuid)
{
try
{
if(unValidatedGuid != Guid.Empty && unValidatedGuid != null )
{
if (Guid.TryParse(unValidatedGuid.ToString(), out Guid validatedGuid))
{
return true;
}
else
{
return false;
}
}
else
{
return false;
}
}
catch (Exception)
{
throw;
}
}

Related

Combine [FromBody] with [FromHeader] in WebAPI in .net Core 3.0

we are writing some API which required sessionId in header and some other data in body.
Is it possible to have only one class automatically parsed partially from header and from body?
Something like:
[HttpGet("messages")]
[Produces("application/json")]
[Consumes("application/json")]
[Authorize(Policy = nameof(SessionHeaderKeyHandler))]
public async Task<ActionResult<MessageData>> GetPendingClockInMessages(PendingMessagesData pendingMessagesRequest)
{
some body...
}
with request class like:
public class PendingMessagesData
{
[FromHeader]
public string SessionId { get; set; }
[FromBody]
public string OrderBy { get; set; }
}
I know, it is possible to do this, but it means, that I have to pass SessionId into the other methods as a parameter, instead of pass only one object. And we would have to do that in every API call.
public async Task<ActionResult<MessageData>> GetPendingClockInMessages(
[FromHeader] string sessionId,
[FromBody] PendingMessagesData pendingMessagesRequest)
{
some body...
}
Thank you,
Jakub
we are writing some API which required sessionId in header and some other data in body. Is it possible to have only one class automatically parsed partially from header and from body
Your GetPendingClockInMessages is annotated with a [HttpGet("messages")]. However, a HTTP GET method has no body at all. Also, it can't consume application/json. Please change it to HttpPost("messages")
Typically, SessionId is not passed in header of Session: {SessionId} like other HTTP headers. Session are encrypted via IDataProtector. In other words, you can't get it by Request.Headers["SessionId"].
Apart from the above two facts, you can create a custom model binder to do that.
Since the Session doesn't come from header directly, let's create a custom [FromSession] attribute to replace your [FromHeader]
public class FromSessionAttribute : Attribute, IBindingSourceMetadata
{
public static readonly BindingSource Instance = new BindingSource("FromSession", "FromSession Binding Source", true, true);
public BindingSource BindingSource { get { return FromSessionAttribute.Instance; } }
}
And since you're consuming application/json, let's create a binder as below:
public class MyModelBinder : IModelBinder
{
private readonly JsonOptions jsonOptions;
public MyModelBinder(IOptions<JsonOptions> jsonOptions)
{
this.jsonOptions = jsonOptions.Value;
}
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
var type = bindingContext.ModelType;
var pis = type.GetProperties();
var result= Activator.CreateInstance(type);
var body= bindingContext.ActionContext.HttpContext.Request.Body;
var stream = new System.IO.StreamReader(body);
var json = await stream.ReadToEndAsync();
try{
result = JsonSerializer.Deserialize(json, type, this.jsonOptions.JsonSerializerOptions);
} catch(Exception){
// in case we want to pass string directly. if you don't need this feature, remove this branch
if(pis.Count()==2){
var prop = pis
.Where(pi => pi.PropertyType == typeof(string) )
.Where(pi => !pi.GetCustomAttributesData().Any(ca => ca.AttributeType == typeof(FromSessionAttribute)))
.FirstOrDefault();
if(prop != null){
prop.SetValue( result ,json.Trim('"'));
}
} else{
bindingContext.ModelState.AddModelError("", $"cannot deserialize from body");
return;
}
}
var sessionId = bindingContext.HttpContext.Session.Id;
if (string.IsNullOrEmpty(sessionId)) {
bindingContext.ModelState.AddModelError("sessionId", $"cannot get SessionId From Session");
return;
} else {
var props = pis.Where(pi => {
var attributes = pi.GetCustomAttributesData();
return attributes.Any( ca => ca.AttributeType == typeof(FromSessionAttribute));
});
foreach(var prop in props) {
prop.SetValue(result, sessionId);
}
bindingContext.Result = ModelBindingResult.Success(result);
}
}
}
How to use
Decorate the property with a FromSession to indicate that we want to get the property via HttpContext.Sessino.Id:
public class PendingMessagesData
{
[FromBody]
public string OrderBy { get; set; } // or a complex model: `public MySub Sub{ get; set; }`
[FromSession]
public string SessionId { get; set; }
}
Finally, add a modelbinder on the action method parameter:
[HttpPost("messages")]
[Produces("application/json")]
[Consumes("application/json")]
public async Task<ActionResult> GetPendingClockInMessages([ModelBinder(typeof(MyModelBinder))]PendingMessagesData pendingMessagesRequest)
{
return Json(pendingMessagesRequest);
}
Personally, I would prefer another way, i.e, creating a FromSessionBinderProvider so that I can implement this without too much effort. :
public class FromSessionDataModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var sessionId = bindingContext.HttpContext.Session.Id;
if (string.IsNullOrEmpty(sessionId)) {
bindingContext.ModelState.AddModelError(sessionId, $"cannot get SessionId From Session");
} else {
bindingContext.Result = ModelBindingResult.Success(sessionId);
}
return Task.CompletedTask;
}
}
public class FromSessionBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null) { throw new ArgumentNullException(nameof(context)); }
var hasFromSessionAttribute = context.BindingInfo?.BindingSource == FromSessionAttribute.Instance;
return hasFromSessionAttribute ?
new BinderTypeModelBinder(typeof(FromSessionDataModelBinder)) :
null;
}
}
(if you're able to remove the [ApiController] attribute, this way is more easier).

aspnet core controller datetime parameter bypasses modelvalid check

I have a number of controllers of this form:
public IActionResult GetSomething(int id, DateTime from) {
...
}
The id and from parameters are given on the query as query parameters. If id is not supplied, the ModelValid state is set to false. But if from is not supplied, ModelValid is true and from is set to 1900-01-01 00:00:00 (DateTime.Min).
How do I make ModelState false if a wanted DateTime parameter isn't supplied?
I decided to go for implementing a DateTime model binder. The following code will not set IsValid=true on ModelState if the DateTime argument is missing. DateTime? (nullable DateTime) is handled fine, but again, if the query parameter is missing, IsValid is set to false instead of setting the parameter to a default value.
First the DateTimeModelBinderProvider:
public class DateTimeModelBinderProvider : IModelBinderProvider
{
/// <inheritdoc />
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (!context.Metadata.IsComplexType)
{
// We can handle DateTime and nullable DateTime
if ((context.Metadata.ModelType == typeof(DateTime)) ||
(context.Metadata.IsNullableValueType && context.Metadata.UnderlyingOrModelType == typeof(DateTime)))
return new DateTimeModelBinder(context.Metadata.ModelType);
}
return null;
}
}
Next the DateTimeModelBinder. Most of the code is copied verbatim from github. Some of it could be left out, but it works as it is:
public class DateTimeModelBinder : IModelBinder
{
private readonly TypeConverter _typeConverter;
public DateTimeModelBinder(Type type)
{
if (type == null)
{
throw new ArgumentNullException(nameof(type));
}
_typeConverter = TypeDescriptor.GetConverter(type);
}
/// <inheritdoc />
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (valueProviderResult == ValueProviderResult.None)
{
// Do not accept an empty value provider result as being ok for DateTime (is ok for DateTime?)
bindingContext.ModelState.TryAddModelError(
bindingContext.ModelName,
bindingContext.ModelMetadata.ModelBindingMessageProvider.ValueMustNotBeNullAccessor(
valueProviderResult.ToString()));
// no entry
return Task.CompletedTask;
}
bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult);
try
{
var value = valueProviderResult.FirstValue;
object model = null;
if (!string.IsNullOrWhiteSpace(value))
{
model = new DateTimeConverter().ConvertFrom(
context: null,
culture: valueProviderResult.Culture,
value: value);
}
if (bindingContext.ModelType == typeof(string))
{
var modelAsString = model as string;
if (bindingContext.ModelMetadata.ConvertEmptyStringToNull &&
string.IsNullOrEmpty(modelAsString))
{
model = null;
}
}
// When converting newModel a null value may indicate a failed conversion for an otherwise required
// model (can't set a ValueType to null). This detects if a null model value is acceptable given the
// current bindingContext. If not, an error is logged.
if (model == null && !bindingContext.ModelMetadata.IsReferenceOrNullableType)
{
bindingContext.ModelState.TryAddModelError(
bindingContext.ModelName,
bindingContext.ModelMetadata.ModelBindingMessageProvider.ValueMustNotBeNullAccessor(
valueProviderResult.ToString()));
return Task.CompletedTask;
}
else
{
bindingContext.Result = ModelBindingResult.Success(model);
return Task.CompletedTask;
}
}
catch (Exception exception)
{
var isFormatException = exception is FormatException;
if (!isFormatException && exception.InnerException != null)
{
// TypeConverter throws System.Exception wrapping the FormatException,
// so we capture the inner exception.
exception = ExceptionDispatchInfo.Capture(exception.InnerException).SourceException;
}
bindingContext.ModelState.TryAddModelError(
bindingContext.ModelName,
exception,
bindingContext.ModelMetadata);
// Were able to find a converter for the type but conversion failed.
return Task.CompletedTask;
}
}
}
Also remember to activate it. I insert it at the start of the provider list to ensure my DateTime provider is used in preference of the default handler:
var mvc = services.AddMvc(config => {
config.ModelBinderProviders.Insert(0, new DateTimeModelBinderProvider());
});
You can solve this issue by creating a model that has a validation attribute for the 'From' property.
I havent tested out the code. But code should be like:
public class Model
{
public int Id { get; set; }
[DateTimeShouldHaveValue]
public DateTime From { get; set; }
}
public class DateTimeShouldHaveValueAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
if (value == null)
return false;
var dateTimeTmp = value.ToString();
DateTime dateTime;
DateTime.TryParse(dateTimeTmp, out dateTime);
if (dateTime == DateTime.MinValue)
return false;
return true;
}
}
public IActionResult GetSomething(Model model)
{
}

How do I find settable properties with Json.NET using a ContractResolver? [duplicate]

Is there a way to ignore get-only properties using the Json.NET serializer but without using JsonIgnore attributes?
For example, I have a class with these get properties:
public Keys Hotkey { get; set; }
public Keys KeyCode
{
get
{
return Hotkey & Keys.KeyCode;
}
}
public Keys ModifiersKeys
{
get
{
return Hotkey & Keys.Modifiers;
}
}
public bool Control
{
get
{
return (Hotkey & Keys.Control) == Keys.Control;
}
}
public bool Shift
{
get
{
return (Hotkey & Keys.Shift) == Keys.Shift;
}
}
public bool Alt
{
get
{
return (Hotkey & Keys.Alt) == Keys.Alt;
}
}
public Modifiers ModifiersEnum
{
get
{
Modifiers modifiers = Modifiers.None;
if (Alt) modifiers |= Modifiers.Alt;
if (Control) modifiers |= Modifiers.Control;
if (Shift) modifiers |= Modifiers.Shift;
return modifiers;
}
}
public bool IsOnlyModifiers
{
get
{
return KeyCode == Keys.ControlKey || KeyCode == Keys.ShiftKey || KeyCode == Keys.Menu;
}
}
public bool IsValidKey
{
get
{
return KeyCode != Keys.None && !IsOnlyModifiers;
}
}
Do I need to add [JsonIgnore] to all of them (I also have many other classes), or there is better way to ignore all get-only properties?
You can do this by implementing a custom IContractResolver and using that during serialization. If you subclass the DefaultContractResolver, this becomes very easy to do:
class WritablePropertiesOnlyResolver : DefaultContractResolver
{
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
IList<JsonProperty> props = base.CreateProperties(type, memberSerialization);
return props.Where(p => p.Writable).ToList();
}
}
Here is a test program demonstrating how to use it:
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
class Program
{
static void Main(string[] args)
{
Widget w = new Widget { Id = 2, Name = "Joe Schmoe" };
JsonSerializerSettings settings = new JsonSerializerSettings
{
ContractResolver = new WritablePropertiesOnlyResolver()
};
string json = JsonConvert.SerializeObject(w, settings);
Console.WriteLine(json);
}
}
class Widget
{
public int Id { get; set; }
public string Name { get; set; }
public string LowerCaseName
{
get { return (Name != null ? Name.ToLower() : null); }
}
}
Here is the output of the above. Notice that the read-only property LowerCaseName is not included in the output.
{"Id":2,"Name":"Joe Schmoe"}
Use the OptIn mode of JSON.net and you'll only need to decorate the properties you want to serialize. This isn't as good as automatically opting out all read only properties, but it can save you some work.
[JsonObject(MemberSerialization.OptIn)]
public class MyClass
{
[JsonProperty]
public string serializedProp { get; set; }
public string nonSerializedProp { get; set; }
}
Udate: Added another possibility using reflection
If the above solution still isn't quite what you're looking for, you could use reflection to make dictionary objects which would then be serialized. Of course the example below will only work for simple classes, so you would need to add recursion if your classes contain other classes. This should at least point you in the right direction.
The subroutine to put the filtered result into a dictionary:
private Dictionary<String, object> ConvertToDictionary(object classToSerialize)
{
Dictionary<String, object> resultDictionary = new Dictionary<string, object>();
foreach (var propertyInfo in classToSerialize.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
if (propertyInfo.CanWrite) resultDictionary.Add(propertyInfo.Name, propertyInfo.GetValue(classToSerialize, null));
}
return resultDictionary;
}
A snippet showing its use:
SampleClass sampleClass = new SampleClass();
sampleClass.Hotkey = Keys.A;
var toSerialize = ConvertToDictionary(sampleClass);
String resultText = JsonConvert.SerializeObject(toSerialize);
You can use a contract resolver like this:
public class ExcludeCalculatedResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
property.ShouldSerialize = _ => ShouldSerialize(member);
return property;
}
internal static bool ShouldSerialize(MemberInfo memberInfo)
{
var propertyInfo = memberInfo as PropertyInfo;
if (propertyInfo == null)
{
return false;
}
if (propertyInfo.SetMethod != null)
{
return true;
}
var getMethod = propertyInfo.GetMethod;
return Attribute.GetCustomAttribute(getMethod, typeof(CompilerGeneratedAttribute)) != null;
}
}
It will exclude calculated properties but include C#6 get only properties and all properties with a set method.
Json.net does have the ability to conditionally serialize properties without an attribute or contract resolver. This is especially useful if you don't want your project to have a dependency on Json.net.
As per the Json.net documentation
To conditionally serialize a property, add a method that returns boolean with
the same name as the property and then prefix the method name with
ShouldSerialize. The result of the method determines whether the
property is serialized. If the method returns true then the property
will be serialized, if it returns false then the property will be
skipped.

Entity Framework : A referential integrity constraint violation occurred when I do EntityState.Modified

I create a website in ASP.NET MVC 4.
It's been so long that I have this error : A referential integrity constraint violation occurred.
It's when i tried to update a entry in my db. Well when someone want to buy a product, i need to change the user who buy.
I have two one to many relationship.
EDIT
This my User class :
public class User
{
private string email, password, firstname, lastname;
private Adress shippingAdress, billingAdress;
private bool isConnected;
private List<Product> products;
//private List<Auction> auctions;
private long idShippingA, idBillingA;
public User()
{
products = new List<Product>();
}
/* public List<Auction> Auctions
{
get { return auctions; }
set { auctions = value; }
}
public void AddAuction(Auction auction)
{
if (auction != null)
auctions.Add(auction);
}*/
public long IdBillingA
{
get { return idBillingA; }
set
{
if (value < 0)
throw new ArgumentException("The id of the billing adress should not be negative");
idBillingA = value;
}
}
public long IdShippingA
{
get { return idShippingA; }
set
{
if (value < 0)
throw new ArgumentException("The id of the shipping adress should not be negative");
idShippingA = value;
}
}
public bool IsConnected
{
get { return isConnected; }
set { isConnected = value; }
}
public virtual List<Product> Products
{
get { return products; }
set
{
if (value == null)
throw new ArgumentNullException("The list of product should not be null");
products = value;
}
}
public Adress BillingAdress
{
get { return billingAdress; }
set
{
if (value == null)
throw new ArgumentNullException("The billing adress should not be null");
billingAdress = value;
}
}
public Adress ShippingAdress
{
get { return shippingAdress; }
set
{
if (value == null)
throw new ArgumentNullException("The shipping adress should not be null");
shippingAdress = value;
}
}
public string Password
{
get { return password; }
set
{
if (string.IsNullOrWhiteSpace(value))
throw new ArgumentException("The password should not be null or empty");
password = value;
}
}
public string Email
{
get { return email; }
set
{
if (string.IsNullOrWhiteSpace(value))
throw new ArgumentException("The email should not be null or empty");
email = value;
}
}
public string Lastname
{
get { return lastname; }
set
{
if (string.IsNullOrWhiteSpace(value))
throw new ArgumentException("The lastname should not be null or empty");
lastname = value;
}
}
public string Firstname
{
get { return firstname; }
set
{
if (string.IsNullOrWhiteSpace(value))
throw new ArgumentException("The fistname should not be null or empty");
firstname = value;
}
}
}
}
EDIT
This is my Product Class :
public class Product
{
private long id, strategyId;
private User userWhoSell;
private User userWhoBuy;
private string userWhoSellId, userWhoBuyId, name, description, urlPicture, isBought;
public string IsBought
{
get { return isBought; }
set { isBought = value; }
}
private SellStrategy strategy;
private float price;
private string strategyString;
public Product()
{
isBought = "F";
}
public float Price
{
get { return price; }
set
{
if (value < 0)
throw new ArgumentException("The price should not be negative");
price = value;
}
}
public string StrategyString
{
get { return strategyString; }
set
{
if (string.IsNullOrWhiteSpace(value))
throw new ArgumentException("The strategy string should not be null, empty or with white space");
strategyString = value;
}
}
public long StrategyId
{
get { return strategyId; }
set
{
if (value < 0)
throw new ArgumentException("The strategy id should not be negative");
strategyId = value;
}
}
public SellStrategy Strategy
{
get { return strategy; }
set
{
if (value == null)
throw new ArgumentNullException("The strategy should not be null");
strategy = value;
}
}
public string SellerId
{
get { return userWhoSellId; }
set
{
if (string.IsNullOrWhiteSpace(value))
throw new ArgumentException("The user id should not be null, empty or with white space");
userWhoSellId = value;
}
}
public string BuyerId
{
get { return userWhoBuyId; }
set
{
userWhoBuyId = value;
}
}
public string UrlPicture
{
get { return urlPicture; }
set
{
if (string.IsNullOrWhiteSpace(value))
throw new ArgumentException("The picture's url should not be null, empty or with white space");
urlPicture = value;
}
}
public long Id
{
get { return id; }
set
{
if (value < 0)
throw new ArgumentException("The id should not be negative");
id = value;
}
}
public string Description
{
get { return description; }
set
{
if (string.IsNullOrWhiteSpace(value))
throw new ArgumentException("The description should not be null, empty or with white space");
description = value;
}
}
public string Name
{
get { return name; }
set
{
if (string.IsNullOrWhiteSpace(value))
throw new ArgumentException("The name should not be null, empty or with white space");
name = value;
}
}
public virtual User Buyer
{
get { return userWhoBuy; }
set
{
userWhoBuy = value;
}
}
public virtual User Seller
{
get { return userWhoSell; }
set
{
if (value == null)
throw new ArgumentNullException("The user should not be null");
userWhoSell = value;
}
}
}
EDIT
this is my context :
public class Context : DbContext
{
public Context(string connString)
: base(connString) { }
public DbSet<User> Users { get; set; }
public DbSet<Adress> Adress { get; set; }
public DbSet<Product> Products { get; set; }
//public DbSet<Auction> Auctions { get; set; }
public DbSet<SellStrategy> Strategies { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<User>().HasKey<string>(u => u.Email);
modelBuilder.Entity<Adress>().HasKey<long>(a => a.Id);
modelBuilder.Entity<Product>().HasKey<long>(p => p.Id);
modelBuilder.Entity<Auction>().HasKey<long>(au => au.Id);
modelBuilder.Entity<Product>()
.HasRequired(p => p.Seller)
.WithMany(u => u.Products)
.HasForeignKey(p => p.SellerId)
.WillCascadeOnDelete(false);
// Otherwise you might get a "cascade causes cycles" error
modelBuilder.Entity<Product>()
.HasOptional(p => p.Buyer)
.WithMany() // No reverse navigation property
.HasForeignKey(p => p.BuyerId)
.WillCascadeOnDelete(false);
// modelBuilder.Entity<Auction>().HasMany<User>(au => au.Users).WithMany(u => u.Auctions);
// modelBuilder.Entity<Auction>().HasRequired(au => au.Product).WithMany().WillCascadeOnDelete(false);
modelBuilder.Entity<User>().HasRequired(u => u.BillingAdress).WithMany().HasForeignKey(u => u.IdBillingA).WillCascadeOnDelete(false);
modelBuilder.Entity<User>().HasRequired(u => u.ShippingAdress).WithMany().HasForeignKey(u => u.IdShippingA).WillCascadeOnDelete(false);
modelBuilder.Entity<User>().Ignore(u => u.IsConnected);
modelBuilder.Entity<Product>().HasRequired<SellStrategy>(p => p.Strategy).WithMany().WillCascadeOnDelete(false);
modelBuilder.Entity<Product>().Ignore(p => p.StrategyString);
modelBuilder.Entity<Product>().Ignore(p => p.Price);
modelBuilder.Entity<SellStrategy>().Property(s => s.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
modelBuilder.Entity<AuctionSelling>().HasKey<long>(a => a.Id);
modelBuilder.Entity<AuctionSelling>().Map(m =>
{
m.MapInheritedProperties();
m.ToTable("AuctionSelling");
});
modelBuilder.Entity<DirectSelling>().HasKey<long>(d => d.Id);
modelBuilder.Entity<DirectSelling>().Map(m =>
{
m.MapInheritedProperties();
m.ToTable("DirectSelling");
});
modelBuilder.Entity<SellStrategy>().Property(s => s.SoldDate).IsOptional();
}
}
EDIT
And when I try to update my product :
public void UpDateProduct(Product product)
{
using(Context context = new Context(connectionString))
{
try
{
Product p = GetById(product.Id);
//context.Products.Attach(p);
p.BuyerId = product.BuyerId;
p.Buyer = product.Buyer;
p.IsBought = "T";
context.Entry(p).State = EntityState.Modified;
context.SaveChanges();
}
catch (Exception ex)
{
}
}
}
This is the line
context.Entry(p).State = EntityState.Modified;
that provoke the error.
This is the full error
A referential integrity constraint violation occurred: The property value(s) of 'User.Email' on one end of a relationship do not match the property value(s) of 'Product.BuyerId' on the other end.
When I do just context.saveChanges, nothing occurs...
I don't know what to do... I want to cry ^^...
Thank you a lot in advance !
This is caused by setting reference navigation properties in the constructor. Specifically:
userWhoSell = new User();
userWhoBuy = new User();
This means that a Product starts out having two dummy User objects that you have to replace for them to become meaningful. Otherwise EF may try to save these dummy objects. I'm pretty sure that by...
context.Entry(p).Entity.Buyer = product.Buyer;
...you actually set such an empty User object, which has a key value no email, not the key value Product.BuyerId you apparently set before.
I'd say: remove most of these initializations in the entities' constructors. Initializing collections makes sense. Sometimes setting default values does (but not primary key value), but reference navigation properties never. See also: EF codefirst : Should I initialize navigation properties?
Side note: neither would I use property setters for validations. That's what data annotations are for. These exceptions may interfere with EF when trying to materialize entities from the database.

xml serialization error on bool types

I am trying to find out how to solve the problem for serializing a type of bool from a camel case string.
I have the following xml
<Root>
<BoolElement>
False
</BoolElement>
</Root>
and the following class
[XmlRoot("Root")]
public class RootObj{
[XmlElement("BoolElement")]
public bool BoolElement{get;set;}
}
this will produce an error.
If I use the same class and rename the "False" to "false" it will work. The problem is that I can't edit the xml.
Does anyone know how can I solve this?
You could use a backing field to aid for the deserialization of this invalid XML (I say invalid because according to the xsd:boolean schema False is an invalid value):
[XmlRoot("Root")]
public class RootObj
{
[XmlElement("BoolElement")]
public string BackingBoolElement
{
set
{
BoolElement = bool.Parse(value.ToLower());
}
get
{
return BoolElement.ToString();
}
}
[XmlIgnore]
public bool BoolElement { get; set; }
}
False is not a valid value for an xsd:boolean (but as you note false and 0 are) - if you cannot change the source data, then you could have a separate property purely for XML serialisation:
[XmlRoot("Root")]
public class RootObj{
[XmlElement("BoolElement")]
public string BoolElementForSerialization
{
get
{
return (this.BoolElement ? "True" : "False");
}
set
{
this.BoolElement = (string.Compare(value, "false", StringComparison.OrdinalIgnoreCase) != 0);
}
}
[XmlIgnore]
public bool BoolElement{get;set;}
}
I created a new Boolean type that can deserialize from any string. It may not be perfect but it suited my needs at the time.
For the class you want to use simply change the data type from bool to SerializableBoolean:
[XmlRoot("Root")]
public class RootObj{
[XmlElement("BoolElement")]
public SerializableBoolean BoolElement{get;set;}
}
You can then use the BoolElement property like any normal bool data type:
RootObj myObj = new RootObj();
if (myObj.BoolElement) { ... }
Here is the code for the SerializableBoolean class, note this code only handles deserializing, serializing to xml wasn't required for my purposes and so not implemented.
[System.Diagnostics.DebuggerDisplay("{Value}")]
public struct SerializableBoolean: System.Xml.Serialization.IXmlSerializable
{
private bool Value { get; set; }
public override bool Equals(object obj)
{
if (obj is string stringBoolean)
{
bool.TryParse(stringBoolean, out bool boolean);
return Value == boolean;
}
else if (obj is bool boolean)
{
return Value == boolean;
}
else if (obj is SerializableBoolean serializableBoolean)
{
return Value == serializableBoolean.Value;
}
else
{
return Value == Convert.ToBoolean(obj);
}
}
public override int GetHashCode()
{
return -1937169414 + Value.GetHashCode();
}
public XmlSchema GetSchema()
{
throw new NotImplementedException();
}
public void ReadXml(XmlReader reader)
{
Value = Convert.ToBoolean(reader.ReadElementContentAsString());
}
public void WriteXml(XmlWriter writer)
{
throw new NotImplementedException();
}
public static bool operator ==(SerializableBoolean obj1, bool obj2)
{
return obj1.Value.Equals(obj2);
}
public static bool operator !=(SerializableBoolean obj1, bool obj2)
{
return !obj1.Value.Equals(obj2);
}
public static implicit operator SerializableBoolean(string value)
{
return new SerializableBoolean() { Value = Convert.ToBoolean(value) };
}
public static implicit operator SerializableBoolean(bool value)
{
return new SerializableBoolean() { Value = value };
}
public static implicit operator bool(SerializableBoolean b)
{
return b.Value;
}
}

Resources