Skip to content

Instantly share code, notes, and snippets.

@bbrt3
Last active August 3, 2021 10:54
Show Gist options
  • Save bbrt3/2bf8aba873bda26d2c15620dc0f034f4 to your computer and use it in GitHub Desktop.
Save bbrt3/2bf8aba873bda26d2c15620dc0f034f4 to your computer and use it in GitHub Desktop.
.NET Core API
We can create custom validation attributes which will be applied to properties by creating classes in ValidationAttributes folder.
public class CourseTitleMustBeDifferentFromDescriptionAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
// return base.IsValid(value, validationContext);
var ex = (ExampleDTO)validationContext.ObjectInstance;
if (ex.FirstName == ex.LastName)
{
return new ValidationResult(
"The provided first name should be different from last name.",
new[] {"ExampleDTO"});
}
return ValidationResult.Success;
}
}
// it still will not occour if controller level validation fails
We can set custom error method in decorator itself.
[Required(ErrorMessage = "LOL TESTING")]
public string FirstName { get; set; }
public class CourseTitleMustBeDifferentFromDescriptionAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
// return base.IsValid(value, validationContext);
var ex = (ExampleDTO)validationContext.ObjectInstance;
if (ex.FirstName == ex.LastName)
{
return new ValidationResult(
ErrorMessage,
new[] {nameof(ExampleDTO)});
}
return ValidationResult.Success;
}
}
For defining validation rules we can use:
a) Data Annotations
b) IValidatableObject
We should validate input, not output
We should use separate DTOs for separate operations.
For checking validation rules we use ModelState, which:
a) it is a dictionary containing both the state of the model and model binding validation (one for each property)
b) it contains a collection of error messages for each property value submitted
There is a method called ModelState.IsValid which checks these rules, if one of them doesn't check out, valid state will be false.
When validation error happens we should return 422 Unprocessable entity error, which means that server understands the content type of the request entity
and the syntax of entity is correct but it wasn't able to process it (the semantics could be off for example).
Response body should contain validation errors.
When a controller is annotated with the ApiController attribute it will automatically return a 400 BadRequest on validation errors.
Data annotations are limited.
public class ExampleDTO
{
[Required]
[MaxLength(100)]
public int Id { get; set; }
[Required]
[MinLength(5)]
public string FirstName { get; set; }
[Required]
[MaxLength(100)]
public string LastName { get; set; }
}
If we would like to ensure that FirstName and LastName have different values from each other then we would need to write a custom rule.
That's where IValidatableObject comes in!!
It is an interface that our class DTO can implement.
// class level validation
public class ExampleDTO : IValidatableObject
{
[Required]
[MaxLength(100)]
public int Id { get; set; }
[Required]
[MinLength(5)]
public string FirstName { get; set; }
[Required]
[MaxLength(100)]
public string LastName { get; set; }
// New method to implement!
public IEnumerable<ValidationResult> Valdiate(ValidationContext validationContext)
{
if (FirstName == LastName)
{
yield return new ValidationResult(
"The provided first name should be different from last name.",
new[] {"ExampleDTO"});
}
}
}
Customly defined validation rules aren't executed when any of the data annotations already resulted in the object being invalid.
1. Install Microsoft.AspNetCore.Mvc.Versioning
// Startup.cs
public void ConfigureServices(IServiceCollection services)
{
...
// basic
services.AddApiVersioning();
// advanced
services.AddApiVersioning(opt =>
{
opt.DefaultApiVersion = new ApiVersion(1,1);
opt.AssumeDefaultVersionWhenUnspecified = true;
opt.ReportApiVersions = true;
// parameter name!
opt.ApiVersionReader = new QueryStringApiVersionReader("ver");
services.AddMvc(opt => opt.EnableEndpointRouting = false)
.SetCompabilityVersion(CompabilityVersion.Version_2_2);
}
// 1.0 is a default version
http://localhost:5001/api/events/all?api-version=1.0
// applying version to controller
[ApiVersion("1.0")]
[ApiVersion("1.1")]
public class XController : Controller
{
[MapToApiVersion("1.0")]
public async Task<ActionResult<CampModel>> Get(string moniker)
{
}
}
// Startup.cs
public void ConfigureServices(IServiceCollection services)
{
...
// basic
services.AddApiVersioning();
// advanced
services.AddApiVersioning(opt =>
{
opt.DefaultApiVersion = new ApiVersion(1,1);
opt.AssumeDefaultVersionWhenUnspecified = true;
opt.ReportApiVersions = true;
// this changed!
// instead of parameter we will expect header now
opt.ApiVersionReader = new HeaderApiVersionReader("X-Version");
services.AddMvc(opt => opt.EnableEndpointRouting = false)
.SetCompabilityVersion(CompabilityVersion.Version_2_2);
}
// Startup.cs
public void ConfigureServices(IServiceCollection services)
{
...
// basic
services.AddApiVersioning();
// advanced
services.AddApiVersioning(opt =>
{
opt.DefaultApiVersion = new ApiVersion(1,1);
opt.AssumeDefaultVersionWhenUnspecified = true;
opt.ReportApiVersions = true;
// get paramters and headers for specifying api version
// that the user wants to use
opt.ApiVersionReader = new ApiVersionReader.Combine(
new HeaderApiVersionReader("X-Version"),
new QueryStringApiVersionReader("ver));
services.AddMvc(opt => opt.EnableEndpointRouting = false)
.SetCompabilityVersion(CompabilityVersion.Version_2_2);
}
// even with multiple options you shouldnt use all of the
// available options, select one of them!
// http://localhost/v1/api/camps
// There is no optionality here
// hard to change in apps
// Startup.cs
public void ConfigureServices(IServiceCollection services)
{
...
// basic
services.AddApiVersioning();
// advanced
services.AddApiVersioning(opt =>
{
opt.DefaultApiVersion = new ApiVersion(1,1);
opt.ReportApiVersions = true;
opt.ApiVersionReader = new UrlSegmentApiVersionReader();
services.AddMvc(opt => opt.EnableEndpointRouting = false)
.SetCompabilityVersion(CompabilityVersion.Version_2_2);
}
// deriving api version from url
[Route("api/v{version:apiVersion}/[controller]")]
[ApiVersion("1.0")]
[ApiVersion("1.1")]
public class XController : Controller
{
[MapToApiVersion("1.0")]
public async Task<ActionResult<CampModel>> Get(string moniker)
{
}
}
private async void Search_Click(object sender, RoutedEventArgs e)
{
...
// By using Task.Run we can execute code that is not asynchronous-friendly
// on different thread so it wont block our main UI for example.
await Task.Run(() =>
{
var lines = File.ReadAllLines(@"path");
var data = new List<StockPrices>();
foreach (var line in lines.Skip(1))...
// different thread that we need to communicate in
// that's where dispatcher comes in, it lets us communicate with our UI
Dispatcher.Invoke(() =>
{
Stocks.ItemSource = data.Where(price => price.Ticker == Ticker.Text);
});
});
...
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment