how to properly redirect to page in asp.netcore - asp.net-core

edit:background on project. This was project was created following the concepts from the tutorials here on data access with razor pages https://learn.microsoft.com/en-us/aspnet/core/data/ef-rp/?view=aspnetcore-2.2. i did not do views or controllers so I currently do not have any actions i can redirect to. do i need to add a controllers folder?
When I save changes to my edit of the model subcategories, I want to go back to the categories page. Normally, this would be fine with a simple RedirectToPage("/index").
That doesn't work here because my index OnGet uses a string ID to filter through the the subcategories (which feels like I'm probably doing that wrong... but it works). Below is what I currently have and it obviously doesn't work:
Edit - OnPost:
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_context.Attach(Subcategory).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!SubcategoryExists(Subcategory.SubcategoryId))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToPage("./Index", "?id" , Subcategory.CategoryName);
}
Index - OnGet:
public async Task OnGetAsync(string id)
{
title = id;
Subcategory = await _context.Subcategory.ToListAsync();
foreach (Subcategory item in Subcategory.ToList())
{
if (item.CategoryName != id)
{
Subcategory.Remove(item);
}
}
}
Can anyone tell me how to properly route the OnPost of my edit razor page to the OnGet of my index razor page?
I've tried to do some research on handlers but I couldn't find a good understandable definition of how to do this.

It is a bit hard to see where you have hooked stuff up, as you have only provided the methods.
Given that OnGetAsync(string id) if mapped to /index you could use something like:
return RedirectToAction("Index", new { id = Subcategory.CategoryName.ToString() });
I would also suggest that you call your methods the same as the endpoint. So if OnGetAsync is your index endpoint. Then call it Index.
Hopefully this helps you, or else you need to provide me with more code.

the fix to the problem without building a controller was pretty much what Kristian Barrnet was saying:
return RedirectToPage("./Index", new { id = Subcategory.CategoryName.ToString() });
however, I should probably add some controllers here anyway to make this a lot more organized

Related

EF Core not setting class variables automatically

I want to switch my code to an async implementation. When I want to do this then I notice that my related data gets not set automatically after I retrieve them like it used to do it.
This is the initial function that gets called from an API controller. I used the AddDbContext function to add the dbcontext class via dependency injection into my controller:
public async Task<Application> GetApplicationById(AntragDBNoInheritanceContext dbContext, int id)
{
List<Application> ApplicationList = await dbContext.Applications.FromSqlRaw("Exec dbo.GetApplication {0}", id).ToListAsync();
Application Application = ApplicationList.First();
if(Application != null)
{
await CategoryFunctions.GetCategoryByApplicationID(Application.Id);
}
}
The GetCategoryByApplicationId function loads the related category of an application which is a many to one relation between Category and Application:
public async Task<Category> GetCategoryByApplicationID(int applicationID)
{
var optionsBuilder = new DbContextOptionsBuilder<AntragDBNoInheritanceContext>();
optionsBuilder.UseSqlServer(ApplicationDBConnection.APPLICATION_CONNECTION);
using (var dbContext = new AntragDBNoInheritanceContext(optionsBuilder.Options))
{
List<Category> category = await dbContext.Categories.FromSqlRaw("Exec GetApplicationCategory {0}", applicationID).ToListAsync();
if (category.Any())
{
return category.First();
}
}
return null;
}
When I want to retrieve an application then the field Category is not set. When I did not use async/await it would set the category automatically for me. Of course I could just return the Category Object from the GetCategoryByApplicationId and then say:
Application.Category = RetrievedFromDbCategory;
But this seems a bit unmaintainable compared to the previous behaviour. Why does this happen now and can I do something about it? Otherwise I don't see much benefits on using async/await .

If criteria not met redirect to different Razor Page from public async Task OnGetAsync()

