ASP.NET Web API Contract Versioning - asp.net

We would like to achieve version based API using content negotiation in accept header.
We are able to achieve for controller & API methods with some inheritance and extending the default HTTP selector.
Controller inheritance is achieved using following sample code,
public abstract class AbstractBaseController : ApiController
{
// common methods for all api
}
public abstract class AbstractStudentController : AbstractBaseController
{
// common methods for Student related API'sample
public abstract Post(Student student);
public abstract Patch(Student student);
}
public class StudentV1Controller : AbstractStudentController
{
public override Post([FromBody]Student student) // student should be instance of StudentV1 from JSON
{
// To Do: Insert V1 Student
}
public override Patch([FromBody]Student student) // student should be instance of StudentV1 from JSON
{
// To Do: Patch V1 Student
}
}
public class StudentV2Controller : AbstractStudentController
{
//
public override Post([FromBody]Student student) // student should be instance of StudentV2 from JSON
{
// To Do: Insert V2 Student
}
}
public abstract class Student
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class StudentV1 : Student
{
}
public class StudentV2 : Student
{
public string Email { get; set; }
}
We have created above architecture to do less code with change in version, say if version 1 has 10 API methods and there is a change in one API method than it should be available in version 2 code without modifying other 9(they are inherited from version 1).
Now, the main problem we are facing is in contract versioning as we cannot instantiate an instance of an abstract student. When someone is posting JSON to API version 1 instance of StudentV1 should be passed in methods and same in version 2.
Is there any way to achieve this?
Thanks in advance!!

ASP.NET API Versioning is capable of achieving your goals. First, you'll want to add a reference to the ASP.NET Web API API Versioning NuGet package.
You would then configure your application something like:
public class WebApiConfig
{
public static void Configure(HttpConfiguration config)
{
config.AddApiVersioning(
options => options.ApiVersionReader = new MediaTypeApiVersionReader());
}
}
Your controllers might look something like:
namespace MyApp.Controllers
{
namespace V1
{
[ApiVersion("1.0")]
[RoutePrefix("student")]
public class StudentController : ApiController
{
[Route("{id}", Name = "GetStudent")]
public IHttpActionResult Get(int id) =>
Ok(new Student() { Id = id });
[Route]
public IHttpActionResult Post([FromBody] Student student)
{
student.Id = 42;
var location = Link("GetStudent", new { id = student.Id });
return Created(location, student);
}
[Route("{id}")]
public IHttpActionResult Patch(int id, [FromBody] Student student) =>
Ok(student);
}
}
namespace V2
{
[ApiVersion("2.0")]
[RoutePrefix("student")]
public class StudentController : ApiController
{
[Route("{id}", Name = "GetStudentV2")]
public IHttpActionResult Get(int id) =>
Ok(new Student() { Id = id });
[Route]
public IHttpActionResult Post([FromBody] StudentV2 student)
{
student.Id = 42;
var location = Link("GetStudentV2", new { id = student.Id });
return Created(location, student);
}
[Route("{id}")]
public IHttpActionResult Patch(int id, [FromBody] StudentV2 student) =>
Ok(student);
}
}
}
I strongly advise against inheritance. It's possible, but is the wrong approach to the problem IMO. Neither APIs nor HTTP support inheritance. That is an implementation detail of the backing language, which is also somewhat of an impedance mismatch. A key problem is that you cannot uninherit a method and, hence, nor an API.
If you really insist on inheritance. Choose one of the following options:
Base class with only protected members
Move business logic out of the controllers
Use extension methods or other collaborators to fulfill shared operations
For example, you might do something like this:
namespace MyApp.Controllers
{
public abstract class StudentController<T> : ApiController
where T: Student
{
protected virtual IHttpActionResult Get(int id)
{
// common implementation
}
protected virtual IHttpActionResult Post([FromBody] T student)
{
// common implementation
}
protected virtual IHttpActionResult Patch(int id, [FromBody] Student student)
{
// common implementation
}
}
namespace V1
{
[ApiVersion("1.0")]
[RoutePrefix("student")]
public class StudentController : StudentController<Student>
{
[Route("{id}", Name = "GetStudentV1")]
public IHttpActionResult Get(int id) => base.Get(id);
[Route]
public IHttpActionResult Post([FromBody] Student student) =>
base.Post(student);
[Route("{id}")]
public IHttpActionResult Patch(int id, [FromBody] Student student) =>
base.Patch(student);
}
}
namespace V2
{
[ApiVersion("2.0")]
[RoutePrefix("student")]
public class StudentController : StudentController<StudentV2>
{
[Route("{id}", Name = "GetStudentV2")]
public IHttpActionResult Get(int id) => base.Get(id);
[Route]
public IHttpActionResult Post([FromBody] StudentV2 student) =>
base.Post(student);
[Route("{id}")]
public IHttpActionResult Patch(int id, [FromBody] StudentV2 student) =>
base.Patch(student);
}
}
}
There are other ways, but that is one example. If you define a sensible versioning policy (ex: N-2 versions), then the amount of duplication is minimal. Inheritance will likely cause more problems than it solves.
When you version by media type, the default behavior uses the v media type parameter to indicate the API version. You can change name if you wish. Other forms of versioning by media type are possible (ex: application/json+student.v1, you'd need a custom IApiVersionReader as there is no standard format. In addition, you'll have to update the ASP.NET MediaTypeFormatter mappings in the configuration. The built-in media type mapping does not consider media type parameters (e.g. the v parameter has no impact).
The following table shows the mapping:
Method
Header
Example
GET
Accept
application/json;v=1.0
PUT
Content-Type
application/json;v=1.0
POST
Content-Type
application/json;v=1.0
PATCH
Content-Type
application/json;v=1.0
DELETE
Accept or Content-Type
application/json;v=1.0
DELETE is an outlier case as it doesn't require a media type in or out. Content-Type will always take precedence over Accept because it is required for the body. A DELETE API can be made API version-neutral, meaning will take any API version, including none at all. This may be useful if you want to allow DELETE without requiring a media type. Another alternative could be to use media type and query string versioning methods. This would allow specifying the API version in the query string for DELETE APIs.
Over the wire, it will look like:
Request
POST /student HTTP/2
Host: localhost
Content-Type: application/json;v=2.0
Content-Length: 37
{"firstName":"John","lastName":"Doe"}
Response
HTTP/2 201 Created
Content-Type: application/json;v=2.0
Content-Length: 45
Location: http://localhost/student/42
{"id":42,"firstName":"John","lastName":"Doe"}

Based on your pasted code you could make AbstractStudentController generic.
Because those APIs that you declare abstract must be implemented in every API version, and you can define type with the generic. I hope I'm not missing something from your description, because Patch is missing from your implementation in StudentV2Controller, but is declared abstract. Do you wanted to derive StudentV2Controller from StudentV1Controller?
public abstract class AbstractBaseController : ApiController
{
// common methods for all api
}
public abstract class AbstractStudentController<StudentType> : AbstractBaseController
{
// common methods for Student related API'sample
public abstract Post(StudentType student);
public abstract Patch(StudentType student);
}
public class StudentV1Controller : AbstractStudentController<StudentV1>
{
public override Post([FromBody]StudentV1 student) // student should be instance of StudentV1 from JSON
{
// To Do: Insert V1 Student
}
public override Patch([FromBody]StudentV1 student) // student should be instance of StudentV1 from JSON
{
// To Do: Patch V1 Student
}
}
public class StudentV2Controller : AbstractStudentController<StudentV2>
{
//
public override Post([FromBody]StudentV2 student) // student should be instance of StudentV2 from JSON
{
// To Do: Insert V2 Student
}
}

Related

ASP.NET Core Web API: Routing by method name?

I remember from ASP.NET Web API that it's sufficient to prefix Web API REST method names with HTTP commands (e.g. GetList() => HTTP GET, Delete() => HTTP DELETE) to have incoming calls appropriately routed.
I also remember that in ASP.NET Web API parameter matching takes place so that even Get(int id) and Get(int id, string name) get automatically and appropriately routed without requiring any attributes.
public class MyController
{
public ActionResult Get(int id) => ...
public ActionResult Get(int id, string name) => ...
public ActionResult DeleteItem(int id) => ...
}
Isn't this all available in ASP.NET Web API Core?
You just need to add the Route to the top of your controller.
Specify the route with api, controller and action:
[Route("api/[controller]/[action]")]
[ApiController]
public class AvailableRoomsController : ControllerBase
{
...
}
Neither could we do action overloads nor prefix action name as Http verb.The way routing works in ASP.NET Core is different than how it did in ASP.NET Web Api.
However, you can simply combine these actions and then branch inside, since all params are optional if you send as querystring
[HttpGet]
public ActionResult<string> Get(int id, string name)
{
if(name == null){..}
else{...}
}
Or you need to use attribute routing to specify each api if you send in route data:
[HttpGet("{id}")]
public ActionResult<string> Get(int id)
{
return "value";
}
[HttpGet("{id}/{name}")]
public ActionResult<string> Get(int id, string name)
{
return name;
}
Refer to Attribute Routing,Web Api Core 2 distinguishing GETs
The aspnetcore webapi controllers do not natively support inference of http verbs by naming convention, but allow you to create your own convention and achieve this behavior.
Create your convention
public class MyConvention : IApplicationModelConvention
{
public void Apply(ApplicationModel application)
{
foreach(var controller in application.Controllers)
{
foreach(var action in controller.Actions)
{
if (action.ActionName.StartsWith("Post"))
{
action.Selectors.First().ActionConstraints.Add(new HttpMethodActionConstraint(new[]{ "POST" }));
}
}
}
}
}
Then register it in Program/Startup:
builder.Services.AddControllers(configure => configure.Conventions.Insert(0, new MyConvention()));
This is available for Core 2 yes, but the way that I know how to do it is something like this
[Route("api/[controller]")]
[ApiController]
public class AvailableRoomsController : ControllerBase
{
private readonly ApplicationContext _context;
public AvailableRoomsController(ApplicationContext context)
{
_context = context;
}
// GET: api/AvailableRooms
[HttpGet]
public async Task<ActionResult<IEnumerable<AvailableRoom>>> GetAvailableRooms()
{
return await _context.AvailableRooms.ToListAsync();
}
// POST: api/AvailableRooms
[HttpPost]
public async Task<ActionResult<AvailableRoom>> PostAvailableRoom(AvailableRoom availableRoom)
{
_context.AvailableRooms.Add(availableRoom);
await _context.SaveChangesAsync();
return CreatedAtAction("GetAvailableRoom", new { id = availableRoom.Id }, availableRoom);
}
[HttpPut] .... etc
}
Now depending on what kind of REST action you specify and what type of model you send to "api/AvailableRooms" if the proper Action exists it will be chosen.
Visual Studio 2019 and I think 2017 can create a controller such as this automatically if you right click your Controllers folder and click Add->Controller and then choose "API Controller with actions, using Entity Framework" and choose one of your Model classes.

Swagger different classes in different namespaces with same name don't work

I got (more than) two Api POST endpoints. Each one needs a json as parameter. But when I use the same class name Payload in two endpoint argument classes, Swagger does not work. When I change one of it e.g. from Payload to Payload1 than it works.
Of course I set the right namespaces into the wrapper classes so it finds it Payload. But I would love to use the same name "Payload" each time. How can I use the same class name Payload?
I can keep the json name "Payload" at both cases and just set different names for the property ("Payload1", "Payload2"). It works. But would be nice to have same property names too.,
Endpoint A
[HttpPost()]
public async Task PostPerson([FromBody]JsonWrapperA jsonWrapperA)
namespace myProject.A
{
public class JsonWrapperA
{
[JsonProperty("name", Required = Required.AllowNull)]
public string Name { get; set; }
[JsonProperty("payload", Required = Required.AllowNull)]
public Payload Payload { get; set; }
}
public class Payload
{
[JsonProperty("value", Required = Required.AllowNull)]
public double Value { get; set; }
}
}
Endpoint B
[HttpPost()]
public async Task PostCompagn([FromBody]JsonWrapperB jsonWrapperB)
namespace myProject.B
{
public class JsonWrapperB
{
[JsonProperty("compagny", Required = Required.AllowNull)]
public string Compagny { get; set; }
[JsonProperty("payload", Required = Required.AllowNull)]
public Payload Payload { get; set; }
}
public class Payload
{
[JsonProperty("age", Required = Required.AllowNull)]
public double Age{ get; set; }
}
}
By default swagger will attempt to build its Schema Ids for objects that are return types or parameter types for your APIs endpoints, and it will display these objects in the "Models" section of the documentation. It will build these schema Ids based on the class names of the objects.
When you try to have two or more classes named the same, even though they are in different namespaces, then you will get the conflicting schemaIds error:
InvalidOperationException: Conflicting schemaIds: Identical schemaIds detected for types NamespaceOne.MyClass and NamespaceTwo.MyClass. See config settings - "CustomSchemaIds" for a workaround
This means Swagger needs to be configured to change the way it generates its SchemaIds. You can simply tell swagger to use an objects fully qualified name which will include namespaces in the schemaIds. You can do this in your Startup.cs file in the ConfigureServices method like this:
//add using statement for Swagger in Startup.cs
using Swashbuckle.AspNetCore.Swagger;
...
public void ConfigureServices(IServiceCollection services)
{
services.AddSwaggerGen(config =>
{
//some swagger configuration code.
//use fully qualified object names
config.CustomSchemaIds(x => x.FullName);
}
}
Using Swashbuckle.AspNetCore Version 5.5.1 i've had the same issue so i solved it using JustSomeDude answer, but afterwards all entities were shown with the full name so i needed a way to show only the name. This is what i did:
options.CustomSchemaIds(x => x.FullName); // Enables to support different classes with the same name using the full name with namespace
options.SchemaFilter<NamespaceSchemaFilter>(); // Makes the namespaces hidden for the schemas
Using this filter class:
public class NamespaceSchemaFilter : ISchemaFilter
{
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
if (schema is null)
{
throw new System.ArgumentNullException(nameof(schema));
}
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
schema.Title = context.Type.Name; // To replace the full name with namespace with the class name only
}
}

Same Policy but different required parameter for each action method

In a .Net core Webapi 2.1 project, I have a tons of action methods.
All action methods should be authorized against the same policy (named FooPolicy) but with a different required argument.
Based on Microsoft's docs: Policy-based-Authorization
One way would be to declare a tons of policies based on different input argument:
services.AddAuthorization(options =>
{
options.AddPolicy("FooPolicy1", policy =>policy.Requirements.Add(new FooRequirement(1)));
options.AddPolicy("FooPolicy2", policy =>policy.Requirements.Add(new FooRequirement(2)));
options.AddPolicy("FooPolicy3", policy =>policy.Requirements.Add(new FooRequirement(3)));
//... May be 30 more same policies here ...
});
As i earlier mentioned, only different part is in new FooRequirement(diffArgs). The other challenge for this solution would be to add each FooPolicy on it's corresponding action method and you may miss a couple of theme:
[Authorize(Policy = "FooPolicy1")]
public IActionResult ActionMethodFoo1(...) {...}
[Authorize(Policy = "FooPolicy2")]
public IActionResult ActionMethodFoo2(...) {...}
[Authorize(Policy = "FooPolicy3")]
public IActionResult ActionMethodFoo3(...) {...}
...List still goes on...
Is there any solution like: declare a policy once but use it with different instance of FooRequirement (which is of type IAuthorizationHandler)? like so:
services.AddAuthorization(options =>
{
options.AddPolicy("FooPolicy", policy =>policy.Requirements.Add(?));
});
And on the action methods:
[Authorize(Policy = "FooPolicy", required = new FooRequirement(1))]
public IActionResult ActionMethodFoo1(...) {...}
[Authorize(Policy = "FooPolicy", required = new FooRequirement(2))]
public IActionResult ActionMethodFoo2(...) {...}
[Authorize(Policy = "FooPolicy", required = new FooRequirement(3))]
public IActionResult ActionMethodFoo3(...) {...}
The main idea is to declare policy once. Two recent code blocks are psudo-code, Does any body knows practical solution with similar concept?
You could implement your own IAuthorizationFilter
custom IAuthorizationFilter
public class CustomAuthorize : IAuthorizationFilter
{
private readonly int _input;
public CustomAuthorize(int input)
{
_input = input;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
//custom validation rule
if (_input == 1)
{
context.Result = new ForbidResult();
}
}
}
Custom CustomAuthorizeAttribute
public class CustomAuthorizeAttribute : TypeFilterAttribute
{
public CustomAuthorizeAttribute(int input) : base(typeof(CustomAuthorize))
{
Arguments = new object[] { input };
}
}
Use
[CustomAuthorizeAttribute(1)]
public IActionResult About()

How to specify default property values for owned entity types in Entity Framework Core 2.0?

I have a simple POCO type, say something like
public class OwnedEntity {
public string stringProperty { get; set; }
public decimal decimalProperty { get; set; }
public bool boolProperty { get; set; }
public int intProperty { get; set; }
}
and an actual entity with an OwnedEntity reference
public class SomeEntity {
public string Id { get; set; }
public OwnedEntity OwnedEntity { get; set; }
}
I set up the relationship like described in the documentation using EF Core's Fluent API:
protected override void OnModelCreating (ModelBuilder builder) {
base.OnModelCreating (builder);
builder.Entity<SomeEntity> ().OwnsOne (e => e.OwnedEntity);
}
I can't find anything on how to define default-values for all the properties of OwnedEntity. I tried to initialize the properties like this:
public class OwnedEntity {
public string stringProperty { get; set; } = "initial"
public decimal decimalProperty { get; set; } = -1M;
public bool boolProperty { get; set; } = false;
public int intProperty { get; set; } = -1;
}
but with no effect. Same goes with the [DefaultValueAttribute] (but that was to expect since it's explicitly mentioned).
There's a bit of information on how to handle initial values for regular entities:
modelBuilder.Entity<SomeOtherEntity>()
.Property(e => e.SomeIntProperty)
.HasDefaultValue(3);
But since I'm facing an Owned Entity Type, I can't access the type via Entity<T>.
Is there a way of doing what I'm looking for?
Some things worth mentioning:
I have a solid amount of specific entities where most of them are using the OwnsOne relation
Declaring all OwnedEntity-properties in a base class is not an option since not all the entities have those properties
I`m using EF Core 2.0.3 and ASP.NET Core MVC 2.0.4
Edit:
Originally, I wanted to have newly created SomeEntity instances to come with preset properties for all of the 'embedded' SomeEntity.OwnedEntity properties.
But looking at how my associated controller works, it all makes sense... I have the following methods for the 'Create' operation:
[HttpGet]
public IActionResult Create () {
return View (nameof (Create));
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create (SomeEntity model) {
context.Add (model);
await context.SaveChangesAsync ();
// redirect etc.
}
Which means that no object is created for the [HttGet] overload of Create and all the HTML inputs linked to properties (via asp-for) are initially empty. Okay. So I guess the proper way of doing this is to manually create a new instance of SomeEntity and pass it to the Create view like this:
[HttpGet]
public IActionResult Create () {
return View (nameof (Create), new SomeEntity());
}
Is this the right approach then or are there some more things to keep in mind?
Assuming you understand what EF Core Default Values are for, and just looking for equivalent of Entity<T>().Property(...) equivalent.
The owned entities are always configured for each owner type by using the ReferenceOwnershipBuilder<TEntity,TRelatedEntity> class methods. To access this class you either use the result of OwnsOne method, or use the OwnsOne overload taking second argument of type Action<ReferenceOwnershipBuilder<TEntity,TRelatedEntity>>.
For instance, using the second approach:
builder.Entity<SomeEntity>().OwnsOne(e => e.OwnedEntity, ob =>
{
ob.Property(e => e.stringProperty)
.HasDefaultValue("initial");
ob.Property(e => e.decimalProperty)
.HasDefaultValue(-1M);
// etc.
});

ASP.NET MVC 5 Common actions for more controllers

I have some controllers (and will be more) which share some actions like those:
public ActionResult DeleteConfirmed(int id)
{
Supplier s = db.Suppliers.Find(id);
s.Deleted = true;
db.SaveChanges();
return RedirectToAction("Index");
}
public ActionResult RestoreConfirmed(int id)
{
Supplier s = db.Suppliers.Find(id);
s.Deleted = false;
db.SaveChanges();
return RedirectToAction("Index");
}
Those action are part of SuppliersController. What this does is that when I delete or restore an object, it marks the object in the database as true for deleted field (and false when it is restored).
The same behavior is shared by many other controllers like CurrenciesController, ProductsController, etc...
In the code I showed you should see that my database entity is clearly specified (Supplier) and also the repository (Suppliers).
I want to find a way to this in a generic way. I want to create a custom controller and all other controllers that shares the same behavior will extended it. In this case ProductsController will extend my DeleteRestoreController.
How can I do this in a "generic" way?
db is a DbContext
public partial class LE: DbContext
{
public LE()
: base("name=LE")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public virtual DbSet<Category> Categories { get; set; }
public virtual DbSet<CategoryText> CategoryTexts { get; set; }
...
}
Categories also share the same behavior.
To go one step further
public abstract class DeleteRestoreController<T> : Controller
{
public virtual Action DeleteConfirmed(int id)
{
var dbset = db.Set<T>();
var s = dbset.Find(id);
s.Deleted = true;
db.SaveChanges();
return RedirectToAction("Index");
}
}
then when defining your controller add the entity type
public class ProductsController : DeleteRestoreController<Supplier>
{
////blah
}
You can implement your DeleteRestoreController as an abstract class.
public abstract class DeleteRestoreController : Controller
{
private IRepository : Repository;
public DeleteRestoreController() { ... }
public DeleteRestoreController(IRepository Repository) { ... }
public virtual Action DeleteConfirmed(int id)
{
Supplier s = db.Suppliers.Find(id);
s.Deleted = true;
db.SaveChanges();
return RedirectToAction("Index");
}
}
If you need to differ from that behaviour in your ProductsController you can simply override that method.
public class ProductsController : DeleteRestoreController
{
public override void DeleteConfirmed()
{
//override the logic
}
}
You could always go one step further and implement a generic repository as well, but I've never gone beyond 6-8 controllers in my applications and didn't create one once.
EDIT I've just read in the comments, that the entities would change from Suppliers in the controllers, so implementing a base controller wouldn't make much sense, if you do not implement a generic interface as well. Robert Harvey has made a great point in stating the complexity has to go somewhere.

Resources