I'm a desktop developer and I'm teaching myself ASP.NET MVC3 with C# (can't use MVC4 yet...). I'm slapping together some small dummy projects for this reason. In this project I use a DropDownListFor with movie categories (we all know this example right?). But because this tutorial was going too fast I'm trying to do something more simple.
My Model:
public class MovieModel {
public int SelectedCategorieID { get; set; }
public List<CategorieModel> Categories { get; set; }
public MovieModel() {
this.SelectedCategorieID = 0;
this.Categories = new List<CategorieModel>() {new CategorieModel {ID = 1,
Name = "Drama"},
new CategorieModel {ID = 2,
Name = "Scifi"}};
}
}
public class CategorieModel {
public int ID { get; set; }
public string Name { get; set; }
}
See? Very simple. I have a strongly typed View in which I can use this model:
#model MvcDropDownList.Models.MovieModel (1st line of Index.cshtml).
The model is filled when the default action of the Home controller is called:
public ActionResult Index() {
ViewBag.Message = "Welcome to ASP.NET MVC!";
Models.MovieModel mm = new Models.MovieModel();
return View(mm);
}
So far so good. No problems. Now I want to show the user the ID of the category it selected in a partial view with unobtrusive ajax... Because I didn't get it to work I started even smaller. Forget the DrowpdownList for now. All I have at the moment is this button:
<input type="button" value="Minicart test" onclick="categoryChosen();" />
And this div:
<div id="minicart">
#Html.Partial("Information")
</div>
The mini cart stuff is from another tutorial, I apologize. Don't let it distract you please.
This javascript:
function categoryChosen() {
var url = "Home/CategoryChosen/" + "2";
$.post(url, function (data) {
debugger;
$("#miniCart").html(data);
});
}
The 2 is indeed solid, from my earlier attempt to get it to work. Eventually I want that to be variable ofcourse...
Which calls this action:
[AcceptVerbs("POST")]
public ActionResult CategoryChosen(string SelectedCategorieID) {
ViewBag.messageString = "2";
return PartialView("Information");
}
Yup, and you see that correctly. I just insert 2 for my test. Because like I said, can't get it to work. The partial view Information looks like this:
#{
ViewBag.Title = "Information";
}
<h2>Information</h2>
<h2>You selected: #ViewBag.messageString</h2>
So, now for the big question. I expected the partial view to render: "You selected: 2". I even see this when I debug the javascript and look what's inside the variable 'data'. Can anyone help me why it doesn't render 2? Then I can move on with teaching myself this stuff. Thank you very much in advance for helping. If you miss any kind of information, do not hesitate to ask.
I think the problem is misspelling id of the minicart div. Your id do not contain any capital letters but your selector does. So instead $("#miniCart") you should use $("#minicart") and it will work.
Make it like this and check if it works
function categoryChosen() {
var url = "Home/CategoryChosen?SelectedCategorieID=" + "2";
$.post(url, function (data) {
debugger;
$("#miniCart").html(data);
});
}
This is provided considering that you haven't did any changes to your routers in global.asax
rather you should add the url like this
UrlHelper
Related
I have a data class that I want to show via a View, allow the User to change it, and bring it back in via data binding. I want to do this via #model dynamic. Is this possible?
This is my model
public class MyData
{
public int A { get; set; }
public string B { get; set; }
}
Getting sent to a view like so
public ActionResult Index()
{
MyData[] myDataList = new MyData[2];
myDataList[0] = new MyData() { A = 2, B = "Fred" };
myDataList[1] = new MyData() { A = 3, B = "Sue" };
dynamic charts = (from ch in myDataList
select new
{
ch.A,
ch.B
}).AsEnumerable().Select(c => c.ToExpando());
return View(charts);
}
With the view like so (this is wrong)
#model IEnumerable<dynamic>
#foreach (dynamic item in Model)
{
<text>
#Html.TextBox("item.B")
</text>
}
I'm floundering a bit here as I don't know if this is even possible and I can't find many complex examples of #model dynamic
To be clear, this example is an academic one, what I want here is to understand what's possible with dynamic models in asp mvc
Any advice?
Thanks
Try this in your view:
#for(int i = 0; i < Model.Lenght; i++)
{
#Html.TextBoxFor(model => model[i].A)
#Html.TextBoxFor(model => model[i].B)
}
While you can get it to work, I wouldn't recommend it.
The simple fix IMHO is to use the ExpandoObject as model directly. There's no point in converting it to dynamic at all.
I'm not sure what I am doing wrong. I have never had this problem before or maybe I have, but I've never noticed. I have a page with a partial view. When the page is submitted, the model is checked to see if it has an ID. If it does, it updates the record. If not, it creates a new one. Pretty standard. Once it is done, the model is returned back to the view. The problem I seem to be having is that it isn't updated with any changes to the model. It is just the same model that was posted. Okay so here is some code. I created a brand new project and it still doesn't work.
Also, I used Firebug to look at the raw data coming back and it is still the same model.
Here is the controller:
[AcceptVerbs(HttpVerbs.Get)]
public ActionResult Test()
{
return this.View(new Test());
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult TestDetailPost(Test testin)
{
Test test = new Test();
test.Id = "1";
test.Name = "Guy";
return this.PartialView("TestDetail", test);
}
Here is the "Test" view:
#model WebAppTest.Models.Test
#using (Ajax.BeginForm("TestDetailPost", new AjaxOptions { HttpMethod = "POST", UpdateTargetId = "TestDetail" }))
{
<p><input type="submit"/></p>
<div id="TestDetail">
#{ Html.RenderPartial("TestDetail", Model); }
</div>
}
Here is the "Test Detail" view:
#model WebAppTest.Models.Test
<p>#Html.TextBoxFor(a => a.Id)</p>
<p>#Html.TextBoxFor(a => a.Name)</p>
And the model:
public class Test
{
public string Id { get; set; }
public string Name { get; set; }
}
So what I have found is that if I remove the "Test testin" from the TestDetailPost action, it returns the model I created. If I don't, it just returns the same model that was posted. Of course, I am not doing any DB saves or anything, the code above is just for trying to figure out why this is happening.
Here is the details of what I am using:
MVC5
jQuery 1.11.1
jquery.unobtrusive-ajax
I have updated all files to the latest version using NuGet.
Call ModelState.Clear(); in your action method like below:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult TestDetailPost(Test testin)
{
ModelState.Clear();
Test test = new Test();
test.Id = "1";
test.Name = "Guy";
return this.PartialView("TestDetail", test);
}
I have given more details in my answer here. I hope this helps.
I've searched all the available tutorials I can find, and I'm still having trouble with Umbraco Surface Controllers. I've created a bare-bones Surface Controller example which sorta works, but has some issues. Here's my code so far, questions to follow:
ContactformModel1.cs:
public class ContactFormModel1
{
public string Email { get; set; }
public string Name { get; set; }
public string HoneyPot { get; set; }
public string Title { get; set; }
public string Last { get; set; }
public string First { get; set; }
public string Addr { get; set; }
public string Phone { get; set; }
public string Time { get; set; }
public string Comment { get; set; }
}
ContactSurfaceController.cs:
public class ContactSurfaceController : Umbraco.Web.Mvc.SurfaceController
{
public ActionResult Index()
{
return Content("this is some test content...");
}
[HttpGet]
[ActionName("ContactForm")]
public ActionResult ContactFormGet(ContactFormModel1 model)
{
return PartialView("~/Views/ContactSurface/Contact1.cshtml", model);
}
[HttpPost]
[ActionName("ContactForm")]
public ActionResult ContactFormPost(ContactFormModel1 model)
{
// Return the form, just append some exclamation points to the email address
model.Email += "!!!!";
return ContactFormGet(model);
}
public ActionResult SayOK(ContactFormModel1 model)
{
return Content("OK");
}
}
Contact.cshtml:
#model ContactFormModel1
#using (Html.BeginUmbracoForm<ContactSurfaceController>("ContactForm"))
{
#Html.EditorFor(x => Model)
<input type="submit" />
}
ContactMacroPartial.cshtml:
#inherits Umbraco.Web.Macros.PartialViewMacroPage
#Html.Action("ContactForm", "ContactSurface")
My Questions:
I'm pretty sure that return ContactFormGet(model) is wrong in the
ContactFormPost method, but everything else I've tried throws an error.
When I try return RedirectToCurrentUmbracoPage(), I get Cannot
find the Umbraco route definition in the route values, the request
must be made in the context of an Umbraco request.
When I try return CurrentUmbracoPage(), I get Can only use
UmbracoPageResult in the context of an Http POST when using a
SurfaceController form.
The routing appears to work correctly (when I put a breakpoint inside ContactFormPost, the debugger stops there). But when the form comes back, I get the exact values I submitted. I don't see the !!! appended to the email address. (Note, this bit of code is just for debugging, it's not meant to do anything useful).
How do I call the "SayOK" method in the controller? When I change the BeginUmbracoForm method to point to SayOK, I still get stuck in the ContactFormPost method.
I'm sure I'm missing something incredibly stupid, but I can't figure this out for the life of me.
I wanted to take a moment to say how I resolved this. After playing around some more, I realized that I didn't really state my problem clearly. Basically, all I'm trying to do is embed an MVC form inside a Partial View Macro, so that it could be used in the content of a page (not embedded in the template).
I could get this solution to work, but I really didn't like how much logic the author put inside the View file. So I adapted his solution this way:
Partial View Macro (cshtml) file:
#inherits Umbraco.Web.Macros.PartialViewMacroPage
#using Intrepiware.Models
#{
bool isPostback = !String.IsNullOrEmpty(Request.Form["submit-button"]);
if(isPostback)
{
#Html.Action("CreateComment", "ContactSurface", Request.Form)
}
else
{
#Html.Partial("~/Views/Partials/ContactForm.cshtml", new ContactFormModel())
}
}
Form Partial View (cshtml) file:
#using Intrepiware.Models
#using Intrepiware.Controllers
#model ContactFormModel
<p>
<span style="color: red;">#TempData["Errors"]</span>
</p>
<p>
#TempData["Success"]
</p>
<div id="cp_contact_form">
#using(Html.BeginUmbracoForm("CreateComment", "BlogPostSurface"))
{
#* Form code goes here *#
}
ContactSurfaceController.cs file:
public class ContactSurfaceController : Umbraco.Web.Mvc.SurfaceController
{
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult ubCreateComment(ContactFormModel model)
{
if (processComment(model) == false)
return CurrentUmbracoPage();
else
return RedirectToCurrentUmbracoPage();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult CreateComment(ContactFormModel model)
{
if(processComment(model) == true)
{
TempData["Success"] = "Thank you for your interest. We will be in contact with you shortly.";
ModelState.Clear();
}
return PartialView("~/Views/Partials/ContactForm.cshtml");
}
private bool processComment(ContactFormModel model)
{
// Handle the model validation and processing; return true if success
}
}
The controller is designed so that the form can be embedded either in the template or a Partial View Macro. If it's embedded in a template, the form should post to ubCreateComment; if it's in a macro, post to CreateComment.
I'm almost positive there's a better/more correct way of doing this, but I ran out of time to work on the project. If someone has a better solution, please post it!
One final question/note: You'll notice that the partial view macro posts Request.Form to the ContactSurfaceController.CreateComment, and MVC magically serializes it for me. That's safe, yeah? If so, doesn't MVC rock? :)
You are using a ChildAction because you are specifying #Html.Action("ContactForm", "ContactSurface") and because of this, in your View you need to:
Use Html.BeginForm(...) and not 'Html.BeginUmbracoForm(...)'
Allow the form to post back to the same path and not to the action
If you do this, then the form will post back to itself as expected.
See the documentation here for further help.
Edit:
Just saw the final part to your question. If you intend SayOK to be your 'thank you' message, I would just call it from your HttpPost action instead of returning the initial view.
I have an PartialViewResult action which renders a PartialView that I call from a $.ajax call on the page.
That PartialView also has a foreach loop for the items in the VM and within that PartialView I have two RenderAction that render two other Partials.
It all works fine, except for the speed at which it's rendered. When I comment out the two nested RenderAction, the main partial view renders extremely fast. When I uncomment them, the main partial view renders between 3 to 5 seconds. Even if I remove all data from the partial views and all data from the actions to only return an empty view, it still takes 3-5 seconds.
Somehow, my app is having problems rendering these two partials even when they're empty.
My code:
Main action:
public PartialViewResult MyTasks(int milestoneId, int currentPage = 1)
{
var mergedTasks = new List<MergedTask>();
var TrackingTeams = _TrackingTeams.GetAll().ToList();
var pagingInfo = new PagingInfo() {CurrentPage = currentPage, ItemsPerPage = 10, TotalItems = _TrackingTeams.GetAll().Count() };
mergedTasks.AddRange(from TrackingTeam in TrackingTeams
let task = allTasks.Single(x=>x.TestId == (int)TrackingTeam.TrackingTask.TestId)
select new MergedTask()
{
Summary = TrackingTeam.TrackingTask.Description,
InternalId = task.Id,
DevTrackingTask = TrackingTeam.TrackingTask,
LastUpdate = task.DateModified
});
return PartialView(new DevTrackingTaskViewModel
{
MergedTasks = mergedTasks,
Category = _categories.GetById(categoryId),
PagingInfo = pagingInfo
});
}
The ViewModel associated with it:
public class TrackingTaskViewModel
{
public List<MergedTask> MergedTasks { get; set; }
public int CountTasks { get; set; }
public PagingInfo PagingInfo { get; set; }
public Category Category { get; set; }
}
public class MergedTask
{
public int InternalId { get; set; }
public string Summary { get; set; }
public TrackingTask TrackingTask { get; set; }
public DateTime LastUpdate { get; set; }
}
My main PartialView:
#foreach (var item in Model.MergedTasks)
{
<script type="text/javascript">
$(document).ready(function () {
$("#TrackingTask#(item.TrackingTask.Id)").hover(function () {
if ($("#snapshotFixerForTrackTask#(item.TrackingTask.Id)").length == 1) {
$("#expandTrackingTaskForTask#(item.TrackingTask.Id)").removeClass("hide");
}
else {
$("#expandTrackingTaskForTask#(item.TrackingTask.Id)").toggleClass("hide");
}
});
});
</script>
<div class="TrackingTaskDiv" id="TrackingTask#(item.TrackingTask.Id)">
<div class="TrackingContainer">
<div id="flagsForTrackingTask#(item.TrackingTask.Id)" class="flags">
#{Html.RenderAction("ShowFlags", "Task", new { trackingid = item.TrackingTask.Id });}
</div>
<div id="TestStatusForTrackTask#(item.TrackingTask.Id)" class="TestStatusWrapper">
#{Html.RenderAction("CheckTrackStatus", "Task", new { trackingid = item.TrackingTask.Id });}
</div>
</div>
<div id="expandTrackingTaskForTask#(item.TrackingTask.Id)" class="expandTrackingTask collapsed hide"></div>
</div>
}
I can paste in the Action for the "ShowFlags" and "CheckTrackStatus" if needed. But as I mentionned even if I remove all the code from the action and the view the rendering still takes 3-5 seconds to render the whole view, there's no difference.
One solution we came up with would be to remove the partials altogether, put the VM of each partial inside the main VM and do the same for the HTML in the partials. But I like the idea of compartmentalizing specific features on a view.
LanFeusT (great name!!),
RenderAction will cause a performance overhead as it makes a complete MVC cycle, rather than just using the current controller context. you may be advised to seek an alternative approach (such as including the required elements in your viewModel). i found this out the hard way as well and it was only when profiling my code that i appreciated the amount of reflection going on for each new RenderAction call (which in 99% of circumstances is both convenient and appropriate).
So my bottom line advice - look at extending your viewmodel.
I urge you to google RenderAction vs RenderPartial for further info..
see:
RenderAction RenderPartial
I have implemented the Ajax Autocomplete feature in my application using a web service file that querys my database and it works great. One problem I am having is allowing the user to see the item's name, as that's what they are typing in the textbox, but when they select it, it saves the item's ID number instead of the actual name. I want it to behave much like a dropdown list, where I can specify what is seen and entered vs. what is actually saved in the database (in this case, the product ID instead of it's name.)
I have this text box in my view, along with the script:
<script type="text/javascript">
Sys.Application.add_init(function() {
$create(
AjaxControlToolkit.AutoCompleteBehavior, {
serviceMethod: 'ProductSearch',
servicePath: '/ProductService.asmx',
minimumPrefixLength: 1,
completionSetCount: 10
},
null,
null,
$get('ProductID'))
});
</script>
<p>
<label for="ProductID">Product:</label>
<%= Html.TextBox("ProductID", Model.Products)%>
<%= Html.ValidationMessage("ProductID", "*")%>
</p>
Here's what is in my asmx file:
public class ProductService : System.Web.Services.WebService
{
[WebMethod]
public string[] ProductSearch(string prefixText, int count)
{
MyDataContext db = new MyDataContext();
string[] products = (from product in db.Products
where product.ProductName.StartsWith(prefixText)
select product.ProductName).Take(count).ToArray();
return products;
}
}
Can anyone help me figure this out? I'm using this so they can just start typing instead of having a dropdown list that's a mile long...
the autocomplete control will post a json object to "servicePath/serviceMethod" so first, set the servicePath to your controller and the serviceMethod option to your action name.
then define this class:
public class AutoCompleteRequest
{
public string PrefixText { get; set; }
public int Count { get; set; }
}
because thats the json object the autocomplete control posts to your controller.
then define a JsonModelBinder:
public class JsonBinderAttribute : CustomModelBinderAttribute
{
public override IModelBinder GetBinder()
{
return new JsonModelBinder();
}
public class JsonModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
try
{
var inputStream = controllerContext.HttpContext.Request.InputStream;
using (var sr = new StreamReader(inputStream))
{
var json = sr.ReadToEnd();
return JsonConvert.DeserializeObject(json, bindingContext.ModelType);
}
}
catch
{
return null;
}
}
}
}
this one is using Json.Net as deserializer.
then define your action like this:
public virtual JsonResult SearchA([JsonBinder]AutoCompleteRequest post)
{
var data = repository.Query<A>()
.Where(s => s.Name.StartsWith(post.PrefixText))
.Take(post.Count)
.Select(s => s.Name)
.ToArray();
return Json(data);
}
note how i use the JsonBinderAttribute on the AutoCompleteRequest parameter and how a Json array of strings is returned.
Edit
i did a follow up blog post here: http://devcarl.posterous.com/how-to-use-ajax-library-4-autocomplete-with-a
Although this doesn't answer your question directly have you thought about using the Jquery autocomplete control?
From my experience it seems more flexible and you could link it up with your existing service and have more control over the data that is returned as well. Here the link if it helps.