I am trying to create a sample validation attribute to learn more about MVC. I have created the validation attribute, but when I run the application the validation attribute is called twice -> before calling the controller and before saving the DBContext. I believe this should be called only once. Can you guide me where am I doing wrong.
Validation Attribute: I am trying to validate whether the property has too many words than the specified maxWords
public class ValidationEx : ValidationAttribute
{
int _maxWords = 1;
public ValidationEx()
: base("{0} has more too many words")
{
_maxWords = 1;
}
public ValidationEx(int maxWords):base("{0} has more too many words")
{
_maxWords = maxWords;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value != null)
{
string data = value as string;
if (data.Split(' ').Length > _maxWords)
{
var errorMessage = FormatErrorMessage(validationContext.DisplayName);
return new ValidationResult(errorMessage);
}
}
return ValidationResult.Success;
}
}
Controller:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Album album)
{
if (ModelState.IsValid)
{
db.Albums.Add(album);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.GenreID = new SelectList(db.Genres, "GenreID", "Name", album.GenreID);
ViewBag.ArtistID = new SelectList(db.Artists, "ArtistID", "ArtistName", album.ArtistID);
return View(album);
}
Note: Validation is fired before reaching controller and while executing db.SaveChanges()
Model:
public class Album
{
public virtual int AlbumID { get; set; }
public virtual int GenreID { get; set; }
public virtual int ArtistID { get; set; }
[Required(ErrorMessageResourceType= typeof(ErrorMessages), ErrorMessageResourceName="TitleRequired")]
[Display(Name="Movie Name")]
[ValidationEx()]
public virtual string Title { get; set; }
[Range(0,1000)]
public virtual decimal Price { get; set; }
public virtual string AlbumArtUrl { get; set; }
public virtual Genre Genre { get; set; }
public virtual Artist Artist { get; set; }
[StringLength(40)]
public virtual string Description { get; set; }
}
DBContext
public class MusicAlbumStoreDBContext : DbContext
{
// You can add custom code to this file. Changes will not be overwritten.
//
// If you want Entity Framework to drop and regenerate your database
// automatically whenever you change your model schema, add the following
// code to the Application_Start method in your Global.asax file.
// Note: this will destroy and re-create your database with every model change.
//
// System.Data.Entity.Database.SetInitializer(new System.Data.Entity.DropCreateDatabaseIfModelChanges<MusicAlbumProject.Models.MusicAlbumStoreDBContext>());
public MusicAlbumStoreDBContext() : base("name=MusicAlbumStoreDBContext")
{
}
public DbSet<Album> Albums { get; set; }
public DbSet<Genre> Genres { get; set; }
public DbSet<Artist> Artists { get; set; }
public DbSet<Order> Orders { get; set; }
}
You are using the same class as model and as view model. There is a reason why MVC distringuishes between these 2 types. You really should add a separate model and a separate view model class.
IsValid() is called twice
before the controller action as the data is validated before the action is called
on db.SaveChanges() because the DB Context also does a validation
Related
I have a problem with EF and MVC on net core.
I have an "item" model. This model has the required "createdate" and "updateDate" fields (I can't have a record without having the record date).
I use both fields with inheritance over BaseEntity.
The point is that to assign these dates, I do it directly in the context, overriding the "SaveChanges" function.
Because of this, by not giving it a value in either the view or the controller, the model evaluation fails because the dates are null. I actually give it value but after passing validation.
What do you think would be the most correct solution?
This is my model:
public class ItemType : BaseEntity
{
[Key]
public int Id { get; set; }
[Required]
public string Description { get; set; }
public ICollection<Item> Items { get; set; }
public int SizeTypeId { get; set; }
public SizeType SizeType { get; set; }
}
public class BaseEntity
{
[Required]
public bool Active { get; set; }
public DateTime? DeleteDate { get; set; }
public DateTime CreatedDate { get; set; }
public DateTime UpdatedDate { get; set; }
public string Comments { get; set; }
}
this is my savechanges override:
public override int SaveChanges()
{
var entries = ChangeTracker
.Entries()
.Where(e => e.Entity is BaseEntity && (
e.State == EntityState.Added
|| e.State == EntityState.Modified));
foreach (var entityEntry in entries)
{
((BaseEntity)entityEntry.Entity).UpdatedDate = DateTime.Now;
if (entityEntry.State == EntityState.Added)
{
((BaseEntity)entityEntry.Entity).CreatedDate = DateTime.Now;
}
}
return base.SaveChanges();
}
this is my controller:
public async Task<IActionResult> Create([Bind("Id,Description,Active,DeleteDate,CreatedDate,UpdatedDate,Comments")] SizeType sizeType)
{
if (ModelState.IsValid)
{
_context.Add(sizeType);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(sizeType);
}
Thanks you very much!!
One easy option would be to just make the properties nullable i.e. DateTime? CreatedDate since the entity isn't already created or deleted it makes sense for the CreatedDate or UpdatedDate properties to be null. However, a better option is to just create a DTO or in other words, a ViewModel that wraps the data of your entity and exposes it to the View. For example:
public class ItemTypeRequest : BaseEntityRequest
{
[Required]
public string Description { get; set; }
public ICollection<Item> Items { get; set; }
public int SizeTypeId { get; set; }
public SizeType SizeType { get; set; }
}
public class BaseEntityRequest
{
[Required]
public bool Active { get; set; }
public string Comments { get; set; }
}
Normally in a well-defined ViewModel, you are exposing to the View only the minimal set of data that is needed to perform the operation. In this case the Create View does not need an Id, CreatedDate, or UpdatedDate, because the entity isn't already created i.e. it does not exist in the database and therefore has no Id or creation date. After creating the ViewModels you can leverage AutoMapper to map the ViewModels data to the entity data
var config = new MapperConfiguration(cfg => {
cfg.CreateMap<ItemTypeRequest , ItemType>();
cfg.CreateMap<ItemType, ItemTypeResponse>();
});
var mapper = config.CreateMapper();
public async Task<IActionResult> Create(ItemTypeRequest itemTypeRequest)
{
if (ModelState.IsValid)
{
ItemType itemType = mapper.Map<ItemType>(itemTypeRequest);
_context.Add(itemType);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View();
}
Following the same login, you should create a response DTO/ViewMode this time with more data if you need it i.e.
public class ItemTypeResponse : BaseEntityResponse
{
public int Id { get; set; }
public string Description { get; set; }
public ICollection<Item> Items { get; set; }
public int SizeTypeId { get; set; }
public SizeType SizeType { get; set; }
}
public class BaseEntityResponse
{
public bool Active { get; set; }
public DateTime CreatedDate { get; set; }
public DateTime UpdatedDate { get; set; }
public string Comments { get; set; }
}
This time the properties CreatedDate and UpdatedDate are included since they have values after you have created the entity.
I am trying to implement a method where I select all records that belong to the correct StateId (NJ, DE, PA); I am using dependency injection and the repository design pattern which I am new to.
Here is what I have in my controller:
private IWorkOrderRepository _workOrderRepository;
public DependencyInjectionController(IWorkOrderRepository workOrderRepository)
{
_workOrderRepository = workOrderRepository;
}
public IActionResult WorkOrders()
{
var model = _workOrderRepository.GetWorkOrders();
return View(model);
}
public IActionResult Nj(string LocationFilter)
{
var locations = _workOrderRepository.GetWorkOrders().Select(x => x.StateId == LocationFilter).ToList();
return View(locations);
}
The GetWorkOrders method works exactly as it's supposed to.
Here is my Model:
public class WorkOrderContext : DbContext
{
public DbSet<WorkOrder> WorkOrder { get; set; }
public WorkOrderContext(DbContextOptions<WorkOrderContext> options)
: base(options)
{
}
}
public class WorkOrder
{
[Key]
public int UserId { get; set; }
public string LocationId { get; set; }
public string Reason { get; set; }
public bool IsActive { get; set; } = true;
public DateTime Date { get; set; } = DateTime.Now;
public string StateId { get; set; }
}
The error I am getting when I hit the route is:
"The model item passed into the ViewDataDictionary is of type 'System.Collections.Generic.List1[System.Boolean]', but this ViewDataDictionary instance requires a model item of type 'System.Collections.Generic.IEnumerable1[WorkOrderServices.Models.WorkOrder]'."
Select is transforming the list of work orders to booleans: true where the StateId matches the location filter, and false where it doesn't.
Instead of transforming the list, you want to filter it with Where: Where(x => x.StateId == LocationFilter).
I'm trying to do the Entity Framework Code First Approach. I made my models then the DbContext and added it to the controller. I followed an online tutorial as I've never used C# before.
However the tables don't create until I add a call to the db in the controller.
public ActionResult Index()
{
db.posts.ToList();
return View();
}
The call however throws.
InvalidOperationException: The class 'SocialMediaMining.Models.SocialMedia.Facebook.posts' has no parameterless constructor.
posts class:
public class posts
{
public dynamic jsonObj { get; set; }
public posts(dynamic json)
{
jsonObj = json;
if (jsonObj != null)
{
id = jsonObj.id;
name = jsonObj.name;
if(jsonObj.feed !=null)
{
feed = new feed(jsonObj.feed);
}
}
}
public string id { get; set; }
public string name { get; set; }
public virtual feed feed { get; set; }
public int postsId { get; set; }
}
The controller:
public class FacebookController : Controller
{
//The dbcontext call
FacebookEntities db = new FacebookEntities();
public ActionResult Index()
{
// the error
db.posts.ToList();
return View();
}
// more code here
}
//DbContext
public class FacebookEntities : DbContext
{
public FacebookEntities() : base("SocialMediaDb")
{
}
public DbSet<posts> posts { get; set; }
//more code here
}
Any help is appreciated
The exception message is pretty straightforward - you need to specify a parameterless constructor for posts class, which should look like this:
public class posts
{
// add this constructor
public posts()
{
}
public dynamic jsonObj { get; set; }
public posts(dynamic json)
{
jsonObj = json;
if (jsonObj != null)
{
id = jsonObj.id;
name = jsonObj.name;
if(jsonObj.feed !=null)
{
feed = new feed(jsonObj.feed);
}
}
}
public string id { get; set; }
public string name { get; set; }
public virtual feed feed { get; set; }
public int postsId { get; set; }
}
Note that any entity class which want to be included in DbSet<T> type parameter must have parameterless constructor to enable binding with EF. Also it is recommended to use PascalCase for entity class names and property names, e.g. Posts.
I have a Controller action the receives 2 URL parameters, which are foreign keys for the data model:
public ActionResult Create(SurveyResponseModel surveyresponsemodel, int MemberId, int ProgramId)
{
surveyresponsemodel.MemberId = MemberId;
surveyresponsemodel.ProgramId = ProgramId;
return View(surveyresponsemodel);
}
Here is the data model:
public class SurveyResponseModel
{
[Key]
public int ResponseId { get; set; }
public int MemberId { get; set; }
public int ProgramId { get; set; }
// "If yes, what changes did you make? Mark all that apply."
[DisplayName("Did you make any changes in your practice, research, or administration activities as a result of participating in this CME activity?")]
public string CmeChanges { get; set; }
[DisplayName("Better patient follow-up")]
public bool PatientFollowUp { get; set; }
public virtual SurveyProgramModel SurveyProgramModel { get; set; }
public virtual PersonModel PersonModel { get; set; }
And the Data Model for "SurveyProgramType"
public class SurveyProgramModel
{
[Key]
public int ProgramId { get; set; }
public int ProgramYear { get; set; }
public int ProgramStatusId { get; set; }
public string ProgramTitle { get; set; }
public int ProgramTypeId { get; set; }
public virtual SurveyProgramTypeModel ProgramType { get; set; }
public virtual ProgramStatusModel ProgramStatusModel { get; set; }
}
What I want to be able to do in my view, is retrieve the ProgramTitle by the URL parameter that is passed for ProgramId. So the view looks something like:
<div class="editor-label">
#Model.SurveyProgramModel.ProgramTitle
</div>
However, #Model.SurveyProgramModel.ProgramTitle is throwing an exception because it is null. I'm thinking I have my navigation property set up incorrectly. Any idea what that is?
Shouldn't you return your view model to the view?
public ActionResult Create(
SurveyResponseModel surveyresponsemodel) //, int MemberId, int ProgramId)
{
// MemberId and ProgramId arguments do not need to be defined
// They will be picked up my MVC model binder, since there are properties
// with the same name in SurveyResponseModel class
//surveyresponsemodel.MemberId = MemberId;
//surveyresponsemodel.ProgramId = ProgramId;
surveyresponsemodel.SurveyProgramModel = new SurveyProgramModel(); // new line
return View(surveyresponsemodel); // <- return your view model here
}
without passing the model to the view, you cant access the properties of the model in your view. thats the possible cause of the error.
public ~ActionResult PassModel(DemoModel _model, int id)
{
// your code goes here....
return View(_model); // pass the model to view ..so you can work on your model
}
I have a model like the following:
public class TestViewModel
{
string UpdateProperty { get; set; }
string IgnoreProperty { get; set; }
ComplexType ComplexProperty { get; set; }
}
where
public class ComplexType
{
long? Code { get; set; }
string Name { get; set; }
}
My controller action:
public Edit(int id, FormColleciton formCollection)
{
var model = service.GetModel(id);
TryUpdateModel(model);
//...
}
When calling the Edit action I have a formCollection parameter containing only a key/value for UpdateProperty.
After the call to TryUpdateModel UpdateProperty is set correctly, IgnoreProperty is left un-touched but ComplexProperty is set to null, even if it previously had a value.
Should TryUpdateModel() only modify properties that are a part of the request? If this is not the case what is the best way to work around this so ComplexProperty is only modified if it is included in the request?
After it was pointed out by Darin that the test case above didn't demonstrate the problem I have added a scenario where this problem really occurs:
public class TestViewModel
{
public List<SubModel> List { get; set; }
}
public class SubModel
{
public ComplexType ComplexTypeOne { get; set; }
public string StringOne { get; set; }
}
public class ComplexType
{
public long? Code { get; set; }
public string Name { get; set; }
}
Controller Action:
public ActionResult Index()
{
var model = new TestViewModel
{
List = new List<SubModel> {
new SubModel{
ComplexTypeOne = new ComplexType{Code = 1, Name = "5"},
StringOne = "String One"
}
}
};
if (TryUpdateModel(model)) { }
return View(model);
}
Sending this request:
/Home/Index?List[0].StringOne=test
updates SubModel.StringOne property but sets ComplexTypeOne to null, even though it is not included in the request.
Is this expected behaviour (given this does not happen unless an enumerable of complex types is used)? How best to work around this?
There must be something wrong with your test case as I was unable to reproduce it. Here's what I tried:
Model (notice that I use public properties):
public class TestViewModel
{
public string UpdateProperty { get; set; }
public string IgnoreProperty { get; set; }
public ComplexType ComplexProperty { get; set; }
}
public class ComplexType
{
public long? Code { get; set; }
public string Name { get; set; }
}
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new TestViewModel
{
IgnoreProperty = "to be ignored",
UpdateProperty = "to be updated",
ComplexProperty = new ComplexType
{
Code = 1,
Name = "5"
}
};
if (TryUpdateModel(model))
{
}
return View();
}
}
Now I send the following request: /home/index?UpdateProperty=abc and inside the condition only the UpdateProperty is modified with the new value from the query string. All other properties, including the complex property, are left untouched.
Also notice that the FormCollection action parameter is useless.