Here is my code to create a new record in ASP.NET Core MVC application.
Code: (CreateController.cs file)
[HttpPost]
public async Task<IActionResult> Add(AddEmployeeViewModel addEmp)
{
var employee = new Employee()
{
Id = Guid.NewGuid(),
Name = addEmp.Name,
Email = addEmp.Email,
Salary = addEmp.Salary,
};
await mvcDemoDbContext.Employees.AddAsync(employee);
await mvcDemoDbContext.SaveChangesAsync();
return RedirectToAction("Index");
}
How to write xUnit test case for this particular method?
I have a created a separate xUnit Test project and create a controller instance in Arrange block.
Thanks for your help!
I tried as below followed the two documents: doc1,doc2,hopes could help
public class UnitTest1
{
[Fact]
public async Task TestFor_RedirecForTestAction_WhenModelStateIsValid()
{
//Arrange
var employee = new Employee() { Id = 1, Name = "SomeName" };
var mockset = new Mock<DbSet<Employee>>();
var mockcontext = new Mock<MVCProjForTestContext>(new DbContextOptions<MVCProjForTestContext>());
mockcontext.Setup(x=>x.Employee).Returns(mockset.Object);
var controller = new HomeController(mockcontext.Object);
//Act
var result=await controller.RedirecForTest(employee);
//Asssert
mockset.Verify(x => x.AddAsync(It.IsAny<Employee>(), default(CancellationToken)), Times.Once());
mockcontext.Verify(x=>x.SaveChangesAsync(default(CancellationToken)), Times.Once()) ;
var redirectresult =Assert.IsType<RedirectToActionResult>(result);
Assert.Null( redirectresult.ControllerName);
Assert.Equal("Index", redirectresult.ActionName);
}
}
public class HomeController : Controller
{
private readonly MVCProjForTestContext _context;
public HomeController(MVCProjForTestContext context)
{
_context=context;
}
public async Task<IActionResult> RedirecForTest(Employee employee)
{
if (ModelState.IsValid)
{
await _context.Employee.AddAsync(employee);
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
else
{
return BadRequest();
}
}
}
public class MVCProjForTestContext : DbContext
{
public MVCProjForTestContext (DbContextOptions<MVCProjForTestContext> options)
: base(options)
{
}
public virtual DbSet<MVCProjForTest.Models.Employee> Employee { get; set; } = default!;
}
The result:
We have been doing some security testing and have found an issue where if an incorrect Content-Type is posted then a 400 is returned instead of 415.
Now this only happens if I apply my Authorize attribute to the entire controller, and then set my Consumes Attribute on the post action. When the Authorize Attribute is applied to only an action along with the Consumes Attribute then this works just fine.
This works: Returns 415
public class MyController : Controller
{
[HttpPost]
[Authorize(Policy = "MyPolicy")]
[Consumes("application/x-www-form-urlencoded")]
public async Task<IActionResult> APostActrion(MyModel model)
{
return View();
}
}
This Doesn't: Returns 400
[Authorize(Policy = "MyPolicy")]
public class MyController : Controller
{
[HttpPost]
[Consumes("application/x-www-form-urlencoded")]
public async Task<IActionResult> APostActrion(MyModel model)
{
return View();
}
}
EDIT
Here is an example of my policy
options.AddPolicy("MyPolicy", b =>
{
b.RequireAuthenticatedUser();
b.RequireClaim(ClaimTypes.Role, "MyRole");
b.Requirements.Add(new OrganisationRequirement());
});
Here is the AuthorizationHandler for OrganisationRequirement
public class OrganisationHandler : AuthorizationHandler<OrganisationRequirement>
{
private readonly StatelessServiceContext _context;
public OrganisationHandler(StatelessServiceContext context)
{
_context = context;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, OrganisationRequirement requirement)
{
if (CanRequest(context))
{
context.Succeed(requirement);
}
else
{
context.Fail();
}
return Task.CompletedTask;
}
private bool CanRequest(AuthorizationHandlerContext context)
{
string OrgId = Encoding.Unicode.GetString(_context.InitializationData, 0, _context.InitializationData.Length);
if (string.IsNullOrEmpty(OrgId))
{
return true;
}
if (context.User.HasClaim(c => c.Type == ClaimTypes.Role && (c.Value == RoleNames.SysAdmin)))
{
return true;
}
if (context.User.HasClaim(c => c.Type == ClaimTypes.Role && c.Value == RoleNames.Service)
&& context.User.HasClaim(c => (c.Type == CustomClaimTypes.OrgCode) && (c.Value == "system")))
{
return true;
}
if (context.User.HasClaim(c => (c.Type == CustomClaimTypes.OrgCode) && c.Value == OrgId))
{
return true;
}
return false;
}
}
Turns out that if you have a [HTTPGet] Method that corresponds to your post method and don't annotate it with [HTTPGet], the post request gets directed to that get action.
This causes an issue:
public class MyController : Controller
{
[Authorize(Policy = "MyPolicy")]
public async Task<IActionResult> MyAction(int id)
{
return View();
}
[HttpPost]
[Authorize(Policy = "MyPolicy")]
[Consumes("application/x-www-form-urlencoded")]
public async Task<IActionResult> MyAction(MyModel model)
{
return View();
}
}
This resolves the issue:
public class MyController : Controller
{
[HttpGet]
[Authorize(Policy = "MyPolicy")]
public async Task<IActionResult> MyAction(int id)
{
return View();
}
[HttpPost]
[Authorize(Policy = "MyPolicy")]
[Consumes("application/x-www-form-urlencoded")]
public async Task<IActionResult> MyAction(MyModel model)
{
return View();
}
}
I try to call a controller method from a view in asp.net core.
I have two different controller in my project. A Homecontroller and a controller for my Model Pupil.
From the navigation in the layout.cshtm I try to call the index method of my Pupilcontroller:
<a asp-action="Index" asp-controller="Pupil">Home</a>
I als tried #Html.Action("Pupil","Index","Pupil")
but nothing worked.
This is my Controller:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
using svkcore.Models;
namespace svkcore.Controllers
{
public class PupilController : Controller
{
private readonly SchuleContext _context;
public PupilController(SchuleContext context)
{
_context = context;
}
// GET: Pupil
public async Task<IActionResult> Index()
{
var schuleContext = _context.Pupils.Include(s => s.Ansprechpartner).Include(s => s.Fach);
return View(await schuleContext.ToListAsync());
}
// GET: Pupil/Details/5
public async Task<IActionResult> Details(int? id)
{
if (id == null)
{
return NotFound();
}
var pupil= await _context.Pupils
.Include(s => s.Ansprechpartner)
.Include(s => s.Fach)
.FirstOrDefaultAsync(m => m.Idschueler == id);
if (pupil== null)
{
return NotFound();
}
return View(pupil);
}
// GET: Pupil/Create
public IActionResult Create()
{
ViewData["AnsprechpartnerId"] = new SelectList(_context.Ansprechpartners, "Idansprechpartner", "Adresse");
ViewData["FachId"] = new SelectList(_context.Faches, "Idfach", "Fach1");
return View();
}
// POST: Pupil/Create
// To protect from overposting attacks, 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("Idschueler,Vorname,Nachname,AnsprechpartnerId,FachId,Klasse,Telefonnummer,Geburtstag")] Schueler schueler)
{
if (ModelState.IsValid)
{
_context.Add(pupil);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
ViewData["AnsprechpartnerId"] = new SelectList(_context.Ansprechpartners, "Idansprechpartner", "Adresse", pupil.AnsprechpartnerId);
ViewData["FachId"] = new SelectList(_context.Faches, "Idfach", "Fach1", schueler.FachId);
return View(pupil);
}
// GET: Schueler/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var schueler = await _context.Pupils.FindAsync(id);
if (schueler == null)
{
return NotFound();
}
ViewData["AnsprechpartnerId"] = new SelectList(_context.Ansprechpartners, "Idansprechpartner", "Adresse", pupil.AnsprechpartnerId);
ViewData["FachId"] = new SelectList(_context.Faches, "Idfach", "Fach1", schueler.FachId);
return View(pupil);
}
// POST: Pupil/Edit/5
// To protect from overposting attacks, 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> Edit(int id, [Bind("Idschueler,Vorname,Nachname,AnsprechpartnerId,FachId,Klasse,Telefonnummer,Geburtstag")] Pupil pupil)
{
if (id != schueler.Idpupil)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(pupil);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!SchuelerExists(pupil.IdPupil))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
ViewData["AnsprechpartnerId"] = new SelectList(_context.Ansprechpartners, "Idansprechpartner", "Adresse", pupil.AnsprechpartnerId);
ViewData["FachId"] = new SelectList(_context.Faches, "Idfach", "Fach1", schueler.FachId);
return View(pupil);
}
// GET: Schueler/Delete/5
public async Task<IActionResult> Delete(int? id)
{
if (id == null)
{
return NotFound();
}
var schueler = await _context.Pupils
.Include(s => s.Ansprechpartner)
.Include(s => s.Fach)
.FirstOrDefaultAsync(m => m.IdPupil == id);
if (Pupil== null)
{
return NotFound();
}
return View(Pupil);
}
// POST: Pupil/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
var schueler = await _context.Pupils.FindAsync(id);
_context.Schuelers.Remove(pupil);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
private bool PupilExists(int id)
{
return _context.Schuelers.Any(e => e.Idschueler == id);
}
}
}
the Routing in the Startup.cs:
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapRazorPages();
});
I always get this answer:
InvalidOperationException: Unable to resolve service for type 'svkcore.Models.SchuleContext' while attempting to activate 'svkcore.Controllers.SchuelerController'.
Can someone please give any advice, how to call a different controller from a link in a view?
Many thanks in advance!
Peter
#Startup-Class
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using svkcore.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace svkcore
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDatabaseDeveloperPageExceptionFilter();
services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddControllersWithViews();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseMigrationsEndPoint();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapRazorPages();
});
}
}
}
Change ApplicationDbContext to SchuleContext
services.AddDbContext<SchuleContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
But, if you want the ApplicationDbContext to be injected also, have the the following configuration.
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDbContext<SchuleContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
I want to restrict delete action for the entities with foriegn key dependencies. To do that I have this code in DbContext.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
foreach (var relationship in modelBuilder.Model.GetEntityTypes().SelectMany(e => e.GetForeignKeys()))
{
relationship.DeleteBehavior = DeleteBehavior.Restrict;
}
}
In my controller action I have this code.
public async Task<IActionResult> Delete(int? id)
{
var departmentDto = await GetAsync(p => p.Id == id);
if (departmentDto == null)
{
return NotFound();
}
return View(departmentDto);
}
// POST: Departments/5/Delete
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
[Route("{id:int:min(1)}/Delete")]
public async Task<IActionResult> DeleteConfirmed(int id)
{
await DeleteAsync(p => p.Id == id);
return RedirectToAction(nameof(Index));
}
}
}
So far I did this without error. Now the question is I want to set my own message in 'deletebehavior.restrict' and return to index insted of exception error. How can I do that? Please help me.
you can catch the exception and provide a proper error message
// POST: Departments/5/Delete
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
[Route("{id:int:min(1)}/Delete")]
public async Task<IActionResult> DeleteConfirmed(int id)
{
try
{
await DeleteAsync(p => p.Id == id);
}
catch(InvalidOperationException e)
{
return new BadRequestObjectResult("child exists for department"); // can return custom error object also
}
return RedirectToAction(nameof(Index));
}
I have a webapi controller with two HttpGet. I defined the routes as shown below.
when I call the methods with the link below, my breakpoint does not get hit.
http://localhost:49869/Api/Articles/SearchArticles/News
http://localhost:49869/Api/Articles/DefaultArticles/News
Is this link not correct?
[Route("api/[controller]")]
public class ArticlesController : ApiController
{
// GET api/Articles/News
[HttpGet]
[Route("SearchArticles/{category}")]
public IEnumerable<ArticlesDto> Get(string category)
{
IEnumerable<ArticlesDto> articlesByCategory = null;
try
{
if (category == null)
{
}
articlesByCategory = _articlesrepository.Find(category);
}
catch(Exception ex)
{
}
return articlesByCategory;
}
// Get api/Articles/News
// Pull default articles by category data
[HttpGet]
[Route("DefaultArticles/{category}")]
public IEnumerable<ArticlesDto> GetDefault(string category)
{
IEnumerable<ArticlesDto> defaultArticlesByCategory = null;
try
{
if (category == null)
{
}
defaultArticlesByCategory = _articlesrepository.FindDefault(category);
}
catch (Exception ex)
{
//return BadRequest(ex.Message);
}
return defaultArticlesByCategory;
}
}