Drivers, Controllers and Strongly Typed Views

Topics: Writing modules
Jul 18, 2011 at 11:45 PM

I am aware that Drivers view shapes and pass dynamic data, wheras Controllers often pass strongly typed models:

The model item passed into the dictionary is of type 'IShapeProxy98d5ddaa1b74450794dbb9e5ebb58db0', but this dictionary requires a model item of type...

Can I pass a strongly typed (non dynamic) model from a Driver to a View (e.g. @model...)?

Coordinator
Jul 19, 2011 at 12:15 AM

I don't think you can, but you can stick a statically-typed object onto the shape and cast that from the view.

Example, in your shape you do something like myObject: myObject, and in your view you do var myObject = (TypeOfMyObject)Model.myObject; You then have strongly-typed access to it.

Jul 19, 2011 at 2:56 AM
Edited Jul 19, 2011 at 3:51 AM

I am running into the same problem.  I am trying to write a front-end contact form but I cannot bind it to the model.

I am considering using a iframe to do the form so I can use model binding on the form.  Any help would be appreciated.

Jul 19, 2011 at 5:54 AM

Thanks Bertrand. Further to this, my views list objects and use html.ActionLink  to view the details (via Routes and Controllers). These by default (with [Themed]) appear in the Content Zone. What is the best way to move the default content to a different layout in the Controller? Placement.info?

Note, I saw some code you wrote in discussion 261644 which included:

        public ActionResult DemoAction()
        {
            Services.WorkContext.Layout.Footer.Add(Shape.DemoShape(Text: "Hello!"));
            return View("DemoView");
        }

Not sure this is part of the solution.

Coordinator
Jul 20, 2011 at 12:44 AM

@sgettis: ew. Did you try what I was suggesting?

@pcs: placement only works for dispatching content parts into local zones, not for arbitrary shapes. To put stuff in other parts of the page, you need to access workContext.Layout.Zones["NameOfTheZone"].Add(shape, "1")

Aug 8, 2011 at 3:28 PM
Edited Aug 8, 2011 at 3:29 PM

I think I ran into similar problem.

@bertrandleroy, I'm not sure I understand how I can use workaround from your comment above (e.g. var myObject = (TypeOfMyObject)Model.myObject)

In my part view I wanted to use code like this (this is view for front-end, I actually dont have/need Editor part, at least now)

<div class="editor-label">
    @Html.LabelFor(model => model.Longitude)
  </div>
  <div class="editor-field">
    @Html.TextBoxFor(model => model.Longitude)
    @Html.ValidationMessageFor(model => model.Longitude)
  </div>

