As part of my current project I've blundered into an enormous MVC web form that I know relatively little about, and am having to do some fairly serious debugging on it. I have plenty of WebForms experience but little with MVC so please forgive me if the following isn't clear, or is asking silly questions.
Part of the form is a custom .ascx control which gets information from another URL and limits what the user can put into that section of the form based on that information. All the form elements can be turned on and off. If you turn this particular form element on, fill it in and submit then the ModelState fails its validation check, complaining that its missing a required value. The form is returned with the problematic element switched on, but no values in the boxes. If you don't switch on this particular element, the ModelState validates properly.
I know enough about MVC to understand that the mapping between form elements and the model occurs under the hood. However this makes it very difficult to debug. I really have no idea what the problem is or, worse, how to go about investigating what the cause is. Any suggestions on either front would be greatly appreciated.
EDIT: Some code
//start to deal with specialist form user controls
case "ViewData.Customer.CustomFieldTypes.BlackWhiteListConfigViewData ":
if (Model.Visible)
{ %>
<div class="formItem">
<label>
<% Html.RenderPartial("~/Views/Shared/EditorTemplates/BlackWhiteListConfigViewData.ascx"); %>
</div>
<% }
else
{ %>
<%= Html.HiddenFor(m => Model.Value) %>
<% }
b break;
And the binding code:
switch (fieldType)
{
case FeatureFieldFactory.BlackWhiteListConfigElTypeName:
{
// create a new default model binder, and tell it which type we actually want it to bind.
BlackWhiteListConfigViewData model = new BlackWhiteListConfigViewData();
bindingContext.ModelMetadata =
ModelMetadataProviders.Current.GetMetadataForType(() => model, typeof(BlackWhiteListConfigViewData));
return base.BindModel(controllerContext, bindingContext);
}
EDIT: BlackWhiteListConfigViewData.ascx code
if (Model.Visible)
{ %>
<script type="text/javascript">
$(cpContext.CurrentService()).bind('onServiceAttributesReady', function (context) {
$(document).ready(function () {
$("#js-hook-BlackWhiteListLoading").hide();
if (context.target.AttributeNames().length === 0) {
$("#js-hook-AddBlackWhiteListEntry").hide();
$("#js-hook-BlackWhiteListConfigTable").hide();
$("#js-hook-BlackWhiteListMessage").html('There are no attribute types present in the catalogue. The Black and White lists can be configured once the catalogue is loaded.');
$("#js-hook-BlackWhiteListMessage").show();
}
else {
$("#js-hook-BlackWhiteListMessage").hide();
$("#js-hook-AddBlackWhiteListEntry").show();
$("#js-hook-BlackWhiteListConfigTable").show();
}
});
});
$(cpContext.CurrentService()).bind('onServiceAttributesError', function (context) {
$(document).ready(function () {
$("#js-hook-BlackWhiteListLoading").hide();
$("#js-hook-AddBlackWhiteListEntry").hide();
$("#js-hook-BlackWhiteListConfigTable").hide();
$("#js-hook-BlackWhiteListMessage").html('No attribute types retrieved from service API. The Black and White lists can be configured when a Build has been completed and service API is accessible.');
$("#js-hook-BlackWhiteListMessage").show();
});
});
cpContext.CurrentService().getAttributes();
</script>
<div class="formItem narrow blackWhiteListConfig">
<% using (Html.BeginCollectionItem("fields"))
{
Model.Value = "n/a";
%>
<%= Html.HiddenFor(m => Model.Id) %>
<%= Html.HiddenFor(m => Model.Type) %>
<%= Html.HiddenFor(m => Model.Name) %>
<%= Html.HiddenFor(m => Model.Visible) %>
<%= Html.HiddenFor(m => Model.DisplayName) %>
<%= Html.HiddenFor(m => Model.Value) %>
<table id="js-hook-BlackWhiteListConfigTable" class="configTable" style="display:none">
<thead>
<tr><th class="configTableColumn">Key Attribute Type</th><th class="configTableColumn">Key Value</th><th class="transparent"></th><th class="configTableColumn">Related Attribute Type</th><th class="configTableColumn">Related Attribute Value</th><th class="configTableColumn narrow">Black</th><th class="configTableColumn narrow">White</th><th class="transparent" style="width:20px"></th></tr>
</thead>
<tbody class="blackWhiteListRows">
<% foreach (BlackWhiteListEntryViewData entry in Model.Entries)
{ %>
<%= Html.EditorForNested(e => entry) %>
<% } %>
</tbody>
</table>
<hr />
<p id="js-hook-AddBlackWhiteListEntry" class="clear" style="display:none">Add another entry</p>
<p id="js-hook-BlackWhiteListMessage" class="clear" style="display:none">There are no attribute types present in the catalogue. The Black and White lists can be configured once the catalogue is loaded.</p>
<img id="js-hook-BlackWhiteListLoading" src="<%=Links.Content.images.content_loading_gif %>" alt="Loading Black and White list configuration..."
style="" class="clear" />
<%= Html.ValidationMessageFor(m => Model) %>
<% } %>
Model.Value) %>
Cheers,
Matt
Try replacing:
<% Html.RenderPartial("~/Views/Shared/EditorTemplates/BlackWhiteListConfigViewData.ascx"); %>
with:
<%= Html.EditorForModel() %>
or if your model type is not BlackWhiteListConfigViewData you could specify it:
<%= Html.EditorForModel("BlackWhiteListConfigViewData") %>
Also inside your editor template you seem to be using some custom helpers such as Html.EditorForNested. Make sure your input field names respect the conventions for binding to a list.
Related
I'm developing a music website in Rails. The landing page is divided and has a search form where you can look up bands stored in the DB. Inside the other division I embedded a player reproducing some random videos. I want the search results being displayed on the same landing page.
When I submit the form the page reloads and the player reloads also and starts playing a different video. What I actually want to achieve is that the player doesn't get interrupted while the search form is submitted.
Don't know if and how this is possible the way I want it. If I got it right this link describes one way to do that but I didn't get it to work.
home controller
def index
#bands = Band.search(params[:search]).order("name ASC")
#random_band = Band.order("RANDOM()").limit(1)
end
def preview
#bands = Band.search(params[:search]).order("name ASC")
render :partial => 'preview', :content_type => 'text/html'
end
_preview.html.erb
<table class="table"
<tr>
<th>Title</th>
<th>Year</th>
</tr>
<% #bands.each do |f| %>
<tr>
<td>
<%= f.name %>
</td>
</tr>
<% end %>
</table>
home#index
<!DOCTYPE html>
<html lang="en">
<form class="navbar-form navbar-left" role="search">
<%= form_tag({:action => 'preview'}, id: "search-form", :remote => true, :'data-update-target' => 'update-container') do %>
<%= text_field_tag :search, params[:search], placeholder: "Search Bands" %>
<%= submit_tag "Search" %>
<% end %>
</form>
<div class="row">
<div class="col-md-6">
<div id="update-container">
<%= #random_band.first.name %>
</div>
</div>
<div class="col-md-6">
<%= video_tag("#{#random_band.first.name}.mp4", size: "460x390",
controls: true, autobuffer: true, autoplay: true) %>
</div>
</div>
</html>
application.js
$(function() {
/* Convenience for forms or links that return HTML from a remote ajax call.
The returned markup will be inserted into the element id specified.
*/
$('form[data-update-target]').live('ajax:success', function(evt, data) {
var target = $(this).data('update-target');
$('#' + target).html(data);
});
});
First problem: The form doesn't seem to find the preview action.
Second problem: If I change the content of the index with the preview action it does find the preview action obviously but it doesn't render the search results inside the update-container. Instead it renders them as a whole new page.
By the way, I know my way around Rails a bit but I've got absolutely no experience with Ajax.
You got most of your code very twisted and not really lean as far as I can tell. The short and very lean way to get your search results updated without reloading the rest of the page (including your player) could look like this. The index function should look like this:
//bands_controller.rb
def index
#bands = Band.search(params[:search])
end
You have to create the search-method inside of your Band-Model:
//Band.rb
def self.search(search)
if search
find(:all, :conditions => ['name LIKE ?', "%#{search}%"])
else
find(:all)
end
end
You need to render your _preview.html.erb inside a div with a certain ID somewhere inside your view:
<div id="bands"><%= render 'preview' %></div>
Your form_tag has a lot of arguments that you won't need anymore:
<%= form_tag bands_path, :method => 'get', :id => "search_form" do %>
<%= text_field_tag :search, params[:search] %>
<%= submit_tag "Search", :name => nil %>
<% end %>
The jquery I use for my forms looks like this.. Pretty simple in my opinion:
$(function() {
$("#search_form input").keyup(function() {
$.get($("#search_form").attr("action"), $("#search_form").serialize(), null, "script");
return false;
});
});
This fires the index.js.erb (you will have to create that) everytime you put a letter into your search field:
//index.js.erb (same folder as index.html.erb)
$("#bands").html("<%= escape_javascript(render("preview")) %>");
This should refresh your div. It's maybe a little work but that's the easiest way to implement an ajax search function into your index page that I could think of.
Sources:
Railscast#27
Railscast#240
I have an ASP.NET MVC2 application. On one of my pages I have a textbox defined in the following manner:
<%: Html.TextBoxFor(model => model.PostCode) %>
Which is working perfectly.
However, for specific countries (model.Country) I do not want to show this TextBox.
What is the best way of implementing this?
This is an .aspx file, not .cshtml
Thanks
Create a property in your model class, and try this:
#if (!Model.IsSpecificCountry) {
#Html.TextBoxFor(model => model.PostCode)
}
Update:
<%if (!Model.IsSpecificCountry) { %>
<%= Html.TextBoxFor(model => model.PostCode) %>
<% } %>
so i was able to make my recaptcha thing working but my problem is though, i want to have it appear only after 3 tries. one option that i have is to redirect the user to a View that will have the captcha already (duplicate of the login but with captcha) and then have him log in through that page. is there any other option? i feel like partial views would cause problems on Post of the page. what do you think is the best way to generate the captcha?
<% using(Html.BeginForm()) {%>
<%: Html.AntiForgeryToken() %>
<%: Html.ValidationSummary() %>
<label>Username:</label>
<%: Html.TextBoxFor(m => m.Username) %>
<br /><br />
<label>Password:</label>
<%: Html.PasswordFor(m => m.Password) %>
<br /><br />
<input type="submit" value="Login" />
<%: Html.ActionLink("Register", "Register", "") %>
<%: Html.ActionLink("Forgot Password", "Password", "") %>
<%: Html.ActionLink("Forgot Username", "Username", "") %>
<%: ReCaptcha.GetHtml(publicKey: "thisismykey", theme: "red") %>
<% } %>
THanks,
G
You are passing in a model (hopefully a ViewModel). Why not add NumberOfFailedLogins to it?
Then you could just put a bit of code around the Recaptcha saying
<%: if (Model.NumberofFailedLogins > 3) { %>
<%: ReCaptcha.GetHtml(publicKey: "thisismykey", theme: "red") %>
<% } %>
NOTE: I am used to Razor syntax, so apologies if the above is not perfect. I'm sure you get the idea!
Obviously you would need to update NumberOfFailedLogins behind the scenes!
EDIT: Just to clarify, the number of failed login attempts ought to be recorded in the membership database behind the scenes automatically (the act of attempting to login would do this; note that the ASP.NET Membership Provider automatically records the number of consecutive failed login attempts out of the box) and it is from there that the ViewModel obtains this information. So it doesn't matter if you are using a bot to attempt to brute-force your way in, it can still be presented with the ReCaptcha after three attempts (and of course can be locked out too if desired).
The ViewUserControl below results in the following error at runtime:
The Collection template was used with an object of type 'System.Data.Entity.DynamicProxies.Collection_1D9779ACB92AE24E3428C288EA7B1480A6477CF8861FB7582692E775613EFB3A', which does not implement System.IEnumerable.
The error occures on this line: <%: Html.EditorFor(model => model) %>
If I change the name of the model object to Collection2 it works. Does it gets confused because Collection is also the name of an object in the .net framework?
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<CollectionManager.Models.Collection>" %>
<% Html.EnableClientValidation(); %>
<% using (Html.BeginForm()) { %>
<%: Html.ValidationSummary(true) %>
<%: Html.EditorFor(model => model) %>
<input type="submit" value="Save" />
<% } %>
remco - yes, i think that would definately be a reserved word. not sure about it getting confused but definately you should take care with naming when potential clashes could occur. the same thing is true of variable names, tho you can prefix them with # to override that i.e. string #absract would be allowable, whereas string abstract wouldn't.
go with the 'reserved' flow :)
jim
I am using VS 2008 MVC.
I developed a controller.
Form controller i fetch the data by using LinqToSql.
& i am retuning a list of that data.
e.g. return View(Students.Tolist());
now i want to display the list using "foreach" loop in view.
so how do i achieve it?
You return your Student list in the Controller Action as the Model for the View, so make sure your View is strongly typed. Your View should have this at the top:
Inherits="System.Web.Mvc.ViewPage<List<Your.Namespace.Student>>"
Then you can iterate over that list in the View like this:
<% foreach(var student in Model)
{ %>
<div class="student">
<%= student.Name %>
<%= student.Age %>
</div>
<% } %>
This is if you use the MVC RC or newer.
You should have a Student class to hold the records outputted by LINQ to SQL. Anonymous types don't work well in this scenario.
<% foreach (var item in (IEnumerable<Student>)ViewData.Model) { %>
<div class="student">
Name: <%= item.Name %> <br />
GPA: <%= item.GPA %>
</div>
<% } %>