UrlHelper.Action() skipping over empty properties - asp.net-mvc

The model below used in two ways:
public class SimpleModel
{
public DateTime? Date { get; set; }
// Some other properties
public SimpleModel()
{
Date = DateTime.Now;
}
}
When model is used in the form, generated URL have empty parameter Date (/Controller/Action?Date=&SomeOtherParams=123) and Date property in model is null (after submitting form with empty date).
...
#Html.TextBoxFor(model => model.Date)
...
Also this model used as third parameter in UrlHelper.Action():
#Url.Action("Action", "Controller", Model) // Model is SimpleModel
In this case if Date is null, generated URL does not contains parameter Date (/Controller/Action?SomeOtherParams=123). And if I following this URL, property Date is DateTime.Now, not null as expected.
How to force passing empty properties to URL?
UPD. Action code
public ActionResult MyAction( SimpleModel model = null )
{
if ( model.Date == null )
{
// show all
}
else
{
// show by date
}
}
Actually, instead of TextBoxFor used DropDownListFor.
#Html.DropDownListFor( model => model.Date, Model.Dates)
User can choose Date from DropDown or live it empty if he want to see all entities.
If user submiting form with empty Date, he following URL /Controller/Action?Date= and getting all entities (Date property was initialized with default value in constructor and then overriten with null).
If user following by generated url via #Url.Action from other page (not submitting form), he getting only todays entities, because URL not contains Date parameter (/Controller/Action). In this case Date property initializing in constructor and this is all.
Problem is model in MyAction never equals null and I cant recognize when user selects empty Date and when he just visit page without parameters.

If you want the Date to be null, then remove the line that assigns DateTime.Now in the empty constructor.
public SimpleModel()
{
//This line is assigning the value to Date whenever a new SimpleModel() is created. Comment it out.
// Date = DateTime.Now;
}

And if I following this URL, property Date is DateTime.Now, not null as expected.
The problem is that the MVC engine is trying to construct a Model object based on the constructor you defined, which sets the Date property to DateTime.Now. It's advised that you do not define a constructor with empty parameters as it will be used by the MVC engine to create the model on POST. As it stands, this is expected behavior.
If you need the functionality, then define another constructor:
public SimpleModel() // Left for MVC
{
}
public SimpleModel(DateTime date) // Use as: new SimpleModel(DateTime.Now) in your action
{
Date = date;
}

Related

MVC Model Binding Post Values Best Practice

I'm trying to figure out if what I'm doing is flawed or acceptable. Specifically, I'm questioning the NULL value I'm getting back in the POST to Controller in 'Timeframes' property. The 'Timeframe' (singular) property DOES contain the value so all is good. However, is this just how model binding works and the property (Timeframes) that is used to populate the DDL comes back as null? Is this best practice and what I'm doing is fine? Is this a concern of sending values around that are not needed...performance concern?
Timeframe = used to return value back to Controller on Post
Timeframes = used to populate DDL values
Drop Down List Box on View:
#Html.DropDownListFor(m => m.Timeframe, Model.Timeframes)
Model:
public class ABCModel
{
public List<SelectListItem> Timeframes { get; set; }
public string Timeframe { get; set; }
}
Controller:
[HttpPost]
public void TestControllerMethod(ABCModel model)
{
//this value is null.
var timeFrames = model.Timeframes;
//this value is populated correctly
var timeFrame = model.Timeframe;
}
A form only posts back the name/value pairs of its successful controls. You have created a form control for property Timeframe, so you get the value of the selected option in the POST method.
You have not (and should not), created form controls for each property of each SelectListItem in your Timeframes property, so nothing relating to it is send in the request when the form is submitted, hence the value of Timeframes is null.
If you need to return the view because ModelState is invalid, then you need to re-populate the TimeFrames property as you did in the GET method (otherwise your DropDownListFor() will throw an exception). A typical implementation migh look like
public ActionResult Create()
{
ABCModel model = new ABCModel();
ConfigureViewModel(model);
return View(model);
}
[HttpPost]
public ActionResult Create(ABCModel model)
{
if (!modelState.IsValid)
{
ConfigureViewModel(model);
return View(model);
}
// Save and redirect
}
private void ConfigureViewModel(ABCModel model)
{
model.TimeFrames = ....; // your code to populate the SelectList
}

