Quantcast
Channel: C1 CMS Foundation - Open Source on .NET
Viewing all articles
Browse latest Browse all 2540

New Post: Community tech preview: MVC Functions

$
0
0
(Kudos to Dmitry Dzygin for these samples and writing the actual implementation)

MVC Functions is a way to turn MVC Controllers and Actions into C1 Functions. This can be done either by decorating your MVC controllers and actions with attributes or via an API, where you pass references to your controllers / actions. The latter is nice if the MVC code is either "hands off" or you just want the MVC code 100% pure. The attribute approach is nice if you like the MVC pattern and want to do custom development with it in C1.

A test website project will all the demo controllers can be downloaded from here:
https://drive.google.com/file/d/0B84rD1f6-jIsN0FLZXQ4bGQtM1U/view?usp=sharing

Your MVC code can be turned into C1 Functions in one of two ways ways:

1) By using [C1Function] attribute
2) Via API, by calling FunctionCollection.RegisterController/RegisterAction methods

There are 2 ways the controller action can be invoked:

Way #1.
Calling a specific action
With RegisterAction, a specific url “/<controller>/<action>” will be passed to the mvc handler.
It is also possible to pass in parameters from the cms via RouteData to that action.
It can be used for example to render layout parts (header, footer, etc.), or pages that do not support routing, and use only f.e. query string parameters.

Example: A search controller, that would accept search query and page information with query string, and if no query is specified, shows a search form:

Controller code:
    public class SearchController: Controller
    {
        public ActionResult Index(string q = null, int p = 1, int pageSize = 10, bool currentSiteOnly = false)
        {
            if (!string.IsNullOrWhiteSpace(q))
            {
                return View("Result", FindPages(q, p, pageSize, currentSiteOnly));
            }

            return View();
        }

         private static SearchResultViewModel FindPages(string searchQuery, int pageNumber, int pageSize, bool currentSiteOnly)
        {
               …..
         }
}
       
Control registration:
// Search
FunctionCollection.RegisterAction<SearchController>("Index", "Demo.Search")
     .AddParameter("PageSize", label: "Page Size")
    .AddParameter("CurrentSiteOnly", label: "Show on current site only");
Description

“Index” – the action to be called,
“Demo.Search” – the name of the C1 function.
AddParameter(parameterName, …) – registering parameters, note that the parameter time in this case is resolved by reflection, so the auto generated UI will have f.e. validation for number fields, and proper widgets for different websites etc.
Parameters yay

Way #2.
Registering a controller function that would take page url’s PathInfo into account

The way the C1 handles page route – if only a part of the url path matches a C1 page, the page will be rendered internally, and will show 404 response only if no functions within a page claim that the extra part of the url (referred as PathInfo in our documentation).

Behind the scene a function placed on the page, will try to execute the following url with an mvc handler: ~/<controller>/<PathInfo>

For example if we have an mvc function, that is referencing a “product” controller, placed on a C1 page “/MyWebsite/Products”

Accessing “/MyWebsite/Products” page will case the function to render “~/<product>” mvc route, which, by default is resolved to the “Index” actin

Accessing “/MyWebsite/Products/Details/5” page will case the function to render “~/<product>/Details/5” mvc route, which will be resolved to the “Details” action.

Example:



ProductController.cs:
[C1Function(Namespace = "Demo", Name="Product")]
public class ProductController: Controller
    {
        public ActionResult Index(string category)
        {
            return View();
        }

        public ActionResult Details(int id)
        {
            return View(id);
        }

        public ActionResult About()
        {
            ViewData["Message"] = "Hello World!";

            return View();
        }
    }
Here the function is registered with [C1Function] attribute, an alternative would be to use
FunctionCollection.RegisterController<ProductController>("Demo.Product");
For navigation between the actions - @Html.ActionLink() extension method is supported and can be used:



~/Views/Product/Index.cshtml
@for (int i = 1; i <= 10; i++)
{
    <div>
        @Html.ActionLink("Product " + i, "Details", new {id = i})
    </div>
}

