Why are my Guids being cleared and time being stripped from my DateTime fields in AspnetCore app? - asp.net-core

Net 5.0
I have several fields on a form, two are readonly fields. One is a Guid the other is a DateTime.
When changes are submitted, the Guid is set to 00000000-0000-0000-0000-000000000000 and the time is removed from my DateTime column 2021-04-13 02:36:37.4567940 becomes 2021-04-13 00:00:00.0000000
<div class="form-group">
<label asp-for="Record.MyGuid" class="control-label"></label>
<input asp-for="Record.MyGuid" class="form-control" readonly/>
<span asp-validation-for="Record.MyGuid" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Record.FormInserted" class="control-label"></label>
<input asp-for="Record.FormInserted" class="form-control" readonly/>
<span asp-validation-for="Record.FormInserted" class="text-danger"></span>
</div>
As they appear on the model
[Display(Name="Website ID")]
[Required(ErrorMessage = "There should not be a record without an Id. Please report this.")]
[Editable(false)]
public Guid MyGuid { get; private set; }
[Display(Name="Submission date")]
[DataType(DataType.Date)]
[Required(ErrorMessage = "Submission date is required.")]
public DateTime FormInserted { get; set; }
When the record is retrieved, it has a complete Guid and date. However, when the Post action starts those values are changed even though the form still correctly displays the values.
Here is the Post handler. When I inspect the object Record, the values have been changed when it reaches the if(Record.Id > 0) step.
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
// At this point, the Guid is now all Zeros
// and the time has been removed from the DateTimee
if(Record.Id > 0)
{
await _RecordData.Update(Record);
}
else
{
// No Id means new record. Add it.
_RecordData.Add(Record);
}
// Commit the changes to the database
await _RecordData.Commit();
// Message to pass to the details page
TempData["StatusMessage"] = "Record saved!";
// Redirect to Details page
return RedirectToPage("./Edit", new { RecordId = Record.MyGuid });
}
What do I need to do to prevent this from happening and preserve the read-only data?

The issue is caused by your model class.
You can change your model like following.
[Display(Name="Website ID")]
[Required(ErrorMessage = "There should not be a record without an Id. Please report this.")]
public Guid MyGuid { get; set; }
[Display(Name="Submission date")]
[DataType(DataType.DateTime)]
[Required(ErrorMessage = "Submission date is required.")]
public DateTime FormInserted { get; set; }
Delete the line [Editable(false)] and change DataType.Date to DataType.DateTime.
Besides delete the code [Editable(false)],you also need to delete the code private.

Related

ASP.Net core Model's date property is blank in edit mode

I have a Razor pages web app and one of the models' is for colleague info and includes their date of birth.
When I look at the scaffolded pages for a colleague, the Date of Birth field is populated in the details page but not the edit page.
Images below will show what I mean.
Here is the Details page
And here is the Edit page
As you will know, as the Edit page is blank, If I change another field e.g. Staff Position and save, the DOB for the colleague becomes null.
As I say the pages are from the EF Core scaffolding so I believe the HTML for the form should be correct.
Edit Page HTML
<div class="form-group">
<label asp-for="Colleague.DateOfBirth" class="control-label"></label>
<input asp-for="Colleague.DateOfBirth" class="form-control" />
<span asp-validation-for="Colleague.DateOfBirth" class="text-danger"></span>
</div>
Colleague is a bind Property of the Colleague model in the Page Model. ALl other fields, as seen in the image populate fine.
Update
As I say it is using Model Binding
[BindProperty]
public Colleague Colleague { get; set; }
OnPost
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_context.Attach(Colleague).State = EntityState.Modified;
try
{
selectedBranch = Colleague.BranchID;
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!ColleagueExists(Colleague.ColleagueID))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToPage("../BranchColleagues", new { id = selectedBranch });
}
[DataType(DataType.Date)]
[Display(Name = "Date of Birth")]
[DisplayFormat(DataFormatString = "{0:dd/MM/yyyy}", ApplyFormatInEditMode = true)]
public DateTime? DateOfBirth { get; set; }
I also faced the same problem. It works while I use .Value in input tag
<input asp-for="Colleague.DateOfBirth.Value" class="form-control" />
I am placing my quick fix solution as the question has been empty now for a month.
By removing this annotation from the POCO class:
[DisplayFormat(DataFormatString = "{0:dd/MM/yyyy}", ApplyFormatInEditMode = true)]
The date value now appears in the edit form as required.

