Copied to clipboard

Flag this post as spam?

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


  • Manfred Klinger 12 posts 82 karma points
    Aug 01, 2023 @ 13:15
    Manfred Klinger
    0

    Redirect forms correctly with both API Controller and Render Controller

    I have two classes, one being an RenderController and one being an API Controller

    They look like this:

    namespace Core.Controllers.Api; 
    
    public class SBLApiController: UmbracoApiController {
    
        private static readonly string ServiceUrl = AppSettingsHelper.GetKey<string>("SBLService");
        private readonly UmbracoHelper _umbracoHelper;
        private readonly IHttpContextAccessor _httpContextAccessor;
        private readonly IUmbracoContextAccessor _umbracoContextAccessor;
        private readonly IPublishedValueFallback _publishedValueFallback;
        private readonly ISettings _settings;
        private readonly ILogger<RenderController> _logger;
        private readonly IHttpService _httpService;
    
        public SBLApiController(UmbracoHelper umbracoHelper, IHttpContextAccessor httpContextAccessor, IUmbracoContextAccessor umbracoContextAccessor, IPublishedValueFallback publishedValueFallback, ISettings settings, ILogger<RenderController> logger, IHttpService httpService) {
            _umbracoHelper = umbracoHelper;
            _httpContextAccessor = httpContextAccessor;
            _umbracoContextAccessor = umbracoContextAccessor;
            _publishedValueFallback = publishedValueFallback;
            _settings = settings;
            _logger = logger;
            _httpService = httpService;
        }
    
        [HttpPost]
        [ValidateAntiForgeryToken]
        [ActionName("GetAltinnUrl")]
        public async Task<ActionResult> GetAltinnUrl(SsnFormViewModel model) {
            var currentPage = _umbracoContextAccessor.GetRequiredUmbracoContext().PublishedRequest?.PublishedContent;
            try {
                bool.TryParse(AppSettingsHelper.GetKey<string>("SBLUseMockData"), out var useMockData);
    
                if (useMockData && string.IsNullOrWhiteSpace(model.AuthenticationCode)) {
                    model.AuthenticationCode = await GetMockupAuthenticationCode();
                }
    
                if (string.IsNullOrWhiteSpace(model.AuthenticationCode)) {
                    throw new ArgumentException("Authentication code is not present");
                }
    
                var altinnUrl = string.Format(AppSettingsHelper.GetKey<string>("SBL:SBLAltinnUrl"), model.AuthenticationCode);
    
                return Redirect(altinnUrl);
            } catch (Exception e) {
                var httpContext = _httpContextAccessor.HttpContext;
                httpContext?.Session.SetString("Consent", AltinnStatus.Error);
            }
            return Redirect(currentPage?.Url() ?? "/");
        }
    

    }

    and

    namespace Core.Controllers; 
    
    public class SBLLandingPageController : RenderController {
    
        private readonly IPublishedValueFallback _publishedValueFallback;
        private readonly ISettings _settings;
        public SBLLandingPageController(
            ILogger<RenderController> logger,
            ICompositeViewEngine compositeViewEngine,
            IUmbracoContextAccessor umbracoContextAccessor,
            IPublishedValueFallback publishedValueFallback,
            ISettings settings) : base(logger, compositeViewEngine, umbracoContextAccessor) {
            _publishedValueFallback = publishedValueFallback;
            _settings = settings;
        }
    
        public override IActionResult Index() {
            var home = new SbllandingPage(CurrentPage, _publishedValueFallback);
            var viewModel = new SbllandingPageViewModel {
                Page = home,
                Settings = _settings,
                ErrorMessage = ModelState.Values.FirstOrDefault()?.Errors?.FirstOrDefault()?.ErrorMessage
            };
            return View("~/Views/Pages/SBLLandingPage.cshtml", viewModel);
        }
    }
    

    In the cshtml file, I want to use the "GetAltinnUrl" method with umbraco forms. I have tried literally 500 different ways of doing so. I either get the message "Could not find a Surface controller route in the RouteTable for controller name" or "415 Unsupported Media Type" or nothing is happening at all.

    I tried all four of these but non worked:

    @using (Html.BeginUmbracoForm<SBLLandingPageController>("GetAltinnUrl", "SBLLandingPageController", FormMethod.Post)) {
    @using (Html.BeginUmbracoForm("GetAltinnUrl", "SBLLandingPage", new {}, new {@class = "consent-form"})) {
    @using (Html.BeginUmbracoForm<SBLLandingPageController>("GetAltinnUrl")) {
    @using (Html.BeginForm("GetAltinnUrl", "SBLApi", FormMethod.Post, new { @class = "consent-form" })) {
    

    I stopped to understand what is actually happening here. Maybe I also got the controller types confused, but the rest works fine like this.

  • Huw Reddick 1929 posts 6697 karma points MVP 2x c-trib
    Aug 02, 2023 @ 16:03
    Huw Reddick
    0

    Hi Manfred

    I think you should be using a surfacecontroller for this, Html.BeginUmbracoForm is for posting to a SurfaceController.

  • Manfred Klinger 12 posts 82 karma points
    Aug 03, 2023 @ 07:13
    Manfred Klinger
    0

    Oh yes. you are in fact right, thank you. I have replaced the API Controller with a SurfaceController. Now, all error messages are gone, but it doesnt do anything anyway.

    It does a post and has a ERROR 400 Bad Request. In fact, it never calls the GetAltInnUrl. this is weird because it obviously can find it, because if I write in something else there, I get the error that it cannot find it.

    using Core.Constants;
    using Core.Helpers;
    using Core.Interfaces;
    using Core.SBL;
    using Core.ViewModels;
    using Microsoft.AspNetCore.Http;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.Extensions.Logging;
    using Umbraco.Cms.Core.Cache;
    using Umbraco.Cms.Core.Logging;
    using Umbraco.Cms.Core.Routing;
    using Umbraco.Cms.Core.Services;
    using Umbraco.Cms.Core.Web;
    using Umbraco.Cms.Infrastructure.Persistence;
    using Umbraco.Cms.Web.Common;
    using Umbraco.Cms.Web.Common.Controllers;
    using Umbraco.Cms.Web.Website.Controllers;
    using Umbraco.Extensions;
    
    namespace Core.Controllers; 
    
    public class SBLApiController: SurfaceController {
    
        private static readonly string ServiceUrl = AppSettingsHelper.GetKey<string>("SBL:SBLService");
        private readonly UmbracoHelper _umbracoHelper;
        private readonly IHttpContextAccessor _httpContextAccessor;
        private readonly IUmbracoContextAccessor _umbracoContextAccessor;
        private readonly ILogger<RenderController> _logger;
        private readonly IHttpService _httpService;
    
        public SBLApiController(IUmbracoContextAccessor umbracoContextAccessor,
            IUmbracoDatabaseFactory databaseFactory, ServiceContext services, AppCaches appCaches,
            IProfilingLogger profilingLogger, IPublishedUrlProvider publishedUrlProvider, UmbracoHelper umbracoHelper, IHttpContextAccessor httpContextAccessor, ILogger<RenderController> logger, IHttpService httpService) : base(umbracoContextAccessor,
            databaseFactory, services, appCaches, profilingLogger, publishedUrlProvider) {
            _umbracoHelper = umbracoHelper;
            _httpContextAccessor = httpContextAccessor;
            _umbracoContextAccessor = umbracoContextAccessor;
            _logger = logger;
            _httpService = httpService;
        }
    
        [HttpPost]
        [ValidateAntiForgeryToken]
        //[ActionName("GetAltinnUrl")]
        //[Route("sbl/getAltInnUrl")]
        public async Task<ActionResult> GetAltinnUrl(SsnFormViewModel model) {
            Console.WriteLine("I AM CALLED");
            var currentPage = _umbracoContextAccessor.GetRequiredUmbracoContext().PublishedRequest?.PublishedContent;
            try {
                bool.TryParse(AppSettingsHelper.GetKey<string>("SBL:SBLUseMockData"), out var useMockData);
    
                // In test environments we need to fetch an authentication code from Altinn manually. This is done
                // by calling the api/sbl-process/initialize/ call in the DPloy API. In the production environment the
                // authentication code will be fetched from the URL parameter.
                if (useMockData && string.IsNullOrWhiteSpace(model.AuthenticationCode)) {
                    model.AuthenticationCode = await GetMockupAuthenticationCode();
                }
    
                if (string.IsNullOrWhiteSpace(model.AuthenticationCode)) {
                    throw new ArgumentException("Authentication code is not present");
                }
    
                var altinnUrl = string.Format(AppSettingsHelper.GetKey<string>("SBL:SBLAltinnUrl"), model.AuthenticationCode);
                Console.WriteLine(altinnUrl);
    
                Console.WriteLine("Redirect 1");
                return Redirect(altinnUrl);
            } catch (Exception e) {
                _logger.LogError($"An error occured while constructing Altinn URL {e}", e);
                var httpContext = _httpContextAccessor.HttpContext;
                httpContext?.Session.SetString("Consent", AltinnStatus.Error);
                Console.WriteLine("Redirect 2");
            }
            Console.WriteLine("Redirect 3");
            return Redirect(currentPage?.Url() ?? "/");
        }
    
        private async Task<string> GetMockupAuthenticationCode() {
            //_httpContextAccessor.HttpContext.Request.GetEncodedUrl()
            var httpContext = _httpContextAccessor.HttpContext;
    
            var host = httpContext?.Request.Scheme + "://" + httpContext?.Request.Host;
            var callback = host + Url.Action("ReceiveConsent");
    
            var initializeUrl = ServiceUrl + AppSettingsHelper.GetKey<string>("SBL:SBLInitialize");
    
            var mockData = new {
                consentCaseReference = "123",
                personIdentifier = "01029413157",
                lastName = "Høk",
                email = "[email protected]",
                handleUserCommunication = false,
                redirectUrl=callback,
                notificationUrl = "http://localhost:19355/sblwebapi/api/notifyapplikator",
                validTo = DateTime.Now.AddDays(1)
            };
    
            var result =
                await _httpService.Post<AltinnInitializationResponse, object>(initializeUrl, mockData);
    
    
            return result == null ? string.Empty : result.AuthorizationCode;
        }
    
        public async Task<ActionResult> ReceiveConsent(string authorizationCode, string status, string failedAuthorizationCode, string errorMessage) {
            if (!string.IsNullOrWhiteSpace(errorMessage)) {
                _logger.LogWarning("Altinn returned an error message when returning from consent: \"" + errorMessage + "\"");
            }
    
            try {
                var httpContext = _httpContextAccessor.HttpContext;
                var pageId = httpContext?.Session.GetInt32(SessionKeys.SBLLandingPageId);
                _httpContextAccessor.HttpContext?.Session.Remove(SessionKeys.SBLLandingPageId);
    
                var updateUrl = ServiceUrl + AppSettingsHelper.GetKey<string>("SBL:SBLUpdate");
    
                // If a user rejects SBL then Altinn sets the authCode to failedAuthenticationCode rather then
                // authenticationCode. Status is also set to "Failed"
                var authCode = authorizationCode;
                var hasRejected = false;
                if (status == AltinnStatus.Failed) {
                    authCode = authorizationCode;// ?? failedAuthorizationCode;
                    hasRejected = true;
                }
    
                // Triggers an update call within DPloy to update SBL status
                // Send put request to Dploy
                await _httpService.Put<DployUpdateResultModel, object>(updateUrl, new {
                    AuthorizationCode = authCode,
                    reject = hasRejected
                });
    
                httpContext?.Session.SetString("Consent", status);
    
                if (pageId <= 0) {
                    throw new Exception("Page Id is not set.");
                }
    
                var redirectPage = _umbracoHelper.Content(pageId);
                return Redirect(redirectPage?.Url() ?? "/");
            } catch (Exception e) {
                _logger.LogError($"An error occured while handling the Altinn response {e}", e);
            }
    
            return Redirect("/");
        }
    }
    
    public class DployUpdateResultModel {
        public string? Status { get; set; }
    }
    class AltinnInitializationResponse {
        public string? AuthorizationCode { get; set; }
    }
    
  • Huw Reddick 1929 posts 6697 karma points MVP 2x c-trib
    Aug 03, 2023 @ 07:42
    Huw Reddick
    0

    What does your form code look like in the view?

    There is a decent example in the docs https://docs.umbraco.com/umbraco-cms/tutorials/members-registration-and-login#assigning-new-members-to-groups-automatically

  • Manfred Klinger 12 posts 82 karma points
    Aug 03, 2023 @ 07:46
    Manfred Klinger
    0

    The code of my view looks like this:

    <div style="margin-top: 30px; max-width: 300px;">
                                @*@using (Html.BeginUmbracoForm<SBLLandingPageController>("sbl/GetAltinnUrl", "SBLLandingPageController", FormMethod.Post)) {*@
                                @using (Html.BeginUmbracoForm("GetAltinnUrl", "SBLApi", new {}, new {@class = "consent-form"})) {
                                @*@using(Html.BeginUmbracoForm<SBLLandingPageController>("sbl/GetAltinnUrl")) {*@
                                @*@using (Html.BeginUmbracoForm<SBLApiController>(nameof(SBLApiController.GetAltinnUrl))) {*@
                                    @Html.AntiForgeryToken()
                                    context.Response.Headers.Remove("X-Frame-Options");
                                    <div class="consent-form__input-wrapper">
                                        @if (Model.Page != null && Model.Page.RequireDirectLoanConsent) {
                                            <div class="direct-loan-consent">
                                                <input type="checkbox" name="ConsentDirectLoan" value="" id="ConsentDirectLoan" required>
                                                <label class="consent_checkbox__label" for="ConsentDirectLoan">@Model.Page.DirectLoanConsentText</label>
                                            </div>
                                        }
    
                                        @if (!string.IsNullOrEmpty(Model.ErrorMessage)) {
                                            <span class="error">@Model.ErrorMessage</span>
                                        }
                                        @if (!string.IsNullOrEmpty(context.Request.Query["ConsentCaseReference"])) {
                                            <input type="hidden" name="ConsentCaseReference" id="ConsentCaseReference" value="@context.Request.Query["ConsentCaseReference"]"/>
                                        }
                                        @*Hidden field that processes the auth code*@
                                        @if (!string.IsNullOrEmpty(context.Request.Query["AuthenticationCode"])) {
                                            <input type="hidden" name="AuthenticationCode" id="AuthenticationCode" value="@context.Request.Query["AuthenticationCode"]"/>
                                        }
    
                                    </div>
                                    <input type="submit" value="@UmbracoHelper.GetDictionaryValue("General_GetDataAltinn", "Hent mine data fra Skatteetaten")" class="button-primary consent-form__submit"/>
                                }
                            </div>
    

    The commented lines are all sorts of things I tried here. Thank you for the link, this seems to be the only of the 500 links I havent read through yet, I will have a look 😁

  • Huw Reddick 1929 posts 6697 karma points MVP 2x c-trib
    Aug 03, 2023 @ 07:51
    Huw Reddick
    0

    you shouldn't need this line @Html.AntiForgeryToken() (should be added by Umbraco)

    You may need to use bind prefix in your method.

    A quick way to test it is reaching your method is to change it to

    public async Task<ActionResult> GetAltinnUrl(Object model)
    

    It should then hit your method regardless of what you are passing to it, you can then try and figure out what it should be expecting :)

Please Sign in or register to post replies

Write your reply to:

Draft