<br/>
<p>
    @Html.ActionLink("Read about", "About")
</p>
~/Views/Product/Details.cshtml
<h1>Details view</h1>

<p>
    Product id: <strong>@Model</strong>
</p>

<br />
<p>
    @Html.ActionLink("Go back", "Index")
</p>
Custom view for the part of the route

In some cases one may have to create a special presentation for a subset of the routes.
Example: lets create a book store where a certain category of books is displayed differently.

BookController routes a defined as:

Route Action
/book Index action – shows the list of categories
/book/category Shows list of books in the current category
/book/category/bookId/BookTitle Shows details about a specific book


BookController.cs:
[RoutePrefix("book")]
public class BookController: Controller
{
          private List<BookModel> _books;

          public ActionResult Index()
          {
                   // ...
          }

          [Route("{category}")]
          public ActionResult Category(string category)
          {
                   // ...
          }

          [Route("{category}/{bookId}/{title}")]
          public ActionResult Book(string category, string bookId, string title)
          {
                   // ...
          }
}
Then we register 2 functions:
FunctionCollection.RegisterController<BookController>("Demo.BooksOverview");
FunctionCollection.RegisterController<BookController>("Demo.BooksByGenre")
                .AddParameter("Category", label: "Genre");
Function “Demo.BooksOverview” we will put at the “/My-book-store” page, and it will both render the index action, and will handle all the sub requests as well:

The second function “Demo.BooksByGenre” is accepting a string parameter, the way the function will be rendered, is that this parameter will be put into the executed virtual mvc route, f.e.

/<controllername/<paramerValue>{/PathInfo}

Lets add a “Horror” page under the “/My-book-store” page, this way accessing:

/My-book-store/Horror will cause rendering of this specific page, rather than the mvc controller, specified in “/My-book-store”.
Here we can add a function call to the Demo.BooksByGenre function, and pass the “Horror” as the “category” parameter’s value.

Horror!!

After that, all the links under /My-book-store/Horror will have a custom theme, where the view for theother categories will remain unchanged.

Setting up the package

One of the recent builds of Composite C1 is required.

MvcFunctions package uses own route collection, to prevent exposure of the internally used routes. That route collection has to be initialized on startup.
For the startup registration, build into C1 auto-discovered [ApplicationStartup] attribute can be used instead of global asax. Example of registering the mvc route, discovering functions with [C1Function] attribute and registering controllers via api on startup:
    [ApplicationStartup]
    internal static class StartupHandler
    {
        public static void OnBeforeInitialize()
        {
            RegisterMvcFunctions();
            RegisterFunctionRoutes();
        }

        private static void RegisterMvcFunctions()
        {
            // Auto discovering all the controllers with [C1Function] attribute
            FunctionCollection.AutoDiscoverFunctions(typeof(StartupHandler).Assembly);

            // Movies (an EntityFramework example from Microsoft http://www.asp.net/visual-studio/overview/2013/aspnet-scaffolding-overview)
            FunctionCollection.RegisterController<MoviesController>("Demo.Movies");

            // Books 
            FunctionCollection.RegisterController<BookController>("Demo.BooksOverview");
            FunctionCollection.RegisterController<BookController>("Demo.BooksByGenre")
                .AddParameter("Category", label: "Genre");

            // Search
            FunctionCollection.RegisterAction<SearchController>("Index", "Demo.Search")
                .AddParameter("PageSize", label: "Page Size")
                .AddParameter("CurrentSiteOnly", label: "Show on current site only");
        }

        private static void RegisterFunctionRoutes()
        {
            FunctionCollection.RouteCollection.MapMvcAttributeRoutes();
            FunctionCollection.RouteCollection.MapRoute(
                "Default", // Route name
                "{controller}/{action}/{id}", // URL with parameters
                new { action = "Index", id = UrlParameter.Optional } // Parameter defaults
                );
        }

        public static void OnInitialized()
        {
        }
    }
// Dmitry Dzygin

Viewing all articles
Browse latest Browse all 2540

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>