ASP.NET 6 web api with ODATA 8: linked entities - asp.net-web-api

I'm struggling with OData 8 and a fairly simple use case which works perfectly well with the plain Web Api routing but seems impossible with OData (at least with my current models & controllers)
Model:
Say I have 2 entities:
Customer: id (primary key), name
CustomerDetail: id (primary key), postcode, age, customer_id (foreign key)
Basically my customer info is split across 2 tables which are linked with a foreign key (CustomerDetail.customer_id==Customer.id)
Desired routing:
/customers/{id} => to return the Customer(id) entity
/customers/{id}/details => to return the CustomerDetail(id) entity
This works out of the box with vanilla web api routing where I can redefine the route of my CustomerDetail controller.
However OData seems to only respond to /customersdetails/{id}.
Is the desired routing at all possible with odata?
What would be the steps involved?
Thanks for your help.
namespace Api.Models
{
public partial class Customer
{
[Key]
public long id { get; set; }
...
}
public partial class CustomerDetail
{
[Key]
public long id { get; set; }
...
public long customer_id { get; set; }
}
}
namespace Api.Controllers.Odata
{
[Route("odata/customers")]
[Authorize]
public class CustomersController : ODataController
{
...
[EnableQuery(PageSize = 20)]
public IQueryable<Customer> Get()
{
return _context.Customer.AsQueryable();
}
[EnableQuery(PageSize = 1)]
[HttpGet("{id}")]
public async Task<ActionResult<Customer>> GetCustomer(int id)
{
var cust= await _context.Customer.FindAsync(id);
return cust;
}
}
}
namespace Api.Controllers.Odata
{
[Route("odata/customersdetails")]
[Authorize]
public class CustomersDetailsController : ODataController
{
...
[EnableQuery(PageSize = 20)]
public IQueryable<CustomerDetail> Get()
{
return _context.CustomerDetail.AsQueryable();
}
[EnableQuery(PageSize = 1)]
[HttpGet("{customer_id}")]
public async Task<ActionResult<CustomerDetail>> GetCustomerDetail(int customer_id )
{
var cust= await _context.CustomerDetail.Where(b => b.customer_id == customer_id ).SingleOrDefaultAsync();
return cust;
}
}
}
With this setup /customersdetails/{id} works and is recognized and served as a OData path.
If [Route("odata/customersdetails")] is changed to [Route("odata/customers/details")] and [HttpGet("{customer_id}")] to [HttpGet("odata/customers/{customer_id}/details")]:
Get: generate ODataException: The key value (details) from request is not valid. The key value should be format of type 'Edm.Long'.
-GetCustomerDetail: is not recognized and returns 404

Related

How to publish a specific event from an implemented interface

