Updating related entities - entity-framework

AppUser identity model:
public virtual ICollection<UserPhones> UserPhones { get; set; }
Using Razor Pages, I call a partial view, like so:
#await Html.PartialAsync("_NameAndID", Model.AppUser)
PageModel:
[BindProperty]
public AppUser AppUser { get; set; }
public IActionResult OnGet()
{
AppUser = _userManager.Users
//.Include(x => x.UserAddresses) //OMITTED BC USING LAZY LOADING
.SingleOrDefaultAsync(x => x.UserName ==
_httpContext.HttpContext.User.Identity.Name).Result;
return Page();
}
Within _NameAndID.cshtml, I explicitly reference a particular telephone from the UserPhones entity. With:
<input type="hidden" asp-for="UserPhones
.SingleOrDefault(z => z.Type == EnumPhoneType.Mobile).UserPhoneId" />
//other properties removed for brevity
<div class="rvt-grid__item">
<label asp-for="UserPhones.SingleOrDefault(z => z.Type == EnumPhoneType.Mobile).PhoneNumber">Mobile Phone</label>
<input asp-for="UserPhones.SingleOrDefault(z => z.Type == EnumPhoneType.Mobile).PhoneNumber" autocomplete="tel" />
<span asp-validation-for="UserPhones.SingleOrDefault(z => z.Type == EnumPhoneType.Mobile).PhoneNumber"></span>
</div>
At runtime, the explicit mobile phone number is loaded properly. However when posting to public async Task<IActionResult> OnPostAsync() the related AppUser.UserPhones is null. (The problem)
Can you help?
Thank you in advance!!!!

The Reason
The asp-for does not work well for this scenario.
Considering your code in _NameAndID.cshtml :
<input asp-for="UserPhones.SingleOrDefault(z => z.Type == EnumPhoneType.Mobile).PhoneNumber" autocomplete="tel" />
Note the LINQ extension method .SingleOrDefault(...) here. The asp-for here does not know how to get the name for UserPhones.SingleOrDefault(z => z.Type == EnumPhoneType.Mobile).PhoneNumber, so it just render it as PhoneNumber. As a result, the rendered html will be :
<input autocomplete="tel" type="text" id="PhoneNumber" name="PhoneNumber" value="">
Let's say someone inputs an value of 911, when posted to server, the payload will be :
PhoneNumber=911
As your page model on server side is :
[BindProperty]
public AppUser AppUser{get;set;}
public IActionResult OnGet()
{
// ...
}
public IActionResult OnPostAsync()
{
return Page();
}
Note the AppUser.UserPhones property is a collection. in other words, AppUser expects a payload like :
UserPhones[0].UserPhoneId=1&UserPhones[0].PhoneNumber=911&UserPhones[1].UserPhoneId=2&UserPhones[1].PhoneNumber=119
However, what you send to the server is :
PhoneNumber=911
So the App.UserPhones will always be null and the AppUser.PhoneNumber property will be 911.
How to Fix
Firstly, in order to bind the UserPhones automatically, I change the type of App.UserPhones to IList<UserPhones> , so that we can use a index syntax
public class AppUser : IdentityUser{
// public virtual ICollection<UserPhones> UserPhones { get; set; }
public virtual IList<UserPhones> UserPhones { get; set; }
}
Secondly, don't use complex query in asp-for, use simple index syntax instead. For example, if you would like to post some UserPhones or post all UserPhones, you can add an index for each field :
#for(var i=0;i <Model.UserPhones.Count(); i++) {
<div class="rvt-grid__item">
<label asp-for="#Model.UserPhones[i].UserPhoneId"></label>
<input asp-for="#Model.UserPhones[i].UserPhoneId"/>
<label asp-for="#Model.UserPhones[i].PhoneNumber"></label>
<input asp-for="#Model.UserPhones[i].PhoneNumber"/>
<span asp-validation-for="#Model.UserPhones[i].PhoneNumber"></span>
</div>
}
In this way, when someone submits the form, AppUser.UserPhones will be the correctly set. Here's a screenshot of demo :

Related

