How do I create an MVC area for API? - asp.net-web-api

I created a new MVC project and I ticked the box for API.
However, I intend to create many API functions and I don't really want to fill up the main controllers folder, so, I thought this will be a good use for an area.
So, I created an area called API, but, no matter what I do, I can't seem to access any of the API pages.
Just to test, I created another controller called test on the API area, and, I can access and do everything as expected.
I came straight from MVC2/3, and then had a few years off... I'm a bit out of touch and I believe this could be related to the /App_Start/WebApiConfig.cs and /Global.asax files, but, I have tried to edit them in various ways without any luck.
Does anyone know what is happening and what I need to do to get the API features working from an area?

You don't need to create an Area for Api controllers, but your Api controllers must derive from ApiController to be picked up. Instead of creating a Area, just organize your controllers inside folders and as long as the controllers follow convention they will be picked up by the WebApi.
In your WebApiConfig.csyou'll be able to define Routes, although I recommend using Attribute Based Routing instead. Then for each "area" you could create a base api controller and stick a `[RoutePrefix("api/areaName")] attribute on the class - and then for every controller in that area, you just derive from that base controller.
Here is a quick example for a "Users" api controller. With the example below you'll be able to to use the following urls:
GET http://localhost:port/api/users
GET http://localhost:port/api/users/some-guid
POST http://localhost:port/api/users
Remember to enable attribute based routing in your WebApiConfig class.
WebApiConfig.cs
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
}
}
UsersApiController.cs
[RoutePrefix("api/users")]
public class UsersApiController : ApiController
{
List<User> _users = new List<User> { new User("Foo", "Bar"), new User("Bar", "Foo") };
[Route("")]
public IHttpActionResult Get()
{
var result = _users;
return Ok(result);
}
[Route("{id:guid}")]
public IHttpActionResult Get(Guid id)
{
var result = _users.FirstOrDefault(q => q.Id == id);
if (result == null)
return NotFound();
return Ok(result);
}
[Route("")]
public IHttpActionResult Post([FromBody]PostModel model)
{
// Process the input model
var user = new User(model.FirstName, model.LastName);
// Save user to database
_users.Add(user);
return Created<User>(Request.RequestUri + user.Id.ToString(), user);
}
public class PostModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class User
{
public User(string firstName, string LastName)
{
Id = Guid.NewGuid();
FirstName = firstName;
LastName = lastName;
}
public Guid Id { get; private set; }
public string FirstName { get; private set; }
public string LastName { get; private set; }
}
}

Related

Understanding APB services and repositories

