MVC 5 routing issue with new url for subcategories - asp.net-mvc-5

In my productcontroller I have two actionresult returning methods:
[Route("Shop/{brand}/{category}/{subcategory?}/{page:int?}")]
public ActionResult Index(string brand, string category, string subcategory, int? page, SortOptions currentSort = SortOptions.SinceDesc)
{ //...
and
[HttpPost]
[Route("Shop/{brand}/{category}/{subcategory?}/{page:int?}")]
public ActionResult Index(ProductsViewModel pvm)
{ //...
And this is my razor view:
#using (#Html.BeginForm("Index", "Products", FormMethod.Post))
{
#Html.DropDownListFor(x => x.SubCatID, Model.SubCategoriesSelectList, new { #class = "multiselect" })
}
When I submit the page it hits the httppost method but the url is still: Shop/nike/shoes even when I selected the subcategory runningshoes from the dropdown.
I would like to have urls like:
shop/nike/shoes
shop/nike/shoes/runningshoes
Being more of a webform-guy I am having a hard time to navigate to new url's and using viewmodel properties as parameters.
edit posted my UI:
to explain my ui:
the first dropdown should get to a subcategory. for instance: shop/nike/shoes/runningshoes
The second should post 'back' to sort the products.
The price slider should post back because it should filter. (would filter client side if there was no paging)
The paging should get so you can deeplink to a certain page: shop/nike/shoes/runningshoes/page2 etc.

In your BeginForm(...) you end up having to pass a route values dictionary with Subcategory = "runningshoes"
This does mix values being passed by GET, aka in the querystring through the route values dictionary, and POST, which will be the values from the form, but will accomplish what you are trying to do. More can be read about the BeginForm(..) overload Here on MSDN
You should end up with:
#using (#Html.BeginForm("Index", "Products", new { subcategory = "runningshoes" }, FormMethod.Post))
{
#Html.DropDownListFor(x => x.SubCatID, Model.SubCategoriesSelectList, new { #class = "multiselect" })
}
EDIT
Just realized you want the value from the form post to be in the QueryString on the response. Instead of returning the view directly from your MVC method for the Form Post what you could possibly do is a return RedirectToAction("ActionName", "ControllerName", new { subcategory = request.SubCategory }); which you would have an action that would support this redirect specifically.
Additional information on redirect to action can be found here on MSDN

Related

Shopware 6 - form field for entity extension in administration

This is a Shopware 6 question. I want to extend the PromotionEntity by adding a max_budget field and display it as a form field in administration. Currently only max_redemptions_global and max_redemptions_per_customer fields exist. The max_budget field should appear in administration right under max_redemptions_global and max_redemptions_per_customer fields. max_budget would act similar to the other two. If the discount for this promotion from total orders combined reaches the value from max_budget, then the promotion does not work anymore.
So I created an entity extension as following:
class PromotionMaxBudgetExtension extends EntityExtension
{
public function extendFields(FieldCollection $collection): void
{
$collection->add(
new OneToOneAssociationField('maxBudget', 'id', 'promotion_id', PromotionMaxBudgetDefinition::class, false)
);
}
public function getDefinitionClass(): string
{
return PromotionDefinition::class;
}
}
Then the definition of the extension:
class PromotionMaxBudgetDefinition extends EntityDefinition
{
public const ENTITY_NAME = 'promotion_max_budget';
public function getEntityName(): string
{
return self::ENTITY_NAME;
}
public function getEntityClass(): string
{
return PromotionMaxBudgetEntity::class;
}
protected function defineFields(): FieldCollection
{
return new FieldCollection([
(new IdField('id', 'id'))->addFlags(new Required(), new PrimaryKey()),
(new FkField('promotion_id', 'promotionId', PromotionDefinition::class)),
(new IntField('max_budget', 'maxBudget')),
(new IntField('left_budget', 'leftBudget')),
(new OneToOneAssociationField('promotion', 'promotion_id', 'id', PromotionDefinition::class, false))
]);
}
}
In order to display the field I had to override the sw-promotion-v2-detail-base. So I modified the sw-promotion-v2-detail-base.html.twig like this:
{% block sw_promotion_v2_detail_base_general_max_uses_customer %}
{% parent %}
<sw-number-field
v-model="??????"
class="sw-promotion-v2-detail-base__field-max-uses-per-customer"
number-type="int"
:label="Max budget"
:placeholder="$tc('sw-promotion-v2.detail.base.general.maxUsesPerCustomerPlaceholder')"
:disabled="!acl.can('promotion.editor')"
allow-empty
/>
{% endblock %}
So, my question would be, how would I tell Shopware this field is for the max_budget entity extension, so that it would save the changes I make to it, at onSave ? Seems like even though the entity extension exists, it is not fetched as an association for the promotion (even with autoload true).
Ok, so I saw how this needs to be done to work perfectly.
Firstly I had to override sw-promotion-v2-detail component and add this js:
Shopware.Component.override('sw-promotion-v2-detail', {
computed: {
promotionCriteria() {
const criteria = this.$super('promotionCriteria');
criteria.addAssociation('maxBudget');
return criteria;
}
},
});
Secondly, in the sw-promotion-v2-detail-base.html.twig the field will look like this:
<sw-number-field
v-if="promotion.extensions.maxBudget"
v-model="promotion.extensions.maxBudget.maxBudget"
class="sw-promotion-v2-detail-base__field-max-budget"
number-type="int"
:label="$tc('vouchers.maxBudget')"
:placeholder="$tc('sw-promotion-v2.detail.base.codes.individual.generateModal.labelPrefix')"
:disabled="!acl.can('promotion.editor')"
allow-empty
/>
Notice the v-model changed to promotion.extensions.....
So the answer would be this:
To add a Shopware 6 form field for an entity extension in admin you have to add the entity association for the extension to the component where the extended entity is fetched (in this case sw-promotion-v2-detail).
After that you can use the extension in twig with promotion.extensions.extensionName....

Yii2 CRUD and Pagination

I am using Yii2 with Pjax for index/gridview listing. using pjax pagination , search all working fine without postback to server.
My problem starts now,
suppose i am on page number 2, i have clicked on edit record of that 2nd page list, i reach to update view, i have done changes and saved, now i am redirected to view , now i clicked on index link from breadcrumbs.
i want to reach to page number 2 of index rather then 1st page.
Traditional process for this is get refereeing page params an append that in breadcrumbs.
But is there any simple approach to this problem where i can write few lines of code and its applied to every where in backend?
Thanks for reading.
For remembering grid filter, pages i use yii2-grid-view-state
If you need to store page only, isn't it quite easy to pass page param into your view url (<model/view>) like <model>/view?id=<id>&page=<page>?
in your index.php view, edit your ActionColumn as follow:
[
'class' => 'yii\grid\ActionColumn',
'urlCreator' => function ($action, $model, $key, $index) {
return \yii\helpers\Url::to([$action, 'id' => $model->id, 'page' => Yii::$app->request->getQueryParam('page', null)]);
},
],
As you can see, I'm getting page param from request url and pass it to models' action buttons (to all buttons, but in your question it would be enough for view button of course)
And when you click to view model, in our Controller we need to get that page value and pass it to our view.php view (in order to place it in breadcrumbs).
Our ModelController:
public function actionView($id, $page = null)
{
return $this->render('view', [
'model' => $this->findModel($id),
'page' => $page,
]);
}
And finally view.php view will get the page value, and populate the index url (if not null):
/* #var $page int */
$this->title = $model->name;
$this->params['breadcrumbs'][] = ['label' => 'Index', 'url' => ['index', 'page' => $page]];
So when you press the Index breadcrumb, it will open the page, where you entered from.
Some advantages againts session implementation (#user1764431 solution):
Each of your tab can return to it's own last page
Simple and stupid solution
Some disadvantages:
If you need to store some filter params, url could stretch very long
Just add following Code in every controller of actionIndex() rest all things will take care
$searchModel = new CentervideosSearch();
$dataProvider = $searchModel->search(Yii::$app->request->queryParams);
/*Code insertion block begin*/
$params = Yii::$app->request->queryParams;
if (count($params) <= 1)
{
$params = Yii::$app->session['customerparams'];
if(isset(Yii::$app->session['customerparams']['page']))
$_GET['page'] = Yii::$app->session['customerparams']['page'];
if(isset(Yii::$app->session['customerparams']['per-page']))
$_GET['per-page'] = Yii::$app->session['customerparams']['per-page'];
}
else
{
Yii::$app->session['customerparams'] = $params;
}
$dataProvider = $searchModel->search($params);
/*Code insertion block Ends*/

MVC 5 Pretty URL

Hi there
I'm working on a system where I have been asked to change the URL in the address line.
To take the short version, we have a profile page for all our lorries, let's say we have a lorry named SuperTransport, so I've made a routing that allows us to access his profile page by typing http: //app.fragtopgaver.dk/SuperTransport, problems are now that when you come to his profile page, something else says in the URL, which says http://app.fragtopgaver.dk/getindex/?slug=supertransport
I need that it still says http://app.fragtopgaver.dk/SuperTransport in the URL when landing on the page.
My routing looks like this:
routes.MapRoute(
name: "slug",
url: "{slug}",
defaults: new { controller = "Home", action = "show" },
constraints: new { slug = ".+" });
And in my Home Controller
public async Task<ActionResult> Show(string slug)
{
return RedirectToRoute(ProfileControllerRoute.GetIndex, new { slug = slug});
}
and my Profile Controller
[Route("GetIndex", Name = ProfileControllerRoute.GetIndex)]
public ActionResult Index(int? page, string slug = null)
Hope someone can give me a hint of what i can do about this.
The Answer was really simple, just had to add
[Route("{slug}")]
to the controller

BeginForm and AttributeRouting [duplicate]

I'm creating a form for a DropDown like this:
#{
Html.BeginForm("View", "Stations", FormMethod.Get);
}
#Html.DropDownList("id", new SelectList(ViewBag.Stations, "Id", "Name"), new { onchange = "this.form.submit();" })
#{
Html.EndForm();
}
If I choose a value from my dropdown I get redirected to the correct controller but the URL is not as I would like to have it:
/Stations/View?id=f2cecc62-7c8c-498d-b6b6-60d48a862c1c
What I want is:
/Stations/View/f2cecc62-7c8c-498d-b6b6-60d48a862c1c
So how do I get the id= querystring parameter replaced by the more simple URL Scheme I want?
A form with FormMethod.Get will always post back the values of its form controls as query string values. A browser cannot generate a url based on your route configurations because they are server side code.
If you really wanted to generate /Stations/View/f2cecc62-7c8c-498d-b6b6-60d48a862c1c, then you could use javascript/jquery to build your own url and redirect
#using (Html.BeginForm("View", "Stations", FormMethod.Get))
{
#Html.DropDownList("id", new SelectList(ViewBag.Stations, "Id", "Name"))
}
var baseUrl = '#Url.Action("View", "Stations")';
$('#id').change(function() {
location.href = baseUrl + '/' $(this).val();
});
Side note: Submitting on the .change() event is not expected behavior and is confusing to a user. Recommend you add a button to let the user make their selection, check it and then submit the form (handle the button's .click() event rather that the dropdownlist's .change() event)
Remove "id"
from
#Html.DropDownList("id", new SelectList(ViewBag.Stations, "Id", "Name"), new { onchange = "this.form.submit();" })

servicestack Razor view with request and response DTO

I'm having a go with the razor functionality in service stack.
I have a razor cshtml view working for one of my response DTO's.
I need to access some values from the request DTO in the razor view that have been filled in from some fields from the REST route, so i can construct a url to put into the response html page and also label some form labels.
Is there anyway of doing this? I don't want to duplicate the property from the request DTO into the response DTO just for this html view. Because i'm trying to emulate an existing REST service of another product, i do not want to emit extra data just for the html view.
eg
http://localhost/rest/{Name}/details/{Id}
eg
#inherits ViewPage<DetailsResponse>
#{
ViewBag.Title = "todo title";
Layout = "HtmlReport";
}
this needs to come from the request dto NOT #Model
link to user
link to user details
If you want to access the Request DTO it needs to be referenced by either by adding the Request to the Response DTO (which you don't want to do), so the other option is to add it to the IHttpRequest.Items Dictionary which is the preferred way to pass data between your filters and services.
public class MyService : Service {
public object Any(MyRequest request) {
base.Request.Items["RequestDto"] = request;
return MyResponse { ... };
}
}
Then in your view:
#{
var myRequest = (MyRequest)base.Request.Items["RequestDto"];
}
Wrapping Re-usable functionality in Request Filters
If you find you need to access the Request DTO in your views a lot, then rather than manually assigning it in each service, you can create a Request Filter Attribute or if you want it assigned all the time in a Global Request Filter.
public class SetRequestDtoAttribute : RequestFilterAttribute {
public override void Execute(
IHttpRequest req, IHttpResponse res, object requestDto)
{
req.Items["RequestDto"] = requestDto;
}
}
Then you can add this behavior by decorating the [SetRequestDto] attribute on different levels of granularity on either an Action, Service, Request DTO or base class.

Resources