Hi im having problem with my view
as you can see there is an input and a select option
<div class="form-group">
<label asp-for="DomWasLoc" class="control-label"></label>
<input asp-for="DomWasLoc" class="form-control" id="firstname" name="firstname" />
<select class="form-control"
id="name" name="name"
asp-items="#(new SelectList(ViewBag.LocationList, "LocName","LocName"))">
<option value="">- Select -</option>
</select>
<span asp-validation-for="DomWasLoc" class="text-danger"></span>
</div>
every time you select an item on on the dropdownList the selected item value is inputted in the input box.
but every time i click submit i always shows this error
AspNetCore._Views_DomesticWastes_Create_cshtml+d__25.MoveNext() in Create.cshtml
+
asp-items="#(new SelectList(ViewBag.LocationList, "LocName","LocName"))">
what should i do so that when i click submit it ignore the select and just add the data from input box?
thanks in advance guys! :)
There is no need to combine input and select for the same field DomWasLoc. You could bind the DomWasLoc directly to select.
A demo code like below:
<div class="form-group">
<label asp-for="DomWasLoc" class="control-label"></label>
#*<input asp-for="DomWasLoc" class="form-control" />*#
<select class="form-control"
asp-for="DomWasLoc"
asp-items="#(new SelectList(ViewBag.LocationList, "LocName","LocName"))">
<option value="">- Select -</option>
</select>
<span asp-validation-for="DomWasLoc" class="text-danger"></span>
</div>
Update:
Complte Code:
1. Model
public class Location
{
public int Id { get; set; }
public string LocName { get; set; }
}
public class DomesticWaste
{
public int Id { get; set; }
public string DomWasLoc { get; set; }
}
Controller
// GET: DomesticWastes/Create
public IActionResult Create()
{
List<Location> locationlist = new List<Location>() {
new Location{ Id = 1, LocName = "L1" },
new Location{ Id = 2,LocName = "L2" },
new Location{ Id = 3,LocName = "L3" }
};
ViewBag.LocationList = locationlist;
return View();
}
// POST: DomesticWastes/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("Id,DomWasLoc")] DomesticWaste domesticWaste)
{
if (ModelState.IsValid)
{
_context.Add(domesticWaste);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(domesticWaste);
}
Related
So i have 2 Viewbags, they each have a list of values from a database table, the first Viewbag have all possible values of a database column, while the other have only the values corresponding to the selected value in the first Viewbag.
I have the logic for the search.
but i need to have the form update after selecting one value, since they both need to be in the same form, it is not searching for the second value.
OBS:i am only using the controllers, and cshtml views, not razor pages.
Here is a simple demo how to create cascading selectlist in asp.net core mvc:
Model:
public class Category
{
public int Id { get; set; }
public string Name { get; set; }
}
public class SubCategory
{
public int Id { get; set; }
public int CategoryId { get; set; }
public string SubCategoryName { get; set; }
}
View(Index.cshtml):
<div>
<div style="float:left;width:40%">
<form id="form">
<div class="form-group row">
<label>Category Name</label>
<div class="col-12">
<select id="CategoryId" class="custom-select mr-sm-2"
asp-items="#(new SelectList( #ViewBag.Category,"Id","Name"))">
<option value="">Please Select</option>
</select>
</div>
</div>
<div class="form-group row">
<label>SubCategory Name</label>
<div class="col-12">
<select id="SubCategoryId" class="custom-select mr-sm-2"
asp-items="#(new SelectList(string.Empty,"Id","SubCategoryName"))">
<option value="">Please Select</option>
</select>
</div>
</div>
<div>
<input type="button" value="Search" />
</div>
</form>
</div>
</div>
#section Scripts
{
<script>
$(function () {
$('#CategoryId').change(function () {
var data = $("#CategoryId").val();
$.ajax({
url: '/Home/GetSubCategory?CategoryId=' + data,
type: 'Get',
success: function (data) {
var items = "";
$.each(data, function (i, item) {
items += "<option value='" + item.value + "'>" + item.text + "</option>";
});
$('#SubCategoryId').html(items);
}
})
});
})
</script>
}
Controller:
public class HomeController : Controller
{
private readonly MvcProj3Context _context;
public HomeController(MvcProj3Context context)
{
_context = context;
}
public IActionResult Index()
{
ViewBag.Category = _context.Category.ToList();
return View();
}
public JsonResult GetSubCategory(int CategoryId)
{
ViewBag.SubCategory = (from m in _context.SubCategory
where m.CategoryId == CategoryId
select m).ToList();
return Json(new SelectList(ViewBag.SubCategory, "Id", "SubCategoryName"));
}
}
Result:
The problem is, that the .cshtml file is completely rendered before its send to your browser. Therefore you cannot change it with C# code after its sent to the browser.
You could use blazor if you want to do it with c#, or you could do it with javascript.
Using ASP.NET Core MVC 3.1.
Added security using the identity scaffolding.
Created default groups following the instructions from eShopOnWeb (https://github.com/dotnet-architecture/eShopOnWeb/blob/master/src/Infrastructure/Identity/AppIdentityDbContextSeed.cs)
Able to seed the database and create
3 groups: Admins, Managers, Users
3 users: Admin, Manager, User
assign the user to respective group.
I need instructions on how to accomplish assigning the roles to users from the User Management form (MVC pattern) at the time when I create a user or need to edit the roles for the users. Also I need MVC pattern not Razor pages like here https://github.com/bhrugen/Uplift/blob/master/Uplift/Areas/Identity/Pages/Account/Register.cshtml.cs
I presume I need to create the ViewModel which would include entries from dbo.AspNetUsers, dbo.AspNetRoles, dbo.AspNetUserRoles for that but not sure what exactly I need and how to perform.
Here is the desired functionality of the form
Here is a simple working demo , you could refer to
Models
public class ApplicationUser: IdentityUser
{
public DateTime BirthDate { get; set; }
public string City { get; set; }
public string Country { get; set; }
}
public class RegisterVM
{
[Required]
[EmailAddress]
[Display(Name = "Email")]
public string Email { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[Display(Name = "Birth date")]
public DateTime BirthDate { get; set; }
public string City { get; set; }
public string Country { get; set; }
[Display(Name ="Management role")]
public string role { get; set; }
public List<IdentityRole> RoleList { get; set; }
}
DbContext
public class ApplicationDbContext : IdentityDbContext<ApplicationUser, IdentityRole, string>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
public DbSet<ApplicationUser> ApplicationUser { get; set; }
}
Register view
#model MVC3_1Identity.Models.ViewModels.RegisterVM
<div class="row">
<div class="col-md-4">
<form method="post">
<h4>Create a new account.</h4>
<hr />
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Email"></label>
<input asp-for="Email" class="form-control" />
<span asp-validation-for="Email" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Password"></label>
<input asp-for="Password" class="form-control" />
<span asp-validation-for="Password" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="BirthDate"></label>
<input asp-for="BirthDate" class="form-control" />
<span asp-validation-for="BirthDate" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="City"></label>
<input asp-for="City" class="form-control" />
<span asp-validation-for="City" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Country"></label>
<input asp-for="Country" class="form-control" />
<span asp-validation-for="Country" class="text-danger"></span>
</div>
<div class="custom-checkbox">
<label asp-for="role"></label>
#for (var i = 0; i < Model.RoleList.Count; i++)
{
<input asp-for="role" type="radio" value="#Model.RoleList[i].Name" />
<label asp-for="#Model.RoleList[i].Name">#Model.RoleList[i].Name</label>
}
<span asp-validation-for="role" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-primary">Register</button>
</form>
</div>
</div>
#section Scripts {
<partial name="_ValidationScriptsPartial" />
}
Controller
public IActionResult Register()
{
var model = new RegisterVM();
model.RoleList = _roleManager.Roles.ToList();
return View(model);
}
[HttpPost]
public async Task<IActionResult> Register(RegisterVM model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser
{
UserName = model.Email,
Email = model.Email,
BirthDate=model.BirthDate,
City=model.City,
Country =model.Country,
};
var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
_userManager.AddToRoleAsync(user,model.role).Wait();
_logger.LogInformation("User created a new account with password and role.");
// other logic you want
}
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
}
return View();
}
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser ,IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddControllersWithViews();
services.AddRazorPages();
}
Result:
I am creating a simple Location and Country relationship and i have been able to link the to tables and i am able to pull the related data, the issue is that when i try post the form the database does not update with the ID of the country.
When monitoring the POST action via Firefox Developer tools i can see the ID of the country is being posted.
LocationsController
public class LocationsController : Controller
{
private readonly DataContext _context;
public LocationsController(DataContext context)
{
_context = context;
}
// DropDown: Populate the Dropdown lists
private void PopulateCountryDropDownList(object selectedCountry = null)
{
var countriesQuery = from c in _context.Countries
orderby c.CountryID
select c;
ViewBag.CountryID = new SelectList(countriesQuery.AsNoTracking(), "CountryID", "Title");
}
// GET: Locations/Create
public IActionResult Create()
{
PopulateCountryDropDownList();
return View();
}
// POST: Locations/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(Location location)
{
if (ModelState.IsValid)
{
_context.Add(location);
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(location);
}
}
Location
using System.ComponentModel.DataAnnotations;
namespace RaceCaribbean.Models
{
public class Location
{
public int LocationID { get; set; }
public string Title { get; set; }
public Country Country { get; set; }
}
}
Country
using System.ComponentModel.DataAnnotations;
namespace RaceCaribbean.Models
{
public class Country
{
public int CountryID { get; set; }
[Required]
public string Title { get; set; }
[Required]
public string Code { get; set; }
}
}
Create.cshtml
#model RaceCaribbean.Models.Location
#{
ViewData["Title"] = "Create";
}
<h2>Create</h2>
<form asp-action="Create">
<div class="form-horizontal">
<h4>Location</h4>
<hr />
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Title" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Title" class="form-control" />
<span asp-validation-for="Title" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<label asp-for="Country" class="col-md-2 control-label"></label>
<div class="col-md-10">
<select asp-for="Country" class="form-control" asp-items="ViewBag.CountryID">
<option selected="selected" value="">-- Select Country --</option>
</select>
<span asp-validation-for="Country" class="text-danger" />
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
</form>
<div>
<a asp-action="Index">Back to List</a>
</div>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
We have written a custom TagHelper to handle Autocomplete in a generic fashion. It has an asp-for attribute, which is defined as as ModelExpression variable.
The autocomplete TagHelper writes out a hidden field (the Id field), as well as an Input field for the autocomplete js code to work on. It ultimately saves the selected items Id value to the hidden field. This autocomplete works very well for multiple fields on a form.
But when the autocomplete TagHelper is incorporated in a list of items using an EditorTemplate to display all items the same (using EditorFor on a List in the model), then we need to set the Z index based name on the hidden field so it comes back to the controller as a List of the items. e.g. z0__*Field*, Z1__*Field*, ...
How do we get the Z index based name prefix that needs to be tacked on to the front of all the field names?
Do we have to make it up ourselves?
or do we extract from the ModelExpression somehow?
The Standard Input TagHelper is getting handled correctly?
Is the EditorFor/EditorTemplate the correct way to handle lists of editable objects in ASP.NET Core 1?
Autocomplete TagHelper
public class AutocompleteTagHelper : TagHelper
{
public ModelExpression AspFor { get; set; }
public ModelExpression AspValue { get; set; }
//public string AspFor { get; set; }
public string Route { get; set; }
public string RouteParameters { get; set; }
public string TargetWrapper { get; set; }
public string DisplayFormat { get; set; }
public string ValueFormat { get; set; }
public string ManageListCallback { get; set; }
public string ListWrapper { get; set; }
public string Placeholder { get; set; }
private SkillDbContext _context;
private readonly UserManager<ApplicationUser> _userManager;
private IMemoryCache cache;
public AutocompleteTagHelper(SkillDbContext Context, UserManager<ApplicationUser> userManager, IMemoryCache cache)
{
_context = Context;
_userManager = userManager;
this.cache = cache;
}
public override void Process(TagHelperContext context, TagHelperOutput output)
{
var hiddenVal = "";
var displayVal = "";
//asp-for="LandingPointId"
//route="/Lookups/GetLandingPoint"
//route-parameter="SomeOtherId"
//target-wrapper="form" key="Id"
//label="{Name} ({Code})"
//output="{code}"
//AspFor.
//get parent model from AspFor
object thisModel = null;
//get value properties
if (AspValue != null)
{
hiddenVal = ValueFormat;
displayVal = DisplayFormat;
thisModel = AspValue.Model;
}
else if (AspFor.Model != null && !AspFor.Model.Equals((object)0))
{
Object Id = AspFor.Model;
string routeMethod = Route.Split('/').Last<string>();
}
if(thisModel != null)
{
PropertyInfo[] propertyInfo = thisModel.GetType().GetProperties();
foreach (var info in propertyInfo)
{
var val = info.GetValue(thisModel);
if (val != null)
{
hiddenVal = hiddenVal.Replace(("{" + info.Name + "}"), val.ToString());
displayVal = displayVal.Replace(("{" + info.Name + "}"), val.ToString());
}
}
}
var isAcList = ManageListCallback != null && ListWrapper != null;
string aspForName = AspFor.Name.Replace(".", "_");
output.TagName = "input"; // replaces <email> with <a> tag
inputId = inputName = aspForName;
output.Attributes["id"] = aspForName;
output.Attributes["name"] = aspForName;
output.Attributes["type"] = "text";
output.Attributes["route"] = Route;
output.Attributes["route-parameters"] = RouteParameters;
output.Attributes["target-wrapper"] = TargetWrapper;
output.Attributes["placeholder"] = Placeholder;
output.Attributes["value-format"] = ValueFormat;
output.Attributes["display-format"] = DisplayFormat;
output.Attributes["value"] = displayVal;
output.Attributes["class"] = "autocomplete form-control" + (isAcList?" hasList":"");
TagBuilder HiddenValue = new TagBuilder("input");
HiddenValue.Attributes["name"] = inputName;
HiddenValue.Attributes["id"] = inputId + "_hidden";
HiddenValue.Attributes["type"] = "hidden";
HiddenValue.Attributes["value"] = hiddenVal;
output.PreElement.SetContent(HiddenValue);
if (isAcList)
{
TagBuilder AddBtn = new TagBuilder("a");
AddBtn.Attributes["id"] = AspFor.Name.Replace(".", "_") + "_submit";
AddBtn.Attributes["class"] = "moana-autocomplete-list-manager disabled btn btn-primary";
AddBtn.Attributes["listwrapper"] = ListWrapper;
AddBtn.Attributes["href"] = ManageListCallback;
AddBtn.InnerHtml.AppendHtml("Add");
output.PostElement.SetContent(AddBtn);
}
}
This is the model
public class AddressEditorModel
{
public int Id { get; set; }
public string AddressLinkTo { get; set; }
public int AddressLink { get; set; }
public string AddressLine { get; set; }
public int ContactTypeId { get; set; }
public string Suburb { get; set; }
public string City { get; set; }
public string Postcode { get; set; }
public int? CountryId { get; set; }
public string ContactTypeName { get; set; }
public string CountryCode { get; set; }
public string CountryName { get; set; }
}
This is the cshtml
#model List<Skill.ViewModels.AddressEditorModel>
<div class="address-info-wrapper">
#Html.EditorFor(m => m)
<div>
This is the controller method call
public async Task<IActionResult> UpdateAddressInfo(List<AddressEditorModel> addresses)
and finally this is the EditorTemplate
#model Skill.ViewModels.AddressEditorModel
<input type="hidden" asp-for="Id" />
<input type="hidden" asp-for="ContactTypeId" />
<input type="hidden" asp-for="AddressLink" />
<input type="hidden" asp-for="AddressLinkTo" />
<input type="hidden" asp-for="CountryId" />
<label class="col-md-12 control-label" style="padding-bottom:20px;">#Model.ContactTypeName</label>
<div class="form-group">
<label asp-for="AddressLine" class="col-md-2 control-label">Address</label>
<div class="col-md-10">
<input asp-for="AddressLine" class="form-control" style="resize:both" />
<span asp-validation-for="AddressLine" class="text-danger" />
</div>
</div>
<div class="form-group">
<label asp-for="Suburb" class="col-md-2 control-label">Suburb</label>
<div class="col-md-10">
<input asp-for="Suburb" class="form-control" />
<span asp-validation-for="Suburb" class="text-danger" />
</div>
</div>
<div class="form-group">
<label asp-for="City" class="col-md-2 control-label">City</label>
<div class="col-md-10">
<input asp-for="City" class="form-control" />
<span asp-validation-for="City" class="text-danger" />
</div>
</div>
<div class="form-group">
<label class="col-md-2 control-label">Country</label>
<div class="col-md-10">
<autocomplete asp-for="CountryId" route="/Lookups/GetCountry" target-wrapper="form" display-format="{Name} ({Code})" value-format="{Id}"></autocomplete>
<span asp-validation-for="CountryId" class="text-danger" />
</div>
</div>
<div class="form-group">
<label asp-for="Postcode" class="col-md-2 control-label">Post Code</label>
<div class="col-md-10">
<input asp-for="Postcode" class="form-control" />
<span asp-validation-for="Postcode" class="text-danger" />
</div>
</div>
Note that the autocomplete tag above in the EditorTemplate generates its own internal tags (as part of the tag helper).
This is a portion of the page for the first Address Information (as shown in firefox)
<input id="z0__Id" type="hidden" value="5" name="[0].Id" data-val-required="The Id field is required." data-val="true">
<input id="z0__ContactTypeId" type="hidden" value="1" name="[0].ContactTypeId" data-val-required="The ContactTypeId field is required." data-val="true">
<input id="z0__AddressLink" type="hidden" value="1" name="[0].AddressLink" data-val-required="The AddressLink field is required." data-val="true">
<input id="z0__AddressLinkTo" type="hidden" value="F" name="[0].AddressLinkTo">
<label class="col-md-12 control-label" style="padding-bottom:20px;">Work</label>
<div class="form-group">
<label class="col-md-2 control-label" for="z0__AddressLine">Address</label>
<div class="col-md-10">
<input id="z0__AddressLine" class="form-control" type="text" value="4a Lansdowne Street" name="[0].AddressLine" style="resize:both">
<span class="text-danger field-validation-valid" data-valmsg-replace="true" data-valmsg-for="[0].AddressLine"> </span>
</div>
</div>
<div class="form-group">
<label class="col-md-2 control-label" for="z0__Suburb">Suburb</label>
<div class="col-md-10">
<input id="z0__Suburb" class="form-control" type="text" value="Bayswater" name="[0].Suburb">
<span class="text-danger field-validation-valid" data-valmsg-replace="true" data-valmsg-for="[0].Suburb"> </span>
</div>
</div>
<div class="form-group">
<label class="col-md-2 control-label" for="z0__City">City</label>
<div class="col-md-10">
<input id="z0__City" class="form-control" type="text" value="Auckland" name="[0].City">
<span class="text-danger field-validation-valid" data-valmsg-replace="true" data-valmsg-for="[0].City"> </span>
</div>
</div>
<div class="form-group">
<label class="col-md-2 control-label">Country</label>
<div class="col-md-10">
<input id="CountryId_hidden" type="hidden" value="1" name="CountryId">
<input id="CountryId" class="moana-autocomplete form-control ui-autocomplete-input" type="text" value="New Zealand (NZ)" display-format="{Name} ({Code})" value-format="{Id}" placeholder="" target-wrapper="form" route-parameters="" route="/Lookups/GetCountry" name="CountryId" autocomplete="off">
<span class="text-danger field-validation-valid" data-valmsg-replace="true" data-valmsg-for="[0].CountryId"> </span>
</div>
</div>
<div class="form-group">
<label class="col-md-2 control-label" for="z0__Postcode">Post Code</label>
<div class="col-md-10">
<input id="z0__Postcode" class="form-control" type="text" value="0604" name="[0].Postcode">
<span class="text-danger field-validation-valid" data-valmsg-replace="true" data-valmsg-for="[0].Postcode"> </span>
</div>
</div>
Note that the Html.EditorFor generates the Zn__fieldname prefixes to the input name attribute as well as an [n].fieldname name for the input id attribute
The issue is how to access the index value, or get this prefix to tack onto our generated inputs from inside the TagHelper i.e. the Zn__* or [n] value, which is essentially the indexor of the EditorFor as it generates the repeated fields
Thanks for any help
Ok I have been working diligently on this.
I eventually had to look at the TagHelpers from the Asp.Net core source project.
I found that we need to access the ViewContext.ViewData.TemplateInfo;
from inside the TagHelper.
Or alternately we could use the IHtmlGenerator object - calling a GenerateTextBox, GenerateHidden, etc to build the TageHelpers as required
I am doing user roles in MVC6 (using EF7), and I am think I am just missing something, but on a form used to define a user roles I get nothing in the posted back Model to the controller
This is my Model
public class UserRoleItem
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public IdentityUserRole<int> userRole { get; set; }
public bool HasRole { get; set; }
}
public class UsersRolesViewModel
{
public int UserId { get; set; }
public string FullName { get; internal set; }
public List<UserRoleItem> UserRoles;
}
This is the view
#model Skill.ViewModels.Manage.UsersRolesViewModel
<br />
<h3>Set Roles for user - #Model.FullName</h3>
<form asp-action="ChangeUsersRoles" >
<div class="form-horizontal">
<hr />
<input type="hidden" asp-for="UserId" />
#foreach (var userRole in Model.UserRoles)
{
<input type="hidden" asp-for="#userRole.Id" />
<div class="form-group">
<div class="inline-block col-md-8">
<span class="col-md-1" align="center">
<input type="checkbox" asp-for="#userRole.HasRole" />
</span>
<div class="col-md-7">
#userRole.Name (#userRole.Description)
</div>
</div>
</div>
}
<hr />
<div class="form-group">
<div class="col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
</form>
This is the Controller
// GET: Users/ChangeUserRoles/5
public async Task<IActionResult> ChangeUserRoles(int? id)
{
if (id == null)
{
return HttpNotFound();
}
var model = new UsersRolesViewModel();
ApplicationUser appUser = await _context.ApplicationUsers.SingleAsync(m => m.Id == id);
if (appUser == null)
{
return HttpNotFound();
}
else
{
model.UserId = (int)id;
model.FullName = appUser.FullName;
var some = from r in _context.Roles
from ur in _context.UserRoles
.Where(inner => r.Id == inner.RoleId && inner.UserId == id)
.DefaultIfEmpty()
select new UserRoleItem
{
Id = (int)r.Id,
Name = r.Name,
Description = r.NormalizedName,
userRole = ur, // this is needed else it has a hissy fit
HasRole = (ur != null)
};
// get all of the Roles and then also link to the ones the user currently has
model.UserRoles = (some).ToList();
}
return View(model);
}
// POST: Users/ChangeUserRoles/5
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> ChangeUserRoles([Bind(include:"UserId,UserRoles")]UsersRolesViewModel userModel)
{
if (ModelState.IsValid)
{
// update based on the changes
// return RedirectToAction("Edit", new { userModel.UserId });
}
return View(userModel);
}
So when I get the post back on Save, the UserRoles list is null, so I assume I am just missing an obvious thing here?
Also another small issue is the EF Linq statement. If I remove the
userRole = ur,
statement from the Select portion of the Linq query, the system has a fit and says my schema is out of date (which it isn't). I think it is due to the following statement where I am testing the outer join value
HasRole = (ur != null)
Although this seems perfectly reasonable and works if the ur variable is used prior to testing for null (or not)
After entering this question - I further investigated the issue and found that I could do what I needed using an EditorTemplate
So I created the EditorTemplate Folder under my Users View folder as it was a UsersController and then added the following UserRoleItem.cshtml file
#model UserRoleItem
<input type="hidden" asp-for="Id" />
<div class="form-group">
<div class="inline-block col-md-8">
<span class="col-md-1" align="center">
<input type="checkbox" asp-for="HasRole" />
</span>
<div class="col-md-7">
#Model.Name (#Model.Description)
</div>
</div>
</div>
I then changed the view (called ChangeUsersRole.cshtml) to be
#model Skill.ViewModels.Manage.UsersRolesViewModel
<br />
<h3>Set Roles for user - #Model.FullName</h3>
<hr />
<form asp-action="ChangeUsersRoles">
<div class="form-horizontal">
<input type="hidden" asp-for="UserId" />
#Html.EditorFor(m => m.UserRoles)
<hr />
<div class="form-group">
<div class="col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
</form>
Note also: that I tried using the new format
<input asp-for="UserRoles" />
instead of this line in the view
#Html.EditorFor(m => m.UserRoles)
But it did not work as expected. Again maybe I am still doing something wrong - or maybe that feature isn't working yet?