How can I reduce redundancy in my controller? - asp.net-core

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

Related

Show multiple Models in same Razor page: OnGet not firing

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.

how to send array to API which contains image and other data in .net core

When I am passing a single object like below then it is working as per below image
[HttpPost]
public async Task<ActionResult> Post([FromForm] MyModel Details)
{
}
but when I am passing the List of the object to API then it is not working. option to upload a file is not visible. and if I entered any values in the array then also I am getting count 0 for details.
[HttpPost]
public async Task<ActionResult> Post([FromForm] List<MyModel> Details)
{}
I want to pass the List of images and descriptions to API. How can I achieve it?
Thanks in advance!
You need custom model binding for the list model . Here is a similar demo:
custom model binding code:
public class MetadataValueModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
throw new ArgumentNullException(nameof(bindingContext));
var values = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (values.Length == 0)
return Task.CompletedTask;
var options = new JsonSerializerOptions() { PropertyNameCaseInsensitive = true };
var deserialized = JsonSerializer.Deserialize(values.FirstValue, bindingContext.ModelType, options);
bindingContext.Result = ModelBindingResult.Success(deserialized);
return Task.CompletedTask;
}
}
Add the model binder to the model class:
public class MasterDTO
{
public string Comments { get; set; }
public IFormFile File { get; set; }
public List<DetailDTO> Details { get; set; }
public MasterDTO()
{
this.Details = new List<DetailDTO>();
}
}
[ModelBinder(BinderType = typeof(MetadataValueModelBinder))]
public class DetailDTO
{
public Int64 ElementId { get; set; }
public double LowerLimit { get; set; }
public double HigherLimit { get; set; }
public string Status { get; set; }
public string UserAuthorization { get; set; }
public DateTime? AutorizationDate { get; set; }
}
controller/action
[HttpPost]
public async Task<IActionResult> CreateProjectLimit([FromForm] MasterDTO masterDto)
{
//...
return Ok();
}
You can just use postman to pass the list of images and Descriptions to API
Below is the right answer. we can use Postman to pass images in the array as shown below.

getting 400 error on webapi call blazorserver

