1

I am trying to build a set of MVC components which can be easily reused with various settings. They are built and used as child actions, because I need them to be able to make partial postback to themselves without the knowledge of the other content of the view on which they are hosted.

I am facing an issue with where to store their parameters without passing them through client (I don't want to build something like view state, and also for security reasons), and make them available to the partial postback.

Here is a simplified example (not that code may not be compilable as I cut my syntax sugar to simplify it to plain MVC):

View code (component usage):

@Html.Action(
    "Default",
    "FacebookFeed",

    new {
        // I don't want this data to pass through client
        settings = new FacebookFeedSettings {
            AppKey = "XYZ",
            AppSecret = "123",
            PageSize = 10
        }
        .ItemTemplate(
            @<div class="feed-item">@item.Title</div>
        )
    }
)

Controller code:

public class FacebookFeedController {
   public ActionResult Default(FacebookFeedSettings settings)
   {
      // Action code using settings

      return PartialView(model);
   }
}

Feed view code:

@using (Ajax.BeginForm("Default", "FacebookFeed", new AjaxOptions { HttpMethod = "POST", InsertionMode = InsertionMode.ReplaceWith }))
{
    // Form code

    <input type="submit" value="Refresh" />
}

So when the Refresh button is hit, it is supposed to render fresh data, but settings is missing in that request.

So far I came up only with solution to make some sort of settings register indexed by string key where they would register their settings set.

Modified view code (component usage):

@Html.Action(
    "Default",
    "FacebookFeed",
    new {
        settingsKey = "HomePageFBFeed"
    }
)

Extra code from user:

[ComponentSettings]
public class HomePageFBFeed : FacebookFeedSettings
{
    public HomePageFBFeed()
    {
        AppKey = "XYZ";
        AppSecret = "123";
        PageSize = 10;
    }
}

Modified controller code:

public ActionResult Default(string settingsKey)
{
   FacebookFeedSettings settings = ComponentSettings.GetSettings(settingsKey);

   // Action code using settings

   return PartialView(model);
}

Modified view code:

@using (Ajax.BeginForm("Default", "FacebookFeed", new AjaxOptions { HttpMethod = "POST", InsertionMode = InsertionMode.ReplaceWith }, new { settingsKey = Model.SettingsKey }))
{
   ...
}

So it this case I pass over the client only some unique ID of that configuration, which is fine, but it has lousy user experience compared to first as it needs to be managed outside of view where the component is placed.

I am also not able to use inline template in this case as shown in the first code part, because in this case settings is built outside the scope of a view.

Note that I also need this to work with application restarts, and across process boundaries (in cloud), so I can't rely on storing the configuration on server side at the first load of the view.

Is there some better way / best practice how to do that in ASP.NET 4.6 / MVC 5?

If not, will it be possible in ASP.NET 5 / MVC 6?

martinh_kentico
  • 913
  • 7
  • 17

2 Answers2

0

Martin, I know you said you don't want to create a view state, but considering the disconnected mode of MVC (you don't want to use server-side Session, also because it doesn't scale), would you be open to transfer an encrypted string of the settings?

Something like this in the view:

@using (Ajax.BeginForm("Default", "FacebookFeed", new AjaxOptions { HttpMethod = "POST", InsertionMode = InsertionMode.ReplaceWith }))
{
    @Html.AntiForgeryToken()
    <input type="hidden" name="Settings" value="@Model.ToEncryptedString()" />
    <input type="submit" value="Refresh" />
}

The ToEncryptedString() would be an extension on your model, that:

  1. Serialize your settings (the FacebookFeedSettings object)
  2. Encrypt it
  3. Convert to Base 64 to make it HTTP friendly

When going back to the controller, all you need to do is to read the Settings parameter in the Default action:

[HttpPost, ValidateAntiForgeryToken]
public ActionResult Default(string settings)
{
    FacebookFeedSettings facebookSettings = FacebookFeedSettings.FromEncryptedString(settings);
    // do something with the settings
    // build the model
    return PartialView(model);
}

The FromEncryptedString() does exactly the opposite direction:

  1. Convert from Base 64 to byte[]
  2. Decrypt the byte array
  3. Deserialize to a FacebookFeedSettings object instance

Basically, this encrypted string works more or less like the anti-forgery token. To make it even more elegant, I guess you can move the settings validation also at attribute level, and mark your action with a custom attribute:

[HttpPost, ValidateAntiForgeryToken, FacebookFeedSettings]
public ActionResult Default(/* No need to capture settings here */)

The FacebookFeedSettingsAttribute would obtain the Settings parameter from the Request and the build and validate the FacebookFeedSettings object instance. I haven't tried going this far in my try, I leave it to you to practice :-)

What do you think?

  • Thank you for the answer. I would like to avoid that, that is basically what I meant by avoiding ViewState. I simplified the case, but the settings may contain more data, some of it is also not serializable (e.g. lambda expression through which a more general component gets the data or that inline item template). I already have a data-driven solution where settings are configured from browser and stored in database but I am also looking to cover scenario where it can be configured on ad-hoc basis more like traditional components. – martinh_kentico Jan 22 '16 at 12:53
0

I understand and I agree, the problem is that you'd like to keep some form of state anyway, in a stateless technology.

Good news is that MVC 6 seems to have the answer to your problem. First of all, child actions do not exist any longer, replaced by View Components. View Components are made of a C# class and a Razor view, and do not have a dependency on a Controller, which eases reusability.

I do not have a direct experience yet, but from what I am reading about MVC 6 and specifically View Components, they are self-contained, so basically you can manage state within the components itself. Parameters are not passed over HTTP from the view, because you actually invoke the component server-side (no trip back from the client).

Also, View components don’t take part in the controller lifecycle but you still have access to ViewBag and ViewData (shared with the Controller). Lasty, like controllers, View Components also take part in dependency injection so any other information you need can simply be injected to the view component.

This should work for you, but you need to wait for MVC 6 :-)

  • Well, it is not so much "state" rather than configuration which doesn't change, so it could be easily recreated on other instances as well and doesn't really need to be maintained on client basis. The key problem is where to best define it so that the look and feel of "components" is similar as in WebForms = ideally configure those components in markup where they are placed if possible somehow. – martinh_kentico Jan 26 '16 at 07:41
  • With ViewComponent model, I am not sure if that will help either, because my components need to be able to make partial postbacks to themselves (I don't want the whole page to reload as it has to be done in WebForms), so if I understand it correctly, I will still need to split the current child action to a ViewComponent and a regular Controller to cover both initial display and AJAX postback in MVC 6. Or am I missing something? – martinh_kentico Jan 26 '16 at 07:44
  • A view component never directly handles an HTTP request so you can’t call directly to a view component from the client side. You will need to wrap the view component with a controller if your application requires this behaviour. As for where to save configuration, have you considered a settings file that contains a JSON object with all the properties to save? If it is server-side only, you can make assumption that the file name matches the class name, consistently with the convention-based philosophy of MVC. – Stefano Tempesta Jan 27 '16 at 12:25
  • I know how it works. I was hoping to be able to provide similar experience as in ASPX markup by configuring properties directly at the location where I place the component, but it seems that it is not doable in MVC, at least not without sending he configuration over the client. – martinh_kentico Feb 12 '16 at 11:57
  • That's true, that's the nature of MVC. After all, also in ASP.NET Web Forms, information is sent to the client as part of the ViewState. The alternative is storing it server-side (file system, database) and have a convention-base approach based, for example, on class attributes. So the developer sets this information as an attribute of your model, and this is saved on the server before the model is passed to the view, and without this information going to the view as well. I am sure you'll figure out a good way of doing it for the next wonderful version of Kentico X :-) – Stefano Tempesta Feb 14 '16 at 07:31