Razor Pages CheckBox throwing error in Edit Page

I need some help in inserting checkbox value to mssql database and retrieving the same in edit page.
This is model class
public class RequestForm{
[Key]
public int ID { get; set; }
public string OtherCompetitorsChecked { get; set; }
public string OtherCompetitorName { get; set; }
}
This is my RequestForm.cshtml file
<div class="tr">
<div class="td">
<input id="ChkOthers" style="margin-left:40px;" asp-for="RequestForm.OtherCompetitorsChecked" type="checkbox" value="Others" /> Others
</div>
<div class="td">
<input id="CompetitorsOthersName" title="Please Fill In Other Competitor Name" asp-for="RequestForm.OtherCompetitorName" type="text" class="form-control-slp" required disabled style="width:50%" />
</div>
</div>
When checking im inserting the checkbox value into database thats why i used string datatype in model class.Im able to insert the data to the database,when im fetching the data its showing error like below
InvalidOperationException: Unexpected expression result value 'Others' for asp-for. 'Others' cannot be parsed as a 'System.Boolean'.
is there any way to fix this?
InvalidOperationException: Unexpected expression result value 'Others'
for asp-for. 'Others' cannot be parsed as a 'System.Boolean'.
public string OtherCompetitorsChecked { get; set; }
This issue relates the OtherCompetitorsChecked data type. The Input Tag Helper sets the HTML type attribute based on the .NET type. Form the following list, we can see that, for the Input checkbox, the .Net type should be Bool type.
So, to solve the above issue, change the OtherCompetitorsChecked's data type to the bool type:
public class RequestForm
{
[Key]
public int ID { get; set; }
public bool OtherCompetitorsChecked { get; set; }
public string OtherCompetitorName { get; set; }
}
Then, in the Get or Post method, when you set its value, it should be true or false.
public void OnGet()
{
RequestForm = new RequestForm() {
ID = 1001,
OtherCompetitorName = "AA",
OtherCompetitorsChecked = true
};
}
Besides, in your application, might be you want to display a list of selected items using checkbox, and want to use a string type to store the selected value. If that is the case, you could try to use the html <input type="checkbox" name ="selectedCourses"> element to display the items (without using Tag helper) or use a <select> tag, then, in the Post method, get the selected option's value via the html element's name property.
Code like this (you could change the checkbox value to the model data, model detail information, refer this tutorial):
<input type="checkbox" name="selectedCompetitorName" value="Others" />
the Post method:
public void OnPost(string selectedCompetitorName)
{
if (ModelState.IsValid)
{
var data = RequestForm;
}
}
<input id="CompetitorsOthersName" title="Please Fill In Other Competitor Name" asp-for="RequestForm.OtherCompetitorName" type="text"
class="form-control-slp" required disabled style="width:50%" />
At the end, according to the above code, I assume you want to make the CompetitorsOthersName text box readonly, if that is the case, try to remove the disabled attribute, and add the readonly attribute. Because, if using the disabled attribute, after submitting the form, the submitted model's CompetitorsOthersName property will be null. You can check it.

Create/Update one-to-many relationship models on one page

