Copied to clipboard

Flag this post as spam?

This post will be reported to the moderators as potential spam to be looked at


  • DonZalmrol 198 posts 700 karma points
    1 week ago
    DonZalmrol
    0

    How to pass data from Umbraco Surfacecontroller to C# controller and back?

    Hi, I am trying to figure out how I can pass data (an image.url) from my site to my code in C# and retrieve something else.

    Put very simply:

    1. My site generates a value (image url)
    2. That value now needs to be send to my C# code (model)
    3. My code does the needed manipulations (controller that extracts the exif GPS coordinates or Google Maps url with coordinates)
    4. Returns said manipulations (post back to my site)
    5. Done (users can click a link with the gps coordinates of the image)

    What I have:

    1. A controller named ExifController
    2. A model named ExifModel
    3. A partial view macro name GalleryV4 (that contains an image gallery)

    What works: The macro works and images are displayed like the should, so now I would like to "send" an image URL to my model and from my to my controller and of course back after getting certain data back (exif in my case).

    Question: How can I do this? The sources on Umbraco tells you how what to create, but unfortunately the examples are all for contact forms...

    Thanks in advance!

  • Simon Ulmbrant 24 posts 118 karma points
    1 week ago
    Simon Ulmbrant
    0

    I should have used an UmbracoApiController instead of a SurfaceController to do the extra manipulation for exif and url.

  • DonZalmrol 198 posts 700 karma points
    1 week ago
    DonZalmrol
    0

    oh and how do I use that? :)

  • Steve Morgan 1208 posts 3922 karma points c-trib
    1 week ago
    Steve Morgan
    0

    Hi,

    It sounds like you're struggling to know where to put your custom code. It might be better as a custom service. That you can call from a Surface Controller / API controller. That way you let Umbraco handle the routing / end point and you keep your custom code that does the magic coordinate lookup in a separate service.

    Then you can use that service where ever you need in your site.

    Take a look at https://our.umbraco.com/documentation/getting-started/Code/Umbraco-Services/#custom-class-example

    I try to keep my logic in the controllers light (skinny controllers) and put logic into services so that tests can be written.

    There's always a 100 ways to do something - this might not be the best for you but I hope it helps.

    Steve

  • DonZalmrol 198 posts 700 karma points
    1 week ago
    DonZalmrol
    0

    My main question/ struggle is to know how I can call the surface controller or api controller from my front-end, push data to it and get it back :)

    And that information seems to be missing from the documentation(s). They all show you how to create a controller, but not really on how to call it within Umbraco...

  • Steve Morgan 1208 posts 3922 karma points c-trib
    1 week ago
    Steve Morgan
    0

    So you say your site generates an image URL - is this a separate site to your Umbraco site or on the same site?

  • Steve Morgan 1208 posts 3922 karma points c-trib
    1 week ago
    Steve Morgan
    0

    There are three different things

    1. A surface controller - I tend to use these for forms only. Imagine them as a bit of extra something that helps deal with the render and post of a form
    2. A controller - more generic
    3. API Controller - no need for Umbraco pages that you call the Surface Controller or Controller from.. will just create an API endpoint (as per the documentation).

    For 1 and 2 you will need to create a page which will provide your url then on that page you'll either have to call the surface controller or controller from the corresponding view.

    It sounds like you want a controller and then to create a view for this page and call it via:

    @Html.Action("MyControllerMethod", "MyController", new { model = Model })
    
  • DonZalmrol 198 posts 700 karma points
    1 week ago
    DonZalmrol
    0

    It is on the same site. My Umbraco code fetches the images from a media folder and presents them as an image gallery. From the gallery I can use the same generated URL that contains the image path, that image is also on the same Umbraco site and contains the needed EXIF data.

    So I need to pass data from my front-end to my back-end C# code and then get the needed needed data (whatever that may be) back to my front-end to do something new with it.

    So in my case pass the image url, extract the needed EXIF data (GPS), push back that data, Umbraco Razor displays the fetched coordinates (e.g. as a text).

  • Steve Morgan 1208 posts 3922 karma points c-trib
    1 week ago
    Steve Morgan
    0

    Hi,

    Definitely sounds like a controller and then:

    @Html.Action("MyControllerMethod", "MyController", new { model = Model })
    

    In your view / partial to get to your logic.

    HTH

  • DonZalmrol 198 posts 700 karma points
    1 week ago
    DonZalmrol
    0

    Hmm seems I get an error when using the @Html.Action part.

    Error:

    Error loading Partial View script (file: ~/Views/MacroPartials/GalleryV4.cshtml)

    Umbraco code

    @Html.Action("ExifProcessor", "ExifController", new { model = Model })
    

    VS project structure: enter image description here

    Then my code: ExifModel.cs

        using System.Drawing.Imaging;
    
    namespace Don_Zalmrol.Models
    {
        public class ExifModel
        {
            public string GetImageUrl { get; set; }
    
            public ExifModel()
            {
    
            }
        }
    }
    

    ExifController.cs

    using System.Web.Mvc;
    using Umbraco.Web.Mvc;
    
    namespace Don_Zalmrol.Controllers
    {
        public class ExifController : SurfaceController
        {
            [ChildActionOnly]
            public ActionResult ExifProcessor()
            {
                // In case you need it...
                var currentNode = Umbraco.TypedContent(UmbracoContext.PageId.GetValueOrDefault());
    
                var model = new Models.ExifModel();
    
                // Use if you wish to redirect the end-user to a different page
                //return View("umbContactUs", model);
    
                // Returns to the original page the end-user was on
                return CurrentUmbracoPage();
            }
    
            [HttpPost]
            public ActionResult ExifProcessor(Models.ExifModel model)
            {
                try
                {
                    model.GetImageUrl = "test";
                    return ViewBag.Message = model.GetImageUrl;
                }
    
                // General error catching
                catch (System.Exception ex)
                {
                    // Do something
                    ModelState.AddModelError("Error", ex.Message);
                    return CurrentUmbracoPage();
                }
    
                // Send success message to end-user
                finally
                {
                    // Do something
                    //model.GetImageUrl = "test";
                    ViewBag.Message = model.GetImageUrl;
                }
            }
        }
    }
    

    As you can see I want to test to push an url and just return the string "test" to see if Umbraco can post/ fetch my code.

    If you see my contact controller, that works without any issues, but I can't seem to create something silly like this...

  • Steve Morgan 1208 posts 3922 karma points c-trib
    1 week ago
    Steve Morgan
    0

    Hi

    If you want to use a SurfaceController I would instead change this a bit. I doubt you're actually POSTing to this from a form. So comment out one of those methods as this pattern is for the render of a form and then to handle the user POSTing - you're just rendering - right?

    Personally I would do something like:

    namespace Don_Zalmrol.Controllers
        {
            public class ExifController : SurfaceController
            {
                [ChildActionOnly]
                public ActionResult ExifProcessor(Don_Zalmrol.Models.ExifModel model)
                {
                    try
                    {
                        // Do your thing here
                        model.GetImageUrl = "test";
    
    
                        return PartialView("Exif/RenderExifGalleryItem", model);
    
                    }
    
                    // General error catching
                    catch (System.Exception ex)
                    {
    
                        // I wouldn't use modelstate-  just return the error in your model and handle in the partial to change the rendering
                        //model.IsError = true;
                        //model.ErrorMessage = "Something went wrong";
                        return PartialView("Exif/RenderExifGalleryItem", model);
                    }   
                }
            }
        }
    

    Call the SC from your main view via:

    @Html.Action("ExifProcessor", "Exif", new { model = Model })
    

    And then create a partial to handle the output in Views/Partials/Exif/RenderExifGalleryItem.cshtml

        @model Don_Zalmrol.Models.ExifModel
    
    <h1>Hello World</h1>
    
  • DonZalmrol 198 posts 700 karma points
    1 week ago
    DonZalmrol
    0

    Ok, but how do I get my data from my site to the surfacecontroller?

    Also I already work with a Partial view marco named Gallery4.cshmtl that I use to generate the image slider/ carousel.

    Perhaps I'm not explaining (or understanding) it not correctly? :)

    1. My site has a macro (Gallery4) and a partial view macro (GalleryV4.cshtml) that I use to select an image folder that contains photographs.
    2. The macro works and generates the images. Code: enter image description here Result: enter image description here
    3. @image.Url needs to be passed to the ExifController to extract the needed EXIF data.
    4. After my ExifController has extracted the needed EXIF data (GPS coordinates for example) it needs to return these values to the same partial view macro
    5. In the partial view macro I can then for example add the returned data in a h3. e.g.: enter image description here

    So I am stuck on item 3.

  • Steve Morgan 1208 posts 3922 karma points c-trib
    1 week ago
    Steve Morgan
    0

    So why not change it to:

    From your main view call a method called OutputGallery on your surfacecontroller.

    Pass the Umbraco Page Model in - then from that model get your images from the picker (I'm assuming they are picked in the umbraco backend?) loop through and add the extra data you want.

    So you're populating a ModelView which is a list of your ExifImageModel which has the image url and anything else you need to output your gallery - your SC build this model view up. Then have a render partial that you call from the surface controller as per my example that loops through your model and outputs the images.

    Steve

  • DonZalmrol 198 posts 700 karma points
    1 week ago
    DonZalmrol
    0

    I don't wish to push all the data to my C# code :) As I only need to get the EXIF data, and this is not always the case.

    e.g. a page where I don't wish to publish the exif data.

    Then again I would "hit" the same issue, how can I get my data from the Umbraco backend (www.mysite.com/umbraco) to my surface controller and then back to my Umbraco backend?

  • Steve Morgan 1208 posts 3922 karma points c-trib
    1 week ago
    Steve Morgan
    0

    Sorry can you please explain what you mean with the "it is not always the case?"

    As in you only want this on one page - does this page have it's own doc type - just use a different view template for this page.

  • DonZalmrol 198 posts 700 karma points
    1 week ago
    DonZalmrol
    0

    For example there are pages on my site where I do not wish to extract the EXIF data (e.g. private locations or where they are not needed/ applicable/ give additional value to my page).

  • Steve Morgan 1208 posts 3922 karma points c-trib
    1 week ago
    Steve Morgan
    0

    OK - lets start again.

    Who is the intended audience for this EXIF data? The back office Editor users or to display on the front end?

    Steve

  • DonZalmrol 198 posts 700 karma points
    1 week ago
    DonZalmrol
    0

    Display to the frontend, so visitors of my personal site.

  • Steve Morgan 1208 posts 3922 karma points c-trib
    1 week ago
    Steve Morgan
    0

    OK - so on some pages you add the EXIF on others you don;t. Then I would add a checkbox to the gallery in the doc type "Show EXIF" then in the surface controller check this value and then only set those values in the viewmodel you want to output.

    You could then either return a different partial view WITH EXIF or without does that make sense?

    Steve

  • DonZalmrol 198 posts 700 karma points
    1 week ago
    DonZalmrol
    0

    I believe so, something like this on my image folders:enter image description here

    Question now is how can I achieve this? And thank you very much in helping me out with this!

  • Steve Morgan 1208 posts 3922 karma points c-trib
    1 week ago
    Steve Morgan
    0

    Hi,

    I was struggling to explain myself so I've made a working proof of concept.

    You will probably need to review how I've done this - particularly how I open the image in memory stream, there might be more efficient ways of doing it.

    I've made a single model for a gallery image - it might be cleaner to have two different models for with and without Exif data.

    I've used https://drewnoakes.com/code/exif/ to get meta data.. might be an easier way and you'll have to install this into your project via nuget.

    1. Add to your page view template this:

      @Html.Action("RenderGallery", "GallerySurface", new { model = Model })
      

    Here we're passing the Umbraco page model so we can get the gallery and your new true false Exif switch.

    1. Create a partial view in a folder called "\Views\Partials\Gallery" called RenderGallery.cshtml note the first line is not being highlighted as code but include it!!

    This takes the view model that is created in the surface controller and renders your gallery (obviously you need to put your custom razor back in here)

    @model Don_Zalmrol.Models.GalleryViewModel
    
    
        <div class="container">
            <div class="row">
                <div class="col-24">
                    <h2>Gallery</h2>
                    @foreach(var curGalleryItem in Model.GalleryItems)
                    {
                        <img src="@curGalleryItem.ImageUrl" alt="todo" />
                        if(Model.ShowExifData)
                        {
                            <p>ExposureTime = @curGalleryItem.ExposureTime</p>
                            <p>FNumber = @curGalleryItem.FNumber</p>
                            <p>Make = @curGalleryItem.Make</p>
                            <p>Model = @curGalleryItem.Model</p>
                            <p>Artist = @curGalleryItem.Artist</p>
                            <p>SubjectLocation = @curGalleryItem.SubjectLocation</p>
    
                        }
                    }
                </div>
            </div>
        </div>
    
    1. Create a surface controller - I've put the models in her for ease - you should put them in your models folder.

      using Don_Zalmrol.Models; using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using Umbraco.Core.Models.PublishedContent; using Umbraco.Web; using Umbraco.Web.Mvc; using MetadataExtractor; using System.IO; using MetadataExtractor.Formats.Exif;

    namespace Don_Zalmrol.Models { public class GalleryImageItem { public string ImageUrl { get; set; } public string ExposureTime { get; set; } public string FNumber { get; set; } public string Make { get; set; } public string Model { get; set; } public string Artist { get; set; } public string SubjectLocation { get; set; }

    }

    public class GalleryViewModel
    {
        public GalleryViewModel()
        {
            GalleryItems = new List<GalleryImageItem>();
        }
        public List<GalleryImageItem> GalleryItems { get; set; }
        public bool ShowExifData {get; set;} 
    
    } } 
    
    
    namespace Don_Zalmrol.Controllers
    {
        public class GallerySurfaceController : SurfaceController
        {
            [ChildActionOnly]
            public ActionResult RenderGallery(IPublishedContent model)
            {
                var viewModel = new GalleryViewModel();
                try
                {
                    // Loop through the images and add them to the gallery
                    // using https://drewnoakes.com/code/exif/ to get meta data.. might be an easier way?
                    var galleryItems = model.Value<List<IPublishedContent>>("galleryItems");
                    viewModel.ShowExifData = !model.Value<bool>("hideEXIFData");
    
                    foreach (var curGalleryImage in galleryItems)
                    {
                        if (curGalleryImage != null)
                        {
                            if (viewModel.ShowExifData)
                            {
                                string path = Server.MapPath(curGalleryImage.Url);
    
                                if (System.IO.File.Exists(path))
                                {
                                    using (MemoryStream imageMemoryStream = new MemoryStream())
                                    {
                                        // todo - check that this doesn't open each image for performance, not sure about the copyto
                                        using (Stream input = System.IO.File.OpenRead(path))
                                        {
                                            input.CopyTo(imageMemoryStream);
                                        }
    
                                        imageMemoryStream.Position = 0;
                                        var directories = ImageMetadataReader.ReadMetadata(imageMemoryStream);
    
                                        var subIfdDirectory = directories.OfType<ExifSubIfdDirectory>().FirstOrDefault();
    
                                        var dateTime = subIfdDirectory?.GetDescription(ExifDirectoryBase.TagDateTime);
    
                                        viewModel.GalleryItems.Add(new GalleryImageItem
                                        {
                                            ImageUrl = curGalleryImage.Url(),
                                            ExposureTime = subIfdDirectory?.GetDescription(ExifDirectoryBase.TagExposureTime),
                                            FNumber = subIfdDirectory?.GetDescription(ExifDirectoryBase.TagFNumber),
                                            Make = subIfdDirectory?.GetDescription(ExifDirectoryBase.TagMake),
                                            Model = subIfdDirectory?.GetDescription(ExifDirectoryBase.TagModel),
                                            Artist = subIfdDirectory?.GetDescription(ExifDirectoryBase.TagArtist),
                                            SubjectLocation = subIfdDirectory?.GetDescription(ExifDirectoryBase.TagSubjectLocation),
                                        });
                                    }
                                }
                            }
                            else
                            {
                                // simple
                                viewModel.GalleryItems.Add(new GalleryImageItem
                                {
                                    ImageUrl = curGalleryImage.Url(),
                                });
                            }
                        }
                    }
                    return PartialView("Gallery/RenderGallery", viewModel);
                }
    
                // General error catching
                catch (System.Exception ex)
                {
    
                    // I wouldn't use modelstate-  just return the error in your model and handle in the partial to change the rendering
                    //model.IsError = true;
                    //model.ErrorMessage = "Something went wrong";
                    return PartialView("Gallery/RenderGallery", viewModel);
                }
            }
        }}
    
  • Steve Morgan 1208 posts 3922 karma points c-trib
    1 week ago
    Steve Morgan
    1

    Grrr can't get the code formatter to behave.

    The surface controller and models are here: http://pastie.org/p/4QVrgGHunnXi4H5K1nbNfy

  • DonZalmrol 198 posts 700 karma points
    1 week ago
    DonZalmrol
    0

    Thank you very much for this!

    My Umbraco backend Macro Partial looks like this: enter image description here

    My VS project now looks like this: enter image description here

    The RenderGallery.cshtml: enter image description here

    My ExifModel: enter image description here

    My ExifController: enter image description here enter image description here enter image description here

    Unfortunately I get these errors when compiling: enter image description here

    Seems to be coming from the ExifController: enter image description here

  • DonZalmrol 198 posts 700 karma points
    1 week ago
    DonZalmrol
    0

    Fixed the issue by changing "model.Value" to "model.GetPropertyValue" for:

    • var galleryItems = model.GetPropertyValue<>
    • viewModel.ShowExifData = !model.GetPropertyValue

    Code compiled without issues and model is loading, unfortunately the custom partialview "RenderGallery" is not loading in.

  • Steve Morgan 1208 posts 3922 karma points c-trib
    1 week ago
    Steve Morgan
    0

    Ah - you're on Umbraco v7 - hence the difference on GetPropertyValue and Url vs Url()

    Check you've definitely added the call to the surface controller in the view on the page as per my step 1).

    You should be able to put a breakpoint on the method in the Surface Controller and see it hit. Also try debugging it step by step - there might be an exception somewhere?

    Steve

  • DonZalmrol 198 posts 700 karma points
    1 week ago
    DonZalmrol
    0

    No worries, I know I need to upgrade to 8, but that's a whole different project!

    I believe we are quite close, I've deduced the following: enter image description here

    I believe its coming from the model, either its empty or something else is blocking the data.

  • DonZalmrol 198 posts 700 karma points
    1 week ago
    DonZalmrol
    0

    When running this from my Umbraco backend

    <div class="pictureSlider">
    
    @foreach (var image in media.Children<Image>())
    {
        <div><center><picture>
            <img class="img-responsive img-rounded lazyload" data-src="@image.Url?width=800&height=800&mode=max&quality=90&metadata=true&autorotate=true&anchor=center&overlay=favicon.png&overlay.position=30,15&overlay.size=50,50&overlay.opacity=100" alt="Image @imageCount1" id="imageExif-@imageCount1"/>
            <h3>Image @imageCount1 of @imageCount0 images</h3>
    
            @* Push @image.Url to ExifController *@
            <!-- load model -->
            @Html.Action("RenderGallery", "GallerySurface", new { model = Model })
            <!-- end loading model -->
    
        </picture></center></div>
    
        imageCount1++;
    }
    

    Are we actually pushing the image URL (@image.Url) or the whole image (@image) to the model? @Html.Action("RenderGallery", "GallerySurface", new { model = Model })

    That's the part I can't wrap my brain around.

    EDIT: Made a pastie for the backend config: http://pastie.org/p/6znRLJAWzy6r4v5IYosDgn

    EDIT 2: I see (and forgot since its been ages I created this gallery) that the value of @image is the media ID! So perhaps thats why the model is not working (or receiving) it properly

  • Steve Morgan 1208 posts 3922 karma points c-trib
    1 week ago
    Steve Morgan
    0

    HI,

    Put a breakpoint on the surface controller and debug it - you'll see where it's going wrong. I suspect the alias for the gallery items is wrong or you've got a version that returns a comma separated list of image IDs rather than a list of IPublishedContent items.

    Then you need to split the string and get each image via var typedMedia = Umbraco.TypedMedia(imageId) before passing it into the Server.MapPath(typedMedia)

  • DonZalmrol 198 posts 700 karma points
    1 week ago
    DonZalmrol
    0

    Let me check, I do believe my gallery is working by an image ID when you select an image or folder from my media.

    Update: My gallery works indeed with an image ID, see the pasted code in the following link http://pastie.org/p/3wEkltwN0wCnxAuAlZchzl

    So how can I change it in the C# code? I'm not yet that proficient with Visual studio :)

    EDIT: When I debug my code, it crashes... So that option is not possible :(

  • DonZalmrol 198 posts 700 karma points
    1 week ago
    DonZalmrol
    0

    So for the debugging I found an alternative way, its not optimal, but it works. I simply output the var(s) to a TXT using System.Io.File.WriteAllText :)

    Seems that the model isn't getting the needed data, so that means either the wrong alias is used or it is the way on how I use the image IDs.

    enter image description here

    FYI: I use the build in macro snippet Gallery for my GalleryV4.

    @*
    Macro to display a gallery of images from the Media section.
    Works with either a 'Single Media Picker' or a 'Multiple Media Picker' macro parameter (see below).
    
    How it works:
        - Confirm the macro parameter has been passed in with a value
        - Loop through all the media Ids passed in (might be a single item, might be many)
        - Display any individual images, as well as any folders of images
    
    Macro Parameters To Create, for this macro to work:
    Alias:mediaIds     Name:Select folders and/or images    Type: Multiple Media Picker
                                                            Type: (note: You can use a Single Media Picker if that's more appropriate to your needs)
    

    *@

  • DonZalmrol 198 posts 700 karma points
    1 week ago
    DonZalmrol
    0

    Hi

    So to make it a bit more clear:

    1. I've used the macro snippet Gallery as my basis.
    2. From that snippet I removed the render function
    3. It still uses the alias "mediaId" but transforms it to

      var media = Umbraco.TypedMedia(mediaId);

    4. If a media folder was selected it shows the image for my image slider with going (foreach) through media.Children and puts it in a new var image

      @foreach (var image in media.Children()) { ... other code ... @Html.Action("RenderGallery", "GallerySurface", new { model = Model}) ... other code ... }

    5. So question now is, which alias or var does the model need?

    Do I need to use "image" since that is the actual last fetched image media ID.

    Or do I need to use "mediaIDs" and create new functionality within my C# Surfacecontroller, but that seems extra work for something that is already available.

    When I output the var "image" I get an media ID returned e.g. . 2167

    Full code of this macro: http://pastie.org/p/6StPKLn4EqvpXBMy2NvbLr

  • Steve Morgan 1208 posts 3922 karma points c-trib
    1 week ago
    Steve Morgan
    0

    Hi,

    We need to take a few steps back here. It's all getting a bit confusing.

    1. What version of Umbraco are you running?
    2. How are the editor / you to picking the images in the back office? I assumed you were using a multiple media picker - you're now posting things that are related to macros - usually these are best used in the Rich Text Editor and not for rendering content in dedicated doc type properties. Can you please screenshot the content node, and how you are picking images. Or are you trying to output everything from a media folder(s)?
    3. What exactly are you trying to achieve? You mentioned EXIF data - I assumed this would be coming from the image file itself - you seem to have external text files now - please explain exactly what you're trying to output and where you expect this data to come from.
    4. What is in galleryItems.txt ? What is the purpose of this file?

    Steve

  • DonZalmrol 198 posts 700 karma points
    1 week ago
    DonZalmrol
    0

    Hi, sorry for the confusion!

    1. Umbraco version 7.15.4 assembly: 1.0.7381.11453

    2. I have always had a macro called "GalleryV4" that I use for my RTE and as as property on my document type, on that document type it is called "pictureGallery" enter image description here And works like this in the template of my Project Item:

              @Umbraco.RenderMacro("GalleryV4", new {mediaIds = @Model.Content.GetPropertyValue("pictureGallery") })
      
    3. EXIF Data from the image itself. What I am trying to output is the EXIF data from the image that is displayed from my Macro "GalleryV4". The macro contains the code to present a slick slider with a main (focused) image and below the GPS data from the EXIF should be shown, then a little lower I have a slider that previews the images.

    4. The txt files are just a workaround for me to "debug" the problem and see what is working and what not (e.g. putting the var into the txt to see its value)

    5. You can see the gallery here on my site working and where the GPS data should appear (all the way at the bottom, where it says Gallery loading in... which comes from the partial you've created in the visual studio project).

    Hope this is more clear for you, if not let me know and I'll try to explain myself better :)

    Thanks again for all the help!

    EDIT: Perhaps you could share your working test with me? Perhaps then I can immediately see why its working on your demo and not on my site.

Please Sign in or register to post replies

Write your reply to:

Draft