I am learning the APB and I am analyzing and extending the code from the Introduction Part 2 tutorial (ASP.NET Core, EF). https://aspnetboilerplate.com/Pages/Documents/Articles/Introduction-With-AspNet-Core-And-Entity-Framework-Core-Part-2/index.html
First I introduced a new entity - Team. Team is a group of people. I added the foreign key to the Person entity.
[Table("AppPersons")]
public class Person : AuditedEntity<Guid>
{
public const int MaxNameLength = 32;
[Required]
[StringLength(MaxNameLength)]
public string Name { get; set; }
public Guid TeamId { get; set; }
[ForeignKey(nameof(TeamId))]
public Team Team { get; set; }
}
I would like to create an ApplicationService that will return the list of people for a specific team with the number of assigned tasks. I don't know how should I combine the Repository and Mapping infrastructure to achieve this.
public class FooAppService : ApplicationService, IFooAppService
{
private readonly IRepository<Task, Guid> _taskRepository;
public FooAppService(IRepository<Task, Guid> taskRepository)
{
_taskRepository = taskRepository;
}
public ListResultDto<PersonWithNumberOfTasksAssignedDto> FooMethod(Guid teamId)
{
...
}
}
I also don't know what is the best way to design the DTO object. Do I need a new DTO object for this service method or should I use a tuple? I do have a PersonDto object that is used to add/edit Person. Should I just wrap it with a count property?
public class PersonWithNumberOfTasksAssignedDto : EntityDto<Guid>
{
public PersonDto Person { get; set; }
public int NumberOfAssignedTasks { get; set; }
}
You can take reference from Abp free startup template.
public async Task<PersonDto> Create(PersonCreateInput input)
{
var person = ObjectMapper.Map<Person>(input);
await CurrentUnitOfWork.SaveChangesAsync();
return MapToEntityDto(person);
}
For example, UserAppService.Create() https://github.com/aspnetboilerplate/module-zero-core-template/blob/164a5c9e28cb29383551d0f3310986ab43d0ceed/aspnet-core/src/AbpCompanyName.AbpProjectName.Application/Users/UserAppService.cs#L55-L76
To retrieve list of items, you can leverage on the AsyncCrudAppService base class which provides sorting and pagination out of the box (via AsyncCrudAppService.GetAll().
See https://github.com/aspnetboilerplate/aspnetboilerplate/blob/14c4fe5a5408a66e913a434688b951815247827d/src/Abp/Application/Services/AsyncCrudAppService.cs#L112-L129

Is there something like [Bind(Exclude="Property")] for asp.net web api?

I'm trying to exclude a property from my Post Action in a web api controller, is there something like [Bind(Exclude="Property")] for asp.net web api?
This is my model:
public class ItemModel
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
I want to exclude the Id in the Post Action, because it is autogenerated, but I need to return it in my Get Action.
I Know I could have two models, one for my Post action and one for my Get action, but I'm trying to do this with just one model.
I would favour mapping models but this could be achieved by checking if the request is a POST in a ShouldSerialize method:
public class MyModel
{
public string MyProperty1 { get; set; }
public string MyProperty2 { get; set; }
public bool ShouldSerializeMyProperty2()
{
var request = HttpContext.Current.Request;
if (request.RequestType == "POST") return false;
return true;
}
}
Where your method name is the name of the property prefixed with ShouldSerialize.
Note this will work for JSON. For XML, you will need to add the following line to your config:
config.Formatters.XmlFormatter.UseXmlSerializer = true;
You can simply create a DTO for POST.

Can I add a view based on a ViewModel and without a controller for the newly added view?

I am newbie to MVC3 and I wonder if this is even possible and good practice?
I have a model + view + controller which works fine. This view shows a list of people - I want to be able to click on a person's name and be redirected to a new view that will show that persons details. This new view only has a ViewModel, but no controller because I plan to pass in the object in the action.
The Person object contains all the properties my view needs to show:
#Html.ActionLink(item.Person.FirstName, "PersonDetails", item.Person)
Is this possible/good practice??
I believe you have an misunderstanding of how MVC works. Your ActionLink will ALWAYS redirect to a corresponding ActionMethod of a Controller. What you'll want to do is create an action method in your controller that accepts the necessary parameters and then returns to the View your ViewModel.
Here is a very quick example to get you started:
public class HomeController : Controller
{
public ActionResult List()
{
return View();
}
public ActionResult DetailById(int i)
{
// load person from data source by id = i
// build PersonDetailViewModel from data returned
return View("PersonDetails", PersonDetailViewModel);
}
public ActionResult DetailByVals(string FirstName, Person person)
{
// build PersonDetailViewModel manually from data passed in
// you may have to work through some binding issues here with Person
return View("PersonDetails", PersonDetailViewModel);
}
}
Not a good way to do it like you want to (in your original post). A view should always have a view model. A view model represents only the data that you want to have on the view, nothing more and nothing less. Do not pass your domail model to the view, but rather use a view model. This view model might contain just a portain of the properties of your domain model.
In your list view you probably have a grid, and next to each row you probably have a details link, or a link on the name (as you have it). When either of these links are clicked then you are directed to a details view. This details view will have its own view model with only the properties that you need to display on the details view.
A domail model might look something like:
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
public string ExampleProperty1 { get; set; }
public string ExampleProperty2 { get; set; }
public string ExampleProperty3 { get; set; }
}
Let say you only want to display the person's id, first name, last name and age then your view model will look like this:
public class PersonDetailsViewModel
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
}
You don't need ExampleProperty1, ExampleProperty2 and ExampleProperty3 because they are not required.
Your person controller might look like this:
public class PersonController : Controller
{
private readonly IPersonRepository personRepository;
public PersonController(IPersonRepository personRepository)
{
// Check that personRepository is not null
this.personRepository = personRepository;
}
public ActionResult Details(int id)
{
// Check that id is not 0 or less than 0
Person person = personRepository.GetById(id);
// Now that you have your person, do a mapping from domain model to view model
// I use AutoMapper for all my mappings
PersonDetailsViewModel viewModel = Mapper.Map<PersonDetailsViewModel>(person);
return View(viewModel);
}
}
I hope this clears things up a little more.

Code first DbContext with current user filter

