Ok I am using a session variable to store a case Id that is linked between tables. I am using .net 3.1 I just need this simple value passed between controllers It appears to only work within the current controller.
Say Relationship Controller is this.
public class RelationShipsController : Controller
{
private readonly MISDBContext _context;
public RelationShipsController(MISDBContext context)
{
_context = context;
}
// GET: RelationShips/Edit/5
public async Task<IActionResult> Edit(int? id) {
if (id == null) {
return NotFound();
}
var relationShips = await _context.RelationShips.FindAsync(id);
if (relationShips == null) {
return NotFound();
}
HttpContext.Session.SetString("relationShipId", relationShips.Id.ToString());
HttpContext.Session.SetString("CaseId", relationShips.MisObjectId.ToString());
return View(relationShips);
}
}
This is the second controller where i wish to read in the above session.
public class VesselsController : Controller
{
private readonly MISDBContext _context;
public VesselsController(MISDBContext context) {
_context = context;
GetCompanies();
}
// POST: Vessels/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("Id,Name,CountryOfOrigon,CompanyId,IMONumber,Flag,Company,Country,CallSign,MMSI,VesselType,Active,isDeleted,isActive,CreatedDate,CreatedBy,MISObjectId,RelationShipId")] Vessels vessels)
{
if (ModelState.IsValid) {
var realtionShipId = Int32.TryParse(HttpContext.Session.GetString("relationShipId"), out int resultRelationshipId);
Int32.TryParse(HttpContext.Session.GetString("CaseId"), out Int32 resultCaseId);
vessels.isActive = true;
vessels.isDeleted = false;
vessels.CreatedBy = HttpContext.Session.GetString("Intitals");
vessels.LastModfiedDate = DateTime.Now;
vessels.CreatedDate = DateTime.Now;
vessels.LastModfiedBy = HttpContext.Session.GetString("Intitals");
vessels.MISObjectId = resultCaseId;
vessels.RelationShipId = resultRelationshipId;
_context.Add(vessels);
await _context.SaveChangesAsync();
return RedirectToAction("Edit", "Relationships", new { Id = vessels.MISObjectId });
}
GetCompanies();
return View(vessels);
}
}
Its this resultCaseId I have lost the variable and yes I have setup the configure middle ware.
app.UseSession();
Make sure you as the user have provided consent. Or mark the session cookie as "essential" like this:
services.AddSession(opts =>
{
opts.Cookie.IsEssential = true; // make the session cookie Essential
});
You can read more about GDPR changes in asp.net core here.
Related
currently I'm building web apps for local. When I create new user, I'm setup the user with Default Password. And I want to, if the User login with the Default Password it will redirect to Change Password Page. And User will not be able to access any page until they change the password.
My current workaround is checking in each controller, is there any Smart way to do this?
Here's some code after doing login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login([FromForm] LoginViewModel vm, string returnUrl = null)
{
if (ModelState.IsValid)
{
var result = await repository.Login(vm);
if (result.IsSuccess)
{
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, result.Data,
new AuthenticationProperties
{
IssuedUtc = DateTime.UtcNow,
ExpiresUtc = DateTime.UtcNow.AddDays(1),
IsPersistent = false
});
if (vm.Password != Password.DefaultPassword)
{
return RedirectToLocal(returnUrl);
}
else
{
return RedirectToAction(nameof(UserController.NewPassword));
}
}
else
{
ViewBag.ErrorMessage = result.ErrorMessage;
}
}
return View(vm);
}
For the other Controller, we always check the Password from session. If the Password is same as Default Password we redirect it to NewPassword Page
Thanks in advance!
You can store user password using session as below
HttpContext.Session.SetString("Password", vm.password);
Then create a filter to check if the user login with the Default Password or not
public class PasswordFilter: IAuthorizationFilter
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly ISession _session;
public PasswordFilter(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
_session = _httpContextAccessor.HttpContext.Session;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
if (_session.GetString("Password") == Password.DefaultPassword)
{
context.Result = new RedirectResult(nameof(UserController.NewPassword));
}
}
}
Finally, you can use this filter by adding this attribute to your controllers
[TypeFilter(typeof(PasswordFilter))]
I hope this approach achieves your goal.
As #JHBonarius said, my current workaround now is to use Custom Middleware for Redirecting to New Password Page.
My middleware look like this:
public class CheckPasswordMiddleware
{
private readonly RequestDelegate next;
public CheckPasswordMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context)
{
if (context.Request.Path != "/User/NewPassword" && context.User.Identity.IsAuthenticated)
{
var userId = context.User.Identity.GetUserId();
if (!string.IsNullOrEmpty(userId))
{
var dbContext = context.RequestServices.GetRequiredService<DatabaseContext>();
var passwordHash = await dbContext.User.Where(x => x.Id.ToLower() == userId.ToLower()).Select(x => x.Password).FirstOrDefaultAsync();
if (Hash.Verify(Password.DefaultPassword, passwordHash))
{
context.Response.Redirect("/User/NewPassword");
}
}
}
await next(context);
}
}
and now I can get rid the check in my other controller, and my Login will just look like this:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login([FromForm] LoginViewModel vm, string returnUrl = null)
{
if (ModelState.IsValid)
{
var result = await repository.Login(vm);
if (result.IsSuccess)
{
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, result.Data,
new AuthenticationProperties
{
IssuedUtc = DateTime.UtcNow,
ExpiresUtc = DateTime.UtcNow.AddDays(1),
IsPersistent = false
});
return RedirectToLocal(returnUrl);
}
else
{
ViewBag.ErrorMessage = result.ErrorMessage;
}
}
return View(vm);
}
Hope this help anyone who want to achieve the same goals.
Thanks!
This question already has answers here:
Using ModelState Outside of a Controller
(2 answers)
Closed 2 years ago.
I'm abstracting out my API logic in my controllers to Mediatr Commands and Queries. My POST and PUT endpoints get validated via standard .NET Core model binding validation, but the patchdoc for my PATCH endpoint needs to check the model state for validity in the controller. Since it's part of the ControllerBase I can't access that context anymore though.
How would you all recommend approaching this?
Patch Endpoint
[HttpPatch("{valueToReplaceId}")]
public IActionResult PartiallyUpdateValueToReplace(int valueToReplaceId, JsonPatchDocument<ValueToReplaceForUpdateDto> patchDoc)
{
var query = new UpdatePartialValueToReplaceCommand(valueToReplaceId, patchDoc);
var result = _mediator.Send(query);
switch (result.Result.ToUpper())
{
case "NOTFOUND":
return NotFound();
case "NOCONTENT":
return NoContent();
case "BADREQUEST":
return BadRequest();
default:
return BadRequest();
}
}
UpdatePartialValueToReplaceCommand
public class UpdatePartialValueToReplaceCommand : IRequest<string>
{
public int ValueToReplaceId { get; set; }
public JsonPatchDocument<ValueToReplaceForUpdateDto> PatchDoc { get; set; }
public UpdatePartialValueToReplaceCommand(int valueToReplaceId, JsonPatchDocument<ValueToReplaceForUpdateDto> patchDoc)
{
ValueToReplaceId = valueToReplaceId;
PatchDoc = patchDoc;
}
}
(BROKEN) UpdatePartialValueToReplaceHandler
public class UpdatePartialValueToReplaceHandler : IRequestHandler<UpdatePartialValueToReplaceCommand, string>
{
private readonly IValueToReplaceRepository _valueToReplaceRepository;
private readonly IMapper _mapper;
public UpdatePartialValueToReplaceHandler(IValueToReplaceRepository valueToReplaceRepository
, IMapper mapper)
{
_valueToReplaceRepository = valueToReplaceRepository ??
throw new ArgumentNullException(nameof(valueToReplaceRepository));
_mapper = mapper ??
throw new ArgumentNullException(nameof(mapper));
}
public async Task<string> Handle(UpdatePartialValueToReplaceCommand updatePartialValueToReplaceCommand, CancellationToken cancellationToken)
{
if (updatePartialValueToReplaceCommand.PatchDoc == null)
{
return "BadRequest";
}
var existingValueToReplace = _valueToReplaceRepository.GetValueToReplace(updatePartialValueToReplaceCommand.ValueToReplaceId);
if (existingValueToReplace == null)
{
return "NotFound";
}
var valueToReplaceToPatch = _mapper.Map<ValueToReplaceForUpdateDto>(existingValueToReplace); // map the valueToReplace we got from the database to an updatable valueToReplace model
updatePartialValueToReplaceCommand.PatchDoc.ApplyTo(valueToReplaceToPatch, ModelState); // apply patchdoc updates to the updatable valueToReplace -- THIS DOESN'T WORK IN A MEDIATR COMMAND BECAUSE I DON'T HAVE CONTROLLERBASE CONTEXT
if (!TryValidateModel(valueToReplaceToPatch))
{
return ValidationProblem(ModelState);
}
_mapper.Map(valueToReplaceToPatch, existingValueToReplace); // apply updates from the updatable valueToReplace to the db entity so we can apply the updates to the database
_valueToReplaceRepository.UpdateValueToReplace(existingValueToReplace); // apply business updates to data if needed
_valueToReplaceRepository.Save(); // save changes in the database
return "NoContent";
}
}
I came across the same situation and solved it by letting ModelState out (changed code):
updatePartialValueToReplaceCommand.PatchDoc.ApplyTo(valueToReplaceToPatch);
//if (!TryValidateModel(valueToReplaceToPatch))
//{
//return ValidationProblem(ModelState);
//}
Of course your missing the 'ModelState' validation this way.I would validate it through the Domain model.
I want to delete records from my database only which don't have any dependencies.
eg:- Company Table has Company Pk primary key and it is the foreign key for department table and location table. So the system should not allow user to delete a company from company if it has dependencies with department and location.
The records without any dependencies can be deleted. How can I do this?
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Threading.Tasks;
using System.Net;
using System.Web;
using System.Web.Mvc;
using Payroll.Core.Domain;
using Payroll.Infrastructure.Data;
using AutoMapper;
using Payroll.Web.Dto;
using Payroll.Core.Interfaces;
using Payroll.Infrastructure.Validators;
namespace Payroll.Web.Controllers
{
[RoutePrefix("Company")]
public class CompanyController : Controller
{
private readonly IMapper _mapper = null;
private readonly IUnitOfWork _work = null;
public CompanyController(IUnitOfWork work, IMapper mapper)
{
_work = work;
_mapper = mapper;
}
// GET: Company
public async Task<ActionResult> Index()
{
var companies = await _work.Companies.GetAllAsync();
var companyDtos = _mapper.Map<List<CompanyDto>>(companies);
return View(companyDtos);
}
// GET: Company/5
[Route("{companyPk:int}")]
public async Task<ActionResult> Details(int? companyPk)
{
//Validate parameters
if (companyPk == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest, "Company identifier is missing.");
}
//Get model from db
Company company = await _work.Companies.GetAsync(companyPk);
if (company == null)
{
return HttpNotFound();
}
//Convert model to dto
CompanyDto companyDto = _mapper.Map<CompanyDto>(company);
return View(companyDto);
}
// GET: Company/New
[Route("New")]
public ActionResult New()
{
var dto = new CompanyDto();
return View(dto);
}
// POST: Company/New
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see https://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
[Route("New")]
public async Task<ActionResult> New(CompanyDto companyDto)
{
//Validate Dto state
if (ModelState.IsValid)
{
//Convert dto to model
Company company = _mapper.Map<Company>(companyDto);
//Assign model properties
company.CompanyPk = new Random().Next(1, 10);
Utilities.Instance.SetEntityProperties(company);
//Validate model
var validator = new CompanyValidator();
var validation = await validator.ValidateAsync(company);
if (validation.IsValid)
{
//Save model to db
_work.Companies.Add(company);
await _work.CompleteAsync();
return RedirectToAction("Index");
}
else
{
foreach (var error in validation.Errors)
{
ModelState.AddModelError(error.PropertyName, error.ErrorMessage);
}
}
}
return View(companyDto);
}
// GET: Company/5/Edit
[Route("{companyPk:int}/Edit")]
public async Task<ActionResult> Edit(int? companyPk)
{
//Validate parameters
if (companyPk == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest, "Company identifier is missing.");
}
//Get the model from db
Company company = await _work.Companies.GetAsync(companyPk);
if (company == null)
{
return HttpNotFound();
}
//Convert model to dto
CompanyDto companyDto = _mapper.Map<CompanyDto>(company);
return View(companyDto);
}
// POST: Company/5/Edit
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see https://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
[Route("{companyPk:int}/Edit")]
public async Task<ActionResult> Edit(int? companyPk, CompanyDto companyDto)
{
//Validate parameters
if (companyPk == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest, "Company identifier is missing.");
}
//Validate Dto state
if (ModelState.IsValid)
{
//Get the model from db
//Because the record now being edited can be changed by another process
var company = await _work.Companies.GetAsync(companyPk);
if (company == null)
{
return HttpNotFound();
}
//Map the previous state of the model to log
var logCompany = _mapper.Map<LogCompany>(company);
//Convert dto to model
//This is a specific mapping which modifies the original model from only bound properties of Dto
_mapper.Map(companyDto, company);
//Validate model
var validator = new CompanyValidator();
var validation = await validator.ValidateAsync(company);
if (validation.IsValid)
{
//Assign log model properties
logCompany.RecordId = 0;
Utilities.Instance.SetLogEntityProperties(logCompany, "E");
//Save model to db
_work.LogCompanies.Add(logCompany);
_work.Companies.Update(company);
await _work.CompleteAsync();
return RedirectToAction("Index");
}
else
{
foreach (var error in validation.Errors)
{
ModelState.AddModelError(error.PropertyName, error.ErrorMessage);
}
}
}
return View(companyDto);
}
// GET: Company/5/Delete
[Route("{companyPk:int}/Delete")]
public async Task<ActionResult> Delete(int? companyPk)
{
//Validate parameters
if (companyPk == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest, "Company identifier is missing.");
}
//Get the model from db
Company company = await _work.Companies.GetAsync(companyPk);
if (company == null)
{
return HttpNotFound();
}
//Convert model to dto
CompanyDto companyDto = _mapper.Map<CompanyDto>(company);
return View(companyDto);
}
// POST: Company/5/Delete
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
[Route("{companyPk:int}/Delete")]
public async Task<ActionResult> DeleteConfirmed(int companyPk)
{
//Get the model from db
Company company = await _work.Companies.GetAsync(companyPk);
//Prepare log model
var logCompany = _mapper.Map<LogCompany>(company);
logCompany.RecordId = 0;
Utilities.Instance.SetLogEntityProperties(logCompany, "D");
//Save model to db
_work.LogCompanies.Add(logCompany);
_work.Companies.Remove(company);
await _work.CompleteAsync();
return RedirectToAction("Index");
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
_work.Dispose();
}
base.Dispose(disposing);
}
}
}
If you have navigation properties in your entity then you can just check if their keys are not null:
company.LocationId !=null && company.DepartmentId!=null
If you don't have Id properties (EF can create them conventionally) then read this documentation and load entities. Assuming that will not add much overhead.
var company = _work.Companies.Where(b => b.CompanyPk == companyPk)
.Include(b => b.Location)
.Include(b => b.Department)
.FirstOrDefault();
company.Location !=null && company.Department!=null
Hoping this is not a production code:
company.CompanyPk = new Random().Next(1, 10);
And never-ever destroy the injected dependency. This is a red flag as you don't control its lifetime, what if there is a pool? The rule is - you didn't create it, you are not responsible to destroy it. If you want explicitly control lifetime - then inject a Factory, create a service, use and destroy.
_work.Dispose();
I am creating a simple todo application in Razor Pages with .NET Core 2.2:
public class IndexModel : PageModel
{
private readonly ApplicationDbContext _context;
public List<TodoItem> TodoItems { get; set; }
public IndexModel(ApplicationDbContext context)
{
_context = context;
}
public async Task OnGet()
{
TodoItems = await _context.TodoItems.Where(t => t.IsDone == false).ToListAsync();
}
but whenever I execute this code:
public async Task<IActionResult> OnPostMarkDoneAsync(int id)
{
if (!ModelState.IsValid)
{
return Page();
}
var item = TodoItems.First(t => t.Id == id);
item.IsDone = true;
_context.TodoItems.Update(item);
var ok = await _context.SaveChangesAsync();
if (ok != 1)
{
return BadRequest(500);
}
return RedirectToPage();
}
I always get a null exception. Also, even if it is in the same page.
When I start the application, the TodoItems list is populated with the correct data. But whenever I execute the OnPostMarkDoneAsync method and debug through it, it shows that the list is now null. Why is that?
TodoItems will not be persisted across requests, so the fact that you have assigned to it in OnGet does not mean it will be still assigned to in OnPostMarkAsDoneAsync. You will need to refetch from the DbContext in OnPostMarkAsDoneAsync, as you have done in OnGet.
I try to start asp.net core web api routing attribute as default route but when I access routing with parameter, I could not get any response
[Route("api/[controller]")]
[ApiController]
public class WarehousesController : ControllerBase
{
private readonly ApplicationDbContext _context;
public WarehousesController(ApplicationDbContext context)
{
_context = context;
}
//http://localhost:2394/api/Warehouses/Project/1 (Not working)
[HttpGet("Project/{projectId}")]
public async Task<IActionResult> GetWarehouseByProjectId([FromRoute] int projectId)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var warehouse = await _context.warehouses.Include(x => x.Projects).Where(y => y.Projects.FirstOrDefault().ProjectId == projectId).ToListAsync();
if (warehouse == null)
{
return NotFound();
}
return Ok(warehouse);
}
}
Try with this one .This works fine for me
[HttpGet("Project/{projectId}")]
public async Task<IActionResult> GetWarehouseByProjectId([FromRoute] int projectId)
{
if (!ModelState.IsValid)
{
return BadRequest("Invalid state");
}
var warehouse =await _context.warehouses.FindAsync(projectId);
if (warehouse == null)
{
return NotFound("not found");
}
return Ok(warehouse);
}