web api with multiple complex parameters - rest

I am trying to create an email service web api with multiple attachments. Need to have only one parameter for web api. But, right now I have 2 complex parameters, which web api doesn't accept. Please suggest how do I implement multiple attachments and use only one complex parameter for web api.
[HttpPost]
[ActionName("sendemail")]
public IHttpActionResult processEmail(EmailModel emailModel,
List<HttpPostedFileBase> attachments)
{
........
}
EmailModel
public class EmailModel
{
public string ToAddress { get; set; }
public string FromAddress { get; set; }
public string Body { get; set; }
}
And, in Email Controller I use the list to attach the attachments to MailMessage object.
foreach (HttpPostedFileBase attachment in attachments)
{
if (attachment != null)
{
string fileName = Path.GetFileName(attachment.FileName);
//create linked resource for embedding image
LinkedResource pic = new LinkedResource(attachment.InputStream, "image/jpg");
//add linked resource to appropriate view
htmlView.LinkedResources.Add(pic);
}
}
//add view
msg.AlternateViews.Add(htmlView);

One solution would be to accept your email model as your parameter, and grab the uploaded files in the HttpContext.Request.Files to loop over. That's assuming you are posting with multipart/form-data

Related

How to get a upload button in swagger for IFormFile combined with other properties?

I have created a Asp.net core 3.1 web api with Swagger to upload files to server. the following code is working fine:
[HttpPost("PostFile")]
public ActionResult PostFile(IFormFile uploadedFile)
{
var saveFilePath = Path.Combine("c:\\savefilepath\\", uploadedFile.FileName);
using (var stream = new FileStream(saveFilePath, FileMode.Create))
{
uploadedFile.CopyToAsync(stream);
}
return Ok();
}
I get a nice upload button in swagger when I try to run this.
However, now I wanted to use a different model. that has some more properties along with the IFormFile.
public class FileUploadRequest
{
public string UploaderName { get; set; }
public string UploaderAddress { get; set; }
public IFormFile File { get; set; }
}
When I try to use this model, I dont see any upload button in Swagger that will help me to attach the file in the request.
For some reason, it shows the IFormFile as String. How can I get a upload button here?
In ASP.NET Core Web API, it binds application/json format data by default. But what your model need is multipart/form-data type data. So you need [FromForm] attribute to specific the source.
I use Swashbuckle.AspNetCore version 5.6.3 in ASP.NET Core 3.1:
[HttpPost]
public ActionResult PostFile([FromForm]FileUploadRequest model)
{
}
Result:
You can use IFormFile again in [HttpPost] so you can see the button.
public class FileUploadRequest
{
public string? UploaderName { get; set; }
public string? UploaderAddress { get; set; }
public IFormFile? File { get; set; }
}
[HttpPost]
public IActionResult PostFile(IFormFile file, FileUploadRequest model)
{
var saveFilePath = Path.Combine("c:\\savefilepath\\", model.UploaderAddress!);
using (var stream = new FileStream(saveFilePath, FileMode.Create))
{
file.CopyToAsync(stream);
}
return Ok();
}

How do I fix a 400 Bad Request error in .Net Core POST operation?

