I have two methods that use different viewmodels but are the same logic. At the moment I have copied and pasted them into their respective controllers. Any way to share these methods somehow?
Song Controller:
public JsonResult IncrementViews(int id)
{
using (ApplicationDbContext db = new ApplicationDbContext())
{
PublishedSongViewModel song = db.PublishedSongs.Single(x => x.Id == id);
song.UniquePlayCounts++;
db.SaveChanges();
return Json(new { UniquePlayCounts = song.UniquePlayCounts }, JsonRequestBehavior.AllowGet);
}
}
Station Controller:
public JsonResult IncrementViews(int id)
{
using (ApplicationDbContext db = new ApplicationDbContext())
{
RadioStationViewModel station = db.RadioStations.Single(x => x.Id == id);
station.UniquePlayCounts++;
db.SaveChanges();
return Json(new { UniquePlayCounts = station.UniquePlayCounts }, JsonRequestBehavior.AllowGet);
}
}
Edit:
class so far:
public static IEnumerable<Type> GetElements(ApplicationDbContext db, Type type)
{
if (type == typeof(SongsController))
return (IEnumerable<Type>)db.PublishedSongs;
else if (type == typeof(RadioStationsController))
return (IEnumerable<Type>)db.RadioStations;
else
throw new Exception("Controller not found, DBHelper");
}
Create a class called BasicController and add the method to it, like this:
public class BasicController {
public JsonResult IncrementViews(int id)
{
using (ApplicationDbContext db = new ApplicationDbContext())
{
var element = DBHelper.GetElements(db, this.GetType()).Single(x => x.Id == id);
element.UniquePlayCounts++;
db.SaveChanges();
return Json(new { UniquePlayCounts = song.UniquePlayCounts }, JsonRequestBehavior.AllowGet);
}
}
}
and modify your classes to inherit from BasicController. You will also have to create the DBHelper class with the GetElements method, which gathers the IEnumerable elements from db based on type.
EDIT: This is how you can create a helper:
public class DBHelper {
public static IEnumerable GetElements(ApplicationDbContext db, System.Type type) {
if (type == typeof(SongController)) {
return db.PublishedSongs;
} else if (type == typeof(StationController)) {
return db.RadioStations;
}
}
}
Related
I have a problem saving the changes to Database Context. When i don't save i can see that the listing status is successfully changed , but when i try to save it I get an error which is saying : " There is already an open DataReader associated with this Connection which must be closed first." And i don't know where that comes from. When i try to do it asynchronous i get the same error.
AdministratorController.cs
[Route("/Admin")]
[ApiController]
public class AdministratorController : Controller
{
private readonly dbContext _dbContext;
public AdministratorController(dbContext dbContext)
{
_dbContext = dbContext;
}
///////////////////////////////////
/// //
/// Accept or Reject Listings //
/// //
//////////////////////////////////
[HttpPost]
[Route("acceptListing/{listingId}")]
[AllowAnonymous]
//[Authorize(Roles="Administrator")]
public ActionResult AcceptList([FromRoute]int listingId)
{
if (!ModelState.IsValid)
{
return BadRequest();
}
if (listingId == null)
{
return NotFound("Listing not found.");
}
foreach (Listing listing in _dbContext.Listings)
{
Console.WriteLine(listing.Status);
if(listing.Id == listingId)
{
if(listing.Status == ListingStatus.Accepted)
{
return BadRequest("Listing already accepted.");
}
else
{
listing.Status = ListingStatus.Accepted;
Console.WriteLine(listing.Status);
_dbContext.SaveChanges();
}
return Ok();
}
}
return BadRequest("Couldn't find right listing.");
}
Rather than looping through all listings looking for the one with the Id you want, just filter and get.
Listing? listing = _dbContext.Listings.FirstOrDefault(l => l.Id == listingId);
if (listing is null)
{
return BadRequest("Couldn't find right listing.");
}
if(listing.Status == ListingStatus.Accepted)
{
return BadRequest("Listing already accepted.");
}
listing.Status = ListingStatus.Accepted;
Console.WriteLine(listing.Status);
_dbContext.SaveChanges();
return Ok();
The problem here is that you are iterating the data being fetched from database and in the same time you are trying to save something from the same context. Quick fix is to use ToList in foreach:
foreach (Listing listing in _dbContext.Listings.ToList())
{
// ..
}
But in general you should not fetch everything from the database to process only one item. Just write query that will filter everything on database side. Something along this lines (not tested):
var listing = _dbContext.Listings.FirstOrDefault(l => l.Id == listingId);
if (listing is null)
{
return NotFound();
}
if (listing.Status == ListingStatus.Accepted)
{
return BadRequest("Listing already accepted.");
}
else
{
listing.Status = ListingStatus.Accepted;
Console.WriteLine(listing.Status);
_dbContext.SaveChanges();
}
return Ok();
change the code like this :
var listings = _dbContext.Listings.Tolist();
foreach (Listing listing in listings)
{
Console.WriteLine(listing.Status);
if(listing.Id == listingId)
{
if(listing.Status == ListingStatus.Accepted)
{
return BadRequest("Listing already accepted.");
}
else
{
listing.Status = ListingStatus.Accepted;
Console.WriteLine(listing.Status);
_dbContext.Update(listing);
_dbContext.SaveChanges();
}
return Ok();
}
}
I'm learning asp.net WebApi and EFCore (CodeFirst) and as an exercise, I'm building Warehouse Api and my update method doesn't work.
This is my repository code:
public void Update(T toUpdate)
{
if(toUpdate == null) throw new ArgumentNullException("Entity is null");
T tmp = GetById(toUpdate.Id);
tmp = toUpdate;
_context.SaveChanges();
}
and this is my Service code:
public void UpdateEmployee(UpdateEmployeeCommand command)
{
UpdateEmployeeCommandValidator validator = new UpdateEmployeeCommandValidator();
var results = validator.Validate(command);
if (!results.IsValid)
{
throw new CommandValidationException(results.Errors.Select(x => new CommandValidationError
{
ErrorCode = x.ErrorCode,
ErrorMessage = x.ErrorMessage,
PropertyName = x.PropertyName
}));
}
_repository.Update(new Employee()
{
Id = command.Id,
FirstName = command.FirstName,
Address = command.Address,
LastName = command.LastName,
Age = command.Age,
Email = command.Email,
PhoneNumber = command.PhoneNumber
});
}
and this is how I use it in Controller:
public ActionResult UpdateEmployee(int Id, UpdateEmployeeCommand command)
{
if(Id != command.Id)
{
return BadRequest();
}
var employeeModelFromRepo = _repository.GetById(Id);
if(employeeModelFromRepo == null)
{
return NotFound();
}
_employeeService.UpdateEmployee(command);
return NoContent();
}
When I call UpdateEmployee, it runs without any error but it doesn't update my database.
I'm new to this, so this might be an easy fix.
I am using this generic update function:
public virtual T Update(T t) where T : class, IBaseEntity // contains Id as primary key
{
if (t == null)
return null;
var exist = Context.Set<T>().Find(t);
// Or you can try
var exist = Context.Set<T>()
.Where(i=>i.Id=t.Id).FirstOrdDefault();
if (exist == null) return exist;
Context.Entry(exist).CurrentValues.SetValues(t);
Context.SaveChanges();
return exist;
}
Don't you forget to call your service method in the controller end-point? UpdateEmployee()
Commented solution works, i just had to add db.Entry(tmp).CurrentValues.SetValues(toUpdate) to the repository code.
This is my first question here so I'm really open for opinions, I searched a lot about ASP.NET Core MVC and still I don't have enough answers if I'm writing code in right way.
In many tutorials on Youtube I saw people create ASP.NET Core MVC applications with CRUD operations but there were just simple without any logic and all code was in controllers.
What if I want add some logic for example checking if my record already exists in the database? Where should I put this?
I have class Patient and I want add patient to database so I got in patient controller this :
public async Task<IActionResult> Create([Bind("PatientId,Name,Surname,Pesel")] Patient patient)
{
if (ModelState.IsValid)
{
String result = facade.Add_patient(patient);
if (result == "Patient added")
{
_context.Add(patient);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
else
{
ViewBag.Message = "Patient exists";
return View();
}
}
}
Facade is my class in model folder where I have this :
public class Facade
{
private Database_controller _context;
public Facade(Database_controller context)
{
_context = context;
}
private List<Patient> patients = new List<Patient>();
public List<Patient> Patients { get => patients; set => patients = value; }
public void updatedata()
{
patients = _context.Patients.ToList();
}
public string Add_patient(Patient patient)
{
Patient Patient = new Patient();
Patient.Name = patient.Name;
Patient.Surname = patient.Surname;
Patient.Pesel = patient.Pesel;
String if_is = addpacjent(Patient);
if (!"Is".Equals(if_is))
{
return "Patient added";
}
else
{
return "Patient exists";
}
}
public String addpacjent(Patient val)
{
bool if_is = patients.Contains(val);
if (if_is == true)
{
return ("Is");
}
else
{
patients.Add(val);
return null;
}
}
}
In Patient class is override method equals for checking
public override bool Equals(Object ob)
{
String Name = Surname;
String Name2 = ((Patient)ob).Surname;
String Pesel1 = Pesel;
String Pesel2 = ((Patient)ob).Pesel;
bool a = Name.Equals(Name2);
if (Pesel2 != "0")
{
bool b = Pesel1.Equals(Pesel2);
bool c = false;
if (a && b == true)
{
c = true;
}
return c;
}
else
return a;
}
Is it the right way? Should I have method _context.Add(patient) in Facade or controller? Where should I check existence in database?
I already have application which I want write in .NET Core in Winforms so I want use as many as possible code from Winforms classes so it's why I started coding like this in ASP.NET Core MVC
The easiest way is to use Any(),this is because Any() will return as soon as it finds a match.
Like my following:
public IActionResult Test()
{
//Simulation data:
var patient = new Patient
{
Name="AA",
SumName="AA-aa",
Pesel="23"
};
//Here you can add your conditions.
if (!_context.Patients.Any(o => o.Name == patient.Name&&o.SumName==patient.SumName&&o.Pesel==patient.Pesel))
{
_context.Add(patient);
_context.SaveChanges();
return RedirectToAction(nameof(Index));
}
else
{
ViewBag.Message = "Patient exists";
return View();
};
}
Below is the demo data in my database:
Sample effect display:
I am trying to flush my cache after I update an item, and I have tried a few different options and none are working as expected
public class PostApiController : Controller
{
private readonly IPostService _postService;
private readonly IPostTagService _postTagService;
private IMemoryCache _cache;
private MemoryCacheEntryOptions cacheEntryOptions;
public PostApiController(IPostService postService, IPostTagService postTagService, IMemoryCache cache)
{
_postService = postService;
_postTagService = postTagService;
_cache = cache;
cacheEntryOptions = new MemoryCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromDays(1));
}
[HttpGet("{url}", Name = "GetPost")]
public IActionResult GetById(string url, bool includeExcerpt)
{
Post cacheEntry;
if (!_cache.TryGetValue($"GetById{url}{includeExcerpt}", out cacheEntry))
{
cacheEntry = _postService.GetByUrl(url, includeExcerpt);
_cache.Set($"GetById{url}{includeExcerpt}", cacheEntry, cacheEntryOptions);
}
if (cacheEntry == null)
{
return NotFound();
}
return new ObjectResult(cacheEntry);
}
[HttpPut("{id}")]
public IActionResult Update(int id, [FromBody] Post item)
{
if (item == null)
{
return BadRequest();
}
var todo = _postService.GetById(id);
if (todo == null)
{
return NotFound();
}
_postService.Update(item);
_postTagService.Sync(item.Tags.Select(a => new PostTag { PostId = item.Id, TagId = a.Id }).ToList());
//Want to flush entire cache here
return new NoContentResult();
}
I have tried to Dispose() MemoryCache here but on next Api call, it is still disposed. Since the keys are somewhat dynamic, I can't just get the keys. How can I go about doing this?
You can store the dictionary instead. This way you can use the dynamic keys for entries and one single static key for the dictionary container, which can be stored in the cache instead of storing each entry separately.
Something like that:
private const string CachedEntriesKey = "SOME-STATIC-KEY";
[HttpGet("{url}", Name = "GetPost")]
public IActionResult GetById(string url, bool includeExcerpt)
{
Dictionary<string, Post> cacheEntries;
if (!_cache.TryGetValue(CachedEntriesKey, out cacheEntries))
{
cacheEntries = new Dictionary<string, Post>();
_cache.Set(CachedEntriesKey, cacheEntries);
}
var entryKey = $"GetById{url}{includeExcerpt}";
if (!cacheEntries.ContainsKey(entryKey))
{
return NotFound(); // by the way, why you do that instead of adding to the cache?
}
return new ObjectResult(cacheEntries[entryKey]);
}
I have an Index Method in my FileController that can return a file that is stored in the Attachments entity. How can I change the method to work with any entity not just the Attachments?
public class FileController : Controller
{
private MainDatabaseContext db = new MainDatabaseContext();
// GET: File
public ActionResult Index(int id)
{
var fileToRetrieve = db.Attachments.Find(id);
var FileObject= File (fileToRetrieve.AttachmentObject, fileToRetrieve.ContentType);
if (FileObject.FileDownloadName.Length == 0)
{
FileObject.FileDownloadName = fileToRetrieve.Filename;
}
return FileObject;
}
}
This is what I have done as a workaround, but it has a lot of repeated code which I wanted to avoid:
public class FileController : Controller
{
private MainDatabaseContext db = new MainDatabaseContext();
enum EntityName
{
Attachment=1,
WAProgramApplicationId,
HouseholdIncome,
HouseholdMember
}
// GET: File
public ActionResult Index(int id=0,int WAProgramApplicationId=0,int householdIncomeID=0,int householdMemberId=0)
{
if (householdIncomeID!=0)
{
return GetFileObject(householdIncomeID, EntityName.HouseholdIncome);
}
if (id!=0)
{
return GetFileObject(id, EntityName.Attachment);
}
if (WAProgramApplicationId != 0)
{
return GetFileObject(WAProgramApplicationId, EntityName.WAProgramApplicationId);
}
if (householdMemberId!=0)
{
return GetFileObject(householdMemberId, EntityName.HouseholdMember);
}
return null;
}
private ActionResult GetFileObject(int id, EntityName entityName)
{
if (entityName==EntityName.Attachment)
{
var fileToRetrieve = db.Attachments.Find(id);
var FileObject = File(fileToRetrieve.AttachmentObject, fileToRetrieve.ContentType);
if (FileObject.FileDownloadName.Length == 0)
{
FileObject.FileDownloadName = fileToRetrieve.Filename;
}
return FileObject;
}
if (entityName == EntityName.HouseholdIncome)
{
var fileToRetrieve = db.HouseholdIncomes.Find(id);
var FileObject = File(fileToRetrieve.AttachmentObject, fileToRetrieve.ContentType);
if (FileObject.FileDownloadName.Length == 0)
{
FileObject.FileDownloadName = fileToRetrieve.Filename;
}
return FileObject;
}
if (entityName==EntityName.WAProgramApplicationId)
{
var fileToRetrieve = db.WAProgramApplications.Find(id);
var FileObject = File(fileToRetrieve.AttachmentObject, fileToRetrieve.ContentType);
if (FileObject.FileDownloadName.Length == 0)
{
FileObject.FileDownloadName = fileToRetrieve.Filename;
}
return FileObject;
}
if (entityName==EntityName.HouseholdMember)
{
var fileToRetrieve = db.HouseholdMembers.Find(id);
var FileObject = File(fileToRetrieve.AttachmentObject, fileToRetrieve.ContentType);
if (FileObject.FileDownloadName.Length == 0)
{
FileObject.FileDownloadName = fileToRetrieve.Filename;
}
return FileObject;
}
return null;
}
}
to make the entity more generic just use the Set method, you can do it like this:
db.Set<YourEntity>().Find(id);
They are some approaches how to implement a generic controller with data.
Check here and here.
If you have a repository that can handle _repository.get<T> and return the right object, you can achieve this.