8
votes

I'm on a project with .Net Core and I'm using ASP Razor Pages. In my model, I have an OnGet which load all the data I need in my view.

public IList<Projet> Projets { get; set; }
public ActionResult OnGet()
{
    Projets = _serviceSelect.getProjets();
    return Page();
}

Then, in my OnPost which is activated when I submit my form.

<form method="post">
    <div asp-validation-summary="All" class="text-danger"></div>
    <input type="radio" value="1" asp-for="LoginData.TypeCompte" />choice<br />
    Username: <input asp-for="LoginData.Username" /><br />
    Password: <input asp-for="LoginData.Password" /><br />
    Remember me: <input asp-for="LoginData.RememberMe" type="checkbox" /><br />
    <input asp-page-handler="connexion" type="submit" value="Login" />
    @Html.AntiForgeryToken()
</form>

I would like to display an error in my view, using my ModelState.

public ActionResult OnPostConnexion()
{
    if (ModelState.IsValid)
    {
       // Do stuff 
    }
    else
    {
        ModelState.AddModelError("", "username or password is blank");
        return Page();
    }
}

But, when I return Page(), It's like the model is reload and when I try to access to my data, my objects are null.

 Object reference not set to an instance of an object.
@foreach (var item in Model.Projets)

How can I update my view without losing my data contain in the model ?

6

6 Answers

5
votes

I solved this issue by replacing return Page(); with return this.OnGet(); in public ActionResult OnPostConnexion()

That way any logic you have in the OnGet which populates PageModel properties gets reused.

So my full example looks like:

    public IEnumerable<SelectListItem> CompanyListing { get; private set; }

    public async Task<IActionResult> OnGet()
    {
        if (!string.IsNullOrEmpty(this.ErrorMessage))
        {
            this.ModelState.AddModelError(string.Empty, this.ErrorMessage);
        }

        this.CompanyListing = await this.PopulateCompanyList();
        return this.Page();
    }

    public async Task<IActionResult> OnPostAsync()
    {
        if (!this.ModelState.IsValid)
        {
            // Something failed. Redisplay the form.
            return await this.OnGet();
        }

        return this.RedirectToPage("/");
    }
1
votes

The reason you are getting an object reference error is because the Model.Projects has not been populated when returning the view and therefore cannot iterate in the foreach loop.

Based off your existing code, you could populate your model again before returning the page.

public ActionResult OnPostConnexion()
{
    if (ModelState.IsValid)
    {
       // Do stuff 
    }
    else
    {
        Projets = _serviceSelect.getProjets();

        ModelState.AddModelError("", "username or password is blank");
        return Page();
    }
}

A better solution would be:

public ActionResult OnPostConnexion(viewModel model)
{
    if (!ModelState.IsValid)
    {
       return View(model);
    }
    else
    {
        //do stuff
    }
}
0
votes

When you call "Page()" this is asking for a new Page object, when you return that object it will be blank. Pages and views are different, generally with ASP you will use views, there are some good articles on MSDN about the differences and why but for now just if you change Page() to View() it should resolve the issue.

0
votes

You need to fix two things in your code:

First you need to mark your Projets property with [BindProperty], like this:

[BindProperty]
public IList<Projet> Projets { get; set; }

Then you need to recode your Page so that the fields of Projets are inside of the form tag so they get posted back to the page and then you need to rewrite how you output them:

<form method="post">
    <div asp-validation-summary="All" class="text-danger"></div>
    <input type="radio" value="1" asp-for="LoginData.TypeCompte" />choice<br />
        Username: <input asp-for="LoginData.Username" /><br />
        Password: <input asp-for="LoginData.Password" /><br />
        Remember me: <input asp-for="LoginData.RememberMe" type="checkbox" /><br />
    <input asp-page-handler="connexion" type="submit" value="Login" />
    @Html.AntiForgeryToken()

    <!-- SEE REMARK BELOW! -->
    @{
        for (int i = 0; i < Model.Projets.Count; i++)
        {
            <input asp-for="@Model.Projets[i].FieldOne" />
            <br />
            <input asp-for="@Model.Projets[i].FieldTwo" />
        }
    }

</form>

REMARK You need to render your fields in a list in such way that they get unique ID's so that the ModelBinder can retrieve them on Post. In my case I named one field FieldOne and this is how it is rendered:

<input type="text" id="Projets_0__FieldTwo" name="Projets[0].FieldTwo" value="one">
0
votes

I faced the same issue and noone from suggested solution worked. I resolved it with simple usage of helping class that keeps the model for me. The solution is not elegant but effective :)

public interface ISessionHelper
    {
        void AddRenewItem(string key, object item);
        object GetItem(string key);
    }
public class SessionHelper : ISessionHelper
{
    private readonly Dictionary<string, object> _sessionBag;

    public SessionHelper()
    {
        _sessionBag = new Dictionary<string, object>();
    }

    public void AddRenewItem(string key, object item)
    {
        if (_sessionBag.Keys.Contains(key)) _sessionBag.Remove(key);
        _sessionBag.Add(key, item);
    }

    public object GetItem(string key)
    {
        if (_sessionBag.Keys.Contains(key))
        {
            return _sessionBag[key];
        }
        return null;
    }
}

In my get method I add an item to the dictionary:

 public async Task<IActionResult> OnGetAsync(int? id)
    {
      ...
        _sessionHelper.AddRenewItem(this.ToString(), this);
        ...

    }

In my post method I take the model back from the dictionary and assign required properties for the current model:

 public async Task<IActionResult> OnPostApplyGuess(int itemId)
    {
        GameModel model = (GameModel) _sessionHelper.GetItem(this.ToString());
        MyProp1= model.MyProp1;
        MyProp2 = model.MyProp2 ;
        MyProp3= model.MyProp3;

        if (!ModelState.IsValid)
        {
            return Page();
        }

        return Page();
    }

Do not forget to add the service as a Singleton:

services.AddSingleton<ISessionHelper, SessionHelper>();

I hope it helped to somebody :)