Using route and body parameters simultaneously on a post request Asp.Net - c#

Preface
I recently was assigned a project for college, were using ASP.NET Core 6 Web API, and Entity Framework. Due to the subject at hand the objective is to implement an API without taking any consideration around the structure and routing of it.
So this is a warning: I did not choose the routing nor can I change it, even if slightly.
The actual problem
I have 3 model classes, let's call them Application, Module and Data. A single application has various child modules, while each module has a series of data children.
The issue is that when I want to create a Module or a Data model, the teachers decided to give it a twist and we're forced to take data from the body and the URL.
So that everyone is on the same page ill give an example of how to create a Module.
The url goes like this
POST: https://ip:port/api/OurProjectName/{application}/
POSTBODY:
<?xml version="1.0"?>
<Module><!--here on the root node i can be a bit malleable and name it whatever i want so no issues with DTO's-->
<id>99999</id>
<name>some name</name>
<creation_dt>{the current timestamp}</creation_dt>
</Module>
If the implementation was correct what it would do is go to the controller grab the application string and the Module/ModuleDTO, fetch an Application model from the database where the application string is equal to the field name.
Then inside that function due to our choice of using EF, we would just grab the module sent in the body(or create one in case of a DTO) and associate the respective parent sent in the route parameters.
My failed attempts
Although I talked about modules, the behaviour of data is nearly the same as modules except the teachers decided that for some reason with data you can only create and delete, and you can't list, update, etc... When it comes to posting instead of
POST: https://ip:port/api/OurProjectName/{application}/
I have to do something like
POST: https://ip:port/api/OurProjectName/{application}/{module}
while application this time only serves for checking past errors due to the way the database was structured.
I firstly attempted to do something like
[HttpPost("{application}/{module}")]
[Produces("application/xml")]
[Consumes("application/xml")]public IActionResult Post(string application, string module, [FromBody] Data data)
{
var mod = _context.Modules.SingleOrDefault(m => m.name == module);
if (mod == null)
{
return NotFound();
}
if (mod.parent.name != application)
{
//... this is irrelevant for now
}
data.parent = mod;
// TODO: dereference here... fix later
mod.datas.Add(data);
_context.Add(data);
_context.SaveChanges();
return Ok(data);
}
But it returned the usual error of 415, although this try was mostly hoping that [FromBody] wouldn't complain at the routing as I've had good results with put (although in that situation it kinda follows convention instead of completely breaking it) and to top it off since Data has a navigation field, swagger will iterate the objects until no parents are found so the xml object in the body looked... well like a configuration file.
I then tried to use decorations around the two strings [FromRoute], but it still wouldn't budge, and got an "unsupported media format".
Then I tried to do some custom manual routing (as in no decorations)
here's my startup file that I created with only that purpose in mind (since I otherwise would expect the Program.cs file to get cluttered which is a thing I hate)
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI();
}
var option = new RewriteOptions();
option.AddRedirect("^$", "swagger");
app.UseRewriter(option);
app.UseCors("AllowAll");
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints => {
endpoints.MapControllers();
endpoints.MapControllerRoute(
name: "postDataCustom",
pattern: "api/OurProjectName/{application}/{module}", new {controller = "DataController", action = "Post"});
});
}
I removed the [HttpPost(etc)] and expected to hopefully fix it, but when I checked swagger not only was it missing but the endpoint didn't even exist (I tested with Postman).
I commented it out (the part where I manually set a ControllerRoute) and found some other posts around the subject.
And I saw that I could actually do some kind of dto and put the decorations in each field, this was game breaking as I knew then I would be able to just place a single variable inside the controller function and avoid [FromData] to go insane and parse properly the stuff.
public IActionResult Post(DataDTO data)
{
var mod = _context.Modules.SingleOrDefault(m => m.name == data.module);
if (mod == null)
{
return NotFound();
}
Console.WriteLine(mod.name);
// ...(if it wrote to console I could progress)
return Ok();
}
public class DataDTO
{
[FromRoute]
public string application { get; set; }
[FromRoute]
public string module { get; set; }
[FromBody]
public Data data { get; set; }
public DataDTO(string application, string module, Data data)
{
this.application = application;
this.module = module;
this.data = data;
}
}
It still gave me the error code, this time since I was a bit more skeptical of such a failure, I made a much simpler DTO and routing just to check if I could at least get a 200 code and I did it, the problem is [FromRoute] is pretty much "ignored"
[HttpPost("{module}")]
public IActionResult Post(testeDTO data)
{
Console.WriteLine(data.module);
Console.WriteLine(data.bb);
return Ok();
}
public class testeDTO
{
[FromRoute]
public string? module { get; set; }
[FromBody]
public string bb { get; set; }
public testeDTO(string bb)
{
this.bb = bb;
}
}
Although the route is there and works fine it never assigns the value to module, it seems like whatever is on the body overwrites it (I tried to get around not having duplicate fields by adding the possibility of module being null so I wouldn't be forced to send a module in the body, but no dice, I can't decouple both behaviors).
At this point I'm kinda out of ideas, it's even more frustrating when old documentation gets in the way with search results.
I've also considered using x-www-urlencode, although I'm not sure if it's an option I can take as again the requirements are very restrictive.
TL;DR I can't get a post with parameters in the route to hit successfully the action Post...
Update as of 26/12/2022:
I was playing around the code and weirdly this version "works" i tested it with swagger and it seems to not produce any error, i even checked git to see if added or removed some kind of config or something of the kind but since the last commit(which was uncharacteristic of me as it was a "backup" of sorts) these were the only things that changed
[HttpPost("{module}")]
public IActionResult Post([FromRoute]string module, [FromBody]testeDTO data){
Console.WriteLine(module);
Console.WriteLine(data.bb);
return Ok();
}
...
public class testeDTO{
[FromBody]
public string bb{get;set;}
public testeDTO(string bb){
this.bb = bb;
}
}
I will try to now play around the code to see if i can get to the solution

