Thursday, February 21, 2019

SharePoint Provider hosted app: Invalid length for a Base-64 char array exception

This error means the string which we are trying to convert to base 64 encoding does not have enough length. More explanation is available at https://stackoverflow.com/questions/12962992/invalid-length-for-a-base-64-char-array-exception.

To  fix this error, append '=' character to the string till it becomes divisible by four.

In a SharePoint provider hosted app, this generally means the client secret (or secondary client secret) needs to be appended as above.

Tuesday, February 12, 2019

View all model state errors while debugging

If we are in break mode while debugging an MVC controller event,  and want to view all ModelState errors, the following command can show all error messages in immediate window:


ModelState.Where(x => x.Value.Errors.Count>0).SelectMany((x, i)=> x.Value.Errors.Select(y=> "Key: " + x.Key + ", Error: " + y.ErrorMessage)).ToList()

Thursday, February 7, 2019

Simplistic Custom Sign In in MVC


To implement a very simple authentication in MVC where authentication is done using values stored in web.config files follow these steps.

1)     Create a folder Identity and create classes CustomSignInManager, CustomUser, CustomUserManager and CustomUserStore



CustomSignInManager class has the following code:

public class CustomSignInManager : SignInManager<CustomUser, string>
    {
        public CustomSignInManager(CustomUserManager userManager, IAuthenticationManager authenticationManager)
            : base(userManager, authenticationManager)
        {

        }

        public static CustomSignInManager Create(IdentityFactoryOptions<CustomSignInManager> options, IOwinContext context)
        {
            return new CustomSignInManager(context.GetUserManager<CustomUserManager>(), context.Authentication);
        }

        public override Task<SignInStatus> PasswordSignInAsync(string userName, string password, bool isPersistent, bool shouldLockout)
        {
            //return base.PasswordSignInAsync(userName, password, isPersistent, shouldLockout);
            var signInStatus = SignInStatus.Failure;
            if (ConfigurationManager.AppSettings["UserName"].Trim().ToLower() == userName.Trim().ToLower() && ConfigurationManager.AppSettings["Password"] == password)
            {
                signInStatus = SignInStatus.Success;
            }
            return Task.FromResult(signInStatus);
        }
    }


CustomUser class has the following code:

public class CustomUser : IUser<string>
    {
        public string Id { get; set; }

        public string UserName { get; set; }

        public string UserRole { get; set; }
    }

CustomUserManager class has the following code:


public class CustomUserManager : UserManager<CustomUser>
    {
        public CustomUserManager(IUserStore<CustomUser> store) : base(store)
        {
        }

        public static CustomUserManager Create()
        {
            var manager = new CustomUserManager(new CustomUserStore());
            return manager;
        }
    }



CustomUserStore class has the following code:


public class CustomUserStore : IUserStore<CustomUser>
    {
        public CustomUserStore()
        {
            //this.database = new CustomDbContext();
        }

        public void Dispose()
        {
            //this.database.Dispose();
        }

        public Task CreateAsync(CustomUser user)
        {
            // TODO
            throw new NotImplementedException();
        }

        public Task UpdateAsync(CustomUser user)
        {
            // TODO
            throw new NotImplementedException();
        }

        public Task DeleteAsync(CustomUser user)
        {
            // TODO
            throw new NotImplementedException();
        }

        public async Task<CustomUser> FindByIdAsync(string userId)
        {
            await Task.Delay(0);
            return new CustomUser();
        }

        public async Task<CustomUser> FindByNameAsync(string userName)
        {
            await Task.Delay(0);
            return new CustomUser();
        }
    }


Add a property CustomSignInManager to AccountsController

public CustomSignInManager CustomSignInManager
        {
            get { return _signInManager ?? HttpContext.GetOwinContext().Get<CustomSignInManager>(); }
            private set { _signInManager = value; }
        }

Login method in AccountsController class has the following code:


