Change the action for DeleteBehavior.Restict exception - asp.net-core-mvc

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));
}

Related

Consume Attribute Ignored when Authorize Policy is applied to entire controller

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();
}
}

Call controller action from view in asp.net mvc core

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")));

Cannot access Web API CORE 3.0 from Postman calls

I am testing simple .NET Web API CORE 3.1 and have setup simple methods by following tutorial on https://learn.microsoft.com/en-us/aspnet/core/tutorials/first-web-api?view=aspnetcore-3.1&tabs=visual-studio. I have managed to run Web API but couldn't make calls from Postman/
Controller
namespace CoreAPIApplication.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class TodoItemsController : ControllerBase
{
private readonly TodoContext _context;
public TodoItemsController(TodoContext context)
{
_context = context;
}
[HttpGet]
public async Task<ActionResult<IEnumerable<TodoItem>>> GetTodoItems()
{
return await _context.TodoItems.ToListAsync();
}
[HttpGet("{id}")]
public async Task<ActionResult<TodoItem>> GetTodoItem(long id)
{
var todoItem = await _context.TodoItems.FindAsync(id);
if (todoItem == null)
{
return NotFound();
}
return todoItem;
}
[HttpPut("{id}")]
public async Task<IActionResult> PutTodoItem(long id, TodoItem todoItem)
{
if (id != todoItem.Id)
{
return BadRequest();
}
_context.Entry(todoItem).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!TodoItemExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return NoContent();
}
[HttpPost]
public async Task<ActionResult<TodoItem>> PostTodoItem(TodoItem todoItem)
{
_context.TodoItems.Add(todoItem);
await _context.SaveChangesAsync();
return CreatedAtAction(nameof(GetTodoItem), new { id = todoItem.Id }, todoItem);
}
[HttpDelete("{id}")]
public async Task<ActionResult<TodoItem>> DeleteTodoItem(long id)
{
var todoItem = await _context.TodoItems.FindAsync(id);
if (todoItem == null)
{
return NotFound();
}
_context.TodoItems.Remove(todoItem);
await _context.SaveChangesAsync();
return todoItem;
}
private bool TodoItemExists(long id)
{
return _context.TodoItems.Any(e => e.Id == id);
}
}
}
I have tried postman with and without SSL certificate enable from postman setting but still no result
From Startup.cs comment out
app.UseHttpsRedirection();
and use http://localhost:5000
If you do need to run it on https, you may need to have a certificate.
You can create one by running : "dotnet dev-certs https --trust" in your terminal/cmd.
Can you enable CorsPolicy by adding this => [EnableCors("CorsPolicy")] on the top of your controller class.
namespace CoreAPIApplication.Controllers
{
[EnableCors("CorsPolicy")]
[Route("api/[controller]")]
[ApiController]

ModelState to check all parameters in Web Api

This is my action the ModelState checks only the bookId parameter. The other one even if it is null, no error is raised.
Is there any way to make it check the ModelState of all parameters?
[HttpPut]
[Route("{bookId}")]
public IHttpActionResult Edit([FromBody] EditBookBindingModel model, int bookId)
{
if (!this.service.ExistsBook(bookId))
{
return this.NotFound();
}
if (!this.ModelState.IsValid)
{
return this.StatusCode(HttpStatusCode.BadRequest);
}
this.service.EditBook(bookId, model);
return this.Ok();
}
You could define an ActionFilterAttribute that protects you from null arguments:
public class CheckModelForNullAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (actionContext.ActionArguments.ContainsValue(null))
actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, "The argument cannot be null");
}
}
Then use this:
[HttpPut]
[Route("{bookId}")]
[CheckModelForNull]
public IHttpActionResult Edit([FromBody] EditBookBindingModel model, int bookId)
{
// model canĀ“t be null now
...
I wrote a custom filter so DataAnnotations works with parameters also.
Here is the filter.
public class ModelValidationFilter : FilterAttribute, IActionFilter, IFilter
{
public Task<HttpResponseMessage> ExecuteActionFilterAsync(HttpActionContext actionContext,
CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation)
{
var parameters = actionContext.ActionDescriptor.GetParameters();
if (parameters.Any())
{
var validationParams = parameters.Where(x => x.GetCustomAttributes<ValidationAttribute>().Any());
if (validationParams.Any())
{
foreach (var item in validationParams)
{
var val = actionContext.ActionArguments[item.ParameterName];
foreach (var attr in item.GetCustomAttributes<ValidationAttribute>())
{
if (!attr.IsValid(val))
{
actionContext.ModelState.AddModelError(item.ParameterName, attr.FormatErrorMessage(item.ParameterName));
}
}
}
}
if (!actionContext.ModelState.IsValid)
{
return Task.FromResult(actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, actionContext.ModelState));
}
}
return continuation();
}
}
Usage (I have'nt tested completely.):
add it to global filters.
config.Filters.Add(new ModelValidationFilter());
public Student Post([Required] addStudentDTO)
{
//Your logic
}
public Student Patch([FromBody,Required] addStudentDTO, [Required,EmailAddress]string emailAddress])
{
//Your logic
}

Why would a custom MVC3 action filter work on one controller action but not on another?

Here's the situation. I've got a single action filter that I'm using in two different controllers. The action filter is defined as:
public class ValidSubmissionAttribute : FilterAttribute, IActionFilter
{
public void OnActionExecuted(ActionExecutedContext filterContext)
{
}
public void OnActionExecuting(ActionExecutingContext filterContext)
{
var controller = filterContext.Controller;
var session = filterContext.HttpContext.Session;
var isValid = controller.TempData["IsValid"];
if (isValid == null || !(bool)isValid)
{
SharedUtilities.LogOutUser(session, controller.ViewData.ModelState);
filterContext.Result = SharedUtilities.GetThankYouRedirect();
}
}
}
When I invoke the Attribute in one controller, like this:
[HttpPost]
public ActionResult DoSomething(string button, Model data)
{
try
{
if (ModelState.IsValid)
{
TempData["IsValid"] =
Request.Form["ValidRequest"] == Session.SessionID;
Session["VerifyDoingSomethingData"] = data;
return RedirectToAction("VerifyDoingSomething");
}
}
catch (Exception ex)
{
}
}
[ValidSubmission]
public ActionResult VerifyDoingSomething()
{
ViewData.Model = Session["VerifyDoingSomethingData"];
return View("VerifyDoingSomething");
}
it functions as expected. However, when I call it from a different controller, like this:
[HttpPost]
public ActionResult Index(string button, Model data)
{
try
{
if (ModelState.IsValid)
{
TempData["IsValid"] =
Request.Form["ValidRequest"] == Session.SessionID;
Session["ViewModel"] = data;
return RedirectToAction("VerifyCancellation");
}
}
catch (Exception ex)
{
}
}
[ValidSubmission]
public ActionResult VerifyCancellation()
{
ViewData.Model = Session["ViewModel"];
return View("VerifyCancellation");
}
the attribute doesn't run at all. My breakpoint in the OnActionExecuting method doesn't get hit.
If I had to guess, I'd say there was some difference in the controllers or in the action methods, but they appear to be functionally similar. Any insights? Why would I be seeing such different behavior?
Aaaaand, I'm a schmuck.
Turns out there's a completely different execution path that I'd forgotten about. That path didn't have the TempData information to use in the ValidSubmisionAttribute. Everything is functioning correctly now.

Resources