sfmskywalker's tutorial [routepart defunct?]

Topics: Troubleshooting, Writing modules
Apr 2, 2012 at 3:10 AM

Hi Everyone, I’m new to orchard and MVC in general, so trawling around the forums etc., it seemed sfmskywalkers tutorial:

http://skywalkersoftwaredevelopment.net/blog/writing-an-orchard-webshop-module-from-scratch-part-7

got the greatest praise for someone like myself to learn from; and yes, it has helped me tremendously in many regards [kudos to sfmskywalker].  However, I note it was written before 1.4’s release, and as I understand it, <routepart> is no longer supported?  Being as I’m using 1.4, I’ve hit a few snags in trying to deal with replacing the defunct code (practically any hurdle at my stage of development throws my understanding into chaos):

ShoppingCart.cshtml

<tbody>
            @foreach (var item in items) {
                 var product = (ProductPart) item.Product;
                 var contentItem = (ContentItem) item.ContentItem;
                 var routePart = contentItem.As<RoutePart>();
                 var title = routePart != null ? routePart.Title : "(no routepart attached)";
                 var quantity = (int) item.Quantity;
...

ShoppingCartController.cs

[AjaxOnly]
        public ActionResult GetItems()
        {
            var products = _shoppingCart.GetProducts();
            var json = new
            {
                items = (from item in products
                         let routePart = item.Item1.As<RoutePart>()
                         select new
                         {
                             id = item.Item1.Id,
                             title = routePart != null ? routePart.Title : "(No RoutePart attached)",
                             unitPrice = item.Item1.Price,
                             quantity = item.Item2
                         }).ToArray()
            };
            return Json(json, JsonRequestBehavior.AllowGet);
        }

Can anyone help me with the updated approach to the previous code? Thanking you kindly, Jane

Coordinator
Apr 2, 2012 at 4:53 AM

You can take a look at the source code for Nwazet.Commerce, as it is loosely based on this code and runs in 1.4. This being said, this code should not have been using the route part to determine the title, even with 1.3. The correct way to get the title is to do _contentManager.GetItemMetadata(item).DisplayText.

Apr 3, 2012 at 7:06 AM

thanks bertrandleroy, i used your suggested code for the AjaxOnly json:

title =  _contentManager.GetItemMetadata(item.Item1).DisplayText,
and that appears to work fine. However, I'm at a complete loss [even after inspecting the Nwazet code as to how to get the other ShoppingCart.cshtml code to work.  Here's my last attempt:

<tbody data-bind='template: {name: "itemTemplate", foreach: items}'>
                        @for (var i = 0; i < items.Count; i++) {
                            var item = items[i];
                            var product = (ProductPart) item.Product;
                            /*
                            var contentItem = (ContentItem) item.ContentItem;
                            var routePart = contentItem.As<RoutePart>();
                            var title = routePart != null ? routePart.Title : "(no routepart attached)";
                             */
                            var contentItem = (IContent) item.Product;
                            var title = item.Title;
                            
                            var quantity = (int) item.Quantity;
                            var unitPrice = product.Price;
                            var totalPrice = quantity*unitPrice;
                            <tr>
                                <td><a href="@Url.ItemDisplayUrl(contentItem)">@title</a>TEST</td>

title is always blank?

I wish to sort these niggles out so I can continue on with the tutorial ('I really dont want to drop it, as it's the first piece of literature on orchard that has made sense to me') and desperately wish to see it through.

Could you please help me with what maybe wrong? Thanking you kindly, Jane

Coordinator
Apr 3, 2012 at 7:40 AM

The Route part doesn't exist anymore in 1.4. In any case, that code should have been using GetMetadata(item).DisplayText on content manager to get the title.

Apr 3, 2012 at 8:18 AM

I'm not sure if I follow, is this what you mean:

var title = contentItem.ContentManager.GetMetadata(item).DisplayText;
VS states with ContentManger: 'Cannot resolve symbol', i'm not sure what @using namespace I should use if this is the correct code?

I also think I'm not returning my contentItem correctly, as:
@Url.ItemDisplayUrl(contentItem)
is blank as well? Thanks for your time and patience, Jane

Coordinator
Apr 4, 2012 at 4:29 AM

You need to inject a reference to an IContentManager. From the view, this can be done with @WorkContext.Resolve<IContentManager>(). But it's better to get the display text from the driver and stick it on the shape already computed so you don't have to do that nasty resolving from the view.

Apr 4, 2012 at 11:28 PM
Edited Apr 5, 2012 at 9:47 PM

Thanks BertrandLeyroy for all your efforts, very much appreciated.  I was really struggling to come to grips with some of the ‘terms’ and implementation of your proposed schema until sfmskywalker - who I for one, will be referring to as ‘smfsYoda’ from now on: ‘does the man ever sleep?’ -  broke it down into an easy digestible piece of tutorage; “outstanding!”.  So for those in the same learning predicament as I, here’s an update to sfmskywalkers (by the man himself) excellent tutorial that should see you through to section 11 without issue using Orchard 1.4 :) .  So once again, BIG thanks to smfsYoda: you’re an inspiration mate!
----------------------------------------------------------------------------------------------------------

The solution: you simply need a reference to the IContentManager in order to use it:

 

var contentItem = (IContent) item.Product;
var title = contentManager.GetItemMetadata(contentItem).DisplayText;

 So the question is: where did “contentManager” come from? From within a controller, we can use dependency injection, but how to get an instance to any service from a view?

 

The answer is: we use the Service Locator Pattern, which is implemented by the WorkContext.

We use the WorkContext property of the view to resolve any service that is registered with the service container (AutoFac). In case you’re wondering, every class that implements an interface that in turn implements IDependency, will be registered with AutoFac.

At the top of the view:

 

@{
    var contentManager = WorkContext.Resolve<IContentManager>();
}

And that’s it!

 

However, as Bertrand mentioned on the discussion thread, it’s preferable to load your data from your controller instead from the view, to adhere to the MVC principle of separation of concerns.

So, to better ourselves, let’s update the ShoppingCartController’s Index action (notice the line in bold):

 

[Themed]

        public ActionResult Index() {

            dynamic shape = _shapeFactory.ShoppingCart();

 

            var query = _shoppingCart.GetProducts().Select(tuple => _shapeFactory.ShoppingCartItem(

                Product: tuple.Item1,
//<b>
                ProductTitle: _contentManager.GetItemMetadata(tuple.Item1).DisplayText,
//</b>
                ContentItem: tuple.Item1.ContentItem,

                Quantity: tuple.Item2

            ));

 

            shape.ShopItems  = query.ToArray();

            shape.Total      = _shoppingCart.Total();

            shape.Subtotal   = _shoppingCart.Subtotal();

            shape.Vat        = _shoppingCart.Vat();

 

            return new ShapeResult(this, shape);

        }

 

 

We effectively added a property to our dynamic ShoppingCartItem shape called ProductTitle (the _contentManager is already a field of the controller class).

Next, we can update the ShoppingCart.cshtml view to use the ProductTitle property and remove the contentManager variable:

 

@for (var i = 0; i < items.Count; i++) {

   var item = items[i];

   ...

   var title = item.ProductTitle;



----------------------------------------------------------------------------------------------------------

Happy coding everybody! Jane

Apr 4, 2012 at 11:44 PM

Nice work guys! Sfmskywalker's tutorial is by far the best getting around for Orchard Padawans like myself ;)

Developer
Apr 5, 2012 at 12:25 PM

I am truly humbled by your praise, thank you. SfmYoda... Yes, I think I'll get used to that name quite easily. If only I could handle the pressure to live up to that ;)
In any case, I do sense a small disturbance in the Force. It seems that I made a typo somewhere in the process.
You see, the word "implanted" should really be "implemented".
Perhaps you could change that, for the sake of my good name?

Seems like I do need to sleep after all! :)

Thanks

Apr 15, 2012 at 10:45 PM

Hey, thanks loads for the above guys, really helped me out.

@sfmskywalker, are you planning to finish these brilliant blog posts -- if so, do you have an estimate of when you may do so?  Thanks for your efforts to date, you have made the introduction to Orchard a lot more enjoyable. Thanks Red

Developer
Apr 15, 2012 at 11:17 PM
Edited Apr 15, 2012 at 11:18 PM

@RedCrom I am planning on it, but work load has been crazy these past few months. I'm waiting for an opportunity where I need to implement the remaining parts in real world implementations, so that the sword slices both ways. Especially the integration with ERP part requires quite some investigation and time.

In the meantime, I started another set of small tutorials that discuss a variety of topics in isolation, as I come across them (currently there's just something small on IClock).

Anyway, during the next week I will be updating the tutorials for 1.4.
Should you have any specific topics you would like to see covered, just let me know and I will keep it in mind.

Thanks!

Apr 16, 2012 at 12:19 AM

Wow!  That was like reading text upon a mirror; 'I could not agree with your sentiments re:  Blog- API introduction enough.'  Being a novice, I’ve endured countless hours of frustration when it comes to learning/developing/implementing orchard due to – what appears to be – a rush to future development and features without the adequate follow-up documentation for the present; so all I can say is, thank God for people like yourself (for I know categorically, without finding your tutorial, I would’ve been another statistic who dropped orchard).  When it comes to future topics, I think your direction is spot on.  I am intrigued with your thoughts on the webshops schema, whether or not you would’ve changed anything had you had 1.4, then -- would’ve projections got a look in? Anyhow, I very much look forward to your future developments and can’t thank you enough for your existing works. Cheers Red 
P.S. +1 for SfmYoda name change as well.

Oct 21, 2013 at 4:59 PM
Edited Oct 21, 2013 at 5:02 PM
@sfmskywalker - I've been following along in your tutorials, and am running into this same problem in part 9. But it doesn't seem to be resolvable in quite the same manner. In Order.Created.cshtml, you have the following code:
@foreach (var detail in order.Details) {
    var productPart = productParts.Single(x => x.Id == detail.ProductId);
    var routePart = productPart.As<RoutePart>();
    var productTitle = routePart != null ? routePart.Title : "(No RoutePart attached)";
    ...
This is being called from OrderController.Create():
var shape = _shapeFactory.Order_Created(
    Order: order,
    Products: _orderService.GetProducts(order.Details).ToArray(),
    Customer: customer,
    InvoiceAddress: (dynamic)_customerService.GetAddress(user.Id, "InvoiceAddress"),
    ShippingAddress: (dynamic)_customerService.GetAddress(user.Id, "ShippingAddress")
);
I am not sure how to make this conform with your suggestion from above:
var query = _shoppingCart.GetProducts().Select(tuple => _shapeFactory.ShoppingCartItem(
    Product: tuple.Item1,
//<b>
    ProductTitle: _contentManager.GetItemMetadata(tuple.Item1).DisplayText,
//</b>
    ContentItem: tuple.Item1.ContentItem,
    Quantity: tuple.Item2
));
I fully admit to having to still bootstrap myself wrt ASP.Net MVC, Linq, and Orchard. I'm making tremendous progress, but I run into stuff like this and I shake my head in wonder at how much more I have to learn!

Any help will be greatly appreciated. I will cross-post this question on your blog, as well.
Coordinator
Oct 26, 2013 at 8:55 AM
RoutePart is gone, and has been replaced by autoroute and alias.
Developer
Oct 26, 2013 at 9:12 AM
Edited Oct 26, 2013 at 9:12 AM
Yeah, I haven't gotten around to update parts 9 through 11. But essentially, if you've been working through 1 to 8, just replace RoutePart with TitlePart when it comes to accessing the title, and use AutoroutePart when routing is involved (RoutePart used to provide both, but has been deprecated as of 1.4. Both its responsibilities have been split into two separate parts: TitlePart and AutoroutePart).

So in the sample you provided above, make updates as follows:
@foreach (var detail in order.Details) {
    var productPart = productParts.Single(x => x.Id == detail.ProductId);
    var titlePart = productPart.As<TitlePart>();
    var productTitle = titlePart != null ? titlePart.Title : "(No TitlePart attached)";