Passing list of models from one page to controller - razor

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

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>

Razor Page: RedirectToPage with Parameters

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

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

button group into razor syntax

I would like to know if I can convert my div button group (btn-group) to razor syntax in my asp.net mvc app? I want the razor syntax so I can preselect and pre activate a button label when entering the page. If razor isn't needed, then can someone please show me how to make a button active and selected on the page being entered from my view model data? It seems that without razor I would have to pass my viewmodel data to javascript to perform, but that doesn't seem right. Here is my html
<div class="form-group">
#Html.LabelFor(model => model.Listing.SpaceType, htmlAttributes: new { #class = "control-label" })
<div class="btn-group form-control" data-toggle="buttons" id="SpaceType">
<label id="SpaceTypeLabel0" class="btn btn-primary">
<input type="radio" name="typeoptions" autocomplete="off" id="0"> House
</label>
<label id="SpaceTypeLabel1" class="btn btn-primary">
<input type="radio" name="typeoptions" autocomplete="off" id="1"> Apartment
</label>
<label id="SpaceTypeLabel2" class="btn btn-primary">
<input type="radio" name="typeoptions" autocomplete="off" id="2"> Studio
</label>
<label id="SpaceTypeLabel3" class="btn btn-primary">
<input type="radio" name="typeoptions" autocomplete="off" id="3"> Other
</label>
</div>
</div>
Here is my model
public class Space
{
public int SpaceId { get; set; }
public virtual SpaceOverview Overview { get; set; }
public virtual SpaceDetails Details { get; set; }
public virtual SpaceListing Listing { get; set; }
public virtual SpaceAddress Address { get; set; }
[Required]
public DateTime DateCreated { get; set; }
}
and spacelisting is
public class SpaceListing
{
[Key, ForeignKey("SpaceOf")]
public int SpaceListingId { get; set; }
public SpaceType SpaceType { get; set; }
public SpaceLocation SpaceLocation { get; set; }
public SpaceAccommodation Accommodation { get; set; }
public Space SpaceOf { get; set; } // one-to-one
}
and spacetype is
public enum SpaceType
{
Home,
Apartment,
Studio,
Park,
Beach,
Field,
Backyoard,
FrontYard,
Other
}
Currently you creating a group of radio buttons with name="typeoptions" which have no relationship to the model, and your not even giving the radio buttons a value attribute so nothing would post back anyway.
The syntax should be
#Html.RadioButtonFor(m => m.Listing.SpaceType, "House", new { id = "House" })
#Html.Label("House")
#Html.RadioButtonFor(m => m.Listing.SpaceType, "Apartment", new { id = "Apartment" })
#Html.Label("Apartment")
...
To make this easier, you can create an extension method
public static class RadioButtonHelper
{
public static MvcHtmlString EnumRadioButtonListFor<TModel, TValue>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TValue>> expression)
{
ModelMetadata metaData = ModelMetadata.FromLambdaExpression(expression, helper.ViewData);
string name = ExpressionHelper.GetExpressionText(expression);
Type type = Nullable.GetUnderlyingType(metaData.ModelType);
if (type == null || !type.IsEnum)
{
throw new ArgumentException(string.Format("The property {0} is not an enum", name));
}
StringBuilder html = new StringBuilder();
foreach (Enum item in Enum.GetValues(type))
{
string id = string.Format("{0}_{1}", metaData.PropertyName, item);
StringBuilder innerHtml = new StringBuilder();
innerHtml.Append(helper.RadioButtonFor(expression, item, new { id = id }));
innerHtml.Append(helper.Label(id, item.ToDescription()));
TagBuilder div = new TagBuilder("div");
div.AddCssClass("radiobutton");
div.InnerHtml = innerHtml.ToString();
html.Append(div.ToString());
}
TagBuilder container = new TagBuilder("div");
container.AddCssClass("radiobutton-container");
container.InnerHtml = html.ToString();
return MvcHtmlString.Create(container.ToString());
}
}
Note, this uses the following extension method
public static string ToDescription(this Enum value)
{
FieldInfo field = value.GetType().GetField(value.ToString());
DescriptionAttribute[] attributes = (DescriptionAttribute[])field.GetCustomAttributes(typeof(DescriptionAttribute), false);
if (attributes.Length > 0)
{
return attributes[0].Description;
}
return value.ToString();
}
which allows you to decorate the enum values with a 'friendly' name
public enum SpaceType
{
Home,
[Description("2 bed apartment")]
Apartment,
....
}
and in the view
#Html.EnumRadioButtonListFor(m => m.Listing.SpaceType)