I am new to the "async" and "task" stuff.
I can't seem to get working a simple if{} else{} inside the OnGetAsync().
public async Task OnGetAsync()
{
if (HttpContext.Session.GetString("LoggedStatus") != null)
{
//KEEP GOING
Accounts = await _context.Accounts.ToListAsync();
}
else
{
RedirectToPage("./Index");
}
}
The error I get is from the Accounts page, which I am trying to avoid even going near by using the "RedirectToPage("./Index")" which is my Home page.
I tried putting "return" word in front of RedirectToPage but it turns red when I do that. Also, if first condition is met (there is a value in the Session object) the Accounts pages shows up with no errors. So, I'm pretty sure the problem is in my attempt to redirect in the "else" statment.
NullReferenceException: Object reference not set to an instance of an object.
OESAC.Pages.Accounts.Pages_Accounts_Index.ExecuteAsync() in Index.cshtml
+
#foreach (var item in Model.Accounts)
The error above is in Accounts right where it loops thru and displays rows.
I'm not sure why it even gets to the Accounts.chstml.
You need to use Task<IActionResult> in public async Task<IActionResult> OnGetAsync(), combined with a return statement.
public async Task<IActionResult> OnGetAsync()
{
if (HttpContext.Session.GetString("LoggedStatus") != null)
{
//KEEP GOING
Accounts = await _context.Accounts.ToListAsync();
return Page();
}
else
{
return RedirectToPage("./Index");
}
}
Microsoft's docs has some good read on this here:
https://learn.microsoft.com/en-us/aspnet/core/razor-pages/?view=aspnetcore-2.2&tabs=visual-studio
Based on a comment, you can run this w/o async.
public IActionResult OnGet()
{
if (HttpContext.Session.GetString("LoggedStatus") != null)
{
//KEEP GOING
Accounts = _context.Accounts.ToList();
return Page();
}
else
{
return RedirectToPage("./Index");
}
}

Asp.net core Custom routing

I am trying to implement custom routing on an asp.net core application.
The desired result is the following:
http://Site_URL/MyController/Action/{Entity_SEO_Name}/
Entity_SEO_Name parameter will be a unique value saved into the database that it is going to help me identify the id of the entity that I am trying to display.
In order to achieve that I have implemented a custom route:
routes.MapMyCustomRoute(
name: "DoctorDetails",
template: " {controller=MyController}/{action=TestRoute}/{name?}");
public class MyTemplateRoute : TemplateRoute
{
public override async Task RouteAsync(RouteContext context)
{
//context.RouteData.Values are always empty. Here is the problem.
var seo_name = context.RouteData.Values["Entity_SEO_Name"];
int entityId = 0;
if (seo_name != null)
{
entityId = GetEntityIdFromDB(seo_name);
}
//Here i need to have the id and pass it to controller
context.RouteData.Values["id"] = entityId;
await base.RouteAsync(context);
}
}
My controller actionresult:
public ActionResult TestRoute(int id)
{
var entity = GetEntityById(id);
return Content("");
}
The problem with this approach is that the context.RouteData.Values are always empty.
Any ideas on how to move forward with this one ?
Your solution too complicated. You can have route template like
template: "{controller=Home}/{action=Index}/{seo?}"
and controller action just like
public ActionResult TestRoute(string seo)
{
var entity = GetEntityBySeo(seo);
return Content("");
}
It is enough, asp.net mvc is smart enough to bind seo variable to the parameter from url path.

Looking for best practice to handle conditional logic inside controller actions in asp.net mvc