I have an .Net Core 2.1 API that posts data using EF core. When I make a POST request from Postman to http://localhost:3642/task/create I get a 400 Bad Request Error (The request cannot be fulfilled due to Bad Syntax).After digging around I got a suggestion to comment out the ValidateAntiForgery token from the controller. When I pass the request from postman with this change I get 200 Ok status message but no data is being committed to the table in Sql Server. Is there something that I should configure in my API, something else am I missing?
My controller looks as follows:
[HttpPost]
// [ValidateAntiForgeryToken]
public async Task<IActionResult>
Create([Bind("Assignee,Summary,Description")] TaskViewModel taskViewModel)
{
if (ModelState.IsValid)
{
_context.Add(taskViewModel);
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
return View();
}
In TaskViewModel.cs I have:
public class TaskViewModel
{
[Required]
public long Id { get; set; }
[Required(ErrorMessage = "Please provide Task Summary")]
[Display(Name = "Summary")]
public string Summary { get; set; }
[Required(ErrorMessage = "Please enter task description")]
[Display(Name = "Description")]
public string Description { get; set; }
[Required(ErrorMessage = "Please select Assignee")]
[Display(Name = "Assign To")]
public string Assignee { get; set; }
}
This is my payload in Postman:
{
"Assignee": "Ed tshuma",
"Summary": "Finish reconciliations",
"Description": "collate all the pending data"
}
There's a number of issues here. First and foremost, why are you saving your view model to the database. This is actually an entity in this case, not a view model. You should definitely be using a view model, but you should also have a separate entity class. Then, your view model should only contain properties that you want to actually allow the user to edit, negating the need entirely for the Bind attribute, which should be avoided anyways. (see: Bind is Evil).
// added "Entity" to the name to prevent conflicts with `System.Threading.Task`
[Table("Tasks")]
public class TaskEntity
{
[Key]
public long Id { get; set; }
[Required]
public string Summary { get; set; }
[Required]
public string Description { get; set; }
[Required]
public string Assignee { get; set; }
}
public class TaskViewModel
{
[Required(ErrorMessage = "Please provide Task Summary")]
[Display(Name = "Summary")]
public string Summary { get; set; }
[Required(ErrorMessage = "Please enter task description")]
[Display(Name = "Description")]
public string Description { get; set; }
[Required(ErrorMessage = "Please select Assignee")]
[Display(Name = "Assign To")]
public string Assignee { get; set; }
}
Also, note the division of responsibility. The entity has only things that matter to the database ([Required] here indicates that the column should be non-nullable). Whereas the view model is concerned only with the view. There's no Id property, since it's not needed or desired, and the display names and error messages to be presented to the user are placed here only.
Then, you'll need to map from your view model to your entity class:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(TaskViewModel model)
{
if (!ModelState.IsValid)
return View(model);
var task = new TaskEntity
{
Assignee = model.Assignee,
Summary = model.Summary,
Description = model.Description
};
_context.Add(task);
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
The mapping here is fairly straight-forward, but you may prefer to utilize a library like AutoMapper to handle this for you: _mapper.Map<TaskEntity>(model).
While this is specifically for a create action, it's worth pointing out the subtle difference for an update. You'll want to first retrieve the existing task from your database and then map the posted values onto that. The rest remains relatively the same:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Update(long id, TaskViewModel model)
{
if (!ModelState.IsValid)
return View(model);
var task = await _context.Tasks.FindAsync(id);
if (task == null)
return NotFound();
task.Assignee = model.Assignee;
task.Summary = model.Summary;
task.Description = model.Description;
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
Finally, as to the main problem from your question, there's two issues. First, this action is designed for a traditional HTML form post (x-www-form-urlencoded). As such, it doesn't make sense to send JSON to it, and sending JSON to it will not work. To test it in Postman, you should send the request as x-www-form-urlencoded. If you do not, then your model will essentially always be invalid, because nothing will be bound to your model from the post body.
In order to receive JSON, your param would need to have the FromBody attribute applied to it ([FromBody]TaskViewModel model). However, if you do that, you can no longer receive traditional form posts, and in this context, that's what's going to be sent. If you were sending via AJAX (where you could conceivably use JSON), then you should also be returning JSON or maybe PartialView, but not View or a redirect.
Lastly, you need to include the request verification token, which should be another key in the post body name __RequestVerificationToken. To get the value to send, you'll need to load the GET version of the view, first, and inspect the source. There will be a hidden input with the value.
Chris Pratt is right, you need to send __RequestVerificationToken.
If you comment out [ValidateAntiForgeryToken] attribute, it seems that you send data from Body-raw-JSON, then you need to use [FromBody] to access data.
[HttpPost]
public async Task<IActionResult> Create([Bind("Assignee,Summary,Description")] [FromBody] TaskViewModel taskViewModel)
If you do not want to add [FromBody], you could send data using form-data
You have to send the anti forgery token with your request if you want to use the decorator [ValidateAntiForgeryToken]. See this link for more information.
Also, even if your model is invalid, you return View(). That means you get a http status 200 even if you send wrong data.
Set a breakpoint on if(ModelState.IsValid) and check if you enter in it. If not, check the format of your payload.
Hope it helps.
EDIT regarding your payload and your model : You need to provide an Id to you payload because of the [Required] decorator in your TaskViewModel. Or you need to get rid of the [Required] attribute on Id. If you don't, if (ModelState.IsValid) will always be false.

Customizing Web API Controller Parameters

We have a Web API Controller generated from a model. The model has this form:
public class Pdf
{
public int ID { get; set; }
public string Name { get; set; }
public string Url { get; set; } // File stored in AWS
public int Job_ID { get; set; }
public List<PdfPage> { get; set; }
}
The automatically generated controller has this default POST route:
// POST: api/Pdfs
[ResponseType(typeof(Pdf))]
public async Task<IHttpActionResult> PostPdf(Pdf pdf)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
db.JobFiles.Add(pdf);
await db.SaveChangesAsync();
return CreatedAtRoute("DefaultApi", new { id = pdf.ID }, pdf);
}
In making a new Pdf, however, the file itself is put in AWS. In the case of hitting this route, where we're making a new Pdf, we want to make a new PDF that will be uploaded to AWS. In order to do that, we want to pass additional parameters to the route that don't exist in the model. Something like this:
// POST: api/Pdfs
[ResponseType(typeof(Pdf))]
public async Task<IHttpActionResult> PostPdf(Pdf pdf, double heightInches, double widthInches)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
// Make a blank PDF based on the dimensions provided
MemoryStream newBlankPdfMemoryStream = PdfOperations.ITextSharpNewBlankPdf(8.5, 11);
pdf.Url = await AWS.S3PubliclyAccessibleInsert("paragonpdfimages", pdf.Job_ID + "/" + pdf.Name, newBlankPdfMemoryStream);
db.JobFiles.Add(pdf);
await db.SaveChangesAsync();
return CreatedAtRoute("DefaultApi", new { id = pdf.ID }, pdf);
}
However, passing this body to the POST route:
{pdf: {Job_Id: 395, Name: "fds"}, widthInches: 8.5, heightInches: 11}
Results in a "405 Method Not Allowed" and a message in the body of "The requested resource does not support http method 'POST'".
How can I accomplish this passing of additional parameters to the POST route? Do I need to make a custom route? If so what would that look like? Thanks in advance.
The problem you're having is caused by the fact that by default Web-Api binds parameters with a simple data type (such as double) using values from the query string of the URL. So you have 2 options:
Tell Web-Api to get the values from the body by modifying your action to be
public async Task<IHttpActionResult> PostPdf(Pdf pdf, [FromBody]double heightInches, [FromBody]double widthInches)
Pass the height and width values in the query string and not in the body.
....../PostPdf?heightInches=11&widthInches=8.5

How to pass query string parameter to asp.net web api 2

How can I pass more than 1 parameters as part of query string to my asp.net web api 2.
This is my asp.net web api 2 method, I am not able to figure out that how can I decorate this method so that it accepts the id and a complex type which is CustomerRequest, I want to use Url something like
http://localhost/api/Customer/?Mobile0012565987&Email=abcxyz.com&IsEmailVerified=true
[ResponseType(typeof(Customer))]
public IHttpActionResult GetCustomer(long id, [FromUri]CustomerRequest request)
{
var customer = db.Customers.Find(request.CustomerId);
if (customer == null)
{
return NotFound();
}
return Ok(customer);
}
This is CustomerRequest class
public class CustomerRequest
{
public string Mobile { get; set; }
public string Email { get; set; }
public Nullable<bool> IsEmailVerified { get; set; }
}
Otherwise pleaase guide me if there is a better way to do it.
Thanks
Based on your code, you need to pass 'id' as well, like this:
http://localhost/api/Customer/?id=12345&Mobile=0012565987&Email=abcxyz.com&IsEmailVerified=true
if you want to make 'id' optional, you can make your method signature look like this:
public IHttpActionResult GetCustomer([FromUri]CustomerRequest request, long id = 0)
this will set id to 0 by default, if you dont pass it in the URL. So you will be able to access your URL like you originally did:
http://localhost/api/Customer/?Mobile=0012565987&Email=abcxyz.com&IsEmailVerified=true

Dynamic validation on MVC 2

This works fine
[MetadataType(typeof(Area_Validation))]
public partial class Area
{
...
}
public class Area_Validation
{
[Required(ErrorMessage = "Please add this field.")]
public int Email { get; set; }
[Required(ErrorMessage = "Please add this field")]
public string Name { get; set; }
}
but how about if Area_Validation is dynamically created? for example Subscription Fields that on back-end can be created by the user and end up like this:
How can I set the Metadata on each field for auto validation?
Currently I'm doing:
public class SubscriberFormViewModel
{
public List<SubscriberFieldModel> Fields { get; private set; }
public Calendar Calendar { get; private set; }
public Company Company { get; private set; }
public SubscriberFormViewModel()
{
// TODO: This is only for testing while validation is not set
}
public SubscriberFormViewModel(Decimal calendarId)
{
if (calendarId > 0)
{
SubscribersRepository db = new SubscribersRepository();
Calendar calendar = db.GetCalendarById(calendarId);
Company company = db.GetCompanyById(calendar.company_id);
this.Fields = db.FindAllSubscriberFieldsByCalendar(calendarId);
this.Calendar = calendar;
this.Company = company;
}
else
this.Fields = new List<SubscriberFieldModel>();
}
}
and I want to set the Metadata in all Fields property
In other words, this Fields are filled up from the Database and can have several types, can be a string, number, dropdown, etc ... kinda like MailChimp Fields Properties:
is there a way to do this programmaticaly or I need to create a jQuery plugin to validate it and stop using use validation from MVC2 ?
Thank you
Actually you can make several validation scenarios, one per type(or more per type if you need). Then Add this validation rules on type creation. When you need to validate, you can call template Validate method, that will check if these rules and if not - will add errors to ModelState, that you will be able to show on front end.
Actually its not that profitable to add any attributes, as attributes pro is that you can decorate your type with them. When you are doing some dynamic things, you`d better have some dynamic way to add validation.
I dont think you can do this using Data Annotations attributes.
There is a project in Codeplex, called Fluent Validation that permit you to add validation rules in a fluent way using .Net code. I never used that project but it seems that maybe can help you in your case with dynamically created objects.
Hope it helps!