I am working on Steven Sanderson's book (Pro ASP.NET MVC 3). I am on p. 294. I've copied word per word what's in the book but it is not working.
This is the action method
public ActionResult Edit(Product product, HttpPostedFileBase image)
{
if(ModelState.IsValid)
{
if(image != null)
{
product.ImageMimeType = image.ContentType;
product.ImageData = new byte[image.ContentLength];
image.InputStream.Read(product.ImageData, 0, image.ContentLength);
}
//...Save product in the database using Entity Framework
}
}
This is how to display the image on the razor page
<img width="150" height="150"
src = "#Url.Action("GetImage", "Home", new { Model.ProductID })" />
And finally, the GetImage
public FileContentResult GetImage(int productID)
{
Product prod = repository.Products.FirstOrDefault(p => p.ProductID == productID);
if (prod != null)
{
return File(prod.ImageData, prod.ImageMimeType);
}
else
{
return null;
}
}
EDIT
I have followed the whole process (while debugging) from the beginning to the end and this is what I can say:
After I have press the "Save" button on the view, the HttpPostedFileBase object is not null.
After I call the db.SaveChanges() method, one row is added in database's table.
When I call the GetImage, it doesn't return null.
But on the view, there's not image displayed
Thanks for helping
File should be FileContentResult since it is bytes and not an actual file on the disk. And img should be prod, correct?
public FileContentResult GetImage(int productID)
{
Product prod = repository.Products.FirstOrDefault(p => p.ProductID == productID);
if (prod != null)
{
return new FileContentResult(prod.ImageData, prod.ImageMimeType);
}
else
{
return null;
}
}
Modify your action method like so:
<img width="150" height="150" src = "#Url.Action("GetImage", "Home", new { #id = Model.ProductID })" />
Other than that small difference (adding the id parameter), your code is very similar to mine, and my images load just fine. BTW, if you look at the HTML source for your page, do you see:
/GetImage/<some number here>
or
/GetImage/
as the value of your src attribute? If the latter, this should definitely fix the problem. If the former, you may need to enable GET requests.
Here's the answer to get that Steven Sanderson example working, change your saving procedure as such:
if (image != null)
{
var product = new Product();
product.FileName = image.FileName; //<- optional filename
product.ImageMimeType = image.ContentType;
int length = image.ContentLength;
byte[] buffer = new byte[length];
image.InputStream.Read(buffer, 0, length);
product.ImageData = buffer;
//Save product to database
}
I had the same problem, too.
Just change the part of Edit method in controller to sth like this:
if (image != null)
{
mvcImages img = db.mvcImages.Where(p => p.p_id == prod.p_id).FirstOrDefault();
prod.p_imageMimeType = image.ContentType;
byte[] buffer = new byte[image.ContentLength];
image.InputStream.Read(buffer, 0, image.ContentLength);
prod.p_imageData = buffer;
img.p_imageMimeType = prod.p_imageMimeType;
img.p_imageData = prod.p_imageData;
db.SaveChanges();
}
That works fine.
Also remember to save changes within the same brackets as your "if" command.
Even simpler resolution is to just change the following line in your product class:
change from:
public byte ImageData {get;set;}
To:
public byte[] ImageData{get;set;}
I run into the same problem.. try this:
public ActionResult Edit(Product product, HttpPostedFileBase image)
{
if(ModelState.IsValid)
{
if(image != null)
{
product.ImageMimeType = image.ContentType;
product.ImageData = new byte[image.ContentLength];
iname.InputStream.Position = 0; // This is what makes the difference
image.InputStream.Read(product.ImageData, 0, image.ContentLength);
}
//...Save product in the database using Entity Framework
}
the problem is in Action method..
it should be
you has to change de name of the Controller... "GetImage" function is in "Admin" Controller.. The rest of the code is fine...
Simple Open the Products.cs file in your entities folder and change
public byte ImageData {get;set;}
To:
public byte[] ImageData{get;set;}
Related
I have a page with many items on it. Each one has a button, that is supposed to take user to another jsp with another layout for detailed information about the current item. Can I even do this with ResponseEntity, as it doesn't redirect anywhere? Or may be there's some better way to do it and send my Object to the page? I tried "ResponseEntity.created(location).body(object)" but it doesn't do the job, I stay on the same page. May be I'm just using it wrong?
My method:
#RequestMapping(value = {"/details+{id}"}, method = RequestMethod.GET)
public ResponseEntity<Item> details(#PathVariable("id") int id) {
Item item = itemService.findById(id);
if(item == null){
return new ResponseEntity<Item>(HttpStatus.NO_CONTENT);
}
return new ResponseEntity<Item>(item, HttpStatus.OK);
}
Have a look at ModelAndView. It's purpose is to return a view with attached model to it. So for each value of the id, you can decide a pair of view and model to return.
#RequestMapping(value = {"/details+{id}"}, method = RequestMethod.GET)
public ModelAndView details(#PathVariable("id") int id) {
String viewToUse;
Map<String, Item> modelToUse;
if(id == ...) {
viewToUse = ...
modelToUse = ...
} else if (id == ...) {
viewToUse = ...
modelToUse = ...
} else if (id == ...) {
viewToUse = ...
modelToUse = ...
}
return new ModelAndView(viewToUse, modelToUse);
}
How can i cache my items and values for dropdown list in MVC?
Is there a way to do so?
I am doing that in controller.
Sample code is.......
public ActionResult Index()
{
RegionTasks regionTasks = new RegionTasks();
ViewBag.Region = GetRegions();}
My controller has function as below.
[OutputCache(Duration = 10, Location = System.Web.UI.OutputCacheLocation.Server)]
private IEnumerable<SelectListItem> GetRegions()
{
RegionTasks regionTasks = new RegionTasks();
return regionTasks.GetRegions();
}
I have tested and it not caches the item for region.
How can i do that?
The OutputCache attribute is used on controller actions to cache the resulting output. It has strictly no effect on other methods.
If you want to cache custom objects you could use the HttpContext.Cache:
private IEnumerable<SelectListItem> GetRegions()
{
var regionTasks = HttpContext.Cache["regions"] as IEnumerable<SelectListItem>;
if (regionTasks == null)
{
// nothing in the cache => we perform some expensive query to
// fetch the result
regionTasks = new RegionTasks().GetRegions();
// and we cache it so that the next time we don't need to perform
// the query
HttpContext.Cache["regions"] = regionTasks;
}
return regionTasks;
}
The regionTasks are now cached under the regions key and accessible from anywhere in your ASP.NET application which has access to the HttpContext.Cache.
Darin is also right, how ever i have done following code to store on server for X minutes.
private IEnumerable<SelectListItem> GetGlobalUrlRegion(string profileName)
{
string cacheKey = "cacheUrlRegion";
RegionTasks regionTasks = RegionTasks.CreateRegionTasks();
IEnumerable<SelectListItem> regionUrlList = HttpContext.Cache[cacheKey] as IEnumerable<SelectListItem>;
if (regionUrlList == null)
{
var regionObject = regionTasks.GetRegions(profileName);
var cTime = DateTime.Now.AddMinutes(int.Parse(System.Configuration.ConfigurationSettings.AppSettings["GlobalCacheDurationInMin"].ToString()));
var cExp = System.Web.Caching.Cache.NoSlidingExpiration;
var cPri = System.Web.Caching.CacheItemPriority.Normal;
regionUrlList = regionObject;
HttpContext.Cache.Insert(cacheKey, regionObject, null, cTime, cExp, cPri, null);
}
return regionUrlList;
}
If I have the following code (EDIT: Sorry if I wasn't clear, I want to encapsulate the following (forget about the view its calling), so that I could do other stuff within the ActionResult):
public ActionResult ModelBased(string[] items, PostedItems postedItems) {
var model = new ItemsViewModel();
var selectedItems = new List<Item>();
var postedItemIDs = new string[0];
if (postedItems == null) postedItems = new PostedItems();
if (items!= null && items.Any()) {
postedCityIDs = items;
postedItems.ItemIDs = items;
}
if (postedItems.ItemIDs != null && postedItems.ItemIDs.Any()) {
postedItemIDs = postedIems.ItemIDs;
model.WasPosted = true;
}
if (postedItemIDs.Any())
selectedItems = ItemRepository.GetAll()
.Where(x => postedItemIDs.Any(s => x.Id.ToString().Equals(s))).ToList();
model.AvailableItems = ItemRepository.GetAll();
model.SelectedItems = selectedItems;
model.PostedItems = postedItems;
return View(model);
}
How might I reuse it in different Actions in my controller without having to copy/paste. I tried doing a private method with the code. But I am stuck on:
Either calling it wrong within an action method : private void Item (Item item) {//copied code from above} then calling Item(item); in the action; or
It has something to do with the (string[] items, PostedItems postedItems) that I am doing wrong; or
Something entirely different that I am not doing right.
Any examples would be much appreciated.
EDIT: The code above works with a CheckBoxList. It's one particular CheckBoxList. But I want to be able to use it in other views without having to copy/paste the code to other ActionResults. Just calling the ActionResult won't work, because I plan on doing other things. In particular, I have code for wizards in each ActionResult, such as:
if ((nextButton != null) && ModelState.IsValid)
return RedirectToAction("EMailConfirm");
return View("EMail/BasicDetails", myData);
which are returning specific views, so call to just the ActionResult won't work, unless I am missing something.
return View(model); tries to find a view for the original action.
Specify return View("ModelBased", model); to always render the view named "ModelBased"
public void SomeAction(string[] items, PostedItems postedItems)
{
// Modify the data as your like
return ModelBased(string[] items, PostedItems postedItems);
}
public void SomeOtherAction(string[] items, PostedItems postedItems)
{
// Modify the data as your like
return ModelBased(string[] items, PostedItems postedItems);
}
private ActionResult ModelBased(string[] items, PostedItems postedItems) {
var model = new ItemsViewModel();
var selectedItems = new List<Item>();
var postedItemIDs = new string[0];
if (postedItems == null) postedItems = new PostedItems();
if (items!= null && items.Any()) {
postedCityIDs = items;
postedItems.ItemIDs = items;
}
if (postedItems.ItemIDs != null && postedItems.ItemIDs.Any()) {
postedItemIDs = postedIems.ItemIDs;
model.WasPosted = true;
}
if (postedItemIDs.Any())
selectedItems = ItemRepository.GetAll()
.Where(x => postedItemIDs.Any(s => x.Id.ToString().Equals(s))).ToList();
model.AvailableItems = ItemRepository.GetAll();
model.SelectedItems = selectedItems;
model.PostedItems = postedItems;
return View(model);
}
Your example is unclear, however, I would normally move common functionality into a seperate method and mark it with [NonAction] attribute. E.g.
[NonAction]
protected UserInfo GetUserInfo(string username)
{
// Return relevant data
}
I would then call GetUserInfo in your action method.
Edit:
You need to look into partial views. You can think of a partial view as a control that you can re-use on multiple pages. For example, I can put a login control in a partial view and renged it on multiple pages. This will promote code re-usability.
I can't give you the example as I haven't done this for a while, but you'd have to do the following:
Instead of return View(); you'll have to return PartialView("_NameOfYourPartialView", viewModel);
Modify your view, so it's no longer a view, but a partial view.
You'll need to do a bit of reading and try it out for yourself.
Good luck
You can call this action from another action that returns ActionResult.
public ActionResult OtherAction()
{
return ModelBased(items, postedItems);
}
Also, why private void? Which part do you actually want to reuse? If it takes an Item and returns ItemsViewModel, it should be private ItemsViewModel - depends on the part you want to reuse. void doesn't return anything.
I have a table in the db, which has the following :- CountryID, CountryName, and CountryImage.
Now I am trying to display the image in the index and I have the following in the View :-
<td>
#if (item.Image != null)
{
<img src="#Model.GetImage(item.Image)" alt="#item.CountryName"/>
}
and then in the ViewModel I have :-
public FileContentResult GetImage(byte[] image)
{
if (image != null)
return new FileContentResult(image, "image/jpeg");
else
{
return null;
}
}
However I cannot see the image properly.
What am I doing wrong?
Thanks in advance for your help and time
UPDATE
Ok So I have implemented the following in the View :-
<td>
#if (item.Image != null)
{
<img src="#Url.Action("GetImage", "CountryController", new { id = item.CountryID })" alt="#item.CountryName" />
}
</td>
and in the CountryController :-
public ActionResult GetImage(int id)
{
var firstOrDefault = db.Countries.Where(c => c.CountryID == id).FirstOrDefault();
if (firstOrDefault != null)
{
byte[] image = firstOrDefault.Image;
return File(image, "image/jpg");
}
else
{
return null;
}
}
but when I try to debug the code, the ActionResult GetImage is not being hit
Two possibilities.
Write a controller action instead which given an image id will return this image:
public ActionResult GetImage(int id)
{
byte[] image = ... go and fetch the image buffer from the database given the id
return File(image, "image/jpg");
}
and then:
<img src="#Url.Action("GetImage", "SomeController", new { id = item.Id })" alt="#item.CountryName" />
Obviously now in your initial model you don't need the Image property. This will be retrieved subsequently in the controller action responsible for that.
Another possibility is to use data URI scheme to embed the images as base64 strings but it might not be widely supported by all browsers:
<img src="data:image/jpg;base64,#(Convert.ToBase64String(item.Image))" alt="#item.CountryName" />
You don't need a controller action in this case as the images are directly embedded into your markup as base64 strings.
In the _Layout.cshtml file, I have a section at the bottom of the body called "ScriptsContent" declared like this:
#RenderSection("ScriptsContent", required: false)
In my view, I can then use this section to add scripts to be executed. But what if I also have a PartialView that also need to use this section to add additional scripts?
View
#section ScriptsContent
{
<script type="text/javascript">
alert(1);
</script>
}
#Html.Partial("PartialView")
PartialView
#section ScriptsContent
{
<script type="text/javascript">
alert(2);
</script>
}
Result
Only the first script is rendered. The second script doesn't exist in source code of the webpage.
Razor seems to only output the first #section ScriptsContent that it sees. What I would like to know is if there's a way to merge each call to the section.
If we cannot do this, what do you propose?
Here's a solution for that problem. It's from this blog: http://blog.logrythmik.com/post/A-Script-Block-Templated-Delegate-for-Inline-Scripts-in-Razor-Partials.aspx
public static class ViewPageExtensions
{
private const string SCRIPTBLOCK_BUILDER = "ScriptBlockBuilder";
public static MvcHtmlString ScriptBlock(this WebViewPage webPage, Func<dynamic, HelperResult> template)
{
if (!webPage.IsAjax)
{
var scriptBuilder = webPage.Context.Items[SCRIPTBLOCK_BUILDER] as StringBuilder ?? new StringBuilder();
scriptBuilder.Append(template(null).ToHtmlString());
webPage.Context.Items[SCRIPTBLOCK_BUILDER] = scriptBuilder;
return new MvcHtmlString(string.Empty);
}
return new MvcHtmlString(template(null).ToHtmlString());
}
public static MvcHtmlString WriteScriptBlocks(this WebViewPage webPage)
{
var scriptBuilder = webPage.Context.Items[SCRIPTBLOCK_BUILDER] as StringBuilder ?? new StringBuilder();
return new MvcHtmlString(scriptBuilder.ToString());
}
}
so anywwhere in your View or PartialView you can use this:
#this.ScriptBlock(
#<script type='text/javascript'>
alert(1);
</script>
)
and in your _Layout or MasterView, use this:
#this.WriteScriptBlocks()
There is no way to share sections between a view and partial views.
Absent a ScriptManager-like solution, you could have a collection of script files (initialized in your view and stored either in HttpContext.Items or in ViewData) to which the partial view would append the script file names it requires. Then towards the end of your view you would declare a section that fetches that collection and emits the right script tags.
The problem with the accepted answer is that it breaks Output Caching. The trick to solving this is to overwrite the OutputCache attribute with your own implementation. Unfortunately we can't extend the original attribute since it has lots of internal methods which we need to access.
I actually use Donut Output Caching which overwrites the OutputCache attribute itself. There are alternative libraries which also use their own OutputCache attribute so I will explain the steps I made to get it to work so that you can apply it to whichever one you're using.
First you need to copy the existing OutputCache attribute and place it within your application. You can get the existing attribute by looking at the source code.
Now add the following property to the class. This is where we store the script blocks so we can render the correct ones when retrieving from the cache.
public static ConcurrentDictionary<string, StringBuilder> ScriptBlocks = new ConcurrentDictionary<string, StringBuilder>();
Now inside the OnActionExecuting method you need to store the cache key (the unique identifier for the output cache) inside the current requests collection. For example:
filterContext.HttpContext.Items["OutputCacheKey"] = cacheKey;
Now modify the ViewPageExtensions class by adding the following (replacing CustomOutputCacheAttribute with the name of your attribute):
var outputCacheKey = webPage.Context.Items["OutputCacheKey"] as string;
if (outputCacheKey != null)
CustomOutputCacheAttribute.ScriptBlocks.AddOrUpdate(outputCacheKey, new StringBuilder(template(null).ToHtmlString()), (k, sb) => {
sb.Append(template(null).ToHtmlString());
return sb;
});
before:
return new MvcHtmlString(string.Empty);
Note: For a slight performance boost you'll also want to make sure you only call "template(null).ToHtmlString()" once.
Now return to your custom OutputCache attribute and add the following only when you are retrieving from the cache inside the OnActionExecuting method:
if (ScriptBlocks.ContainsKey(cacheKey)) {
var scriptBuilder = filterContext.HttpContext.Items["ScriptBlockBuilder"] as StringBuilder ?? new StringBuilder();
scriptBuilder.Append(ScriptBlocks[cacheKey].ToString());
filterContext.HttpContext.Items["ScriptBlockBuilder"] = scriptBuilder;
}
Here's the final code of my attribute:
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.Mvc;
using System.Web.UI;
using DevTrends.MvcDonutCaching;
public class CustomOutputCacheAttribute : ActionFilterAttribute, IExceptionFilter {
private readonly IKeyGenerator _keyGenerator;
private readonly IDonutHoleFiller _donutHoleFiller;
private readonly IExtendedOutputCacheManager _outputCacheManager;
private readonly ICacheSettingsManager _cacheSettingsManager;
private readonly ICacheHeadersHelper _cacheHeadersHelper;
private bool? _noStore;
private CacheSettings _cacheSettings;
public int Duration { get; set; }
public string VaryByParam { get; set; }
public string VaryByCustom { get; set; }
public string CacheProfile { get; set; }
public OutputCacheLocation Location { get; set; }
public bool NoStore {
get { return _noStore ?? false; }
set { _noStore = value; }
}
public static ConcurrentDictionary<string, StringBuilder> ScriptBlocks = new ConcurrentDictionary<string, StringBuilder>();
public DonutOutputCacheAttribute() {
var keyBuilder = new KeyBuilder();
_keyGenerator = new KeyGenerator(keyBuilder);
_donutHoleFiller = new DonutHoleFiller(new EncryptingActionSettingsSerialiser(new ActionSettingsSerialiser(), new Encryptor()));
_outputCacheManager = new OutputCacheManager(OutputCache.Instance, keyBuilder);
_cacheSettingsManager = new CacheSettingsManager();
_cacheHeadersHelper = new CacheHeadersHelper();
Duration = -1;
Location = (OutputCacheLocation)(-1);
}
public override void OnActionExecuting(ActionExecutingContext filterContext) {
_cacheSettings = BuildCacheSettings();
var cacheKey = _keyGenerator.GenerateKey(filterContext, _cacheSettings);
if (_cacheSettings.IsServerCachingEnabled) {
var cachedItem = _outputCacheManager.GetItem(cacheKey);
if (cachedItem != null) {
filterContext.Result = new ContentResult {
Content = _donutHoleFiller.ReplaceDonutHoleContent(cachedItem.Content, filterContext),
ContentType = cachedItem.ContentType
};
if (ScriptBlocks.ContainsKey(cacheKey)) {
var scriptBuilder = filterContext.HttpContext.Items["ScriptBlockBuilder"] as StringBuilder ?? new StringBuilder();
scriptBuilder.Append(ScriptBlocks[cacheKey].ToString());
filterContext.HttpContext.Items["ScriptBlockBuilder"] = scriptBuilder;
}
}
}
if (filterContext.Result == null) {
filterContext.HttpContext.Items["OutputCacheKey"] = cacheKey;
var cachingWriter = new StringWriter(CultureInfo.InvariantCulture);
var originalWriter = filterContext.HttpContext.Response.Output;
filterContext.HttpContext.Response.Output = cachingWriter;
filterContext.HttpContext.Items[cacheKey] = new Action<bool>(hasErrors => {
filterContext.HttpContext.Items.Remove(cacheKey);
filterContext.HttpContext.Response.Output = originalWriter;
if (!hasErrors) {
var cacheItem = new CacheItem {
Content = cachingWriter.ToString(),
ContentType = filterContext.HttpContext.Response.ContentType
};
filterContext.HttpContext.Response.Write(_donutHoleFiller.RemoveDonutHoleWrappers(cacheItem.Content, filterContext));
if (_cacheSettings.IsServerCachingEnabled && filterContext.HttpContext.Response.StatusCode == 200)
_outputCacheManager.AddItem(cacheKey, cacheItem, DateTime.UtcNow.AddSeconds(_cacheSettings.Duration));
}
});
}
}
public override void OnResultExecuted(ResultExecutedContext filterContext) {
ExecuteCallback(filterContext, false);
if (!filterContext.IsChildAction)
_cacheHeadersHelper.SetCacheHeaders(filterContext.HttpContext.Response, _cacheSettings);
}
public void OnException(ExceptionContext filterContext) {
if (_cacheSettings != null)
ExecuteCallback(filterContext, true);
}
private void ExecuteCallback(ControllerContext context, bool hasErrors) {
var cacheKey = _keyGenerator.GenerateKey(context, _cacheSettings);
var callback = context.HttpContext.Items[cacheKey] as Action<bool>;
if (callback != null)
callback.Invoke(hasErrors);
}
private CacheSettings BuildCacheSettings() {
CacheSettings cacheSettings;
if (string.IsNullOrEmpty(CacheProfile)) {
cacheSettings = new CacheSettings {
IsCachingEnabled = _cacheSettingsManager.IsCachingEnabledGlobally,
Duration = Duration,
VaryByCustom = VaryByCustom,
VaryByParam = VaryByParam,
Location = (int)Location == -1 ? OutputCacheLocation.Server : Location,
NoStore = NoStore
};
} else {
var cacheProfile = _cacheSettingsManager.RetrieveOutputCacheProfile(CacheProfile);
cacheSettings = new CacheSettings {
IsCachingEnabled = _cacheSettingsManager.IsCachingEnabledGlobally && cacheProfile.Enabled,
Duration = Duration == -1 ? cacheProfile.Duration : Duration,
VaryByCustom = VaryByCustom ?? cacheProfile.VaryByCustom,
VaryByParam = VaryByParam ?? cacheProfile.VaryByParam,
Location = (int)Location == -1 ? ((int)cacheProfile.Location == -1 ? OutputCacheLocation.Server : cacheProfile.Location) : Location,
NoStore = _noStore.HasValue ? _noStore.Value : cacheProfile.NoStore
};
}
if (cacheSettings.Duration == -1)
throw new HttpException("The directive or the configuration settings profile must specify the 'duration' attribute.");
if (cacheSettings.Duration < 0)
throw new HttpException("The 'duration' attribute must have a value that is greater than or equal to zero.");
return cacheSettings;
}
}
I also had to modify the Donut Output Cache library to make IExtendedOutputCacheManager and the OutputCacheManager constructor public.
Please note this has been extracted from my application and may require some minor tweaks. You should also place WriteScriptBlocks at the bottom of the page so it is not called until after all child actions are triggered.
Hope this helps.