I am trying to generate a list of dates into a selectList in Asp.Net MVC5. I would like to have week commencing list for only 5 weeks in a row but am hitting real problems on how to go about this.
I ideally I would need this in my create ActionMethod because I want to use this against time recorded for that week.
I have been trying to use the following example How can I get the DateTime for the start of the week? and am running into difficulties.
What I have is
Model:
public class TimeSheet
{
public int TimeSheetId { get; set; }
public DateTime WeekCommencing { get; set; }
public int MondayHours { get; set; }
public int TuesdayHours { get; set; }
public int WednesdayHours { get; set; }
public int ThursdayHours { get; set; }
public int FridayHours { get; set; }
public int SaturdayHours { get; set; }
public int SundayHours { get; set; }
public bool CompletedTimeSheet { get; set; }
public int PlanId { get; set; }
public virtual ICollection<Plan> Plan { get; set; }
}
Controller: Create Method
// GET: TimeSheets/Create
public ActionResult Create()
{
DateTime today = DateTime.Today;
if(today.DayOfWeek == DayOfWeek.Monday && today.Day <= 7)
ViewBag
return View();
}
// POST: TimeSheets/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "TimeSheetId,WeekCommencing,MondayHours,TuesdayHours,WednesdayHours,ThursdayHours,FridayHours,SaturdayHours,SundayHours,CompletedTimeSheet,PlanId")] TimeSheet timeSheet)
{
if (ModelState.IsValid)
{
db.TimeSheets.Add(timeSheet);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(timeSheet);
}
Please can someone help me or advise
Many thanks
Mark
Not sure exactly what you mean by "5 weeks in a row", so I just did previous 5 weeks. Completely untested, so if any problems then say.
Edit: Edited so only next 5 Mondays get taken.
It's a bit ambiguous as to what you want as you haven't posted what you have tried.
public class TimeSheet
{
public DateTime DateSelected { get; set; }
}
public ActionResult Create()
{
int weekCount = 5;
List<DateTime> listDates = new List<DateTime>();
for (int i = 0; i < (weekCount * 7); ++i) //Get next 5 weeks
{
//Adds only next 5 mondays to the list of dates
if (DateTime.Today.AddDays(i).DayOfWeek == DayOfWeek.Monday)
listDates.Add(DateTime.Today.AddDays(i));
}
ViewData["DateList"] = new SelectList(listDates);
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "TimeSheetId,WeekCommencing,MondayHours,TuesdayHours,WednesdayHours,ThursdayHours,FridayHours,SaturdayHours,SundayHours,CompletedTimeSheet,PlanId,DateSelected")] TimeSheet timeSheet)
{
if (ModelState.IsValid)
{
db.TimeSheets.Add(timeSheet);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(timeSheet);
}
#Html.DropDownListFor(x => x.DateSelected, (SelectList)ViewData["DateList"], new {#class = "form-control"})
#Html.ValidationMessageFor(x => x.DateSelected, "", new {#class = "text-danger"})
Related
I have the following controller's action:
public async Task<IActionResult> Create([FromForm]LanguageViewModel viewModel)
{
if (!ModelState.IsValid)
{
return View(viewModel);
}
var newLanguage = new Language()
{
Sort = viewModel.Sort,
LangCode = viewModel.Code,
LangName = viewModel.Name
};
await _languageRepository.SaveAsync(newLanguage);
return RedirectToAction("Index");
}
And the following viewModel:
public class LanguageViewModel
{
public int Sort { get; set; }
[Required(ErrorMessage = "Language code is required")]
public string Code { get; set; }
[Required(ErrorMessage = "Language name is required")]
public string Name { get; set; }
}
Note, that LanguageViewModel is not my domain (Entity framework) object. For map on my database I have another one:
public class Language
{
public int LanguageId { get; set; }
public int Sort { get; set; }
public string LangName { get; set; }
public string LangCode { get; set; }
}
So, I'm working with ASP.NET MVC Core 3.1 and I need to check if Language with given code already exists or not. If it exists, I would like to show validation error about it. You can say that I can solve it like:
public async Task<IActionResult> Create([FromForm] LanguageViewModel viewModel)
{
if (!ModelState.IsValid)
{
return View(viewModel);
}
var lang = await _languageRepository.All.FirstOrDefaultAsync(x =>
x.LangCode.ToUpper() == viewModel.Code.ToUpper());
if (lang != null)
{
ModelState.TryAddModelError("Code", $"The language with code {viewModel.Code} already exists");
}
if (!ModelState.IsValid)
{
return View(viewModel);
}
var newLanguage = new Language()
{
Sort = viewModel.Sort,
LangCode = viewModel.Code,
LangName = viewModel.Name
};
await _languageRepository.SaveAsync(newLanguage);
return RedirectToAction("Index");
}
Ok. it works. But it's ugly :( Maybe there is better solution?
Maybe that would be a little bit cleaner solution.
if (_languageRepository.Any(o => o.LangCode.Equals(txnId, StringComparison.InvariantCultureIgnoreCase)))
{
ModelState.TryAddModelError("Code", $"The language with code {viewModel.Code} already exists");
}
Better to try this introduced after EF Core 5:
https://learn.microsoft.com/en-us/ef/core/modeling/indexes?tabs=data-annotations
[Index(nameof(Url), IsUnique = true)]
public class Blog
{
public int BlogId { get; set; }
public string Url { get; set; }
}
Disclaimer; I'm a mvc nub.
I'm kinda stuck, this worked like a couple of days ago, and I'm not sure why it doesn't now. The problem is I'm not getting the subclass data back, only the data from the "normal" properties. I'm populating 5 rows from within Global.asax, and the table ReDayModels is in the DB. The keys are okay also in the DB.
edit: Both tables ReWeekModel and ReDayModel has data. I'm using code first approach.
I have this DB:
public class ReChronoDB : DbContext
{
public DbSet<ReWeekRowModel> WeekRows { get; set; }
}
And this is my DbSet rows
public class ReWeekRowModel
{
public ReWeekRowModel()
{
CreatedDate = DateTime.Now;
Days = new List<ReDayModel>()
{
new ReDayModel {DayName = DayOfWeek.Monday},
new ReDayModel {DayName = DayOfWeek.Tuesday},
new ReDayModel {DayName = DayOfWeek.Wednesday},
new ReDayModel {DayName = DayOfWeek.Thursday},
new ReDayModel {DayName = DayOfWeek.Friday},
new ReDayModel {DayName = DayOfWeek.Saturday},
new ReDayModel {DayName = DayOfWeek.Sunday}
};
}
public DateTime CreatedDate { get; private set; }
[Key]
public int Id { get; set; }
public string Name { get; set; }
[DisplayName("Week #"), Range(1, 53)]
public int WeekOfYear { get; set; }
[Range(2017,2075)]
public int Year { get; set; }
public List<ReDayModel> Days { get; set; }
public Priority Priority { get; set; }
public Status Status { get; set; }
public Category Category { get; set; }
public Responsible Responsible { get; set; }
public ResponseGroup ResponseGroup { get; set; }
public string CaseRef { get; set; }
[StringLength(75), DisplayName("Short Description")]
public string Description { get; set; }
public string Details { get; set; }
}
And here the subclass:
public class ReDayModel
{
[Key]
public int Id { get; set; }
public DayOfWeek DayName { get; set; }
public double Hours { get; set; }
public string Comments { get; set; }
}
And finally, here is the controller with the Index Action.
public class ReChronoController : Controller
{
ReChronoDB _reChronoDb = new ReChronoDB();
// GET: ReChrono
public ActionResult Index(string responsegroup = "", string responsible = "", int week = 0, int year = 0)
{
CultureInfo ciCurr = CultureInfo.CurrentCulture;
ViewBag.CurrentWeek = ciCurr.Calendar.GetWeekOfYear(DateTime.Now, CalendarWeekRule.FirstFourDayWeek,
DayOfWeek.Monday);
ViewBag.CurrentYear = ciCurr.Calendar.GetYear(DateTime.Today);
var model = _reChronoDb.WeekRows.ToList();
// For dropdownlists
ViewBag.Years = _reChronoDb.WeekRows.Select(r => r.Year).Distinct();
ViewBag.Weeks = _reChronoDb.WeekRows.Select(r => r.WeekOfYear).Distinct();
var filteredmodel = ReChronoDomainLayer.FilteredWeekRows(model, responsegroup, responsible, week, year);
return View(filteredmodel);
}
}
When I hover over the "model" variable and drill down, the Days collection for the rows have no values.
edit: Here is the sql that is being executed:
SELECT
[Extent1].[Id] AS [Id],
[Extent1].[CreatedDate] AS [CreatedDate],
[Extent1].[Name] AS [Name],
[Extent1].[WeekOfYear] AS [WeekOfYear],
[Extent1].[Year] AS [Year],
[Extent1].[Priority] AS [Priority],
[Extent1].[Status] AS [Status],
[Extent1].[Category] AS [Category],
[Extent1].[Responsible] AS [Responsible],
[Extent1].[ResponseGroup] AS [ResponseGroup],
[Extent1].[CaseRef] AS [CaseRef],
[Extent1].[Description] AS [Description],
[Extent1].[Details] AS [Details]
FROM [dbo].[ReWeekRowModels] AS [Extent1]
edit: this is the context?
public class ReChronoDBInitializer :
DropCreateDatabaseIfModelChanges<ReChronoDB>
{
protected override void Seed(ReChronoDB context)
{
base.Seed(context);
var enumToLookup = new EnumToLookup();
enumToLookup.Apply(context);
ReChronoDomainLayer.CreateDummyEntries(context);
}
}
Seems like its completely ignoring the List Days collection.
I've also tried without the filteredmodel, but i left it in here, so you understand why I have parameters in the action.
Any ideas?
First of all please make sure that ReDayModel is part of ReChronoDB as well:
public class ReChronoDB : DbContext
{
public DbSet<ReWeekRowModel> WeekRows { get; set; }
public DbSet<ReDayModel> DayRows { get; set; }
}
Now your controller action attempts to use lazy loading but it is disabled due to incorrect property type for Days property. If you want to enabled lazy loading your class should look like this:
public class ReWeekRowModel
{
...
public virtual ICollection<ReDayModel> Days { get; set; }
...
}
As an alternative you can use eager loading which would require a change in your action to use .Include() method:
public class ReChronoController : Controller
{
...
public ActionResult Index(string responsegroup = "", string responsible = "", int week = 0, int year = 0)
{
...
var model = _reChronoDb.WeekRows.Include(w => w.Days).ToList();
...
}
}
If you always need the Days collection this might result in better performance. Please read more here.
I have a controller which is used to save data in database. The controller looks like below:
[Authorize]
[HttpPost]
public ActionResult Create(EmployeeFormViewModel viewModel)
{
var _employee = new Employee
{
Employee = User.Identity.GetUserId(),
DateTime = DateTime.Parse(string.Format("{0} {1}", viewModel.Date, viewModel.Time))
};
_context.Employees.Add(_employee);
_context.SaveChanges();
return RedirectToAction("Index", "Home");
}
I want to remove this line of code
DateTime.Parse(string.Format("{0} {1}", viewModel.Date, viewModel.Time))
and make this calculations somewhere else in order to keep the controller clean.
Which is the best way to archive this?
From the data given I see that you have used a ViewModel called EmployeeFormViewModel to saperate the logic from the model. I would guess that your ViewModel looks something like below:
public class EmployeeFormViewModel
{
public string Venue { get; set; }
public string Date { get; set; }
public string Time { get; set; }
}
Now, in order to make the changes in controller, i would suggest you make it look like below:
[Authorize]
[HttpPost]
public ActionResult Create(EmployeeFormViewModel viewModel)
{
var _employee = new Employee
{
Employee = User.Identity.GetUserId(),
DateTime = viewModel.DateTime
};
_context.Employees.Add(_employee);
_context.SaveChanges();
return RedirectToAction("Index", "Home");
}
and after that go to your ViewModel and add the new property that you added in the Controller (DateTime). Now your ViewModel should look something like below:
public class EmployeeormViewModel
{
public string Venue { get; set; }
public string Date { get; set; }
public string Time { get; set; }
public DateTime DateTime
{
get
{
return DateTime.Parse(string.Format("{0} {1}", Date, Time));
}
}
}
I hope this solves your problem.
To offer a different perspective, I'd suggest you could put it in an extension method. The concept of combining date and time strings doesn't really feel like it should belong to your domain model, it feels like a generic thing that you might want to use across your application (or even in other applications). I would do this...
public static class DateTimeExtensions
{
public static DateTime ParseToDateTime(this string date, string time = null)
{
return string.IsNullOrEmpty(withTime) ? DateTime.Parse(date) : DateTime.Parse($"{date} {time}");
}
}
And in the controller...
[Authorize]
[HttpPost]
public ActionResult Create(EmployeeFormViewModel viewModel)
{
var _employee = new Employee
{
Employee = User.Identity.GetUserId(),
DateTime = viewModel.Date.ParseToDateTime(viewModel.Time)
};
EDIT: Additionally...to incorporate etr's answer, which is also a good approach, you could combine the two...
public class EmployeeormViewModel
{
public string Venue { get; set; }
public string Date { get; set; }
public string Time { get; set; }
public DateTime DateTime
{
get
{
return Date.ParseToDateTime(Time);
}
}
}
Rich domain is the way.
public class Employee
{
public Employee(int id, object date, object time)
{
Id = id;
DateTime = DateTime.Parse(string.Format("{0} {1}", date, time))
}
public int Id { get; protected set; }
public DateTime DateTime { get; protected set; }
}
And them:
[Authorize]
[HttpPost]
public ActionResult Create(EmployeeFormViewModel viewModel)
{
_context.Employees.Add(new Employee(User.Identity.GetUserId(), viewModel.Date, viewModel.Time));
_context.SaveChanges();
return RedirectToAction("Index", "Home");
}
I like strong type binding and a Post method as follows:
public ActionResult Create(EmployeeFormViewModel viewModel)
{
viewModel.Post(User.Identity.GetUserId());
_context.Employees.Add(_employee);
_context.SaveChanges();
return RedirectToAction("Index", "Home");
}
The view model looking like this:
public class EmployeeFormViewModel
{
Employee Employee { get; set; }
DateTime Date { get; set; }
DateTime Time { get; set; }
public void Post(int empid)
{
Employee= new Employee
{
EmployeeID = empid,
DateTime = DateTime.Parse(string.Format("{0} {1}", Date, Time))
};
return;
}
}
This is all possible because of the nice MVC Binding engine which generates the EmployeeFormViewModel based on query strings, prior to calling the action method.
I put a "Post" method in all of my ViewModels and let MVC do the work.
For some reason, when I post this view model back to the controller and add in the model for binding, it ends up being null. The application that I am working with is a massive one. Also I haven't written much of the code so this model is massive so I will just add the parts that matter, but could other properties be preventing the model binding?
I do know that it has been working but in the last little bit it started not. Maybe it's not even something with the model, would just love some help debugging it.
POST Action:
[HttpPost]
public ActionResult Categories(int applicationId, SqsApplicationViewModel model)
{
// Save away the ids they chose
_sqsApplicationCategoryService.SaveCategories(applicationId, model.Display_Categories.Where(i => i.Selected).Select(i => i.CategoryId).ToList());
// Complete the step
_sqsApplicationStepService.CompleteStep(applicationId, SqsStep.Categories);
return RedirectToAction("Documents");
}
View Model:
public class SqsApplicationViewModel : IMappable
{
public int Id { get; set; }
public int SupplierId { get; set; }
public int? SqsApprovalLevelId { get; set; }
// Other properties .....
public List<SqsChosenCategoryViewModel> Display_Categories { get; set; }
// Other properties .....
}
public class SqsChosenCategoryViewModel
{
public int CategoryId { get; set; }
public string Name { get; set; }
public string CategoryAmountString { get; set; }
public bool Selected { get; set; }
public IList<SqsDocumentComplianceViewModel> Documents { get; set; }
}
View:
#using (Html.BeginForm())
{
#Html.HiddenFor(m => m.Id)
#if (Model.Display_Categories != null && Model.Display_Categories.Count() > 0)
{
for (var i = 0; i < Model.Display_Categories.Count; i++)
{
#Html.HiddenFor(m => m.Display_Categories[i].CategoryId)
#Html.CheckBoxFor(m => m.Display_Categories[i].Selected)
#Model.Display_Categories[i].Name
}
}
}
Also, the values being sent back in firebug are:
Id:1061
Display_Categories[0].CategoryId:4
Display_Categories[0].Selected:true
Display_Categories[0].Selected:false
Display_Categories[1].CategoryId:1
Display_Categories[1].Selected:false
Display_Categories[2].CategoryId:2
Display_Categories[2].Selected:false
Display_Categories[3].CategoryId:3
Display_Categories[3].Selected:false
Display_Categories[4].CategoryId:6
Display_Categories[4].Selected:true
Display_Categories[4].Selected:false
Display_Categories[5].CategoryId:8
Display_Categories[5].Selected:false
Display_Categories[6].CategoryId:10
Display_Categories[6].Selected:false
Display_Categories[7].CategoryId:7
Display_Categories[7].Selected:false
Display_Categories[8].CategoryId:9
Display_Categories[8].Selected:false
Display_Categories[9].CategoryId:11
Display_Categories[9].Selected:false
Display_Categories[10].CategoryId:5
Display_Categories[10].Selected:true
Display_Categories[10].Selected:false
-------------EDIT----------------
I tried using the following test models and it worked. Is it possible that another property in the Model could be hindering the binding? I added some random ones in these too and it still worked.
public class TestViewModel
{
public int Id { get; set; }
public IList<TestSubViewModel> Display_Categories { get; set; }
public string TestProp { get { return "asdfasdfasdf"; } }
public TestSubViewModel TestGetFirst { get { return this.Display_Categories.FirstOrDefault(); } }
}
public class TestSubViewModel
{
public int CategoryId { get; set; }
public string Name { get; set; }
public string CategoryAmountString { get; set; }
public bool Selected { get; set; }
public IList<SqsDocumentComplianceViewModel> Documents { get; set; }
}
So I'm just going to answer my own question, though it isn't solved as much as there is another way to do it.
I believe that when you typehint the model and it binds it, in the background it uses "TryUpdateModel()" and so I just called this in the controller and for some reason it worked. Not sure if I miss out on anything else by doing it this way, but it has worked for me.
Also you can debug what might be the issue by doing it this way by the following:
var model = new ViewModel();
var isSuccess = TryUpdateModel(model);
if (!isSuccess)
{
foreach (var modelState in ModelState.Values)
{
foreach (var error in modelState.Errors)
{
Debug.WriteLine(error.ErrorMessage);
}
}
}
Taken from this post: How to find the exceptions / errors when TryUpdateModel fails to update model in asp.net mvc 3
I'm having trouble binding data to a collection item's collection (I'm also having trouble wording my problem correctly). Let's just make thing easier on everyone by using an example with psudo models.
Lets say I have the following example models:
public class Month()
{
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Week> Weeks { get; set; }
}
public class Week()
{
public int ID { get; set; }
public int MonthID { get; set; }
public String Name { get; set; }
public virtual ICollection<Day> Days { get; set; }
}
public class Day()
{
public int ID { get; set; }
public String Name { get; set; }
}
...and an example viewmodel:
public class EditMonthViewModel()
{
public Month Month { get; set; }
public List<Week> Weeks { get; set; }
public List<Day> AllDays { get; set; }
}
The purpose of the Edit Action/View is to enable users to edit a month, the weeks assigned to the month, and add and remove days from weeks of a certain month. A view might help.
#model myProject.ViewModels.EditMonthViewModel
//...
#using (Html.BeginForm())
{
//Edit Month Stuff...
#for(int i = 0; i < Model.Weeks.Count(); i++)
{
<h2>#Model.Weeks[i].Name</h2>
#Html.EditorFor(model => Model.Weeks[i].Name)
//loop through all possible days
//Select only days that are assigned to Week[i]
#for(int d = 0; d < Model.AllDays.Count(); d ++)
{
//This is the focus of this question.
//How do you bind the data here?
<input type="checkbox"
name="I have no idea"
#Html.Raw(Model.Weeks[i].Days.Contains(Model.AllDays[d]) ? "checked" : "") />
}
}
}
Controller Action methods
public ActionResult Edit(int id)
{
var viewModel = new EditMonthViewModel();
viewModel.Month = db.Months.Find(id);
viewModel.Weeks = db.Weeks.Where(w => w.MonthID == id).ToList();
viewModel.AllDays = db.Days.ToList();
}
[HttpPost]
public ActionResult Edit(EditMonthViewModel viewModel)
{
var monthToUpdate = db.Months.Find(viewModel.Month.ID);
//...
if(viewModel.Weeks != null)
{
foreach (var week in viewModel.Weeks)
{
var weekToUpdate = monthToUpdate.Weeks.Single(w => w.ID == week.ID);
//...
/*So, I have a collection of weeks that I can grab,
but how do I know what was selected? My viewModel only has a
list of AllDays, not the days selected for Week[i]
*/
}
}
How can I ensure that when I submit the form the selected days will bind to the week?
It looks like the easiest thing to do is to make it a goal for your form to populate a data structure of the type IEnumerable<DayModel>, where DayModel is defined as:
public class DayModel
{
public int WeekId { get; set; }
public int DayId { get; set; }
public bool IsIncluded { get; set; }
}
You could keep your Razor code as is for the most part, but then when it comes to rendering the checkboxes, you can do something like this:
#{
var modelIdx = 0;
}
// ...
<input type="hidden" name="days[#modelIdx].WeekId" value="#Model.Weeks[i].Id" />
<input type="hidden" name="days[#modelIdx].DayId" value="#Model.AllDays[d].Id" />
<input type="checkbox" name="days[#modelIdx].IsIncluded" value="#(Model.Weeks[i].Days.Contains(Model.AllDays[d]) ? "checked" : "")" />
#{ modelIdx++; }
Then, your controller action you post to could have this signature:
[HttpPost]
public ActionResult Edit(IEnumerable<DayModel> days)
{
//...
}
Something that helps me is to never confuse view models, which should only be used for the model for views (GET actions generally) and non-view models (what we call plain models). Avoid having your POST actions try to bind to view models, and it will simplify your life greatly.