Razor Page: RedirectToPage with Parameters - razor

I have a razor page which displays company and list of staff:
#page "/admin/companies/editor/{id}"
#model EditorModel
#{
}
<h1>admin . companies . editor</h1>
<h4>Id = #Model.Id</h4>
<h4>Name = #Model.OrgStructure.Organisation.Name</h4>
<h4>Staff Count = #Model.OrgStructure.Staff.Count</h4>
<table>
<tr>
<th>Id</th>
<th>Name</th>
</tr>
#if (Model.OrgStructure.Staff.Any())
{
foreach (Staff s in Model.OrgStructure.Staff)
{
<tr>
<td>#s.Id</td>
<td>#s.Username</td>
</tr>
}
}
else
{
<tr>
<td colspan="2">No Staff</td>
</tr>
}
</table>
<a class="btn btn-primary" asp-page="/admin/companies/staff/create" asp-route-orgId="#Model.OrgStructure.Organisation.Id">Create Staff</a>
#functions {
public class EditorModel : PageModel
{
public string Id { get; set; }
public OrganisationStructure OrgStructure { get; set; }
public void OnGet(string id)
{
Id = id;
using (DirectoryApp app = ServicesFactory.MakeDirectoryService())
{
OrgStructure = app.GetOrganisationStructureAsync(new Guid(id)).Result;
}
}
}
}
When I click:
<a class="btn btn-primary" asp-page="/admin/companies/staff/create" asp-route-orgId="#Model.OrgStructure.Organisation.Id">Create Staff</a>
It takes me to my staff creation page:
#page "/admin/companies/staff/create/{orgId}"
#model CreateModel
#{
}
<h1>admin . companies . staff . create</h1>
<h3>OrganisationId = #Model.OrganisationId</h3>
<form method="post">
<input name="OrgId" value="#Model.OrganisationId" />
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label>User Name</label>
<input name="UserName" class="form-control" value="#Model.UserName" />
</div>
<button type="submit" class="btn btn-primary">Submit</button>
<a class="btn btn-secondary" asp-page="/admin/companies/editor" asp-route-id="#Model.OrganisationId">Back</a>
</form>
#functions {
public class CreateModel : PageModel
{
[BindProperty]
[Required]
public string UserName { get; set; }
public Guid OrganisationId { get; set; }
public void OnGet(string orgId)
{
OrganisationId = new Guid(orgId);
}
public async Task<IActionResult> OnPostAsync(string userName, string orgId)
{
if (ModelState.IsValid)
{
using (DirectoryApp app = ServicesFactory.MakeDirectoryService())
{
await app.AddStaffToOrganisationAsync(
new Guid(orgId),
new Staff()
{
Username = userName
});
return RedirectToPage("/admin/companies/editor/" + orgId.ToString());
}
}
return Page();
}
}
}
I have 2 questions:
If I click Submit and have a Model error then I get the error message but the fields which are using #Model.OrganisationId are blanked out. I guess that when OrganisationId is set in the OnGet() method, this is lost after a post. Does this mean I'd need to repopulate my CreateModel.OrganisationId in the OnPostAsync() method?
If I am successful the line:
return RedirectToPage("/admin/companies/editor/" + orgId.ToString());
should take me back to the original screen viewing the company with the newly added staff member. However, I am getting an error:
An unhandled exception occurred while processing the request.
InvalidOperationException: No page named '/admin/companies/editor/473be1aa-7d62-4964-b744-b34e0489d7ad' matches the supplied values.
But, if I copy and paste the "/admin/companies/editor/473b..." into the address bar in the browser after "localhost:12345" then the page opens no problem.
What am I doing wrong?
UPDATE
I need to replace:
return RedirectToPage("/admin/companies/editor/" + orgId.ToString());
with
return RedirectToPage("/admin/companies/editor", new { id = orgId.ToString() });

