This is a question related to this post (which is also made by me)
All the explanation of the website I'm creating is in the link above. Please see it there.
That being said, I'm trying to have two models of different types within a razor, and I'm having difficulty properly dealing with the directives, (#using #inherits, and all that)
and I get errors saying it failed to bind my custom made model to the one that is created by the CMS (UmbracoTemplateView).
In the course of thinking how to circumvent this, I came up with assigning #using directive for IPublishedContent, thus something like below.
This way, the UmbracoTemplateView should be assigned to a variable with a much narrower scope, meaningI should be able to avoid triggering that error.
#model CodeShare.Library.Models.SearchViewModel
#using CodeShare.Web.Controllers
<div class="carousel-inner">
<div class="carousel-item active">
<!--this part uses UmbracoTemplatePage to render the content dynamically-->
#using (UmbracoTemplatePage)
{
var pageSize = 5;
IEnumerable<IPublishedContent> newsPosts = Umbraco.AssignedContentItem.DescendantOrSelf("news").Children.Where(x => x.IsVisible() && x.DocumentTypeAlias == "newsPost").OrderByDescending(x => x.UpdateDate);
var page = 1; int.TryParse(Request.QueryString["p"], out page);
var totalPages = (int)Math.Ceiling((double)newsPosts.Count() / (double)pageSize);
if (page > totalPages)
{
page = totalPages;
}
else if (page < 1)
{
page = 1;
}
}
#foreach (var item in newsPosts.Skip((page - 1) * pageSize).Take(pageSize))
{
var imagid = item.GetPropertyValue("image");
string imgurl = Umbraco.Media(imagid.ToString()).Url;
<a href="#item.Url" class="media">
<img src="#imgurl">
<div class="media-body">
<h5 class="mt-0">#item.Name</h5>
<span>最終更新日: #item.UpdateDate.ToString("yyyy/MM/dd") </span>
</div>
</a>
}
</div>
From here I want to use the custom model, because the input will made by the user not in the backoffice.
in my understanding, with this beginform method below, it sends those HiddenFor's as a set of parameters to the method called "SubmitSearchForm" in a controller class called "SurfaceController"
#using (Ajax.BeginForm("SubmitSearchForm", "Surface", new AjaxOptions()
{
UpdateTargetId = "carousel",
HttpMethod = "POST",
InsertionMode = InsertionMode.Replace,
}))
#Html.HiddenFor(m => m.DocTypeAliases)
#Html.HiddenFor(m => m.FieldPropertyAliases)
#Html.HiddenFor(m => m.PageSize)
#Html.HiddenFor(m => m.PagingGroupSize)
#Html.TextBoxFor(m => m.SearchTerm)
}
<button id="submit-button">Search</button>
<div class="carousel">
#{ Html.RenderAction("RenderSearchResults", "Surface", new { Model = Model.SearchResults });}
</div>
}
The only change from the one I posted on our.umbraco is #using directive when trying to render from the model, which is of "UmbracoTemplateView".
Here, I'm receiving an error saying the directive that can be assigned to the #using directive here must be of the type "IDisposable" and UmbracoTemplateView is not.
I understand what is meant by the error:
UmbracoTemplateView is not a native type from .NET thus needs to be clearly specified when it can be disposed of to signal at the garbage collector. How can I assign this interface?
========================================================================
added on 18/10/2018
I separated these two views in different cshtml's but I'm having the same problem at a higher level (should I call this node)
So when I display the page, it will be delegated like
"WebBase.cshtml"=>"Home.cshtml" => "Parts that need UmbracoTemplateView(in cshtml)" & "Parts that need custom model(in cshtml)" ....
//I get an error when I put the following two in the same cshtml
#inherits Umbraco.Web.Mvc.UmbracoTemplatePage
#model CodeShare.Library.Models.SearchViewModel
#using CodeShare.Web.Controllers
#using ClientDependency.Core.Mvc
#{
Layout = "WebBase.cshtml";
}
#* the fun starts here *#
#{Html.RenderPartial("~/Views/Partials/Home/.....cshtml");}
#{Html.RenderPartial("~/Views/Partials/Home/....cshtml");}
#{Html.RenderPartial("~/Views/Partials/Home/.....cshtml");}
<!-- only this "news" part requires custom model by Mr.Paul -->
#{Html.RenderPartial("~/Views/Partials/Home/_News.cshtml");}
#{Html.RenderPartial("~/Views/Partials/Home/....cshtml");}
As such at some point I am going to need two models of different types in one place anyway
Related
Caution, before You read the rest
This question is not about POST method, redisplaying view with submited form or binding input values to controller method parameters. It's purely about rendering the View using html helper (HiddenFor or Hidden - both returns the same).
I created a simple hidden field using HiddenFor helper
#Html.HiddenFor(m => m.ProductCode)
and my problem is that value for this hidden field is rendered as null:
<input id="productCode" name="productCode" type="hidden" value/>
Even if I set it when instantiating a model and of course it's confirmed with debugging (it always has a value).
So instead it should look like this:
<input id="productCode" name="productCode" type="hidden" value="8888888"/>
Because I know there are some questions like this one (actually all of them refer to changing form values during form POST) I included list of things I tried already. My code is right below this section which I belive to be essential.
So far I tried:
ModelState.Clear() everywhere possible - cause as we know the value from ModelState is first place where it looks for the value. NO EFFECT which I expected, cause my ModelState is empty (my case is not about changing value during POST in controller as in many questions like that), so it should take value from my view model.
Not using HiddenFor helper, but pure html instead. WORKS, but its just workaround, not an answer to the problem.
Duplicating line with helper in view as follows:
#Html.HiddenFor(m => m.ProductCode)
#Html.HiddenFor(m => m.ProductCode)
PARTIALLY WORKS Produces first input as value/> and second as value="8888888"/> which indicates that there is probably something that hides initial property value. Anyway, I found nothing in ViewData at any point, nor in query string. Obviously I can't accept it this way, no explonation needed I guess.
Changing name of the property. Originally it was ProductCode. I changed it to productCode, ProdCode, ProductCodeasd, etc. and all of these WORKS. Again, it looks like there is something that hides/updates the value, but again - 100% sure there is no JS or anything else doing it. So still no answer found - just workaround again.
Explicitly setting the value for HiddenFor: #Html.HiddenFor(x => x.ProductCode, new {Value = #Model.ProductCode}). NO EFFECT, renders the same way.
Using #Html.Hidden instead of #Html.HiddenFor. NO EFFECT, with name set as ProductCode it renders the same way.
One more thing I found interesting. Reading html with Display page source in Chrome [ctrl+U] shows that value is valid value="8888888"/>, but in DevTools it's still value/> and of course submitting the form passes null to Controller method.
Model
public class Product
{
public string Description { get; set; }
public int Quantity { get; set; }
public string ProductCode { get; set; }
public string ImageUrl { get; set; }
public Product(string desc, string productCode, string imgUrl)
{
Description = desc;
ProductCode = productCode;
ImageUrl = imgUrl;
}
}
View
#model Product
#using (Html.BeginForm("UpdateCart", "Cart"))
{
<div class="row pad10">
<div class="col-sm-6 text-center">
<img src="#Model.ImageUrl" width="300" height="300" />
</div>
<div class="col-sm-6 text-justify">
<p>#Model.Description</p>
<div class="row padding-top-2">
<div class="col-sm-6">
<label>#CommonResources.Quantity: </label>
</div>
<div class="col-sm-4">
#Html.TextBoxFor(m => m.Quantity, new
{
#class = "form-control",
#data_val_required = CommonResources.FieldRequired,
#data_val_number = CommonResources.ValidationNumber
})
#Html.ValidationMessageFor(model => model.Quantity, "", new { #class = "text-danger" })
#Html.HiddenFor(m => m.ProductCode)
</div>
</div>
</div>
</div>
<div class="text-center col-xs-12 padTop20 padBottom20">
<input type="submit" value="Submit" class="whtBtn pad" />
</div>
}
Controller
The view is returned from controller with RedirectToAction as follows:
ValidateAndProceed -> ResolveNextStep (here redirection occurs) -> ShowProduct
public ActionResult ValidateAndProceed()
{
var order = Session.Current.Order;
var lang = LangService.GetSelectedLanguage();
var invoice = Session.Current.CurrentInvoice;
var localCurrency = Session.Current.LocalCurrencyInfo;
List<CheckoutValidationFieldError> errors = new List<CheckoutValidationFieldError>();
errors = ValidationService.ValidateAddress(order);
if (errors.Count > 0)
{
return RedirectToAction("InvalidAddress", "Address", new { serializedErrors = JsonConvert.SerializeObject(errors) });
}
return ResolveNextStep(order, invoice);
}
public ActionResult ResolveNextStep(IOrder order, IInvoice invoice)
{
if (OrderService.ShowProductView(order, invoice))
{
return RedirectToAction("ShowProduct");
}
return RedirectToAction("Summary");
}
public ActionResult ShowProduct()
{
Product model = ProductService.GetProduct(Session.Current.CurrentInvoice);
return View("~/Views/Product.cshtml", model );
}
Finally, what can cause such a weird behavior? I've already ran out of options. Maybe anyone had problem like mine before, would appreciate any clue on this case.
I debugged the whole process of rendering the view (got into .Net sources) checking every possible place that could make it fail and found nothing.
After #AndyMudrak and #Jeremy Lakeman comments I decided to try again to find JavaScript responsible for that behavior, but deeper than I did before. What I found was a really silly script where element Id is being concatenated from three strings what I didn't expect, cause it's really badly implemented. So finally - JavaScript is doing it and there is no bad behavior from framework etc.
Actually I am a bit disappointed (even if it's good to know this easy answer) cause it looked much more complicated than it really was and it took me hours to find out how simple it is :|
Thanks for comments, sorry for final simplicity.
I wanted to implement a simple pagination, and PagedList.MVC NuGet package sounded like the best solution for me. HOWEVER, when I click on generated buttons to go to 2nd, 3rd, etc. page, 1st one remains active, and all that happens is refresh of the first page, but I obviously want it to navigate to the expected page...
I followed these two tutorials to see if I've done everything right:
Github
Microsoft
My controller:
public ActionResult Index(int? pageNumber)
{
var modelList = _employeeService.GetEmployeeViewToPagedList(pageNumber);
return View(modelList);
}
The service method that gets called (I know that "ToPagedList()" is usually called from the controller, but the current state is a result of trying everything, and the fact that I get "DbContext disposed" error if I modify to return something like "View(modelList.ToPagedList(pageNumber, pageSize))" from the controller):
public IPagedList<EmployeeView> GetEmployeeViewToPagedList(int? pageNumber)
{
using (var _unitOfWork = UnitOfWork.GetUnitOfWork())
{
var list = (IQueryable<EmployeeView>)_unitOfWork.context.EmployeeViews.OrderByDescending(x => x.Id);
return list.ToPagedList((pageNumber ?? 1), 10);
}
}
My view:
#model PagedList.IPagedList<Company.DAL.Views.EmployeeView>
#using PagedList.Mvc;
<link href="~/Content/PagedList.css" rel="stylesheet" type="text/css" />
#{
ViewBag.Title = "Index";
}
<h2>List of all employees</h2>
<p>
#Html.ActionLink("Add new employee", "AddNewEmployee")
</p>
#if (Model != null && Model.Count() > 0)
{
<table class="table">
... all needed <tr>'s, <th>'s, <td>'s ...
</table>
<br/>
#Html.PagedListPager(Model, page => Url.Action("Index", new { page, pageSize =
Model.PageSize }))
}
I am trying to figure this out for days now, and the closest I got was this question, but I am not sure where to find that JS function, so I could try that as well.
EDIT:
Generated HTML:
<div class="pagination-container">
<ul class="pagination">
<li class="active"><a>1</a></li>
<li>2</li>
<li>3</li>
<li class="PagedList-skipToNext">»</li>
</ul>
</div>
I decided to post an answer here, since I solved the problem, and somebody else might find this useful.
So, in the controller, my Index method looks like this:
public ActionResult Index(int? pageNumber)
{
//some logic
}
As you can see, it accepts an int variable named pageNumber as a parameter.
But then there's this on my view:
#Html.PagedListPager(Model, page => Url.Action("Index", new { page, pageSize = Model.PageSize }))
SO, here I am passing a variable named page to my Index method.
That's the mistake! Variable in the method parameter list has to be named page as well.
I just trying to change my usercontrol function to razor function.but the page load time by using razor function is more than usercontrol function can any one know why this time taken.here is my razor code
#inherits RazorFunction
#using System.Linq;
#using Composite.Data;
#using Atc.Data;
#using System.Web.UI.WebControls;
#using System.Collections.Generic;
#functions {
public override string FunctionDescription
{
get { return "Function for footer"; }
}
}
<ul>
<li>##Copyright 2012 </li>
#{
using(DataConnection dCon =new DataConnection())
{
SitemapNavigator sn = new SitemapNavigator(dCon);
PageNode p = sn.CurrentHomePageNode;
List<PageNode> hiddenPages = dCon.Get<Page_Settings>()
.Where(x => x.FooterNavVisibility == true).OrderBy(x => x.Position)
.Select(x => sn.GetPageNodeById(x.PageId))
.ToList<PageNode>();
foreach (var item in hiddenPages)
{
<li>#item.Title</li>
}
}
}
<li>
<!-- AddThis Button BEGIN -->
<div class="addthis_toolbox addthis_default_style ">
<a class="addthis_button_compact"></a>
<a class="addthis_counter addthis_bubble_style"></a>
</div>
</li>
</ul>
<script type="text/javascript" src="http://s7.addthis.com/js/250/addthis_widget.js#pubid=ra-5008fecf0e8dcc29"></script>
It could be that actual UserControl's execution time was accounted in the line "ASP.NET controls: PageLoad, EventHandling, PreRender", which currently takes 91 ms.
On a side note: now need to create new DataConnection/SitemapNavigator objects -> there are Data, and SitemapNavigator properties on the base class for the razor functions.
If you want to optimize it, you can f.e. cache the "hiddenPages" variable, and clear cache on Page or PageSettings data type changes.
I really dislike page loads, I think they detract from the user experience, so I'm trying to make my web application heavily AJAX-ified.
When the user clicks on "Add new", Javascript generates a form based on a model using Razor, with the following code:
<script type="text/javascript">
var strNewCategoryForm = '<div><i class="glyphicon glyphicon-folder-open rightfolderpadding"></i>#using (Html.BeginForm("AddCategory", "Password", FormMethod.Post, new { #class="newcategoryform", role = "form", id="[1]" })) { #Html.AntiForgeryToken() #Html.PasswordFor(m => m.Category_ParentID, new { type = "hidden", value = "0" }) #Html.PasswordFor(m => m.CategoryName, new { type = "text" }) <span class="btn-group groupalign"><i class="glyphicon glyphicon-save"></i>Save</span> }</div>';
</script>
The code works great, Razor is able to generate the form within the string, so I dont have any issues with making this work.
However, for code readability and ease of development, it's not that great.
I'm still quite new to MVC and razor, so I'm just wondering, is there a better or "MVC/Razor standard" way of doing this, that I don't know about?
Edit:
In case anyone is interested, I've used both bits of Exception's answers:
In the partial view:
#model Secure_Password_Repository.Models.Category
<div><i class="glyphicon glyphicon-folder-open rightfolderpadding"></i> \
#using (Ajax.BeginForm("AddCategory", "Password", new AjaxOptions { HttpMethod="post", OnFailure="" }, new { #class="newcategoryform", role = "form", id="[1]" }))
{
#: \
#Html.AntiForgeryToken() #: \
#Html.HiddenFor(m => m.Category_ParentID, new { value = "0" }) #: \
#Html.TextBoxFor(m => m.CategoryName) #: \
#: <span class="btn-group groupalign"><i class="glyphicon glyphicon-save"></i>Save</span> \
}</div>
In the main view:
<script type="text/javascript">
var strNewCategoryForm = '#Html.Partial("_NewCategoryForm")';
</script>
The "\" at the end of each line in the partial view tell JavaScript that each line is continuation of a string value.
Answer 1 :-
If You are so keen to AJAX-ify your web app then better way is to use Ajax helper in Asp.MVC such as
#Ajax.BeginForm() or #Ajax.ActionLink() and Helpers like #Html.Partial() ,
#Html.RenderPartial() etc. are also handy for asynchronously loading data.
Their Basic Usage(I m taking hypothetical example here) :-
#Ajax.ActionLink("Show",
"Show",
null,
new AjaxOptions { HttpMethod = "GET",
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "dialog_window_id",
OnComplete = "your_js_function();" })
#using (Ajax.BeginForm("Edit", "Cars", new AjaxOptions
{
InsertionMode = InsertionMode.Replace,
HttpMethod = "POST",
OnSuccess = "updateSuccess"
}, new { #id = "updateCarForm" })) { ..... }
Follow this link :- http://www.codeguru.com/csharp/.net/working-with-ajax-helper-in-asp.net-mvc.htm
Above link will be helpful for you to understand building Forms with Ajax Helpers.
and one thing more the way you are building forms with razor syntax in javascript is not at all a good option 'in my opinion'.
Answer 2 :-
A small demo how to build a completely ajax-ified form which donot require any page reload:
#using (Ajax.BeginForm("Index", "Home", null, new AjaxOptions { HttpMethod = "POST", InsertionMode = InsertionMode.Replace, UpdateTargetId = "Mydiv" }, new { #id = "frm" , #style ="width:700px" }))
{
//Your HTML here
}
Make above View a Partial View say 'Index.cshtml' and then return it this way through Controller as :
Public ActionResult Index()
{
return PartialView("Index");
}
Making Partial Views and loading Partial views through Jquery is handy to make unobtrusive forms.
This is more of an HTML-thing than MVC/Razor, as you are essentially asking on how to embed templates into your website. AFAIK html doesn't Support templating out of the box just yet, so you'd Need some JavaScript for that (in your case right now, you're probably using jquery)
Most template engines like knockoutjs, handlebars, etc. (maybe even jquery) support embedding templates similar to this:
<script type="text/html" id="my_template">
<div>
<p>
My template
</p>
</div>
</script>
The browser would not render that html, but a JavaScript library would use it (optionally doing some runtime data-binding) and display it.
Note: you can obviously put the html from that template into a partial view:
_MyTemplate.cshtml:
<div>
<p>
My template
</p>
</div>
View:
<script type="text/html" id="my_template">
#Html.Partial("MyTemplate")
</script>
Most template engines also support loading templates asynchronously, in which case you just render them the partial view alone.
Hope this helps a little.
I have a page with a video at the top and a list of videos you can choose from. Currently, clicking a link in the video list will reload the entire page. I need it to only refresh the partial view I have containing the video at the top of the page.
I saw several posts here on SO showing how to reload partial views with JQuery, but couldn't get it to work correctly in my situation. I'm unsure how to pass the correct id of the video along.
Controller:
public ActionResult Videos(int topVideo = 0)
{
VideosModel model = new VideosModel();
model.Videos = StatsVideoService.GetEntityList(new Lookup(TableStatsVideo.IsDeleted, false)).OrderByDescending(x => x.DateCreated).ToList();
if (topVideo == 0)
model.TopVideo = model.Videos.First();
else
{
model.TopVideo = model.Videos.Where(x => x.StatsVideoId == topVideo).FirstOrDefault();
if (model.TopVideo == null)
model.TopVideo = model.Videos.First();
}
return View(model);
}
View:
#model Project.Models.VideosModel
<section class="videos">
<div id="top_video">
#{Html.RenderPartial("StatsVideo", Model.TopVideo);}
</div>
<ul>
#foreach (var item in Model.Videos)
{
<li>
<div class="videoList">
<a href ="#Url.Action("Videos", "Home", new { topVideo = item.StatsVideoId })">
<img src="#Url.Content("~/Content/img/video-ph.png")" />
</a>
<p class="videoTitle">#item.Title</p>
</div>
</li>
}
</ul>
</section>
If there's any more information needed, please let me know.
After several hours of bashing my head against the wall, I got it to work! Just as a reference to anyone else in the future who's viewing this article, here's how I got it to work:
I set the onclick of the link to point to a javascript method, passing in the id of the video as a parameter:
#foreach (var item in Model.Videos)
{
<li>
<div class="videoList">
<a href ="#" onclick="updateTopVideo(#item.StatsVideoId)">
<img src="#Url.Content("~/Content/img/video-ph.png")" />
</a>
<p class="videoTitle">#item.Title</p>
</div>
</li>
}
And then I included this script in the view at the bottom:
<script>
var updateTopVideo = function (itemId) {
var url = '#Url.Content("~/Home/StatsVideo/")';
url = url + itemId;
$.get(url, "", callBack, "html");
};
var callBack = function (response) {
$('#top_video').html(response);
};
</script>
Finally, I added a method to my controller that would return the partial view needed for the video at the top of the screen:
public ActionResult StatsVideo(int Id)
{
IStatsVideo vid = StatsVideoService.GetEntity(new Lookup(TableStatsVideo.StatsVideoId, Id));
if (vid == null)
vid = StatsVideoService.GetEntityList(new Lookup(TableStatsVideo.IsDeleted, false)).OrderByDescending(x => x.DateCreated).FirstOrDefault();
return PartialView(vid);
}
This code should be fairly easy to understand. Basically, the onclick calls the first javascript method, which then calls the controller. The controller builds the partial view and returns it. The first javascript method passes it to the second javascript method which sets the html of the div "top_video" to be the returned partial view.
If anything doesn't make sense, or anyone's having trouble with this in the future, let me know and I'll do my best to offer some help.
I think there may be several confusing and inconsistent elements here.
First, you are returning a full view instead of a partial view. This reloads all containing elements, not just the part that is relevant to your partial view.
Second, you are using Url.Action, which only generates the url. I would recommend using Ajax.ActionLink, which allows you to do fully ajax calls, refreshing the content of your partial div and updating a target div element.
instead of:
<div class="videoList">
<a href ="#Url.Action("Videos", "Home", new { topVideo = item.StatsVideoId })">
<img src="#Url.Content("~/Content/img/video-ph.png")" />
</a>
<p class="videoTitle">#item.Title</p>
</div>
try the more modern solution
<div class="videoList">
#Ajax.ActionLink(
"Videos",
"Home",
"new { topVideo = item.StatsVideoId },
new AjaxOptions {
HttpMethod = "GET",
OnSuccess = "handleSuccess"
}
)
</div>
This way you can be very specific on what you want each link to do, and you can pass along multiple parameters as well as define a callback function. You can also use "UpdateTargetId" in your ajax options to load your newly refreshed partial view into a DOM element.
You can remove the around the image and just store the url generated by the Url.Action in a data-href attribute.
Then you can use the jquery load method to load the data:
$(".videolist>img").click(function () {
$("#content").load($(this).data("href"));
});
I created a fiddle that loads content dynamically here, so you can play with it if you want: http://jsfiddle.net/bTsLV/1/