Show multiple Models in same Razor page: OnGet not firing - asp.net-core

On a page in my Razor .Net 6 application I have to show the data of 3 models. So I created a Model EigObjFact, comprising the three needed models:
namespace StallingRazor.Model
{
public class EigObjFact
{
public Eigenaar Eigenaren { get; set; }
public IEnumerable<Model.Object> Objecten { get; set; }
public IEnumerable<Factuur> Facturen { get; set; }
}
}
In the Details Page Controller I call the EigObjFact in OnGet:
namespace StallingRazor.Pages.Eigenaren
{
public class DetailsModel : PageModel
{
private readonly ApplicationDbContext _db;
public DetailsModel(ApplicationDbContext db)
{
_db = db;
}
public async void OnGetAsync(int id)
{
EigObjFact model = new EigObjFact();
model.Eigenaren = _db.Eigenarens.Find(id);
model.Objecten = await _db.Objectens.Where(s => s.EigenarenID == id).ToListAsync();
model.Facturen = await _db.Facturens.Where(x => x.EigenarenID == id).ToListAsync();
}
}
}
The mapping of the 3 models works fine in the Page because I use:
#model StallingRazor.Model.EigObjFact
Problem: the OnGetAsync handler in the Details page never fires, so the model is empty when used in the page.
What am I missing?

The model of razor page needs to be a PageModel type.So you need to replace #model StallingRazor.Model.EigObjFact with #model StallingRazor.Pages.Eigenaren.DetailsModel.
And you need to add a property which type is EigObjFact to DetailsModel,so that you can get EigObjFact model from DetailsModel:
namespace StallingRazor.Pages.Eigenaren
{
public class DetailsModel : PageModel
{
[BindProperty]
public EigObjFact model { get; set; }= new EigObjFact();
private readonly ApplicationDbContext _db;
public DetailsModel(ApplicationDbContext db)
{
_db = db;
}
public async void OnGetAsync(int id)
{
model.Eigenaren = _db.Eigenarens.Find(id);
model.Objecten = await _db.Objectens.Where(s => s.EigenarenID == id).ToListAsync();
model.Facturen = await _db.Facturens.Where(x => x.EigenarenID == id).ToListAsync();
}
}
}
Then if you want to use the data of EigObjFact model in view.you can try to use #Model.model.xxx.

Related

How to create dropdown list in ASP.NET Core?