if (!ModelState.IsValid)
            {
                return View(model);
            }
            //Authenticate the user
            var result = await CustomSignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);
            switch (result)
            {
                case SignInStatus.Success:
                    var user = new CustomUser() {Id= "1", UserName = model.Email };
                    await CustomSignInManager.SignInAsync(user, model.RememberMe, false);
                    return RedirectToLocal(returnUrl);
                case SignInStatus.LockedOut:
                    return View("Lockout");
                case SignInStatus.RequiresVerification:
                    return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
                case SignInStatus.Failure:
                default:
                    ModelState.AddModelError("", "Invalid login attempt.");
                    return View(model);
            }

MVC BeginForm with querystring

For a provider hosted app in SharePoint any request to the server must preserve standard SharePoint tockens in query string. I have found a blog that explains how to achieve similar functionality in plain MVC
https://www.danylkoweb.com/Blog/quick-tip-querystrings-with-aspnet-mvc-beginform-FO

In practise, instead of adding another BeginFormQueryString extension to Html object I only copied the extension method on query string

public static RouteValueDictionary ToRouteValueDictionary(this NameValueCollection collection)
        {
            var routeValues = new RouteValueDictionary();
            foreach (string key in collection)
            {
                routeValues[key] = collection[key];
            }
            return routeValues;

        }

And then added the routevalues to OOB BeginForm method like so:



@using (@Html.BeginForm("myAction", "myController", Request.QueryString.ToRouteValueDictionary(), FormMethod.Post))
{

Wednesday, February 6, 2019

MVC datepicker editor template

To add datepicker template by default to all datetime fields follow the steps mentioned below:


1) Make sure jquery, jquery-ui, jquery-validate and jquery-validate-date libaries are being used
2) Add datetime editor template at View/Shared/EditorTemplates/DateTime.cshtml
@model DateTime?
@{
    var value = "";
    if (Model.HasValue)
    {
        value = String.Format("{0:d}", Model.Value.ToString("dd/MM/yyyy"));
    }
}

@Html.TextBox("", value, new { @class = "datepicker", type = "text" })

2) Add DataType and DisplayFormat attributes to all datetime fields
        [DataType(DataType.DateTime)]

        [DisplayFormat(DataFormatString = "{0:dd/MM/yyyy}", ApplyFormatInEditMode = true)]

3) Add globalization tag to web.config
   <system.web>
    <globalization culture="en-GB" uiCulture="en-GB" enableClientBasedCulture="false"></globalization>
  </system.web>

4) Add date validation and datepicker bind script to document on load

jQuery(document).ready(function ($) {
    $.validator.addMethod('date', function (value, element) {
        if (this.optional(element)) {
            return true;
        }
        var valid = true;
        try {
            $.datepicker.parseDate('dd/MM/yyyy', value);
        }
        catch (err) {
            valid = false;
        }
        return valid;
    });
    $('.datepicker').datepicker({ dateFormat: 'dd/MM/yyyy' });

});

5)

Friday, February 1, 2019

MVC edit form for an entity

Many times we need to use edit forms for complex entities. Though creating a form is rather easy with dot net MVC, there are some important bits which need to be remembered. The following link has a comprehensive example of implementing such a form.

https://blog.rsuter.com/asp-net-mvc-how-to-implement-an-edit-form-for-an-entity-with-a-sortable-child-collection/

Though this method seems very good, it was coded in older version of MVC and hence probably does not work with MVC core.

Another simpler way is to convert the model in JSON string and decode it in our action method. I prefer this method since it gives us complete control over the data passed from view to action method.

More information is available at https://stackoverflow.com/questions/21529092/asp-net-mvc-passing-models-by-actionlink

c# httpclient The remote certificate is invalid according to the validation procedure: RemoteCertificateNameMismatch

 If we get this error while trying to get http reponse using HttpClient object, it could mean that certificate validation fails for the remo...