Currently I am looking for best practice in handling conditions inside the controller actions in asp.net mvc. For example -
public ActionResult Edit(int Id = 0)
{
var Item = _todoListItemsRepository.Find(Id);
**if (Item == null)
return View("NotFound");
if (!Item.IsAuthorized())
return View("NotValidOwner");**
return View("Edit", Item);
}
The above two conditions marked in bold is used in other actions inside the controller. So, in order not to repeat these conditions in all the actions. I have used the below approach.
[HttpGet]
[Authorize]
[ModelStatusActionFilter]
public ActionResult Edit(int Id = 0)
{
var Item = _todoListItemsRepository.Find(Id);
return View("Edit", Item);
}
public class ModelStatusActionFilterAttribute : ActionFilterAttribute
{
private readonly ITodoListItemsRepository _todoListItemsRepository;
public ModelStatusActionFilterAttribute()
: this(new TodoListItemsRepository())
{
}
public ModelStatusActionFilterAttribute(ITodoListItemsRepository todoListItemsRepository)
{
_todoListItemsRepository = todoListItemsRepository;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
try
{
var Id = Convert.ToInt32(filterContext.RouteData.Values["Id"]);
var Item = _todoListItemsRepository.Find(Id);
if (Item == null)
{
filterContext.Result = new ViewResult() { ViewName = "NotFound" };
}
else if (!Item.IsAuthorized())
{
filterContext.Result = new ViewResult() { ViewName = "NotValidOwner" };
}
}
catch
{
}
}
}
I am unsure if this is the best practice in handling such scenarios. So, could someone please advise ?
Regards,
Ram
usually you don't use action filter for so-called business logic of your web application - this is what the controllers are for. Action filter are rather for the whole stuff which is external to the actual logic - common case is logging, performance measurement, checking if user is authenticated / authorized (I don't think this is your case, although you call IsAuthorized method on the "Item").
Reducing code is generally good thing but in this case, I don't think putting the logic to action is a good way, because you;ve actually made it a bit unreadable, and unreadable code is in my opinon much worse than repeated code.
Also, specifically in your case, for all valid items you actually call the _todoListItemsRepository.Find() twice (for each valid item), which might be costly if this is some webservice call or db lookup.
If the code is just repeated throughout the actions, make a method out of it like:
private View ValidateItem(Item) {
if (Item == null)
return View("NotFound");
if (!Item.IsAuthorized())
return View("NotValidOwner");
return null; }

ASP.NET MVC , proper way to persist dynamic selectList?

I am learning on MVC4+EF 5.0 project, i using VS2012 default template to create blank project and scaffolding the database to *.edmx model, and a edit view which use for edit a staff working on which company.
I experience a problem is maintenance the SelectList in edit view(Dropdown) when user fail input and return to it.
The DropDownList bind the ListItem from controller:
Edit.cshtml
#Html.DropDownListFor(model => model.CompanyID, (SelectList)ViewData["CompanySelectList"])
MemberController.cs
[HttpGet]
public ActionResult Edit(int SelectedCompanyID = 0, int StaffID = 0)
{
IQueryable<company_benefit> companys = from c in db.company where c.ID.Equals(CompanyID) select c ;
ViewData["CompanySelectList"] = new SelectList(companys, "ID", "Name", SelectedCompanyID);
staff s = db.staff.Find(StaffID);
if (s == null)
{
return HttpNotFound();
}
return View(s);
}
[HttpPost]
public ActionResult Edit(staff s)
{
if (ModelState.IsValid)
{
db.Entry(s).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index"); //Edit Success
}
return View(s); //Edit Fail
}
If someone submit the form with invalid data resulting fail input, It will return the view.
However, the SelectList is bind from ViewData, so the ViewData will gone when the page is loaded, and it is behavior of viewdata ,and i change to TempData is not help too.
So do i need build the SelectList again when Post to Edit Action?
I concern to use session to store that, but afriad to break to MVC design pattern.
My english is not good, sorry for confusion.
Thanks you.
A quick solution is In your http post method for edit again create your view data
[HttpPost]
public ActionResult Edit(staff s)
{
if (ModelState.IsValid)
{
db.Entry(s).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index"); //Edit Success
}
IQueryable<company_benefit> companys = from c in db.company where c.ID.Equals(CompanyID) select c ;
ViewData["CompanySelectList"] = new SelectList(companys, "ID", "Name", SelectedCompanyID);
return View(s); //Edit Fail
}
What you are doing is basically saying that when you return from your edit view to the server then server should rebuild view data and call the same view so it can populate the list.
There is a better way where you can create a model which includes both your current model and a list<companys> companies = new list<companys>(); and then populate it again from database. Again the concept is same just using strongly typed model.