Parse dynamic parameters from url in action - asp.net-mvc-3

In test project I try to learn specification pattern and how use it in online store. Products specifications grouped by category e.g.: Color(blue, withe, etc), Height(100cm, 200cm, etc). Specifications added to product based on selected category in product. In all articles that I have read, specifications in the action are known as parameters. But if specifications added dynamically in my test project, I don't know how purse it. For instance, I have this controller action:
public ActionResult Products(string category,
string[] specificationNameValuePairs, int page = 1, int pageSize = 9,
string order = "Position, Name", string ordertype = "asc")
{
...
}
How can I bind specificationNameValuePairs in this action? Or I must redesign my project and use static specifications and for each specifications category create model for binding?

You must need to know about Url.Action method. Please check the below link, it may help.
Url Action

Related

How do I bypass the limitations of what MVC-CORE controllers can pass to the view?

From what I've read, I'm supposed to be using ViewModels to populate my views in MVC, rather than the model directly. This should allow me to pass not just the contents of the model, but also other information such as login state, etc. to the view instead of using ViewBag or ViewData. I've followed the tutorials and I've had both a model and a viewmodel successfully sent to the view. The original problem I had was that I needed a paginated view, which is simple to do when passing a model alone, but becomes difficult when passing a viewmodel.
With a model of
public class Instructor {
public string forename { get; set; }
public string surname { get; set; }
}
and a viewmodel of
public class InstructorVM {
public Instructor Instructors { get; set; }
public string LoggedIn { get; set; }
}
I can create a paginated list of the instructors using the pure model Instructor but I can't pass InstructorVM to the view and paginate it as there are other properties that aren't required in the pagination LoggedIn cause issues. If I pass InstructorVM.Instructors to the view, I get the pagination, but don't get the LoggedIn and as this is just the model, I may has well have passed that through directly.
An alternative that was suggested was to convert/expand the viewmodel into a list or somesuch which would produce an object like this that gets passed to the view
instructor.forename = "dave", instructor.surname = "smith", LoggedIn="Hello brian"
instructor.forename = "alan", instructor.surname = "jones", LoggedIn="Hello brian"
instructor.forename = "paul", instructor.surname = "barns", LoggedIn="Hello brian"
where the LoggedIn value is repeated in every row and then retrieved in the row using Model[0].LoggedIn
Obviously, this problem is caused because you can only pass one object back from a method, either Instructor, InstructorVM, List<InstructorVM>, etc.
I'm trying to find out the best option to give me pagination (on part of the returned object) from a viewmodel while not replicating everything else in the viewmodel.
One suggestion was to use a JavaScript framework like React/Angular to break up the page into a more MVVM way of doing things, the problem with that being that despite looking for suggestions and reading 1001 "Best JS framework" lists via Google, they all assume I have already learned all of the frameworks and can thus pick the most suitable one from the options available.
When all I want to do is show a string and a paginated list from a viewmodel on a view. At this point I don't care how, I don't care if I have to learn a JS framework or if I can do it just using MVC core, but can someone tell me how to do this thing I could do quite simply in ASP.NET? If it's "use a JS framework" which one?
Thanks
I'm not exactly sure what the difficulty is here, as pagination and using a view model aren't factors that play on one another. Pagination is all about selecting a subset of items from a data store, which happens entirely in your initial query. For example, whereas you might originally have done something like:
var widgets = db.Widgets.ToList();
Instead you would do something like:
var widgets = db.Widgets.Skip((pageNumber - 1) * itemsPerPage).Take(itemsPerPage).ToList();
Using a view model is just a layer on top of this, where you then just map the queried data, no matter what it is onto instances of your view model:
var widgetViewModels = widgets.Select(w => new WidgetViewModel
{
...
});
If you're using a library like PagedList or similar, this behavior may not be immediately obvious, since the default implementation depends on having access to the queryset (in order to do the skip/take logic for you). However, PagedList, for example has StaticPagedList which allows you to create an IPagedList instance with an existing dataset:
var pagedWidgets = new StaticPagedList<WidgetViewModel>(widgetViewModels, pageNumber, itemsPerPage, totalItems);
There, the only part you'd be missing is totalItems, which is going to require an additional count query on the unfiltered queryset.
If you're using a different library, there should be some sort of similar functionality available. You'll just need to confer with the documentation.

