How do I pass an IFormFile to my service layer? - c#

I'm working on an ASP.NET Core 2 MVC application, trying my hand at setting up a service layer. I have a pretty basic application in which I want to:
upload an image alongside a name
add a record to the database
store the file on disk in the wwwroot folder with a naming convention that corresponds to the primary key of the record I just added.
Right now I've got that all that working just fine in my controller with a viewmodel. But I'd like to instead have a nice method in my service layer that does both the database add and the file system add, but I'm not finding a way to pass the file over to the service.
I figured out how to make the database add in the service layer too, but not how to get the file over there to do anything with it.
This is my controller:
[HttpPost]
public async Task<IActionResult> Create(CreateMentorViewModel model)
{
var mentor = new Mentor
{
FirstName = model.FirstName,
LastName = model.LastName,
Created = DateTime.Now,
};
_ctx.Add(mentor);
await _ctx.SaveChangesAsync();
int newid = mentor.Id;
if (model.Image != null)
{
var uploads = Path.Combine(_hostingEnvironment.WebRootPath, "img/profilepics");
var fileId = newid + ".jpg";
var filePath = Path.Combine(uploads, fileId);
model.Image.CopyTo(new FileStream(filePath, FileMode.Create));
}
return RedirectToAction("Index", "Mentor");
}
What I'm looking for is some pointers on how to send the file from the viewmodel in the controller to the service or maybe more to the point - should I do that at all?

You shouldn't depend on ASP.NET in your service layer. Pass on Stream instead:
var result = await myService.ProcessFileAsync(formFile.OpenReadStream());

You can simply pass the IFormFile as a parameter to the service e.g.
public void MyMethod(IFormFile file)
{
...
}
and pass model.Image
[HttpPost]
public async Task<IActionResult> Create(CreateMentorViewModel model)
{
...
myService.MyMethod(model.Image);
...
}

Related

How do i call a Put-method from WEB API in separate project?