ASP.net MVC6 show Empty in a form when for some datetime

I am working on asp.net MVC6 .
I have a model
public class MeterValueViewModel: IAuditable
{
public int Id { get; set; }
public DateTime MyDate{ get; set; }
}
I have this in my form
<input asp-for="MyDate" class="form-control">
MyDate could be 1/1/1900 which is the default date in my application or any other valid date(any date greater than 1/1/1900 is considered valid in my system).
What I want to do is to globally set the date sothat my form show empty date when the value of MyDate is 1/1/1900. It should do the same for another DateTime property in any ViewModel. So this should happen in any form.
Is there some sort of model binding or tag helper to take care of this globally?
Edit - Solution Using Tag Helpers
If you want to do this using tag helpers, create the following tag helper:
[HtmlTargetElement("input", Attributes = "asp-for", TagStructure = TagStructure.WithoutEndTag)]
public class DefaultDateTimeTagHelper : TagHelper
{
private const string ValueAttribute = "value";
private DateTime DefaultDateTime = new DateTime(1900, 1, 1);
public override void Process(TagHelperContext context, TagHelperOutput output)
{
var value = output.Attributes[ValueAttribute].Value;
if (value != null)
{
DateTime dt;
if (DateTime.TryParse(value.ToString(), out dt)
&& dt.Date == DefaultDateTime)
{
output.Attributes.SetAttribute(ValueAttribute, "");
}
}
}
}
Next, register the helper in ~/Views/_ViewImports.cshtml. I'm just registering all tag helpers in my local application, using the * syntax:
#using WebApplication1
#addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
#addTagHelper *, WebApplication1
That's all there is to it. Any input tag that has an asp-for attribute will be checked to see if it has a value attribute that contains a valid DateTime. If it does, and the value equals 1/1/1900, value will be set to an empty string.
Solution Using Editor Templates
You can solve this by using a shared editor template that will be used to render all types of DateTime. To do that, you'll want to do the following:
Create a new EditorTemplates folder in your ~/Views/Shared folder. The name is important, as it has special meaning in MVC, so name it the same as I've shown.
Create a new DateTime.cshtml view in this folder. Again, the name is important as it will be matched against the type you wish to create a template for (in this case, a DateTime instance).
Once you've done that, it should look like this:
Open up DateTime.cshtml and change it to the following:
#model DateTime
#if (Model.Date == new DateTime(1900, 1, 1))
{
#Html.TextBox("", "")
}
else
{
#Html.TextBox("", Model)
}
If the date being passed in matches your default date, it will render a textbox with an empty string, otherwise it will render your date as normal.

Custom Validator Not Showing Error Message