How to create the dropdown list in one to many relation. I want to populate the category data in Post form and then want to save using POST mode.
Here is my full code:
public class Category
{
public Category()
{
Posts = new Collection<Post>();
}
public int Id{get;set;}
public string Title { get; set; }
}
public class Post
{
public int Id
public string Title { get; set; }
public string Body { get; set; }
public Category Category { get; set; }
public int CategoryId { get; set; }
}
PostFormVM:
public class PostFormVM
{
public int Id { get; set; }
[Required]
public string Title { get; set; }
public string Body { get; set; }
[Required]
public int CategoryId { get; set; }
public IEnumerable<Category> Categories { get; set; }
}
Mapping is here:
public class ApplicationProfile : AutoMapper.Profile
{
public ApplicationProfile()
{
CreateMap<Category, CategoryFormVM>().ReverseMap();
CreateMap<Post, PostFormVM>().ReverseMap();
}
}
Generic Repository implementation
public class GenericRepository<T>:IGenericRepository<T> where T:class
{
private readonly ApplicationDbContext _context;
public GenericRepository(ApplicationDbContextcontext)
{
_context = context;
}
public async Task<List<T>> GetAllAsync()
{
return await _context.Set<T>().ToListAsync();
}
}
ICategoryRepository:
public interface ICategoryRepository:IGenericRepository<Category>
{
}
CategoryRepository implementation
public class CategoryRepository :GenericRepository<Category>, ICategoryRepository
{
public CategoryRepository(ApplicationDbContext context):base(context)
{
}
}
PostRepo Implementation:
public class PostRepository : GenericRepository<Post>, IPostRepository
{
public PostRepository(ApplicationDbContext context) : base(context)
{
}
}
PostController:
public class PostItemController : Controller
{
private readonly IPostRepository _postRepository;
private readonly ICategoryRepository _categoryRepository;
private readonly UserManager<ApplicationUser> _userManager;
private readonly IMapper _mapper;
public PostItemController(IPostRepository postRepository, ICategoryRepository categoryRepository, IMapper mapper, UserManager<ApplicationUser> userManager)
{
_postRepository = postRepository;
_categoryRepository = categoryRepository;
_userManager = userManager;
_mapper = mapper;
}
public IActionResult Create()
{
//Here I want to populate the category data I have used the ViewBag and ViewData here
//I am unable to get the data from the database
ViewBag.Categories= _categoryRepository.GetAllAsync();
return View(new PostFormVM());
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(PostFormVM viewModel)
{
try
{
if (!ModelState.IsValid)
return View("Create", viewModel);
if (ModelState.IsValid) {
//Here I also want to map the selected category item and save to Post table.
var post = _mapper.Map<Post>(viewModel);
post.ApplicationUserId = _userManager.GetUserId(HttpContext.User);
if (viewModel.IsEdit.Equals("false"))
{
await _postRepository.CreateAsync(post);
}
else
{
await _postRepository.UpdateAsync(post);
}
}
}
catch (Exception)
{
}
return RedirectToAction(nameof(Index));
}
I want help to populate the category data in Post Entity Create form.
You can put a breakpoint on this line ViewBag.Categories = _categoryRepository.GetAllAsync();, you can see such a result prompt Result =" {Not yet computed} ", because the method in your generic repository uses the await keyword to operate Asynchronous method, it will wait for the end of the previous process before calculating the result.
Try change you code in Generic Repository like below:
public List<T> GetAllAsync()
{
return _context.Set<T>().ToList();
}
IGenericRepository
public interface IGenericRepository<T> where T : class
{
List<T> GetAllAsync();
}
Show the Category list ,controller
public IActionResult Create()
{
IEnumerable<Category> categories = _categoryRepository.GetAllAsync();
ViewBag.Categories = categories;
return View(new PostFormVM());
}
View
<select asp-for="CategoryId" asp-items="#(new SelectList(ViewBag.Categories,"Id","Title"))"></select>
Result:

Asp.Net Core - How to create object-specific sub-routes

I have a model class Dispute with one-to-many relationships.
I would navigate and perform CRUD operation on its related objects within a specific disputeId.
I would compose the url as follow:
Disputes/Details/(disputeId)/(related_objects)
where related_objects can be, for example, Persons, God, etc.
What kind of approach i can use?
You could use attribute routing to realize the route. You need to pass navigation properties as your relative_objects.Refer to my demo:
1.Model:
public class Dispute
{
[Key]
public int DisputeId { get; set; }
public List<Person> Persons{ get; set; }
}
2.DbContext:
public DbSet<Dispute> Disputes{ get; set; }
public DbSet<Person> Persons{ get; set; }
3.Controller:
[Route("Disputes")]
public class DisputesController : Controller
{
private readonly ApplicationDbContext _context;
public ProductsController(ApplicationDbContext context)
{
_context = context;
}
// GET: Disputes/Details/5/Persons
[Route("Disputes/{disputeId}/{related_objects}")]
public async Task<IActionResult> Details(int? disputeId, string related_objects)
{
if (disputeId== null)
{
return NotFound();
}
var dispute = await _context.Disputes.Include(related_objects)
.FirstOrDefaultAsync(m => m.DisputeId == disputeId);
//other logic
}
}

Call WeatherAPI with HttpClient

I created Web API to receive daily temperature from OpenWeatherAPI.
I put the API call in the MVC project; (plan to create new project later for better microservice architecture.)
Someone had mentioned in the code:
in your HomeController you're attempting to simply just call the action like a method on an instance of WeatherController. You need to use HttpClient there as well. Also, don't new up HttpClient directly. It should be treated as a singleton
How would I conduct this? This is the original code, started programming month ago.
MVC Page:
namespace WeatherPage.Controllers
{
public class HomeController : Controller
{
public WeatherController weathercontroller = new WeatherController();
public IActionResult Index()
{
return View();
}
public Async Task<IActionResult> About()
{
ViewData["Message"] = "Your application description page.";
ViewData["test"] = weathercontroller.City("Seattle");
return View();
}
}
}
API Controller:
[Route("api/[controller]")]
public class WeatherController : ControllerBase
{
[HttpGet("[action]/{city}")]
public async Task<IActionResult> City(string city)
{
Rootobject rawWeather = new Rootobject();
using (var client = new HttpClient())
{
try
{
client.BaseAddress = new Uri("http://api.openweathermap.org");
var response = await client.GetAsync($"/data/2.5/weather?q={city}&appid=APIkey&units=metric");
response.EnsureSuccessStatusCode();
var stringResult = await response.Content.ReadAsStringAsync();
rawWeather = JsonConvert.DeserializeObject<Rootobject>(stringResult);
return Ok(rawWeather);
}
catch (HttpRequestException httpRequestException)
{
return BadRequest($"Error getting weather from OpenWeather: {httpRequestException.Message}");
}
}
}
}
public class Rootobject
{
public Coord coord { get; set; }
public Weather[] weather { get; set; }
public string _base { get; set; }
public Main main { get; set; }
public int visibility { get; set; }
public Wind wind { get; set; }
public Clouds clouds { get; set; }
public int dt { get; set; }
public Sys sys { get; set; }
public int id { get; set; }
public string name { get; set; }
public int cod { get; set; }
}
This works in my project:
https://localhost:55555/api/weather/city/washington
Retrieve Data From Third party Openweather Api
Should We Call Web Api from Mvc Application in Same Solution
It roughly means you should use dependency injection .
Don't create an instance of HttpClient every time when you need it , just ask for an instance of HttpClient instead .
Extract your code of getting weather in the weather controller into a service , and ask for the service both in weather controller api and home controller
The WeatherService :
public interface IWeatherService
{
Task<Rootobject> CityAsync(string city);
}
public class WeatherService : IWeatherService{
private HttpClient _httpClient ;
public WeatherService(IHttpClientFactory clientFactory){
this._httpClient = clientFactory.CreateClient();
}
public async Task<Rootobject> CityAsync(string city){
Rootobject rawWeather = new Rootobject();
this._httpClient.BaseAddress = new Uri("http://api.openweathermap.org");
var response = await this._httpClient.GetAsync($"/data/2.5/weather?q={city}&appid=APIkey&units=metric");
response.EnsureSuccessStatusCode();
var stringResult = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<Rootobject>(stringResult);
}
}
The new WeatherController :
[Route("api/[controller]")]
public class WeatherController : ControllerBase
{
private IWeatherService _weatherService;
public WeatherController(IWeatherService wetherService ){
this._weatherService= wetherService;
}
[HttpGet("[action]/{city}")]
public async Task<IActionResult> City(string city)
{
try
{
var rawWeather=await this._weatherService.CityAsync(city);
return Ok(rawWeather);
}
catch (HttpRequestException httpRequestException)
{
return BadRequest($"Error getting weather from OpenWeather: {httpRequestException.Message}");
}
}
}
The new HomeController:
public class HomeController : Controller
{
private IWeatherService _weatherService;
public HomeController(IWeatherService wetherService ){
this._weatherService= wetherService;
}
public IActionResult Index()
{
return View();
}
public async Task<IActionResult> About()
{
ViewData["Message"] = "Your application description page.";
ViewData["test"] = await this._weatherService.CityAsync("Seattle");
return View();
}
}
The ConfigureServices:
services.AddHttpClient();
services.AddSingleton<IWeatherService ,WeatherService>();

How can I reduce redundancy in my controller?

I use ASP.NET Core for the backend of my Azure App Service app. For each table in the database, I create a controller which acts as an API endpoint for that table. There are no problems with the code but for every Controller, I an repeating the same logic except for the Include(m => m.<Property>). Is there a way I can move all these logic into the parent TableController class and have the Include() method stuff in the model class?
These are some sample files:
Table Controller (parent class for all API controllers):
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
namespace Backend.API
{
public abstract class TableController<T> : Controller
{
// Public Methods
[HttpGet]
[Route("")]
public abstract Task<IActionResult> GetAllAsync();
[HttpPost]
[Route("")]
public abstract Task<IActionResult> CreateAsync([FromBody] T created);
[HttpGet]
[Route("{id}")]
public abstract Task<IActionResult> GetAsync(string id);
[HttpPatch]
[Route("{id}")]
public abstract Task<IActionResult> UpdateAsync(string id, [FromBody] T updated);
[HttpDelete]
[Route("{id}")]
public abstract Task<IActionResult> DeleteAsync(string id);
}
}
A sample Model:
using System;
using System.ComponentModel.DataAnnotations;
namespace Backend.Models.DB
{
public class BlogPost
{
// Public Properties
public DateTime DatePublished { get; set; }
public Guid Id { get; set; }
[Required]
public string Body { get; set; }
[Required]
public string Title { get; set; }
public string DatePublishedString =>
string.Format("Posted on {0}.", DatePublished.ToString().ToLower());
[Required]
public User Publisher { get; set; }
// Constructors
public BlogPost() : this(null, null, null, new DateTime()) { }
public BlogPost(BlogPost post) :
this(post.Title, post.Body, post.Publisher, post.DatePublished) { }
public BlogPost(string title, string body, User publisher, DateTime datePublished)
{
Title = title;
if (datePublished == new DateTime())
DatePublished = DateTime.Now;
else
DatePublished = datePublished;
Body = body;
Publisher = publisher;
}
// Public Methods
public void Update(BlogPost updated)
{
Body = updated.Body;
Title = updated.Title;
}
}
}
A sample Controller:
using System;
using System.Threading.Tasks;
using Backend.Models.DB;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace Backend.API
{
[Route("tables/BlogPost")]
public class BlogPostController : TableController<BlogPost>
{
// Private Properties
private readonly BadmintonClubDBDataContext _db;
// Constructors
public BlogPostController(BadmintonClubDBDataContext db) => _db = db;
// Overridden Methods
public override async Task<IActionResult> GetAllAsync()
{
var posts = await _db.BlogPosts
.Include(bp => bp.Publisher)
.ToArrayAsync();
return Json(posts);
}
public override async Task<IActionResult> CreateAsync([FromBody] BlogPost created)
{
if (!ModelState.IsValid)
return BadRequest();
BlogPost post = (await _db
.AddAsync(new BlogPost(created))).Entity;
await _db.SaveChangesAsync();
return Json(post);
}
public override async Task<IActionResult> GetAsync(string id)
{
BlogPost post = await _db.BlogPosts
.Include(bp => bp.Publisher)
.SingleOrDefaultAsync(bp => bp.Id == new Guid(id));
if (post == null)
return NotFound();
return Json(post);
}
public override async Task<IActionResult> UpdateAsync(string id, [FromBody] BlogPost updated)
{
BlogPost post = await _db.BlogPosts
.Include(bp => bp.Publisher)
.SingleOrDefaultAsync(bp => bp.Id == new Guid(id));
if (post == null)
return NotFound();
if (post.Id != updated.Id || !ModelState.IsValid)
return BadRequest();
post.Update(updated);
await _db.SaveChangesAsync();
return Json(post);
}
public override async Task<IActionResult> DeleteAsync(string id)
{
BlogPost post = await _db.BlogPosts
.FindAsync(id);
if (post == null)
return NotFound();
_db.BlogPosts.Remove(post);
await _db.SaveChangesAsync();
return Ok();
}
}
}
Looking at the sample controller, I have moved most of the logic into the model through methods like Update() and the constructors. How do I also move the logic for the Include() methods to my model as well?
Declaring method with parameter of expression of func of entity it will allow you to pass lambda expression while calling method
public ICollection<TData> FindAll<TInclude>(Expression<Func<TData, TInclude>> include)
{
using (var ctx = new TContext())
{
return ctx.T.Include(include).ToList();
}
}
And then method can be called as follow
var entityWithNavigationProp = FindAll(entity=>entity.propertyName);
For more about detail have a look at Pass a lambda parameter to an include statement
And for expression check this

exception:"type was not mapped" in entityframework codefirst with layers

i'm trying to apply LAYERS Concept on demo project developed using mvc and entity framework both
Data Annotations : for validations in Data Access Layer and
Fluent API : for mapping and tables relations
Problem : DbContext didn't Create DB and there is a Runtime Exception :
The type 'Domain.DataLayer.Member' was not mapped. Check that the type has not been explicitly excluded by using the Ignore method or NotMappedAttribute data annotation. Verify that the type was defined as a class, is not primitive, nested or generic, and does not inherit from EntityObject.
Code : my solutions consists of :
1- class library (Domain.Classes project): where i wrote all of my classes
public class Member
{
public int Id { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
public string FullName { get; set; }
}
2- DAL (Domain.DataLayer project): also another class library and i referenced domain.classes
namespace Domain.DataLayer.Repositories
{
[MetadataType(typeof(MemberMetadata))]
public partial class Member : Classes.Member , IValidatableObject
{
public Member()
{
Tasks = new HashSet<Task>();
History = new HashSet<Commint>();
}
public string ConfirmPassword { get; set; }
public HashSet<Task> Tasks { get; set; }
public HashSet<Commint> History { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var result = new List<ValidationResult>();
if (!string.Equals(Password,ConfirmPassword))
{
result.Add(new ValidationResult("mismatch pwsd", new[] {"ConfirmPassword" }));
}
return result;
}
}
}
and i used repository pattern :
public class MemberRepository : IRepository<Member>
{
public Task<IQueryable<Member>> GetAllEntities()
{
return Task<IQueryable<Member>>.Factory.StartNew(() => new Context().Members.AsQueryable());
}
}
3-BLL : for sake of simplicity : there is no Business Logic Layer
4- PL (Domain.Application MVC Project) : Member Controller :
public async Task<ActionResult> Index()
{
var members = await _repository.GetAllEntities();
return View(members);
}
Note : i depended on DbContext to create DB with name like : Domain.DataLayer.Context but it didn't craete DB so i created the DB and passed the connectionString through Context constructor like this :
namespace Domain.DataLayer
{
public class Context : DbContext
{
public Context(): base("InterviewDemo") // i tried also base("name=InterviewDemo")
{
}
public DbSet<Member> Members { get; set; }
public DbSet<Task> Tasks { get; set; }
public DbSet<Commint> TaskHistory { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new MemberConfig());
modelBuilder.Configurations.Add(new TaskConfig());
modelBuilder.Configurations.Add(new CommintConfig());
base.OnModelCreating(modelBuilder);
}
}
}