ASP.NET MVC Razor Content Placeholders - asp.net-mvc

Perhaps this has been addressed somewhere else, but I can't find the keywords to search for.
In ASP.NET MVC with the Razor view engine: I have a view which renders with the same if statement repeated numerous times, in different parts of the html. I'm wondering if there's a way to consolidate all of these if's into a single if and set values for placeholders that would save the spot where each of those if's are, like so:
<div>#ph1</div>
<div>#ph3</div>
<div>#ph2</div>
#if(true)
{
ph1 = "<div>adfad<div>"
ph2 = "dsfaadfad"
ph3 = Model.Value
}
This is kind of a stupid example, but I think it makes the point of what I mean.

Your code is fine. You could also use a Razor Helper
http://weblogs.asp.net/scottgu/archive/2011/05/12/asp-net-mvc-3-and-the-helper-syntax-within-razor.aspx

#{
MvcHtmlString ph1 = new MvcHtmlString("");
MvcHtmlString ph2 = new MvcHtmlString("");
if (true)
{
ph1 = new MvcHtmlString("<div>" + Model.Value + "<div>");
ph2 = new MvcHtmlString("<div>fsgfdgdfg<div>");
}
}
#ph1
#ph2
Again, silly usage, but it makes the point.
As was suggested in one of the answers, a nice addition to what I have is to assign a helper. This makes it easier to assign multiple statements without a lot of concatenation.
#helper helper()
{
<span>#Model.Value</span>
<div>dsfadsfasdfdfa</div>
}
#{
MvcHtmlString ph1 = new MvcHtmlString("");
MvcHtmlString ph2 = new MvcHtmlString("");
if (true)
{
ph1 = new MvcHtmlString("<div>" + Model.Value + "<div>");
ph2 = new MvcHtmlString(helper().ToHtmlString());
}
}
#ph1
#ph2
If anybody has better ideas, I'd still be interested.

Views shouldn't normally have logic in your views (especially a single helper that is creating html). A better option is to use partial views or display for templtates.
models/[controller]
public class SomeViewModel()
{
//[UIHint("PhoneNumber")]
//public string ph1 { get; set; }
//[UIHint("PhoneNumber")]
//public string ph1 { get; set; }
//[UIHint("PhoneNumber")]
//public string ph1 { get; set; }
//if these all represent phone numbers, it would be ideal to
[UIHint("PhoneNumbers")]
IEnumerable<string> PhoneNumbers { get; set; }
}
views/[controllers]
#model SomeViewModel
#Html.DisplayFor(m => m.PhoneNumbers);
view/shared/DisplayTemplates/PhoneNumbers.cshtml
#model IENumerable<string>
#foreach (string phoneNumber in Model)
{
<div>#phoneNumber</div>
}

Related

#model dynamic binding with arrays

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.

How can I pass the current URL params to method for replacement

I am using the Pager HTML Helper class from twitter.bootstrap.mvc. It will build list of page links for Bootstrap.
How would I pass the current URL parameters to this function?
public static MvcHtmlString Pager(this HtmlHelper helper,
int currentPage, int totalPages,
Func<int, string> pageUrl, <-- This
string additionalPagerCssClass = "") {
...
a.MergeAttribute("href", pageUrl(i));
...
}
It is called like this:
#Html.Pager(Model.PageIndex, Model.TotalPages, x => Url.Action("Results", "Search", new { page = x }))
The x => Url.Action("Results", "Search", new { page = x }) is the part that I don't know how to change. This is a search results page and has the search settings in the URL as parameters. For the paging to work, I need these params.
Is my only option to specify every single param and have them in the ViewModel as well as the URL?
Is my only option to specify every single param and have them in the
ViewModel as well as the URL?
It is probably not your only option, you could look at using ViewData or cookies for example, but it is probably the best option.
The ViewModel should really contain all the data required to render your view. Given that your paging controls depend on this data, it is not unreasonable for it to be part of your ViewModel.
You View will still look reasonably tidy
e.g.
`#Html.Pager(Model.PageIndex, Model.TotalPages, x => Url.Action("Results", "Search", new { page = x, search = Model.Search, orderBy = Model.OrderBy }))`
If you have a lot of parameters t pass, you could enhance this by making a special "Paging" class which will hold all the details required for the paging, have this as a property of your model modify the HtmlHelper to accept this, to tidy up some of this code.
e.g.
public class PagingDetails
{
public int TotalPages { get; set; }
public int CurrentPage { get; set; }
public int TotalResults { get; set; }
public string Search { get; set; }
public string OrderBy { get; set; }
...other parameters...
}
public static MvcHtmlString Pager(this HtmlHelper helper,
Func<int, string> pageUrl,
PagingDetails pagingDetails,
string additionalPagerCssClass = "")
{
// need to tweak this to append extra parameters to resulting URL
}
`#Html.Pager(x => Url.Action("Results", "Search", new { page = x }, Model.PagingDetails))`

How do I add a Custom Query for a drop down and retain the View Model Pattern?