Full code (github)...
I'm moving from MediatR to MassTransit to publish my domain events to a queue.
I'm using an interface IDomainEvent in different domain events that implement such interface (in this case PersonCreated and PersonPositionCreated). Then I have an entity 'Person' with a list of IDomainEvent in which I register all the domain events occurred. I Also have a consumer for each specific event.
After persist my entity, I want to iterate all the events of the entity and publish them to the queue.
// Event interface.
public class IDomainEvent
{
}
// Events.
public class PersonCreated : IDomainEvent
{
public int Id { get; set; }
}
public class PersonPositionCreated : IDomainEvent
{
public string Position { get; set; }
}
// Entity.
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public string Position { get; set; }
public List<IDomainEvent> Events { get; set; };
}
// Consumers.
public class PersonCreatedConsumer : IConsumer<PersonCreated>
{
public Task Consume(ConsumeContext<PersonCreated> context)
{
Debug.Print(context.Message.Id.ToString());
return Task.CompletedTask;
}
}
public class PersonPositionCreatedConsumer : IConsumer<PersonPositionCreated>
{
public Task Consume(ConsumeContext<PersonPositionCreated> context)
{
Debug.Print(context.Message.Position);
return Task.CompletedTask;
}
}
// My command.
//...
// Creates Person.
Person person = new Person(){ Id = 1, Name = "Alice", Position = "Developer" };
// Registers the events to the list.
person.Events.Add(new PersonCreated() { Id = person.Id });
person.Events.Add(new PersonPositionCreated() { Position = person.Position });
foreach (IDomainEvent personEvent in person.Events)
{
// This way, it publish an IDomainEvent and I don't want to use a IConsumer<IDoaminEvent> because I need specific consumers.
// How can I tell the bus that I sending the specific event and not the IDomainEvent?
//(I know that inside the iteration I'm dealing with IDomainEvent but it have access to the class that implement the interface).
// NOTE: That way works with MediatR specific handlers.
| |
\ /
\ /
\/
_bus.Publish(personEvent);
}
// Of course this two lines works!
//_bus.Publish<PersonCreated>(new PersonCreated() { Id = 1 });
//_bus.Publish<PersonPositionCreated>(new PersonPositionCreated() { Position = "Developer" });
//...
How can I tell the bus that I am sending the specific event and not the IDomainEvent?
(I know that inside the iteration I'm dealing with IDomainEvent, but it has access to the class that implement the interface).
You should call:
_bus.Publish((object)personEvent);
By casting it to an object, MassTransit will call GetType() and then publish to the exchange (and ultimately consumer) for the object type (aka, implemented message type).

HotChocolate GraphQL query to page/filter/sort on nested array fields

HotChocolate Version=12.3.2.0
I want to be able to page/filter/sort on nested fields. For example, where user id = 1234, get the user's 1st document set, then the 1st docFile in the document set, ordered by docFile createdDate.
public class User
{
public int Id {get;set}
[UsePaging]
[UseFiltering]
[UseSorting]
public List<Document> Documents { get; set; }
}
public class Document
{
[UsePaging]
[UseFiltering]
[UseSorting]
public List<DocFile> DocFiles { get; set; }
public User User {get;set;}
}
public class DocFile
{
public string Name {get;set}
public DateTime CreatedDate {get;set;}
public Document Document {get;set;}
}
[UseAppDbContext]
[UsePaging]
[UseProjection]
[UseFiltering]
[UseSorting]
public async Task<Connection<User>> GetUsersAsync(
IResolverContext context,
[ScopedService] DbContext dbContext,
CancellationToken cancellationToken
)
{
var dbResult = dbContext.Users.Filter(context).Sort(context).Project(context).ToArray();
var result = await dbResult.ApplyCursorPaginationAsync(context, cancellationToken);
return result;
}
GraphQL Query
users(
where: {id: {eq: 1234}}
) {
nodes {
documents(first:1){
id
files(first:1 order:{createdDate: DESC}) {
nodes {
name
createdDate
}
}
}
}
}
But when I execute the GraphQL query I currently get the following error:
"exceptionType": "InvalidOperationException",
"message": "No generic method 'OrderByDescending' on type 'System.Linq.Enumerable' is compatible with the supplied type arguments and arguments. No type arguments should be provided if the method is non-generic. "
Any idea on how to do this?
If the [UseSorting] annotation comes from HotChocolate.Types it's the old way of filtering (uses a different syntax). Then it should be order_by in your query.
Try [HotChocolate.Data.UseSorting] to match your query.

swagger swashbuckle does not support nested class as action method parameter

I am using asp.net 5
I have two model class, which are nested, both of the inner class are named Command
public class EditModel
{
public class Command
{
public int Id { get; set; }
public string Info { get; set; }
}
}
and
public class CreateModel
{
public class Command
{
public int Id { get; set; }
public string Info { get; set; }
}
}
In my Controller class has two methods
[HttpPost]
public IActionResult PutData(CreateModel.Command model)
{
return Ok();
}
[HttpPut]
public IActionResult PostData(EditModel.Command model)
{
return Ok();
}
Since for both Put and Post's query I am using nested class both name Command, Swagger will return the following error
An unhandled exception has occurred while executing the request.
Swashbuckle.AspNetCore.SwaggerGen.SwaggerGeneratorException: Conflicting method/path combination "PUT Test" for actions -
TestSwagger.Controllers.TestController.PutData
(TestSwagger),TestSwagger.Controllers.TestController.PostData
(TestSwagger). Actions require a unique method/path combination for
Swagger/OpenAPI 3.0. Use ConflictingActionsResolver as a workaround
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GenerateOperations(IEnumerable1 apiDescriptions, SchemaRepository schemaRepository) at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GeneratePaths(IEnumerable1
apiDescriptions, SchemaRepository schemaRepository)
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GetSwagger(String
documentName, String host, String basePath)
at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext
httpContext, ISwaggerProvider swaggerProvider)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext
context)
Swagger will work, if I change one of the Command model name to something different.
Yet, I believe this nested class model name is legit and should work with swagger also. If there a way to work around this. Thanks
By adding c.CustomSchemaIds(x => x.FullName);
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "TestSwagger", Version = "v1" });
c.CustomSchemaIds(x => x.FullName);
});
solved the schemaId conflict. Thanks to this question