How and where to validate uniqueness of attribute/property in ASP.NET Core MVC using Entity Framework Core 6

I want to check, that users table records does not have specific email already stored.
If there is, then ModelState.IsValid returns false in controller action.
I understand need of unique constraint and I understand issues with race conditions. Those are not my concern right now.
At this point I just want to make ModelState.IsValid to return false after querying data in right place and making model invalid.
Should I implement such validation:
in DbContext?
in my entity classes (User / Company etc.)?
in SomeCustomValidation class?
directly in controller (since there I can already query the database...)
somewhere else...
And nice bonus would be create solution reusable across all entities :)
How should I do it?
You can custom validation attribute like below:
public class TestEmailAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value,
ValidationContext validationContext)
{
var context = (YourDbContext)validationContext.GetService(typeof(YourDbContext));
if(!context.User.Any(a=>a.Email==value.ToString()))
{
return ValidationResult.Success;
}
return new ValidationResult("Email exists");
}
}
Model:
public class User
{
public int Id { get; set; }
[TestEmail]
public string Email { get; set; }
}
View(Test.cshtml):
#model User
<form method="post" asp-action="Test" asp-controller="Home">
<div class="form-group">
<input asp-for="Email" />
<span asp-validation-for="Email" class="text-danger"></span>
</div>
<input type="submit" value="Post"/>
</form>
Controller:
//GET render the Test.cshtml
public async Task<IActionResult> Test()
{
return View();
}
[HttpPost]
public async Task<IActionResult> Test(User user)
{
if(!ModelState.IsValid)
{
return View(user);
}
return RedirectToAction("Index");
}
better way is that you check it before every insert or update by :
if(db.Table.Any(x => x.UniqueCloumn == newValue))
error = "this record is already exist"
else
{
db.Table.Add(newObject);
db.Savechanges()
}
also there is some approach for reusable code that I do not recommend :
https://www.codeproject.com/Tips/1143215/Validate-a-Unique-Constraint-at-dbContext-Validate

Insert into DB from ASP.Net form

