Good evening everyone! I have been following along with a tutorial to learn how to program in .NET. The tutorial goes over how to add an item but it doesn't cover deletion. Here is my code:
Service Layer
public async Task Delete(int locationId)
{
var location = _context.Locations.Where(l => l.Id == locationId);
_context.Remove(location);
await _context.SaveChangesAsync();
}
Controller
public IActionResult Delete(int id)
{
_locationService.Delete(id);
return RedirectToAction("Index", "Location");
}
View
<table class="table table-hover" id="locationIndexTable">
<thead>
<tr>
<th>Name</th>
<th>Address</th>
<th>Phone Number</th>
<th>Delete Location</th>
</tr>
</thead>
<tbody>
#foreach (var location in Model.LocationList)
{
<tr>
<td>
<a asp-controller="Location" asp-action="Detail" asp-route-id="#location.Id">
#location.Name
</a>
</td>
<td>
#location.Address
</td>
<td>
#location.PhoneNumber
</td>
<td>
<a asp-controller="Location" asp-action="Delete" asp-route-id="#location.Id" class="btn btn-sm btn-primary">TODO Delete</a>
</td>
</tr>
}
</tbody>
My button clicks through and redirects back to my location index view. However it does not delete the location from the database as intended. I know the issue must lie with the controller.
In the off chance that this will help someone in the future here is what I changed my code to to get it to work:
Service Layer
public async Task Delete(int locationId)
{
var location = _context.Locations.SingleOrDefault(l => l.Id == locationId);
_context.Remove(location);
await _context.SaveChangesAsync();
}
Controller
public async Task <IActionResult> Delete(int id)
{
await _locationService.Delete(id);
return RedirectToAction("Index", "Location");
}
The problem is because of the where method! It returns a collection and EF's Remove method does not complain about it as it takes an object. Try changing that with FirstOrDefault or SingleOrDefault or the Find method and you will be good to go.
public async Task Delete(int locationId)
{
var location = _context.Locations.FirstOrDefault(l => l.Id == locationId);
_context.Remove(location);
await _context.SaveChangesAsync();
}
However if you tried using the Generic version, you would surely notice the problem immediately. A better way would be to to:
//EF Core
var item = await dbContext.FindAsync<Location>(locationId);
dbContext.Remove<Location>(item);
await dbContext.SaveChangesAsync();
Related
I want to create a view to return a list of all user with their role. The following is my controller code:
public async Task<ActionResult> Index()
{
var model = await _userManager.Users
.Select(u => new UserViewModel()
{
Id = u.Id,
UserName = u.UserName,
Email = u.Email,
FullName = u.FullName,
Roles = _userManager.GetRolesAsync(u).Result
})
.ToListAsync();
//var model = _context.Users.Include(u => u.Roles).ToList();
return View(model);
}
My view model
public class UserViewModel
{
public string? Id { get; set; }
public string? UserName { get; set; }
[EmailAddress]
public string? Email { get; set; }
[StringLength(50)]
[Display(Name = "Full Name")]
public string? FullName { get; set; }
public IList<string>? Roles { get; set; }
}
My view
#model IEnumerable<WebApp.ViewModels.UserViewModel>
#{
ViewData["Title"] = "Index";
int i = 0, j = 0;
}
<h1>Index</h1>
<a asp-area="Admin" asp-controller="Account" asp-action="Create">Create account</a>
<a asp-area="Admin" asp-controller="Account" asp-action="ResetPwd">Reset Password</a>
<table class="table table-striped">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">UserName</th>
<th scope="col">Full Name</th>
<th scope="col">Roles</th>
<th scope="col">Action</th>
</tr>
</thead>
<tbody>
#foreach (var user in Model)
{
{ i++; j++; }
<tr>
<th scope="row">#i</th>
<td>#user.UserName</td>
<td>#user.FullName</td>
<td>
#string.Join(", ", user.Roles)
</td>
<td class="dropdown">
<button class="btn btn-primary dropdown-toggle" type="button" id="actions-dropdown-#j" data-bs-toggle="dropdown"
aria-haspopup="true" aria-expanded="false">
Actions
</button>
<div class="dropdown-menu dropdown-menu-end bg-light" aria-labelledby="actions-dropdown-#j">
<a class="dropdown-item text-danger" asp-area="Admin" asp-controller="Account" asp-action="ResetPwd" asp-route-id="#user.Email">
Reset Password
</a>
</div>
</td>
</tr>
}
</tbody>
</table>
The following is Program.cs code:
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using WebApp.Data;
using WebApp.Models;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddControllersWithViews();
var app = builder.Build();
using (var scope = app.Services.CreateScope())
{
var services = scope.ServiceProvider;
await SeedData.Initializer(services, app.Configuration);
}
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
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 =>
{
// In general, routes with areas should be placed earlier in the route table
// as they're more specific than routes without an area.
endpoints.MapControllerRoute(
name: "areas",
pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}");
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
app.MapRazorPages();
app.Run();
This code is work on local, but when I publish to Azure, an exception occur. I found the problem is related to Roles = _userManager.GetRolesAsync(u).Result of my controller (I posted above). If I remove this line, the exception is disappear.
In ASP.NET Framework, by working with property Roles of 'Users', I can access the user roles easily, but in ASP.NET Core this property is hidden. So, could everyone know a convenient way to resole my problem?
I read the book 'Pro Entity Framework Core 2 for ASP.NET MVC'. I'm currently at the beginning of chapter 12 and have some problems. I have this controller:
namespace DataApp.Controllers
{
public class HomeController : Controller
{
private IDataRepository repository;
public HomeController(IDataRepository repo)
{
repository = repo;
}
public IActionResult Index()
{
return View(repository.GetAllProducts());
}
public IActionResult Create()
{
ViewBag.CreateMode = true;
return View("Editor", new Product());
}
[HttpPost]
public IActionResult Create(Product product)
{
repository.CreateProduct(product);
return RedirectToAction(nameof(Index));
}
public IActionResult Edit(long id)
{
ViewBag.CreateMode = false;
return View("Editor", repository.GetProduct(id));
}
[HttpPost]
public IActionResult Edit(Product product)
{
repository.UpdateProduct(product);
return RedirectToAction(nameof(Index));
}
[HttpPost]
public IActionResult Delete(long id)
{
repository.DeleteProduct(id);
return RedirectToAction(nameof(Index));
}
}
}
The index view looks like this:
#model IEnumerable<DataApp.Models.Product>
#{
ViewData["Title"] = "Products";
Layout = "_Layout";
}
<table class="table table-sm table-striped">
<thead>
<tr><th>ID</th><th>Name</th><th>Category</th><th>Price</th></tr>
</thead>
<tbody>
#foreach (var p in Model)
{
<tr>
<td>#p.Id</td>
<td>#p.Name</td>
<td>#p.Category</td>
<td>$#p.Price.ToString("F2")</td>
<td>
<form asp-action="Delete" method="post">
<a asp-action="Edit"
class="btn btn-sm btn-warning" asp-route-id="#p.Id">
Edit
</a>
<input type="hidden" name="id" value="#p.Id" />
<button type="submit" class="btn btn-danger btn-sm">
Delete
</button>
</form>
</td>
</tr>
}
</tbody>
</table>
<a asp-action="Create" class="btn btn-primary">Create New Product</a>
If I run the application and click the Edit or Create button, I don't get the Editor view. If I navigate in the browser to /Home/Edit, then the view is shown. What can be the problem?
You can find the complete source code for this chapter here:
Chapter 2
Please note, that I'm at the beginning of the chapter and the files in the source code may contain more, that I currently have, but according to the book, it should work on this stage too.
I have this controller which creates a List where T is a class model called GamingEvents.
public async Task<IActionResult> Index(DateTime start, DateTime end)
{
List<GamingEvents> gamingEventsListings = await sg.GenerateGameEventsSchedule();
ViewData["RangeStart"] = start;
ViewData["RangeEnd"] = end;
return View(gamingEventsListings);
}
In my view I generate this table to display the data:
#model List<GameManager.Models.GamingEvents>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.GameId)
</td>
<td>
#Html.DisplayFor(modelItem => item.GameName)
</td>
<td>
#Html.DisplayFor(modelItem => item.DayNames)
</td>
</tr>
}
Now, I want to send all this data to another controller. So I made this form:
<form asp-controller="Scheduling" asp-action="ScheduleBlock" method="post">
<fieldset>
<button formaction="/Scheduling/ScheduleBlock/">Schedule Games</button>
</fieldset>
</form>
So I need the method, GenerateGameEventsFromSchedule(), to accept the two data properties I pass to the view, ViewData["RangeStart"] and ViewData["RangeEnd"].
So I started to write the controller:
[HttpPost]
public async Task<IActionResult> GenerateGameEventsFromSchedule(DateTime start, DateTime end)
{
foreach (event in GamingEvents)
{
//...do this
}
return View();
}
Obviously it's not finished.
My problem is, how would I pass the list of GamingEvents from my view above, to this new controller so that I can do additional processing on each item in the list?
Thanks!
So I have I have a simple structure where one purchase have a collection of expenses, and each expense have an account(plastic, cash, plastic#2...).
So the json my api gets is similar to this:
[
{"$id":"1","Id":1,"Name":"apple","Value":100.0,"AccountId":1,"Account":
{"$id":"2","Id":1,"Name":"Cash"}},
{"$id":"3","Id":2,"Name":"pear","Value":50.0,"AccountId":1,"Account":
{"$ref":"2"}},
{"$id":"4","Id":3,"Name":"raspberry","Value":10.0,"AccountId":1,"Account":
{"$ref":"2"}}
]
I see my json is not writing my cash account each time it needs it, it is refering it with
{"$ref":"2"}
where
{"$id":"2","Id":1,"Name":"Cash"}
so when I render my table with this html:
<table>
<tbody data-bind="foreach: gastos">
<tr>
<td data-bind="text: $data.id"></td>
<td data-bind="text: $data.name"></td>
<td data-bind="text: $data.value"></td>
<td data-bind="text: $data.account.Name"></td>
<td>
<button type="button" class="btn btn-xs">
<i class="glyphicon glyphicon-trash"></i>
</button>
</td>
</tr>
</tbody>
</table>
I get this, because the account for pear, and raspberry are nulls:
So how do you handle $ref in knockout?
I am mapping to 'gastos' this way:
$.getJSON('#ViewBag.GastosUrl', function (data) {
data.forEach(function(o) {
gastos.push(new gastoVM(o.Id, o.Name, o.Value, o.Account));
});
});
var gastoVM = function(Id, Name, Value, Account) {
var self = this;
self.id = Id;
self.name = Name;
self.value = Value;
self.account = Account;
};
thanks.
I'm not familiar with entity-framework but with the data as provided, a couple options available (JSFiddle):
Build up the account information alongside the gastos. And only provide the $id or $ref for later referencing.
var vm = {
gastos: [],
accounts: {},
accountLookup: function(accountId){
return this.accounts[accountId];
}
}
//... inside AJAX call
var accountId = o.Account["$id"]
if(accountId)
{
vm.accounts[accountId] = o.Account;
}
Use a ko utility method to lookup the account from within your array.
accountLookupByUtil: function(accountId) {
var gasto = ko.utils.arrayFirst(this.gastos, function(item) {
if(item.account['$id'] == accountId)
{
return item
}
});
return gasto.account;
}
From the html:
<td data-bind="text: $root.accountLookup($data.accountId).Name"></td>
<td data-bind="text: $root.accountLookupByUtil($data.accountId).Name"></td>
Note: Both methods are available in the fiddle, thus some properties are provided that would not be necessary depending upon the method used.
ı am using a view in ajax begin form. ı search one thing and result is correct then alert not render the partial view. but it isnt render correct view a blank page and a see my partial view. thanks
my view
#using (Ajax.BeginForm("AjazKullanici", new AjaxOptions { UpdateTargetId = "trBilgiler", HttpMethod = "Post" }))
{
<tr>
<td style="width: 20%">
T.C. Kimlik No :
</td>
<th align="left">
#Html.TextBoxFor(model => model.TcNo)
#Html.ValidationMessageFor(model => model.TcNo)
<input type="submit" id="btnBilgiGetir" value="Bilgi Getir" class="Button" width="75px" />
</th>
</tr>
}
<tr id="trBilgiler">
#{Html.RenderPartial("BilgilerKullanici");}
</tr>
my controller
public ActionResult AjazKullanici()
{
ViewData["dropGizliSoru"] = _db.EHASTANEGIZLISORUs;
return View();
}
[HttpPost]
public PartialViewResult AjazKullanici(YeniKullaniciModel model)
{
if (model.TcNo != null)
{
var userKontrol = _db.KULLANICIBILGILERIs.Where(x => x.KULLANICIKOD == model.TcNo);
if (userKontrol.Any())
{
Response.Write("<script langauge='javascript'>alert('Girmiş Olduğunuz T.C. Kimlik Numarasına Ait Kullanıcı Kaydı Vardır.')</script>");
return PartialView();
}
else
{
return PartialView("BilgilerKullanici",model);
}
}
return PartialView();
}