I have built a Web API that is connected to a database for persons. I am now trying to call this Web API from a separate MVC-application which is supposed to have full CRUD. So far i have managed to do so with the Get and Post-methods to create a new person and see a list of the persons currently in the database.
When trying to do a similar call for the Put-method, i get the following error:
This is how my method UpdatePerson is written in my API-application:
[HttpPut]
[Route("{id:guid}")]
public async Task<IActionResult> UpdatePerson([FromRoute] Guid id, UpdatePersonRequest updatePersonRequest)
{
var person = await dbContext.Persons.FindAsync(id);
if (person != null)
{
person.Name = updatePersonRequest.Name;
person.Email = updatePersonRequest.Email;
person.Phone = updatePersonRequest.Phone;
person.Address = updatePersonRequest.Address;
await dbContext.SaveChangesAsync();
return Ok(person);
}
And this is how i am trying to consume the API in my separate MVC-project:
[HttpGet]
public IActionResult Edit()
{
return View();
}
[HttpPost]
public async Task<IActionResult> Edit(PersonViewModel pvm)
{
HttpClient client = new();
StringContent sContent = new StringContent(JsonConvert.SerializeObject(pvm), Encoding.UTF8, "application/json");
HttpResponseMessage response = await client.PutAsync("https://localhost:7281/api/Persons/", sContent);
response.EnsureSuccessStatusCode();
if (response.IsSuccessStatusCode)
{
return RedirectToAction("Get");
}
else
{
return NotFound();
}
}
Everything is working fine when i try to update the database through the API-app so i am not really sure what is wrong with my request. I hope that someone here can spot the issue right away or at least help me out as i am quite a beginner with WEB APIs.
I have mostly tried changing the URL in my MVC-project but the issue remains.
Are you sure you are receiving the request? It seems that your URI is
"https://localhost:7281/api/Persons/"
and your API is expecting
"https://localhost:7281/api/Persons/{id}" -> where {id} should be the guid
you need to append the guid in the URI
Looks like the request doesn't receive the correct the correct parameters, because the URI that appears in your picture seems a generic method.

Return a file and a string from Controller to view in ASP.NET MVC

I was wondering if it's possible to send a string to the client and at the same time a file to the client. My purpose it is that if file has been sent to the client, it has also to show up a message of 'file downloaded successfully" like a mix of the following codes that belong to controllers:
return File(files, "application/octet-stream", "FNVBA.zip");
vm.OutputMessage = OutputMessages.SuccessDownload;
return RedirectToAction("Crea", "Spedizione", vm);
If that's possible only through AJAX, is there some sample to follow? It means I need to send the file to the AJAX request.
The approach I used for this was to send the file as a byte array in a json object, and include the filename in the object as well.
//Snippet from controller:
try
{
var imageFile = await _imageStore.GetImageFile(gId);
var imageObject = new RetrievedImageObject();
imageObject.InitializeFile(imageFile);
imageObject.Info = await _infoStorageService.GetInfo(gId);
return new JsonResult (imageObject);
}
// Snippet from ImageObject. The filename is derived from the id:
public void InitializeFile (RetrievedImageFile f)
{
DocType = f.DocType;
Id = f.Id;
using (var memStream = new MemoryStream())
{
f.FileContentStream.CopyTo(memStream);
FileContentByteArray = memStream.ToArray();
}
}

How to connect Blazor WebAssembly to DataBase

I recently started developing a Blazor WebAssembly app, and now I'm settling on a database connection.
All lessons and instructions say that you need to enter information into the Startup.cs file and appsettings.json, but these files are not in the project.
I do not understand. In Blazor WebAssembly, is there no way to connect to the DB?
Not directly. Blazor WebAssembly is a front end framework. You need to create an API controller to wrap your database connection and use HttpClient to call the api. A straight forward way to do it is to use an asp.net core web api controller wrapping an Entity Framework Core Database context.
#inject HttpClient Http
<template_html_here/>
#code
{
protected override async Task OnInitializedAsync()
{
products = await Http.GetFromJsonAsync<Product[]>("api/Products");
}
}
Controller:
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
private readonly ProductsDbContext _context; // your database context
public ProductsController(ProductsDbContext context)
{
_context = context;
}
[HttpGet]
public IEnumerable<Product> Get()
{
return _context.Products.ToList();
}
}
You can read more about blazor at https://learn.microsoft.com/en-us/aspnet/core/blazor/call-web-api?view=aspnetcore-3.1.
And on asp.net core web APIs on https://learn.microsoft.com/en-us/aspnet/core/tutorials/first-web-api?view=aspnetcore-3.1&tabs=visual-studio.
It is now 2022. .NET 6 has shipped, and Blazor WebAssembly has support for compiled binaries.
That means there are now three options for using a database in a Blazor WebAssembly application.
#1. Create a webApi. Call the webApi on from the client as you can see being done in the default sample. See FetchData.razor
protected override async Task OnInitializedAsync()
{
forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast");
}
and WeatherForecastController.cs on the server. The default solution does not call a database, but you could easily use a dbContext in Get() to pull data from a database.
#2: With support for compiled binaries in Blazor WebAssembly, it is now possible to completely host Sqlite in WebAssembly.
https://github.com/TrevorDArcyEvans/BlazorSQLiteWasm
#3: IndexedDb. Through js interop, the IndexDb in the browser can be used. Large amounts of data can be stored in this Db, and like the name implies, it is indexed. Since this can be accidentally cleared, it is most useful in a PWA, where that is more difficult. Also, with this and Sqlite, anything done in the browser is open to the user and hackers that compromise the user's maching.
I use https://github.com/wtulloch/Blazor.IndexedDB
You add schema in program.cs:
builder.Services.AddIndexedDB(dbStore =>
{
dbStore.DbName = "SomeDbName";
dbStore.Version = 1;
dbStore.Stores.Add(new StoreSchema
{
Name = "People",
PrimaryKey = new IndexSpec { Name = "id", KeyPath = "id", Auto = false },
Indexes = new List<IndexSpec>
{
new IndexSpec{Name="alias", KeyPath = "alias", Auto=false},
new IndexSpec{Name="isAvailable", KeyPath = "isAvailable", Auto=false},
new IndexSpec{Name="communityId", KeyPath = "communityId", Auto=false},
new IndexSpec{Name="isFriend", KeyPath = "isFriend", Auto=false},
}
});
});
In this code, the names of the fields are camelCased, whereas the objects I'm constructing are PascalCase. This was actually necessary for me to get it to work. I think my serializer may be set to camelCase Json or something, so watch that.
And then you add remove and search using:
public async Task AddPersonAsync(LocalPerson member)
{
var newPerson = new StoreRecord<LocalPerson>
{
Data = member,
Storename = PeopleStoreName
};
await _dbManager.AddRecord(newPerson);
}
public async Task<LocalPerson> GetPersonByIdAsync(Guid id)
{
var localPerson = await _dbManager.GetRecordById<Guid, LocalPerson>(PeopleStoreName, id);
return localPerson;
}
public async Task<List<LocalPerson>> GetPeopleAsync()
{
var results = await _dbManager.GetRecords<LocalPerson>(PeopleStoreName);
return results;
}
public async Task<List<LocalPerson>> GetPeopleByCommunityAsync(Guid id)
{
var indexSearch = new StoreIndexQuery<Guid>
{
Storename = PeopleStoreName,
IndexName = "communityId",
QueryValue = id,
};
var result = await _dbManager.GetAllRecordsByIndex<Guid, LocalPerson>(indexSearch);
if (result is null)
{
return new List<LocalPerson>();
}
return (List<LocalPerson>)result;
}
If you are referring to local storage (browser storage) then this component by Chris Sainty could help you.
However if you are looking for a connection to a Database like a SQL Server or document storage like Mongo it can not be done directly.
Blazor Wasm is for front end development. You will need to call web APIs that connect to databases stored on servers.

