I have a controller method as below to send an image to a MVC view to display
public FileResult ShowImage(GuidID)
{
DataServiceClient client = new DataServiceClient ();
AdviserImage result;
result = client.GetAdviserImage(ID);
return File(result.Image, "image/jpg" );
}
in my view I am using
<img src="<%= Url.Action("ShowImage", "Adviser", new { ID = Model.AdviserID }) %>" alt="<%:Model.LicenceNumber %>" />
to display the image
but some ids does not have a image and returning null, I want to check the file result is null withing the view and if its null not not to display the image.
You will need another, separate controller action that checks the datastore and returns ContentResult which will be either true or false (or some other string you want to tell whether an ID has the bytes or not) and then in the view you will need this:
if(#Html.Action("action", "controller").ToString().Equals("true", StringComparison.OrdinalIgnoreCase)){
// render image tag with the call to the other action that returns FileResult
}
The other option is that you have a view model which contains a reference to the image bytes. That way you prepare the model for the view (the parent model) in the controller and pull the bytes for the image there, then in the view you would have:
if(Model.ImageBytes.Length() > 0) {
... do something
}
with ImageBytes property being of type byte[]
For instance, this is a snippet from one of my views:
#model pending.Models.Section
#if (Model != null && Model.Image != null && Model.Image.ImageBytes.Count() > 0)
{
<a href="#Model.Url" rel="#Model.Rel">
<img title="#Model.Title" alt="#Model.Title" src="#Url.Action(MVC.Section.Actions.Image(Model.Id))" /></a>
}
HTH
Why not check for null in your controller and leave the logic out of the display:
result = client.GetAdviserImage(ID);
if (result == null)
{
result = AdviserImage.Missing;
}
You can create a default image and make it static. If you really don't want to display the image then create an Html extension method to keep the logic out of the view:
public static string AdviserImage(this HtmlHelper helper, AdviserImage image, int id, int lic)
{
if (image != null)
{
string url = string.Format("/Adviser/ShowImage/{0}", id);
string html = string.Format("<img src=\"{0}\" alt=\"{1}\" />", url, image.lic);
return html;
}
return string.Empty; // or other suitable html element
}
Related
How can I get both the image and tooltip in a single call to a MVC controller method? I can use something like the following to get the image, but how to also get the associated tooltip? The use case is to display an image if the user is allowed to see the image, else display a generic image and a tooltip indicating why the image is not being shown.
To clarify, I would like to avoid two calls to the controller, once to get the image path and tooltip, and another to get the image file. Not only will this result in two round trips across the network, it would also repeat the validation checks. The problem is that the img src call only accommodates the image, not other properties such as the title associated with the image.
<img src="#Url.Action("GetPicture", "User", new { userId = Model.User.Id })" />
Can't you just have a second method for GetTitle using the same permission logic from GetImage and return the appropriate text for each user? Then call this method for the title attribute.
Can you approach the problem in the following way.
Create Model
public class ImageViewModel
{
public string ImagePath
{
get;
set;
}
public string ImageTitle
{
get;
set;
}
}
Create a partial View
#using StackOverFlowProject.Models
#model ImageViewModel
<img src=#Model.ImagePath title=#Model.ImageTitle />
Your Controller
public PartialViewResult _Image(string userID)
{
ImageViewModel model = new ImageViewModel();
//Here You can check what image and tooltip you want show to the user
//model = //FillData from DB /
return PartialView("_Image", model);
}
and at last in Your MainView where you want to display the Image render the partial view
Not sure if this is the best way to do it, but I did it by creating an Ajax form, submitting it using jQuery, returning a JSON object with the byte array encoded as a Base64 string, and using Javascript to display the image. Seems to be working so far, will know more from further tests.
In the view:
<div id="imgDiv">
#using (Ajax.BeginForm("GetImg", "User", null, new AjaxOptions()
{
HttpMethod = "POST",
Url = Url.Action("GetImg", "User"),
OnSuccess = "DisplayImageWithTooltip(data, 'imgDiv')",
}, new { id = "ImgForm", #class = "imageGetterWithTooltip" }))
{
#Html.AntiForgeryToken()
#Html.Hidden("userId", #Model.User.Id)
}
</div>
Javascript to submit form:
$(".imageGetterWithTooltip").submit();
In Controller (based on https://stackoverflow.com/a/9464137/1385857)
return Json(new
{
fileBytes = Convert.ToBase64String(<File byte[]>),
fileType = <FileType>,
tooltip = <toolTip>
}, JsonRequestBehavior.AllowGet);
Javascript to display image
function DisplayImageWithTooltip(data, target) {
var oImg = document.createElement("img");
oImg.width = 150;
oImg.height = 150;
oImg.setAttribute('src', "data:" + data.fileType + ";base64," + data.fileBytes);
oImg.setAttribute('title', data.tooltip);
document.getElementById(target).appendChild(oImg);
}
Using Manish's ideas, my simplified solution is to create a partial view and supply it the image data directly:
Controller:
vmMiniData data = new Models.vmMiniData();
byte[] byteArray = Users.GetPersonnelImage(personnelID);
if (byteArray != null)
{
data.ImageStr = Convert.ToBase64String(byteArray);
}
else
{
data.ImageStr = Convert.ToBase64String(Users.GetPersonnelImage("00000000-0000-0000-0000-000000000000")); //get blank image
}
data.CaptionStr = Users.GetUserJobTitle(personnelID);
return PartialView("Personnel/MiniPersonnelPartial", data);
Model:
public static byte[] GetPersonnelImage(string personnelID)
{
byte[] img = (byte[])(from record in db.PersonnelImages
.Where(R => R.PersonnelID == new Guid(personnelID))
select record.Image).FirstOrDefault();
return img;
}
Then in the partial:
#model vmMiniData
<div>
<div>#Model.CaptionStr</div>
<div> <img src="data:image;base64,#Model.ImageStr" style="width:60px;min-height:30px;" /></div>
</div>
It works very well in MVC 5 :).
I've got the following snippet of Razor code, that exists in probably 15 different pages, that I'd like to reuse, if possible:
<div class="col-xs-12">
#if (#Model.Rating == 0)
{
<img src="/Images/Rating/NoRating.jpg" alt="" width="125">
}
else if (#Model.Rating == 1)
{
<img src="/Images/Rating/One.jpg" alt="" width="125">
}
else if (#Model.Rating == 2)
{
<img src="/Images/Rating/Two.jpg" alt="" width="125">
}
else if (#Model.Rating == 3)
{
<img src="/Images/Rating/Three.jpg" alt="" width="125">
}
else if (#Model.Rating == 4)
{
<img src="/Images/Rating/Four.jpg" alt="" width="125">
}
else if (#Model.Rating == 5)
{
<img src="/Images/Rating/Five.jpg" alt="" width="125">
}
</div>
What I would love to be able to do is to call a method and have the method return this code where I have it in my Razor .cshtml file. The method would also have to accept a parameter. In this case, the parameter would be a rating value of between 0 and 5. I would then replace all occurrences of #Model.Rating with the parameter value. Is it possible to do this? I'd rather not have to resort to a partial view if possible.
What I would love to be able to do is to call a method and have the method return this code where I have it in my Razor .cshtml file. The method would also have to accept a parameter. In this case, the parameter would be a rating value of between 0 and 5. I would then replace all occurrences of #Model.Rating with the parameter value. Is it possible to do this?
Solution 1:
You can create an extension method of HtmlHelper class like this :
public static class RatingExtensions
{
public static MvcHtmlString Rating(this HtmlHelper helper, short rating)
{
var imageSrc = "/Images/Rating/";
switch (rating)
{
case 0:
imageSrc += "NoRating.jpg";
break;
case 1:
imageSrc += "One.jpg";
break;
// And so on....
default:
throw new IndexOutOfRangeException(string.Format("The following rating: {0} is not expected.",
rating));
}
return new MvcHtmlString(String.Format("<img src='{0}' alt='' width='125' />", imageSrc));
}
}
In your view after importing the namespace of your extension method into the view, you call your extension method by writing this line:
#Html.Rating(Model.Rating)
Solution 2:
Just create a partial view and put it into the Shared sub-folder of your Views folder. Lets name it _Ratring.cshtml. The content of this file muste be the following (Notice #model directive which is in short type):
#model short
#{
var imageSrc = "/Images/Rating/";
switch (Model)
{
case 0:
imageSrc += "NoRating.jpg";
break;
case 1:
imageSrc += "One.jpg";
break;
// And so on....
default:
throw new IndexOutOfRangeException(string.Format("The following rating: {0} is not expected.",
Model));
}
}
<div class="col-xs-12">
<img src="#imageSrc" alt="" width="125">
</div>
You use this solution in your view by call Html.RenderPartial method liek this :
#{
Html.RenderPartial("_Rating", Model.Rating);
}
Solution 1 is better because you can move the extension method in its own assembly project and use it accross multiple projects.
Option 1
You may create a custom Html helper method. I would prefer to rename the image name from One.jpg to 1.jpg so that you do not need to write much code from the number passed into the string representation of that. You can simply relate your Model.Rating value to the image name as they directly match.
But if you still want to keep the image names as the string way, You may need to write a switch statement inside your method to convert the integer value to the string value (1 to One, 2 to Two etc..). The problem with this approach is, If you ever add a new rating like 12 or 13, you need to go to this file and update your switch statements again ! So i prefer the first approach of renaming the image names to match with the numeric representation of Model.Rating value
public static class MyCustomImageExtensions
{
public static MvcHtmlString RatingImage(this HtmlHelper helper, int imageId,
string alt,int width)
{
var path = "Images/Rating/NoRating.jpg";
if (imageId > 0)
{
path = string.Format("Images/Rating/{0}.jpg", imageId);
}
return new MvcHtmlString(string.Format("<img src='{0}' alt='{1}' width='{2}'>"
, path, alt, width));
}
}
You may call it in your razor view like
#Html.RatingImage(Model.Rating,"alt test",250)
You may add the Alternate text property and width to your model so that you do not need to hard code it in your main view.
Option 2
Since you are not doing much logic inside the helper method, you may simply use partial view where you will have the markup you want to use and pass the model properties to that.
I would change a lot, but it's really worth it. Create your own ModelMetadataProvider.
public class MyModelMetaDataProvider : DataAnnotationsModelMetadataProvider
{
public override ModelMetadata GetMetadataForProperty(Func<object> modelAccessor, Type containerType, string propertyName)
{
var result = base.GetMetadataForProperty(modelAccessor, containerType, propertyName);
if (string.IsNullOrEmpty(result.TemplateHint)
&& typeof(Enum).IsAssignableFrom(result.ModelType))
{
result.TemplateHint = result.ModelType.ToString();
}
return result;
}
}
Register it in the global.asax
ModelMetadataProviders.Current = new MyModelMetaDataProvider();
Create an Enum:
public enum StarRating
{
NoRating = 0,
One = 1,
Two = 2,
Three = 3,
Four = 4,
Five = 5
}
Update your model
public class SomeMethodViewModel
{
public StarRating Rating { get; set; }
}
Create a Display template
/Views/Shared/DisplayTemplates/StarRating.cshtml
#model StarRating
<img src="/Images/Rating/#(Model.ToString()).jpg" alt="" width="125">
In your view:
#Html.DisplayFor(m => m.Rating)
i have a view to display the information of a specific model.
in that information i have an image, which is a byte of array.
now i want to display that image.
i make this in my view
<img src="#Url.Action("getImg", "Image", new { image = Model.image })" />
note that the Image controller is not the same controller that the current view belong to
what am i getting wrong please?
all the other imformation are displayed correctly.
Edit
this is the controller that i want to call
public class ImageController : Controller
{
//
// GET: /Image/
public ActionResult Index()
{
return HttpNotFound();
}
// To convert the Byte Array to the image
public FileContentResult getImg(byte[] image)
{
if (image != null)
{
return new FileContentResult(image, "image/jpeg");
}
else
{
return null;
}
}
}
This appears to be a very poor choice in design. This code:
<img src="#Url.Action("getImg", "Image", new { image = Model.image })" />
So the image will be sent as a byte array to the client (lets say 60,000 bytes). Will create html that might look like:
<img src="/Image/getImg/?image=bc15b2c53... (lots of characters" />
This html is really long, basically sending the image as a byte array to the client. Next the browser will make another request to get the image by sending the byte array back to the controller (another 60,000 bytes to the server).
Next, the controller will return the byte array sent to it back again to the browser, as an image. Three trips of 60k of data is a terrible idea.
Update
The better way to do this is to not send the bytes array to the view, but an ID.
<img src="#Url.Action("getImg", "Image", new { id = Model.id })" />
Then in the controller:
public class ImageController : Controller
{
public FileContentResult getImg(int?/guid? id)
{
if (id.HasValue)
{
byte[] bytes = db.GetBytesById(id.Value);
return new FileContentResult(bytes, "image/jpeg");
}
else
{
// be nice to the browser, send the correct result!
return new FileNotFoundResult();
}
}
}
I would like to access the TempData in my helper for a flash message (like in ruby)
I get a runtime error of
The name 'TempData' does not exist in the current context
my Flash.cshtml is as follows
#helper Show()
{
var message = "test message";
var className = "info";
if (TempData["info"] != null)
{
message = TempData["info"].ToString();
className = "info";
}
else if (TempData["warning"] != null)
{
message = TempData["warning"].ToString();
className = "warning";
}
else if (TempData["error"] != null)
{
message = TempData["error"].ToString();
className = "error";
}
<script>
$(document).ready(function () {
$('#flash').html('#HttpUtility.HtmlEncode(message)');
$('#flash').toggleClass('#className');
$('#flash').slideDown('slow');
$('#flash').click(function () { $('#flash').toggle('highlight') });
});
</script>
}
in the layout i have
<section id="main">
#Flash.Show()
<div id="flash" style="display: none"></div>
#RenderBody()
</section>
TempData belongs to ControllerBase class which is base class for controllers, it's not accessible to shared views which no controller is behind them,
One possible workaround is to pass the controller to your helper method or access it through HtmlHelper.
#helper SomeHelper(HtmlHelper helper)
{
helper.ViewContext.Controller.TempData
}
Just pass TempData to your helper.
The call to the helper in your layout will look like this.
#Flash.Show(TempData)
Your Flash.cshtml helper will look like this.
#helper Show(System.Web.Mvc.TempDataDictionary tempData)
{
// The contents are identical to the OP's code,
// except change all instances of TempData to tempData.
}
It looks like you're using TempData where you really want to be using the ViewBag or ViewData["key"].
Controller
ViewBag.info=someString;
return View(model);
View
if (ViewBag.info != null)
{
message = ViewBag.info;
className = "info";
}
Check out this article: http://weblogs.asp.net/hajan/archive/2010/12/11/viewbag-dynamic-in-asp-net-mvc-3-rc-2.aspx
Some also use TempData in order to help data survive a redirect. If that is the case you can fix your problem by first assigning data to TempData:
TempData["myStuff"] = myData;
Then inside your new redirected action:
ViewBag["myBaggedStuff"] = TempData["myStuff"];
Then use ViewBag in your shared View.
I'm having a bit of a brain fart; it must be Monday...
I have an MVC form, which allow the user to submit an image.
The image is saved to a folder, then I want to redirect to another Controller and Action to display the image.
What are my options for passing the image name and path back to the controller action to display the graphic?
// Handles the updload, contains a control (ascx)
// and the control's action method is in another controller
public ActionResult Index()
{
return View();
}
// I want this page to display the image uploaded in the Upload.ascx control
// that is in the index method above:
public ActionResult Result()
{
ViewData["MyImage"] = ???
}
Thanks much.
Where is the image being stored? In your content area or in a database? If it's in a database, then I'd construct the controller/action url to display that image form the db. If it's in your content area, then you can construct the url based on the name of the uploaded file. I'd probably create a model rather than passing the url in view data, but view data is a valid (i.e., it works) alternative.
public ActionResult Result( int id ) // db storage
{
return View( new UploadModel
{
ImageUrl = Url.Action( "display", "image", new { id = id }
} );
}
public ActionResult Result() // content area
{
var imageName = ... get image name from ??? ...
return View( new UploadModel
{
ImageUrl = Url.Content( "~/content/images/uploads/" + iamgeName );
});
}