I'm building an ASP.NET MVC3 website with an code first database and have the following question:
Is it possible to make an instance of MyDbContext class with an additional argument set which will be used for filtering the results of calls to mydbcontext.
I want to use this for restricting the resultset to the current user that is logged in on my asp.net mvc website.
Any directions would be great!
I don't see why that should be a problem. Something like this should work:
public class Northwind : DbContext
{
public DbSet<Product> Products { get; set; }
public DbSet<Category> Categories { get; set; }
}
public class FilteredNorthwind : Northwind
{
public IQueryable<Products> GetFilteredProducts(string userRole)
{
return Products.Where(product => product.UserRole == userRole);
}
}
Update
To make it impossible for your MyDbContext to be abused, you could put all your database code and models into a separate project/assembly. Then make your DbContext an internal class (instead of public), then create a public class (FilteredDbContext) that wraps your MyDbContext and exposes methods that allow you to only grab the data your allowed to see. Then in your main assembly (your web project), you will only be able to use FilteredDbContext.
So, for example:
internal class Northwind : DbContext // note: internal class
{
public DbSet<Product> Products { get; set; }
public DbSet<Category> Categories { get; set; }
}
public class FilteredNorthwind // note: does not inherit from `Northwind`
{
private readonly _dbContext = new Northwind();
public IQueryable<Products> GetProducts(string userRole)
{
return _dbContext.Products.Where(product => product.UserRole == userRole);
}
}
If Northwind and FilteredNorthwind are in a separate assembly from your web app, you can instantiate only FilteredNorthwind from your web app.
Update 2
If you use a ViewModel, then your web app can't get back to the list of all products for a category because you extract out only the properties you need (and only the properties the user is allowed to see).
public class ProductViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
public IEnumerable<Products> GetProducts(string userRole)
{
return _dbContext.Products
.Where(product => product.UserRole == userRole)
.Select(product => new ProductViewModel
{
Id = product.Id,
Name = product.Name,
Price = product.Price
};
}
You could make a layer above and hide the generated one and create a your own DbContext which derives from the generated MyDbContext. Just a wild guess but it seems logical to me and so you can implement your own argument set and still use the generated one.
I would do this:
public interface IUserContext {
string User { get; set; }
}
public class Database : DbContext {
public IDbSet<Product> Products { get; set; }
}
public class AuthorizedDatabase {
private readonly Database _database;
private readonly IUserContext _userContext;
public AuthorizedDatabase(Database database, IUserContext userContext) {
_database = database;
_userContext = userContext;
}
private bool Authorize<TEntity>(TEntity entity) {
// Some code here to look at the entity and the _userContext and decide if it should be accessible.
}
public IQueryable<Product> Products {
get {
return _database.Products.Where(Authorize);
}
}
}
This would allow me to cleanly abstract the actual logic around the authorization (and your IUserContext interface can be as simple or complex as required to suite your exact needs.)
To ensure that the user is unable is circumvert this protection using a navigation property (Product.Category.Products, for example.) you might need to turn off lazy loading and explicitly load the required related entities.
Have a look at this post from ADO.NET Team Blog for ideas: loading related entities

Using Data Annotations Validation Manually and Object Graphs

Let's assume that I have two simple classes:
public class CustomerDetails
{
[Required]
public string Address
{
get;
set;
}
}
public class Customer
{
public Customer()
{
Details = new CustomerDetails();
}
[Required]
public string Name
{
get;
set;
}
public CustomerDetails Details
{
get;
private set;
}
}
When I try to manually validate Customer class in a Console application in this way:
var customer = new Customer() { Name = "Conrad" };
var context = new ValidationContext(customer, null, null);
var results = new List<ValidationResult>();
Validator.TryValidateObject(customer, context, true);
Then -even though I chose to validate all properties of the customer instance- Validator just validates the Name property of the customer instance, but not the Address property of the Details.
Is this by design or am I missing something here? Moreover, if this is by design then is there a robust way to manually validate the full object graph decorated with validation attributes, including nested types instead of using validator for the whole object graph manually?
Please note that this is tested within a Console application and not an ASP.NET MVC application.
Kind regards.
I had almost the same problem but with the collection of nested objects. I was able to resolve it by implementing IValidatableObject on a container class. In your case it's slightly easier. Something like this:
public class Customer : IValidatableObject
{
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var context = new ValidationContext(this.Details, validationContext.ServiceContainer, validationContext.Items);
var results = new List<ValidationResult>();
Validator.TryValidateObject(this.Details, context, results);
return results;
}
}
Hope this helps.

Resources