If I click Submit and have a Model error then I get the error message
but the fields which are using #Model.OrganisationId are blanked out.
I guess that when OrganisationId is set in the OnGet() method, this is
lost after a post. Does this mean I'd need to repopulate my
CreateModel.OrganisationId in the OnPostAsync() method?
The answer to this was:
Instead of declaring arguments in the OnPostAsync method, I should use the properties in the CreateModel itself (with binding).
Use [BindProperty] on OrganisationId property in CreateModel.
On the input control, change name of OrgId field to OrganisationId.
So:
<input name="OrgId" value="#Model.OrganisationId" />
Changes to:
<input name="OrganisationId" value="#Model.OrganisationId" />
AND
public async Task<IActionResult> OnPostAsync(string userName, string orgId)
{
if (ModelState.IsValid)
{
using (DirectoryApp app = ServicesFactory.MakeDirectoryService())
{
await app.AddStaffToOrganisationAsync(
new Guid(orgId),
new Staff()
{
Username = userName
});
return RedirectToPage("/admin/companies/editor/" + orgId.ToString());
}
}
return Page();
}
Changes to:
public async Task<IActionResult> OnPostAsync()
{
if (ModelState.IsValid)
{
using (DirectoryApp app = ServicesFactory.MakeDirectoryService())
{
await app.AddStaffToOrganisationAsync(
OrganisationId,
new Staff()
{
Username = this.UserName
});
return RedirectToPage("/admin/companies/editor", new { id = OrganisationId.ToString() });
}
}
return Page();
}
AND
[BindProperty]
[Required]
public string UserName { get; set; }
public Guid OrganisationId { get; set; }
Changes to
[BindProperty]
[Required]
public string UserName { get; set; }
[BindProperty]
public Guid OrganisationId { get; set; }
Using the above:
On a Post that fails Model validation, the properties persist on the UI after
return Page();
The Redirect to page works now that I have move the parameter to the second argument as an anonymous object, instead of concatenating a string as a query parameter.

RedirectToPage("/admin/companies/editor", new { id = OrganisationId.ToString() }) is correct
If you need to pass a list as parameters for RedirectToPage you can do it like this:
return RedirectToPage("./Index", new { MigrationStatus = new List<int>() { (int)MigrationStatus.AwaitingStart, (int)MigrationStatus.InProgress } });
It will create a URL like this:
https://localhost:123/Migrations?MigrationStatus=3&MigrationStatus=4

Related

DropDownListFor not selecting

