Well, this newbie is doing something wrong when displaying images uploaded to the server:
model:
public class Person
{
public int ID { get; set; }
public string Name { get; set; }
public string ImageUrl { get; set; }
}
controller (upload - called by the [HttpPost] public ActionResult Create):
public void Upload(Person person)
{
var image = WebImage.GetImageFromRequest();
var filename = Path.GetFileName(image.FileName);
var path = Path.Combine(Server.MapPath("~/App_Data/Uploads/Fotos"), filename);
image.Save(path);
person.ImageUrl = Url.Content(Path.Combine("~/App_Data/Uploads/Fotos", filename));
}
create view:
...
#using (Html.BeginForm("Create", "Person", FormMethod.Post, new { #encType = "multipart/form-data" }))
{
...
#FileUpload.GetHtml(initialNumberOfFiles: 1, allowMoreFilesToBeAdded: false, includeFormTag: false, uploadText: "image")
...
<div>
<input type="submit" value="Create" /> |
#Html.ActionLink("Back", "Index")
</div>
}
So far, so good, the image is uploaded to the folder and the url is saved
Now, I want to see it in the Detail View
detail view:
<div class="display-foto">
<img src="#Url.Content(Server.MapPath(Model.ImageUrl))" alt="IMAGE" />
</div>
Viewing the generated code, everything seems to be alright:
<img src="D:\Users\x\Documents\Visual Studio 2010\Projects\CMI_AD\CMI_AD\App_Data\Uploads\Fotos\_nofoto.jpg" alt="IMAGE" />
But the fact is that nothing appears on the screen except the text "IMAGE".
What am I doing wrong?
P.S. I've tried without the Server.MapPath, using the relative address "~\App_Data\Uploads\Fotos_nofoto.jpg" and the result is the same.
--- EDIT ---
#kmcc049: I've tried your suggestion creating a helper
public static class MyHelpers
{
public static IHtmlString MyImage(this HtmlHelper htmlHelper, string url)
{
var urlHelper = new UrlHelper(htmlHelper.ViewContext.RequestContext);
var img = new TagBuilder("img");
img.Attributes["alt"] = "[IMAGE]";
img.Attributes["src"] = UrlHelper.GenerateContentUrl(url, htmlHelper.ViewContext.HttpContext);
return MvcHtmlString.Create(img.ToString(TagRenderMode.SelfClosing));
}
}
the call in the view:
#Html.MyImage(Model.ImageUrl)
the generated code is
<img alt="[IMAGE]" src="/App_Data/Uploads/Fotos/_nofoto.jpg" />
but the result is the same: no image :(
--- SOLVED ---
Apparently the App_Data is not a good location to save uploaded files because accessing them will result an Error 403 - Forbidden. I move the files to ~/Uploads/Fotos and it works.
That's not a valid HTTP path. That's the path to the folder on your computer.
try the UrlHelper.GenerateContentUrl() method instead
http://msdn.microsoft.com/en-us/library/system.web.mvc.urlhelper.generatecontenturl.aspx
Related
I am trying to get image files from the database and bind it to a KendoUI ListView. The problem is that it is not showing images at all.
This is what I have done:
View
<script type="text/x-kendo-tmpl" id="template">
<div class="product">
<img src="#Url.Content("#:PhotoID# + #:MIMEType#")" />
</div>
</script>
<div id="imageListView2" class="demo-section">
#(Html.Kendo().ListView<WorcesterMarble.ViewModels.PhotosViewModel>()
.Name("listView")
.TagName("div")
.ClientTemplateId("template")
.DataSource(dataSource =>
{
dataSource.Read(read => read.Action("GetImages", "StockReceiptsGrid").Data("passStockIDToListView"));
dataSource.PageSize(1);
})
.Pageable()
.Selectable(selectable => selectable.Mode(ListViewSelectionMode.Multiple))
//.Events(events => events.Change("onChange").DataBound("onDataBound"))
)
</div>
Controller
public JsonResult GetImages([DataSourceRequest] DataSourceRequest request, int stockReceiptID)
{
var photos = _stockPhotosRepository.GetStocReceiptkPhotos(stockReceiptID).ToList();
var photosList = new List<PhotosViewModel>();
//var photosList = new List<FileContentResult>();
if (photos.Count != 0)
{
foreach (var stockPhoto in photos)
{
var photoVm = new PhotosViewModel();
photoVm.PhotoID = stockPhoto.PhotoID;
photoVm.Image = stockPhoto.ImageData;
photoVm.MIMEType = stockPhoto.MIMEType;
// FileContentResult file = File(stockPhoto.ImageData, stockPhoto.MIMEType);
photosList.Add(photoVm);
}
return Json(photosList.ToList(), JsonRequestBehavior.AllowGet);
}
else
{
return null;
//FilePathResult file = this.File("/Content/Images/80.jpeg", "image/jpeg");
//return file;
}
return null;
}
Photo View Model:
public class PhotosViewModel
{
public int PhotoID { get; set; }
public byte[] Image { get; set; }
public string MIMEType { get; set; }
public int StockReceiptID { get; set; }
}
I am not sure if the problem is caused by the image url setting in the template. as you see it is not actually a url because the image is not saved anywhere except from the database. this is a screenshot of how the listview looks like; simply blank even though there must 15 images displayed!
Please let me know any clues or solutions to this problem.
I know this is a bit older, but what you need to do is change the line return Json(photosList.ToList(), JsonRequestBehavior.AllowGet); to the following:
return Json(photosList.ToDataSourceResult(request),
JsonRequestBehavior.AllowGet);
If the method ToDataSourceResult is not recognized, you have to add
using Kendo.Mvc.Extensions;
on top of your document.
It looks like you're missing a return in your controller (just before the end of your if)
return Json(photosList.ToList(), JsonRequestBehavior.AllowGet);
EDIT
Also, I noticed this:
<img src="#Url.Content("#:PhotoID# + #:MIMEType#")" />
Shouldn't that be:
<img src="#Url.Content("#:ImageData#")" />
or something similar?
It might be to late to answer, but your issue is that the json data being sent back to your view is to large so your images are not showing, rather save your images to a file and then render your images via a URL.
I have a MVC 3 page that returns a list of user responses with a partial view called "memo" (which displays/add memos) for each response. When I add a memo to a response, it should update the db and the list of memos for that response. It should be partial page update via ajax, which effects only the partial view "memo".
The view Response.chtml that contains "memo":
#using (Html.BeginForm("Response", "User", FormMethod.Post, new { id = "UserResponse" }))
{
.... code removed ....
#foreach (var response in Model)
{
<div class="qna"><input type="text" id=#response.responseId value="#response.ResponseText" />
<div>#Html.Partial("_memo", response.responseId)</div>
}
.....
The partial page "_memo.chtml":
<div>add memo</div>
<ul id="memos">
#foreach (var memo in Model) {
<li>#memo.Text</li>
}
</ul>
<form method="post" id="memoForm"
action="#Url.Action("AddMemo")">
#Html.TextArea("Memo", new { rows = 5, cols = 50 })
<br />
<input type="submit" value="Add" />
</form>
Controller for view User/Response:
[HttpGet]
public ActionResult Response(id)
{
.....
return View(responses);
I just started with the code above, need help filling the blanks.
If I pass the response Id to the partial view, how do I pull the list of memos for that response? Will it involve ajax? (instead of ..Partial("_memo", response.memos))
How do I update the partial view via ajax call. What is ajax call (sample code) on the client side and how would the controller look? When the ajax call is successful, how do I update the list memos div="memos" to reflect the new memo?
Will the form action from Response conflict with form action of the partial view Memo?
Answers to Questions:
You shouldn't pass the responseId to the partial, you should pass the memo collection from your response object and make your partial view strongly typed to that collection.
See full code example below.
You don't need the form in the partial since you're making a simple ajax call to add the new memo. See full code example below.
This is a modified example from a project I am currently working on:
There is a bit of code to follow, so here goes:
This is my model. There are several sections on a career planning form, one of which is a section to select and update competencies. The SelectCompetencies model has a collection of competencies within it. The user will have the ability to add competencies. When they do, it will be added to the database and will update the list of competencies in the partial.
public class CareerPlanningFormViewModel
{
// code removed ...
public SelectCompetenciesModel SelectCompetencies { get; set; }
// code removed ...
}
public class SelectCompetenciesModel
{
public int CareerPlanningFormID { get; set; }
public IList<CompetencyModel> Competencies { get; set; }
public byte MaximumCompetenciesAllowed { get; set; }
}
public class CompetencyModel
{
public int CompetencyID { get; set; }
public int? CompetencyOptionID { get; set; }
public string ActionPlan { get; set; }
public IDictionary<int, string> CompetencyOptions { get; set; }
}
The main view of the career planning form: /Views/CPF/CareerPlanningForm.cshtml
#model MyNamespace.Models.CareerPlanningForm.CareerPlanningFormViewModel
<link rel="stylesheet" href="#Url.Content("~/Content/CreateCPF.css")" />
#using (Html.BeginForm())
{
// other sections loaded here...
// code removed for brevity...
#Html.Partial("SelectCompetencies", Model.SelectCompetencies)
// other sections loaded here...
// code removed for brevity...
}
The SelectCompetencies partial: /Views/CPF/SelectCompetencies.cshtml
The user will fill in the new action plan text and click the add competency button.
That will post via ajax to CPFController/NewCompetencyTemplate
#model MyNamespace.Models.CareerPlanningForm.SelectCompetenciesModel
#Html.HiddenFor(m => m.CareerPlanningFormID)
<h3>Select Competencies</h3>
<p class="guidance">
Select up to #Model.MaximumCompetenciesAllowed competencies to focus on improving.
</p>
<table id="CompetenciesTable">
<thead>
<tr>
<th>Competency</th>
<th>Action Plan:</th>
</tr>
</thead>
<tbody>
#for (int i = 0; i < Model.Competencies.Count(); i++)
{
#Html.EditorFor(m => m.Competencies[i])
}
</tbody>
<tfoot id="CompetenciesTableFooter" class="#(Model.Competencies.Count() < Model.MaximumCompetenciesAllowed ? "" : "hidden")">
<tr>
<td colspan="2">
#Html.TextArea("NewActionPlanText")
#Html.Button(ButtonType.Button, "Add Another Competency", "add", new { id = "AddCompetencyButton" })
</td>
</tr>
</tfoot>
</table>
#section script
{
<script>
jQuery(document).ready(function ($) {
var competenciesTableBody = $('#CompetenciesTable tbody'),
competenciesTableFooter = $('#CompetenciesTableFooter'),
addCompetencyButton = $('#AddCompetencyButton'),
newCompetencyTemplateUrl = '#Url.Content("~/CPF/NewCompetencyTemplate")',
count = competenciesTableBody.find('tr').length,
newActionPlanText = $('#NewActionPlanText'),
careerPlanningFormID = $('#CareerPlanningFormID');
addCompetencyButton.click(function () {
$.ajax({
url: newCompetencyTemplateUrl(),
type: 'POST',
data: {
careerPlanningFormID: careerPlanningFormID,
actionPlan: newActionPlanText,
itemCount: count
},
dataType: 'html',
success: function (data) {
var elements = $(data);
// other code removed here...
competenciesTableBody.append(elements);
// other code removed here...
}
});
});
});
</script>
}
Views/CPF/EditorTemplates/CompetencyModel.cshtml
#model MyNamespace.Models.CareerPlanningForm.CompetencyModel
<tr class="competency">
<td>
#Html.DropDownListFor(m => m.CompetencyOptionID, new SelectList(Model.CompetencyOptions, "Key", "Value"), "Select competency...")
</td>
<td>
#Html.TextAreaFor(m => m.ActionPlan, new { #class = "competencyActionPlan" })
#Html.HiddenFor(m => m.CompetencyID)
</td>
</tr>
The controller containing the action to add the new competency: /Controllers/CPFController.cs
This will call the CareerPlanningFormService to add the new competency and will return a partial view for NewCompetencyTemplate that will render out the new competency
public class CPFController : Controller
{
private readonly ICareerPlanningFormService careerPlanningFormService;
public CPFController(ICareerPlanningFormService careerPlanningFormService)
{
this.careerPlanningFormService = careerPlanningFormService;
}
[HttpPost]
public PartialViewResult NewCompetencyTemplate(int careerPlanningFormID, int itemCount, string newActionPlanText)
{
var count = itemCount + 1;
// Even though we're only rendering a single item template, we use a list
// to trick MVC into generating fields with correctly indexed name attributes
// i.e. Competencies[1].ActionPlan
var model = new SelectCompetenciesModel
{
Competencies = Enumerable.Repeat<CompetencyModel>(null, count).ToList()
};
model.Competencies[count - 1] = this.careerPlanningFormService.BuildNewCompetencyModel(careerPlanningFormID, newActionPlanText);
return this.PartialView(model);
}
}
My service class: CareerPlanningFormService.cs
This handles the business logic and makes the calls to the repository to add the item to the database and returns a new CompetencyModel
public class CareerPlanningFormService : ICareerPlanningFormService
{
private readonly IMyRenamedRepository repository;
private readonly IPrincipal currentUser;
public CareerPlanningFormService(
IMyRenamedRepository repository,
IPrincipal currentUser)
{
this.repository = repository;
this.currentUser = currentUser;
}
public CompetencyModel BuildNewCompetencyModel(int careerPlanningFormID, string newActionPlanText)
{
var competency = new Competency
{
CareerPlanningFormID = careerPlanningFormID,
CompetencyOptionID = null,
ActionPlan = newActionPlanText
};
this.repository.Add(competency);
this.repository.Commit();
return new CompetencyModel
{
CompetencyID = competency.CompetencyID,
CompetencyOptionID = competency.CompetencyOptionID,
ActionPlan = competency.ActionPlan,
CompetencyOptions = this.GetCompetencyOptionsForCareerPlanningFormID(careerPlanningFormID)
};
}
}
Now, the partial for NewCompetencyTemplate: Views/CPF/NewCompetencyTemplate.cshtml
This is very simple, it simply renders the same editor template as above, for the last competency in the collection (which we just added)
#model MyNamespace.Models.CareerPlanningForm.SelectCompetenciesViewModel
#Html.EditorFor(m => m.Competencies[Model.Competencies.Count() - 1])
When the ajax call succeeds, it will receive this partial back from the controller action method it called. It then takes the partial and appends it to the competencies table body
// snippet from ajax call above
competenciesTableBody.append(elements);
I hope this helps. Let me know if you have any additional questions.
While you're correct that you can do it just by returning a partial view containing the updated content, you may also consider using jQuery's load method.
Look here, in particular at the "loading page fragments" section. Basically you can just get the original page again and jQuery will "extract" the content you want as long as it can be targetted by a selector (such as a div id).
Note, this solution is not suitable in all cases as there will be redundant markup in the response from the server because you will be discarding the rest of the page content and just using the updated part.
I am basically trying to display a dropdownlist on my data entry view, and the dropdownlist keeps giving me the error "An expression tree may not contain a dynamic operation". I have added "#Model MyModel" to the top of my view, but still can't get past this error. Does anyone have an idea of how to resolve this issue? I have a controller that looks like this
using MvcApplication1.Models;
namespace MvcApplication1.Controllers
{
public class MyController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult EnterInfo()
{
GetUT myGetUT = new GetUT();
ViewBag.uts = GetOptions();
return View(myGetUT);
}
[HttpPost]
public ActionResult EnterInfo(GetUT myGetUT)
{
ViewBag.uts = GetOptions();
return View(myGetUT);
}
private List<UT> GetOptions()
{
List<UT> uts = new List<UT>();
uts.Add(new UT() { ID = 1, Name = "1st" });
uts.Add(new UT() { ID = 2, Name = "2nd" });
uts.Add(new UT() { ID = 3, Name = "3rd" });
uts.Add(new UT() { ID = 4, Name = "4th" });
return uts;
}
}
}
and a view that looks like
#Model MyModel
#{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<title>EnterInfo</title>
</head>
<body>
<div>
#Html.DropDownListFor(x => x.UTID, new SelectList(ViewBag.uts, "ID", "Name", Model.UTID))
Enter an Amount :-<input type="text" name="Amount" /><br />
<input type="submit" value="Submit Info" />
</div>
</body>
</html>
Thanks for all the help.
Well, ViewBag is a dynamic type, so I assume that is what it is complaining about. Try putting public List<UT> UTs { get; set; } as a property on MyModel and change your helper to use the UTs property from your model. Like this:
Controller:
public ActionResult EnterInfo()
{
GetUT myGetUT = new GetUT();
myGetUT.UTs = GetOptions();
return View(myGetUT);
}
View:
#Html.DropDownListFor(x => x.UTID,
new SelectList(Model.UTs, "ID", "Name", Model.UTID))`
Edit: If it isn't obvious, your view should be typed to a GetUT type (because that's what you are passing in to the View() function in the EnterInfo action) - I assume that's what you mean when you said #Model MyModel. If not, change it to #Model GetUT and put the property on the GetUT class.
When my page loads in "edit" mode, my text fields render correctly, but my numeric fields render with the error validation text visible, even though the value in the field is valid:
My problem is in a more complex project, but I was able to reproduce it in an out-of-the-box MVC 3 application in which I just added these bits. Why does the numeric field display the error text but the text field is fine when the page loads?
What is going on here?
I have the following for my Model, Controller, and View:
Model:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
namespace MvcIssues.Models
{
public enum Operations
{
View = 0,
Edit = 1
}
public class ShowsModel
{
public Operations Operation { get; set; }
[Display(Name = "Name")]
[DataType(DataType.Text)]
[StringLength(10)]
public string Name { get; set; }
[Display(Name = "Number")]
[Required]
[Range(typeof(int), "1", "999")]
public int Number { get; set; }
}
}
Controller:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using MvcIssues.Models;
using MvcIssues.Data;
namespace MvcIssues.Controllers
{
public class TestController : Controller
{
// GET: /Test/Shows
[AcceptVerbs(HttpVerbs.Get)]
[ActionName("Shows")]
[Authorize]
public ActionResult SelectedShows()
{
ShowsData shows = MvcApplication.Shows;
ShowsModel model = new ShowsModel();
model.Operation = Operations.View;
model.Name = shows.Name;
model.Number = shows.Number;
return View(model);
}
// POST: /Test/Shows
[AcceptVerbs(HttpVerbs.Post)]
[ActionName("Shows")]
[ValidateInput(false)]
[Authorize]
public ActionResult ShowsSubmit(ShowsModel data)
{
string name = data.Name;
int number = data.Number;
ShowsModel model = new ShowsModel();
if (Request.Form.AllKeys.Contains("btnEdit"))
{
ShowsData shows = MvcApplication.Shows;
model.Name = shows.Name;
model.Number = shows.Number;
model.Operation = Operations.Edit;
}
else if (Request.Form.AllKeys.Contains("btnCancel"))
{
ShowsData shows = MvcApplication.Shows;
model.Name = shows.Name;
model.Number = shows.Number;
model.Operation = Operations.View;
}
else if (Request.Form.AllKeys.Contains("btnSaveEdit"))
{
ShowsData shows = MvcApplication.Shows;
shows.Name = name;
shows.Number = number;
model.Name = shows.Name;
model.Number = shows.Number;
model.Operation = Operations.View;
}
return View("Shows", model);
}
}
}
View:
#model MvcIssues.Models.ShowsModel
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
<h2>Test Page</h2>
<div>
Show = #this.ViewData.Model.Name
<br />
Number = #this.ViewData.Model.Number.ToString()
</div>
<hr />
<div>
#Html.ValidationSummary(true, "oops!")
#using (Html.BeginForm())
{
<div>
Name: #(this.ViewData.Model.Operation == MvcIssues.Models.Operations.View ?
Html.TextBoxFor(m => m.Name, new { disabled = "disabled", maxLength = "20" })
:
Html.TextBoxFor(m => m.Name))
#Html.ValidationMessageFor(m => m.Name)
<br />
Number: #(this.ViewData.Model.Operation == MvcIssues.Models.Operations.View ?
Html.TextBoxFor(m => m.Number, new { disabled = "disabled" })
:
Html.TextBoxFor(m => m.Number))
#Html.ValidationMessageFor(m => m.Number)
</div>
<div>
#switch (this.ViewData.Model.Operation)
{
case MvcIssues.Models.Operations.Edit:
<input type="submit" name="btnSaveEdit" value="Save" />
<input type="submit" name="btnCancel" value="Cancel" />
break;
case MvcIssues.Models.Operations.View:
default:
<input type="submit" name="btnEdit" value="Edit" />
break;
}
</div>
}
</div>
If anyone can help me it would be much appreciated.
I had the same problem too. Here's what caused it, and how I fixed it:
The cause:
[Authorize]
[HttpGet]
public ActionResult BidToolv2(BidToolv2ViewModel model)
{
...
The fix:
[Authorize]
[HttpGet]
public ActionResult BidToolv2()
{
BidToolv2ViewModel model = new BidToolv2ViewModel();
Essentially the problem was when the user first visited the page, the controller took an empty model and when the page loaded, it assumed it had already been passed the model (perhaps?). Not totally sure on that, but to fix it I removed the model as a parameter and instead created a model in the controller action itself
Hope this helps!
Okay, posted this to asp.net forum. Probably a little bit more concisely worded version of the same question.
Solution was a bit of a hack but seems to be working well - created empty constructors for my problematic view model classes and inside the empty constructors I initialized the properties to valid values. Did the trick.
You need Required to make sure something is entered.
You need Range to make sure when something is entered that the values meet your requirements
Example:
[Required(ErrorMessage="Weekly Rental value is required")]
[Range(1, 9999, ErrorMessage = "Value must be between 1 - 9,999")]
public string WeeklyRental { get; set; }
I have the following in my project (MVC3) N2CMS 2.2.1 (from nuget). I am able to edit the main page using the "manage parts" functionality and drag an image part onto the page and set it's content. However, when it renders the page it renders my Image.cshtml file inside of the _layout.cshtml (like it would for a page, not a part)... this is causing the site to have multiple head/footer etc tags and breaking the layout. How can i get the DroppableZones to render the partials properly (without having to set Layout = "" in every partial view manually). If you would like to test this yourself, you can check out the code from https://github.com/robbihun/N2CMSBaseStarterSite run it, go through the N2CMS setup with the sqlite db and use the manage parts feature (http://screencast.com/t/w9q5a49Ei8) to drag an image part onto the home page.
_layout.cshtml
<body>
#{ Html.ControlPanel().Render(); }
#RenderBody()
<footer>© #DateTime.Now.Year</footer>
#RenderSection("scripts", false)
</body>
StartHome.cshtml
<div>
#{ Html.DroppableZone(Zones.ImageSlider).Render(); }
</div>
Image.cshtml
#model ImagePart
<div class="image">
<img src="#Model.Image" alt="#Model.Title" />
<span class="title">#Model.Title</span>
<span class="description">#Model.ShortDescription</span>
</div>
StartHomePage.cs
[PageDefinition("Start Page", Description = "The start or home page of the website.", SortOrder = 1, InstallerVisibility = InstallerHint.PreferredRootPage | InstallerHint.PreferredStartPage)]
[WithEditableTitle("Title", 1, Focus = true, ContainerName = Tabs.Content)]
[RestrictParents(typeof(IRootPage))]
public class StartHomePage : PageBase
{
[EditableFreeTextArea("Main Content", 2, ContainerName = Tabs.Content)]
public virtual string MainContent { get; set; }
}
ImagePart.cs
[PartDefinition("Image Part", Description = "Image with title and description.")]
[WithEditableTitle("Title", 10)]
public class ImagePart : PartBase
{
[FileAttachment, EditableImageUpload("Image", 5)]
public virtual string Image { get; set; }
[EditableTextBox("Short Description", 15, TextMode = TextBoxMode.MultiLine, Rows = 5, Columns = 15)]
public virtual string ShortDescription { get; set; }
}
Add a _ViewStart.cshtml in the Views/Shared/Parts folder, and it'll override the Layout setting in that folder. In the new _ViewStart, set Layout = null; and don't set the Layout in your partial views.
Here's a pull request for you: https://github.com/robbihun/N2CMSBaseStarterSite/pull/1