MVC Dynamic Views From Url Slug - c#

I am building my first .NET MVC web application and loving the simplicity and flexibility. I have however come to my first stumbling block.
I have a list of items and if you are not logged in you will see a preview link I would like the link to direct to something like below:
/preview/unique-slug
The view should then allow me to display the contents from the database (essentially a details page)
I am not sure how to approach this nor what I should be Google'ing as the results I got were poor.
Any pointers please?

This is the way I've done this in the past:
You need to store in the database a column with the slug that each item is referred to by. Then, create a route like the following:
routes.MapRoute("PreviewLink",
"/preview/{slug}",
new { controller = "Preview", action = "Details" }
);
So this will call the Details method on the PreviewController like so:
class PreviewController : Controller
{
public ActionResult Details(string slug)
{
var model = db.GetItemBySlug(slug);
return View("Details", model);
}
}
Basically, you'll get the slug as a string in your action method, query the database for the item with the corresponding slug, and display it. Essentially, we just replace an integer id with a string slug.
Hopefully that clears things up for you!

Related

How to use dropdownlists in ASP.NET MVC with lookup values without ViewBag?

This should be really easy, and I can't for the life of me realize what I haven't found any answers to this despite at least an hour of Googling. Anyway, here we go:
I want to create a simple ASP.NET MVC view with some dropdowns that allow the user to select some values, that are then saved as ID:s to the database. As I've been told that ViewBag/ViewData is the devils work I've tried to solve this by adding a selectlist to my (View)Model like:
public virtual SelectList Accommodations { get; set; }
and then in my controller fill it up with something like:
LoanApplication theLoanApplication = new LoanApplication();
theLoanApplication.Accommodations = new SelectList(db.Accommodations.ToList(), "AccommodationID", "Name");
and finally in my view use this to get it all working:
#Html.DropDownListFor(model => model.AccommodationID, Model.Accommodations, "Choose Accommodation")
This works as it should, but when i try to save the model, I get errors that the Accommodations property (the list) is null which is kind of expected. Everything else in my model is as it should though.
I've looked at the well known "Countoso University" code example on the ASP.NET site and in it they seem to use ViewBag to solve similar problems. Is it so that ViewBag isn't all that bad to use in a scenario like that and that it might even be the best possible solution? If not, what would the prefered way be to solve this problem and not use ViewBag/ViewData?
Thank you.
In your controller post action, simply reload the Accommodations list from the db as you do in the get action.
You'd normally do this if the modelstate has errors and you want to pass these back to the view, otherwise there's no need to reload the select list. You've not specified exactly where it gives null reference, so I'm assuming when you have modelstate errors and reloading the view to show the server-side errors. eg:
[HttpPost]
public ActionResult EditAccommodation(AccommodationViewModel model)
{
if (!ModelState.IsValid)
{
model.Accommodations = new SelectList(db.Accommodations.ToList(), "AccommodationID", "Name");
return View(model);
}
// else modelstate ok and model.AccommodationID set
...
}
As an aside, I recommend you keep the variable names clear, eg:
public virtual SelectList AccommodationSelectList { get; set; }
then, when you refer to Accommodations it's clear if it's the select list (which isn't really a list of accommodations, it's a list of select items) or the real list of accommodations from the database.

How to filter model data via query string params in asp.net mvc 4 ?

