As you can see I want to add directly image file to the sql server by getting id, carId, uploadDate as Json and imagePath as file byte array.
How can I do that what should I change ?
The default model binder can not handle byte arrays that would be set to null.
As #viveknuna mentioned, if possible, you can try to use IFormFile to process or save the uploaded file.
Besides, if you really want to bind selected file to byte arrays, you can try to implement and use a custom model binder, like below.
public class ImageToByteArrayModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
// ...
// implement it based on your actual requirement
// code logic here
// ...
if (bindingContext.ActionContext.HttpContext.Request.Form.Files["ImagePath"]?.Length > 0)
{
var fileBytes = new byte[bindingContext.ActionContext.HttpContext.Request.Form.Files["ImagePath"].Length];
using (var ms = new MemoryStream())
{
bindingContext.ActionContext.HttpContext.Request.Form.Files["ImagePath"].CopyTo(ms);
fileBytes = ms.ToArray();
}
bindingContext.Result = ModelBindingResult.Success(fileBytes);
}
return Task.CompletedTask;
}
}
Apply the ModelBinder attribute to model property
[ModelBinder(BinderType = typeof(ImageToByteArrayModelBinder))]
public byte[] ImagePath { get; set; }
Test Result
Related
I need to import a file, and also the Person model that I have shown below.
I am able to upload the file, However, I am not able to retrieve the Person model data in the importFileAndOtherInfo method that I have written.
Note: I am testing this web API via Postman. How can I upload a file, and also send Person Model data via Postman?
Person
int pId
string PName
School schoolAttended
My implementation:
[HttpPost]
public async Task<int> importFileAndOtherInfo(Person person)
{
var stream = HttpContext.Current.Request.Files[0].InputStream
// HOW TO RETRIEVE THE PERSON DATA HERE.
}
What I understood from your question is, you want to pass model data and the file in stream at the same time; you can't send it directly, the way around is to send file with IFormFile and add create your own model binder as follows,
public class JsonWithFilesFormDataModelBinder: IModelBinder
{
private readonly IOptions<MvcJsonOptions> _jsonOptions;
private readonly FormFileModelBinder _formFileModelBinder;
public JsonWithFilesFormDataModelBinder(IOptions<MvcJsonOptions> jsonOptions, ILoggerFactory loggerFactory)
{
_jsonOptions = jsonOptions;
_formFileModelBinder = new FormFileModelBinder(loggerFactory);
}
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
throw new ArgumentNullException(nameof(bindingContext));
// Retrieve the form part containing the JSON
var valueResult = bindingContext.ValueProvider.GetValue(bindingContext.FieldName);
if (valueResult == ValueProviderResult.None)
{
// The JSON was not found
var message = bindingContext.ModelMetadata.ModelBindingMessageProvider.MissingBindRequiredValueAccessor(bindingContext.FieldName);
bindingContext.ModelState.TryAddModelError(bindingContext.ModelName, message);
return;
}
var rawValue = valueResult.FirstValue;
// Deserialize the JSON
var model = JsonConvert.DeserializeObject(rawValue, bindingContext.ModelType, _jsonOptions.Value.SerializerSettings);
// Now, bind each of the IFormFile properties from the other form parts
foreach (var property in bindingContext.ModelMetadata.Properties)
{
if (property.ModelType != typeof(IFormFile))
continue;
var fieldName = property.BinderModelName ?? property.PropertyName;
var modelName = fieldName;
var propertyModel = property.PropertyGetter(bindingContext.Model);
ModelBindingResult propertyResult;
using (bindingContext.EnterNestedScope(property, fieldName, modelName, propertyModel))
{
await _formFileModelBinder.BindModelAsync(bindingContext);
propertyResult = bindingContext.Result;
}
if (propertyResult.IsModelSet)
{
// The IFormFile was successfully bound, assign it to the corresponding property of the model
property.PropertySetter(model, propertyResult.Model);
}
else if (property.IsBindingRequired)
{
var message = property.ModelBindingMessageProvider.MissingBindRequiredValueAccessor(fieldName);
bindingContext.ModelState.TryAddModelError(modelName, message);
}
}
// Set the successfully constructed model as the result of the model binding
bindingContext.Result = ModelBindingResult.Success(model);
}
}
Model
[ModelBinder(typeof(JsonWithFilesFormDataModelBinder), Name = "data")]
public class Person
{
public int pId {get; set;}
public string PName {get; set;}
public School schoolAttended {get; set;}
public IFormFile File { get; set; }
}
Postman request:
I am using the same in netcoreapp2.2. Works successfully.
Now when migrating from notecoreapp2.2 to netcoreapp3.1 I'm facing issues with private readonly IOptions<MvcJsonOptions> _jsonOptions;
As MvcJsonOptions is a breaking change from core 2.2 to 3.0.
Check this:
Migration of netcoreapp2.2 to netcoreapp3.1 - convert MvcJsonOptions to core3.1 compatable
This is to submit a view that uses ajax to pull the data.
I am trying to retrieve an image from MongoDb GridFS bucket. The download operation works. But the issue is, the view model that I use has:
public User User { get; set; }
public List<IFormFile> ImageUpload { get; set; }
I get the user from the db context, and use that to identify the image from the bucket and I want to return it to the view from the controller.
The following is my controller code:
public IActionResult AddEditUser(int id = 0)
{
if (id == 0)
{
return View(new UserImgBinder());
}
else
{
var userImageId = _context.User.Where(x => x.UserId.Equals(id)).Select(y => y.AvatarImg).ToString();
var stream = ContextNew.ImagesBucket.OpenDownloadStream(new ObjectId(userImageId.ToBson()));
var contentType = stream.FileInfo.Metadata["ContentType"].AsString;
//File(stream, contentType);
IFormFile file = new FormFile(stream, 0, stream.Length,"","");
var UserImgBinder = new UserImgBinder
{
User = _context.User.Where(x => x.UserId.Equals(id)).FirstOrDefault(),
ImageUpload = file
};
return View(UserImgBinder);
}
}
Since ImageUpload is of type List and what I am trying to feed it is not a list I am getting error.
What should I write to solve this? Is this approach bad?
Please help.
Thanks.
Try code like:
var UserImgBinder = new UserImgBinder
{
User = _context.User.Where(x => x.UserId.Equals(id)).FirstOrDefault(),
ImageUpload = new List<IFormFile> {
file
}
};
Got a little bit of a problem. I have a program that builds an observable collection of Users. The User has a Firstname, Lastname, and Image. I can add the user to the observable collection, but I also want to save the collection and load it everytime I reopen the program.
My problem is that while its fairly easy to save a firstname and lastname, the writer can't write the image to the xml file. Is there any way around this?
Here's what I have so far:
the observable collection:
ObservableCollection<VendorClass> ProfileList = new ObservableCollection<VendorClass>();
the problematic writer:
XmlSerializer xs = new XmlSerializer(typeof(ObservableCollection<VendorClass>));
using (StreamWriter wr = new StreamWriter("vendors.xml")) //Data/customers.xml
{
xs.Serialize(wr, ProfileList);
}
Any ideas? And if there does exist a solution to write in an image, is there a viable way to read it out again?
XmlSerializer can't serialize or deserialize the WPF image types like BitmapImage etc. It is however able to (de)serialize byte arrays. So you may add a byte[] ImageBuffer property to your Person class, which contains the binary image data. You would then also set the XmlIgnore attribute on the Image property to suppress its (de)serialization, and set XmlElement("Image") on the ImageBuffer properties to (de)serialize it as <Image>...</Image>.
public class User
{
public string FirstName { get; set; }
public string LastName { get; set; }
[XmlIgnore]
public BitmapSource Image { get; set; }
[XmlElement("Image")]
public byte[] ImageBuffer
{
get
{
byte[] imageBuffer = null;
if (Image != null)
{
using (var stream = new MemoryStream())
{
var encoder = new PngBitmapEncoder(); // or some other encoder
encoder.Frames.Add(BitmapFrame.Create(Image));
encoder.Save(stream);
imageBuffer = stream.ToArray();
}
}
return imageBuffer;
}
set
{
if (value == null)
{
Image = null;
}
else
{
using (var stream = new MemoryStream(value))
{
var decoder = BitmapDecoder.Create(stream,
BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
Image = decoder.Frames[0];
}
}
}
}
}
This approach has also been suggested for properties of type Bitmap in this answer.
You would base64 encode the image to convert it to a string and then write that into a CDATA section. See How do you serialize a string as CDATA using XmlSerializer?
Ideally I would like to have an URL in following format:
/api/categories/1,2,3...N/products
And this would return all products for the specified categories. Having one API call with multiple category IDs saves me several database calls, thus improves performance.
I can easily implement this in a following way.
public HttpResponseMessage GetProducts(string categoryIdsCsv)
{
// <1> Split and parse categoryIdsCsv
// <2> Get products
}
However, this doesn't look like a clean clean solution, and possibly breaking SRP principle. I also tried using ModelBinder, however it adds parameters to query string.
Questions:
Is there a clean way to implement such URL structure?
Or is there a different/better approach to retrieve all products for multiple categories?
Please let me know if you need any further clarification.
I've just found an answer to my question. Route attribute had missing parameter when using ModelBinder.
[Route("api/categories/{categoryIds}/products")]
public HttpResponseMessage GetProducts([ModelBinder(typeof(CategoryIdsModelBinder))] CategoryIds categoryIds)
{
// <2> Get products using categoryIds.Ids
}
And CategoryIds would be
public class CategoryIds
{
public List<int> Ids{ get; set; }
}
And CategoryIdsModelBinder would be
public class CategoryIdsModelBinder : IModelBinder
{
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
if (bindingContext.ModelType != typeof(CategoryIds))
{
return false;
}
var val = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (val == null)
{
return false;
}
var key = val.RawValue as string;
if (key == null)
{
bindingContext.ModelState.AddModelError(bindingContext.ModelName, "Wrong value type");
return false;
}
var values = val.AttemptedValue.Split(',');
var ids = new List<int>();
foreach (var value in values)
{
int intValue;
int.TryParse(value.Trim(), out intValue);
if (intValue > 0)
{
ids.Add(intValue);
}
}
if (ids.Count > 0)
{
var result = new CategoryIds
{
Ids= ids
};
bindingContext.Model = result;
return true;
}
bindingContext.ModelState.AddModelError(
bindingContext.ModelName, "Cannot convert value to Location");
return false;
}
We can use Post methods
[RoutePrefix ( "api/categories" )]
public class TestController
{
[HttpPost]
[Route ( "getProducts" )]
public HttpResponseMessage GetProducts ( HttpRequestMessage request )
{
HttpResponseMessage message = null;
string input = string.Empty;
input = request.Content.ReadAsStringAsync ().Result;
var ids = Newtonsoft.Json.JsonConvert.DeserializeObject<List<string>> ( input );
}
}
Unfortunately Web API can not parse your data as array or as some kind of your custom object out of the box.
If you want to parse your url param as array you can try to do:
Write your own route constraint which will read and convert your param from string to array of ints/strings/whatever;
Write your custom type converter and use it with your data model;
write your value provider and also use it with your data model
Use parameter binding
Moreover you can always use query params which is never will break principles of REST :)
Please see more details about here and here
Hope that helps
I am storing images as byte arrays inside a SQL database.
Here is my Index method (inside the controller):
public ActionResult Index()
{
var list = db.UserProfiles.ToList();
Dictionary<int, ActionResult> picture = list.ToDictionary(item => item.CarID, item => CarPic(item.Picture));
ViewBag.CarPictures = picture;
return View(list);
}
Here is my CarPic method:
public ActionResult CarPic(byte[] imageBytes)
{
return imageBytes == null ? null : File(imageBytes, "image/jpeg");
}
Here is how I am trying to display the image inside my view:
foreach(var item in Model)
{
<img src="#ViewBag.CarPictures[item.CarID]"/>
}
This is what shows up in the web browser:
Here is a screenshot of Intellisense:
So Picture is not null, it is a byte array. The image itself is a JPEG. Here is the code I am using to convert the image to a byte array to store it in the database:
[Authorize]
[HttpPost]
public ActionResult Create(Stock stock, HttpPostedFileBase file)
{
if (file != null)
{
if (file.ContentType.Contains("image"))
{
using (var inputStream = file.InputStream)
{
var memoryStream = inputStream as MemoryStream;
if (memoryStream == null)
{
memoryStream = new MemoryStream();
inputStream.CopyTo(memoryStream);
}
var data = memoryStream.ToArray();
stock.Picture = data;
}
}
}
if (ModelState.IsValid)
{
db.UserProfiles.Add(stock);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(stock);
}
I have checked the Database and the field is indeed populated. Here is my Stock model:
[Table("Stock")]
public class Stock
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int CarID { get; set; }
public byte[] Picture { get; set; }
//etc etc
}
I am tearing my hair out here trying to work out why this isn't working, as I think I have done everything right. What am I doing wrong?
Thanks very much
You're misunderstanding the <img> tag.
<img src="..." /> allows you to display an image from a URL.
The src attribute must specify a URL that returns the actual image.
If you want to embed the image in the page, you can use a data: URI.