I have an issue when I want to insert an object into the database.
My model is Colis class which has a foreign key to ZoneReserve (ZoneReserveId), which has a foreign key on Reserve (ReserveId).
In my form I choose an existing ZoneReserve and Reserve, but when I post my form, new lines are created in DB, in table ZoneReserve and Reserve. Entity framework do not retrieve the existing line or I don't know...
I don't know if I'm clear enough, sorry for my english ;)
Do you have any advice ? I'm stuck et I tried everything :(
Thank you guys
Colis Model Class :
public class Colis
{
public int ColisId { get; set; }
[Display(Name = "Code barre du colis")]
public string CodeBarreColis { get; set; }
public bool IndAVendreColis { get; set; }
public virtual TypeColis TypeColis { get; set; }
[Display(Name = "Type de colis")]
public int TypeColisId { get; set; }
public ZoneReserve ZoneReserve { get; set; }
[Display(Name = "Emplacement du colis")]
public int ZoneReserveId { get; set;
}
ZoneReserve Model Class :
public class ZoneReserve
{
public int Id { get; set; }
public string NomZoneReserve { get; set; }
public Reserve Reserve { get; set; }
public int ReserveId { get; set; }
}
Reserve Model Class :
public class Reserve
{
[Display(Name = "Réserve")]
public int Id { get; set; }
public string NomReserve { get; set; }
}
My Action in ColisController :
[HttpPost]
public ActionResult CreerColis(Colis colis)
{
_context.Colis.Add(colis);
_context.SaveChanges();
return RedirectToAction("ListeColis");
}
My Form in the view :
#using (Html.BeginForm("CreerColis", "Colis"))
{
<div class="form-group">
#Html.LabelFor(m => m.Colis.CodeBarreColis)
#Html.TextBoxFor(m => m.Colis.CodeBarreColis, new { #class = "form-control" })
</div>
<div class="checkbox">
<label>
#Html.CheckBoxFor(m => m.Colis.IndAVendreColis) A vendre ?
</label>
</div>
<div class="form-group">
#Html.LabelFor(m => m.Colis.TypeColisId)
#Html.DropDownListFor(m => m.Colis.TypeColisId, new SelectList(Model.TypeColis, "Id", "NomTypeColis"), "Selectionner un type", new { #class = "form -control" })
</div>
<div class="form-group">
#Html.LabelFor(m => m.Colis.ZoneReserve.Reserve.Id)
#Html.DropDownListFor(m => m.Colis.ZoneReserve.Reserve.Id, new SelectList(Model.Reserve, "Id", "NomReserve"), "Selectionner une zone", new { #class = "form -control" })
</div>
<div class="form-group">
#Html.LabelFor(m => m.Colis.ZoneReserveId)
#Html.DropDownListFor(m => m.Colis.ZoneReserve.Id, new SelectList(Model.ZoneReserve, "Id", "NomZoneReserve"), "Selectionner une zone", new { #class = "form -control" })
</div>
<button type="submit" class="bt, btn-primary">Enregistrer</button>
}
<script src="~/Scripts/jquery-3.4.1.min.js"></script>
<script>
$(document).ready(function () {
$("#Colis_ZoneReserve_Reserve_Id").change(function () {
$.get("/ZoneReserve/ListeZoneReserveParReserve", { ReserveId: $("#Colis_ZoneReserve_Reserve_Id").val() }, function (data) {
$("#Colis_ZoneReserve_Id").empty();
$.each(data, function (index, row) {
$("#Colis_ZoneReserve_Id").append("<option value='" + row.Id + "'>" + row.NomZoneReserve+ "</option>")
});
});
})
});
</script>
It looks like your razor page is posting info about navigation properties of the Colis object to the controller and creating the full objects instead of creating a new Colis object with just the int foreign key specified.
As is, when posted, '''colis.ZoneReserve''' is not null nor is '''colis.ZoneReserve.Reserve''' reference which tells entity framework to create those related object as well when you .Add(colis) to the context.
[HttpPost]
public ActionResult CreerColis(Colis colis)
{
_context.Colis.Add(colis);
_context.SaveChanges();
return RedirectToAction("ListeColis");
}
You are POSTing unintended parameters to your controller, specifically '''Colis.ZoneReserve.Id''' and '''Colis.ZoneReserve.Reserve.Id''' as you BOUND TO in your razor page (see comments in code):
<div class="form-group">
#Html.LabelFor(m => m.Colis.ZoneReserve.Reserve.Id)
<!-- DropDownListFor m.Colis.ZoneReserve.ReserveId will send that (navigation path) value to the server. //-->
#Html.DropDownListFor(m => m.Colis.ZoneReserve.Reserve.Id, new SelectList(Model.Reserve, "Id", "NomReserve"), "Selectionner une zone", new { #class = "form -control" })
</div>
<div class="form-group">
#Html.LabelFor(m => m.Colis.ZoneReserveId)
<!-- DropDownListFor m.Colis.ZoneResearch.Id will send that navigation property to the server //-->
#Html.DropDownListFor(m => m.Colis.ZoneReserve.Id, new SelectList(Model.ZoneReserve, "Id", "NomZoneReserve"), "Selectionner une zone", new { #class = "form -control" })
</div>
To fix your razor page (and not send unintended values to the server)
change the first drop down list for Reserve to NOT be for anything it'll bind to on the server (you don't even need to POST it's value if you can strip it before submit), one way is to change it's name to something meaningless such as "UnnecessaryData" that won't map in the controller when posted (pseudo-code, not tested)
<div class="form-group">
#Html.LabelFor(m => m.Colis.ZoneReserve.Reserve.Id)
#Html.DropDownList(new SelectList(Model.Reserve, "Id", "NomReserve"),
"Selectionner une zone",
new { #class = "form-control", name = "UnnecessaryData" })
</div>
Change the second drop-down-list to map to the correct property on the Colis object, notice all I did was change m => m.Colis.ZoneReserve.Id to the FK property of Colis m => m.Colis.ZoneReserveId:
<div class="form-group">
#Html.LabelFor(m => m.Colis.ZoneReserveId)
#Html.DropDownListFor(m => m.Colis.ZoneReserveId, new SelectList(Model.ZoneReserve, "Id", "NomZoneReserve"), "Selectionner une zone", new { #class = "form -control" })
</div>
When you POST the form, your Colis object in the controller should have a NULL ZoneReserve property and a non-zero ZoneReserveId property - this will prevent the other data records from being created by entity framework.
Note: You can also simply strip the data from the Colis in the POST controller - but that doesn't correct your implementation on the client razor page that's sending unintended structure to the server in the POST method.
Also note: Because you don't validate that the navigation properties of Colis are NULL in the controller, a malicious user COULD create a lot of crap data on the server by POSTing full object tree data that'll be added with the controller method as implemented.

Blazor validation over a MongoDB Datamodel

I try to link my MongoDB class model to my Blazor page component. I tried to keep all the System.ComponentModel.DataAnnotations.ValidationAttribute to an interface and let the 'real' class with the Bson decoration apart as:
public interface ITestIt
{
int id { get; set; }
[Required(ErrorMessage = "Material cost is required")]
[StringLength(5, ErrorMessage = "Name is too long.")]
string MyName { get; set; }
}
public class TestIt : ITestIt
{
[BsonId]
public int id { get; set; }
public string MyName { get; set; }
}
And include it in my page as:
<h1>Hello, world!</h1>
Welcome to your new app.
<EditForm Model=#testIt OnValidSubmit="HandleValidSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<div class="form-group">
<label for="Name">Name</label>
<InputText #bind-Value=testIt.MyName class="form-control" id="Name" />
<ValidationMessage For="() => testIt.MyName" Description="Salut" />
</div>
<input type="submit" class="btn btn-primary" value="Save" />
</EditForm>
#code{
private ITestIt testIt;
private bool IsDone = false;
protected override async Task OnInitializedAsync()
{
if (IsDone) return;
testIt = new TestIt();
IsDone = true;
}
private void HandleValidSubmit()
{
Console.WriteLine("OnValidSubmit");
}
But it don't work, what is the best way to separate the both without having to decorate all my data model with the DataAnnotations tags and not having to copy one by one each property one by one to and other object?
Thanks!
I don't think Blazor reflects over data annotations of interfaces, only the properties of the implementing object.
I keep my validations in a separate project completely. To do this I use FluentValidation.
You can write a component that accepts an EditContext as a cascading parameter, hook into the events where it requests validation, and execute the FluentValidation code.
Or you can use a pre-made library such as https://www.nuget.org/packages/PeterLeslieMorris.Blazor.FluentValidation/

How to edit nested collections in MVC5?

I have a EF-model which contains a "key" and a "value". The value-table contains a FK to the key. In the EF-model it looks like this:
public partial class dict_key
{
public dict_key()
{
this.dict_value = new HashSet<dict_value>();
}
public int id { get; set; }
public string name { get; set; }
...
public virtual ICollection<dict_value> dict_value { get; set; } //dict_value contains a string "value"
}
My controller is passing the information for editing like this:
// GET: Keys/Texts/5
[Authorize]
public async Task<ActionResult> Texts(int? id)
{
var key = await db.dict_key
.Include(x => x.dict_value)
.Where(x => x.id.Equals(id.Value))
.FirstOrDefaultAsync();
return View(key);
// Debugging 'key' shows that dict_value has 3 correct values.
}
This gets passed to my View which shows the dict_value's correct:
#model Dict.Models.dict_key
#using (Html.BeginForm())
{
<div>Key: #Model.name </div>
<table class="table">
<tr>
<th>Language</th>
<th>Text</th>
</tr>
#for (var i = 0; i < Model.dict_value.Count(); i++)
{
<tr>
<td> #Model.dict_value.ElementAt(i).dict_lang.name_en </td>
<td> #Html.EditorFor(x => x.dict_value.ElementAt(i).value) </td>
</tr>
}
<div class="form-group">
<input type="submit" value="Save" />
</div>
</table>
}
When submitting my changes back to the controller...
[HttpPost]
public async Task<ActionResult> Texts(dict_key dict_key)
{
if (ModelState.IsValid)
{
//Also tried: db.Entry(dict_key).State = EntityState.Modified;
db.Entry(dict_key.dict_value).State = EntityState.Modified;
await db.SaveChangesAsync();
return RedirectToAction("Texts");
}
return View(dict_key);
}
..then my "dict_key" is totally different from the object I passed to my edit-view. The passed object contained the collection of dict_value's and the "returned" and edited object returns with the proper key object, but with an empty dict_value collection.
I try to avoid using a userdefined model or the viewbag to do all of that stuff manually. What is the best practise solution for this?
Collection.ElementAt doesn't generate a proper field name in Razor. You need a List. Here you should use a view model instead of your entity directly and simply make your dict_value collection a List<dict_value> there.
Alternatively, you can create an editor template for dict_value and then in your view just do:
#Html.EditorFor(m => m.dict_value)
Where dict_value there is your entire collection. Razor will render an instance of the editor template for each member of the collection and properly index everything.

In MVC2, how do I validate fields that aren't in my data model?

I am playing with MVC2 in VS 2010 and am really getting to like it. In a sandbox application that I've started from scratch, my database is represented in an ADO.NET entity data model and have done much of the validation for fields in my data model using Scott Guthrie's "buddy class" approach which has worked very well.
However, in a user registration form that I have designed and am experimenting with, I'd like to add a 'confirm email address' or a 'confirm password' field. Since these fields obviously wouldn't exist in my data model, how would I validate these fields client side and server side?
I would like to implement something like 'Html.ValidationMessageFor', but these fields don't exist in the data model. Any help would be greatly appreciated.
I use view models. I don't create the data model instance to persist until the view model is valid.
Below is a simple example. Notice that some of the properties are data models, but the validation properties only exist on this view model.(the base isn't pertinent here)
public class ProblemAddToDepartmentProductView : ViewModel
{
public Problem Problem { get; set; }
public IList<Product> AllProducts { get; set; }
public IList<Department> AllDepartments { get; set; }
public string ProblemId { get; set; }
public string ProblemName { get; set; }
[DisplayName("Choose the product:")]
[Required(ErrorMessage = "Select the Product.")]
public string SelectedProduct { get; set; }
public SelectList GetProducts()
{
var selectList = new SelectList(AllProducts, "Id", "Name");
return selectList;
}
[DisplayName("Choose the department using this problem for that product:")]
[Required(ErrorMessage = "Select the Department.")]
public string SelectedDepartment { get; set; }
public SelectList GetDepartments()
{
var selectList = new SelectList(AllDepartments, "Id", "Name");
return selectList;
}
internal class ProductSelect
{
public Guid Id { get; set; }
public string Name { get; set; }
}
}
It will also help to see it wired on the page:
<fieldset>
<legend>Fields</legend>
<div class="editor-label">
<%= Html.LabelFor(x => x.SelectedProduct) %>
</div>
<div class="editor-field">
<%= Html.DropDownListFor(x => x.SelectedProduct, Model.GetProducts(),"--Select One--") %>
<%= Html.ValidationMessageFor(x => x.SelectedProduct)%>
</div>
<div class="editor-label">
<%= Html.LabelFor(x => x.SelectedDepartment) %>
</div>
<div class="editor-field">
<%= Html.DropDownListFor(x => x.SelectedDepartment, Model.GetDepartments(),"--Select One--") %>
<%= Html.ValidationMessageFor(x => x.SelectedDepartment)%>
</div>
<p>
<input type="submit" value="Add Selected" />
</p>
</fieldset>
I also do this so the model will have these values if the validation fails, to pull back in the needed data for the drop downs:
p.ProblemId) %>
<%= Html.HiddenFor(p => p.ProblemName) %>
Client-Side:
Using Javascript Validation
and/or
Server-Side:
Validate in Controller (Using FormCollection) - or
Create "CustomViewModel" Class that encapsulates all validation strongly type your view - or
You could add two string properties to your Model. Doing so will allow you to populate an instance of your model in a Controller and validate appropriately in your Model/s...