I am a newbie at MVC and having trouble displaying the result of a certain method in a view input field. I am not too sure if I am supposed to create the method in the cshtml or in a separate cs file.
I have used the #function tag to create my method in a cshtml file below
#functions{
public int DaysCalc(DateTime first, DateTime second)
{
QuoteApp.Models.Quote ts = new QuoteApp.Models.Quote();
ts.StartDate = first;
ts.EndDate = second;
int days;
days = (second.Day - first.Day);
return days;
}
}
I am calling it this way in the cshtml file
#Html.EditorFor(model =>model.DaysCalc(model.StartDate, model.EndDate), new { htmlAttributes = new { #class = "form-control", disabled = "disabled"} })
I get an error stating
Templates can be used only with field access, property access, single-dimension array index, or single-parameter custom indexer expressions.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.InvalidOperationException: Templates can be used only with field access, property access, single-dimension array index, or single-parameter custom indexer expressions.
Source Error:
Line 63: #Html.LabelFor(model => model.noOfDays, htmlAttributes: new { #class = "control-label col-md-2" })
Line 64: <div class="col-md-10">
Line 65: #Html.EditorFor(model =>#DaysCalc(model.StartDate, model.EndDate),new { htmlAttributes = new { #class = "form-control", disabled = "disabled"} })
Line 66: #Html.ValidationMessageFor(model => model.noOfDays, "", new { #class = "text-danger" })
Line 67: </div>
Source File: Line: 65
If you want to use EditorFor, create a property that returns your calculated value.
public class YourModel
{
public int CalculatedDays
{
get
{
QuoteApp.Models.Quote ts = new QuoteApp.Models.Quote();
ts.StartDate = first;
ts.EndDate = second;
return (ts.EndDate - ts.StartDate);
}
}
}
#Html.EditorFor(model => model.CalculatedDays, new { htmlAttributes = new { #class = "form-control", disabled = "disabled"} })
You can only use the For methods with actual properties on your model (i.e. not functions). I'm not sure what you're actually trying to achieve by doing this, so I can't really help you further unless you update your question, but that explains your error at least.
EDIT
First and foremost, I need to point out that your date calculation is not correct. What if StartDate is 2014-06-30 and EndDate is 2014-07-01. Then the result of is going to 1 - 30 = -29. I'm reasonably sure that's not what you're looking for. There's a method on DateTime just for this purpose:
TimeSpan difference = EndDate.Subtract(StartDate);
The result is a TimeSpan, which you can then call difference.Days to get the number of days involved. Also of note, there's a TotalDays property off TimeSpan that will return fractional days (whereas Days just returns whole days).
Next, for what it's worth, and since you're new to all this, the in-view Razor helpers are a nice novelty, but they're impractical to the point of being useless, and frankly, they violate MVC (the pattern, not the framework from Microsoft). If you need to do this type of calculation, the best place for it is on your model. You can implement a property like:
public int Days
{
get
{
return EndDate.Subtract(StartDate).Days;
}
}
However, that's read-only (there's no setter method), and if you're talking about using this as an input value, it doesn't make sense to have a read-only property backing it (unless, I guess, if you make it a read-only input, in which case it might as well just be plain-text). So, if this is something you intend to post back to you'll need to figure out what that means in terms of a setter. Honestly, I can't see what you would do with that because the only logical thing to do is have it set values for StartDate and EndDate, and you can't do that without some point of reference. You could require StartDate to be set, and then take a value for Days and then use that to calculate EndDate, but it all boils down to what your business requirements are.
Add the Days property to your view model and store the result of the DaysCalc on it.
Then you can use it like:
#Html.EditorFor(model =>model.Days, new { htmlAttributes = new { #class = "form-control", disabled = "disabled"} })
Try creating a property on your model (this is assuming you are using a typed model, not a dynamic one):
public int Days
{
get {
QuoteApp.Models.Quote ts = new QuoteApp.Models.Quote();
ts.StartDate = StartDate;
ts.EndDate = EndDate;
int days;
days = (EndDate.Day - StartDate.Day);
return days;
}
}
Usage:
#Html.EditorFor(model => model.Days, new { htmlAttributes = new { #class = "form-control", disabled = "disabled"} })
I'm not sure if EditorFor will work quite right with a readonly property or not, but it looks like you are just using it for display or some other purpose anyway?
Also, I'm not sure what you are using the Quote object for, since it doesn't appear you are doing anything with it other than creating it, so this could be possibly simplified into:
public int Days
{
get {
return EndDate.Day - StartDate.Day;
}
}
Related
Edit 1
Just to Clarify a couple of points,
The datetime picker/script appears to be working fine, the times and dates are selectable and fill the textbox with the correct values as expected.
The issue occurs when a time/date is manually entered into the textbox by the user and an invalid time is entered (i.e. "78/5/2017 12:00" or "12/5/2017 12:62")
I've updated a typo in the code, the error is still occurring with the corrected code.
I have my fingers crossed I'm missing something obvious because this makes no sense to me. Any help you can offer would be much appreciated. I have included my code at the end of the question.
The Problem
I have two DateTime fields which are included in a form on a page in my application: StartTime and EndTime.
Both of the fields are setup and put onto the page using (what I believe to be) identical code.
The StartTime field works perfectly, only excepting valid times as input and displaying an error for nonsensical times such as 28:30 or 17:67 until the User corrects it.
The EndTime field however does not validate correctly. Bad inputs are switched back to the current Time/Date before being passed back to the controller, the controller never even sees the bad values meaning I can't catch it and return an error at that point.
If nonsensical values are given in both fields then the submission is prevented and both fields show a validation error which suggests that the EndTime validation does work, it just doesn't prevent form submission.
My Efforts
As I have one working field I have attempted to use that to correct the error. However I hit a stumbling block in realising that there are no differences between the two. Deciding that I must have missed something I switched the variable names round so that the StartTime would be using the EndTime code and vice versa, I did this in each of the sections below one by one hoping to find a point where the field which was working swapped. That, however, never happened. Even once the entirety of their code was switched over it was still found to be the EndTime variable/field which was broken and the StartTime variable/field which was working.
My Research
Despite spending nearly a week with this bug now I have been unable to find any similar problems online and am at a complete stumbling block as to where to go or what to try now. I have tried looking for issues caused by DateTime calendar pickers as well as validation errors in general but can't find anything of use to this situation.
This is one of the last bugs to fix before the project is completed and so any help or even ideas you can offer would be amazing.
The Code
I have included everything I could think to here that interacts with the fields in question. If I have missed anything or you need more info please let me know.
The Entity Model
I have the following two DateTime fields in my Record Entity
public partial class Record
{
// Other entity fields
// ....
// ...
// ..
[DisplayName("Start Time")]
[DataType(DataType.DateTime)]
[DisplayFormat(DataFormatString = "{0:g}", ApplyFormatInEditMode = true)]
public DateTime StartTime { get; set; }
[DisplayName("End Time")]
[DataType(DataType.DateTime)]
[DisplayFormat(DataFormatString = "{0:g}", ApplyFormatInEditMode = true)]
public DateTime EndTime { get; set; }
// and in the constructor
public Record()
{
// initialise the DateTime fields with the current DateTime,
// adjusted for daylight savings
BaseController b = new BaseController();
StartTime = b.TimeNow();
EndTime = b.TimeNow();
}
}
For the sake of completion this is the TimeNow() function's code:
public DateTime TimeNow()
{
TimeZoneInfo tz = TimeZoneInfo.FindSystemTimeZoneById("GMT Standard Time");
DateTime t = DateTime.Now;
if (tz.IsDaylightSavingTime(t))
t = t.AddHours(1);
return t;
}
The ViewModel
The Record entity is then included into a ViewModel as follows:
public class Home_UserAddRecord
{
[DisplayName("Record")]
public Record record { get; set; }
// Other ViewModel fields
// ....
// ...
// ..
// and the blank constructor:
public Home_UserAddRecord()
{
record = new Record();
Error = false;
ErrorMessage = string.Empty;
}
}
The CSHTML Form
They are then included into a form on the page like so:
#using (Html.BeginForm())
{
<div class="form-horizontal">
<div class="form-group col-md-12">
#Html.LabelFor(model => model.record.StartTime, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-5">
#Html.EditorFor(model => model.record.StartTime, new { htmlAttributes = new { #Value = Model.record.StartTime.ToString("dd/MM/yyyy HH:mm"), #class = "form-control", #id = "StartDate" } })
#Html.ValidationMessageFor(model => model.record.StartTime, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group col-md-12">
#Html.LabelFor(model => model.record.EndTime, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-5">
#Html.EditorFor(model => model.record.EndTime, new{ htmlAttributes = new{ #Value = Model.record.EndTime.ToString("dd/MM/yyyy HH:mm"), #class = "form-control", #id = "EndDate" } })
#Html.ValidationMessageFor(model => model.record.EndTime, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
}
An Attached Script
And then finally they have a script applied to them to allow the use of a calendar picker on each input. The script looks like this:
#section Scripts{
<script>
var Start = new dhtmlXCalendarObject("StartDate");
Start.setDateFormat("%d/%m/%Y %H:%i");
Start.showToday();
Start.attachEvent("onTimeChange", function (d) {
var DateText = Start.getDate(true)
document.getElementById("StartDate").value = DateText;
});
var End = new dhtmlXCalendarObject("EndDate");
End.setDateFormat("%d/%m/%Y %H:%i");
End.showToday();
End.attachEvent("onTimeChange", function (d) {
var DateText = End.getDate(true)
document.getElementById("EndDate").value = DateText;
});
</script>
}
Perhaps a suggestion is to use DateTime.TryParseExact method, which will validate the "String" representation of the date using your desired format, and will return an error when the string does not comply to your specified format. Here is code, note dateFormats are based on Australian Standard dates. You can of course add hours and minutes to this too.
Note parsedDate is a DateTime format. Usage of below is:
public void test(){
DateTime ParsedDate;
string SomeDate = "12-May-2017";
if(parseDate(SomeDate, out ParsedDate))
{
// Date was parsed successfully, you can now used ParsedDate, e.g.
Customer.Orders[0].DateRequired = ParsedDate;
}
else
{
// Throw an error
}
}
And the method declaration. Use either in static class, or directly in your class.
public static bool parseDate(string theDate, out DateTime parsedDate)
{
string[] dateFormats = { "d-M-yy", "d-MMM-yy", "d-MMM-yyyy", "d-M-yyyy", "d/M/yy", "d/M/yyyy", "yyyy-mm-dd" };
bool result = DateTime.TryParseExact(
theDate,
dateFormats,
new CultureInfo("en-AU"),
DateTimeStyles.None, out parsedDate);
return result;
} //Convert string-based date to DateTime. Uses a variety of parse templates
I once faced a similar issue where view was submitting the model even if validation failed. I happened to be not using ModelState.IsValid() in the controller action. Do check if this helps.
I am trying to properly display a (nullable) Date-type-only field. For this field, my Model and ViewModel classes are defined as....
[DisplayFormat(DataFormatString = "{0:M/dd/yyyy}")]
[DataType(DataType.Date)]
public DateTime? PeriodEnd { get; set; }
In my details view (based on model), it's showing the date properly (excluding the time element):
#Html.DisplayNameFor(model => model.PeriodEnd)
Problem: In my edit view (based on ViewModel), it's showing the time also, which I'm trying to exclude. Here's how it's defined.
#Html.TextBoxFor(model => model.PeriodEnd, new { #class = "datepicker" })
I also tried....
#Html.TextBoxFor(model => model.PeriodEnd.Value().ToShortDateString(),
new { #class = "datepicker" })
... but that produced an error.
Is there a better way to accomplish this?
Well, first, you're not utilizing the data annotation. Something like DataType.Date doesn't do anything on its own. Using editor templates (Html.EditorFor), it can be utilized to provide an date type input, but if you use Html.TextBoxFor, it's basically ignored.
You can fix that by either 1) using Html.EditorFor instead or 2) explicitly setting the type: #Html.TextBoxFor(m => m.PeriodEnd, new { type = "date", #class = "datepicker" }).
This was the solution:
#Html.TextBoxFor(m => m.PeriodEnd, "{0:M/d/yyyy}", new { #class = "datepicker" })
Thanks to all for your feedback!
There is an entity that has public List<DayOfWeek> DefaultDaysOfWeek.
I want to bind this list to a set of 7 checkboxes respectively in the view.
I have not found a default way to do something similar to:
#Html.EditorFor(model => model.DefaultDaysOfWeek, new { htmlAttributes = new { #class = "form-control" } })
I have also tried the CheckBoxList(For) framework.
I believe the reason none of this has worked is because I cannot access this enumeration in the View.
#if (Model.RecDateFrom.HasValue)
{
#Html.EditorFor(model => model.RecDateFrom,
new {htmlAttributes =
new {#Value = Model.RecDateFrom.Value.ToString("yyyy-MM-dd"),
#class = "form-control input-sm small-input-fix"}})
}
else
{
#Html.EditorFor(model => model.RecDateFrom,
new {htmlAttributes = new {#class = "form-control input-sm small-input-fix"}})
}
#Html.ValidationMessageFor(model => model.RecDateFrom, "", new {#class = "text-danger"})
You can see above how I have to handle if the datetime is null before setting the value. I have to set the value because MVC uses the incorrect format for a date input, making it so chrome doesn't have the correct default value.
I do not want to use the accepted solution in this question because that changes the format of the display for also.
I've tried using editor templates, but it seems like you have to start from scratch, rather than extending the built in editor template for Date datatypes (this seems like a large flaw of MVC, unless I'm missing something).
If you are wanting to render the browsers HTML5 datepicker, you just need to apply the correct attributes to you property. Note the format string must be in the ISO format.
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime RecDateFrom { get; set; }
and then in the view
#Html.EditorFor(m => m.RecDateFrom, new { htmlAttributes = new { #class = "form-control input-sm small-input-fix" } })
Side note: The HTML5 datepicker is not supported in older browsers and not yet at all in FireFox, so it may be better (at least in the short term) to use a jquery plugin (and set the format in the plugin initializer). For example, using the jquery ui datepicker - $('#datepicker').datepicker({ dateFormat: 'dd-mm-yy' })
One of my models contains a property which looks like this
private Nullable<DateTime> myDate;
[Display(Name = "My Date")]
[Editable(false)]
// [DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:dd/MM/yyyy HH:mm}", ApplyFormatInEditMode = true)]
public DateTime MyDate
{
get { return this.myDate?? new DateTime(1, 1, 1); }
set { this.myDate = value; }
}
// [DataType(DataType.Date)] is commented out because it's a leftover from some experiments I made.
In my "edit" view I render the property like this
// input.noborder { border: 0 none; }
#Html.EditorFor(model => model.MyDate, new { #readonly = true, #class = "noborder" })
#Html.ValidationMessageFor(model => model.InvoiceDate)
I did not create an EditorTemplate for DateTime type. Finally, in the scripts of the same view, I attach jQuery Datepicker
var $dpMyDate = $('[name=MyDate]');
$dpMyDate.datepicker();
I basically want to render a read-only datepicker (next step would be to implement more scripts which would enable it if some conditions are met).
Everything seems to work but upon clicking the "save" button, validation tells me that the field is required, but I expected it not to be, since I didn't put the [Required] attribute in the class. Also (but this might just be me assuming too much) it feels weird that a readonly input field would be required (how is one supposed to fill it?)
I don't see anything wrong with the code, am I missing something ?
From reading the comments it looks like you failed at the javascript validation that ASP.NET MVC adds. The issue is that, while your private field is nullable, the property is non-nullable (DateTime is a struct). ASP.NET MVC binds to the property and concludes that it is "Required" even though it's not marked as [Required].
As a result, ASP.NET MVC will generate javascript as if it were marked as a [Required] property. In particular, you used an EditorFor() and so the javascript ASP.NET MVC injects will treat it as required. Since it's a readonly form element I believe the javascript will complain.
The way to get around this is to use a DisplayFor() which doesn't have that javascript attached.
Note that if you have a nullable property that is [Required] but you don't want it to be editable, you'll actually need to do something extra in addition to using DisplayFor()--you'll need to submit something in the HTTP request that ASP.NET MVC knows how to model bind. The most popular option is an <input> element with type=hidden. Because the user has absolute control over the HTTP request he sends to the server, you would probably want to ignore the submitted value to prevent "overposting".
Try this,
Change this
#Html.EditorFor(model => model.MyDate, new { #readonly = true, #class = "noborder" })
to
#Html.TextBoxFor(model => model.MyDate, new { #readonly = true, #class = "noborder" })
And remove #Html.ValidationMessageFor(model => model.InvoiceDate) it's no need.
I think its because your date not Nullable.
Try to define it like
public DateTime? MyDate