Enum not coverting expando object to XML in .net Core Web API GET response

I have a get method on a .net Core 3.1 Web API controller that returns an expando object which is generated from a model class.
The model contains an enum value:
public class MyModel
{
public int Id { get; set; }
public string Name { get; set; }
public Gender Gender { get; set; }
}
public enum Gender
{
Male,
Female
}
[HttpGet("{id}")]
public async Task<IActionResult> GetAsync(int id)
{
var recordFromDB = await dbService.GetAsync(id);
if (recordFromDB == null)
return NotFound();
var returnModel = mapper.Map<MyModel>(recordFromDB).ShapeData(null);
return Ok(returnModel);
}
public static ExpandoObject ShapeData<TSource>(this TSource source, string fields)
{
var dataShapedObject = new ExpandoObject();
if (string.IsNullOrWhiteSpace(fields))
{
var propertyInfos = typeof(TSource).GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var propertyInfo in propertyInfos)
{
var propertyValue = propertyInfo.GetValue(source);
((IDictionary<string, object>)dataShapedObject).Add(propertyInfo.Name, propertyValue);
}
return dataShapedObject;
}
... more of the method here but it's never hit so I've removed the code
}
If I do a get request for this record with the accept header application/json it all works fine.
if I change the accept header to application/xml I receive the following error:
System.Runtime.Serialization.SerializationException: Type 'Gender'
with data contract name 'Gender' is not expected. Add any types not
known statically to the list of known types - for example, by using
the KnownTypeAttribute attribute or by adding them to the list of
known types passed to DataContractSerializer.
If I remove the enum value from the model the XML is generated fine.
Where do I add the KnownTypeAttribute or how do I get around this error?
Thanks in advance

ASP.NET MVC Patterns