Step 1)
Go back to your first attempt:
[HttpPost("{application}/{module}")]
[Produces("application/xml")]
[Consumes("application/xml")]
public IActionResult Post(string application, string module, [FromBody] Data data)
{
// Your code
}
Step 2)
Add this line to your Program.cs:
builder.Services.AddControllers().AddXmlSerializerFormatters();
or this line to your ConfigureServices method:
services.AddControllers().AddXmlSerializerFormatters();
Step 3)
Rework your XML to match the parameter name:
<?xml version="1.0"?>
<Data>
<id>99999</id>
<name>some name</name>
<creation_dt>{the current timestamp}</creation_dt>
</Data>
Things should then work.
Step 2) is key, listen to what your being told - 415 is unsupported media type (which you know). So support the media type. I don't know why you think it shouldn't be possible to use a route parameter and a body parameter in the same endpoint but it is.
You may also not want to mix attribute routing and configuration level routing.

I found the answer, apparently the issue revolved around
[FromBody]
//... and
[Consumes("application/xml")]
As the edit on my question suggested i removed the consumes decoration and it started working unfortunately as suggested by the question i cant either change the routing nor can i change the media type it produces and consumes, so my idea was at the time "if i cant use body decorations ill just manually parse the body and keep the routing decorations".
And thats when it hit me XMLserializer requires at least one parameterless constructor to work, and since i had placed parameterless constructors on all other models except on this one, it tricked me into thinking the issue was around the decorators. Ill leave some resources that helped me get into the solution
How to parse xml into objects
Request params documentation

Related

Why isn't my C# .Net Core Rest API route finding my method?