I've read many articles which they state that querying should not be placed in the Controller, but I can't seem to see where else I would place it.
My Current Code:
public class AddUserViewModel
{
public UserRoleType UserRoleType { get; set; }
public IEnumerable<SelectListItem> UserRoleTypes { get; set; }
}
public ActionResult AddUser()
{
AddUserViewModel model = new AddUserViewModel()
{
UserRoleTypes = db.UserRoleTypes.Select(userRoleType => new SelectListItem
{
Value = SqlFunctions.StringConvert((double)userRoleType.UserRoleTypeID).Trim(),
Text = userRoleType.UserRoleTypeName
})
};
return View(model);
}
The View:
<li>#Html.Label("User Role")#Html.DropDownListFor(x => Model.UserRoleType.UserRoleTypeID, Model.UserRoleTypes)</li>
How do I retain the View Model and Query and exclude the User Type that should not show up?
I think that you are doing it just fine.
Any way... all you can do to remove the querying logic from controller is having a ServiceLayer where you do the query and return the result.
The MVC pattern here is used correctly... what your are lacking is the other 2 layers (BusinessLayer and DataAccessLayer)... since ASP.NET MVC is the UI Layer.
UPDATE, due to comment:
Using var userroletypes = db.UserRoleTypes.Where(u=> u.UserRoleType != 1);
is OK, it will return a list of UserRoleType that satisfy the query.
Then, just create a new SelectList object using the userroletypes collection... and asign it to the corresponding viewmodel property. Then pass that ViewModel to the View.
BTW, I never used the db.XXXX.Select() method before, not really sure what it does... I always use Where clause.
SECOND UPDATE:
A DropDownList is loaded from a SelectList that is a collection of SelectItems.
So you need to convert the collection resulting of your query to a SelectList object.
var userroletypes = new SelectList(db.UserRoleTypes.Where(u=> u.UserRoleType != 1), "idRoleType", "Name");
then you create your ViewModel
var addUserVM = new AddUserViewModel();
addUserVM.UserRoleTypes = userroletypes;
and pass addUserVM to your view:
return View(addUserVM );
Note: I'm assuming your ViewModel has a property of type SelectList... but yours is public IEnumerable<SelectListItem> UserRoleTypes { get; set; } so you could change it or adapt my answer.
I don't see anything wrong with your code other than this db instance that I suppose is some concrete EF context that you have hardcoded in the controller making it impossible to unit test in isolation. Your controller action does exactly what a common GET controller action does:
query the DAL to fetch a domain model
map the domain model to a view model
pass the view model to the view
A further improvement would be to get rid of the UserRoleType domain model type from your view model making it a real view model:
public class AddUserViewModel
{
[DisplayName("User Role")]
public string UserRoleTypeId { get; set; }
public IEnumerable<SelectListItem> UserRoleTypes { get; set; }
}
and then:
public ActionResult AddUser()
{
var model = new AddUserViewModel()
{
UserRoleTypes = db.UserRoleTypes.Select(userRoleType => new SelectListItem
{
Value = SqlFunctions.StringConvert((double)userRoleType.UserRoleTypeID).Trim(),
Text = userRoleType.UserRoleTypeName
})
};
return View(model);
}
and in the view:
#model AddUserViewModel
<li>
#Html.LabelFor(x => x.UserRoleTypeId)
#Html.DropDownListFor(x => x.UserRoleTypeId, Model.UserRoleTypes)
</li>

Nested RenderAction are rendered very slowly

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

HtmlHelper building a dropdownlist, using ServiceLocator : code smell?

Is it a code smell to have to following pattern, given the following code (highly simplified to get straight to the point) ?
The models :
class Product
{
public int Id { get; set; }
public string Name { get; set; }
public Category Cat { get; set; }
}
class Category
{
public int Id { get; set; }
public string Label { get; set; }
}
The view to edit a Product :
<% =Html.EditorFor( x => x.Name ) %>
<% =Html.EditorFor( x => x.Category ) %>
The EditorTemplate for Category
<% =Html.DropDownList<Category>() %>
The HtmlHelper method
public static MvcHtmlString DropDownList<TEntity>(this HtmlHelper helper)
where TEntity : Entity
{
var selectList = new SelectList(
ServiceLocator.GetInstance<SomethingGivingMe<TEntity>>().GetAll(),
"Id", "Label");
return SelectExtensions.DropDownList(helper, "List", selectList, null, null);
}
For information, the real implementation of the helper method takes some lambdas to get the DataTextField and DataValueField names, the selected value, etc.
The point that bothers me is using a servicelocator inside the HtmlHelper. I think I should have a AllCategories property in my Product model, but I would need to be populated in the controller every time I need it.
So I think the solution I'm using is more straightforward, as the helper method is generic (and so is the modelbinder, not included here). So I just have to create an EditorTemplate for each type that needs a DropDownList.
Any advice ?
IMHO I'd leave it the way it is, have the same thing in another project.
BUT the service location bothered me as well so for another project I made this part of an ActionFilter which scans a model, finds all the anticipated dropdowns and does a batch load into ViewData. Since the ServiceLocator or Repository/Context/whatever is already injected into the Controller you don't have to spread your service location all over the place.
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
foreach( var anticipated in SomeDetectionMethod() )
{
var selectList = new SelectList(
ServiceLocator.GetInstance<SomethingGivingMe<TEntity>>().GetAll(),
"Id", "Label");
ViewData["SelectList." + anticipated.Label/Name/Description"] = selectList;
}
}
In the view you can then make a helper to load up those dropdowns via a custom editor template or other method.
advice: look at the asp.net mvc sample application from here: http://valueinjecter.codeplex.com/
good luck ;)
This is how ValueInjecter's Sample Application could get the dropdowns:
(but it doesn't right now cuz I'm ok with the Resolve thing)
public class CountryToLookup : LoopValueInjection<Country, object>
{
ICountryRepo _repo;
public CountryToLookup(ICountryRepository repo)
{
_repo = repo;
}
protected override object SetValue(Country sourcePropertyValue)
{
var value = sourcePropertyValue ?? new Country();
var countries = _repo.GetAll().ToArray();
return
countries.Select(
o => new SelectListItem
{
Text = o.Name,
Value = o.Id.ToString(),
Selected = value.Id == o.Id
});
}
}

Resources