How to show all products in nopCommerce?

I would like to show all products without creating any new category and mapping to it.
Can any one help me?
Thanks in advance.
It's true; in order for a product to be displayed in nopCommerce, it must be assigned to a category. Your best bet is to create a top-level umbrella category, like "All Products", and add all of your products to that umbrella category.
As far as I know their must be a category associated with product.
You can create a plugin, map a route to it (for example map to 'allproducts' route), and create your own Controllers, Actions and Views within the plugin. Then insert in the main menu a link to the route by mean of
#Html.RouteLink(routeName, null) //or similar overloads
The plugin creation part is too huge to be described here. http://www.nopcommerce.com/documentation.aspx is a good start.
PS:/ Regarding routing, each plugin can implement a route registrar by implementing the "IRouteProvider" interface.
:)
You can do that by modifying the code. I have done it before. It is actually quite simple.
Modify the Category action of the Catalogue controller to receive a nullable CategoryId:
public ActionResult Category(int? categoryId, CatalogPagingFilteringModel command){
modify the action to not break because of this nullable paramters.
The most important part to modify is where you build the list of category Ids to filter:
var categoryIds = new List<int>();
if (category != null)
{
categoryIds.Add(category.Id);
}
if (_catalogSettings.ShowProductsFromSubcategories)
{
//include subcategories
categoryIds.AddRange(GetChildCategoryIds(category.Id));
}
The mothod _productService.SearchProducts will receive an empty list of category Ids and will not filter any products.

Achieving product/Apple-ipad in place of product/Index/23

I wasn't able to put the write words for the Title, so I explained what I'm looking after.
Currently, I am using following ActionLink code in View.
#Html.ActionLink(#product.Name, "Index", "Product", new { id = #product.Id }, null)
This code redirects to following action method in Product Controller
public ActionResult Index(int id)
{
Product product = pe.Products.Where(p => p.Id == id).First();
ViewBag.Title = product.Name;
ViewBag.Description = product.MetaDescription;
ViewBag.Keywords = product.MetaKeywords;
return View(product);
}
Now, what i want: instead of mysite.com/Product/Index/22 , my URL should be something like mysite.com/Product/Apple-ipad.
I know, I can use product Name instead of Id and pass it to the action method. But, this way i think that the queries will get slower since Id field is indexed but Name isn't. Is this the only option at my disposal. Let me know how will you handle this requirement.
Use the product name in the route instead of ID and put a nonclustered index on the product name column in the database.
That way you don't get a performance hit on the select (although you will get one on the insert/update/delete but I suspect those happen far less than the selects).
CREATE INDEX IX_[index_name]
ON [schema].[table_name] ([column_name]);
Another way I've seen used in a few places is using both the name and the id like mysite.com/Product/Apple-ipad/22. The name is not actually used by the code, and it's just there for SEO. One big disadvantage of this is that someone that does not like your site can put various urls leading to the same content all over the internet for google to find. Google doesn't like that so your site is penalized and you are worse than before.

How do I display customer specific information avoiding using xslt?

My place of work currently maintains a website for several customers which is written using classic asp. Each customer requires specific parts of the website to be written specifically to them.
For example, customer A requires an Address to be input, displayed and stored in the following format:
Address Line 1
Address Line 2
Address Line 3
Address Line 4
Address Line 5
Postcode
whereas customer B requires the Address to be input, displayed and stored as:
Street
Town
City
Postcode
and so forth...
Therefore, my place of work took the path of storing the data as xml in the database and using xsl (of which I currently know little) to transform the data to html.
So if we require information from the user via a html form, the xml is transformed using xsl. The user then enters the information and submits the data via the form. An asp page is then used to validate the data. This asp page is specific to the xsl page used to display the form. Therefore, we are now in a postion where for each customer we have many xsl pages and many customer specific asp pages (where much of the code is duplicated).
I have been asked to move the site over to asp.net mvc3 and to remove much of the duplication and was wondering what would be the best way to cater for this customer specific field functionality. My preference would be to keep the data stored as xml as the database layer is accessed using com components which I would like to reuse without changing.
I have read that I could keep the xsl pages and develop an xslt view engine to display the html. However, I am not sure how I would validate the data when the user submits the form?
What would be the best way to display customer specific fields if I was to remove the xsl completely? Or would I have to have customer specific views and view models?
Any thoughts would be much appreciated.
If you really want to use MVC's built in validation / model functionality I think your best bet would be to use the XmlSerializer or use DataContracts to develop something that serializes to and from your XML (once its retrieved from the COM objects, so you don't need to re-code those), then you can use those classes as Models for MVC and use the standard data annotations for taking advantage of the richer MVC model functionality and skip the XSL step entirely.
To couple this with a custom specific view, what I typically do is override the default view engine to have one that actually will try names that are more specific to the customer/object and then fallback to a general one.
This view engine would allow you to pass a view to pass a view name (ie. FallbackViewEngine.BuildViewName("General", "Customer Name") and it would look for "General.Customer Name.cshtml" first and then "General.cshtml" as a fallback. This way you can actually use customer specific views in your view folder.
public class FallbackViewEngine : RazorViewEngine
{
const string NameSeparator = "==";
const string FileSeparator = ".";
public static string BuildViewName(string root, params string[] fallbackList)
{
if (string.IsNullOrWhiteSpace(root)) throw new ArgumentNullException("root");
if (fallbackList == null) throw new ArgumentNullException("fallbackList");
var sb = new StringBuilder(root);
foreach (var s in fallbackList)
{
if (string.IsNullOrWhiteSpace(s)) continue;
sb.Append(NameSeparator);
sb.Append(s);
}
return sb.ToString();
}
public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
{
if (string.IsNullOrWhiteSpace(viewName)) throw new ArgumentNullException("viewName");
var names = viewName.Split(new string[] {NameSeparator}, StringSplitOptions.None);
var searched = new List<string>();
//iterate from specific to general
for (var i = names.Length; i >= 1; i--)
{
var result = base.FindView(controllerContext, string.Join(FileSeparator, names, 0, i), masterName, useCache);
if (result.View != null)
{
return result;
}
else
{
searched.AddRange(result.SearchedLocations);
}
}
return new ViewEngineResult(searched);
}
}

ASP.NET MVC 3 areas and DDD aggregate roots

I'm building a site and am considering using areas to cover a similar scenario to the one I'm about to describe.
I currently have a site with 4 sections, lets call these Create, Manage, Section 3 and Section 4
Create and Manage are actions on the domain object that I'm working with. The domain object has a number of collections of sub objects that relate to it. These need to be created and managed as well.
I am using Products as an example so as not to give anything away but it doesn't quite fit the same domain - so please don't say "Why don't you have a Products section"
My current implementation has a ManageController which has Actions like Categories, Category, ProductsForCategory
I'm thinking I need areas, however, some URLs will need to be scoped so I want
/Manage/Category/8/Products
/Manage/Category/8/Product/1
Is this possible using Areas? Do I need to set up new routing rules?
Would my CategoryController have 2 parameters on the action e.g.
public ActionResult Product(int categoryId, int productId)
{
//get category
var cat = GetCategory(categoryId);
//get product
var product = cat.Products.SingleOrDefault(x=>x.Id == productId);
if(product == null)
return RedirectToAction("Index","Manage");
return View(product);
}
Then I would have a routing rule that passed in the category id?
Is my thinking on this correct?
This is possible with Areas.. although it's my understanding that areas are mainly recommended for structuring your code into a meaningful folder structure to deal with large-ish MVC apps, whereas it looks like you want to use it for achieving nested routes?
To map your nested route to /Manage/Category/8/Product/1 you could create your "Manage" area, and then add a route like so:
context.MapRoute(null,
"Manage/{controller}/{categoryId}/{action}/{id}",
new
{
action = "Product",
id = "1",
categoryId = "2"
});
You then create an action method to accept those params:
public ActionResult Product(string categoryId, string id)
However, your question talks about aggregate DDD roots, so I suspect I've only answered part of the question?

Resources