I'm trying to convert an older ASP.NET application to MVC (I am just learning MVC). and I have a need to display an image in a Gridview. The image itself is stored in a SQL Server table as datatype image. The code that was used previously is below. Can someone suggest an approach using MVC? I was thinking of creating a partial page that I could embed in a standard view, but not sure if that is the right design to implement.
Thanks is advance!
` string sqlText = "SELECT * FROM Images WHERE img_pk = " + id;
SqlConnection connection = new SqlConnection(ConfigurationManager.ConnectionStrings["LocalSqlServer"].ConnectionString);
SqlCommand command = new SqlCommand(sqlText, connection);
connection.Open();
SqlDataReader dr = command.ExecuteReader();
if (dr.Read())
{
//Response.Write("test");
Response.BinaryWrite((byte[])dr["img_data"]);
}
connection.Close();
}
Then it can be referenced using this image tag:
<asp:Image Height="73" Width="80" ID="Image1" ImageAlign="Middle" ImageUrl='<%#"viewimage.aspx?id=" + Eval("ImageId") %>' runat="server"/></a></td>
The first thing is to forget about GridView in an ASP.NET MVC application. Server side controls, postbacks, viewstate, events, ... all those are notions that no longer exists.
In ASP.NET MVC you work with Models, Controllers and Views.
So you could write a controller action which will fetch the image from the database and serve it:
public class ImagesController: Controller
{
public ActionResult Index(int id)
{
string sqlText = "SELECT img_data FROM Images WHERE img_pk = #id";
using (var conn = new SqlConnection(ConfigurationManager.ConnectionStrings["LocalSqlServer"].ConnectionString))
using (var command = conn.CreateCommand())
{
conn.Open();
command.CommandText = sqlText;
command.Parameters.AddWithValue("#id", id);
using (var reader = command.ExecuteReader())
{
if (!reader.Read())
{
return HttpNotFound();
}
var data = GetBytes(reader, reader.GetOrdinal("img_data"));
return File(data, "image/jpg");
}
}
}
private byte[] GetBytes(IDataReader reader, int columnIndex)
{
const int CHUNK_SIZE = 2 * 1024;
byte[] buffer = new byte[CHUNK_SIZE];
long bytesRead;
long fieldOffset = 0;
using (var stream = new MemoryStream())
{
while ((bytesRead = reader.GetBytes(columnIndex, fieldOffset, buffer, 0, buffer.Length)) > 0)
{
byte[] actualRead = new byte[bytesRead];
Buffer.BlockCopy(buffer, 0, actualRead, 0, (int)bytesRead);
stream.Write(actualRead, 0, actualRead.Length);
fieldOffset += bytesRead;
}
return stream.ToArray();
}
}
}
and then in your view simply:
<img src="#Url.Action("Index", "Images", new { id = "123" })" alt="" />
Now of course all this controller action is nice and dandy, but you should really abstract all data access into a repository:
public interface IImagesRepository
{
byte[] GetImageData(int id);
}
then implement this method for the data provider you are using:
public class ImagesRepositorySql: IImagesRepository
{
public byte[] GetImageData(int id)
{
// you already know what to do here.
throw new NotImplementedException();
}
}
Finally you will have your controller become database agnostic. Layers in your application are now weakly coupled between them which would allow you to reuse and unit test them in isolation:
public class ImagesController: Controller
{
private readonly IImagesRepository _repository;
public ImagesController(IImagesRepository repository)
{
_repository = repository;
}
public ActionResult Index(int id)
{
var data = _repository.GetImageData(id);
return File(data, "image/jpg");
}
}
and the last part would be to configure your favorite DI framework to inject the proper implementation of the repository into the controller.
Related
The retrieval and image conversion from byte to image is working great
API CODE:
[HttpGet]
public HttpResponseMessage MemberImage(string employeeId)
{
IMemberProcedures storedProcedure = new StoredProcedure();
IValidation validation = new CommonRepository();
RequestModel request = SQL.Read(out List<MemberDetail> members, storedProcedure.SAMPLESTOREDPROCEDURE("::1", employeeId));
byte[] imgData = members[0].Picture;
MemoryStream ms = new MemoryStream(imgData);
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
response.Content = new StreamContent(ms);
response.Content.Headers.ContentType = new
MediaTypeHeaderValue("image/png");
return response;
}
Trying this in postman returns a fully converted image
Now I wanted to display that image to my Web and this is what I have tried;
WEB CODE:
[HttpGet]
public ActionResult MemberImage(string id)
{
IGetInterface Ip = new IpHelper();
HttpResponseMessage image = API.GetResponse($"api/QMS/MemberImage?employeeId={id}");
var foo = image.Content.ReadAsStringAsync().Result;
return File(Enumerable.Range(0, foo.Length)
.Where(x => x % 2 == 0)
.Select(x => Convert.ToByte(foo.Substring(x, 2), 16))
.ToArray(), "image/png");
}
This triggers an error of Unsupported Media and I dont think I am doing the right way of displaying the image from web controller to my view.
Is there any other way on displaying image result of API to WEB?
Finally after several retries I got it working by using ReadAsByteArrayAsync().
[HttpGet]
public ActionResult MemberImage(string id)
{
IGetInterface Ip = new IpHelper();
HttpResponseMessage image = API.GetResponse($"api/QMS/MemberImage?employeeId={id}");
byte[] foo = image.Content.ReadAsByteArrayAsync().Result;
return File(foo, "image/png");
}
Display it to view by doing this;
<img src='#Url.Action("MemberImage", new { id = Model.Member.EmployeeNumber })'>
Just as the title says I am not getting the MVC Controller to return HttpResponseMessage correctly.
[HttpGet]
[AllowAnonymous]
public HttpResponseMessage GetDataAsJsonStream()
{
object returnObj = new
{
Name = "Alice",
Age = 23,
Pets = new List<string> { "Fido", "Polly", "Spot" }
};
var response = Request.CreateResponse(HttpStatusCode.OK);
var stream = new MemoryStream().SerializeJson(returnObj);
stream.Position = 0;
response.Content = new StreamContent(stream);
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
return response;
}
This is what I got using MVC Controller:
It works fine when using WebApi ApiController
Correct me if I'm wrong I think the problem is MVC is serializing HttpResponseMessage instead of returning it.
By the way I am using MVC 5.
Thanks in advance.
EDIT
I would like to have the flexibility to write to the response stream directly when returning large datasets.
Perhaps try returning an ActionResult from your MVC method instead.
public ActionResult GetDataAsJsonStream() {}
In order to return a stream, you'll likely have to use FileStreamResult. What would be even easier is just returning a JsonResult.
public ActionResult GetDataAsJson()
{
object returnObj = new
{
Name = "Alice",
Age = 23,
Pets = new List<string> { "Fido", "Polly", "Spot" }
};
return Json(returnObj, JsonRequestBehavior.AllowGet);
}
This is pseudo code but the concept should be sound.
Thanks to Phil I got it to work by having the MVC controller return FileStreamResult.
Here is the code
public ActionResult GetDataAsJsonStream()
{
object returnObj = new
{
Name = "Alice",
Age = 23,
Pets = new List<string> { "Fido", "Polly", "Spot" }
};
var stream = new MemoryStream().SerializeJson(returnObj);
stream.Position = 0;
return File(stream, "application/json");
}
UPDATE
A better way to do this is to write directly to the response stream without creating a memory stream
public ActionResult GetJsonStreamWrittenToResponseStream()
{
object returnObj = new
{
Name = "Alice",
Age = 23,
Pets = new List<string> { "Fido", "Polly", "Spot" }
};
Response.ContentType = "application/json";
Response.OutputStream.SerializeJson(returnObj);
return new EmptyResult();
}
One of my Web API methods works perfectly, and the other not at all.
By works perfectly, I mean this:
The other one, though, doesn't seem to even know about itself. It answers the browser request with:
The code seems to be set up the same for both of them, so I don't know why one works like a charm and the other fails so thuddily.
The pertinent code is:
CONTROLLER
public class DepartmentsController : ApiController
{
private readonly IDepartmentRepository _deptsRepository;
public DepartmentsController(IDepartmentRepository deptsRepository)
{
if (deptsRepository == null)
{
throw new ArgumentNullException("deptsRepository is null");
}
_deptsRepository = deptsRepository;
}
[Route("api/Departments/Count")]
public int GetCountOfDepartmentRecords()
{
return _deptsRepository.Get();
}
[Route("api/Departments")]
public IEnumerable<Department> GetBatchOfDepartmentsByStartingID(int ID, int CountToFetch)
{
return _deptsRepository.Get(ID, CountToFetch);
}
REPOSITORY
public class DepartmentRepository : IDepartmentRepository
{
private readonly List<Department> departments = new List<Department>();
public DepartmentRepository()
{
using (var conn = new OleDbConnection(
#"Provider=Microsoft.ACE.OLEDB.12.0;User ID=Freebo;Password=RunningOnEmpty;Data Source=C:\CDBWin\DATA\CCRDAT42.MDB;Jet OLEDB:System database=C:\CDBWin\Data\nrbq.mdw"))
{
using (var cmd = conn.CreateCommand())
{
cmd.CommandText = "SELECT td_department_accounts.dept_no, IIF(ISNULL(t_accounts.name),'No Name provided',t_accounts.name) AS name FROM t_accounts INNER JOIN td_department_accounts ON t_accounts.account_no = td_department_accounts.account_no ORDER BY td_department_accounts.dept_no";
cmd.CommandType = CommandType.Text;
conn.Open();
int i = 1;
using (OleDbDataReader oleDbD8aReader = cmd.ExecuteReader())
{
while (oleDbD8aReader != null && oleDbD8aReader.Read())
{
int deptNum = oleDbD8aReader.GetInt16(0);
string deptName = oleDbD8aReader.GetString(1);
Add(new Department { Id = i, AccountId = deptNum, Name = deptName });
i++;
}
}
}
}
}
public int Get()
{
return departments.Count;
}
private Department Get(int ID) // called by Delete()
{
return departments.First(d => d.Id == ID);
}
If entering:
http://shannon2:28642/api/Departments/Count
in the browser works to execute the Controller's GetCountOfDepartmentRecords() method, why does entering:
http://localhost:28642/api/Departments/5/6
(or:
http://localhost:28642/api/Departments/1/5
etc) not work to execute the Controller's GetBatchOfDepartmentsByStartingID() method?
Your route is missing its parameters.
[Route("api/Departments/{ID:int}/{CountToFetch:int}")]
This question looks similar to your other question below:
Why is my Web API call returning "No action was found on the controller 'DPlatypus' that matches the request"?
If you are expecting the values to come from a non-query string part of a url, you need to define them in the route template. So, it should be
[Route("api/Departments/{id}/{countToFetch}")]
Following is a good article to read about routing and action selection in Web API:
http://www.asp.net/web-api/overview/web-api-routing-and-actions
How to display images in MVC4 from database.
Step 1:
In my code, retrieve the data and place in array class.
public class ImageTable
{
public string ImageId { get; set; }
public string CategoryId { get; set; }
public byte[] Image { get; set; }
}
public class DataAcceess
{
public ImageTable[] GetImages()
{
ImageTable[] Images = null;
SqlConnection Conn = new SqlConnection("Data Source=;Initial Catalog=;UserID=;Password=;");
Conn.Open();
//SqlCommand Cmd = new SqlCommand("Select [Product ID],ImageView1 From Cpecial_Image_tbl", Conn);
SqlCommand Cmd = new SqlCommand("Select b.[Category ID],a.[Product ID], a.[ImageView1] from Cpecial_Image_tbl as a inner join [Cpecial_Product_tbl] as b ON a.[Product ID]=b.[Product ID]", Conn);
SqlDataReader Reader = Cmd.ExecuteReader();
DataTable dt = new DataTable();
dt.Load(Reader);
Images = new ImageTable[dt.Rows.Count];
int i = 0;
foreach (DataRow Dr in dt.Rows)
{
Images[i] = new ImageTable()
{
ImageId = (string)Dr["Product ID"],
CategoryId = (string)Dr["Category ID"],
Image = (byte[])Dr["ImageView1"]
};
i = i + 1;
}
Conn.Close();
return Images;
}
Step 2: In controller retreive the image value assign, it in byte array and return to the view like this.
public ActionResult Index(string id)
{
// var image = db.Categories.First(m => m.CategoryID == id).Picture;
DataAcceess objContext = new DataAcceess();
//byte[] Image = (from a in Images select a.Image.ToArray());
byte[] a;
foreach (var item in objContext.GetImages())
{
a = item.Image;
return File(a, "Image/jpg");
}
return View();
}
Step 3: I added the tag in view like this
this will show only one image.
I want to show all the the images, and also manipulate the image with respect to the Filters
(sorting ascending , desending with catagoryId) like in shopping cart.
Could any one give me the solution?
You have to retrieve every Image separately.
A return statement ends the function where you are.
Personally I would save the images on the file system and paths to them in a ImageUrl annotated model property.
You can then just make a DisplayFor because the images will be a property of your Model.
If you save your image in a Database. The database will get big and slow.
I am trying to save the name of each individual file in a database after they have been uploaded. I have this code that successfully uploads the files.
$(function () {
$("#uploader").plupload({
// General settings
runtimes: 'silverlight,flash,html5',
url: '#Url.Content( "~/FileUploadChunks/UploadChunk" )',
max_file_size: '10mb',
chunk_size: '1mb',
unique_names: true,
// Flash settings
flash_swf_url: '/plupload/js/plupload.flash.swf',
// Silverlight settings
silverlight_xap_url: '/plupload/js/plupload.silverlight.xap'
});
});
This is the Controller
[HttpPost]
public ActionResult UploadChunk(int? chunk, int chunks, string name)
{
var fileUpload = Request.Files[0];
var uploadpath = Server.MapPath("~/App_Data/UploadedFiles");
chunk = chunk ?? 0;
using (var fs = new FileStream(Path.Combine(uploadpath, name), chunk == 0 ? FileMode.Create : FileMode.Append))
{
var buffer = new byte[fileUpload.InputStream.Length];
fileUpload.InputStream.Read(buffer, 0, buffer.Length);
fs.Write(buffer, 0, buffer.Length);
}
return Content("chunk uploaded", "text/plain");
}
Just to test, I tried something like this to try to capture the name and add them to a list to return to the view, but I cannot figure out how to print out the list in the view to see its contents.
[HttpPost]
public ActionResult UploadChunk(int? chunk, int chunks, string name)
{
var fileUpload = Request.Files[0];
var uploadpath = Server.MapPath("~/App_Data/UploadedFiles");
chunk = chunk ?? 0;
using (var fs = new FileStream(Path.Combine(uploadpath, name), chunk == 0 ? FileMode.Create : FileMode.Append))
{
var buffer = new byte[fileUpload.InputStream.Length];
fileUpload.InputStream.Read(buffer, 0, buffer.Length);
fs.Write(buffer, 0, buffer.Length);
}
List<string> list = new List<string>();
foreach (string inputTagName in Request.Files)
{
HttpPostedFileBase file1 = Request.Files[inputTagName];
if (file1.ContentLength > 0)
{
list.Add(file1.FileName);
}
}
ViewBag.List = list;
}
Ultimately I am just trying to loop through the names and save them in a database. Any help is appreciated. Thanks
You didn't give any information about you database, etc. for us to provide you with good help. However I'll give it a shot.
To answer your other question,
I tried something like this to try to capture the name and add them to
a list to return to the view, but I cannot figure out how to print out
the list in the view to see its contents.
In your view, something like this should show them:
#foreach (string fileName in ViewBag.List)
{
#fileName
<br />
}
Assuming your using Entity Framework Code Fist, to save the file name to your database you need to have a model.
public class UploadedFileName
{
public int Id { get; set; }
public string Name { get; set; }
}
then in your controller you can alter the foreach statement you used to something like this:
foreach (HttpPostedFile file in Request.Files)
{
if (file.ContentLength > 0)
{
UploadedFileName uploadedFile = new UploadedFileName();
uploadedFile.Name = file.FileName;
databaseContext.UploadedFileNames.Add(uploadedFile);
}
}
The HttpPostedFile above may need to be HttpPostedFileBase, if you have an error. And the databaseContext is your database context, usually it is db.