Using AspNet Blazor and its EditForm:
I am creating a simple form that should contain both an update and a delete button. I do not seem to find any examples of how to pass parameters to the submit.
I have tried to place an #onclick in the delete button pointing towards DeleteObject, but then I get no validation (I actually do not need validation in this case, but I want to do it anyway), plus that the SaveObject was also called after the delete...
<EditForm Model="#selectedCar" OnValidSubmit="#SaveObject">
<DataAnnotationsValidator />
<ValidationSummary />
....My <InputText>'s for all values I have in my object
<button type="submit" class="btn btn-primary" value="Save">Spara</button>
<button type="submit" class="btn btn-primary" value="Delete">Delete</button>
</EditForm>
#code {
[Parameter]
public string Id { get; set; }
CarModel selectedCar;
protected override async Task OnInitializedAsync()
{
selectedCar = await _CarService.GetCar(int.Parse(Id));
}
protected async Task SaveObject()
{
selectedCar.Id = await _CarService.SaveCar(selectedCar);
}
protected async Task DeleteObject(int Id)
{
selectedCar.Id = await _CarService.DeleteCar(selectedCar);
}
}
I want to be able to call specific functions for each button, without going around validation.
Anyone have a good idea how to do this?
Ok, I ended up with the following solution. It seems to work as expected.
<EditForm Model="#selectedCar" Context="formContext">
<DataAnnotationsValidator />
<ValidationSummary />
....My <InputText>'s for all values I have in my object
<button type="submit" class="btn btn-primary" #onclick="#(() => SaveCar(formContext))">Save</button>
<button type="submit" class="btn btn-primary" #onclick="#(() => UpdateStockQuantity(formContext))">Update stock quantity</button>
<button type="submit" class="btn btn-secondary" #onclick="#(() => DeleteCar(formContext))">Delete</button>
</EditForm>
#code {
[Parameter]
public string Id { get; set; }
CarModel selectedCar;
protected override async Task OnInitializedAsync()
{
selectedCar = await _CarService.GetCar(int.Parse(Id));
}
protected async Task SaveCar(EditContext formContext)
{
bool formIsValid = formContext.Validate();
if (formIsValid == false)
return;
selectedCar.Id = await _CarService.SaveCar(selectedCar);
}
... plus same approach with UpdateStockQuantity and DeleteCar.
}
The two buttons will submit the form with the validations.
And then you can check on the Boolean and call any logic you want:
<EditForm Model="#Input" OnValidSubmit="#UpdateAsync">
<DataAnnotationsValidator />
<div class="row">
<div class="form-group col-md-12">
<label class="required"> Name</label>
<InputText class="form-control" #bind-Value="Input.Name" />
<span class="err"><ValidationMessage For="#(() => Input.Name)" /></span>
</div>
</div>
<div class="text-center">
<button type="submit" #onclick="#(()=> Input.IsNew = false)" class="btn">save 1</button>
<button type="submit" #onclick="#(()=> Input.IsNew = true)" class="btn">save 2</button>
</div>
</EditForm>
#code{
async Task UpdateAsync()
{
if (Input.IsNew)
{
//do somthing
}
else
{
//do another somthing
}
}
}
If you use type="button" then only the #onclick method is called and not the OnValidSubmit method. But this way there's no validation.
Related
UI Section
<div class="input-group mb-3">
<div class="input-group-prepend">
<label class="input-group-text" for="inputGroupSelect">AppId</label>
</div>
<select class="custom-select" id="inputGroupSelect" #bind="#appId">
#if (appIds != null)
{
foreach (var appId in appIds)
{
<option value="#appId">#appId</option>
}
}
</select>
</div>
<div class="input-group mb-3">
<div class="input-group-prepend">
<label class="input-group-text" for="appKeyFormControlInput">AppKey</label>
</div>
<input type="text" class="form-control" id="appKeyFormControlInput" #bind="#appKey" #bind:event="oninput">
<div class="input-group-append">
<button class="btn btn-outline-secondary" type="button" #onclick="SetAppKey">Get AppKey</button>
</div>
</div>
Code section
#code
{
private IEnumerable<string> appIds;
private string appId { get; set; }
private string appKey { get; set; }
protected override async Task OnInitializedAsync()
{
using HttpClient httpClient = new HttpClient();
var result = await httpClient.GetAsync("API_ADDR");
appIds = JObject.Parse(await result.Content.ReadAsStringAsync())["content"].ToObject<IEnumerable<string>>();
appId = appIds?.FirstOrDefault();
}
private async void SetAppKey()
{
using HttpClient httpClient = new HttpClient();
var result = await (await httpClient.GetAsync("API_ADDR")).Content.ReadAsStringAsync();
if (!string.IsNullOrWhiteSpace(result))
{
var app = JObject.Parse(result)["content"].ToObject<Application>();
appKey = app.AppKey;
}
}
}
Expectation
When I click the Get AppKey button,
the value of input#appKeyFromControlInput shows
the appkey of current AppId(which in input#inputGroupSelect).
Actually:
When I click the Get AppKey button,
input#appKeyFromControlInput have no reaction;
When I click the Get AppKey again,
input#appKeyFromControlInput shows the correct appkey.
Environment:
.NET 5 preview 6
Blazor WebAssembly
windows 10 2004
The following code"
<label class="input-group-text" for="appKeyFormControlInput">AppKey</label>
Should be
<label class="input-group-text" for="appKeyFormControlInput">#AppKey</label>
AppKey is interpreted by the compiler as a string, whereas #AppKey is considered an expression that should be evaluated, the result of which is to be displayed between the opening and the closing tags.
You should also changed this: private async void SetAppKey()
to this: private async Task SetAppKey()
When you use async void your method does not return a completed Task object, and thus no one around knows when your component should be re-rendered. Thus, your component is not rendered on the first click, only on the second click, reflecting the values from the first click.
Always use async with Task, not with void.
Note: UI events such as the click event always trigger re-rendering of the component.
I have an asp.net core MVC partial view called ExportPagePartial that allows user to export a page from the system and downloads it. In the HttpGet controller action I show the partial view (as a Modal pop-up) to get user input.
Modal Popup
<a class="dropdown-item" asp-action="ExportPagePartial" asp-route-userId="#Model.UserId" asp-route-businessAccountId="#Model.BusinessAccountId" asp-route-projectId="#Model.ProjectId" asp-route-pageId="#Model.PageId" data-toggle="modal" data-target="#ModalPlaceholder" title="Export page."><i class="fas fa-cloud-download-alt"></i> Export</a>
Controller Get Action
[HttpGet]
public IActionResult ExportPagePartial(string userId, string businessAccountId, string projectId, string pageId)
{
ExportPageViewModel model = new ExportPageViewModel()
{
// Set properties
};
return PartialView(nameof(ExportPagePartial), model);
}
Once the user hits Export button from the Modal pop-up partial view (which is a form submit action) the following HTTPPost action is correctly called.
In this action I have to get the file from the Web Api and then download it via the browser, however after download is complete i want to close the partial view. Once the download is complete the partial view is still visible.
The return action never works and partial modal pop-up view does not close
return RedirectToAction(nameof(BlahRedirectAction));
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> ExportPagePartial(ExportPageViewModel model)
{
// Call Web API to get the file
string downloadUrl = "blah_blah_url";
using (HttpResponseMessage httpResponse = await WebApiClient.HttpClient.PostAsJsonAsync(downloadUrl, unprotectedExportInput))
{
if (!httpResponse.IsSuccessStatusCode)
{
throw new InvalidOperationException(await httpResponse.Content.ReadAsStringAsync());
}
// Download the file now.
ActionContext actionContext = new ActionContext(HttpContext, ControllerContext.RouteData, ControllerContext.ActionDescriptor, ModelState);
FileStreamResult fileContent = File(await httpResponse.Content.ReadAsStreamAsync(), httpResponse.Content.Headers.ContentType.MediaType, httpResponse.Content.Headers.ContentDisposition.FileName);
await fileContent.ExecuteResultAsync(actionContext);
}
// Redirect to main pain
// The view never redirects and partial view is still visible
return RedirectToAction(nameof(BlahRedirectAction));
}
fileContent.ExecuteResultAsync(actionContext);
This is because when you download the file, ExportPagePartial has determined the return flow, and will not perform the RedirectToAction.
I suggest that you change the post method that triggers ExportPagePartial to ajax to achieve, so that you can successfully execute ExportPagePartial and after the method, redirect the page to what you want in js.
Here is a complete code of my demo based on your code:
public class ExportTestController : Controller
{
public IActionResult Index()
{
return View();
}
[HttpGet]
public IActionResult ExportPagePartial(string userId, string businessAccountId, string projectId, string pageId)
{
ExportPageViewModel model = new ExportPageViewModel()
{
Id = 1,
Gender = "male",
Name = "aaa",
Number = "1231244"
};
return PartialView(nameof(ExportPagePartial), model);
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> ExportPagePartial(ExportPageViewModel model)
{
// Call Web API to get the file
string downloadUrl = "blah_blah_url";
using (HttpResponseMessage httpResponse = await WebApiClient.HttpClient.PostAsJsonAsync(downloadUrl, unprotectedExportInput))
{
if (!httpResponse.IsSuccessStatusCode)
{
throw new InvalidOperationException(await httpResponse.Content.ReadAsStringAsync());
}
// Download the file now.
ActionContext actionContext = new ActionContext(HttpContext, ControllerContext.RouteData, ControllerContext.ActionDescriptor, ModelState);
FileStreamResult fileContent = File(await httpResponse.Content.ReadAsStreamAsync(), httpResponse.Content.Headers.ContentType.MediaType, httpResponse.Content.Headers.ContentDisposition.FileName);
await fileContent.ExecuteResultAsync(actionContext);
}
// Redirect to main pain
// The view never redirects and partial view is still visible
return RedirectToAction(nameof(BlahRedirectAction));
}
Index.cshtml:
#{
ViewData["Title"] = "Index";
Layout = null;
}
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
<script>
$(function () {
$("a").click(function () {
var route = $(this).attr("href");
$('#partial').load(route);
})
$("form").submit(function () {
$.ajax({
url: $("form").attr('action'),
type: 'Post',
data: $("form").serializeArray(),
success: function () {
//$("#ModalPlaceholder").hide();
window.location.href = "/ExportTest/BlahRedirectAction";
}
});
})
})
</script>
<a class="dropdown-item" asp-action="ExportPagePartial"
asp-route-userId="1" asp-route-businessAccountId="1"
asp-route-projectId="1" asp-route-pageId="1"
data-toggle="modal" data-target="#ModalPlaceholder" title="Export page."><i class="fas fa-cloud-download-alt"></i> Export</a>
<div class="modal fade" id="ModalPlaceholder" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
<form asp-action="ExportPagePartial" method="post">
<div id="partial">
</div>
</form>
</div>
ExportPagePartial.cshtml:
#model ExportPageViewModel
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Modal title</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<div class="form-group">
<label asp-for="Id" class="control-label">#Model.Id</label>
<input asp-for="Id" class="form-control" hidden />
</div>
<div class="form-group">
<label asp-for="Name" class="control-label"></label>
<input asp-for="Name" class="form-control" />
<span asp-validation-for="Name" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Gender" class="control-label"></label>
<input asp-for="Gender" class="form-control" />
<span asp-validation-for="Gender" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Number" class="control-label"></label>
<input asp-for="Number" class="form-control" />
<span asp-validation-for="Number" class="text-danger"></span>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button type="submit" class="btn btn-primary" >Save changes</button>
</div>
</div>
</div>
Here is the test result:
Please can someone advise as I have been going round in circles with this. I have a view model which contains a list of my entity:
public List<Employee> EmployeeList{ get; set; }
This is being populated like so:
var list = _context.Employees.Include(x => x.Office).Where(x => x.EmployeeID== id).ToList();
var model = new EmployeeViewModel()
{
EmployeeList= list
};
The view contains:
#model MyProject.Models.EmployeeViewModel
#{
ViewData["Title"] = "Form";
Layout = "~/Views/Shared/_Layout.cshtml";
}
#foreach (var item in Model.EmployeeList)
{
#Html.HiddenFor(modelItem => item.EmployeeID)
#Html.EditorFor(modelItem => item.Name)
}
#using (Html.BeginForm("MyForm", "Employees", FormMethod.Post))
{
<button type="submit" class="btn btn-info" name="SubmitAction" value="Submit">Update</button>
}
I can see all the results but when I change a value on any rows editor from the view no changes are picked up in my controller:
switch (submitAction)
{
case "Submit":
{
foreach (var Response in model.EmployeeList)
{
Console.WriteLine(Response);
}
return View(model);
}
}
Please can anyone point me in the right direction?
Many thanks
Move foreach code to #using (Html.BeginForm) inside
EDITED
Index.cshtml
<form method="post" asp-controller="Employee" asp-action="Index">
#foreach (var item in Model.EmployeeList)
{
<input type="hidden" asp-for="item.EmployeeID" />
<input type="text" asp-for="item.Name" />
}
<button type="submit" class="btn btn-info" name="SubmitAction" value="Submit">Update</button>
</form>
EmployeeController.cs
[HttpPost]
public IActionResult Index(Employee emp)
{
return View();
}
EDITED 05 Feb 2020
Change foreach to for loop.
<form method="post" asp-action="Index">
#{
for (int i = 0; i <= Model.Count() - 1; i++)
{
<input type="hidden" asp-for="#Model[i].Id" />
<input type="text" asp-for="#Model[i].Name" />
<input type="text" asp-for="#Model[i].EmailAddress" />
<br /><br />
}
}
<button>Save</button>
</form>
Input email address.
In your case, use #Model.EmployeeList[i].EmployeeID in view. and use Employee parameter in your controller.
I want my user to be able to change his username so i made this:
<form asp-action="UpdateUserProfile" asp-controller="Account" method="post">
<div class="account-details-item">
<h2 class="center-text text-left account-details-item-title">UserName:</h2>
<input name="username" id="username-text" readonly="readonly" class="center-text account-details-item-value" asp-for="User.UserName" value=#Model.User.UserName>
<a id="btn-username" class="account-details-item-btn" >Edit</a>
</div>
<div class="account-details-item">
<h2 class="center-text text-left account-details-item-title">Email:</h2>
<input name="email" readonly="readonly" id="email-text" class="center-text account-details-item-value email" asp-for="User.Email" value=#Model.User.Email />
<a id="btn-email" class="account-details-item-btn" >Edit</a>
</div>
<div class="account-details-item">
<h2 class="center-text text-left account-details-item-title">Phone number:</h2>
<input name="phonenumber" readonly="readonly" id="phone-text" class="center-text account-details-item-value" asp-for="User.PhoneNumber" value=#Model.User.PhoneNumber>
<a id="btn-phone" class="account-details-item-btn" >Edit</a>
</div>
<div class="btns-container">
<div class="btn-item"><a asp-action="Index" asp-controller="Cart" asp-route-id=#Model.User.CartId>Go To Cart</a></div>
<div id="save-btn" class="btn-item"><button type="submit">Save Changes</button></div>
</div>
</form>
And in AccountController:
[HttpPost]
public IActionResult UpdateUserProfile()
{
var username = Request.Form["username"];
var phonenumber = Request.Form["phonenumber"];
var email = Request.Form["email"];
var user = _userService.GetUser(User);
if(`enter code here`IsUsernameDiffrent(username))
{
_userService.UpdateUsername(User, username);
_userManager.UpdateAsync(user.Result);
}
else if(IsEmailDiffrent(email))
{
_userService.UpdateEmail(User, email);
_userManager.UpdateAsync(user.Result);
}
else if (IsPhoneNumberDiffrent(phonenumber))
{
_userService.UpdatePhoneNumber(User, phonenumber);
_userManager.UpdateAsync(user.Result);
}
return RedirectToAction("Index");
}
And in Service Class:
public async void UpdateUsername(ClaimsPrincipal user, string newUsername)
{
var currentUser = await GetUser(user);
currentUser.UserName = newUsername;
_dbContext.SaveChanges();
}
The issue is that if user change his username he still have to login with the old one,
changes are made in database but whenever i try to login with new username it says "Invalid login attempt"
I have an Update User Action in my project and it does work in mine, and its little different from yours, but you can try to change it like that:
[HttpPost]
public async Task<IActionResult> UpdateUserProfile()
{
var user = _userService.GetUser(User);
if (user == null)
{
return StatusCode(404);
}
var username = Request.Form["username"];
if(IsUsernameDifferent(username))
{
user.UserName = username;
var result = await _userManager.UpdateAsync(user);
if (result.Succeeded)
{
return RedirectToAction("Action", "Controller");
}
return View();
}
return View();
}
I feel that more code must be seen to analyze the issue. But, make sure you are using transaction scope in your services when you are making changes to the data in the database like update or delete.
Using this technique will ensure that the code is consistent. Changes to the database will not happen unless everything inside the scope is successful. For example, if you are updating and then deleting, what if while deleting an error occurs. Then your data will be updated in the database but not deleted. This is to make sure that both happens successfully or nothing happens at all.
Refer this link.
https://learn.microsoft.com/en-us/ef/core/saving/transactions
I have next method where we use fom post to send date to database:
[HttpPost]
public ActionResult AddDateToDatabase(StudyDateViewModel model)
{
var studyDate = model.StudyDate;
if (ModelState.IsValid)
{
using (var personRepository = new StudentsRepository<PersonalInformation>())
{
string userId = User.Identity.GetUserId();
var changeStudyDate = personRepository.Get()
.Where(c => c.UserId == userId)
.FirstOrDefault();
changeStudyDate.StudyDate = studyDate;
personRepository.Update(changeStudyDate);
TempData["message"] = $"Well done. Your study date is {studyDate.Value.ToShortDateString()}. Don't forget to check email ";
}
}
else
{
return View(model);
}
return RedirectToAction("PersonalCabinet");
}
This is my partial view where we call our method:
#using (Html.BeginForm("AddDateToDatabase", "Home", FormMethod.Post, new { enctype = "multipart/form-data" , id = "dateForm" }))
{
<div class="input-group input-medium date date-picker" data-date-format="dd-mm-yyyy" data-date-start-date="+0d">
#Html.TextBoxFor(m => m.StudyDate, new { #class = "form-control", Value = Model.StudyDate })
<span class="input-group-btn">
<button class="btn default" type="button">
<i class="fa fa-calendar"></i>
</button>
</span>
</div>
<br />
<input type="submit" value="Choose" class="btn btn-info" />
}
And we call our partil view next:
#Html.Partial("AddDateToDatabase", new StudyDateViewModel())
So my question is how to get value from database in same input after form submit?? Because when I click submit value pass to server good but it dissapears from this input after reloading page? So What I should do in this case?