Support us .Net Basics C# SQL ASP.NET Aarvi MVC Slides C# Programs Subscribe Download

Edit role in asp.net core

Suggested Videos
Part 77 - Extend IdentityUser in ASP.NET Core | Text | Slides
Part 78 - Creating roles in asp.net core | Text | Slides
Part 79 - Get list of roles in asp.net core | Text | Slides

In this video we will discuss how to edit an existing role in asp.net core using the Identity API.

Navigating to Edit Role View


The following view displays the list of all roles. 

asp.net core edit role example


When Edit button on one of the roles is clicked, we want to go to EditRole action in the AdministrationController. We are using asp-action and asp-controller tag helpers to do that. We also want to pass the ID of the role to edit. We pass the Role ID using asp-route-id tag helper.

<a asp-controller="Administration" asp-action="EditRole"
   asp-route-id="@role.Id" class="btn btn-primary">
    Edit
</a>

asp-route-id tag helper includes the Role ID in the URL 

/Administration/EditRole/7360350f-2662-4842-8a78-58a308043477

Edit Role View

We want the Edit Role View to be as shown below.

asp.net identity edit role
  • Role ID is non-editable, so keep the input element disabled
  • Only Role Name is editable
  • Display the list of Users in the Role
Edit Role View Model

The following EditRoleViewModel class carries the data EditRole view needs. Users property holds the list of Users in this role. The constructor initializes Users property so we do not get NULL reference exceptions.

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace EmployeeManagement.ViewModels
{
    public class EditRoleViewModel
    {
        public EditRoleViewModel()
        {
            Users = new List<string>();
        }

        public string Id { get; set; }

        [Required(ErrorMessage = "Role Name is required")]
        public string RoleName { get; set; }

        public List<string> Users { get; set; }
    }
}

Edit Role Action Methods

// Role ID is passed from the URL to the action
[HttpGet]
public async Task<IActionResult> EditRole(string id)
{
    // Find the role by Role ID
    var role = await roleManager.FindByIdAsync(id);

    if (role == null)
    {
        ViewBag.ErrorMessage = $"Role with Id = {id} cannot be found";
        return View("NotFound");
    }

    var model = new EditRoleViewModel
    {
        Id = role.Id,
        RoleName = role.Name
    };

    // Retrieve all the Users
    foreach (var user in userManager.Users)
    {
        // If the user is in this role, add the username to
        // Users property of EditRoleViewModel. This model
        // object is then passed to the view for display
        if (await userManager.IsInRoleAsync(user, role.Name))
        {
            model.Users.Add(user.UserName);
        }
    }

    return View(model);
}

// This action responds to HttpPost and receives EditRoleViewModel
[HttpPost]
public async Task<IActionResult> EditRole(EditRoleViewModel model)
{
    var role = await roleManager.FindByIdAsync(model.Id);

    if (role == null)
    {
        ViewBag.ErrorMessage = $"Role with Id = {model.Id} cannot be found";
        return View("NotFound");
    }
    else
    {
        role.Name = model.RoleName;

        // Update the Role using UpdateAsync
        var result = await roleManager.UpdateAsync(role);

        if (result.Succeeded)
        {
            return RedirectToAction("ListRoles");
        }

        foreach (var error in result.Errors)
        {
            ModelState.AddModelError("", error.Description);
        }

        return View(model);
    }
}

Edit Role View

@model EditRoleViewModel

@{
    ViewBag.Title = "Edit Role";
}

<h1>Edit Role</h1>