I am working on an API.
I have an "AbstractController.cs" and I am having difficulties calling a GET with two parameters.
[Route("api/[controller]")]
[ApiController]
public class AbstractController : ControllerBase
{
// GET: api/Abstract
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "missing implementation" };
}
Going to https://localhost:44363/api/abstract/ generates this.
["missing implementation"]
Awesome!
Now I need to make the Get method that passes in a show year, and code to a SQL query. Easy enough?
// GET: api/Abstract?ShowYear=2019&ShowCode=248621
[Route("api/{controller}/{ShowYear}/{ShowCode}")]
[HttpGet]
public Abstract Get(int ShowYear, int ShowCode) // GetAbstractByYearAndSHCode
{
string x = "";
return new Abstract();
}
No matter what I do I can't get this method to breakpoint/enter execution!
I'm guessing it's a routing issue, but I've tried most tenable ways of calling the endpoint.
Now I check the MS Documentation like any self-respecting-learning-programmer would.
https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/routing?view=aspnetcore-3.1
Endpoint routing in ASP.NET Core 3.0 and later:
Doesn't have a concept of routes. Doesn't provide ordering guarantees
for the execution of extensibility, all endpoints are processed at
once.
GULP Sounds scary.
I haven't touched my middleware at this point.
All examples I've looked at don't have my "MapControllers();" method in their Startup.cs.
This is because it's new to .NET Core 3.0/3.1
The method: "Adds endpoints for controller actions to the IEndpointRouteBuilder without specifying any routes."
OK, so do I have to manually specify the route here still?
I did this, and it sorta works.
Well, it breakpoints in the startup.cs now when I go to https://localhost:44363/api/abstract/2019/287
Wait, it's not doing ANYTHING with my controller code! Why?
The following (also above) code ends up declaring as a null in the startup.cs
var controller = context.Request.RouteValues["Abstract"];
Hoping to learn what I'm doing wrong.
MapGet("/api/abstract/{showYear}/{showCode}", ...);
Isn't this code responsible for mapping that route with the controller named AbstractController.cs in my Controllers folder? Not hitting any breakpoints.
Edit:
Reading through this which compares the differences in the Startup.cs from .Net Core 2, 2.2, MVC projects vs 3.0
I have a good feeling reading all of this, I will find the issue.
https://andrewlock.net/comparing-startup-between-the-asp-net-core-3-templates/
Edit.. nevermind didn't find a resolution.
Edit 2:
Completely commenting out this troublesome code in startup.cs:
endpoints.MapGet("/api/abstract/{showYear}/{showCode}", async context =>
{
var controller = context.Request.RouteValues["Abstract"];
var showYear = context.Request.RouteValues["showYear"];
var showCode = context.Request.RouteValues["showCode"]; // just an example.
//await context.Response.WriteAsync($"Hello {showYear} is your year, {showCode} for the code!");
//GetAbstractByYearAndSHCode();
});
endpoints.MapGet("/api/abstract/{showYear}", async context =>
{
var name = context.Request.RouteValues["name"];
await context.Response.WriteAsync($"Hello {name}!");
});
Resolves my issue of not being able to reach the controller.
https://localhost:44363/api/abstract/2019 hits, and the id value is 2019. Great.
https://i.imgur.com/rmHyDPg.png the output looks great.
I am still not able to use > 1 parameter. How do I simply use the Year, and ShowCode paramaters? What's the syntax?
https://localhost:44363/api/abstract/2019
Just add the parameters to your attribute
[HttpGet("{ShowYear}/{ShowCode}")]
The "api/abstract" route is already used by the first method.
You cannot use it again for other actions. Create a new one as shown inline.
[HttpGet("{GetAbstractByYearAndSHCode}",Name = "GetAbstractByYearAndSHCode")]
public Abstract Get(int ShowYear, int ShowCode) // GetAbstractByYearAndSHCode
{
string x = "";
return new Abstract();
}
And call the url as shown:
https://localhost/api/abstract/GetAbstractByYearAndSHCode?ShowYear=1&ShowCode=5

Swagger not loading - Failed to load API definition: Fetch error undefined

