I am starting developing an Web API using ASP.NET Core MVC. I have been following this Microsoft tutorial: https://learn.microsoft.com/en-us/aspnet/core/tutorials/first-web-api
So far, I have created the code for the following operations:
GET /api/todo
GET /api/todo/{id}
Code:
[Route("api/[controller]")]
public class TodoController : Controller
{
private readonly ITodoRepository _todoRepository;
public TodoController(ITodoRepository todoRepository)
{
_todoRepository = todoRepository;
}
// GET: api/values
[HttpGet]
public IEnumerable<TodoItem> GetAll()
{
return _todoRepository.GetAll();
}
// GET api/values/5
[HttpGet("{id}", Name ="GetTodo")]
public IActionResult GetById(long id)
{
var item = _todoRepository.Find(id);
if(item == null)
{
return NotFound();
}
return new ObjectResult(item);
}
}
However, when I run the Application I am getting the following 500 error:
I understand what the error means, by not why it's happening. Did I miss something?
The interesting part is that I can access the API that was autogenerated when creating the application. But not the new one I added manually.
Change Settings in your Properties -> launchSettings.json File.
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:49885",
"sslPort": 44337
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "api/todo",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"TodoApi": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "api/todo",
"applicationUrl": "https://localhost:5001;http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
because by default the "lauchUrl" is set to "api/values" this is the reason why you were getting that error.
Also add this in your Todo Controller, refer the documentation for why this should be added.
namespace TodoApi.Controllers
{
[Route("api/todo")]
[ApiController]
I suggest you to follow the guidelines for creating the app till the last and run it after completion to avoid error.
Related
I am new to fluent validation and also a beginner in Web API. I have been working on a dummy project to learn and your advice will be much appreciated. After following the FluentValidation website, I was able to successfully implement fluent validation.
However, my response body looks very different and contains a lot of information. Is it possible to have a regular response body with validation errors?
I will put down the steps I took to implement fluent validation. your advice and help are much appreciated. I am using manual validation because based on the fluent validation website they are not supporting the auto validation anymore.
In the program file, I added
builder.Services.AddValidatorsFromAssemblyContaining<CityValidator>();
Then I added a class that validated my City class which has two properties Name and Description:
public class CityValidator : AbstractValidator<City>
{
public CityValidator()
{
RuleFor(x => x.Name)
.NotNull()
.NotEmpty()
.WithMessage("Please specify a name");
RuleFor(x => x.Description)
.NotNull()
.NotEmpty()
.WithMessage("Please specify a Description");
}
}
In my CitiesController constructor I injected Validator<City> validator; and in my action, I am using this code:
ValidationResult result = await _validator.ValidateAsync(city);
if (!result.IsValid)
{
result.AddToModelState(this.ModelState);
return BadRequest(result);
}
The AddToModelState is an extension method
public static void AddToModelState(this ValidationResult result, ModelStateDictionary modelState)
{
if (!result.IsValid)
{
foreach (var error in result.Errors)
{
modelState.AddModelError(error.PropertyName, error.ErrorMessage);
}
}
}
On post, I am getting the response as
{
"isValid": false,
"errors": [
{
"propertyName": "Name",
"errorMessage": "Please specify a name",
"attemptedValue": "",
"customState": null,
"severity": 0,
"errorCode": "NotEmptyValidator",
"formattedMessagePlaceholderValues": {
"PropertyName": "Name",
"PropertyValue": ""
}
},
{
"propertyName": "Description",
"errorMessage": "Please specify a name",
"attemptedValue": "",
"customState": null,
"severity": 0,
"errorCode": "NotEmptyValidator",
"formattedMessagePlaceholderValues": {
"PropertyName": "Description",
"PropertyValue": ""
}
}
],
"ruleSetsExecuted": [
"default"
]
}
While the regular response without Fluent Validation looks like this:
{
"errors": {
"": [
"A non-empty request body is required."
],
"pointofInterest": [
"The pointofInterest field is required."
]
},
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "00-1a68c87bda2ffb8de50b7d2888b32d02-94d30c7679aec10b-00"
}
The question: is there a way from the use the fluent validation and get the response format like
{
"errors": {
"": [
"A non-empty request body is required."
],
"pointofInterest": [
"The pointofInterest field is required."
]
},
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "00-1a68c87bda2ffb8de50b7d2888b32d02-94d30c7679aec10b-00"
}
Thank you for your time.
Updated ans:
with your code, you can simply replace.
return BadRequest(result); // replace this line with below line.
return ValidationProblem(ModelState);
then you get same format as required.
------------------------*----------------------------------------
Please ignore this for manual validation.
You don't need explicit validation call.
this code is not required:
ValidationResult result = await _validator.ValidateAsync(city);
if (!result.IsValid)
{
result.AddToModelState(this.ModelState);
return BadRequest(result);
}
it will auto validate the model using your custom validator.
you simply need this
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
and it will give you errors in the require format.
if(!result.IsValid)
{
result.AddToModelState(this.ModelState);
return ValidationProblem(ModelState);
}
Below is the index.js code I am using to connect to a MySQL DB in my cypress test:
const mysql = require('mysql')
function queryTestDb(query, config) {
const connection = mysql.createConnection(config.env.db)
connection.connect()
return new Promise((resolve, reject) => {
connection.query(query, (error, results) => {
if (error) reject(error)
else {
connection.end()
return resolve(results)
}
})
})
}
module.exports = (on, config) => {
on('task', { queryDb: query => { return queryTestDb(query, config) }, });
require('cypress-grep/src/plugin')(config)
return config
}
Currently, my test use the DB credentials provided in cypress.json on this line:
const connection = mysql.createConnection(config.env.db)
But I want the framework to run in different environments, as the database name is different.
I have already created qa.json & staging.json config files that store the DB credentials like so:
qa.json:
{
"extends": "./cypress.json",
"baseUrl": "myUrl",
"env": {
"db": {
"host": "myHost",
"user": "myUser",
"password": "myPassword",
"database": "taltektc_qa"
}
}
}
staging.json:
{
"extends": "./cypress.json",
"baseUrl": "myUrl",
"env": {
"db": {
"host": "myUrl",
"user": "myUser",
"password": "myPassword",
"database": "taltektc_stage"
}
}
}
Here is the command I am currently using to run the tests:
npx cypress open --config-file staging.json
I tried to update my index.js below, but I get a Cypress is not defined error message:
module.exports = (on, config) => {
on('task', { queryDb: query => { return queryTestDb(query, Cypress.config()) }, });
Can someone please tell me what changes are required in my index.js so that I can specify which config file to use when making the DB connection?
In a Node plugin task, the config parameter is equivalent to Cypress.config() in the browser-side spec.
You should be getting the correct config resolved after --config-file staging.json is applied, so the original code is all you need
module.exports = (on, config) => {
on('task', { queryDb: query => { return queryTestDb(query, config) }, });
You can check what has been resolved after opening the runner, under settings/configuration
In a Web-Application, written in C# in .NET Core 3.1, i want to Display a Datatable of all the Blobs in one of our Company's Azure Blob Storages. For this i'm using Datatables.net in the Frontend with Ajax-Calls which target a selfwritten API in the same Web-App. The API should get all the searched Blobs, format them for easier viewing and then give them back to the Table. Locally it really works like a charm. However, soon after deployment i noticed that the Ajax-Call just simply returns a 404 Response.
For reference:
My API-Controller
[Route("Blob/api/[controller]")]
[ApiController]
public class BlobsController : ControllerBase
{
private readonly string _connectionString;
private readonly string _container;
private readonly BlobContainerClient _client;
public BlobsController(IConfiguration configuration)
{
IEnumerable<IConfigurationSection> _blobStorageSection = configuration.GetSection("BlobStorage").GetChildren();
_connectionString = _blobStorageSection.Single(e => e.Key == "ConnectionString").Value;
_container = _blobStorageSection.Single(e => e.Key == "ContainerName").Value;
_client = new BlobContainerClient(_connectionString, _container);
}
[HttpGet("{EncodedDelimiter}/{EncodedPrefix}")]
public ActionResult GetBlobs(string EncodedDelimiter, string EncodedPrefix)
{
if (! StorageIsAvailable())
return NotFound();
string Delimiter = WebUtility.UrlDecode(EncodedDelimiter);
string Prefix = WebUtility.UrlDecode(EncodedPrefix);
Pageable<BlobHierarchyItem> BlobHierarchy = _client.GetBlobsByHierarchy(delimiter: Delimiter, prefix: Prefix);
return Ok(EnrichBlobList(BlobHierarchy));
}
[HttpGet("init/{EncodedDelimiter}")]
public ActionResult Initialize(string EncodedDelimiter)
{
if (! StorageIsAvailable())
return NotFound();
string Delimiter = WebUtility.UrlDecode(EncodedDelimiter);
Pageable<BlobHierarchyItem> BlobHierarchy = _client.GetBlobsByHierarchy(delimiter: Delimiter);
return Ok(EnrichBlobList(BlobHierarchy));
}
Here the Ajax-Call Snippet
var Table = $("#BlobTable").DataTable({
ajax:{
url: "api/Blobs/init/%2F",
dataSrc: ""
},
processing: true,
columnDefs:[
{
"targets": 0,
"data": "standartizedName",
},
{
"targets": 1,
"data": null,
"render": function(full){
return renderTyp(full);
},
"width": "10%"
},
{
"targets": 2,
"data": null,
"render": function(full){
return renderDatum(full);
},
"width": "15%"
},
{
"targets": 3,
"data": null,
"render": function(full){
return renderAction(full);
},
"orderable": false,
"searchable": false,
"width": "10%"
}
],
order:[[1, "desc"]],
pageLength: 50
});
And bc i have seen similar Problems where the Source-Problem was in StartUp:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<TestDbcontext>(options => options.UseSqlServer(Configuration.GetConnectionString("TestDB"),
sqlServerOptionsAction: sqlOptions =>
{
sqlOptions.EnableRetryOnFailure(
maxRetryCount: 10,
maxRetryDelay: TimeSpan.FromMinutes(5),
errorNumbersToAdd: null
);
}));
if (Env.IsDevelopment())
{
UseFakeAuthenticationAndAuthorization(services);
//UseAuthenticationAndAuthorization(services, Configuration);
}
else
{
//UseFakeAuthenticationAndAuthorization(services);
UseAuthenticationAndAuthorization(services, Configuration);
}
services.AddControllersWithViews();
RepositoriesTestGUI(services);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Home}/{id?}");
});
}
Has someone else an Idea what could be the Problem? An API-Call is an absolute Necessity since the Storage contains 70k Files
EDIT:
By Request here are the Network Details
NetworkDetails
Bc this is my first Post i can't put embedded Pictures in apparently
The root cause of this problem is not having permission to access. The easiest way to test is to change the access level to Container.
My test steps:
Access a picture in container( Blob or Container access level ).
Uri like:
https://testaccount.blob.core.windows.net/testblob/SignalR.png
Result
Access a picture in container( private access level ). We will reproduce the issue.
Suggestion
We can write a method to generate sas token to access the files. You can refer my code or read the official doc.
Before I start questioning let me give you a simplified example of my case:
Imagine you have Views:
public final class Views {
public interface Id { }
public interface IdText extends Id { }
public interface FullProfile extends IdText { }
}
You also have a class User which has subscribers that are of the same type User.
The properties id and username are serialized in the Views.IdText.class view. And the property subscribers is serialized in the Views.FullProfile.class view.
#Entity
public class User implements UserDetails {
#Id
#JsonView(Views.IdText.class)
private Long id;
#JsonView(Views.IdText.class)
private String username;
#JsonIdentityReference
#JsonIdentityInfo(
property = "id",
generator = ObjectIdGenerators.PropertyGenerator.class
)
#JsonView(Views.FullProfile.class)
private Set<User> subscribers = new HashSet<>();
}
And a controller (ProfileController) that has a method called get that returns a user's profile.
#RestController
public class ProfileController {
#GetMapping("{id}")
#JsonView(Views.FullProfile.class)
public User get(#PathVariable("id") User user) {
// ... some service methods that has the necessary logic.
return user;
}
}
As you can see, the method serializes the user's profile in the Views.FullProfile.class view, so the output is:
{
"id": 39,
"username": "ryan",
"subscribers": [
{
"id": 42,
"username": "elliott",
"subscribers": [
{
"id": 432,
"username": "oliver",
"subscribers": [
{
"id": 2525,
"username": "james",
"subscribers": [
39,
432
]
},
{
// ... a lot of useless data goes on.
}
]
},
{
"id": 94923,
"username": "lucas",
"subscribers": [
345, 42
]
}
]
},
{
"id": 345,
"username": "jocko",
"subscribers": [
94923
]
}
]
}
When a user's profile is being serialized, I don't need the user's subscribers to be serialized in the Views.FullProfile.class view
but in the Views.IdText.class view so that the output would be:
{
"id": 39,
"username": "ryan",
"subscriptions": [
{
"id": 42,
"username": "elliott"
},
{
"id": 345,
"username": "jocko"
}
]
}
How can I make jackson to use different views on nested entities of the same type?
Or what else do I have to do to make that happen?
After some time of continuous searching I found someone issued the same problem on Github: #JsonIgnoreProperties should support nested properties #2940
As stated in that issue:
No plans to ever implement this (due to delegating design, will not be possible with current Jackson architecture), closing.
I have a legacy spring code where they use ModelAndView and they add the objects to it as below.
ModelAndView result = new ModelAndView();
result.addObject("folders", folders);
return result;
for the above i am getting response as
{
"folders": [
{
"recordCount": 0,
"folderContentType": "Reports",
"folderId": 34,
},
{
"recordCount": 2,
"folderContentType": "SharedReports",
"folderId": 88,
}
]
}
I have changed these to use Spring's RestController with a POJO backing the results returned from DB.
#GetMapping("/folders")
public List<Folder> getAllFolders() {
return Service.findAllFolders(1,2);
}
This returns a JSON as below
[
{
"folderId": 359056,
"folderName": "BE Shared Report Inbox",
"folderDescription": "BE Shared Report Inbox",
},
{
"folderId": 359057,
"folderName": "BE Shared Spec Inbox",
}]
How could i return this as exactly as my legacy code response. I know i can convert the List to Map and display. But, is there any equivalent
way.
Thanks.
You can put your result into a map.
#GetMapping("/folders")
public List<Folder> getAllFolders() {
return Service.findAllFolders(1,2);
}
Change to:
#GetMapping("/folders")
public Map<String,List<Folder>> getAllFolders() {
Map<String,List<Folder>> map = new HashMap<>();
map.put("folders",Service.findAllFolders(1,2));
return map;
}