Can't find an example of this online that doesn't involve creating or updating rows for each individual model on separate pages. I have a simple visitation form, where the overall Visit is a model, with the host's information and other generic parameters. The second model is Visitor, of which a Visit can have many. Relationship works great, I can update them separately.
I've built a request form which I'd like to do everything on one page. Top part of the form is generic information about the visit and the bottom half is a javascript dynamic form section to add/remove visitors on the fly. Form works great, enters the Visit information just fine, but I can't take in the List from the information coming in. Names for them are following the 'Visitors[1].Name' etc etc format.
I've tried adding List Visitors as a variable inside the Visit model, I've also tried a combined custom model, containing both Visit and Visitors. Anyone have any suggestions?
According to your description, I guess this issue may be related with input's name value. Since the model binding will bind the value according to the parameter's name. I suggest you could check the input name to make sure it is match the model binding format.
For example:
If your visit and visitor's class as below:
public class Visit
{
public int id { get; set; }
public string visitname { get; set; }
public List visitors { get; set; }
}
public class Visitors
{
public int id { get; set; }
public string visitor { get; set; }
}
Then the visitor's input name should be visitors[0].id , visitors[1].id,visitors[2].id, visitors[0].visitor,visitors[1].visitor or else.
More details, you could refer to below codes:
Controller:
public class HomeController : Controller
{
Visit visits;//It is a global variable
public HomeController()
{
visits = new Visit
{
id = 10,
visitname = "visit1",
visitors = new List<Visitors>
{
new Visitors{ id=19, visitor="visitor1"},
new Visitors{ id=20, visitor="visitor2"}
}
};
}
public IActionResult Index()
{
return View(visits);
}
}
In Index.cshtml, the changes made by JavaScript to the view may affect the changes of the subscript in Visitors1.Name. So the index value should be changed when adding elements and deleting corresponding elements.
#model solution930.Models.Visit
#{
//Set a global variable
var count = Model.visitors.Count;
}
<form action="/home/get" method="post">
id
<input asp-for="#Model.id" />
visitname
<input asp-for="#Model.visitname" />
<div id="visitors">
#for (var i = 0; i <count; i++)
{
<div class="visitor">
<input name="visitors[#i].id" asp-for="#Model.visitors[i].id" />
<input name="visitors[#i].visitor" asp-for="#Model.visitors[i].visitor" />
<input type="button" name="name" value="deleterow" onclick="del(event,#Model.visitors[i].id)" />
</div>
}
</div>
<input type="submit" name="name" value="sub" />
</form>
<button id="addvisit" onclick="add()">add</button>
#section Scripts{
<script>
var hasCount=#count;
function del(e, id) {
if (index == 0) {
console.log(e.currentTarget.parentElement)
e.currentTarget.parentElement.remove()
return;
}
location.href = '/home/delete?id=' + id
}
function add() {
var ele = '<div class="visitor"> <input name="visitors[' + hasCount + '].id" type="number" data-val="true" data-val-required="The id field is required." id="visitors_' + hasCount + '__id" value=""> <input name = "visitors[' + hasCount + '].visitor" type = "text" id = "visitors_' + hasCount + '__visitor" value = "" > <input type="button" name="name" value="deleterow" onclick="del(event,0)"> </div>'
$('#visitors').last().parent().append(ele)
hasCount++
console.log(hasCount)
}
</script>
}
Result:

Any alternative to hidden fields when updating from a viewmodel? I don't want to have all fields in the edit page

I know there is a way to do this the "right" way, but for some reason I can't find an answer. I even saw on Microsofts guide that hidden fields are the way to go, but it feels "wrong".
I am finding my update works fine when I put all the hidden fields in the Edit form:
<input type="hidden" asp-for="OrgUnits.Organizations" />
<input type="hidden" asp-for="OrgUnits.Address" />
<input type="hidden" asp-for="OrgUnits.AlternateId" />
<input type="hidden" asp-for="OrgUnits.Category" />
<input type="hidden" asp-for="OrgUnits.City" />
<input type="hidden" asp-for="OrgUnits.FriendlyPath" />
<input type="hidden" asp-for="OrgUnits.IsTop" />
<input type="hidden" asp-for="OrgUnits.Name" />
<input type="hidden" asp-for="OrgUnits.NextChildId" />
<input type="hidden" asp-for="OrgUnits.RecoveryOverride" />
<input type="hidden" asp-for="OrgUnits.RowStatus" />
<input type="hidden" asp-for="OrgUnits.RowVersion" />
<input type="hidden" asp-for="OrgUnits.State" />
<input type="hidden" asp-for="OrgUnits.UseAppVersion" />
<input type="hidden" asp-for="OrgUnits.ZipCode" />
But, that seems like a poor way to write code. I only want a few of the fields in this table to be editable.
Here is my controller:
public async Task<IActionResult> Edit(string id, [Bind("OrgUnits")] OrgUnitsViewModel orgUnitsViewModel)
{
id = Uri.UnescapeDataString(id);
if (id != orgUnitsViewModel.OrgUnits.OrgUnitId)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
//Get org for the DbCatalog
var org = await _opkCoreContext.Organizations.FindAsync(orgUnitsViewModel.OrgUnits.OrgId);
_serverConnectionHelper.SetDatabaseConnectStringToSession(org.DbCatalog);
_opkDataContext.Update(orgUnitsViewModel.OrgUnits);
await _opkDataContext.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!OrgUnitsExists(orgUnitsViewModel.OrgUnits.OrgUnitId))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index), new { currentSearchFilter = orgUnitsViewModel.OrgUnits.OrgUnitId });
}
return View(orgUnitsViewModel);
}
Is this really how this is supposed to be done. I went the route of AutoMapper, but that was failing for me and I don't quite understand how to use it. Anyways, here is my error:
DbUpdateConcurrencyException: Database operation expected to affect 1 row(s) but actually affected 0 row(s). Data may have been modified or deleted since entities were loaded.
Hopefully one of you smart people out there know the answer. I am surprised I can't find anything on Google or SO because I know this is extremely common. It is just that hidden fleids seems so wrong because what if you miss one?
Thank you very much in advance.
I personally do following For partially update an entity:
If I don't want to send an entire model to action to change entire entity, I would make an endpoint(API action) that partially update entity and return success status code instead of View. I would use ajax request to the endpoint to change the entity without refreshing the page.
This is my code for partially updating a Employee entity:
Employee.cs
public class Employee
{
public int Id { get; set; }
[Required(ErrorMessage ="Employee name is a required field.")]
[MaxLength(30,ErrorMessage ="Maximum length for the Name is 30 chrachters.")]
public string Name { get; set; }
[Required(ErrorMessage = "Age is a required field.")]
public int Age{ get; set; }
[Required(ErrorMessage = "Position is a required field.")]
[MaxLength(20, ErrorMessage = "Maximum length for the Position is 20 chrachters.")]
public string Position { get; set; }
public int CompanyId { get; set; }
public Company Company { get; set; }
}
EmployeeUpdateDto.cs
public class EmployeeUpdateDto
{
[Required(ErrorMessage = "Employee name is required")]
[MaxLength(30, ErrorMessage = "Maximum length for the Name is 30 characters")]
public string Name { get; set; }
[Range(18, int.MaxValue, ErrorMessage = "Minimum age must be 18")]
public int Age { get; set; }
[Required(ErrorMessage = "Employee position is required")]
[MaxLength(20, ErrorMessage = "Maximum length for the Position is 20 characters")]
public string Position { get; set; }
}
Controller.cs
public class EmployeesController : ControllerBase
{
private IRepositoryManager _repository;
private ILoggerManager _logger;
private IMapper _mapper;
public EmployeesController(IRepositoryManager repository, ILoggerManager logger, IMapper mapper)
{
_repository = repository;
_logger = logger;
_mapper = mapper;
}
[HttpPatch("{id}")]
public async Task<IActionResult> PartiallyUpdateEmployee(int id, JsonPatchDocument<EmployeeUpdateDto> employeePatches)
{
if (employeePatches is null)
{
_logger.LogError("JsonPatchDocument object sent from client is null");
return BadRequest();
}
var employeeEntity = await _repository.EmployeeRepository.GetEmployeeAsync(employeeId, trackChanges:true);
if (employeeEntity null)
{
_logger.LogInfo($"Employee with id {id} doesn't exist in the database.");
return NotFound();
}
var employeeUpdateDto = _mapper.Map<EmployeeUpdateDto>(employeeEntity);
employeePatches.ApplyTo(employeeUpdateDto, ModelState);
TryValidateModel(employeeUpdateDto);
if (!ModelState.IsValid)
{
_logger.LogError("invalid model state for the patch document");
return UnprocessableEntity(ModelState);
}
_mapper.Map(employeeUpdateDto, employeeEntity);
await _repository.SaveAsync();
return NoContent();
}
//other action methods
}
You must send your request body in the following standard patch format (json):
[
{ "op": "replace", "path": "/name", "new_name": "new name" },
{ "op": "remove", "path": "/position" }
]
That's it. the sample request above would change the Employee name to "new_name" and set the Position to its default value (in this case null).
Above sample needs these prerequisites to work:
Microsoft.AspNetCore.JsonPatch to support JsonPatchDocument type.
Microsoft.AspNetCore.Mvc.NewtonsoftJson to support mapping request to JsonPatchDocument<T>. Configure this in ConfigureServices() method:
services.AddControllersWithViews
.AddNewtonsoftJson();
AutoMapper.Extensions.Microsoft.DependencyInjection to map EmployeeUpdateDto to Employee. Add a mapping profile class and configure AutoMapper in ConfigureServices() method:
services.AddAutoMapper(typeof(Startup));
and
public class MappingpProfile : Profile
{
public MappingpProfile()
{
CreateMap<CompanyUpdateDto, Company>();
CreateMap<CompanyCreationDto, Company>();
CreateMap<Employee, EmployeeDto>();
CreateMap<EmployeeCreationDto, Employee>();
CreateMap<EmployeeUpdateDto, Employee>().ReverseMap();
}
}
In above code we use CreateMap<EmployeeUpdateDto, Employee>().ReverseMap(); for our needs.