On my Domain Model for my ASP.net MVC3 application I have built a custom validator to ensure the date of birth is inserted in a particular format.
I am saving the date of birth as a string, because my application needs to save the date of birth of long dead people, e.g. Plato, Socrates, etc., just in case you were wondering why not use DateTime to save date of birth.
Here is my custom validator code:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public sealed class ValidateDODDOB : ValidationAttribute
{
// Error Message
private const string DefaultErrorMessage = "Please type the date in the format specified.";
// Gets or sets the Regular expression.
private Regex Regex { get; set; }
// The pattern used for Date of Birth and Date of Death validation.
public string Pattern { get { return #"^(?:\d+\s)?(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)?(?:\s?\d+)(?:\sBCE)?$"; } }
// Initializes a new instance of the VerifyDODDOB class.
public ValidateDODDOB() : base(DefaultErrorMessage)
{
this.Regex = new Regex(this.Pattern);
}
// Determines whether the specified value of the object is valid.
// true if the specified value is valid; otherwise, false.
public override bool IsValid(object value)
{
// convert the value to a string
var stringValue = Convert.ToString(value);
var m = Regex.Match(stringValue);
return m.Success;
}
}
The above works in terms of validating, and stopping the Create/Edit Actions from proceeding through to the database. But no error message is being displayed when the form is returned to the View!
UPDATE IN RESPONSE TO COMMENT 01:
Sorry Olive, I should have posted the view code too. Here it is:
<div class="inputField">
#Html.LabelFor(x => x.DOB, "Date of Birth")
#Html.TextBoxFor(model => model.DOB)
#Html.ValidationMessageFor(model => model.DOB)
</div>
So Yes, I have told it to to show the Validation message as well. And as far as AJAX, it is not via AJAX. Like you said, it is after a full POST Request.
Do you mean you want the message to show in a ValidationSummary control?
If so, try setting the "excludePropertyErrors" of the ValidationSummary HtmlHelper to false:
#Html.ValidationSummary(false)
This will tell the summary control to summary display all errors (having it set to 'true', the default value, will tell the control to display model-level errors only).
I think what you are probably wanting to do is use this method
protected override ValidationResult IsValid(object value, ValidationContext context)
{
// convert the value to a string
var stringValue = Convert.ToString(value);
var m = Regex.Match(stringValue);
if(!m.Success)
{
return new ValidationResult(DefaultErrorMessage);
}
return null;
}
Then in your view make sure you have the ValidationMessageFor, and in the Controller make sure you check ModelState.IsValid and return the original View if it is not valid. That ought to do it.

The conversion of a datetime2 data type to a datetime data type resulted in an out-of-range value

I have the following code in my HomeController:
public ActionResult Edit(int id)
{
var ArticleToEdit = (from m in _db.ArticleSet where m.storyId == id select m).First();
return View(ArticleToEdit);
}
[ValidateInput(false)]
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(Article ArticleToEdit)
{
var originalArticle = (from m in _db.ArticleSet where m.storyId == ArticleToEdit.storyId select m).First();
if (!ModelState.IsValid)
return View(originalArticle);
_db.ApplyPropertyChanges(originalArticle.EntityKey.EntitySetName, ArticleToEdit);
_db.SaveChanges();
return RedirectToAction("Index");
}
And this is the view for the Edit method:
<% using (Html.BeginForm()) {%>
<fieldset>
<legend>Fields</legend>
<p>
<label for="headline">Headline</label>
<%= Html.TextBox("headline") %>
</p>
<p>
<label for="story">Story <span>( HTML Allowed )</span></label>
<%= Html.TextArea("story") %>
</p>
<p>
<label for="image">Image URL</label>
<%= Html.TextBox("image") %>
</p>
<p>
<input type="submit" value="Post" />
</p>
</fieldset>
<% } %>
When I hit the submit button I get the error: {"The conversion of a datetime2 data type to a datetime data type resulted in an out-of-range value.\r\nThe statement has been terminated."} Any ideas what the problem is? I'm assuming that the edit method is trying to update the posted value in the DB to the edited on but for some reason it's not liking it... Although I don't see why the date is involved as it's not mentioned in the controller method for edit?
The issue is that you're using ApplyPropertyChanges with a model object that has only been populated with data in the form (headline, story, and image). ApplyPropertyChanges applies changes to all properties of the object, including your uninitialized DateTime, which is set to 0001-01-01, which is outside of the range of SQL Server's DATETIME.
Rather than using ApplyPropertyChanges, I'd suggest retrieving the object being modified, change the specific fields your form edits, then saving the object with those modifications; that way, only changed fields are modified. Alternately, you can place hidden inputs in your page with the other fields populated, but that wouldn't be very friendly with concurrent edits.
Update:
Here's an untested sample of just updating some fields of your object (this is assuming you're using LINQ to SQL):
var story = _db.ArticleSet.First(a => a.storyId == ArticleToEdit.storyId);
story.headline = ArticleToEdit.headline;
story.story = ArticleToEdit.story;
story.image = ArticleToEdit.image;
story.modifiedDate = DateTime.Now;
_db.SubmitChanges();
This is a common error people face when using Entity Framework. This occurs when the entity associated with the table being saved has a mandatory datetime field and you do not set it with some value.
The default datetime object is created with a value of 01/01/1000 and will be used in place of null. This will be sent to the datetime column which can hold date values from 1753-01-01 00:00:00 onwards, but not before, leading to the out-of-range exception.
This error can be resolved by either modifying the database field to accept null or by initializing the field with a value.
DATETIME supports 1753/1/1 to
"eternity" (9999/12/31), while
DATETIME2 support 0001/1/1 through
eternity.
Msdn
Answer:
I suppose you try to save DateTime with '0001/1/1' value. Just set breakpoint and debug it, if so then replace DateTime with null or set normal date.
This one was driving me crazy. I wanted to avoid using a nullable date time (DateTime?). I didn't have the option of using SQL 2008's datetime2 type either (modelBuilder.Entity<MyEntity>().Property(e => e.MyDateColumn).HasColumnType("datetime2");).
I eventually opted for the following:
public class MyDb : DbContext
{
public override int SaveChanges()
{
UpdateDates();
return base.SaveChanges();
}
private void UpdateDates()
{
foreach (var change in ChangeTracker.Entries<MyEntityBaseClass>())
{
var values = change.CurrentValues;
foreach (var name in values.PropertyNames)
{
var value = values[name];
if (value is DateTime)
{
var date = (DateTime)value;
if (date < SqlDateTime.MinValue.Value)
{
values[name] = SqlDateTime.MinValue.Value;
}
else if (date > SqlDateTime.MaxValue.Value)
{
values[name] = SqlDateTime.MaxValue.Value;
}
}
}
}
}
}
You can also fix this problem by adding to model (Entity Framework version >= 5)
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime CreationDate { get; set; }
If you have a column that is datetime and allows null you will get this error. I recommend setting a value to pass to the object before .SaveChanges();
I got this error after I changed my model (code first) as follows:
public DateTime? DateCreated
to
public DateTime DateCreated
Present rows with null-value in DateCreated caused this error.
So I had to use SQL UPDATE Statement manually for initializing the field with a standard value.
Another solution could be a specifying of the default value for the filed.
In my case, in the initializer from the class I was using in the database's table, I wasn't setting any default value to my DateTime property, therefore resulting in the problem explained in #Andrew Orsich' answer. So I just made the property nullable. Or I could also have given it DateTime.Now in the constructor. Hope it helps someone.
It looks like you are using entity framework. My solution was to switch all datetime columns to datetime2, and use datetime2 for any new columns, in other words make EF use datetime2 by default. Add this to the OnModelCreating method on your context:
modelBuilder.Properties<DateTime>().Configure(c => c.HasColumnType("datetime2"));
That will get all the DateTime and DateTime? properties on all the entities in your model.
I had the same problem, unfortunately, I have two DateTime property on my model and one DateTime property is null before I do SaveChanges.
So make sure your model has DateTime value before saving changes or make it nullable to prevent error:
public DateTime DateAdded { get; set; } //This DateTime always has a value before persisting to the database.
public DateTime ReleaseDate { get; set; } //I forgot that this property doesn't have to have DateTime, so it will trigger an error
So this solves my problem, its a matter of making sure your model date is correct before persisting to the database:
public DateTime DateAdded { get; set; }
public DateTime? ReleaseDate { get; set; }
[Solved] In Entity Framework Code First (my case) just changing DateTime to DateTime? solve my problem.
/*from*/ public DateTime SubmitDate { get; set; }
/*to */ public DateTime? SubmitDate { get; set; }
If you are using Entity Framework version >= 5 then applying the [DatabaseGenerated(DatabaseGeneratedOption.Computed)] annotation to your DateTime properties of your class will allow the database table's trigger to do its job of entering dates for record creation and record updating without causing your Entity Framework code to gag.
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime DateCreated { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime DateUpdated { get; set; }
This is similar to the 6th answer, written by Dongolo Jeno and Edited by Gille Q.
You have to enable null value for your date variable :
public Nullable<DateTime> MyDate{ get; set; }
Also, if you don't know part of code where error occured, you can profile "bad" sql execution using sql profiler integrated to mssql.
Bad datetime param will be displayed something like here :
This problem usually occurs when you are trying to update an entity. For example you have an entity that contains a field called DateCreated which is [Required] and when you insert record, no error is returned but when you want to Update that particular entity, you the get the
datetime2 conversion out of range error.
Now here is the solution:
On your edit view, i.e. edit.cshtml for MVC users all you need to do is add a hidden form field for your DateCreated just below the hidden field for the primary key of the edit data.
Example:
#Html.HiddenFor(model => model.DateCreated)
Adding this to your edit view, you'll never have that error I assure you.
Try making your property nullable.
public DateTime? Time{ get; set; }
Worked for me.
If you ahve access to the DB, you can change the DB column type from datetime to datetime2(7)
it will still send a datetime object and it will be saved
The model should have nullable datetime. The earlier suggested method of retrieving the object that has to be modified should be used instead of the ApplyPropertyChanges.
In my case I had this method to Save my object:
public ActionResult Save(QCFeedbackViewModel item)
And then in service, I retrieve using:
RETURNED = item.RETURNED.HasValue ? Convert.ToDateTime(item.RETURNED) : (DateTime?)null
The full code of service is as below:
var add = new QC_LOG_FEEDBACK()
{
QCLOG_ID = item.QCLOG_ID,
PRE_QC_FEEDBACK = item.PRE_QC_FEEDBACK,
RETURNED = item.RETURNED.HasValue ? Convert.ToDateTime(item.RETURNED) : (DateTime?)null,
PRE_QC_RETURN = item.PRE_QC_RETURN.HasValue ? Convert.ToDateTime(item.PRE_QC_RETURN) : (DateTime?)null,
FEEDBACK_APPROVED = item.FEEDBACK_APPROVED,
QC_COMMENTS = item.QC_COMMENTS,
FEEDBACK = item.FEEDBACK
};
_context.QC_LOG_FEEDBACK.Add(add);
_context.SaveChanges();
Error:
The conversion of a datetime2 data type to a datetime data type resulted in an out-of-range value.
This error occurred when due to NOT assigning any value against a NOT NULL date column in SQL DB using EF and was resolved by assigning the same.
Hope this helps!
Got this problem when created my classes from Database First approach. Solved in using simply
Convert.DateTime(dateCausingProblem)
In fact, always try to convert values before passing, It saves you from unexpected values.
you have to match the input format of your date field to the required entity format which is yyyy/mm/dd
I think the most logical answer in this regard is to set the system clock to the relevant feature.
[HttpPost]
public ActionResult Yeni(tblKategori kategori)
{
kategori.CREATEDDATE = DateTime.Now;
var ctx = new MvcDbStokEntities();
ctx.tblKategori.Add(kategori);
ctx.SaveChanges();
return RedirectToAction("Index");//listele sayfasına yönlendir.
}
It is likely something else, but for the future readers, check your date time format. i had a 14th month
change "CreateDate": "0001-01-01 00:00:00" to "CreateDate": "2020-12-19 00:00:00",
CreateDate type is public DateTime CreateDate
error json:
{
"keyValue": 1,
"entity": {
"TodoId": 1,
"SysId": "3730e2b8-8d65-457a-bd50-041ce9705dc6",
"AllowApproval": false,
"ApprovalUrl": null,
"ApprovalContent": null,
"IsRead": true,
"ExpireTime": "2020-12-19 00:00:00",
"CreateDate": "0001-01-01 00:00:00",
"CreateBy": null,
"ModifyDate": "2020-12-18 9:42:10",
"ModifyBy": null,
"UserId": "f5250229-c6d1-4210-aed9-1c0287ab1ce3",
"MessageUrl": "https://bing.com"
}
}
correct json:
{
"keyValue": 1,
"entity": {
"TodoId": 1,
"SysId": "3730e2b8-8d65-457a-bd50-041ce9705dc6",
"AllowApproval": false,
"ApprovalUrl": null,
"ApprovalContent": null,
"IsRead": true,
"ExpireTime": "2020-12-19 00:00:00",
"CreateDate": "2020-12-19 00:00:00",
"CreateBy": null,
"ModifyDate": "2020-12-18 9:42:10",
"ModifyBy": null,
"UserId": "f5250229-c6d1-4210-aed9-1c0287ab1ce3",
"MessageUrl": "https://bing.com"
}
}

ASP.NET MVC 2 "value" in IsValid override in DataAnnotation attribute passed is null, when incorrect date is submitted

This is my first question here on stack overflow.
i need help on a problem i encountered during an ASP.NET MVC2 project i am currently working on.
I should note that I'm relatively new to MVC design, so pls bear my ignorance.
Here goes :
I have a regular form on which various details about a person are shown. One of them is "Date of Birth". My view is like this
<div class="form-items">
<%: Html.Label("DateOfBirth", "Date of Birth:") %>
<%: Html.EditorFor(m => m.DateOfBirth) %>
<%: Html.ValidationMessageFor(m => m.DateOfBirth) %>
</div>
I'm using an editor template i found, to show only the date correctly :
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<System.DateTime?>"%>
<%= Html.TextBox("", (Model.HasValue ? Model.Value.ToShortDateString() : string.Empty))%>
I used LinqToSql designer to create my model from an sql database. In order to do some validation i made a partial class Person to extend the one created by the designer (under the same namespace) :
[MetadataType(typeof(IPerson))]
public partial class Person : IPerson
{ //To create buddy class }
public interface IPerson
{
[Required(ErrorMessage="Please enter a name")]
string Name { get; set; }
[Required(ErrorMessage="Please enter a surname")]
string Surname { get; set; }
[Birthday]
DateTime? DateOfBirth { get; set; }
[Email(ErrorMessage="Please enter a valid email")]
string Email { get; set; }
}
I want to make sure that a correct date is entered. So i created a custom DataAnnotation attribute in order to validate the date :
public class BirthdayAttribute : ValidationAttribute
{
private const string _errorMessage = "Please enter a valid date";
public BirthdayAttribute() : base(_errorMessage) { }
public override bool IsValid(object value)
{
if (value == null)
{
return true;
}
DateTime temp;
bool result = DateTime.TryParse(value.ToString(), out temp);
return result;
}
}
Well, my problem is this. Once i enter an incorrect date in the DateOfBirth field then no custom message is displayed even if use the attribute like [Birthday(ErrorMessage=".....")]. The message displayed is the one returned from the db ie "The value '32/4/1967' is not valid for DateOfBirth.". I tried to enter some break points around the code, and found out that the "value" in attribute is always null when the date is incorrect, but always gets a value if the date is in correct format. The same ( value == null) is passed also in the code generated by the designer.
This thing is driving me nuts. Please can anyone help me deal with this?
Also if someone can tell me where exactly is the point of entry from the view to the database. Is it related to the model binder? because i wanted to check exactly what value is passed once i press the "submit" button.
Thank you.
Generally speaking all validation stuff is work after binder binded values. As you can understand it's not possible to bind dateTime value from string like "asdvfvk". So, when binder encounters with such an error it adds it to the ModelState (take a look at ModelState["DateOfBirth"].Errors[0].ErrorMessage), and binds default value. Default value for DateTime? is null, so that's why you always get it in IsValid method. It's normal.
So as you can see validation for date has sence if you whant to check for example if it's bigger then some other date. If input string is incorrect no further verification have sence.
What can you do?
First straightforward way - you can correct your action like this
[HttpPost]
public ActionResult About(Person person, string dateOfBirth) {
var birthdayAttribute = new BirthdayAttribute();
if( !birthdayAttribute.IsValid(dateOfBirth)) {
ModelState["DateOfBirth"].Errors.Clear();
ModelState.AddModelError("DateOfBirth", birthdayAttribute.ErrorMessage);
}
.......
}
As you can see there is string dateOfBirth, so binder have no problems with binding string value. But this will not make your users happy.
The better way - ensure that string will be in correct format with client Javascript. Actualy people use date picker controls for dates and feel themselves good.
In addition take a look here http://forums.asp.net/t/1512140.aspx
Especialy Brad Wilson's answer.

Resources