Search Issue in ASP.NET MVC 4 - c#

I'm new here and I have a question about a search piece of code in MVC 4. I have this piece of code:
public ActionResult Index(string searchTerm = null)
{
var model = entities.Users
.Where( m => searchTerm == null || m.SureName.Contains(searchTerm))
.Select(m => new UserViewModel
{
Name = m.Name,
SureName = m.SureName,
Department = m.Department,
Mail = m.Mail
});
}
Index.cshtml
<form method="get">
<input type="search" name="searchTerm " />
<input type="submit" value="Search ..." />
</form>
#foreach (var item in Model)
{
<div>
<h4>#item.Name, #item.SureName</h4>
<div>#item.Department, #item.Mail</div>
</div>
}
When I run this it is working till I want to search anything. Problem is in routing. Because if I will manually type ":XXXX/?searchTerm=Luk" everything is working fine. But when I do the same on the page URL looks like this ":XXXX/?searchTerm+=Luk". Does anybody know, why there is that + mark ?? I think that is the issue I have here.

You have a whitespace here:
<input type="search" name="searchTerm " />
This will be HTML encoded to a "+" in your query string. Remove the whitespace from the "name" attribute and you should be fine.

Related

.NET Multiple Data Filters

I've been using the EF6 tutorial and created filters which work great! However the guide only shows how to make one filter. I've made multiple filters, though they need different strings to operate. However pagination uses searchString (one of the filter strings) to display based on filtered results. How can I operate multiple filters alongside pagination??
Filters and pagination:
if (searchString != null)
{
page = 1;
}
else
{
searchString = currentFilter;
}
ViewBag.CurrentFilter = searchString;
var clients = from s in db.Clients.Include(c => c.MJTopics).Include(c => c.UserTable)
select s;
if (!String.IsNullOrEmpty(searchString))
{
clients = clients.Where(s => s.clientN.Contains(searchString)
|| s.homePage.Contains(searchString));
}
if (!String.IsNullOrEmpty(NameString))
{
clients = clients.Where(s => s.clientEmail.Contains(NameString)
|| s.contName.Contains(NameString));
}
if (!String.IsNullOrEmpty(RIString))
{
clients = clients.Where(s => s.clientN.Contains(RIString));
}
if (!String.IsNullOrEmpty(TopicString))
{
clients = clients.Where(s => s.MJTopics.topicalTF.Contains(TopicString));
}
Pagination view:
Page #(Model.PageCount < Model.PageNumber ? 0 : Model.PageNumber) of #Model.PageCount
#Html.PagedListPager(Model, page => Url.Action("Index",
new { page, sortOrder = ViewBag.CurrentSort, currentFilter = ViewBag.CurrentFilter }))
</div>
Filters in view:
<div class="col-md-4">
#using (Html.BeginForm())
{
<p class="filtertext">Filter Clients:</p>
<p class="filterbox">#Html.TextBox("searchString")</p>
<p class="filterbox2"><input type="submit" value="Search" class="btn btn-default btn-sm selector2" /> </p>
}
</div>
<div class="col-md-4">
#using (Html.BeginForm())
{
<p class="filtertext">Find by Name: </p>
<p class="filterbox"> #Html.TextBox("NameString")</p>
<p class="filterbox2"><input type="submit" value="Search" class="btn btn-default btn-sm selector2" /></p>
}
</div>
The PagedList.Mvc project was abandoned in 2013. The site warns:
IMPORTANT: This package is no longer maintained. Please see ernado-x/X.PagedList for a drop-in replacement.
Although 5 years later, the changes may be big enough that you can't just replace one with the other.
The Nuget package for the replacement is X.PagedList.Mvc
The examples on the Github repo show that it uses IQueryable< T> directly, without filter strings, eg :
var products = MyProductDataSource.FindAllProducts(); //returns IQueryable<Product> representing an unknown number of products. a thousand maybe?
var pageNumber = page ?? 1; // if no page was specified in the querystring, default to the first page (1)
var onePageOfProducts = products.ToPagedList(pageNumber, 25); // will only contain 25 products max because of the pageSize
ViewBag.OnePageOfProducts = onePageOfProducts;
return View();
and
<!-- output a paging control that lets the user navigation to the previous page, next page, etc -->
#Html.PagedListPager( (IPagedList)ViewBag.OnePageOfProducts, page => Url.Action("Index", new { page }) )