i am trying to setup a blazor server app, calling a webapi.
I keep getting a 400 error returned, when I call the API.
I have 3 Projects, projectserver and projectapi. projectserver is where the Blazor app sits and Project API is where the API sits.
I don't know if the apicall can find the API as it does not hit any breakpoints in the API section, I am totally confused, as if it cannot find the API then it should return a 404 or other error and not 400 ?
thank you for your efforts.
this is my code,
Projectserver, this is where I post the Register Model to the API
public string message { get; set; }
public RegisterModel r = new RegisterModel();
private async Task Create(MouseEventArgs e)
{
var json = Newtonsoft.Json.JsonConvert.SerializeObject(r);
var client = clientfactory.CreateClient("ServerApi");
var result = await client.PostAsJsonAsync("/Account/Register",json); // check the Startup file and check base address for the Full route.
message = result.StatusCode.ToString();
}
}
the ClientFactory returns the base address of what is defined in startup.cs
services.AddHttpClient("ServerApi", client => client.BaseAddress = new Uri("https://localhost:44302/"));
the API is Projectserver and defined as follows.
[Route("[controller]")]
[ApiController]
public class AccountContoller : ControllerBase
{
private readonly ApplicationDbContext _context;
private readonly SecurityOptions _securityOptions;
private readonly JwtIssuerOptions _jwtOptions;
// GET: api/<Account>
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/<Account>/5
[HttpGet("{id}")]
public string Get(int id)
{
return "value";
}
// POST api/<Account>
[HttpPost]
public void Post([FromBody] string value)
{
}
// POST api/<Account>
[HttpPost("Register")]
public async Task<ActionResult<RegisterResult>> Register(RegisterModel model)
{
RegisterResult r = new RegisterResult();
var Exisits = await _context.Users.Where(r => r.EmailAddress == model.Email).FirstOrDefaultAsync();
if(Exisits != null)
{
r.Sucsess = false;
r.ErrorMessage = "Email - Already Exisits";
return r;
}
else
{
try
{
User newuser = new User();
newuser.CreatedDateTime = DateTime.UtcNow;
newuser.UserID = Guid.NewGuid();
newuser.MobileNumber = model.MobileNumber;
newuser.Password = model.Password;
newuser.FirstName = model.FirstName;
newuser.Surname = model.LastName;
_context.Users.Add(newuser);
await _context.SaveChangesAsync();
r.Sucsess = true;
return r;
}
catch(Exception e)
{
r.Sucsess = false;
r.ErrorMessage = e.ToString();
return r;
}
}
}
the Model classes are defined as Serializable
[Serializable]
public class RegisterResult
{
public bool Sucsess { get; set; }
public string ErrorMessage { get; set; }
}
[Serializable]
public class RegisterModel
{
public string UserName { get; set; }
public string Password { get; set; }
public string Email { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string RoleID { get; set; }
public string EntityID { get; set; }
public string MobileNumber { get; set; }
}
Can you please modify your code as below and give it a try:-
var serializedBody = JsonConvert.SerializeObject(r);
var jsonRequestBodyContent = new StringContent(serializedBody, Encoding.UTF8,"application/json");
var client = clientfactory.CreateClient("ServerApi");
var result = await client.PostAsync("/Account/Register",jsonRequestBodyContent);

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>();

ASP.Net core NullReferenceException was not handled by the user [duplicate]

This question already has answers here:
What is a NullReferenceException, and how do I fix it?
(27 answers)
Closed 5 years ago.
Not a Duplicate: See the comment and answer to this question.
I am almost new to ASP.net Core development. I am building a sample web api with MySql as my database.
My Book model:
public class Book
{
[Key]
public long Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public string Author { get; set; }
public DateTime CreatedOn { get; set; } = DateTime.UtcNow;
public Book()
{
}
}
public class WebAPIDataContext : DbContext
{
public WebAPIDataContext(DbContextOptions<WebAPIDataContext> options)
: base(options)
{
}
public DbSet<Book> Books { get; set; }
}
Book Repository:
public class BookRepository: IBookRepository
{
private readonly WebAPIDataContext _db;
public BookRepository(WebAPIDataContext db) {
_db = db;
}
public Book CreateBook(Book book) {
_db.Books.Add(book);
_db.SaveChanges();
return book;
}
public void DeleteBook(long id)
{
Book book = GetBook(id);
if (book != null) {
_db.Books.Remove(book);
_db.SaveChanges();
}
}
public List<Book> GetAllBooks()
{
return _db.Books.AsNoTracking().ToList();
}
public Book GetBook(long id)
{
return _db.Books.FirstOrDefault(o => o.Id == id);
}
public void UpdateBook(long id, Book book)
{
throw new NotImplementedException();
}
}
public interface IBookRepository
{
List<Book> GetAllBooks();
Book GetBook(long id);
Book CreateBook(Book book);
void UpdateBook(long id, Book book);
void DeleteBook(long id);
}
And Book controller:
[Route("api/[controller]")]
public class BooksController : Controller
{
private readonly IBookRepository _books;
[HttpGet("")]
public IActionResult GetAllBooks()
{
List<Book> books = _books.GetAllBooks();
return Ok(books);
}
[HttpGet("{id}")]
public IActionResult GetBook(long id)
{
Book book = _books.GetBook(id);
if (book == null)
{
return NotFound();
}
return Ok(book);
}
[HttpPost]
public IActionResult CreateBook([FromBody] Book book)
{
if (ModelState.IsValid == false)
{
return BadRequest(ModelState);
}
Book createdBook= _books.CreateBook(book);
return CreatedAtAction(
nameof(GetBook), new { id = createdBook.Id }, createdBook);
}
[HttpPut("{id}")]
public IActionResult UpdateBook(long id, [FromBody] Book book)
{
if (ModelState.IsValid == false)
{
return BadRequest(ModelState);
}
try
{
_books.UpdateBook(id, book);
return Ok();
}
catch (EntityNotFoundException<Book>)
{
return NotFound();
}
}
[HttpDelete("{id}")]
public IActionResult DeleteBook(long id)
{
_books.DeleteBook(id);
return Ok();
}
}
When I do a POST request from Swagger to create a new book it gives me NullReferenceException error. What am I doing wrong?
For this first case to work you needed to have
services.AddScoped<IBookRepository, BookRepository>();
in ConfigureServices method located in the startup.cs
public class BooksController : Controller
{
public BooksController(IBookRepository books){
_books = books;
}
...
Now this assumes you hooked up your IBookRepository to be included in the container for Asp.NET Core in the startup class.
IF NOT then this is what you are missing!
public class BooksController : Controller
{
public BooksController(){
_books = new BookRepository();
}
If you have never worked with Dependency Injection before this I suggest you read up on it, also goes by IOC (inversion of control).