There is an api, implemented by means of Asp.Net Core. There are two controllers GoodsController and StoreController. After assigning a good to a store, it is necessary to redirect user to the new version of the good.
There are two problems with this workflow:
CreatedAtAction("Get", "goods", new { id = goodModel.Id }, goodModel) gives us .../api/goods?id=123, what I want is .../api/goods/123.
I wasn't able to get .../api/goods/123 route from CreatedAtAction(...), so I decided to create one more action that takes id from query, but asp.net isn't able to tell method GetAll() from GetFromQuery([FromQuery] int id).
Is there a way to solve at least one of these problems?
The code is given below.
[Route("api/[controller]", Name = "GoodsController")]
public class GoodsController : Controller
{
[HttpGet]
public IEnumerable<GoodModel> GetAll()
{
//Returns goods
}
[HttpGet]
public IActionResult Get0FromQuery([FromQuery] int id)
{
//Returns good by id
}
[HttpGet]
[Route("{id:int}", Name = "GetGood")]
public GoodModel Get([FromRoute]int id)
{
//Returns good by id
}
}
[Route("api/[controller]")]
public class StoreController : Controller
{
[HttpPost]
[Route("goods")]
public IActionResult Post([FromBody] AssignGoodToStoreCommand command)
{
//Assign good to store, get goodModel
return CreatedAtAction("Get", "goods", new { id = goodModel.Id }, goodModel);
}
}
Related
I started refactoring an ASP.Net 5 web application which uses MVC 6 and Entity Framework 7 when I was wondering about some points. My controllers currently use the DbContext implementation via dependency injection to fill the view models and return them for rendering with the view. Like:
public class UserController : Controller
{
[FromServices]
public MyContext myContext { get; set; }
//route which renders all users
public IActionResult Index()
{
var users = myContext.User.OrderBy(u => u.Name);
List<UserIndexViewModel> v = new List<UserIndexViewModel>();
foreach (var item in users)
{
v.Add(new UserIndexViewModel { Name = item.Name, City = item.City, DateOfBirth = item.DateOfBirth });
}
return View(v);
}
//route to edit user
public async Task<ActionResult> Edit(int id)
{
User user = await FindUserAsync(id);
UserEditViewModel v = new UserEditViewModel { Name = user.Name, City = user.City };
return View(v);
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Update(int id, UserEditViewModel userModel)
{
User user = await FindUserAsync(id);
try
{
user.Name = userModel.Name;
user.City = userModel.City;
myContext.User.Attach(user);
myContext.Entry(user).State = EntityState.Modified;
await myContext.SaveChangesAsync();
return RedirectToAction("Index");
}
catch (Exception)
{
ModelState.AddModelError(string.Empty, "Unable to save changes.");
}
return View(userModel);
}
private Task<User> FindUserAsync(int id)
{
return myContext.User.SingleOrDefaultAsync(u => u.UserId == id);
}
}
I did some research and I found some blog posts (like this) which asks to keep controllers clean. Ok.. why not?
I started creating a kind of view model builder to put the logic out of the controller methods over to the view model builder. In the above linked article is a hint to create for each view model a own view-model-builder class - which makes sense in my eyes.
For the model User exist two views and thus two view models (UserIndexViewModel and UserEditViewModel). When I create the related view-model-builder classes they should derivate from the same (abstract) class because it could be that both child classes needs a helper method (like FindUserAsync() - it is not the case in my example but just imagine). So I would have a construct like the following:
public interface IViewModelBuilder<TController, TViewModel>
{
TViewModel Build(TController controller, TViewModel viewModel);
Task<TViewModel > BuildAsync(TController controller, TViewModel viewModel);
}
public abstract class UserViewModelBuilder<TViewModel> : IViewModelBuilder<UserController, TViewModel> { ... }
public class UserIndexViewModelBuilder : SiteViewModelBuilder<UserIndexViewModel> { ... }
public class UserEditViewModelBuilder : SiteViewModelBuilder<UserEditViewModel> { ... }
This mentioned function which is needed by multiple view model builders of one model should be implemented in the abstract class (UserViewModelBuilder in my case), right?
I did it like this:
public abstract class UserViewModelBuilder<TViewModel> : IViewModelBuilder<UserController, TViewModel>
{
[FromServices]
public MyContext myContext { get; set; }
public abstract TViewModel Build(UserController controller, TViewModel viewModel);
public abstract Task<TViewModel> BuildAsync(UserController controller, TViewModel viewModel);
public Task<User> FindUserAsync(int id)
{
return myContext.User.SingleOrDefaultAsync(u => u.UserId == id);
}
}
So with this abstract class I can create the implementation of UserIndexViewModelBuilder and UserEditViewModelBuilder classes as well as for example UserDeleteViewModelBuilder and UserCreateViewModelBuilder classes in future...
Now the question:
Is this a right approach to separate the logic from the controller? If yes, do I need a kind of factory for all view model builders which can be accessed via DI in all my controllers? If it is the way which is kind of best practice for MVC applications. Is there something different to the mentioned guide which can/should be used in MVC 6 apps?
Is the abstract class the right place to call the DbContext implementation via DI? It feels not good for me.
Some other points which I missed? Some if-statements for checking response were removed from the UserController snipped for better readability.
Thank you! :-)
My Web API has two methods hooked up to a repository.
When I make a call to
"api/Cust/GetCustomers"
the full list of customers in my database is being returned. This is fine. As a heads up, i'm using Northwind so the IDs for a Customer are a group of letters. eg - ALFKI or ANTON
When I make a call to a specific CustomerID, for example
"api/Cust/GetCustomers/alfki"
I don't get an error, but the same list from above(containing all customers in the database) is returned. I'm finding this strange because my impression would be that i'd get a not found error if something is incorrect in my controller or repository.
Does anybody with experience know how something like this happens.
I have an already completed example to work off of, and in that example navigating to a specific will return records only for that customer, which is what i'm looking to do.
Here is the code in my api controller, which is almost identical
I'm thinking there must be something subtle in the routing configs that could cause this without causing an error
CustomersAPIController.cs
public class CustomersAPIController : ApiController
{
//
// GET: /CustomersAPI/
private INorthwindRepository _repo;
public CustomersAPIController(INorthwindRepository repo)
{
_repo = repo;
}
//This routing doesn't work, but if it is a possible issue,
the call for a specific customer wasn't working before I added it
[Route("api/Cust/GetOrders({id})")]
public IQueryable<Order> GetOrdersForCustID(string id)
{
return _repo.GetOrdersForCustID(id);
}
[Route("api/Cust/GetCustomers")]
public IQueryable<Customer> GetAllCustomers()
{
return _repo.GetCustomers();
}
[HttpGet]
[Route("api/Cust/GetCustomers/alfki")]
public Customer GetCustomerByID(string id)
{
Customer customer = _repo.GetCustomerByID(id);
return customer;
}
//===========================================
protected override void Dispose(bool disposing)
{
_repo.Dispose();
base.Dispose(disposing);
}
}
and here is my repo
repo.cs
public interface INorthwindRepository:IDisposable
{
//private northwndEntities _ctx = new northwndEntities();
IQueryable<Customer> GetCustomers();
IQueryable<Customer> TakeTenCustomers();
Customer GetCustomerByID(string id);
IQueryable<Order> GetOrders();
IQueryable<Order> GetOrdersForCustID(string id);
Order FetchOrderByID(int orderID);
}
public class NorthwindRepository : INorthwindRepository
{
northwndEntities _ctx = new northwndEntities();
public IQueryable<Customer> GetCustomers()
{
return _ctx.Customers.OrderBy(c => c.CustomerID);
}
public IQueryable<Customer> TakeTenCustomers()
{
var foo = (from t in _ctx.Customers
select t).Take(10);
return foo;
}
public IQueryable<Order> GetOrdersForCustID(string id)
{
var orders = _ctx.Orders.Where(x => x.CustomerID == id).OrderByDescending(x=>x.OrderDate).Take(4);
return orders;
}
public Customer GetCustomerByID(string id)
{
return _ctx.Customers.Find(id);
}
public void Dispose()
{
_ctx.Dispose();
}
Here is a link to a screenshot of the url in my example to work off of, working as intended and returning the records for a specific ID
http://postimg.org/image/oup88k83f/
In this second one, it is a link to my api that I have been basing on my example to work from.
http://postimg.org/image/858t1oph9/
As mentioned above, the code is nearly identical, except for some small changes to the routing and maybe the api controller names.
If anyone has any idea what is causing this, all suggestions are appreciated.
Thank you
*Update fixed a typo in my code
My routeconfig.cs (the same as the template provided my MVC4 API selection when creating a new project)
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
please, fixed the Route for the action GetCustomerById, look:
[Route("api/Cust/GetCustomers/{id}")]
public Customer GetCustomerByID(string id)
With Web API, I need to redirect the following two Restful routes to two different action methods:
/products/2 -> Get info for product id 2
/products?someOptionalId=456 -> Get info for all products. Use someOptionalId as a filter if provided.
Unfortunately using the standard routing and model binding scheme, since both URLs point to the same products controller and have one id as a parameter, I either run into a compile time issue creating two Get methods with same int parameter, or a run time issue with MVC not able to pick a particular action method
Compile time error
public IQueryable<Product> Get(int someOptionalIdQs)
{
}
public Product Get(int id)
{
}
Run time error (Note hack to use a string for someOptionalIdQs and then convert to int)
public IQueryable<Product> Get(string someOptionalIdQs)
{
}
public Product Get(int id)
{
}
Please suggest a fix ideally without having to make any routing config changes given that I would like to keep the routing as clean as possible. Thanks.
As your Method has an optional Id parameter you can simply use a nullable int for the Get for the collection.
The code below will support the following urls:
http:// server /api/products
http:// server /api/products?someOptionalIdQs=3
http:// server /api/products/2
Code example
public class Product
{
public string Name { get; set; }
}
public class ProductsController : ApiController
{
public IQueryable<Product> Get([FromUri] int? someOptionalIdQs = null)
{
if(someOptionalIdQs.HasValue)
{
//apply the filter
}
return new List<Product>().AsQueryable();
}
public Product Get(int id)
{
return new Product();
}
}
Use your first approach but try renaming one of your Get methods. Note that if the action name doesn't have a prefix of 'Get', make sure the [HttpGet] attribute is used.
// [HttpGet]
public IQueryable<Product> Get2(int someOptionalIdQs)
{
}
public Product Get(int id)
{
}
What you can do it probly see if the this.HttpContext.Request.QueryString.AllKeys.Contains("someOptionalIdQs") then do the processing according to Option Id Qs else your normal work flow would work.
But this would be a cryptic implementation an ideally you should create a new URL all together for a different work flow for the app.
In my controllers I pass in a IUnitOfWork object (which is generated from IoC) which is then used in controller actions for database functionality (the IUnitOfWork is passed to my service layer).
In one of my views, I want to give a link to the /Company/View/<id>, so I call the following:
<li>#Html.ActionLink(company.Name, MVC.Company.View(company.Id))</li>
This is being called not from the Company controller, but from a view in a different controller. The problem seems to be that the MVC.Company.View(company.Id) seems to actually be invoking the CompanyController.View(id) method itself. This is bad for 2 reasons.
1) Since the CompanyController's non-parameterless constructor is never called, no UnitOfWork exists, and thus when the View(int id) action is called, the action's database calls fail with a NullReferenceException.
2) Even if IUnitOfWork exists, my view should not be triggering database calls just so my links are generated. Html.ActionLink(company.Name, "View", new { id = company.Id }) doesn't trigger any database calls (as the action method isn't invoked) so as far as I'm concerned tml.ActionLink(company.Name, MVC.Company.View(company.Id)) shouldn't be triggering any DB calls either. It's excessive database calls for absolutely no gain.
Is there any reason T4MVC was created to function this way?
Edit: Here are the declarations for the CompanyController
public partial class CompanyController : MyCustomBaseController
{
public CompanyController(IUnitOfWork unitOfWork)
{
}
public virtual ActionResult Add(int jobSearchId)
{
}
public virtual ActionResult Edit(int id)
{
}
[HttpPost]
public virtual ActionResult Edit(Company company, int jobSearchId)
{
}
public virtual ActionResult View(int id)
{
}
}
public class MyCustomBaseController: Controller
{
public MyCustomBaseController()
{
}
public int CurrentUserId { get; set; }
}
Strange, I'm not able to repro this issue with the code above. What should be happening is that calling MVC.Company.View(company.Id) ends up calling an override of your action method, and never actually call your real action method.
To make this work, the generated code should look like this (only keeping relevant things):
public static class MVC {
public static Mvc3Application.Controllers.CompanyController Company = new Mvc3Application.Controllers.T4MVC_CompanyController();
}
public class T4MVC_CompanyController: Mvc3Application.Controllers.CompanyController {
public T4MVC_CompanyController() : base(Dummy.Instance) { }
public override System.Web.Mvc.ActionResult View(int id) {
var callInfo = new T4MVC_ActionResult(Area, Name, ActionNames.View);
callInfo.RouteValueDictionary.Add("id", id);
return callInfo;
}
}
Can you look at the generated code you get to see if it's any different? Start by doing a 'Go To Definition' on 'MVC', which will open up T4MVC.cs (under T4MVC.tt).
In my mvc application, i'm having a controller where many actions are their.
I'm having a property for the controller class.
In index controller i'm setting the value for the property ,
will it able to get same value in another action..
public class HomeController : BaseController
{
int sample =0;
public ActionResult Index(int query)
{
this.sample = test;
}
public ActionResult Result()
{
this.sample -------- can this 'll give the value of wat i get in index action.
}
}
Since the controller will be created and destroyed with each web request, you can't store data in private variables across web requests, which is a good thing because, different users will be making different requests, so you need to use caching.
Try this:
public class HomeController : BaseController
{
public ActionResult Index(int query)
{
ControllerContext.HttpContext.Session["query"] = query;
}
public ActionResult Result()
{
int query = (int)ControllerContext.HttpContext.Session["query"];
}
}