Partial within a partial null exception

I have a MVC form which is more complex than all of my others, utilising three models.
Company -> Base_IP -> RequestedIP which goes ViewModel -> Partial1 -> Partial2
I am using BeginCollectionItem for this has each model has a property list of the the model down from it. IE - Company has a property called baseIps, the BaseIp class has a property called requestedIps, it is requestedIps that is coming back null, the count is there on page render, but is not on submit.
When submitting to the database in the post Create(), I get nulls on the 'requestedIps' property, why is this?
I've added the offending controller and partial code samples below, not the entire thing as it's massive/redundant - any questions, please let me know.
Controller - [HttpGet]Create()
public ActionResult Create()
{
var cmp = new Company
{
contacts = new List<Contact>
{
new Contact { email = "", name = "", telephone = "" }
}, pa_ipv4s = new List<Pa_Ipv4>
{
new Pa_Ipv4
{
ipType = "Pa_IPv4", registedAddress = false, existingNotes = "", numberOfAddresses = 0, returnedAddressSpace = false, additionalInformation = "",
requestedIps = new List<IpAllocation>
{
new IpAllocation { allocationType = "Requested", cidr = "", mask = "", subnet = "" }
}
}
}
};
return View(cmp);
}
Controller - [HttpPost]Create()
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Company cmp) // does not contain properties assigned/added to in view render
{
if (ModelState.IsValid)
{
db.companys.Add(cmp);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(cmp);
}
Create View
#model Company
#using (Html.BeginForm())
{
<div id="editorRowsAsn">
#foreach (var ip in Model.pa_ipv4s)
{
#Html.Partial("Pa_IPv4View", ip)
}
</div>
<br />
<div data-role="main" class="ui-content">
<div data-role="controlgroup" data-type="horizontal">
<input type="submit" class="ui-btn" value="Create" />
</div>
</div>
}
Pa_Ipv4 View
#model Pa_Ipv4
#using (Html.BeginCollectionItem("pa_ipv4s"))
{
#Html.AntiForgeryToken()
<div id="editorRowsRIpM">
#foreach (var item in Model.requestedIps)
{
#Html.Partial("RequestedIpView", item)
}
</div>
#Html.ActionLink("Add", "RequestedManager", null, new { id = "addItemRIpM", #class = "button" }
}
RequestedIpView
#model IpAllocation
<div class="editorRow">
#using (Html.BeginCollectionItem("requestedIps"))
{
<div class="ui-grid-c ui-responsive">
<div class="ui-block-a">
<span>
#Html.TextBoxFor(m => m.subnet, new { #class = "checkFiller" })
</span>
</div>
<div class="ui-block-b">
<span>
#Html.TextBoxFor(m => m.cidr, new { #class = "checkFiller" })
</span>
</div>
<div class="ui-block-c">
<span>
#Html.TextBoxFor(m => m.mask, new { #class = "checkFiller" })
<span class="dltBtn">
<img src="~/Images/DeleteRed.png" style="width: 15px; height: 15px;" />
</span>
</span>
</div>
</div>
}
</div>
You first (outer) partial will be generating correct name attributes that relate to your model (your code does not show any controls in the Pa_Ipv4.cshtml view but I assume you do have some), for example
<input name="pa_ipv4s[xxx-xxx].someProperty ...>
however the inner partial will not because #using (Html.BeginCollectionItem("requestedIps")) will generate
<input name="requestedIps[xxx-xxx].subnet ...>
<input name="requestedIps[xxx-xxx].cidr ...>
where they should be
<input name="pa_ipv4s[xxx-xxx].requestedIps[yyy-yyy].subnet ...>
<input name="pa_ipv4s[xxx-xxx].requestedIps[yyy-yyy].cidr ...>
Normally you can pass the prefix to the partial using additional view data (refer this answer for an example), but unfortunately, you do not have access to the Guid generated by the BeginCollectionItem helper so its not possible to correctly prefix the name attribute.
The articles here and here discuss creating your own helper for handling nested collections.
Other options include using nested for loops and including hidden inputs for the collection indexer which will allow you to delete items from the collection and still be able to bind to your model when you submit the form.
for (int i = 0; i < Model.pa_ipv4s.Count; i++)
{
for(int j = 0; j < Model.pa_ipv4s[i].requestedIps.Count; j++)
{
var name = String.Format("pa_ipv4s[{0}].requestedIps.Index", i);
#Html.TextBoxFor(m => m.pa_ipv4s[i].requestedIps[j].subnet)
#Html.TextBoxFor(m => m.pa_ipv4s[i].requestedIps[j].cidr)
...
<input type="hidden" name="#name" value="#j" />
}
}
However if you also need to dynamically add new items you would need to use javascript to generate the html (refer examples here and here)
If you look at your final markup you will probably have inputs with names like
input name="subnet"
input name="cidr"
input name="mask"
This is how the form collection will appear when the form gets posted. Unfortunately this will not bind to your Company model.
Your fields will need to look like this instead
input name="Company.pa_ipv4s[0].subnet"
input name="Company.pa_ipv4s[0].cidr"
input name="Company.pa_ipv4s[0].mask"
input name="Company.pa_ipv4s[1].subnet"
input name="Company.pa_ipv4s[1].cidr"
input name="Company.pa_ipv4s[1].mask"
There are multiple ways to "fix" this, and each has its own caveats.
One approach is to setup "Editor" views (typically in ~/Views/Shared/EditorTemplates/ClassName.cshtml), and then use #Html.EditorFor(x => x.SomeEnumerable). This will not work well in a scenario in which you need to be able to delete arbitrary items from the middle of a collection; although you can still handle those cases by means of an extra property like ItemIsDeleted that you set (e.g. via javascript).
Setting up a complete example here would be lengthy, but you can also reference this tutorial: http://coding-in.net/asp-net-mvc-3-how-to-use-editortemplates/
As a start, you would create a simple template like
~/Views/Share/EditorTemplates/Contact.cshtml:
#model yournamespace.Contact
<div>
#Html.LabelFor(c => c.Name)
#Html.TextBoxFor(c => c.Name)
#Html.ValidationMessageFor(c => c.Name)
</div>
<div>
#Html.LabelFor(c => c.Email)
#Html.TextBoxFor(c => c.Email)
#Html.ValidationMessageFor(c => c.Email)
</div>
... other simple non-enumerable properties of `Contact` ...
#Html.EditorFor(c => c.pa_ipv4s) #* uses ~/Views/Shared/EditorTemplates/pa_ipv4s.cshtml *#
In your view to edit/create a Company, you would invoke this as
#Html.EditorFor(company => company.Contacts)
(Just like the EditorTemplate for Company invokes the EditorFor pa_ipv4s.)
When you use EditorFor in this way, MVC will handle the indexing automatically for you. (How you handle adding a new contact/IPv4/etc. here is a little more advanced, but this should get you started.)
MVCContrib also has some helper methods you can use for this, but it's not particularly simple from what I recall, and may tie you down to a particular MVC version.

Post a new variable in the same view by Ajax && Razor

I have an asp.net mvc4 application in which i'd to use the .post ajax's function to post a variable in my view. i had this problem and i posted it here : question, so i'm now trying to solve it by the use of Ajax
View : index.cshtml
<td>
Donner votre avis.
</td>
and
<form method="Post" action="/User/Validate_Expert_Decision" target="_parent">
<span>
<b style="color:red" >
Votre justification *
<b />
<br />
<br />
<textarea rows="10" cols="75" name="justification"></textarea>
</span>
<input type="hidden" name="element" value="#Request.Params["element"]" />
<p>
<input type="submit" name="submit">
<input type="button" name="cancel" value="Annuler" onClick="closebox()">
</p>
</form>
the javascript function
function openbox2(formtitle, fadin) {
var self = $(this);
var arr = self.data('arr');
$.post("/index.cshtml", { element: arr });
var box = document.getElementById('box');
document.getElementById('shadowing').style.display = 'block';
var btitle = document.getElementById('boxtitle');
btitle.innerHTML = formtitle;
if (fadin) {
gradient("box", 0);
fadein("box");
}
else {
box.style.display = 'block';
}
}
controller : Validate_Expert_Decision
public ActionResult Validate_Expert_Decision()
{
string id_element = Request.Params["element"];
return RedirectToAction("Display_Task_List", new { id_project = id_project});
}
the problem is that i always get an empty value of id_element in string id_element = Request.Params["element"];.
What are the reasons of this error? How can i fix it?
If you want to post an ajax request you must use urls in this pattern:
/Area/Controller/Action
in your ajax request you specify your view name, but url for ajax request must be:
$.post("#Url.Action("ActionName, ControllerName, new { Area = "AreaName" }")", { element: arr });
and if you don't have area, just remove last argumant.
Instead of Request.Params["element"] you can easily get your variables by argumant in your action method:
public ActionResult Validate_Expert_Decision(string element)
** If you want to learn ASP.Net MVC i suggest you to read this book:
Pro ASP.Net MVC 4
This is one of best books that i have read until now

How to bind OrderedDictionary?

I try to bind an OrderedDictionary to a view but when the post method gets invoked the Dictionary is always empty.
Here is my code:
[HttpGet]
public ViewResult Edit(string username, string password)
{
Xml test = new Xml(#"c:\Users\pc\Desktop\xml - Copy.xml");
XmlNode userNode = test.GetUserNodeByUsernameAndPassword(username, password);
User user = new User();
user.BindData(userNode);
return View(user.user);
}
[HttpPost]
public ViewResult Edit(OrderedDictionary attributes)
{
return View(attributes);
}
And here is the view:
#using (Html.BeginForm("Edit", "Users")) {
#Html.ValidationSummary(true)
<fieldset>
<legend>User</legend>
<p>
<input type="submit" value="Save" />
</p>
#{int counter = 0;}
#{string name = "";}
#foreach (DictionaryEntry attribute in Model)
{
{ name = "[" + counter + "].key"; }
<input type="hidden" name=#name value=#attribute.Key />
#attribute.Key #Html.TextBoxFor(m => attribute.Value)
counter++;
<br />
}
</fieldset>
}
And the result Html looks like this is:
<input type="hidden" value="Username" name="[0].key">
Username
<input id="attribute_Value" type="text" value="Anamana" name="attribute.Value">
So the content of the OrderedDictionary appears fine in the view but when I make a post back the binding isn't working and the directory remains empty.
Concept
To bind a dictionary you have to change the name attribute in the html input tag. Something like this:
In your controller:
[HttpPost]
public ActionResult Edit(IDictionary<string, string> attributes)
{
}
In your HTML:
<input type="text" name="attributes[0].Key" value="A Key" />
<input type="text" name="attributes[0].Value" value="A Value" />
<input type="text" name="attributes[1].Key" value="B Key" />
<input type="text" name="attributes[1].Value" value="B Value" />
The attributes name should be before the index [0] on ther name attribute, because your action expect it.
Tips
I would use the HiddenFor and TextBoxFor HTML Helper of the Asp.Net MVC.
#Html.HiddenFor(model => model[i].Key)
#Html.TextBoxFor(model => model[i].Value)
And it will render in the format that the asp.net mvc will understand and get it working.
For more samples about databind take a look at this link.
Meantime I have found the solution.
I can pass an OrderedDictionary to the view page.
It process it by the following Razor code:
#model System.Collections.Specialized.OrderedDictionary
(...)
#{int counter = 0;}
#{string name = "";}
#foreach (DictionaryEntry attribute in Model)
{
{ name = "[" + counter + "].key"; }
#Html.Hidden(name, attribute.Key)
{name = "[" + counter + "].value";}
#attribute.Key #Html.TextBox(name, attribute.Value)
counter++;
<br />
}
The result HTML's structure fits to the samples which is found in a book, the values from the dictionary appears fine on the page.
After POST was invoked the POST handler function gets the modified values in a Dictionary.
[HttpPost]
public ViewResult Edit(Dictionary<string, string> attributes)
{}
I don't know why but I can't use OrderedDictionary here.

mvc3 Problem passing data from View to Controller

I am using mvcContrib to generate a grid to allow users to filter data by keying in search data. There are several partial views that are rendered in my Index View:
Here is the partial view that handles the searching:
#model CRMNPS.Models.PagedViewModel<CRMNPS.Models.NPSProcessed>
#using (Html.BeginForm("Index", "Home", FormMethod.Get))
{
<label>
Model Number: #Html.TextBox("searchWord" )
<br /><br />From Date: #Html.EditorFor(m => m.FromDate)
</label>
<label>
<Br /><br />To Date: #Html.EditorFor(m => m.ToDate)
</label>
<label>
<br /><br /> <input class="button" value="Search" type="submit" />
<br />
</label>
}
Here is my Index view:
#model PagedViewModel <CRMNPS.Models.NPSProcessed>
#{
ViewBag.Title = "CRM Processed List";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Processed List</h2>
#{Html.RenderPartial("SearchBox");}
#{Html.RenderPartial("Pager", Model.PagedList);}
#Html.Grid(Model.PagedList).AutoGenerateColumns().Columns(column =>{
column.For(x => Html.ActionQueryLink(x.ModelNumber, "Edit", new { id = x.Id
})).Named("Id").InsertAt(1);
}).Sort(Model.GridSortOptions).Attributes(#class => "grid-style")
#using (Html.BeginForm("Index", "Home", FormMethod.Post, new { FromDate = Model.FromDate, ToDate = Model.ToDate, SearchWord = Model.SearchWord }))
{
<p>
<input class="button" value="Export to Excel" type="submit" />
</p>
}
At the bottom of the Index View I have another submit within the Html.BeginForm with a Formmethod.Post.
The Index ActionResult that calls this view passes a viewmodel with the search criteria and a IQueryable object that the mvcContrib uses.
When the user presses the Export to Excel push button I would like to pass the selected values back to the Index actionresult HttpPost controller. (FromDate, ToDate and SearchWord)
The FromDate, ToDate and SearchWord values always come back null.
I am fairly new to MVC so any constructive comments are welcome.
Thanks
Joe
Since they are not in the form that you are posting - (Export to Excel is in a separate form).
The inputs
FromDate, ToDate and SearchWord
Are in the first form (in the partial view). So those values don't show up in the controller (since they are not part of the http post).
If you want to see all these values being passed back to the controller, they should be under one
Html.BeginForm
One way is to put'em all in the same form as MoXplod suggested or you can use some javascript to send search values as query string by hooking the submit event of second form like
$('#excel-form').live('click', function(){
var action = this.action;
var searchString = $('#SearchWord').val();
var toDateString = $('#ToDate').val();
var fromDateString = $('#FromDate').val();
if(action.indexOf('?')<0)
{
action = action+"?SearchWord="+searchString;
}
else
{
action = action+"&SearchWord="+searchString;
}
action = action + "&ToDate="+toDateString + "&FromDate=" + fromDateString;
$(this).attr('action', action);
return true;
});
it will put these values in querystring and make them available in action method. Alternatively, you can use ajax to post these values to controller rather than full regular post back.

Categories

Resources