I am creating a view which initially pulls/display all the records on the page and later there are filters(textbox and checkbox) on left where user can filter the results from.
This is my first mvc app, so I have followed the below mentioned approach:
// GET: /Search/
public ActionResult Home(int page = 1)
{
SearchController have a Home method which is default and pushes the data to view to display in grid.
Controls to filter the data is wrapped under:
#using (Html.BeginForm("Home", "Search", FormMethod.Post))
{
for that I have
[HttpPost]
public ActionResult Home(Partner partner)
My QUESTION is :
a) The search needs to be a query string based so that users can share the filtered result so what we be the best way to filter the Model data (partner here) via QS, I know I can either pass the whole model to the ActionResult or I can accept each field name in the AcitionResult.
b) How do you protect the QS params, the best practice ?
Thanks a lot in advance.
First of all, you shouldn't use HttpPost to query filtered data! POST is for performing commands (typically adding new data, but can be used for other functions as well), while GET is used for queries.
Your Home action should be slightly modified:
public ActionResult Home(int page = 1, Person person)
{
// get the initial data - i assume that you using some context for it (you can use service as well)
using(var context = new DbContext())
{
var data = context.... //get the data here
if(person != null)
{
data = data.Where(p => p.id == person.id).ToList(); //filter by id for example
}
//assuming your view gets a List as a model
return View(data)
}
}
I know I can either pass the whole model to the ActionResult or I can accept each field name in the AcitionResult
I wouldn't use your display model as a filter. I would rather create new filter model that will include only the properties that you can filter by them. For example your display model is a list of Person objects and you allow to filter them by name, age, id. I would create something like:
public class PersonFilterModel
{
public int Page //the one from your example
public string Name {get;set;}
public int? age {get;set;}
public int? id {get;set;}
//you can add properties for filter type (starts with, less than, bigger than)
}
so your action will be
public ActionResult Home(PersonFilterModel filter)
If number of filtered properties in not too big you can specify them as action arguments one by one without creating a model:
public ActionResult Home(int page, string name, int? age, int? id)
Regarding the protection of query string: https and ssl are the standard ways that are used in most of the cases.
You could create two methods: One for GET and the other for POST.
[HttpPost]
public ActionResult Home(Partner partner)
[ActionName("Home")]
[HttpGet]
public ActionResult GetHome(Partner partner)
When you searching and passing parameters in the query string, it will go to the GetHome method, but the URL will still be the same.
I would like to first answer question b. I am assuming you want to prevent users from using a query string that will filter data in a way that shows data they should not be seeing. I don't think you need to secure the query string. Security should be handled server side. You shouldn't put anything in a query string that needs to be secured, for example a password.
Providing the ability to filter based on a query string is pretty powerful, especially if you do this in a dynamic way. I recently did this in a framework I wrote called Dynamic MVC (http://dynamicmvc.com). If you interested in using that you can install through nuget. It would provide the functionality your asking for with almost no coding on your part. However, if your not interested in Dynamic MVC you could take a look at how I did it on CodePlex (https://dynamicmvc.codeplex.com)
If you just want a quick summary, here is how I did it:
Parse the query string for any relevant properties for your model. You could use reflection to get the property names.
Once you have the relevant properties you can use dynamic linq to filter the data with a linq to entities query.

Returning view with model and query string

I am trying to return to view from a controller that has both querystring and model
return View("BillingReport.aspx/?report=" + fc["report_selector"], bilDet);
but this gives me a runtime error of page not found as it appends .aspx etc at the end of the url.
RedirectToAction() doesnt have an option to do it.
Is there a way to do it or does mvc3 limit us to using either a query string or a model
MVC does not support what you are looking for,
But I dont understand why do you want to Redirect To a URL with ModelValues.
Any redirection is a GET request, so you can construct the model and return View from that action.
View() expects a view name and model associated with it.
Redirect() or RedirectToAction() are used to redirect the url to another controller/action. So you can not pass a model.Even if you will try to pass model it will append model properties as querystring parameters.
Here is a reason why you would want use the model and the querystring: the querystring allows you to give user way to save URL with state information. The model allows you to pass a lot of non-flattened data around. So here is I think how to do this in MVC 5 (maybe does not work for older versions, but probably does):
Use 2 actions rather than 1 for the view. use first one to set querystring via RedirectToAction. Use second action to return model to the view. And you pass the model from the first action to the second action via session state. Here is example code:
public ActionResult Index(string email){
Session["stuff"]=Load(email);
return RedirectToAction("View1action", new { email = email, color = "red" });
}
public ActionResult View1action(string email){
return View("View1",(StuffClass)Session["stuff"]);
}
I agree with Manas's answer and if I were you I would consider changing the design if possible. As a side note, the following technique is possible:
TempData["bilDet"] = bilDet;
return RedirectToAction(....); // your controller, action etc.
On the action you can then retrieve your TempData. TempData will automatically be removed.
But also check out: ASP.NET MVC - TempData - Good or bad practice

MVC3 Partial Views

Still Learning MVC3, EF. For now I am connecting to MySql but I believe that will not be relevant. For simplicity, I decided to use one database for my test application and I have included a category to differentiate the data. For eg I have a news, events,info and pages categories. Now when it comes to listing contents in views for example at the homepage, I want to list latest 5 news items(news category), latest 5 events(events category), welcome text(info category). i have been able to create partialViews to list these at the different sections of the homepage. But I feel am doing this wrongly since in each of these PartialViews I am querying the same table over and over and just filtering with where cat=....in the LINQ query.
Can you please confirm if that should be the practice or there is a better way to do this.
You could do the following:
Controller:
public ActionResult Home()
{
IEnumerable<MyDateRecords> myData = LinqQueryToGetAllTheDataUnFiltered();
ViewData.Model = new MyViewData { MyData = myData; }
return View();
}
ViewModel class:
public class MyViewData
{
List<MyDataRecords> MyData { get; set; }
List<MyDataRecords> News { get { return MyData.Where(m => m.Category = "News"); } }
List<MyDataRecords> Events { get { return MyData.Where(m => m.Category = "Events"); } }
}
View:
#model MyViewModel
#Html.Partial("NewsPartial", Model.News)
#Html.Partial("EventsPartial", Model.Events)
Partial:
#model IEnumerable<MyDataRecord>
This way we only queried for the data once and just passed a different set to each partial
For an uncomplicated way of presenting this type of data, what you are doing is fine. You should look at the OutputCacheAttribute for any PartialView Method you use on your Controller.
This is pretty inefficient. But it's good that you noticed this because querying that database is often the bottleneck in any given request.
For starters you should retrieve that data into a dictionary or model and then pass it to the partial views to render similar to what Bassam outlined. Ideally, this should be taken care of in the Controller to stick to the MVC paradigm and then passed to the main view, which would then pass the appropriate data to the partial views.
Once you get more advanced/familiar with ASP.NET MVC, you can start looking into caching. I'd stay away from caching for right now since it get somewhat tricky if you have data that is rapidly changing since you need to start worrying about updating/synchronizing/etc.

MVC 3 form post and persisting model data

I think I'm missing some fundamentals on how MVC forms work. I have a search form on my home page that has five or six different fields a user can search on. So I have this POSTing to my results action just fine. The Result action looks like this:
[HttpPost]
public ActionResult Results(SearchModel model)
{
ResultsModel results = new ResultsModel();
results.ResultList = SearchManager.Search(model).ToList();
return View("Results", results);
}
I've simplified the above method for this post, but the idea is the same. So this all works fine. My results page shows up with the list of results and my user is at the following URL:
http://www.site.com/results
So...now I want to do something fairly common. I have two dropdown lists on the results page. "Sort by" and "# of results per page". How do I do that and send the full set of model data back to the controller so I can query with the new parameters? In reality, the SearchModel class has about 60 different fields. Potentially all of that data could be contained in the model. How do you persist that to a page "post back"?
This same question has me a little stumped about how to do paging as well. My paging links would go to a URL like:
http://www.site.com/results/2
But that assumes that we're responding to a GET request (I don't want 60 fields of data in the querystring) and that the model data is passed between GET requests, which I know isn't the case.
As I said, I think I'm missing some fundamentals about working with MVC 3, models and form posts.
Can anyone help point me in the right direction here? I'll be happy to edit/update this post as needed to clarify things.
EDIT: I also wanted to point out, I'd like to avoid storing the view model in a Session variable. This site will eventually end up being load balanced in a web farm and I'm really trying to avoid using Session if possible. However, if it's the only alternative, I'll configure another session state provider, but I'd prefer not to.
You can add your current SearchModel parameters to the route values for your form. Several versions of BeginForm allow you to pass in an object/RouteValuesDictionary.
#Html.BeginForm("Action", "Controller", new { SearchModel = Model }, FormMethod.Post)
This should pass-through your current SearchModel values so you can re-use them to get the next page. You need to have a controller action defined that will accept any current-page form values as well as the SearchModel.
I have not done this with form posts, but from what I have done and from what the docs say, this is where I would start. Of course, this also means that each of your page number "links" on the page will need to be doing posts. That is really inconvenient for users if they want to be able to use the Back button in the browser.
In this context, you can try to define a route that allows the page number to appear as a part of the URL -- "Action/Controller/{page}". However, I am not sure how that will work given that the form is doing a post.
Response to Comment:
Yeah, you can use Route Values to add the SearchModel to each page link, but as I said in the comment above, since the links will do a "get," your users will see the SearchModel serialized as a part of the link.
Either way, using Route Values is your answer to getting back your original SearchModel without using hidden fields, Session, or TempData.
Your SearchModel class needs to contain your search criteria and your results. Something like below. If you use a PagedList for your results then it will contain the current page, total pages, total items, etc. You can limit the amount of information in your page by only writing the search criteria that contain values.
public class SearchModel
{
public string Product { get; set; }
public string Sku { get; set; }
public string Size { get; set; }
public string Manufacturer { get; set; }
// etc...
public PagedList ResultsList { get; set; }
}
[HttpPost]
public ActionResult Results(SearchModel model)
{
model.ResultList = SearchManager.Search(model).ToList();
return View(model);
}
One of the options I'm coming up with here is to implement a distributed caching system that supports acting as a custom session provider (i.e. Memcached or Windows Server AppFabric), thereby allowing me to use TempData (and Session) in a load balanced environment like so:
[HttpPost]
public ActionResult Results(SearchModel model)
{
ResultsModel results = new ResultsModel();
results.ResultList = SearchManager.Search(model).ToList();
TempData["SearchModel"] = model;
return View("Results", results);
}
[HttpGet]
public ActionResult Results(int? page)
{
SearchModel model = (SearchModel)TempData["SearchModel"];
ResultsModel results = new ResultsModel();
results.ResultList = SearchManager.Search(model).ToList();
TempData["SearchModel"] = model;
return View("Results", results);
}
Any thoughts on this approach? Seems like a lot to have to go through just to get search parameters passed between requests. Or maybe I was just spoiled with this all happening behind the scenes with WebForms. :)
This seems to be another interesting option for Webforms spoiled guy ;) Persisting model state in ASP.NET MVC using Serialize HTMLHelper
Some kind of ViewState incarnation. It is part of MVC Futures . Not sure how long it is in Futures project and why it cannot get into main lib.

Categories