Understanding Get Method Overrides - asp.net-web-api

I am totally new to Web API an am not understanding how to Filter get calls.
This method returns all items in my database.
// GET: api/LogEntries
public IQueryable<LogEntry> GetLogEntries()
{
return db.LogEntries;
}
This method returns a specific item in my database.
// GET: api/LogEntries/5
[ResponseType(typeof(LogEntry))]
public IHttpActionResult GetLogEntry(int id)
{
LogEntry logEntry = db.LogEntries.Find(id);
if (logEntry == null)
{
return NotFound();
}
return Ok(logEntry);
}
So now I want to filter the returned records so I created this method but it won't work because the specific item method gets called. I seem to be missing a concept and am hoping you can point me to more clear understanding. Thanks
// GET: api/LogEntries
public IQueryable<LogEntry> GetLogEntries(string levelID)
{
int levIdInt;
if (Int32.TryParse(levelID, out levIdInt))
{
return db.LogEntries.Take(300).Where(l => (int)l.Level == levIdInt).OrderByDescending(d => d.TimeStamp);
}
return db.LogEntries.Where(i => i.ID < 0);
}

You need to specify the route for that method
[Route("api/LogEntries/Level/{levelID}"]
public IQueryable<LogEntry> GetLogEntries(string levelID)
{}
More on routing is available here http://www.asp.net/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2

Related

Why does Resolver behave differently in Query vs Mutation?

I've been struggling with the issue/behaviour described below for while now and can't seem to figure out what's going on. This is all based on inherited/adapted code, so it's completely possible that I'm missing something fundamental. It's been literally years since I've asked a question on SO, so please bear that in mind if I'm not complying with the current expectations on formatting and content/detail.
I've got a number of domain classes involved in this problem, but at a high level, what I'm trying to do is to count the number of users with a specific role in relation to a group.
The behaviour that I'm seeing when I execute a GraphQL query (See below), is that the attribute in question (capacityUsed) is populated as expected, which makes me believe that the wiring is correct.
However, when I try to access the attribute within a GraphQL mutation, the underlying data to populate the value isn't retrieved, so the default value is returned, which isn't accurate or what I want.
Ultimately what I'm trying to do is to access (and make business decisions based on) the GroupRoleCapacity.CapacityUsed attribute. I'd like to sort out what's wrong with the config/setup for the mutation so that my resolver works as expected in both situations.
I'd appreciate any help or insight into what might be causing this. I've gone through the related HotCholocate and GraphL documentation a number of times and tried just about every iteration of related keywords that I can think of searching for an explanation of what's going on, and haven't been able to find a solution.
Here's how things are currently wired up (pulling out extraneous info/attributes to keep it short):
//GraphQL Entity Configuration
public class GroupRoleCapacityType : ObjectType<GroupRoleCapacity>
{
protected override void Configure(IObjectTypeDescriptor<GroupRoleCapacity> descriptor)
{
descriptor
.Field(grc => grc.Group)
.ResolveWith<Resolvers>(r => r.GetGroup(default!, default!))
.UseDbContext<AppDbContext>()
.Description("This is the Group for this GroupRoleCapacity.");
descriptor
.Field(grc => grc.Role)
.ResolveWith<Resolvers>(r => r.GetRole(default!, default!))
.UseDbContext<AppDbContext>()
.Description("This is the Role for this GroupRoleCapacity.");
descriptor
.Field(grc => grc.CapacityUsed)
.ResolveWith<Resolvers>(r => r.GetCapacityUsed(default!, default!))
.UseDbContext<AppDbContext>()
.Description("This is the number of Users with the indicated Role for this GroupRoleCapacity.");
}
protected class Resolvers
{
public Group GetGroup([Parent] GroupRoleCapacity GroupRoleCapacity, [ScopedService] AppDbContext context)
{
return context.Groups.FirstOrDefault(p => p.Id == GroupRoleCapacity.GroupId);
}
public Role GetRole([Parent] GroupRoleCapacity GroupRoleCapacity, [ScopedService] AppDbContext context)
{
return context.Roles.FirstOrDefault(p => p.Id == GroupRoleCapacity.RoleId);
}
public int GetCapacityUsed([Parent] GroupRoleCapacity GroupRoleCapacity, [ScopedService] AppDbContext context)
{
return context.Users.Count(u => u.GroupId == GroupRoleCapacity.GroupId &&
u.UserRoles.Any(ur => ur.RoleId == GroupRoleCapacity.RoleId));
}
}
}
//GraphQL Service Startup
public class Startup
{
public IConfiguration Configuration { get; }
public Startup(IConfiguration configuration) { Configuration = configuration; }
public void ConfigureServices(IServiceCollection services) {
services.AddScoped(p => p.GetRequiredService<IDbContextFactory<AppDbContext>>().CreateDbContext());
services
.AddGraphQLServer()
.AddQueryType<Query>()
.AddMutationType<Mutation>()
.AddType<GroupType>()
.AddType<RoleType>()
.AddType<UserType>()
.AddType<UserRoleType>()
.AddType<GroupRoleCapacityType>()
}
}
Using the query below seems to work as expected and the GroupRoleCapacity.CapacityUsed attribute is populated appropriately when accessing the data via a query
{ groupRoleCapacity { groupId roleId capacityUsed }}
//GraphQL Query
public class Query
{
[UseDbContext(typeof(AppDbContext))]
[GraphQLType(typeof(NonNullType<ListType<NonNullType<GroupRoleCapacities.GroupRoleCapacityType>>>))]
public IQueryable<GroupRoleCapacity> GetGroupRoleCapacity([ScopedService] AppDbContext context)
{
return context.GroupRoleCapacities;
}
}
However, when I use a mutation similar to below, GroupRoleCapacity.CapacityUsed isn't being set correctly, which is what's making me suspect a problem with either the Resolver implementation or the configuration.
mutation { addUserToGroup(input: { userId: 1, groupId: 1, roleId: 1}) { result { userId, groupId, roleId }}}
//GraphQL Mutation
public class Mutation
{
public Mutation(IConfiguration configuration) { }
[UseDbContext(typeof(AppDbContext))]
public async Task<AddUserToGroupPayload> AddUserToGroupAsync(AddUserToGroupInput input,
[ScopedService] AppDbContext context
)
{
int someLimit = 5;
var roleCapacity = context.GroupRoleCapacities.First(grc =>
grc.GroupId == input.GroupId &&
grc.RoleId == input.RoleId);
if(roleCapacity.CapacityUsed > someLimit) {
throw ApplicationException("limit exceeded");
}
//add user to group and save etc
return new AddUserToGroupPayload(result);
}
}
Difference here that Hot Chocolate is used only for GraphQL querying, but your code calls EF Core context explicitly. You need some common part.
What you can do:
Better expose DTO instead of database entity
Write common query which returns this DTO
Feed Hot Chocolate with this query. In this case you do not need resolvers at all (if I understand this library correctly)
Sample query (without DTO, never used Hot Chocolate and not familiar with it's attributes)
public static class MyQueries
{
public static IQueryable<GroupRoleCapacity> GetGroupRoleCapacity(AppDbContext context)
{
return context.GroupRoleCapacities.Select(c => new GroupRoleCapacity
{
GroupId = c.GroupId,
Group = c.Group, // I assume you have defined navigation properties
Role = c.Role,
RoleId = c.RoleId,
CapacityUsed = await context.Users.Count(u => u.GroupId == c.GroupId &&
u.UserRoles.Any(ur => ur.RoleId == c.RoleId))
);
}
}
Query:
//GraphQL Query
public class Query
{
[UseDbContext(typeof(AppDbContext))]
[GraphQLType(typeof(NonNullType<ListType<NonNullType<GroupRoleCapacities.GroupRoleCapacityType>>>))]
public IQueryable<GroupRoleCapacity> GetGroupRoleCapacity([ScopedService] AppDbContext context)
{
return MyQueries.GetGroupRoleCapacity(context);
}
}
Mutation:
//GraphQL Mutation
public class Mutation
{
public Mutation(IConfiguration configuration) { }
[UseDbContext(typeof(AppDbContext))]
public async Task<AddUserToGroupPayload> AddUserToGroupAsync(AddUserToGroupInput input,
[ScopedService] AppDbContext context
)
{
int someLimit = 5;
var roleCapacity = MyQueries.GetGroupRoleCapacity(context)
.FirstAsync(grc =>
grc.GroupId == input.GroupId &&
grc.RoleId == input.RoleId);
if (roleCapacity.CapacityUsed > someLimit)
{
throw ApplicationException("limit exceeded");
}
//add user to group and save etc
return new AddUserToGroupPayload(result);
}
}

ASP.NET core HttpGet single Web API

Good Morning,
I’m having difficulty setting up my HTTPGETs and then testing the solution in Postman.
I’m trying to return a single result on both occasions however when I input the parameters nothing loads. So I'm clearly missing something which i need some help on please.
I have 1 parameter {id} in my CashMovementController and if I navigate to localhost/api/cashmovements/{id} it loads however if pass the {id} parameter in postman it fails.
Then in my BondCreditRatingsController I have 2 parameters {ISIN} & {Date} and again I'm not sure how to approach this.
Love to hear some advice/help on this please
Thanks GWS
Startup.cs
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
CashMovementsController.cs
[Route("api/[controller]")]
public class CashMovementsController : Controller
{
private ICashMovementRepository _cashmovementRepository;
[HttpGet("{id}", Name = "GetCashMovement")]
public IActionResult Get(int id)
{
CashMovement _cashmovement = _cashmovementRepository.GetSingle(u => u.CashMovementId == id);
if (_cashmovement != null)
{
CashMovementViewModel _cashmovementVM = Mapper.Map<CashMovement, CashMovementViewModel>(_cashmovement);
return new OkObjectResult(_cashmovementVM);
}
else
{
return NotFound();
}
}
}
BondCreditRatingsController.cs
[Route("api/[controller]")]
public class BondCreditRatingsController : Controller
{
private IBondCreditRatingRepository _bondcreditratingRepository;
public BondCreditRatingsController(IBondCreditRatingRepository bondcreditratingRepository)
{
_bondcreditratingRepository = bondcreditratingRepository;
}
[HttpGet("{id}", Name = "GetBondCreditRating")]
public IActionResult Get(string id, DateTime efffectivedate)
{
BondCreditRating _bondcreditrating = _bondcreditratingRepository.GetSingle(u => u.ISIN == id, u => u.EffectiveDate == efffectivedate);
if (_bondcreditrating != null)
{
BondCreditRatingViewModel _bondcreditratingVM = Mapper.Map<BondCreditRating, BondCreditRatingViewModel>(_bondcreditrating);
return new OkObjectResult(_bondcreditratingVM);
}
else
{
return NotFound();
}
}
If you want to map it to api/Controller/method/id you would need to use the code below because you want to map parameter order (no other identifier) to a specific parameter name in the action.
[HttpGet("GetCashMovement/{id}")]
Your current code should work with below since you are using named parameters and because the request can't be mapped to any other template.
/api/CashMovements/GetCashMovement?id=1
But that attribute syntax will also (possibly unintentionally) trigger:
/api/CashMovements/1
Since a sum of your defined template for that action is:
[Route("api/[controller]/{id}")]
Reason to why /api/ApiTest/GetCashMovement maps GetCashMovement.Get(int i) is because id is defined as optional in startup
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/**{id?}**");
A question mark (?) after the route parameter name defines an optional
parameter.
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/routing?view=aspnetcore-3.0#create-routes

ServiceStack caching strategy

I'm learning ServiceStack and have a question about how to use the [Route] tag with caching. Here's my code:
[Route("/applicationusers")]
[Route("/applicationusers/{Id}")]
public class ApplicationUsers : IReturn<ApplicationUserResponse>
{
public int Id { get; set; }
}
public object Get(ApplicationUsers request)
{
//var cacheKey = UrnId.Create<ApplicationUsers>("users");
//return RequestContext.ToOptimizedResultUsingCache(base.Cache, cacheKey, () =>
return new ApplicationUserResponse
{
ApplicationUsers = (request.Id == 0)
? Db.Select<ApplicationUser>()
: Db.Select<ApplicationUser>("Id = {0}", request.Id)
};
}
What I want is for the "ApplicationUsers" collection to be cached, and the times when I pass in an Id, for it to use the main cached collection to get the individual object out.
If I uncomment the code above, the main collection is cached under the "users" key, but any specific query I submit hits the Db again. Am I just thinking about the cache wrong?
Thanks in advance,
Mike
this line
var cacheKey = UrnId.Create<ApplicationUsers>("users");
is creating the same cache key for all the requests, you must use some of the request parameters to make a "unique key" for each different response.
var cacheKey = UrnId.Create<ApplicationUsers>(request.Id.ToString());
this will give you the "urn:ApplicationUsers:0" key for the get all and the "urn:ApplicationUsers:9" for the request with Id = 9
now you can use the extension method in this way.
return RequestContext.ToOptimizedResultUsingCache(Cache, cacheKey, () => {
if(request.Id == 0) return GetAll();
else return GetOne(request.Id);
});
I hope this helps, regards.

Return raw objects from Action methods and convert them to JsonResult before rendering

The website that I'm working on is heavily depending on ajax/json and knockout.js.
I would like to have a lot of my Controllers return view-tailored 'json objects', without wrapping them in a JsonResult when returning the method.
This would mean I could easily composite multiple calls into one parent object, but still be able to call the Actions separately too.
Simplified example:
public object Main(int groupId)
{
var viewModel = new
{
Persons = Employees(groupId),
Messages = AllMessages()
};
return viewModel;
}
public object Employees(int groupId)
{
return DatabaseContext.Employees.Where(e => e.GroupId == groupId).ToList();
}
public object AllMessages()
{
return DatabaseContext.Messages.ToList();
}
I was hoping I could capture the returned object in OnActionExecuted and at that point wrap the whole result up in a final JsonResult.
The result is already converted to a string and captured in a ContentResult though.
Any ideas? :) Thanks,
A good approach on this is to create helper methods for your entity calls. Or if you have those methods already somewhere, they can actually serve as the helper methods. In that manner you can return a list of strongly-typed Messages and Employees as well as returning your desired parent object. You can then have individual controller methods that returns json objects. In addition, you can extend the parent viewmodel to return additional fields.
The Parent ViewModel
public class ParentModel {
public Employee Persons {get;set;}
public Message Messages {get;set;}
}
The Helper Methods
The beauty of using helper methods similar to what is defined here is that you can apply a few more logic to your query, and more, and you don't have to change anything in your controller methods.
public ParentModel GetMain(int groupId)
{
var viewModel = new ParentModel
{
Persons = Employees(groupId),
Messages = AllMessages()
};
return viewModel;
}
public IEnumerable<Employee> Employees(int groupId)
{
return DatabaseContext.Employees.Where(e => e.GroupId == groupId).ToList();
}
public IEnumerable<Message> AllMessages()
{
return DatabaseContext.Messages.ToList();
}
The Controller Methods
public ActionResult GetParent(int groupId){
return Json(helperinstance.GetMain());
}
public ActionResult GetEmployees(int groupId){
return Json(helperinstance.Employees());
}
public ActionResult GetMessages(int groupId){
return Json(helperinstance.AllMessages());
}
Thanks for the answer. I'm not going for the solution of von v. because I like to keep the boilerplate as small as possible.
In the end I am trying out the following approach. It seems to work pretty well for now, but I still have to test it in real production.
If anyone has some (security) concerns with this, I'm happy to hear them in the comments.
// BaseController
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
var returnType = ((ReflectedActionDescriptor)filterContext.ActionDescriptor).MethodInfo.ReturnType;
// is the returnType not deriving from ActionResult? Automatically wrap it in a JsonResult
if ( !typeof(ActionResult).IsAssignableFrom(returnType) )
{
var result = filterContext.ActionDescriptor.Execute(filterContext, filterContext.ActionParameters);
filterContext.Result = Json( result );
}
}

LINQ InvalidOperationException when making a query

Ok, last edit, promise:
With the following class:
public partial class MembershipModule : BaseConnection<MembershipModule>
{
private const string AccessPrivilege = "Access";
public bool Accessible()
{
return this.Privileges().Any(p => p.Name.Equals(AccessPrivilege));
}
public IQueryable<MembershipAction> Privileges()
{
var privileges = from p in LinqUtil.Context.MembershipModuleActions
where
p.MembershipModule.Id.Equals(this.Id) &&
p.MembershipRolePrivileges.Any(rp => rp.ModuleActionId.Equals(p.Id))
select p.MembershipAction;
return privileges;
}
}
Why does this work
public static List<MembershipModule> Collection()
{
List<MembershipModule> collection = new List<MembershipModule>();
if(!MembershipUser.Connected)
return collection;
foreach(MembershipModule m in LinqUtil.Context.MembershipModules)
{
if(m.Accessible())
collection.Add(m);
}
return collection;
}
While this doesn't?
public static List<MembershipModule> Collection()
{
if(!MembershipUser.Connected)
return new List<MembershipModule>();
return LinqUtil.Context.MembershipModules.Where(m => m.Accessible()).ToList();
}
looks to me like you are trying to execute a linq to object operation and it is trying to convert it to a sql statement.
Keep in mind: If this were to succede, I expect that the sql related to the folowing call would happen once per MembershipModule:
return this.Privileges().Any(p => p.Name.Equals(AccessPrivilege));
Have you tried skipping using the Accesible method, and hitting the Privileges collection directly? Something like this the code below. I have changed the return statement. This is the gist, may not be 100% correct:
public static List<MembershipModule> Collection()
{
if(!MembershipUser.Connected)
return new List<MembershipModule>();
var mod = MembershipModule.SearchById(new Guid("b012d35f-6af1-47de-9e54-e5df957c07e1"));
var y = from p in LinqUtil.Context.MembershipModuleActions
where
p.MembershipModule.Id.Equals(mod.Id) &&
p.MembershipRolePrivileges.Any(rp => rp.ModuleActionId.Equals(p.Id))
select p.MembershipAction;
var u = y.Any(p => p.Name.Equals(AccessPrivilege));
return LinqUtil.Context.MembershipModules.Where(m => m.Privileges().Any(p => p.Name.Equals(AccessPrivilege)).ToList();
}
Could it have to do with a static method (public static List<_membershipModule> Collection()) of a class trying access instance members (AccessPrivilege)?

Resources