Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I keep my model in my View after OnPost?

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 ?

like image 561
Thomas Avatar asked May 03 '18 12:05

Thomas


People also ask

Why use Razor Pages?

From the docs, "Razor Pages can make coding page-focused scenarios easier and more productive than using controllers and views." If your ASP.NET MVC app makes heavy use of views, you may want to consider migrating from actions and views to Razor Pages.

What does Bind property do?

Property binding in Angular helps you set values for properties of HTML elements or directives. Use property binding to do things such as toggle button features, set paths programmatically, and share values between components.

How do Razor Pages work?

Razor Pages focus on page-based scenarios for building web applications rather than using controllers and views like a traditional ASP.NET MVC application. Once the application receives an HTTP request, it moves through the middleware pipeline until it reaches a middleware component that can handle and process it.


3 Answers

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("/");
    }
like image 160
Adam Carter Avatar answered Nov 10 '22 00:11

Adam Carter


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
    }
}
like image 35
j9070749 Avatar answered Nov 10 '22 00:11

j9070749


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 :)

like image 1
Mr.B Avatar answered Nov 10 '22 00:11

Mr.B