Like it is used for Editor part in documentation article about creating Content Part (http://www.orchardproject.net/docs/Writing-a-content-part.ashx)

 

But then I ran into problem with casting error message

The model item passed into the dictionary is of type 'IShapeProxy98d5ddaa1b74450794dbb9e5ebb58db0', but this dictionary requires a model item of type...

Am I'm trying to do something completely wrong?

Ultimately I'm trying to create 2 custom forms which will be calling WCF services from another MVC application (not orchard based)

And I want these 2 forms to be Widgets.

So I want to utilize MVC validation via Model attributes.

Can you please give me right direction.

Thanks

Aug 8, 2011 at 3:36 PM
Edited Aug 8, 2011 at 3:36 PM

I found another topic (http://orchard.codeplex.com/discussions/233941) where you saying

You need to choose between using a strongly typed view model like lots of editor views are still doing, or use shapes. If you use shapes, you'll need to use the non-strongly-typed overloads of the Html helpers, or shape templates, or shape methods for rendering.

So I guess my question would be how to write module defining Widgets which not using shapes?

Coordinator
Aug 8, 2011 at 7:38 PM

For the front-end, I don't think you can. Then again, you don't need the Lambda-based Html helpers there. Also, what I said was incomplete, you can actually use the Lambda helpers if you have a strongly typed object as one of the fields of the shape, if you cast them properly. Or, as I said, just use the non-strongly-typed helpers, they work just fine.

Aug 9, 2011 at 6:52 AM
Edited Aug 9, 2011 at 6:54 AM

you can actually use the Lambda helpers if you have a strongly typed object as one of the fields of the shape, if you cast them properly

Can you give simple example of this, sorry for asking, but I'm not very experienced yet in Orchard, but trying my best to improve in this area. thanks

I could use non-strongly typed helpers, but then I guess I will loose for example ability to create correct Labels for my input controls for which I have properties in my ViewModel with attribute DisplayName

Coordinator
Aug 10, 2011 at 12:01 AM

Never mind that, it doesn't exactly work. One thing you can do though is set the Model property on your shape, and that will set what is used as Model in the view. You can then declare the type of the model on top of your razor file the way you would usually.

Aug 10, 2011 at 3:32 PM
bertrandleroy wrote:

Never mind that, it doesn't exactly work. One thing you can do though is set the Model property on your shape, and that will set what is used as Model in the view. You can then declare the type of the model on top of your razor file the way you would usually.

I've tried this, but doesnt appears to work, or I did not understand what you are suggesting

So I've put this in my driver class

public class ContactUsDriver : ContentPartDriver<ContactUsPart>
    {
        protected override DriverResult Display(ContactUsPart part, string displayType, dynamic shapeHelper)
        {
            return ContentShape("Parts_ContactUs", () => shapeHelper.Parts_ContactUs(Model: part));
        }
    }

And then I have this as my view

@model XXX.Models.ContactUsPart
           
@using Orchard.Utility.Extensions;

@{using (Html.BeginForm("send", "contactUs", new { area = "XXX" }, FormMethod.Post, new { @class = "contact-form" }))
  {
    @Html.ValidationSummary()
    
	<fieldset>
		<div class="row">
            @Html.LabelFor(m => m.FirstName);
            @Html.TextAreaFor(m => m.FirstName);
            @Html.ValidationMessageFor(m => m.FirstName);
        </div>

	    <div class="row get-estimates">
            <input class="get-estimates contact-us-button" type="submit" value="@T("Contact Us")" />	
        </div>
	</fieldset>
    
    @Html.Hidden("ReturnUrl", Context.Request.ToUrlString())
    
    @Html.AntiForgeryTokenOrchard() 
    
  }
}

And it does appears to work. I'm getting this exception

The model item passed into the dictionary is of type 'IShapeProxy674961fc5cfc4f5d94549fd4941a65b8', but this dictionary requires a model item of type 'ChristmasLightLeads.Models.ContactUsPart'.
Any suggestions will be appreciated :)

Coordinator
Aug 10, 2011 at 6:14 PM

Yeah, you got it, and I'm pretty sure I've used that technique before, so I'm not sure why it doesn't work. So did you try the non-strongly-typed helpers?

Coordinator
Aug 10, 2011 at 6:17 PM
Edited Aug 10, 2011 at 6:18 PM

oh, and another idea may be to have an interface that fits your shape. If you declare the model in the view to be of type that interface, the Clay shape will probably be cast to the interface and it may start working. (but I would also encourage you to try the non-strongly-typed helpers, that's by far the easiest solution).

Aug 10, 2011 at 6:18 PM

Yes, I tried non-strongly-typed helpers, and it worked.

But in that case I have to use "old style" controller method, instead of method accepting my view model.

Coordinator
Aug 10, 2011 at 6:24 PM

I'm sorry, I don't understand that last sentence. Can you explain?

Aug 10, 2011 at 6:37 PM

Well. When I have strongly-typed view I could write such method on my controller

[HttpPost]
public ActionResult Send(ContactUsPart model)

and have strongly typed model object already populated with data entered by user.

By "old style", I mean non-strongly-typed viewes, where you need to create model object yourself (Controller method do not accept it as parameter), and update it with data from forms with the following method

TryUpdateModel(model, new[] { "FirstName", "Email", "Phone", "Address", "City", "StateCode", "ZipCode", "IssueTypeCode", "Description"});

does it makes sense now?

Coordinator
Aug 10, 2011 at 7:25 PM

I'm not sure that's true. Model binding should work based on just what the posted form data is, and from what I understand it can't know about anything else.

Aug 11, 2011 at 2:21 AM

Can you clarify what my part of the message above exactly looks wrong for you?

Thanks

Coordinator
Aug 11, 2011 at 3:07 AM

I don't think it's true that you need a strongly typed view for the controller action to be able to accept strongly typed parameters. If the model binder fan find the form fields it needs, it will map that just fine.

Sep 11, 2011 at 1:29 PM
Edited Sep 11, 2011 at 1:31 PM

I ran into the above problem as well.  The error occured in the controller in the POST.  The return from UpdateEditor is a shape, which is a dynamic object.  If the ModelState in invalid (input validations failed) then originally I was passing the shape. which is assigned to the model var, directly to the view.  This was causing the error.  After inspection of the shape, I noticed that it contained a Content Item, which contained a list of Parts.  One of the Parts was of the type I needed to send to my view.  I simply iterated through the list, got the part I was looking for, and passed it to the view.

        // Create
        public ActionResult Create()
        {
            var newTestimonial = _contentManager.New<TestimonialPart>("Testimonial");
            return View(newTestimonial);
        }


        [HttpPost, ActionName("Create")]
        public ActionResult CreatePOST() 
        {
            // create testimonial content item
            var testimonialContentItem = OrchardServices.ContentManager.New("Testimonial");
            // persist (?) a content item
            OrchardServices.ContentManager.Create(testimonialContentItem);
            // update testimonial content item parts through drivers, changes will be automatically propagated to the DB
            var model = OrchardServices.ContentManager.UpdateEditor(testimonialContentItem, this);

            if (!ModelState.IsValid)
            {
                OrchardServices.TransactionManager.Cancel();
                TestimonialPart testimonialPart = _contentManager.New<TestimonialPart>("Testimonial");
                //ContentItem item = (ContentItem)model.ContentItem;
                foreach (Object part in model.ContentItem.Parts)
                {
                    if (part is TestimonialPart)
                    {
                        testimonialPart = (TestimonialPart)part;
                    }
                }
                return View(testimonialPart);
            }

            _notifier.Information(T("Testimonial created"));
            return RedirectToAction("Index");
        }

 

Dec 2, 2011 at 10:52 AM

I also got the same proxy error.This issue is going on for long time.Please anyone suggest a solution.

Dec 2, 2011 at 4:05 PM
sureshrajamani wrote:

I also got the same proxy error.This issue is going on for long time.Please anyone suggest a solution.

Do you have any more details, such as the code that causes the error for you?

Dec 7, 2011 at 1:14 PM

I had the same problem. The error message 

The model item passed into the dictionary is of type 'IShapeProxy98d5ddaa1b74450794dbb9e5ebb58db0', but this dictionary requires a model item of

seems to come from the @Display(Model.Contents) in the widget.cshtml (when adding my widget to the Contents zone).

 

Following the "Writing a content part" I created my LocalStorageDriver like this:

 

    public class LocalStorageDriver : ContentPartDriver<LocalStoragePart>
    {
        protected override DriverResult Display(LocalStoragePart part, string displayType, dynamic shapeHelper)
        {
            return ContentShape("Parts_LocalStorage", 
                () => shapeHelper.Parts_LocalStorage(Version: part.Version));
        }

        //GET
        protected override DriverResult Editor(LocalStoragePart part, dynamic shapeHelper)
        {
            return ContentShape("Parts_LocalStorage_Edit",
                () => shapeHelper.EditorTemplate(
                    TemplateName: "Parts/LocalStorage",
                    Model: part,
                    Prefix: Prefix));
        }
        //POST
        protected override DriverResult Editor(LocalStoragePart part, IUpdateModel updater, dynamic shapeHelper)
        {
            updater.TryUpdateModel(part, Prefix, null, null);
            return Editor(part, shapeHelper);
        }
    }

The Editor method works fine, but the Display method gives the error message. Since the Editor worked I just let the Display return the same shape:

        protected override DriverResult Display(LocalStoragePart part, string displayType, dynamic shapeHelper)
        {
            return Editor(part, shapeHelper);
        }

 

Which works fine for me.

Or add a folder  View\DisplayTemplates\Part and put the view in there using:

        protected override DriverResult Display(LocalStoragePart part, string displayType, dynamic shapeHelper)
        {
            return ContentShape("Parts_LocalStorage",
                           () => shapeHelper.DisplayTemplate(
                               TemplateName: "Parts/LocalStorage",
                               Model: part,
                               Prefix: Prefix));
        }

I am quite new to Orchard and I might be missing something here, but it seems to me that the

 return ContentShape("Parts_LocalStorage", 
     () => shapeHelper.Parts_LocalStorage(Version: part.Version));

does not find the correct view?

Dec 7, 2011 at 2:26 PM

This line:

return ContentShape("Parts_LocalStorage", 
     () => shapeHelper.Parts_LocalStorage(Version: part.Version));

Will look for the view "Views\Parts_LocalStorage.cshtml".

But you can't have an @model declaration at the start of that file because you're not passing in a model. All it has is the dynamic property Version, which you access with @Model.Version.

Can you show the code for your view?

 

 

 

 

 

 

Dec 7, 2012 at 2:56 PM

We are planning to use widgets in our project. we have a login page for which we have created a part. While rendering this we are getting the below error.

"The model item passed into the dictionary is of type 'IShapeProxy674961fc5cfc4f5d94549fd4941a65b8', but this dictionary requires a model item of type 'MyApplication.Models.Login'".

As suggested above, we could resolve the above issue by using non-strongly typed html helpers. However this approach does not completely fit to our requirement, as we have already developed the application using strongly typed views and controllers.

we want achieve the below:
1. we want to use login model (our own) for displaying this part.
2. After posting the data, we need to validate credentials and if they are not valid, we need to show "Invalid credentials" message to end user. For this, currently we are setting login model properties to null and message property to "Invalid credentials" and re-rendering the view. please suggest how we can achieve the same using part.

Thanks in advance.

Developer
Dec 8, 2012 at 4:22 AM

Seems reasonable enough. Could you explain into detail what exactly the issue is with passing in a strongly typed view model to your view? I do that all the time without any issues.

Dec 10, 2012 at 1:42 PM

Getting Following error for TextBoxFor:

“An expression tree may not contain a dynamic operation.”

Display Part:


    @using (Html.BeginForm("Login", "Login", FormMethod.Post))
    {
        @Html.AntiForgeryToken()
        @(Html.ValidationSummary(false))
       

            <label for="UserName" class="BXSLabel">
                @T("UserName"):</label>
           

                @Html.TextBoxFor(m => m.ContentPart.UserName)
            

       

       

            <label for="Password" class="BXSLabel">
                @T("Password"):</label>
           

             @Html.PasswordFor(m => m.ContentPart.Password)
           

       

       

           

              @if (Model != null)
               { @Model.ContentPart.ErrorMessage }
                                      
           

       

       

             <input type="submit" id="bxsLoginButton" value="@T("Login")" class="BXSLogin" />
        

    }

Driver Code:

       protected override DriverResult Display(LoginPart part, string displayType, dynamic shapeHelper)
        {
            return ContentShape("Parts_FlightEngine_Login", () => shapeHelper.Parts_FlightEngine_Login(ContentPart: part));
        }

Models:

LoginPart:


    public class LoginPart : ContentPart
    {
        public LoginModel LoginModel { get; set; }
    }

    public class LoginModel
    {
        public string UserName { get; set; }

        public string Password { get; set; }

        public string ErrorMessage { get; set; }
       
    }

Please let us know if we are doing anything wrong.

Developer
Dec 10, 2012 at 2:25 PM

Oh, so the model of your view is not strongly typed, but dynamic. Extension methods can't work with dynamic, so what you need to do is store your strongly typed view model in a local variable, and use that local variable with the HTML helpers. E.g.: 

@{
   var loginModel = (LoginModel)Model.ContentPart.LoginModel;
}

@Html.TextBoxFor(m => loginModel.UserName);
@Html.PasswordFor(m => loginModel.Password);
Dec 11, 2012 at 2:00 PM

Thanks for the response.

With the change you suggested we could overcome the issue of strongly typed HTML helpers.

If I type wrong username and password and hit login button page gets posted back and the post method validates the credential and inject validation message (invalid credential) to ErrorMessage property of loginmodel.

So as the login part re-rendered the value of ErrorMessage property is lost.

So please guide us how we can return updated model from driver. Being first time user of widget system we may missed on some point.

Developer
Dec 11, 2012 at 2:31 PM

Yes, you will need to do a TryUpdateModel in the overloaded Editor method of your driver and pass in a new instance of your LoginModel. If model validation fails, you simply use the LoginModel instance and set it to the property on your part. Should work like a charm.

Dec 27, 2012 at 2:27 PM

Sorry for the delay, I was busy with other stuff could not make time to look into this.

If my understanding is correct, the Editor method is for editor template i.e. when we modify/add information in editor template and save it then we need to update the model, which is already implemented (as a sample) and working fine.  

But our requirement is that we are updating the LoginModel in Login controller if the validation fails and the same should be bound to the part while re-rendering. So, that the error message is displayed in UI.

Please let me know if you need more information.

Developer
Dec 31, 2012 at 4:34 PM

I'm afraid I I'm not sure what the issue is nor what you're trying to do exactly. It looks like you implemented a LoginPart, attached it to a content type (I'll assume you called it something like LoginPage). Now when a LoginPage is rendered, it posts back to a LoginController. That LoginController is responsible for validating the credentials.

A few questions & remarks:

  1. How will the LoginController know what content item to render if validation fails?
  2. Once you know which content item to render, before you actually invoke BuildDisplay on it, you could set a Boolean flag using WorkContext.SetState or on the HttpContext.Items collection, so that your driver can check that value whether or not to display an error message. That way you don't have to duplicate login validation (which I assume you will have to do if you were planning to update the view model both on the controller and the driver?)