<form method="post" class="mt-3">
    <div class="form-group row">
        <label asp-for="Id" class="col-sm-2 col-form-label"></label>
        <div class="col-sm-10">
            <input asp-for="Id" disabled class="form-control">
        </div>
    </div>
    <div class="form-group row">
        <label asp-for="RoleName" class="col-sm-2 col-form-label"></label>
        <div class="col-sm-10">
            <input asp-for="RoleName" class="form-control">
            <span asp-validation-for="RoleName" class="text-danger"></span>
        </div>
    </div>

    <div asp-validation-summary="All" class="text-danger"></div>

    <div class="form-group row">
        <div class="col-sm-10">
            <button type="submit" class="btn btn-primary">Update</button>
            <a asp-action="ListRoles" class="btn btn-primary">Cancel</a>
        </div>
    </div>

    <div class="card">
        <div class="card-header">
            <h3>Users in this role</h3>
        </div>
        <div class="card-body">
            @if (Model.Users.Any())
            {
                foreach (var user in Model.Users)
                {
                    <h5 class="card-title">@user</h5>
                }
            }
            else
            {
                <h5 class="card-title">None at the moment</h5>
            }
        </div>
        <div class="card-footer">
            <a href="#" class="btn btn-primary" style="width:auto">Add Users</a>
            <a href="#" class="btn btn-primary" style="width:auto">Remove Users</a>
        </div>
    </div>
</form>

asp.net core tutorial for beginners

