(Contrived) example collection urls:
VERB /leagues
VERB /leagues/{leagueId}/teams
VERB /leagues/{leagueId}/teams/{teamId}/players
The goal is to configure my associations and proxies to automatically target these urls.
Currently, I have a model for each of League, Team, and Player, with a hasMany association chain in the direction of
(League) ---hasMany--> (Team) ---hasMany--> (Player)
with the the Id's of the owning model used as the foreign key in the associated model.
(I.e. each Team has a leagueId which equals the id of it's owning League, and each Player has a TeamId which equals the id of it's owning Team)
One attempt at solving this can be found here. However, it didn't work for me. My initial attempt was overriding the buildUrl method of the proxies as:
buildUrl: function(request) {
var url = this.getUrl(request),
ownerId = request.operation.filters[0].value;
url = url.replace('{}', ownerId);
request.url = url;
return Ext.data.proxy.Rest.superclass.buildUrl
.apply(this, arguments);
},
Which works perfectly for a url resource depth of 1 (VERB /leagues/{leagueId}/teams). The problem is when I do something like:
League.load(1, {
callback: function(league) {
league.teams().load({
callback: function(teams) {
// all good so far
var someTeam = teams[0];
someTeam.players().load({
// problem here. someTeam.players() does not have a filter
// with leagueId as a value. Best you can do is
// GET/leagues/undefined/teams/42/players
});
}
});
}
});
What do I need to override in order to get all the information I need to build the url in the buildUrl methods? I don't want to manually add a filter each time - that sort of defeats the purpose and I might aswel set the proxy each time.
Thanks for your help
My current (working) solution I came up with was to modify the constructor of internal resources and manually add filters to the generated stores there. For example:
App.models.Team
constructor: function() {
this.callParent(arguments);
this.players().filters.add(new Ext.util.Filter({
property: 'leagueId',
value: this.raw.leagueId
}));
return this;
},
and that way all the filter property:value pairs are available in the buildUrl methods via request.operation.filters.
I'll accept this answer for now (since it works), but will accept another answer if a better one is suggested.
Related
I am working on a mvc core 3.1 application. Seo requirements are to show product name with main site instead of complete url.
My original url is
www.abc.com/Fashion/ProductDetail?productId=5088&productName=AliviaBlack&brandId=3
Requirement are
www.abc.com/alivia-black
I have tried following by using attribute routing
endpoints.MapControllerRoute(
name: "SpecificRoute",
pattern: "/{productName}",
defaults: new { controller = "Fashion", action = "ProductDetail", id= "{productId}" });
In view page
<a asp-route-productId="#product.ProductId"
asp-route-productName="#Common.FilterURL(product.ProductName)"
asp-route-brandId="#product.FashionBrand.FashionBrandId" asp-route="SpecificRoute">
Result is
www.abc.com/alivia-black?productId=5223&brandId=4
How to remove question mark and parameters after it.
First off, URIs need to be resilient. You say your current requirement is to have URIs like this:
www.abc.com/alivia-black
i.e.:
{host}/{productName}
That's a very bad URI template because:
It does not uniquely identify the product (as you could have multiple products with the same name).
It will break existing links from external websites if you ever rename a product or replace a product with the same name. And this happens a lot in any product database.
Because you're putting the {productName} in the "root" of your URI structure it means it's much harder to handle anything else besides viewing products (e.g. how would you have a /contact-us page? What if you had a product that was named contact-us?)
I stress that is is very important to include an immutable key to the entity being requested (in this case, your productId value) in the URI and use that as a primary-reference, so the productName can be ignored when handling an incoming HTTP request. This is how StackOverflow's and Amazon's URIs work (you can trim off the text after a StackOverflow's question-id and it will still work: e.g.
https://stackoverflow.com/questions/69748993/how-to-show-seo-friendly-url-in-mvc-core-3-1
https://stackoverflow.com/questions/69748993
I strongly recommend you read this article on the W3.org's website all about designing good URIs, as well as other guidance from that group's homepage.
I suggest you use a much better URI template, such as this one:
{host}/products/{productId}/{productName}
Which will give you a URI like this:
abc.com/products/5088/alivablack
Handling such a link in ASP.NET MVC (and ASP.NET Core) is trivial, just set the route-template on your controller action:
[Route( "/products/{productId:int}/{productName?}" )]
public async Task<IActionResult> ShowProduct( Int32 productId, String? productName = null )
{
// Use `productId` to lookup the product.
// Disregard `productName` unless you want to use it as a fallback to search your database if `productId` doesn't work.
}
As for generating URIs, as I recommend against using TagHelpers (e.g. <a asp-route-) because they don't give you sufficient control over how the URI is rendered, instead you can define a UrlHelper extension method (ensure you #import the namespace into your .cshtml pages (or add it to ViewStart):
public static class MyUrls
{
public static String ShowProduct( this IUrlHelper u, Int32 productId, String productName )
{
const String ACTION_NAME = nameof(ProductsController.ShowProduct);
const String CONTROLLER_NAME = "Products"; // Unfortunately we can't use `nameof` here due to the "Controller" suffix in the type-name.
String url = u.Action( action: ACTION_NAME, controller: CONTROLLER_NAME, values: new { productId = productId, productName = productName } );
return url;
}
}
Then you can use it like so:
View AliviaBlack
You can also make ShowProduct accept one of your Product objects directly and then pass the values on to the other overload (defined above) which accepts scalars:
public static String ShowProduct( this IUrlHelper u, Product product )
{
String url = ShowProduct( u, productId: product.ProductId, productName: product.ProductName );
return url;
}
Then you can use it like so (assuming product is in-scope):
#product.ProductName
I am new to VUE and trying to post a complex object with a list of tokens to an MVC c# route.
The network tab from my post request shows:
tokens[0][field]: 129
tokens[0][id]: 1
tokens[0][name]: MyPriority
tokens[0][operator]:
tokens[1][field]:
tokens[1][id]: 3
tokens[1][name]: -
tokens[1][operator]: -
The MVC controller shows the object, and it shows the number of tokens passed. However, the framework is not binding to the properties being passed in (field, id, name, operator). I am unsure if I need to amend the MVC part to bind, or change the JS object before posting.
the MVC controller is
public ActionResult CreateRule(Rule rule, List<Token> tokens)
the Js Code is:
methods: {
saveRule() {
let tokens = [];
debugger;
for (let i = 0; i < this.tokens.length; i++) {
var t = {};
t.field = this.tokens[i].field;
t.id = this.tokens[i].id;
t.name = this.tokens[i].name;
t.operator = this.tokens[i].operator;
tokens.push(t);
}
let newRule = {
id: this.rule.id,
type: this.rule.type,
name: this.rule.name,
field: this.rule.field,
tokens: tokens
};
this.$emit("save-rule", newRule);
},
It does not feel like a great way to have to copy all the parameters into a new object, so I assume this is not the best way. The vue tutorials had it cloning the data for posting to the server. In any case, it has not made any difference to the MVC reading the post data.
I have seen people try Stringify, but issues around datatypes the { ...rule } seemed to make no difference. I was hoping it was a quick answer as it must be possible, it is far from specialized or unique action!
I edited my codes
.factory('ProductsService',['$ionicSideMenuDelegate', '$http', 'ApiEndpoint', '$ionicTabsDelegate', '$ionicSlideBoxDelegate', '$stateParams', 'AuthService', function($ionicSideMenuDelegate, $http, ApiEndpoint, $ionicTabsDelegate, $ionicSlideBoxDelegate, $stateParams, AuthService){
var products = [];
var prod = [];
return {
GetProducts: function(){
return $http.get(ApiEndpoint.url + '/products', {}).then(function(response){
products = response.data.products;
return response;
});
},
GetProduct: function(productId){
angular.forEach(products, function(product, key){
$scope.prod = {}; //ERROR: $scope is not defined
if(product.id == productId){
prod = product;
return product;
}
})
return prod;
}
}
}])
..after I click the item that error appears..And the page doesnt show the details must shown..
I am not sure if I got your question.
Usally every View has its own Controller. So in your case one controller for the menu.html and for the productpage. Of course you can also use the same controller for both views.
If you want to use one controller for both views the controller can provide the data for both views.
If you want a controller for each view you have to share to data between the controllers. For sharing data between different controllers you can find a lot of help:
- Stackoverflow share data between controllers
- Thinkster using services to share data between controllers
In both solutions you had to call your api in this services. For that you should understand the angularjs concept of $q and promises:
Angularjs Documentation of $q
If could specifiy which data you want to call from which page to another, I can improve this answer.
EDIT:
Based on your comment I can add the following suggestion. In your products.html you want to display the details of a choosen product. Thats roughly said a master-detail-pattern. You can take a look at this: Master-Detail-Pattern.
You will have to change your state-config and add a state for the product-details. Something like that (you will have to change the code):
.state('productDetails', {
url: "/product/:id",
templateUrl: 'templates/product.html',
controller: 'yourCtrl'
});
In your controller you can get the given :id over the state-params:
var productId= $stateParams.id;
To achive that it works you also have to edit your menu.html. For every product you need a link which looks like:
{{product.name}}
This has to be wrapped in your ng-repeat. But that is all discribed on the given page.
Of course there are also other possibilities to do what you want.
Consider the below code. It works fine when getting data from the server. I have a custom data adapter (staffManagemetnService) which creates client-side entities from the json returned by the server.
However, if I make a call to executeQueryLocally, it fails and raises the following exception: Cannot find an entityType for resourceName: 'GetInternalResourcesByCompetence'. Consider adding an 'EntityQuery.toType' call to your query or calling the MetadataStore.setEntityTypeForResourceName method to register an entityType for this resourceName
var query = breeze.EntityQuery.from('GetInternalResourcesByCompetence').withParameters(parameters);
var result = self.manager.executeQueryLocally(query.using(dataService.staffManagementService));
if (result) {
return $q.resolve(result);
} else {
return this.manager.executeQuery(query.using(dataService.staffManagementService))
.then(function (data) {
return data.results;
})
.catch(function (err) {
logError('Restrieving resources days off failed', err, true);
});
}
I'm not sure what this means. Should it not work out-of-the-box since I've specifically asked breeze to use the custom dataAdapter ?
It's important to different between resource names and entity type names. Resource names are usually part of an endpoint and in plural (eg orders). Type names are typically singular (eg order).
Locally breeze cannot do much with the resource name, since it won't call the endpoint. Instead you ask for a certain entity type name.
You can map an entityType to a resourcename using the setEntityTypeForResourceName function:
metadataStore.setEntityTypeForResourceName('Speakers', 'Person');
See chapter "Resources names are not EntityType names" and the following chapters here: http://www.getbreezenow.com/documentation/querying-locally
I want to bind request parameters to a domain object so that I can add in updating of a domain object across a few controllers and actions.
At the minute this works:
def addEditBeerCommand = new AddEditBeerCommand()
Map carParams = params.findAll {
(
AddEditBeerCommand.metaClass.hasProperty(addEditBeerCommand, it.key)
&&
!( it.key in ["tastingDate", "price"])
)
}
addEditBeerCommand = new AddEditBeerCommand(carParams)
But I'll have to handle tastingDate (string to date conversion) and price (string to double), is there a way I can tap into the grails databinding?
The documentation is quite complete, and details all the options available for data binding within Grails. There are a lot of options available for customizing binding and this section of the documentation (along with all the rest) is worth the read. I'm sure you will find exactly what you need.
Per your comments it appears using bindObjectToInstance is what you are looking for.
import static org.codehaus.groovy.grails.web.binding.DataBindingUtils.bindObjectToInstance
...
def filters = {
namedFilter(controller:"myController", action: "myAction") {
// request params are available as params property of the filter class
def cmd = new SomeCommandObject()
bindObjectToInstance(cmd, params)
}
}