I have a DropDownListFor that doesn't seem to be setting the expected Model property. Here's the HTML:
#using (Html.BeginForm())
{
#Html.DropDownListFor(x => x.SelectedItem,
new SelectList(Model.ItemList, "Id", "Desc"), null,
new { #class = "selected-list", #size = "3" });
<div>
#Model.SelectedItem
</div>
<input type="submit" value="Do Stuff"
asp-controller="My"
asp-action="DoStuff"
asp-route-itemId="#Model.SelectedItem" />
</div>
}
The div is just there for debugging purposes, and it either shows 0 or blank.
The underlying model has a property:
public int SelectedItem { get; set; }
I've also tried a string:
public string SelectedItem { get; set; } = String.Empty;
(Hence why 0 or blank)
The actual list itself displays fine, it just doesn't update the model on select. How can I get it to update the model?
If you use asp-route-itemId, your action need contains parameter named itemId to receive the value. But for your scenario, it seems to be useless to receive such value, because it will always receive the value you set instead of the dropdown changed value.
Here is a simple demo you could follow and check if any difference with yours:
Model
public class TestVM
{
public string SelectedItem { get; set; } = String.Empty;
public List<Item> ItemList { get; set; }
}
public class Item
{
public int Id { get; set; }
public string Desc { get; set; }
}
View(Index.cshtml)
#model TestVM
#using (Html.BeginForm())
{
#Html.DropDownListFor(x => x.SelectedItem,
new SelectList(Model.ItemList, "Id", "Desc"), null,
new { #class = "selected-list", #size = "3" });
<div>
#Model.SelectedItem
</div>
<input type="submit" value="Do Stuff"
asp-controller="Home"
asp-action="DoStuff" />
}
Controller
[HttpGet]
public IActionResult Index()
{
//Note: hard-coded the model just for easy testing.....
var model = new TestVM()
{
SelectedItem = "1",
ItemList = new List<Item>()
{
new Item(){Id=1,Desc="aa"},
new Item(){Id=2,Desc="bb"},
new Item(){Id=3,Desc="cc"}
}
};
return View(model);
}
[HttpPost]
public IActionResult DoStuff(TestVM model)
{
return View("index", model);
}
Result

ASP.NET Core Identity use different user table

I'm trying to do a web App with ASP.NET Core 3.1. I have an existing SQL Database with full of users. I have only read access to this database, so I can't change nothing.
My question is, can I somehow use it to authentication to Login instead of AspNetUsers table?
(I don't need registration, nor forget password, etc., just a safety login)
My User database has these columns: Id(varchar),Name(varchar),Rank(int),Password(varchar),Email(varchar),Phone(varchar)
use it to authentication to Login instead of AspNetUsers table
Below is a demo, you can refer to it.
LoginModel
public class LoginModel
{
[Required]
[Display(Name ="Username")]
public string UserName { get; set; }
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
public bool RememberLogin { get; set; }
public string ReturnUrl { get; set; }
}
UserModel
public class UserModel
{
public int UserId { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string Role { get; set; }
}
AccountController
[Note] I set fake data, you can get it directly from the database.
public class AccountController : Controller
{
//Sample Users Data, it can be fetched with the use of any ORM
public List<UserModel> users = null;
public AccountController()
{
users = new List<UserModel>();
users.Add(new UserModel() { UserId = 1, Username = "Anoop", Password = "123", Role = "Admin" });
users.Add(new UserModel() { UserId = 2, Username = "Other", Password = "123", Role = "User" });
}
public IActionResult Login(string ReturnUrl = "/")
{
LoginModel objLoginModel = new LoginModel();
objLoginModel.ReturnUrl = ReturnUrl;
return View(objLoginModel);
}
[HttpPost]
public async Task<IActionResult> Login(LoginModel objLoginModel)
{
if (ModelState.IsValid)
{
var user = users.Where(x => x.Username == objLoginModel.UserName && x.Password == objLoginModel.Password).FirstOrDefault();
if (user == null)
{
//Add logic here to display some message to user
ViewBag.Message = "Invalid Credential";
return View(objLoginModel);
}
else
{
//A claim is a statement about a subject by an issuer and
//represent attributes of the subject that are useful in the context of authentication and authorization operations.
var claims = new List<Claim>() {
new Claim(ClaimTypes.NameIdentifier,Convert.ToString(user.UserId)),
new Claim(ClaimTypes.Name,user.Username),
new Claim(ClaimTypes.Role,user.Role),
new Claim("FavoriteDrink","Tea")
};
//Initialize a new instance of the ClaimsIdentity with the claims and authentication scheme
var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
//Initialize a new instance of the ClaimsPrincipal with ClaimsIdentity
var principal = new ClaimsPrincipal(identity);
//SignInAsync is a Extension method for Sign in a principal for the specified scheme.
//await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,
// principal, new AuthenticationProperties() { IsPersistent = objLoginModel.RememberLogin });
await HttpContext.SignInAsync(
principal, new AuthenticationProperties() { IsPersistent = objLoginModel.RememberLogin });
return LocalRedirect(objLoginModel.ReturnUrl);
}
}
return View(objLoginModel);
}
public async Task<IActionResult> LogOut() {
//SignOutAsync is Extension method for SignOut
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
//Redirect to home page
return LocalRedirect("/");
}
}
In HomeController, use [Authorize] on ConfidentialData() method
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
[Authorize]
public IActionResult ConfidentialData()
{
return View();
}
}
ConfidentialData view
#{
ViewData["Title"] = "Confidential Data";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Confidential Data</h2>
#if (User.Identity.IsAuthenticated)
{
<table class="table table-bordered">
#foreach (var claim in User.Claims) {
<tr><td>#claim.Type</td><td>#claim.Value</td></tr>
}
</table>
}
Register Authentication in startup
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(x => x.LoginPath = "/account/login");
...
app.UseAuthentication();
app.UseAuthorization();
Result:
Read Use cookie authentication without ASP.NET Core Identity to know more.
Login form
#model LoginModel
#{
ViewData["Title"] = "Login";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Login</h2>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Login">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
#if (!string.IsNullOrEmpty(ViewBag.Message))
{
<span class="text-danger">
#ViewBag.Message
</span>
}
#Html.HiddenFor(x => x.ReturnUrl)
<div class="form-group">
<label asp-for="UserName" class="control-label"></label>
<input asp-for="UserName" class="form-control" />
<span asp-validation-for="UserName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Password" class="control-label"></label>
<input asp-for="Password" class="form-control" />
<span asp-validation-for="Password" class="text-danger"></span>
</div>
<div class="form-group">
<div class="checkbox">
<label>
<input asp-for="RememberLogin" /> #Html.DisplayNameFor(model => model.RememberLogin)
</label>
</div>
</div>
<div class="form-group">
<input type="submit" value="Login" class="btn btn-default" />
</div>
</form>
</div>
</div>

Change the HTML element in a Blazor application by pressing a button?

I wanted to ask if it is possible to change the type of an HTML element in a Blazor application by pressing a button.
That I turn a normal text into a text input field for example.
I have a table where the first column has a button for each entry in the table.
My goal is that when you press the button, the fields turn into input fields in time, so you can edit the values.
This is just a quick photo edit, but this is how I imagined it.
You can use an index array to track the edit status of each company. When the button is clicked, the corresponding value in the index array will be toggled. If the value is set to true, the cell will display an input field where you can edit the company name. The updated name will be saved to the list.
Instead of using a simple string to store company information, you can create a class that contains multiple properties such as the company name and location..
Output:
Demo: https://blazorfiddle.com/s/lil3olrf
Implementation:
#page "/"
<h3>Companies</h3>
<table class="table table-bordered table-sm">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Company</th>
<th scope="col">Location</th>
</tr>
</thead>
<tbody>
#foreach (var company in Companies)
{
var index = Companies.IndexOf(company);
<tr>
<td>
<button type="button" class="btn btn-primary"
#onclick="#(() => { Edits[index] = !Edits[index]; })">
#(Edits[index] ? "Back" : "Edit")
</button>
</td>
<td>
#if (Edits[index])
{
<input class="form-control" type="text"
style="background-color:lightyellow;"
value="#company.Name"
#oninput="#(e => { Companies[index].Name = e.Value.ToString(); })"/>
}
else
{
#company.Name
}
</td>
<td>
#if (Edits[index])
{
<input class="form-control" type="text"
style="background-color:lightyellow;"
value="#company.Location"
#oninput="#(e => { Companies[index].Location = e.Value.ToString(); })"/>
}
else
{
#company.Location
}
</td>
</tr>
}
</tbody>
</table>
<ul>
#*Check realtime changes to Company names when you edit them*#
#foreach (var company in Companies)
{
<li>#company.Name: #company.Location</li>
}
</ul>
#code {
private List<Company> Companies { get; set; } = new List<Company>
{
new Company("Globex Corporation", "Germany"),
new Company("Soylent Corp", "Switzerland"),
new Company("Umbrella Corporation", "Netherlands")
};
private bool[] Edits { get; set; }
protected override void OnInitialized()
{
Edits = new bool[Companies.Count];
}
public class Company
{
public Company(string name, string location)
{
Name = name;
Location = location;
}
public string Name { get; set; }
public string Location { get; set; }
}
}
Here's a basic component and demo page to demonstrate how you can do this. There's a component and a demo page.
#page "/"
<div>
<button class="btn btn-dark" disabled="#isEdit" #onclick="GoEdit">Edit</button>
#if (this.isEdit)
{
#EditControl
}
else
{
#ViewControl
}
</div>
#code {
[Parameter] public RenderFragment ViewControl { get; set; }
[Parameter] public RenderFragment EditControl { get; set; }
protected string disabled => isEdit ? "disabled": "";
protected bool isEdit { get; set; }
private void GoEdit(MouseEventArgs e)
=> isEdit = !isEdit;
}
And a demo page:
#page "/Demo"
<h3>EditorTest</h3>
<CascadingValue Value="model">
<EditForm EditContext="editContext">
<EditFormState #ref="editFormState" EditStateChanged="EditStateChanged"></EditFormState>
<div>
<SwitchedEditorComponent>
<EditControl>
Email: <InputText #bind-Value="model.Email" placeholder="Enter your Email Address"></InputText>
</EditControl>
<ViewControl>
Email: <InputText #bind-Value="model.Email" disabled></InputText>
</ViewControl>
</SwitchedEditorComponent>
</div>
</EditForm>
</CascadingValue>
#code {
private dataModel model { get; set; } = new dataModel();
private EditFormState editFormState;
private EditContext editContext;
protected override Task OnInitializedAsync()
{
this.editContext = new EditContext(model);
return base.OnInitializedAsync();
}
private void EditStateChanged(bool editState)
{
StateHasChanged();
}
public class dataModel
{
public string Email { get; set; }
}
}

