.NET CORE API: Is it possible to conditionally validate a model depending on the http verb used to submit it? - fluentvalidation

I'm very new to the FluentValidation and I'd like to validate my model in different ways depending on which verb was used to submit it.
Given a very simple class, I'd like to ignore the ID property on a POST but ensure it's been provided on a PUT. Is this something the FluentValidation can do?
public class CategoryModel
{
public int ID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
I'm guessing I have to get the HttpContext over to the validators so I can determine the http method used, but I don't want to re-invent the wheel if there's already a built-in way.
Thanks!!

I think I've come up with a solution by passing the context to my validator. However, if something looks wrong or out of place, please let me know.
public class CategoryModel
{
public int ID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
public class CategoryModelValidator : AbstractValidator<CategoryModel>
{
public CategoryModelValidator(IHttpContextAccessor context)
{
if (context.HttpContext.Request.Method == HttpMethods.Post)
RuleFor(x => x.ID).Empty();
if (context.HttpContext.Request.Method == HttpMethods.Put)
RuleFor(x => x.ID).NotEmpty();
RuleFor(x => x.Name).Length(1, 30);
}
}
Passing the context was easier than I thought. All I had to do was register it in my ConfigureServices method with services.AddHttpContextAccessor() and DI takes care of the rest.
To be honest, this is a pretty handy validation tool

Related

Masstransit RabbitMQ publisher fails to publish "correct" contract

I have been playing around for a while with Masstransit and RabbitMQ creating message contracts to enable communication between two .NET core web API's and I have stumbled across the following.
Suppose I have the contract specified below in both projects:
public class Profile : BaseEntity
{
public string Name { get; set; }
public string Description { get; set; }
public bool IsActive { get; set; }
public DefaultMode Mode { get; set; }
public string SuperMode { get; set; }
public ExtraClient? Client { get; set; }
}
Whenever I publish a message
await _publishEndPoint.Publish<Profile>(profileUpdate);
The other end (consumer on the receiver/subscriber API) is able to get only primitive type fields, for example, Name, Description and IsActive.
The rest of the fields are parsed as null even though I am well aware that prior publishing the properties of profileUpdate have proper values.
Has anyone faced something similar?
Is it because of the inheritance?
Kind regards,
Edit#1: Syntax
Edit#2: Posting more information
As requested here is my consumer class:
public class ProfileUpdateConsumer : IConsumer<Profile>
{
private readonly IProfilesHub _profileHub;
public ProfileUpdateConsumer(IProfilesHub profileHub)
{
_profileHub = profileHub;
}
public async Task Consume(ConsumeContext<Profile> context)
{
await _profileHub.BroadcastProfileUpdate(context.Message);
}
}
And here is my publisher class:
public class ProfilePublisher : IPublisher
{
private readonly IPublishEndpoint _publishEndPoint;
public ProfilePublisher(IPublishEndpoint publishEndpoint)
{
_publishEndPoint = publishEndpoint;
}
public async Task PublishProfileUpdate(object profileUpdate)
{
await _publishEndPoint.Publish<Profile>(profileUpdate);
}
}
I know it's been a while, but for anyone who might come across this.
After discussing with #Chris Patterson ,
I did a manual conversion of the one data type to the other, and made sure that all fields are there.
This worked, even though I still feel quite puzzled as to what was causing this behavior.

Automapper not mapping between two objects (which are virtually the same for all intents and purposes)

Yes, this is ANOTHER "Automapper not mapping" question. Either something broke or I'm going the stupid way about it. I'm building a webapp with ASP.NET Core 2.1 using AutoMapper 3.2.0 (latest stable release at the time) though I have tested with 3.1.0 with no luck either.
Question
Simple object to be mapped to another. For the sake of testing and trials, these are now EXACTLY the same, yet still automapper gives:
AutoMapperMappingException: Missing type map configuration or unsupported mapping.
Mapping types:
NotificationModel -> NotificationViewModel
ProjectName.Models.Dashboard.NotificationModel -> ProjectName.Models.Dashboard.NotificationViewModel
The strange thing is, I have previously mapped this model set 7 ways to sunday in the Startup.cs file with the only thing changing is my facial expression. Other maps work as indicated using similar, if not the same code for them.
The Models
NotificationModel.cs
public class NotificationModel
{
public int Id { get; set; }
public string Content { get; set; }
public DateTime CreateTS { get; set; }
public bool FlagRead { get; set; }
public bool FlagSticky { get; set; }
public bool FlagReceipt { get; set; }
public string ReceiptContact { get; set; }
public string UserId { get; set; }
public bool CANCELLED { get; set; }
}
NotificationViewModel.cs
public class NotificationViewModel
{
public int Id { get; set; }
//Reminder, this model has been amended to exactly represent that of the original model for testing purposes.
public string Content { get; set; }
public DateTime CreateTS { get; set; }
public bool FlagRead { get; set; }
public bool FlagSticky { get; set; }
public bool FlagReceipt { get; set; }
public string ReceiptContact { get; set; }
public string UserId { get; set; }
public bool CANCELLED { get; set; }
}
Startup & Automapper Config
Mapper.Initialize(cfg =>
{
// Some other mappings removed for clarity.
cfg.CreateMap<GroupViewModel, GroupModel>().ReverseMap();
//cfg.CreateMap<EntityViewModel, EntityModel>().ReverseMap().ForAllOtherMembers(opt => opt.Ignore());
cfg.CreateMap<NotificationModel, NotificationViewModel>().ForAllMembers(opt => opt.Ignore());
cfg.CreateMap(typeof(NotificationViewModel), typeof(NotificationModel));
//I even left out the .ReverseMap, for testing purposes.
});
Mapper.AssertConfigurationIsValid();
Usage
NotificationViewModel test = _mapper.Map<NotificationViewModel>(item); << Which is where I receive the exception.
Other Attempts
Ok, so I've been through some more articles explaining different things and subsequently tried the following respectively:
cfg.CreateMap(typeof(NotificationModel), typeof(NotificationViewModel));
cfg.CreateMap<NotificationModel, NotificationViewModel>().ReverseMap().ForAllMembers(opt => opt.Ignore());
cfg.CreateMap<NotificationModel, NotificationViewModel>().ForAllOtherMembers(opt => opt.Ignore());
Along with:
NotificationViewModel test = _mapper.Map<NotificationViewModel>(item);
_mapper.Map(item, typeof(NotificationViewModel), typeof(NotificationModel));
NotificationViewModel existingDestinationObject = new NotificationViewModel();
_mapper.Map<NotificationModel, NotificationViewModel>(item, existingDestinationObject);
I've tried amending the .Map()/.Map<> usage several ways, none of which seemed to yield anything but an exception about not having been configured.
So short of manually writing a conversion for this object (which is simple enough for its purpose), I am in dire need of a solution here. If not to use, then atleast to learn from and help others facing the same.
UPDATE
IT WORKS!
Scanning through the project, I noticed that somewhere in previous documentation - I read about creating a type of "config" class that just inherits from an abstract class called Profile. In this class you will also be able to define your maps, yet what is strange is that I am not able to drop this class and simply use the config maps setup in my Startup.cs file. Automapper will refuse to hold any maps that are not defined in this separate class. The below seems to get me what I need, however I still need an explanation as to why Automapper doesn't function as desired without it:
public class AMConfig : Profile
{
public AMConfig()
{
CreateMap<ManageUserModel, IndexViewModel>();
CreateMap<IndexViewModel, ManageUserModel>();
CreateMap<NotificationViewModel, NotificationModel>().ReverseMap();
CreateMap<List<NotificationViewModel>, List<NotificationModel>>().ReverseMap();
CreateMap<TaskViewModel, TaskModel>().ReverseMap();
}
}
Thanks!
Scanning through the project, I noticed that somewhere in previous documentation - I read about creating a type of "config" class that just inherits from an abstract class called Profile. In this class you will also be able to define your maps, yet what is strange is that I am not able to drop this class and simply use the config maps setup in my Startup.cs file. Automapper will refuse to hold any maps that are not defined in this separate class. The below seems to get me what I need, however I still need an explanation as to why Automapper doesn't function as desired without it:
public class AMConfig : Profile
{
public AMConfig()
{
CreateMap<ManageUserModel, IndexViewModel>();
CreateMap<IndexViewModel, ManageUserModel>();
CreateMap<NotificationViewModel, NotificationModel>().ReverseMap();
CreateMap<List<NotificationViewModel>, List<NotificationModel>>().ReverseMap();
CreateMap<TaskViewModel, TaskModel>().ReverseMap();
}
}

Create a Parent with existing children in EntityFramework core

I am building a Web API and have two models: Task and Feature:
public class Feature
{
[Key]
public long FeatureId { get; set; }
public string Analyst_comment { get; set; }
public virtual ICollection<User_Task> Tasks { get; set; }
public Feature()
{
}
}
public class User_Task
{
[Key]
public long TaskId { get; set; }
public string What { get; set; }
[ForeignKey("FeatureId")]
public long? FeatureId { get; set; }
public User_Task()
{
}
}
I create Tasks first and then create a Feature that combines few of them. Task creation is successful, however while creating a Feature with existing Tasks, my controller throws an error saying the task already exists:
My FeatureController has following method:
//Create
[HttpPost]
public IActionResult Create([FromBody] Feature item)
{
if (item == null)
{
return BadRequest();
}
** It basically expects that I am creating a Feature with brand new tasks, so I guess I will need some logic here to tell EF Core that incoming tasks with this feature already exist **
_featureRepository.Add(item);
return CreatedAtRoute("GetFeature", new { id = item.FeatureId }, item);
}
How to tell EF core that incoming Feature has Tasks that already exist and it just needs to update the references instead of creating new ones?
My context:
public class WebAPIDataContext : DbContext
{
public WebAPIDataContext(DbContextOptions<WebAPIDataContext> options)
: base(options)
{
}
public DbSet<User_Task> User_Tasks { get; set; }
public DbSet<Feature> Features { get; set; }
}
And repo:
public void Add(Feature item)
{
_context.Features.Add(item);
_context.SaveChanges();
}
When calling Add on a DBSet with a model that was not loaded from EF, it thinks it is untracked and will always assume it is new.
Instead, you need to load the existing record from the dbcontext and map the properties from the data passed into the API to the existing record. Typically that is a manual map from parameter object to domain. Then if you return an object back, you would map that new domain object to a DTO. You can use services like AutoMapper to map the domain to a DTO. When you're done mapping, you only need to call SaveChanges.
Generally speaking, loading the record and mapping the fields is a good thing for the security of your API. You wouldn't want to assume that the passed in data is pristine and honest. When you give the calling code access to all the properties of the entity, you may not be expecting them to change all the fields, and some of those fields could be sensitive.

EF Core no .Include() method on DBset

I'm currently completely unable to call .Include() and intellisense (in vscode) doesn't seem to think it exists.
Now after a long time searching the web I've found this:
Not finding .Include() method in my EF implementing Generic repository
which seems to suggest that .Include exists only in System.Data.Entities, which is only available for EF 5 and 6.
So how do i eager load my list property for an entity in EF core?
heres my context
public class Database : DbContext
{
//Set new datasources like this: public DbSet<class> name { get; set; }
public DbSet<Domain.Resource> Resources { get; set; }
public DbSet<Domain.ResourceType> ResourceTypes { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlite("Filename=./something.db");
}
}
Heres the data classes:
public class Resource
{
public int ResourceId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public int ResourceTypeId { get; set; }
public ResourceType ResourceType { get; set; }
}
public class ResourceType
{
public int ResourceTypeId { get; set; }
public string Name { get; set; }
public List<Resource> Resources { get; set; }
}
Then I do something like:
public List<ResourceType> GetAll()
{
var router = new Database();
var result = router.ResourceTypes.Include(rt => rt.Resources); //It's here there's absolutely no .Include method
return result.ToList();
}
Does .Include not exist in EF Core?
It's a direct consequence of a missing reference in the file where I'm making a call to the method (though i'm not quite sure i understand how...)
Anyways, adding:
using Microsoft.EntityFrameworkCore;
like Tseng and Smit suggested, did the trick. (in the file in which i define the function)
Though why that works i have no idea. I thought .include would automatically be available through the DbSet.
Thanks though! :)
Small, late EDIT: as Christian Johansen pointed out in his comment, the reason it needs the import to see the method signature, is that it is an extension method, which is a topic I strongly encourage any up-and-coming C# developer to learn about as it is immensely useful.
If you end up here, a user of EF 6 or below and happen to miss that OP actually mentioned this like I did, you want to add
using System.Data.Entity;
to your class.
Here is a previous answer that is tracking this issue in EF7. It appears it is now 'included'.

Why my EF collection are not lazy?

I'm using EF 4.1 Code first. I have a model of user and a model of setting.
Each time the repository returns a user the Setting is also loaded. I've marked the Setting as virtual all my access modifiers are public LazyLoadingEnabled and ProxyCreationEnabled are enabled by default.
What am I missing?
public class User : BaseEntity
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public virtual ICollection<Setting> Settings { get; set; }
}
public class Setting
{
public int UserID { get; set; }
public int SettingID { get; set; }
public string Value { get; set; }
}
The User might have several setting, so there is a one to many relationship with a foreign key in setting.
The user configuration is
public class UserConfiguration : EntityTypeConfiguration<User>
{
public UserConfiguration()
{
HasKey(u => u.ID);
HasMany(u => u.Settings).WithOptional().HasForeignKey(u => u.UserID);
}
}
and the Setting configuration is:
public class SettingsConfiguration : EntityTypeConfiguration<Setting>
{
public SettingsConfiguration()
{
ToTable("UserSettings");
HasKey(s => new { s.UserID, s.SettingID });
}
}
Lazy loading means the opposite of what you think it means.
With lazy loading (virtual property and defaults)
Settings is not retrieved immediately when querying User
Settings is retrieved when it's accessed for the first time. The DbContext must be open at that time for this to happen; otherwise you'll get an exception
Without lazy loading (non-virtual property and/or explicitly disabled)
Settings is not retrieved immediately when querying User
Settings will never be retrieved automatically; it will return null (which, in my opinion, is a terrible design decision: null is a wrong value and you shouldn't be able to get it)
In both cases, you can load Settings eagerly by using .Include(x => x.Settings), or when needed, by calling context.Entry(user).Collection(x => x.Settings).Load()