21 comments:

  1. /label asp-for="Id" class="col-sm-2 col-form-label"///label/
    /div class="col-sm-10"/
    /input asp-for="Id" disabled class="form-control" //
    /input hidden asp-for="Id" //
    //div/

    ReplyDelete
  2. When using Core 3.0 I get an exception When I run EditRole(String Id) (video Number 80).
    This is the line that throws exception
    if (await userManager.IsInRoleAsync(user, role.Name))

    InvalidOperationException: There is already an open DataReader associated with this Command which must be closed first

    If any one has a fix, please leave a response.
    Here is the whole method:
    // Role ID is passed from the URL to the action
    [HttpGet]
    public async Task EditRole(string id)
    {
    // Find the role by Role ID
    var role = await roleManager.FindByIdAsync(id);

    if (role == null)
    {
    ViewBag.ErrorMessage = $"Role with Id = {id} cannot be found";
    return View("NotFound");
    }

    var model = new EditRoleViewModel
    {
    Id = role.Id,
    RoleName = role.Name
    };

    // Retrieve all the Users
    foreach (var user in userManager.Users)
    {
    // If the user is in this role, add the username to
    // Users property of EditRoleViewModel. This model
    // object is then passed to the view for display
    if (await userManager.IsInRoleAsync(user, role.Name))
    {
    model.Users.Add(user.UserName);
    }
    }

    return View(model);
    }

    ReplyDelete
    Replies
    1. In Core 3.0 , I finally found what to do:
      change userManager.Users to userManager.Users.ToList().
      userManager.Users used to work on 2.2

      Delete
    2. Thanks, now it work (userManager.Users.ToList())

      Delete
    3. Thanks for bring this question up, I had exactly the same issue, fixed by adding .ToList()

      Delete
    4. OR
      you can add below option to connection string:
      MultipleActiveResultSets=true;

      Delete
    5. MultipleActiveResultSets=true; is better solution.
      and it worked for me.

      Delete
    6. Thanks ,I Had the Same Issue ,fixed by Adding ToList()

      Delete
    7. I am using .Net Core 3.1. Adding ToList() solved the error for me.

      foreach (var user in userManager.Users.ToList())

      Delete
    8. I reckon MultipleActiveResultSets=true; and .ToList() are both better to apply the applications, first one enables the multiple DataReader, second one makes the IQueryable object to IEnumable object which is a good practice, IMHO, thanks for your sharing!

      Delete
  3. Error:
    {
    "resource": "/c:/Websites/EmployeeManagement/Controllers/EditRoleViewModel.cs",
    "owner": "msCompile",
    "code": "CS0103",
    "severity": 8,
    "message": "The name 'userManager' does not exist in the current context [C:\\Websites\\EmployeeManagement\\EmployeeManagement.csproj]",
    "startLineNumber": 68,
    "startColumn": 34,
    "endLineNumber": 68,
    "endColumn": 34
    }

    ReplyDelete
  4. An unhandled exception occurred while processing the request.
    NullReferenceException: Object reference not set to an instance of an object.
    EpaMgt.Controllers.AdministrationController.EditRole(string id) in AdministrationController.cs, line 70

    EpaMgt.Controllers.AdministrationController.EditRole(string id) in AdministrationController.cs
    +
    foreach (var user in userManager.Users)

    anyone?

    ReplyDelete
  5. How do you display both User's name and email at the same time ?
    I'd added model.Names.Add(user.Name). it worked actually ...
    However, I need them to display in a single line ..
    how do I do it ?

    ReplyDelete
  6. When I click edit button, this error message found.

    An unhandled exception occurred while processing the request.
    InvalidOperationException: There is already an open DataReader associated with this Command which must be closed first.
    Microsoft.Data.SqlClient.SqlCommand+<>c.b__164_0(Task result)

    Stack Query Cookies Headers Routing
    InvalidOperationException: There is already an open DataReader associated with this Command which must be closed first.
    Microsoft.Data.SqlClient.SqlCommand+<>c.b__164_0(Task result)
    System.Threading.Tasks.ContinuationResultTaskFromResultTask.InnerInvoke()
    System.Threading.Tasks.Task+<>c.<.cctor>b__274_0(object obj)
    System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, object state)
    System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, object state)
    System.Threading.Tasks.Task.ExecuteWithThreadLocal(ref Task currentTaskSlot, Thread threadPoolThread)
    Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)
    Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)
    Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)
    Microsoft.EntityFrameworkCore.Query.Internal.QueryingEnumerable+AsyncEnumerator.InitializeReaderAsync(DbContext _, bool result, CancellationToken cancellationToken)
    Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.ExecuteAsync(TState state, Func> operation, Func>> verifySucceeded, CancellationToken cancellationToken)
    Microsoft.EntityFrameworkCore.Query.Internal.QueryingEnumerable+AsyncEnumerator.MoveNextAsync()
    System.Runtime.CompilerServices.ValueTaskAwaiter.GetResult()
    Microsoft.EntityFrameworkCore.Query.ShapedQueryCompilingExpressionVisitor.SingleOrDefaultAsync(IAsyncEnumerable asyncEnumerable, CancellationToken cancellationToken)
    Microsoft.EntityFrameworkCore.Query.ShapedQueryCompilingExpressionVisitor.SingleOrDefaultAsync(IAsyncEnumerable asyncEnumerable, CancellationToken cancellationToken)
    Microsoft.AspNetCore.Identity.EntityFrameworkCore.UserStore.IsInRoleAsync(TUser user, string normalizedRoleName, CancellationToken cancellationToken)
    Microsoft.AspNetCore.Identity.UserManager.IsInRoleAsync(TUser user, string role)
    EmployeeManagement.Controllers.AdminController.EditRole(string id) in AdminController.cs
    +
    if (await userManager.IsInRoleAsync(user, role.Name))
    Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor+TaskOfIActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, object controller, object[] arguments)
    System.Threading.Tasks.ValueTask.get_Result()
    System.Runtime.CompilerServices.ValueTaskAwaiter.GetResult()

    ReplyDelete
    Replies
    1. Add MultipleActiveResultSets=true to the provider part of your connection string (where Data Source, Initial Catalog, etc. are specified).
      like here in my example in the file appsettings.json
      "ConnectionStrings": {
      "EmployeeDBConnection": "server=(localdb)\\MSSQLLocalDB;database=YourDatabasename;Trusted_Connection=true;MultipleActiveResultSets=true"}

      Delete
  7. you forget to add the action name and controller name for Update button

    ReplyDelete
  8. This comment has been removed by the author.

    ReplyDelete
  9. why used public EditRoleViewModel()
    {
    Users = new List();
    }

    ReplyDelete
    Replies
    1. // The constructor initializes Users property so we do not get NULL reference exceptions. try to run your project without add this part. you can see that null reference exception.

      Delete
  10. I have tried all these solutions but still, i get this error

    System.NullReferenceException
    HResult=0x80004003
    Message=Object reference not set to an instance of an object.
    Source=EmployeeManagement
    StackTrace:
    at EmployeeManagement.Controllers.AdministrationController.d__6.MoveNext() in C:\Projects\EmployeeManagement\Controllers\AdministrationController.cs:line 79

    ReplyDelete
  11. when i click on update button the not found page execute although role != null why this happen

    ReplyDelete

It would be great if you can help share these free resources