I am using asp net core mvc and trying to develop a RESTful app. Let's say I have models like this:
internal class Album
{
public long Id { get; set; }
public string Name { get; set; }
public long ExecutorId { get; set; }
public virtual Executor Executor { get; set; }
}
internal class Executor
{
public long Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Album> Albums { get; set; }
}
And I have 2 controllers, that I wrote like this:
[ApiVersion("1")]
[Route("api/v{api-version:apiVersion}/[controller]")]
public class ExecutorsController : Controller
{
[HttpGet("{id}", Name = "GetExecutor")]
public IActionResult GetById(long id)
{
//some code
}
[HttpGet(Name = "GetExecutors")]
public IActionResult GetAll()
{
//some code
}
}
[ApiVersion("1")]
[Route("api/v{api-version:apiVersion}/[controller]")]
public class AlbumController : Controller
{
[HttpGet("{id}", Name = "GetAlbum")]
public IActionResult GetById(long id)
{
//some code
}
[HttpGet(Name = "GetAlbums")]
public IActionResult GetAll()
{
//some code
}
}
I can call http://localhost:48234/api/v1/Album/1 and get album by Id,
I can call http://localhost:48234/api/v1/Album and get all albums. The same thing works with executors. So, what I want to do, is to be able to get albums by executorId and that my route would look like
http://localhost:48234/api/v1/executors/1/albums, which will return all albums for executor with Id = 1. The problem is that I would like to put this action into AlbumsController:
[HttpGet]
[Route("executors/{executorId}/albums")]
public IActionResult GetAlbumsByExecutorId(long executorId)
{
return new ObjectResult(_service.GetAlbumsByExecutorId(executorId));
}
This code works just fine, but it puts ControllerName (Albums) at the beginning. How can I remove ControllerName from my route? Or maybe I shouldn't do this and just put this action to ExecutorsController? I decided to do it like this, because if action returns albums, it should be placed in AlbumsController. Am I wrong?
Just remove [controller] from route path in controller and move it to the methods.
Something like this:
[ApiVersion("1")]
[Route("api/v{api-version:apiVersion}/")]
public class AlbumController : Controller
{
[HttpGet("album/{id}", Name = "GetAlbum")]
public IActionResult GetById(long id)
{
//some code
}
[HttpGet("album", Name = "GetAlbums")]
public IActionResult GetAll()
{
//some code
}
[HttpGet]
[Route("executors/{executorId}/albums")]
public IActionResult GetAlbumsByExecutorId(long executorId)
{
return new ObjectResult(_service.GetAlbumsByExecutorId(executorId));
}
}
If you want to get albums, you should place action method in AlbumController.
I'd like to recommend you this way:
[HttpGet(Name = 'GetAlbumsByExecutorId')]
[Route("{executorId}/albums")]
public IActionResult GetAlbumsByExecutorId(long executorId)
{
return new ObjectResult(_service.GetAlbumsByExecutorId(executorId));
}
Related
I'm curious if it's possible to bind a query string that is passed in with a GET request to a Model.
For example, if the GET url was https://localhost:1234/Users/Get?age=30&status=created
Would it be possible on the GET action to bind the query parameters to a Model like the following:
[HttpGet]
public async Task<JsonResult> Get(UserFilter filter)
{
var age = filter.age;
var status = filter.status;
}
public class UserFilter
{
public int age { get; set; }
public string status { get; set; }
}
I am currently using ASP.NET MVC and I have done quite a bit of searching but the only things I can find are related to ASP.NET Web API. They suggest using the [FromUri] attribute but that is not available in MVC.
I just tested the this, and it does work (at least in .net core 3.1)
[HttpGet("test")]
public IActionResult TestException([FromQuery]Test test)
{
return Ok();
}
public class Test
{
public int Id { get; set; }
public string Yes { get; set; }
}
You can can create an ActionFilterAttribute where you will parse the query parameters, and bind them to a model. And then you can decorate your controller method with that attribute.
For example
public class UserFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
var controller = actionContext.ControllerContext.Controller as CustomApiController;
var queryParams = actionContext.Request.GetQueryNameValuePairs();
var ageParam = queryParams.SingleOrDefault(a => a.Key == "age");
var statusParam = queryParams.SingleOrDefault(a => a.Key == "status");
controller.UserFilter = new UserFilter {
Age = int.Parse(ageParam.Value),
Status = statusParam.Value
};
}
}
The CustomApiController (inherits from your current controller) and has a UserFilter property so you can keep the value there. You can also add null checks in case some of the query parameters are not sent with the request..
Finally you can decorate your controller method
[HttpGet]
[UserFilter]
public async Task<JsonResult> Get()
{
var age = UserFilter.age;
var status = UserFilter.status;
}
I'm trying to make a api like the OPENBANKPROJECT. Such as
/api/banks/{BANK_ID}/atms/{ATM_ID} etc. I guess banks and atm is a different controller
I want get a global (api/Claim/{id}/Detail/[action] i need this {id}) parameter before before [action] initialize (maybe in constructor).
How do i get this {id} before [action] initialize?
[Route("api/Claim/{id}/Detail/[action]")]
public class ClaimDetailController
{
int _id; // assignment {id}
public ClaimDetailController(IClaimDetailService claimDetailService)
{
`Need Query Id before execute action`
}
[HttpPost]
public async Task<BaseResponse> ClaimDetailInfoPolicy(ClaimDetailKeyModel model)
{
return `codes with _id`;
}
}
public class ClaimDetailKeyModel
{
public long FileNo { get; set; }
public long RecourseNo { get; set; }
}
Solution was simple :)
[ApiController]
[Route("api/Claim/{claimId}/Detail/[action]/")]
public class ClaimDetailController
{
[FromRoute(Name = "claimId")]
public int Id { get; set; }
public ClaimDetailController(IClaimDetailService claimDetailService)
{
`bla bla`
}
[HttpPost]
public async Task<BaseResponse> ClaimDetailInfoPolicy(ClaimDetailKeyModel model)
{
return `codes with Id`
}
}
Add this id to your request handler.
[HttpPost]
public async Task<BaseResponse> ClaimDetailInfoPolicy(int id, ClaimDetailKeyModel model)
{
return `codes`;
}
I have an index method in a controller which looks like this :
public ActionResult Index()
{
var object = _ObjectService.GetAll();
return View(object);
}
Which give me a list of object with those properties :
public class Object : EntityWithNameAndId
{
public virtual Site Site { get; set; }
public virtual List<User> Users { get; set; }
public virtual List<Planning> Plannings { get; set; }
public virtual Guid IdPilote { get; set; }
}
Now in my Index() view, i want to get the User who's related to the IdPilote id and display its name.
I tried something like this, thanks to this topic ASP.Net MVC: Calling a method from a view :
#model List<MyClass.Models.Promotion>
#foreach (var item in Model)
{
<td>#item.Site.Name</td>
#{
var id = item.IdPilote;
//Here Interface and Service are folders
var user = MyDAL.Interface.Service.IUserService.Get(id);
}
<td>
//This is where i try to display my User name,
//that i get dynamically using the idPilote for each User in list
</td>
}
But Get(id) is not recognize as a valid method..
public interface IUserService : IDisposable
{
User Get(Guid id);
}
public class UserService : IUserService
{
private MyContext context;
public UserService(MyContext context)
{
this.context = context;
}
public User Get(Guid id)
{
return context.User.Where(w => w.Id == id).SingleOrDefault();
}
}
So what's the best way to get my User object inside my view, since i only get an Id ?
Should i create a new list, using the first one, in my Index method (where i can call IUserInterface.Get()) or is there a better way to do it ?
Make it worked by creating a new list and a specific ViewModel, as suggested :
public class IndexObjectViewModel : EntityWithNameAndId
{
public virtual Site Site { get; set; }
public virtual List<User> Users { get; set; }
public virtual List<Planning> Plannings { get; set; }
//To store User instead of its Id
public virtual User Pilote { get; set; }
}
Now Index() looks like this :
public ActionResult Index()
{
var objects = _IObjectService.GetAll();
ViewBag.NotPromoExist = false;
var indexObj = new List<IndexObjectViewModel>();
foreach (var p in objects)
{
var indexModel = new IndexObjectViewModel();
indexModel.Id = p.Id;
indexModel.Name = p.Name;
indexModel.Site = p.Site;
indexModel.Users = p.Users;
indexModel.Plannings = p.Plannings;
indexModel.Pilote = _IUserService.Get(p.IdPilote);
indexObj.Add(indexModel);
}
return View(indexObj);
}
Everything is done in the controller now. Not sure if it's the best way to do it though..
I'm adding an API controller to my MVC application to retun JSON data
In my application I have a class called Album:
public class Album
{
public int Id { get; set; }
public string AlbumName { get; set; }
public int YearReleased { get; set; }
public string AlbumInfo { get; set; }
public string imgAlbumCover { get; set; }
}
My database contains a table of several Album objects
I created an API controller to return this list of Albums in Json format.
I added the following code to WebApiConfig.cs to get JSON back instead of XML:
using System.Net.Http.Headers;
GlobalConfiguration.Configuration.Formatters
.JsonFormatter.MediaTypeMappings.Add
(new System.Net.Http.Formatting.RequestHeaderMapping("Accept",
"text/html",
StringComparison.InvariantCultureIgnoreCase,
true,
"application/json"));
When I do an Albums API call in the browser, returned is a list of Album objects in JSON format.
Instead of returning the list of Albums, I'd like to retun a RootObject that has 1 property called Albums, where Albums is a list of Album objects. Is there a way of doing this in the controller? I don't want to have to create a new RootObject class.
Below is the code for my API controller:
namespace Music.Controllers.API
{
public class AlbumsController : ApiController
{
private MusicContext db;
public AlbumsController()
{
db = new MusicContext();
}
public IEnumerable<Album> GetAlbums()
{
return (db.Albums.ToList());
}
}
}
Then create a viewmodel as such and return the same like
public class AlbumListResponseModel
{
public IEnumerable<Album> Albums { get; set; }
}
public async Task<IActionResult> GetAlbums()
{
AlbumListResponseModel model = new AlbumListResponseModel
{
Albums = db.Albums;
}
return OK(model);
}
If you are using WEB API 2.0 then consider using IActionResult rather
Change the GetAlbums return type to HttpResponseMessage and change the return statement as
return Request.CreateResponse(HttpStatusCode.OK, new {Albums = db.Albums.ToList() });
That's way you don't need to create a new class.
Full Code :
namespace Music.Controllers.API
{
public class AlbumsController : ApiController
{
private MusicContext db;
public AlbumsController()
{
db = new MusicContext();
}
public HttpResponseMessage GetAlbums()
{
return Request.CreateResponse(HttpStatusCode.OK, new {Albums = db.Albums.ToList() });
}
}
}
This is my view model.
public class ProductViewModel
{
public Guid Id { get; set; }
public string Code { get; set; }
public string Name { get; set; }
public bool IsAvailable { get; set; }
}
When form is posted from client the form is submitted to this Controller
public async Task<IHttpActionResult> AddProduct(ProductViewModel productViewModel)
{
await ServiceInstances.PostAsync("product/add", productViewModel);
return Ok();
}
Then this controller submit the form to the API controller
Which is on my separate Project.
[HttpPost]
[Route("add")]
public IHttpActionResult AddProduct(ProductViewModel model)
{
_productService.AddProduct(model.UserServiceDetails());
return Ok();
}
Extension UserServiceDetails Where i get the Login User Info
public static UserServiceDetailModel<T> UserServiceDetails<T>(this T model)
{
var serviceRequestModel = new ServiceRequestModel<T>()
{
Model = model,
LoginInfo = HttpContext.Current.User.Identity.GetUserLoginInfo();
};
}
AddProductService:
public void AddProduct(UserServiceDetailModel<ProductViewModel> serviceRequestModel)
{
var repo = _genericUnitOfWork.GetRepository<Product, Guid>();
var mapped = _mapper.Map<ProductViewModel, Product>(serviceRequestModel.Model);
mapped.Id = Guid.NewGuid();
mapped.CreatedDate = GeneralService.CurrentDate();
mapped.CreatedById = serviceRequestModel.LoginInfo.UserId;
repo.Add(mapped);
_genericUnitOfWork.SaveChanges();
}
Now my question is Is there any way to assign the value to this field CreatedDate and CreatedById before posting it to service?
Reduce these logic to mapper:
mapped.CreatedDate = GeneralService.CurrentDate();
mapped.CreatedById = serviceRequestModel.LoginInfo.UserId;
Or is there any way that those field gets mapped to Product when
var mapped = _mapper.Map<ProductViewModel, Product>(serviceRequestModel.Model);
Sometime i may have the List<T> on view-model and there i have to add this field using the loop.
So this same mapping may get repeated over and over on Add Method Or Update.
In some entity i have to assign the ModifiedDate and ModifiedById also.
My Mapper Configuration:
public class ProductMapper : Profile
{
public ProductMapper()
{
CreateMap<ProductViewModel, Product>();
}
}
I cannot add the Enitity as IAuditableEntity and Overrride in ApplicationDbContext because my DbContext is in separate Project and i donot have access to Identity there.