formData is always null when arriving in API core 3.1

Trying to upload an image to my backend using FormData. file inside my API is always null when using my angular code.
Creating FormData:
upload() {
let formData = new FormData();
formData.append('upload', this.editedImage);
console.log("editedImage: " + this.editedImage);
console.log("formdata: " + formData);
this.imageService.create(formData).subscribe(response => {
let temp = response;
console.log(temp);
})
}
Service that does the post call:
create(formData: FormData): Observable<any> {
console.log("formdata in service: " + formData.get('upload'));
return this.http.post<any>(`${this.url}`, formData).pipe(
tap(data => console.log('Image verstuurd: ' + data)),
catchError(this.handleError)
);
}
API that receives it:
[HttpPost]
async public Task<ActionResult> Save([FromForm]IFormFile file)
{
// Get reference to blob
var uniqueName = $#"{Guid.NewGuid()}-{file.FileName}";
CloudBlockBlob blockBlob = _blobContainer.GetBlockBlobReference(uniqueName);
using var ms = new MemoryStream();
file.CopyTo(ms);
var stream = ms.ToArray();
await blockBlob.UploadFromByteArrayAsync(stream, 0, stream.Length);
return Json(new
{
name = blockBlob.Name,
uri = blockBlob.Uri
});
}
It works when I upload an image using Postman, not when using the angular code.
EDIT:
The correct answer was found here:
https://stackoverflow.com/a/35325907/7128762
The problem is basicly that I needed to give my IFormFile the correct name in my controller.
[FromForm] is going to try to build a reference type for the form data it receives so your FormFile needs to be inside of a reference type you define.
Set up your model with something like this:
public class YourDataModel
{
public IFormFile File { get; set; }
// whatever other properties you need
}
Try something like this in your API Controller:
[HttpPost]
async public Task<ActionResult> Save([FromForm]YourDataModel file)
{
// your logic
}

How to call a Post api with multiple parameters

How can i call a Post method with multiple parameters using HttpClient?
I am using the following code with a single parameter:
var paymentServicePostClient = new HttpClient();
paymentServicePostClient.BaseAddress =
new Uri(ConfigurationManager.AppSettings["PaymentServiceUri"]);
PaymentReceipt payData = SetPostParameter(card);
var paymentServiceResponse =
paymentServicePostClient.PostAsJsonAsync("api/billpayment/", payData).Result;
I need to add another parameter userid. How can i send the parameter along with the 'postData'?
WebApi POST method prototype:
public int Post(PaymentReceipt paymentReceipt,string userid)
Simply use a view model on your Web Api controller that contains both properties. So instead of:
public HttpresponseMessage Post(PaymentReceipt model, int userid)
{
...
}
use:
public HttpresponseMessage Post(PaymentReceiptViewModel model)
{
...
}
where the PaymentReceiptViewModel will obviously contain the userid property. Then you will be able to call the method normally:
var model = new PaymentReceiptViewModel()
model.PayData = ...
model.UserId = ...
var paymentServiceResponse = paymentServicePostClient
.PostAsJsonAsync("api/billpayment/", model)
.Result;
UserId should be in query string:
var paymentServiceResponse = paymentServicePostClient
.PostAsJsonAsync("api/billpayment?userId=" + userId.ToString(), payData)
.Result;
In my case my existing ViewModels don't line up very nicely with the data I want to post to my WebAPI. So, instead of creating an entire new set of model classes, I posted an anonymous type, and had my Controller accept a dynamic.
var paymentServiceResponse = paymentServicePostClient.PostAsJsonAsync("api/billpayment/", new { payData, userid }).Result;
public int Post([FromBody]dynamic model)
{
PaymentReceipt paymentReceipt = (PaymentReceipt)model.paymentReceipt;
string userid = (string)model.userid;
...
}
(I'd be curious to hear some feedback on this approach. It's definitely a lot less code.)

Categories