I am fairly new to MVC, but after playing with it (MVC 3/Razor), I am hooked.
I have a few questions:
1) What is the best, or most widely used pattern to develop MVC apps in? Repository, DDD, UOW?
2) I am using the Entity Framework 4, so could some please explain to me or point me to a good source that will explain the Repository Pattern w/EF4? Doesn't EF4 take place as the business layer and the data access layer? Does the Repository Pattern even provide a benefit?
3) Also, one last question, could someone explain the whole relationship between the Controller, the Model and the View? I get the basics, but maybe a little more in depth of the correct way to use it. View Models - Say I have a view that displays customer info, and one that edits it, should I have a view model and an edit model, or can the be passed around?
4) Examples??
Thanks for the help up front,
$("Sam")
** EDIT **
Am I on the right track here:
Public Class HomeController
Inherits System.Web.Mvc.Controller
Function Index(ByVal id As Integer) As ActionResult
Return View(New HomeModel)
End Function
<HttpPost()> _
Function Index(ByVal Model As HomeModel) As ActionResult
Return View(Model)
End Function
End Class
Public Class HomeModel
Private _Repository As IRepository(Of Customer)
Public Property Customer As Customer
Public Sub New()
End Sub
Public Sub New(ByVal ID As Integer)
_Repository = New CustomerRepository
Customer = _Repository.GetByID(ID)
End Sub
End Class
Public Interface IRepository(Of T)
Function GetByID(ByVal ID As Integer) As T
Sub Add(ByVal Entity As T)
Sub Delete(ByVal Entity As T)
End Interface
Public Class CustomerRepository
Implements IRepository(Of Customer)
Public Sub Add(ByVal Entity As Customer) Implements IRepository(Of Customer).Add
End Sub
Public Sub Delete(ByVal Entity As Customer) Implements IRepository(Of Customer).Delete
End Sub
Public Function GetByID(ByVal ID As Integer) As Customer Implements IRepository(Of Customer).GetByID
Return New Customer With {.ID = ID, .FirstName = "Sam", .LastName = "Striano"}
End Function
End Class
Public Class Customer
Public Property ID As Integer
Public Property FirstName As String
Public Property LastName As String
End Class
I use generic repositories that get instantiated in a service class (using Dependency Injection with Ninject).
The service class essentially performs two functions:
It provides all the methods that the controller will consume.
It has a property called ViewModel, that essentially maps the data that the views need into a MyViewModel class.
The Controller consumes the service class. With this "pattern", your controllers look like:
namespace ES.eLearningFE.Areas.Courses.Controllers
{
public partial class CourseController : Controller
{
ICourseDisplayService service;
public CourseController(ICourseDisplayService service)
{
this.service = service;
}
public virtual ActionResult Display(int CourseId, int StepOrder, string PupilName, string TutorName)
{
service.CourseId = CourseId;
service.StepOrder = StepOrder;
service.PupilName = PupilName;
service.TutorName = TutorName;
if (Request.IsAjaxRequest())
{
return PartialView(service.ViewModel);
}
else
{
return View(service.ViewModel);
}
}
}
}
The ViewModel class only hold display data and no methods (except the odd really simple method to retrieve data from another property that is, for example a List<> object).
Works really well. An example of a service class:
namespace ES.eLearning.Domain.Services.Courses
{
public class SqlCourseDisplayService : ICourseDisplayService
{
DataContext db;
public SqlCourseDisplayService(DbDataContextFactory contextFactory)
{
db = contextFactory.Make();
CoursesRepository = new SqlRepository<Course>(db);
StepsRepository = new SqlRepository<CourseStep>(db);
StepLinksRepository = new SqlRepository<StepLink>(db);
UserCoursesRepository = new SqlRepository<UserCourse>(db);
CourseTutorsRepository = new SqlRepository<CourseTutor>(db);
UsersRepository = new SqlRepository<User>(db);
}
#region ICourseDisplayService Members
public ViewModels.CourseDisplayVM ViewModel
{
get
{
return new ViewModels.CourseDisplayVM
{
CourseId = this.CourseId,
CourseName = this.Course.Name,
Steps = this.Steps,
ActiveStepIndex = this.ActiveStepIndex,
CurrentStepIndex = this.CurrentStepIndex,
Pupil = new UserDto { UserId = this.PupilId, UserName = this.PupilName },
Tutors = this.GetTutors(this.CourseId),
Tutor = tutorName == null ? null : new UserDto { UserName = this.TutorName, UserId = this.TutorId}
};
}
}
#region Entities
int courseId;
public int CourseId
{
get
{
if (courseId == 0) throw new ApplicationException("Invalid Course Id!");
return courseId;
}
set
{
if (value == 0) throw new ApplicationException("Invalid Course Id!");
try
{
Course = (from c in CoursesRepository.Query where c.CourseId == value select c).First();
Steps = Course.CourseSteps.ToList();
courseId = value;
}
catch {throw new ApplicationException("No Course found for Course Id: " + value);}
}
}
public Data.Course Course { get; private set; }
public int StepOrder { get; set; }
public List<Data.CourseStep> Steps { get; private set; }
public int ActiveStepIndex
{
get
{
if (PupilName == null)
{
throw new ApplicationException("Pupil not set!");
}
if (CourseId == 0)
{
throw new ApplicationException("Course not set!");
}
try
{
var x = (from uc in UserCoursesRepository.Query where (uc.IdCourse == CourseId) && (uc.UserName == PupilName) select uc).First();
return x.ActiveStepIndex;
}
catch { throw new ApplicationException("Could not get Active Step!"); }
}
}
#endregion
#region Users
string tutorName;
public string TutorName
{
get
{
if (tutorName == null) throw new ApplicationException("Invalid call to get Tutor Name [Null Tutor Name]!");
return tutorName;
}
set
{
tutorName = value;
TutorId = (Guid)Membership.GetUser(tutorName).ProviderUserKey;
}
}
public Guid TutorId { get; set; }
string pupilName;
public string PupilName
{
get { return pupilName; }
set
{
pupilName = value;
PupilId = (Guid)Membership.GetUser(pupilName).ProviderUserKey;
}
}
public Guid PupilId { get; set; }
#endregion
#region Utility Properties
public int CurrentStepIndex { get; set; }
public int StepCount
{
get
{
return Steps == null ? 0 : Steps.Count();
}
}
#endregion
#region Private Utilities
private List<UserDto> GetTutors(int CourseId)
{
return (from ct in CourseTutorsRepository.Query join u in UsersRepository.Query
on ct.TutorName equals u.UserName
where (ct.CourseId == courseId)
select new UserDto { UserName = ct.TutorName, UserId = u.UserId }).ToList();
}
#endregion
#region Repositories
private IRepository<Course> CoursesRepository
{
get;
set;
}
private IRepository<CourseStep> StepsRepository
{
get;
set;
}
private IRepository<StepLink> StepLinksRepository
{
get;
set;
}
private IRepository<UserCourse> UserCoursesRepository
{
get;
set;
}
private IRepository<CourseTutor> CourseTutorsRepository
{
get;
set;
}
private IRepository<User> UsersRepository
{
get;
set;
}
#endregion
#endregion
}
}
May not be everyone's choice, but hey, it works for me... AND (more importantly) my clients and their users.
Edit
As requested in the comment below, the Repository that I use:
namespace ES.eLearning.Domain
{
public class SqlRepository<T> : IRepository<T> where T : class
{
DataContext db;
public SqlRepository(DataContext db)
{
this.db = db;
}
#region IRepository<T> Members
public IQueryable<T> Query
{
get { return db.GetTable<T>(); }
}
public List<T> FetchAll()
{
return Query.ToList();
}
public void Add(T entity)
{
db.GetTable<T>().InsertOnSubmit(entity);
}
public void Delete(T entity)
{
db.GetTable<T>().DeleteOnSubmit(entity);
}
public void Attach(T entity)
{
db.GetTable<T>().Attach(entity);
}
public void Save()
{
db.SubmitChanges();
}
#endregion
}
}
And the IRepository Interface:
namespace Wingspan.Web.Mvc
{
public interface IRepository<TEntity> where TEntity : class
{
List<TEntity> FetchAll();
IQueryable<TEntity> Query {get;}
void Add(TEntity entity);
void Delete(TEntity entity);
void Attach(TEntity entity);
void Save();
}
}
This should help you getting started. There are a lot of tutorials and videos available; for example:
Understanding Models, Views and Controllers
The ASP.NET MVC 2.0 basics and excellent introduction by Scott Hanselman. Personally one of my favorite speakers.
And also at www.asp.net; there are a few tutorials/examples to help you getting started. For example the Music Store sample
Unfortunately, I'm not so familiar with EF4/Repository pattern. But here's a blogpost about this pattern.
1) I would say that the repository pattern is the most widely used, then there is inversion of controll too.
2) I can't really point out the benefits with using a repository for entity framework other than that the controller should not know about how to acces data other then asking a repository. This makes it easy to switch it out sometime.
You can also eager load the data to make sure that the view don't call the database in every iteration of a foreach, for example a collection of users to display data from a child entity. You can probly do this anyway, but I feel that the repository is the right place to do it.
3) I can't tell you about the concept in a more in depth way, but I can tell some about viewmodels. In my opinion you should only use viewmodels if there is anything more then one entity you want to send to the view, for example a list of countries. You can alo use a viewmodel to "flatten" out very complex objects.
I would defiantly say the repository pattern is used a lot. This pattern can be used with Dependency Injection. Using Dependency Injection makes Unit Testing a breeze because you can snap different repositories to an abstract repoistory. Check out http://ninject.org/ for a simple to use Dependecy injector for .NET.
View Models should hold display data and transfer that data from the controller to the view. If you want to edit and display customer info, take a look at this

Resources