Passing list of models from one page to controller

I've tried to pass a list of models to controller but with no luck. I have a problem with generating empty view and then pass values from filled forms to controller. What I have:
Models
public class PostsModel
{
[Required]
[DataType(DataType.DateTime)]
public DateTime PublishDate { get; set; }
[Required]
public List<PostModel> Posts { get; set; }
}
public class PostModel
{
public string Language {get;set;}
public string X {get;set;}
public string Y {get;set;}
// and other properties
}
Controller
public IActionResult New()
{
ViewData["ButtonName"] = "Add";
return View(new PostsModel { PublishDate = DateTime.UtcNow });
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> New(PostsModel model)
{
if (ModelState.IsValid)
{
// some code
// never reaches this point
}
return View(model);
}
Form:
<form method="post">
<h4>XYZ</h4>
<hr />
#{
Model.Posts = new List<PostModel>(2);
for (var i = 0; i < 2; i++)
{
Model.Posts.Add(new PostModel());
}
foreach (var test in Model.Posts)
{
<h4>xyz</h4>
<div class="form-group">
<label asp-for="#test.Subject">Temat</label>
<input asp-for="#test.Subject" class="form-control" />
<span asp-validation-for="#test.Subject" class="text-danger"></span>
</div>
}
}
<button type="submit" class="btn btn-primary">#ViewData["ButtonName"]</button>
</form>
Of course model is never valid. I don't have an idea how to do such functionality.
As #StephenMuecke said, using for-loop is working approach. In this particular problem that I had it's enough to do:
Controller:
public IActionResult New()
{
ViewData["ButtonName"] = "Add";
// example:
return View(new PostsModel { PublishDate = DateTime.UtcNow, Posts = new List<PostModel>(2) { new PostModel(), new PostModel() } });
}
View:
for (var i = 0; i < Model.Posts.Count; i++)
{
<h4>xyz</h4>
<div class="form-group">
<label asp-for="#Model.Posts[i].Subject">Temat</label>
<input asp-for="#Model.Posts[i].Subject" class="form-control" />
<span asp-validation-for="#Model.Posts[i].Subject" class="text-danger"></span>
</div>
#*and so one*#
}
I also want to thank M. Kostrzewski and M. Kaletka who also helped me on some .net group