Trying to setup swagger in conjunction with a web application hosted on IIS express. API is built using ASP Net Core. I have followed the instructions prescribed on the relevant microsoft help page regarding Swashbuckle and ASP.NET Core.
Thus far I have got the swagger page to load up and can see that the SwaggerDoc that I have defined is loading, however no API's are present. Currently am getting the following error:
"Fetch error undefined ./swagger/v1/swagger.json"
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// services.AddDbContext<TodoContext>(opt =>
// opt.UseInMemoryDatabase("TodoList"));
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
// Register the Swagger generator, defining 1 or more Swagger documents
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info { Title = "API WSVAP (WebSmartView)", Version = "v1" });
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// Enable middleware to serve generated Swagger as a JSON endpoint.
app.UseSwagger();
// Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.),
// specifying the Swagger JSON endpoint.
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("./swagger/v1/swagger.json", "My API V1");
c.RoutePrefix = string.Empty;
});
app.UseMvc();
}
}
So after a lot of troubleshooting it came down to basically two things, but I feel that in general this could be helpful to someone else in the future so I'm posting an answer.
First- if ever your stuck with the aforementioned error the best way to actually see whats going on is by adding the following line to your Configure() method
app.UseDeveloperExceptionPage();
Now if you navigate to the 'swagger/v1/swagger.json' page you should see some more information which will point you in useful direction.
Second- now for me the error was something along the lines of
'Multiple operations with path 'some_path' and method 'GET' '
However these API were located inside of dependency libraries so I was unable to apply a solution at the point of definition. As a workaround I found that adding the following line to your ConfigureServices() method resolved the issue
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info { Title = "API WSVAP (WebSmartView)", Version = "v1" });
c.ResolveConflictingActions(apiDescriptions => apiDescriptions.First()); //This line
});
Finally- After all that I was able to generate a JSON file but still I wasn't able to pull up the UI. In order to get this working I had to alter the end point in Configure()
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("./v1/swagger.json", "My API V1"); //originally "./swagger/v1/swagger.json"
});
I'm not sure why this was necessary, although it may be worth noting the web application's virtual directory is hosted on IIS which might be having an effect.
NOTE: Navigating to swagger/v1/swagger.json will give you more details, for me it was causing issue due to undecorated action. This information is mentioned in comment by #MarkD
I've been working with .Net Core 3.1 and I spent some time to find out and understanding what was going on.
The issue can arise from many different reasons:
Swagger configuration errors
Classes with the same name but in different namespaces
Public methods without the rest attribute (Get, Post, etc.)
First, take a look the link below just to check if your setup is ok:
Add Swagger(OpenAPI) API Documentation in ASP.NET Core 3.1
Then,
A good tip to find out the problem is to run the application without to use IISExpress and check the console log. Any error found to generate the documentation will be displayed there.
In my case, the problems was that I had a public method (that should be private) without any rest attribute:
After change the method from public to private I solve the issue.
I was able to find the error by opening the network tab and looking at the response for swagger.json
Simply navigate to https://localhost:{PortNo}/swagger/v1/swagger.json and get much more details about the error message.
also I had similar problem in .NET 5.0, I solved below way:
I added this line as attribute over controller:
[Consumes("application/json")]
I've been working with .NET 5 and I spent some time trying to understand what was going on.
I got an error like the one below:
Then I resolved this problem by the following:
Open startup.cs file
Add the following code in Configure method
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger(c =>
{
c.RouteTemplate = "/swagger/{documentName}/swagger.json";
});
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "API v1"));
}
And in ConfigureServices method
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "API", Version = "v1" });
c.ResolveConflictingActions(apiDescriptions => apiDescriptions.First());
});
Thanks to TheCodeBuzz for Resolved: Failed to load API definition (undefined /swagger/v1/swagger.json)
Note the difference between the RouteTemplate string and the SwaggerEndpoint string. One uses {documentName} and the other uses "v1" as a literal.
I've come across the same error before, after struggling to find the reason, I discovered that one of my API in one of my controllers have no HTTP verb as an attribute, So I fixed it by putting [HttpGet] on my API.
So here is my advice, check your API controllers, maybe you forget the same thing as me!
Take a look at my code, I realized that I should change this :
public async Task<Product> ProductDetail(int id)
{
return await _productQueries.GetProductDetail(id);
}
to this:
[Route("ProductDetail")]
[HttpPost]
public async Task<Product> ProductDetail(int id)
{
return await _productQueries.GetProductDetail(id);
}
I had similar issue, I solved it using the Route attribute on the offending controller method:
[HttpGet, Route("Throw")]
public ActionResult<string> Throw()
{
_logger.LogInformation("I think I am going to throw up");
throw new NotSupportedException("For testing unhandled exception logging.");
}
I felt that ResolveConflictingActions may potentially sweep a real issue under the rug.
I had two issues that caused the same error.
I have two classes with the same name under two different namespaces. Swagger could not reconcile this when generating the swagger doc. To fix it I added the line options.CustomSchemaIds(x => x.FullName);
See explanation here
I had a method without an [HttpGet] annotation. Swagger needs the HTTP endpoints to be explicitly defined.
I found both issues by inspecting the Output in visual studio after the API loaded.
I just spent two hours on this issue, but my cause was entirely different, it had NOTHING to do with routes or annotations. I had 2 classes with the same name (but different namespaces): MyProject.Common.ClassName and MyProject.Api.ClassName. Swagger/swashbuckle couldn't tell the difference between the two, so I got that useless error.
Those 2 hours were spent trial-and-error commenting out controllers and endpoints, to finally find 3 endpoints offending endpoints. All 3 endpoints had different routes, different (or no) custom authorization, and different method names. It turned out that all 3 endpoints either accepted a parameter, or returned an object, that contained the API version of my class. Nowhere was the Common version used. Swagger couldn't tell them apart, and puked all over itself.
Why oh why can't Swagger or Swashbuckle provide actual error messages? Would have saved me a couple of hours...
I just forgot to add HTTP attributes in my controller as soon as I add HTTP attribute it works like a charm for me.
Source : https://www.benday.com/2020/12/16/webapi-core-swagger-failed-to-load-api-definition-error/
Here we go:
I created WEB Controller instead of WEB API Controller. That makes this kind of error.
During creation of new Controller, make sure that you created right WEB API controller.
Surely it is one of the Controller's method that is faulty. To get the method, at times you might need to take out all your controllers, Try and insert them one after the other then you will test along to find the Controller with bugs.
For ex. If you have like 3Controllers say
>Controller
>>>AuthController
>>>UserController
>>>HomeController
Take two out of the controllers out and test the controller by adding one controller after each successful testing. With that you will know the controller that has a faulty method.
>Controller
>>>AuthController
If the methods in AuthenController is fine, It will run, If not Check the methods.
>Controller
>>>AuthController
>>>UserController
and carry out the next check on the controller like that of Authen.
I had the same problem, so I checked it using inspect element on the browser. The "Console" tab shows the file where the problem originated from (v1/swagger/json:1). Opening it by clicking it showed that one of the helper methods I used in my controller was "Public". Changing it to "Private" fixed the problem for me.
This page also has good tips:
https://btrehberi.com/swagger-failed-to-load-api-definition-fetch-error-undefined-hatasi-cozumu/yazilim/
Swagger in my case needed [HttpAction] with all public members in controller. Unfortunately I misspelled constructor name and since it was public, was throwing this error.
For ASP.NET Core 3.1 I had to ensure the verb were not ambiguous and I found this out by first running the API project without IIS in VS2019 (Green Arrow > left-click the carrot icon and select the name of the project this causes a console window to appear on start up so you can inspect what's happening and see errors).
[HttpGet("MyEndPointA")
Then Swagger is able to generate the documentation correctly.
Solved issue in dotNet 6! Just change the attribute order of [ApiController]
In my case, there were 2 methods in the Controller class, which had the same annotations, and URL. (Our team was using Entity Framework, ASP.NET and Swagger.)
[HttpGet("GetMyGreatData/{patientId}")]
[ValidatePatient]
public async Task<ActionResult<ServiceResponse<IEnumerable<MyGreatModel>>>> GetMyGreatData(
[FromRoute] int patientId, int offset = 0, int limit = 0)
{
//method details...
}
[HttpGet("GetMyGreatData/{patientId}")]
[ValidatePatient]
public async Task<ActionResult<ServiceResponse<IEnumerable<MyGreatModel>>>> GetMyGreatData(
[FromRoute] int patientId,
[FromQuery] DateTimeOffset? startdate = null,
[FromQuery] DateTimeOffset? endDate = null,
int offset = 0,
int limit = 0)
{
//method details...
}
deleting one method solved the issue for me.
I was having the same issue, the base controller was not decorated with Http and removing that has made it work.
This error can happen when you deploy an App Service to Azure. I've redeployed the App Service to Azure and the error disappeared.
When this happened to me, I tracked it down to URL path param having an underscore which it's compatible with the asp generator
Changing this:
/block-content/{machine_name}:
To this
/block-content/{machineName}:
Solved it for me
<b>make sure the name "v1" matches the path in the swagger endpoint</b>
<p>
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo {
Title = "ODAAPP",
Version = "v1" });
});
</p>
<br/>
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json",
"ODAAPP v1"));
enter code here
This will also happen if you use same route for multiple action methods (Overloading is OK)
In my case, the project was configured to authenticate using identity server 4 using AddPolicy() at startup.cs and there were usages of [Authorize]
I removed the things for startup.cs and usages of [Authorize]
Will update more soon
In my case I had two identicall inner classes.
Extracted them to a single one refactored the namespaces and voilá, all returned to work properly.
I have experienced the same error when I was using Swagger and also Microsoft.AspNetCore.OData. I usually try to use the latest version - but bringing it down to v 7.5.12 - did solve my issue.
Also adding following to every Action method in the Controller, makes it work with OData v8.x too: [HttpGet], [HttpPost], or [ApiExplorerSettings(IgnoreApi = true)]
I had a similar Fetch error 404 swagger/v1/swagger.json, when trying to integrate Swagger documentation in ASP.NET Core 3.1 Web API project. I tried almost all of the above suggestions but failed.
After an hour of hit-and-trial, I decided to give NSwag a try using this reference, instead of Swashbuckle and it just worked like a charm :)
I got the similar issues - the root cause is I forgot to add the annotations :-(
Reasons for this Error
i resolved this issue by this way
Use [HttpGet] attribute above the api controller method.
And, because of different versions of swashbuckle, these errors may come.
you should use the correct swagger endpoint url
v1/swagger.json or swagger/v1/swagger.json
choose above one based on the version you are using.
Note:
Use this url for reference https://myget.org/feed/domaindrivendev/package/nuget/Swashbuckle.AspNetCore.Swagger/6.2.3-preview-1963
Refer the official swagger documentation.
lot of information is there with crystal clear documents
https://swagger.io/docs/
'Multiple operations with path 'some_path' and method 'GET' '
[HttpGet]
public IActionResult Get()
{
return Ok(_userService.Get());
}
[HttpGet]
public IActionResult Get(int Id)
{
return Ok(_userService.Get(Id));
}
Just modify DataAnnotation:
[HttpGet]
public IActionResult Get()
{
return Ok(_userService.Get());
}
[HttpGet("{Id}"] //HERE
public IActionResult Get(int Id)
{
return Ok(_userService.Get(Id));
}

POST of IFormFile not making it to action

I've got a .NET Core application that has a controller named Documents with a POST signature like this:
[HttpPost]
public async Task<IActionResult> PostAsync(CreateDocumentRequest createDocumentRequest)
The CreateDocumentRequest looks like this:
public class CreateDocumentRequest
{
public string Name { get; set; }
public string Description { get; set; }
public IFormFile File { get; set; }
}
Pretty simple. I then have a POST request configured in Postman like this:
URL: http://localhost:9090/api/documents
Body: configured as form-data and I have Name, Description and File all configured in the key-value pair interface. Furthermore, File is set as a file type so it allowed me to browse for a file.
When executing this POST the DocumentsController executes the constructor and Application Insights indicates that PostAsync was matched:
Activated Event Time Duration Thread
Application Insights: Trace "Route matched with {action = "PostAsync", controller = "Documents"}. Executing action TdlLims.MediaService.Controller.DocumentsController.PostAsync (TdlLims.MediaService)"
However, it never enters the action. My gut tells me that model binding is failing. This is for two reasons. One, all other pieces of the routing work according the Application Insights. Two, if I remove the parameters entirely, it does enter the action. What I've tried:
Added [FromForm] to the createDocumentRequest
Accepted only an IFormFile into the action, dropping the complex object
Split up the CreateDocumentRequest into three different parameters
And some other things along the way with less signifigance
Now, I'm suspect that when we're setting up Mvc, we may be missing something. We are configuring a few things, but I feel like we're missing a formatter for multipart/form-data somehow. I feel that way because we're using AddMvcCore instead of AddMvc:
.AddAuthorization()
.AddJsonFormatters()
.AddApiExplorer()
.AddJsonOptions(options =>
{
options.SerializerSettings.Converters.Add(new OptionConverter());
options.SerializerSettings.Converters.Add(new StringEnumConverter());
});
Finally, I can confirm the controller is working in general, because I have a GET that is accessible and it makes it to the action:
[HttpGet("{id}")]
public async Task<IActionResult> GetAsync(int id)
In the end, the issue was the size of the file. It would be nice if .NET Core threw an error rather than returning a 200 when something like that happened. I was trying to upload some images, and I'm going to need to figure out the right way to increase the file size, but when I uploaded a small text file the POST worked and the file was deserialized properly into the IFormFile.
I believe the attributes RequestFormLimits and RequestSizeLimit are going to play a role in setting that max file size in the end.

ASP.NET Web API 5.2.3 Attribute Route returning 404

I've got an ASP.NET Web API project that I'm working on. I've got an APIController called SpellbookController that has basic CRUD operations to an EntityFramework repository.
However, when I try to add a method that takes a parameter that's not an id, I can't seem to get it to route correctly.
Here's what it looks like:
// GET: api/Spellbooks/user#email.com
[ResponseType(typeof(List<Spellbook>))]
[Route("api/Spellbooks/{username}")]
public IHttpActionResult GetSpellbook(string username)
{
List<Spellbook> spellbooks = db.Spellbooks.Where(x=>x.Username == username).ToList();
if (spellbooks == null)
{
return NotFound();
}
return Ok(spellbooks);
}
So I'm trying to hit http://localhost:xxxx/api/Spellbooks/emailaddress,
but I get a 404 every time.
It's definitely an APIController, and I have config.MapHttpAttributeRoutes(); turned on.
What am I missing?
Where is your username parameter?
Your call should looks like this-
http://localhost:xxxx/api/Spellbooks/emailaddress/David
Update
Try to declare the parameter as string {username:string}
Update 2
The problem is with your '.' as you can see in this link.
You can send the email without the point in the parameter and then replace ite back to you regular mode or you can use all the good advice that the link provide.
If you step through it, do you get a value when looking at username? Or is it always null?
If your route is not working, a simple way to debug the route is to make a request to http://localhost:xxxx/api/Spellbooks/?emailaddress=thisemailaddress and see if you can get a response. In fact, from a REST standard, it can be argues that it's a cleaner route, since you will be returning a collection of elements, rather than a single object.

asp web api patch implementation

Assume i have this model
public partial class Todo
{
public int id { get; set; }
public string content { get; set; }
public bool done { get; set; }
}
And i send this as json data to my controller as a patch request.
This is mearly the action of toggeling a checkbox.
I think it makes sence that i only want to sent that to my server, and not the entire model.
{ "id":1, "done" : true }
What does my WebApi controller need to look like in order to correctly process this, simple, json patch request ? Should i be using web api for this, or should i use a more rpc styled approach with mvc ?
It seems like a very basic thing to do, but i can't seem to get it right !
I think i might need to use a different parameter in my controller method, but i'm not sure.
Thank you for your time.
You can find PATCH feature in the OData pre-release Nuget package: Microsoft.AspNet.WebApi.OData.
Information how you can use it to create an action for handling PATCH can be found in the Partial Updates (PATCH requests) section of the blog post about OData support in ASP.NET Web API.
Changing the method to PATCH doesn't change Web API behaviour in any way. There is no built in mechanism for doing partial updates. One of the reasons there was no PATCH method for so long is that there is no ubiquitous media type for applying patches to resources.
Secondly, you are asking Web API to do object serialization for you so there just is no such concept of applying a partially updated object. There would be so many conventions to agree on, what does a null value mean, what about an empty value, how do I say "don't update this DateTime". What about related objects, child items? How do you cause a child item to be deleted? Unless the CLR team implements some concept of a type that only contains a subset of members from another type, partial updates and object serialization are not going to go well together.
Aliostad mentions UpdateModel and that is possible when updating from a HTML form because the media type application/x-www-form-urlencoded explicitly allows for an arbitrary set of name value pairs. There is no "object serialization" going on. It is just a match of names from the form being matched to names on the Model object.
For myself, I created a new media type I use to do partial updates that works like a form but is more advanced in that it can handle hierarchial data and it maintains an order to the updates.
ASP.NET Web API seems to be missing UpdateModel, TryUpdateModel, etc.
In ASP.NET MVC, you could use them to achieve the desired effect. I have created a work item in ASP.NET Web Stack which you can vote for and if it gets enough votes, it will be implemented.
I used Microsoft.AspNet.WebApi.OData for my project and I had some problems working with JSON (working with numbers in my case). Also, the OData package has some dependencies which, from my point of view, are too big for a single feature (~7MB with all dependecies).
So I developed a simple library which do what you are asking for: SimplePatch.
How to use
Install the package using:
Install-Package SimplePatch
Then in your controller:
[HttpPatch]
public IHttpActionResult PatchOne(Delta<Todo> todo)
{
if (todo.TryGetPropertyValue(nameof(Todo.id), out int id)) {
// Entity to update (from your datasource)
var todoToPatch = Todos.FirstOrDefault(x => x.id == id);
if (todoToPatch == null) return BadRequest("Todo not found");
todo.Patch(todoToPatch);
// Now todoToPatch is updated with new values
} else {
return BadRequest();
}
return Ok();
}
The library support massive patch too:
[HttpPatch]
public IHttpActionResult PatchMultiple(DeltaCollection<Todo> todos)
{
foreach (var todo in todos)
{
if (todo.TryGetPropertyValue(nameof(Todo.id), out int id))
{
// Entity to update (from your datasource)
var entityToPatch = Todos.FirstOrDefault(x => x.id == Convert.ToInt32(id));
if (entityToPatch == null) return BadRequest("Todo not found (Id = " + id + ")");
person.Patch(entityToPatch);
}
else
{
return BadRequest("Id property not found for a todo");
}
}
return Ok();
}
If you use Entity Framework, you have to add only two lines of code after the call to the Patch method:
entity.Patch(entityToPatch);
dbContext.Entry(entityToPatch).State = EntityState.Modified;
dbContext.SaveChanges();
Furthermore, you can exclude some properties to be updated when the Patch method is called.
Global.asax or Startup.cs
DeltaConfig.Init((cfg) =>
{
cfg.ExcludeProperties<Todo>(x => x.id);
});
This is usefull when you are working with an entity and you don't want to create a model.

Categories