Model Validation using StringLength validation attribute

In my ASP.NET MVC Core 1.1.1 I've a form with one input as follows where user is required to enter the district code as two characters such as 01, 02,...10,11, etc. But when I submit the form by entering district code as, say, 123, it still successfully submits the form without forcing user to enter the district code as two characters. What I may be missing?
MyViewModels
...
[Display(Name = "District"),StringLength(2)]
public string Dist { get; set; }
...
Form
#model MyProj.Models.MyViewModel
...
<div class="form-group">
<label asp-for="Dist" class="col-md-2 control-label"></label>
<div class="col-md-2">
<input asp-for="Dist" class="form-control" />
<span asp-validation-for="Dist" class="text-danger"></span>
</div>
<div class="col-sm-pull-8">01 to 53 — district codes</div>
</div>
...
NOTE
I am using default ASP.NET Core Web Application template with latest version of VS2017 that by default installs necessary Bootstrap and other javascripts when one uses such template.
1 - The first parameter of StringLength is Maximum length. To define minimum you can do:
[Display(Name = "District")]
[Required]
[StringLength(2, MinimumLength = 2)]
public string Dist { get; set; }
or:
[Display(Name = "District")]
[Required]
[MinLength(2)]
[MaxLength(3)]
public string Dist { get; set; }
The Required attribute is missing!
View
Validation script:
#section Scripts { #Html.Partial("_ValidationScriptsPartial") }
Controller
[HttpPost]
public IActionResult Contact(MyViewModel model)
{
if (ModelState.IsValid)
{ ... }