Json Result to List in ASP.net View

This is my form in contact class
<form method="POST">
<fieldset>
<div class="form-group">
<input class="form-control" placeholder="E-mail" name="subid" autofocus="">
</div>
<button href="" class="btn btn-gm btn-success">View</button>
</fieldset>
This is my code in controller and i have view called Contact
[HttpGet]
public ActionResult Contact()
{
return View();
}
[HttpPost]
public ActionResult Contact(string subid)
{
try
{
var webClient = new WebClient();
string url = string.Format(BASE_URL + "Subjectstatus/{0}", subid);
var json = webClient.DownloadString(url);
var js = new JavaScriptSerializer();
ViewBag.attendlist = js.Deserialize<List<attendlist>>(json);
return View();
}
catch
{
ViewBag.con = "gg";
return null;
}
}
This is my attendlist class
public class attendlist
{
[Display(Name = "ID")]
public string sid { get; set; }
[Display(Name = "Name")]
public string name { get; set; }
[Display(Name = "Subject")]
public string sub { get; set; }
[Display(Name = "Date")]
public string date { get; set; }
}
This is Contact View im trying to loop it using for each
#foreach (var dd in ViewBag.attendlist)
{
<h3>#dd.ID</h3>
}
This is the error when im try to load contact view
[HttpGet]
public ActionResult Contact()
{
return View( new ViewContact() );
}
[HttpPost]
public ActionResult Contact(ViewContact contact)
{
try
{
var webClient = new WebClient();
string url = string.Format(BASE_URL + "Subjectstatus/{0}", contact.subid);
var json = webClient.DownloadString(url);
var js = new JavaScriptSerializer();
attendlist list= js.Deserialize<attendlist>(json)
contact.attendlist = list;
return View(contact);
}
catch
{
ViewBag.con = "gg";
return null;
}
}
Class ViewContact
public class ViewContact
{
public attendlist list { get; set; }
public string subid { get; set; }
}
And the foreach at the view
foreach(var dd in Model.list){
//your html view code
}
The view
<form method="POST">
<fieldset>
<div class="form-group">
#Html.TextBoxFor(m => m.subid, new { #class = "form-control", #placeholder = "Email" }) >
</div>
<button href="" class="btn btn-gm btn-success">View</button>
</fieldset>
*Note textboxfor , get the model atributte and it bind later it correctly to the action submit
*Note that now you have to pass a contect not a string to Contact(ViewContact contact) action