I want to add a url to my DTOs, specifically a canonical link for each object in a collection. I'm using Dapper to map my SQL result into a collection, so I was hoping to do this within the POCO itself.
public class Group {
public string Name { get; set; }
public string url {
get {
//CODE TO BUILD LINK HERE (e.g., https://example.com/v1/groups/1234)
}
}
}
I've seen use of Url.Link() but I've only gotten that to work within my controller - not my url property above. If it can't be done within the POCO, is there a preferred way to update my collection of Group objects after Dapper loads them?
After dapper/service loads the records, you will then need to traverse the collection and generate the URL based on the identifier for the record and the route
Assuming
public class Group {
public string Name { get; set; }
public string Url { get; set; }
}
A controller that generates the record URL could follow the following pattern
[Route("v1/groups")]
public class GroupsController : Controller {
//...omitted for brevity
//GET v1/groups
[HttpGet("")]
public IActionResult Get() {
IEnumerable<Group> groups = service.GetGroups()
.Select(group => {
var url = Url.RouteUrl("GetGroup", new { name = group.Name });
group.Url = url;
return group;
}).ToList();
if(!groups.Any()) return NotFound();
return Ok(groups);
}
//GET v1/groups/1234
[HttpGet("{name}", Name = "GetGroup")]
public IActionResult Get(string name) {
var group = service.GetGroup(name);
if(group == null) return NotFound();
group.Url = Url.RouteUrl("GetGroup", new { name = group.Name });
return Ok(group);
}
}
Reference Generating URLs by route
Related
I got in error when I try to perform Sofdelete
Cannot implicitly convert type
'System.Collections.Generic.List<##.##.Employee>' to
'Microsoft.AspNetCore.Mvc.IActionResult'.
Here is my index I tried to use ToList() and ToList<Employee>, but it's not working
public IActionResult Index()
{
var employees = _dbContext.Employees.Where(x => x.status == '1')
.ToList();
return employees;
}
My DbContext:
public class DataContext : DbContext
{
public DataContext(DbContextOptions options) : base(options)
{
}
public DbSet<Employee> Employees { get; set; }
public override int SaveChanges()
{
foreach( var entry in ChangeTracker.Entries())
{
var entity = entry.Entity;
if (entry.State == EntityState.Deleted)
{
entry.State = EntityState.Modified;
entity.GetType().GetProperty("status").SetValue(entity, '0');
}
}
return base.SaveChanges();
}
}
Employee:
namespace empAPI.Models
{
public class Employee
{
public Guid Id { get; set; }
public char status{ get; set; } = '1';
public string Name { get; set; }
public string Department { get; set; }
public DateTime? CreatedDate { get; set; } = DateTime.Now;
}
}
Change your code to:
public IActionResult Index()
{
var employees = _dbContext.Employees.Where(x => x.status == '1').ToList();
return View(employees);
}
Read the following article: Understanding Action Results
A controller action returns something called an action result. An
action result is what a controller action returns in response to a
browser request.
The ASP.NET MVC framework supports several types of action results
including:
ViewResult - Represents HTML and markup.
EmptyResult - Represents no result.
RedirectResult - Represents a redirection to a new URL.
JsonResult - Represents a JavaScript Object Notation result that can be used in an AJAX application.
JavaScriptResult - Represents a JavaScript script.
ContentResult - Represents a text result.
FileContentResult - Represents a downloadable file (with the binary content).
FilePathResult - Represents a downloadable file (with a path).
FileStreamResult - Represents a downloadable file (with a file stream).
All of these action results inherit from the base ActionResult class.
In most cases, a controller action returns a ViewResult.
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 created a controller for returning JSON to a mobile application. Given this action:
public JsonResult GetFirm(int id)
{
Firm firm = new Firm();
firm = dbContext.Firms.FirstOrDefault(s => s.id == id);
string firmString = JsonConvert.SerializeObject(firm);
return Json(firmString, JsonRequestBehavior.AllowGet);
}
This method(above method) throw self referencing loop error and I write :
Firm firm = new Firm();
firm = dbContext.Firms.FirstOrDefault(s => s.id == id);
string firmString = JsonConvert.SerializeObject(firm, Formatting.None,
new JsonSerializerSettings()
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
});
return Json(firmString, JsonRequestBehavior.AllowGet);
But this method send object with all child and collections and json include "\" before all attribute.
I want to send json object without child or collections.
Analysis and Issue
Your issue is clearly somewhere in your model you have a circular reference. I am highly confident it is due to you are trying to deserialize a database model (an EF model at a guess) directly. Doing that is not recommended.
A database model will contain references typically to other classes, which in themselves reference back to the first class due to representing a database structure. So you likely have something like:
public class ParentItemDataModel
{
[Key]
public Guid Id { get; set; }
public string SomeInfo { get; set; }
public List<ChildItemDataModel> Children { get; set; }
}
public class ChildItemDataModel
{
public ParentItemDataModel Parent { get; set; }
public string SomeInfo { get; set; }
}
So as you can see, a clear reference loop if deserialized.
Recommendation
Separate your API models returned to clients from your underlying database models by creating a new, simple class that contains only the required information such as:
public class ParentItemApiModel
{
public Guid Id { get; set; }
public string SomeInfo { get; set; }
}
Then you can get that information into the model the old-fashioned way:
var parentApiModel = new ParentItemApiModel
{
Id = dataModel.Id,
SomeInfo = dataModel.SomeInfo
};
Or you can do it with an extension method in your data model (so your typically lower core Api model has no reference back up to the database layer):
public static class DataModelExtensions
{
public static ParentItemApiModel ToApiModel(this ParentItemDataModel dataModel)
{
return new ParentItemApiModel
{
Id = dataModel.Id,
SomeInfo = dataModel.SomeInfo
};
}
}
So that now you can do this:
var apiModel = dataModel.ToApiModel();
Or in your example that would then be:
string firmString = JsonConvert.SerializeObject(firm.ToApiModel());
Or any other way you like.
Direct fix
If you do not want to fix it by the recommendation, the quickest, simplest way is to flag your data model Firm properties that you don't want in the JSON model with [JsonIgnore].
Try returing a string instead of JsonResult
public string GetFirm(int id)
{
Firm firm = new Firm();
firm = dbContext.Firms.FirstOrDefault(s => s.id == id);
string firmString = JsonConvert.SerializeObject(firm, Formatting.None,
new JsonSerializerSettings()
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
});
return firmString;
}
I have an Employee class, taken from a database. In that Employee class, I want to add a Manager object, which is also an Employee, based on the managerCode that is in the Employee database. See below for the class.
The managerCode is not defined as a key, . I don’t need recursion, i.e. I don’t need the Manager’s manager, etc. Just one level, the Employee and his manager.
Using .NET 4.5, c#, OData v4
I am using OData to send back the Employee, but the Manager part isn’t added in the response, even if it’s there in the object I try to send.
What am I missing? Something in the WebApiConfig?
Thanks
Employee class, first 4 fields are directly taken from database.
Class Employee
{
[Key]
public int id { get; set; }
public string employeeCode { get; set; }
public string employeeName { get; set; }
public string managerCode { get; set; }
[NotMapped]
public Employee Manager { get; set; }
}
Controller class. GetEmployeeById(id) will get Employee(s) with their Manager.
[HttpGet]
[EnableQuery]
[ODataRoute("employeeById(id={id})")]
public IHttpActionResult employeeById([FromODataUri]int id)
{
var sets = dbContext.GetEmployeeById(id);
if (!sets.Any())
return NotFound();
return this.CreateOKHttpActionResult(sets);
}
WebApiConfig
public static void Register(HttpConfiguration config)
{
config.MapODataServiceRoute("ODataRoute", "odata",GetEdmModel(),
new DefaultODataBatchHandler(GlobalConfiguration.DefaultServer));
config.EnsureInitialized();
}
private static IEdmModel GetEdmModel()
{
var builder = new ODataConventionModelBuilder();
builder.Namespace = "employee_odata";
builder.ContainerName = "employee_odataContainer";
builder.EntitySet<Employee>("Employee");
builder.EnableLowerCamelCase();
var unboundGetEmployee = builder.Function("employeeById");
unboundGetEmployee.Returns<Employee>();
unboundGetEmployee.Parameter<int>("id");
unboundGetEmployee.Namespace = "employee_odata.Functions";
return builder.GetEdmModel();
}
SOLUTION
Remove unboundGetEmployee from WebApiConfig (not needed).
Make Manager item in Employee class virtual, without the [NotMapped]:
public virtual Manager Manager { get; set; }
Controller:
[EnableQuery]
[ODataRoute("Employee({id})")]
public IHttpActionResult employeeById([FromODataUri]int id)
{
//handle data return normally...
//I have to detect $expand=Manager, to fetch the actual Manager object.
//otherwise it's null (not linked with primary key)
}
With these fee changes, $expand is working well.
You need to add $expand to show navigation property, like
localhost\odata\employee_odata.Functions.employeeById(id=1)?$expand=Manager
And for get employee by Id, I suggest you to use this method in controller:
[EnableQuery]
public IHttpActionResult Get(int id)
{
var sets = dbContext.GetEmployeeById(id);
if (!sets.Any())
return NotFound();
return this.CreateOKHttpActionResult(sets);
}
Then request like localhost\odata\Employee(1) can route to that method.
I am using Entity Framework 6 and trying to get some data from my controller. The data is from a table of words which has a key of WordId (the actual words).
My controller has this method:
[Route("Get")]
public IQueryable<Word> Get()
{
return db.Words;
}
Here’s my Word object:
public class Word
{
public string WordId { get; set; } // WordId (Primary key) (length: 20)
public int CategoryId { get; set; }
}
Is there a way that I can use this method to just get those words that have a first character between A and E ?
Never return Entity or IQueryables from the controller, instead create a model and return it as mentioned below:
string matchStr = "abcde";
[Route("Get")]
public List<WordModel> Get()
{
return db.Words.Where(p=>matchStr.ToUpper().Contains(p.UniqueID.ToUpper().FirstOrDefault())).Select(p=>new WordModel(){
WordId = p.WordId,
CategoryId = p.CategoryId
}).ToList();
}
Create a model class WordModel, as
public class WordModel
{
public string WordId { get; set; }
public int CategoryId { get; set; }
}
I agree with Sophia. It is a good idea to expose view models (designed based on the view) rather than the actual entities. Lets assume WordVm is that in your case. The return type form the controller action will be a List. Add a reporsitory or a manager class that will be called from your action method that returns your result. I am creating a manager that has access to the db context class with the Word entity.
[Route("Get")]
public List<WordVm> Get()
{
var manager = new WordsManager()
return manager.GetWords();
}
public class WordsManager
{
public List<WordVm> GetWords()
{
return repo.Words.Where(a => {
var t = a.Trim().ToUpper().Substring(0, 1)[0];
return t >= 'A' && t <= 'E';
}).ToList();
}
}
Thank you,
Soma.