8 Replies - 541 Views - Last Post: 22 April 2019 - 10:19 AM

#1 BenignDesign   User is online

  • holy shitin shishkebobs
  • member icon




Reputation: 7951
  • View blog
  • Posts: 12,382
  • Joined: 28-September 07

Help a desperate newbie out? Combining codes questions

Posted 18 April 2019 - 11:48 AM

I've been desperately trying to get a grasp on this whole ASP.net/MVC thing with limited success. I've worked through tutorial after tutorial, but they all seem to be set up the same way - they include some partial projects and I get to add code to them as the tutorial tells me to do - with generally limited explanation as to what anything actually does. I've worked exclusively in PHP for the better part of two decades. My brain isn't handling the change as well as I'd hoped.

Anyway, what I need is a form that accepts a file upload and a radio button selection, and, when submitted, spits out the contents of the file based on the radio button selected. Through the help of tutorials and code snippets, I have created one working project that uploads the file, reads it, and spits it back out (thank you, Lynda.com), and a second working project that takes radio button input and spits out the selection (thank you, modi). My question, is how do I smash these things together and make them work?

Radio Button Code:

Index.cshtml
@model WebApplication8.Models.HomeViewModel
@{ 
    ViewData["Title"] = "Home";
}

<h1>@Model.Message</h1>

<h3>Test</h3><br />
<hr />

@using (Html.BeginForm())
{
    @Html.LabelFor(a=>a.TextBoxTest)@Html.TextBoxFor(z=>z.TextBoxTest)
    <br /><br />

    @Html.RadioButtonFor(a=>a.TestRadioButtonselected, "Users")@Html.Label("Users")
    @Html.RadioButtonFor(a=>a.TestRadioButtonselected, "Quiz")@Html.Label("Quiz")
    <br />
    <input type="submit" value="Submit" />
}



HomeController.cs
using System.Web.Mvc;
using WebApplication8.Models;

public class HomeController:Controller
{
    public ActionResult Index()
    {
        return View(new HomeViewModel());
    }

    [HttpPost]
    public ActionResult Index(HomeViewModel model)
    {
        model.Message = $"Radiobutton value: {model.TestRadioButtonselected} / Textbox value: {model.TextBoxTest}";
        return View(model);
    }
}



HomeViewModel.cs
using System.ComponentModel.DataAnnotations;

namespace WebApplication8.Models
{
    public class HomeViewModel
    {
        public string TestRadioButtonselected { get; set; }

        public string Message { get; set; }

        [Display(Name = "Title")]
        public string TextBoxTest { get; set; }
    }
}



File Upload Code:

Index.cshtml
@{
    ViewBag.Title = "Home Page";
}
@using WebApplication6.Controllers
@model IEnumerable<WebApplication6.Controllers.FileModel>

@using (Html.BeginForm("Index", "Home", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
    <label>File: </label><input type="file" name="postedFile" />
    <input type="submit" value="Import" />
}
@if (Model.Count() > 0)
{
    <hr />
    <table cellpadding="5" cellspacing="0">
        <tr>
            <th>ID</th>
            <th>Group Name</th>
            <th>Access Level</th>
        </tr>
        @foreach (FileModel groups in Model)
        {
            <tr>
                <td>@groups.GroupId</td>
                <td>@groups.GroupName</td>
                <td>@groups.AccessLevel</td>
            </tr>
        }
    </table>
}



HomeController.cs
using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace WebApplication6.Controllers
{ 
    public class HomeController : Controller
    {
        //GET: Home
        public ActionResult Index()
        {
            return View(new List<FileModel>());
        }

        [HttpPost]
        public ActionResult Index(HttpPostedFileBase postedFile)
        {
            List<FileModel> groups = new List<FileModel>();
            string filePath = string.Empty;
            if(postedFile!=null)
            {
                string path = Server.MapPath("~/Uploads/");
                if(!Directory.Exists(path))
                {
                    Directory.CreateDirectory(path);
                }
        
                filePath = path + Path.GetFileName(postedFile.FileName);
                string extension = Path.GetExtension(postedFile.FileName);
                postedFile.SaveAs(filePath);

                //Read the contents of CSV file
                string csvData = System.IO.File.ReadAllText(filePath);

                //Execute a loop over the rows
                foreach (string row in csvData.Split('\n'))
                {
                    if(!string.IsNullOrEmpty(row))
                    {
                        groups.Add(new FileModel
                        {
                            GroupId = Convert.ToInt32(row.Split(',')[0]),
                            GroupName = row.Split(',')[1],
                            AccessLevel = row.Split(',')[2]
                        });
                    }
                }
            }        

            return View(groups);
        }
    }

    public class FileModel
    {
        ///<summary>
        ///Gets or sets CustomerId
        ///</summary>
        public int GroupId { get; set; }

        ///<summary>
        ///Gets or sets GroupName
        /// </summary>
        public string GroupName { get; set; }

        ///<summary>
        ///Gets or sets AccessLevel
        /// </summary>
        public string AccessLevel { get; set; }
    }
}



Both individual projects have been tested and are working without an issue. Any attempt to combine the two, however, culminates in disaster - error messages at every turn, and every attempt to fix one brings others... which tells me it's an ID10T error.

I know, in theory, how to merge these - add the input from one form to the other form (and for the Index.cshtml portion, I've got it handled):

Combined Code:

Index.cshtml
@model WebApplication7.Controllers.HomeViewModel
@{ 
    ViewData["Title"] = "Home";
}

<h1>@Model.Message</h1>

<h3>Test</h3><br />
<hr />

@using (Html.BeginForm())
{
    <label>File to Upload: </label><input type="file" name="postedFile" />
    <br /><br />
    <label>File type:</label><br />
    @Html.RadioButtonFor(a=>a.TestRadioButtonselected,"Users")@Html.Label("Users")
    <br />
    @Html.RadioButtonFor(a=>a.TestRadioButtonselected,"Quiz")@Html.Label("Quiz")
    <br /><br />
    <input type="submit" value="Submit" />
}

@if(Model.Count()>0)
{
    <hr />
    <table cellpadding="5" cellspacing="0">
        <tr>
            <th>ID</th>
            <th>Group Name</th>
            <th>Access Level</th>
        </tr>
        @foreach(FileModel groups in Model)
        {
            <tr>
                <td>@groups.GroupId</td>
                <td>@groups.GroupName</td>
                <td>@groups.AccessLevel</td>
            </tr>
        }
    </table>
}



Whether or not I've combined these through the best method is fully debatable, but it works which is kind of the goal right now.

HomeController.cs

This is where I get all discombobulated and turned around.

ActionResult Index() for one file says return View(new HomeViewModel());, the other says return View(new List<FileModel>());. How do I combine these? Do they get combined or do I have have a second ActionResult Index() definition? Can ActionResult Index() return more than one parameter?

Then there's the [HttpPost] stuff - public ActionResult Index(HttpPostedFileBase postedFile) and public ActionResult Index(HomeViewModel model). Do I create two separate ActionResult Index() definitions for these or can I pass both parameters to the same one? Why is this one also called ActionResult Index()? How many ActionResult Index() things do we really need? What's the point in the [HttpPost]? What's the reasoning behind HttpPostedFileBase? And how, when you're writing this stuff entirely for yourself and not following tutorials, do you have any flipping clue what to use for things?

In addition, what I'm ultimately trying to do is use a switch/case method to process the file differently depending on the radio button selection. For example, selecting the "Users" radio button would read the file data and write the contents to the Users table of a database (currently just prints the information to the screen as the database isn't built yet). However, selecting the "Quiz" radio button would read the file and write the contents to the Quiz table where the uploading user would then assign the new Quiz to a module, location within the module, and a user group code (to denote where the quiz occurs within the system and to which groups it should appear). If the "PDF" radio button is selected, it will upload the file to a specified folder and the file will be assigned to a specific module by the uploading user (from here, in the UI, a link will be generated for other users to download the PDF). I know how to set up the switch/case code, I just don't know WHERE to set it up - does it go in the HomeController? A new model class? In the ActionResult Index() area? Some other area whose name I'm supposed to pluck out of thin air? I AM SO CONFUSED!

I hope something in that wall made a modicum of sense, and I hope I'm not severely overthinking the whole thing or missing something extremely obvious. Any and all help will be hugely appreciated. I am in so far over my head, I could cry.

This post has been edited by BenignDesign: 18 April 2019 - 12:07 PM


Is This A Good Question/Topic? 0
  • +

Replies To: Help a desperate newbie out? Combining codes questions

#2 modi123_1   User is offline

  • Suitor #2
  • member icon



Reputation: 15062
  • View blog
  • Posts: 60,147
  • Joined: 12-June 08

Re: Help a desperate newbie out? Combining codes questions

Posted 18 April 2019 - 11:54 AM

Give me a few to tinker with this and see about straightening it out.. I have a code release here I gotta prep for.
Was This Post Helpful? 0
  • +
  • -

#3 modi123_1   User is offline

  • Suitor #2
  • member icon



Reputation: 15062
  • View blog
  • Posts: 60,147
  • Joined: 12-June 08

Re: Help a desperate newbie out? Combining codes questions

Posted 18 April 2019 - 12:46 PM

Okay..commenting like a mad man.. bit a short bit.
Was This Post Helpful? 0
  • +
  • -

#4 modi123_1   User is offline

  • Suitor #2
  • member icon



Reputation: 15062
  • View blog
  • Posts: 60,147
  • Joined: 12-June 08

Re: Help a desperate newbie out? Combining codes questions

Posted 18 April 2019 - 01:03 PM

GroupModel is an ancellary class.
    public class GroupModel
    {
        ///<summary>
        ///Gets or sets CustomerId
        ///</summary>
        public int GroupId { get; set; }

        ///<summary>
        ///Gets or sets GroupName
        /// </summary>
        [Display(Name = "Group Name")] //just an example of how data annotations change what the label displays to make pretty names.. default is variable name.
        public string GroupName { get; set; }

        ///<summary>
        ///Gets or sets AccessLevel
        /// </summary>
        public string AccessLevel { get; set; }
    }


HomeView is what is passed over hte fence between the server to client to server again.
I think of it like a bucket going over the fence.. or one of those kid 'shape sorter' things passed over the fence. Server sends it over to the client saying "hey.. here's things you would like to know and things I would like back".. anything else is ignored.

The client takes the bucket and does what ever. If it needs to return data on a POST call it better be shoving the right shape in the right hole.. so strings and integers to the right variables.

On a post then the server could have nothing back, new data, or old data.

In this case the model has some strings, a collection of 'groupmodel' which we intend to show, and a single groupmodel instance to get user input.

    public class HomeViewModel
    {

        public string TestRadioButtonselected { get; set; }

        public string Message { get; set; }

        [Display(Name = "Title")]
        public string TextBoxTest { get; set; }

        public List<GroupModel> myGroups { get; set; } // for the data to be pulled

        public GroupModel aNewGroup { get; set; } // need an object to collect user input for the return back to the server.

        public HomeViewModel()
        {
            myGroups = new List<GroupModel>();// collections maybe null so best to have a constructor that initializes it.
            aNewGroup = new GroupModel();// same with this object.
        }
    }
 


The client tries to make sense of the bucket of stuff passed over by the server. Displays what it can, waits for user input on the others, etc.
@model HomeViewModel
@{
    ViewData["Title"] = "Home";
}
<h1>@Model.Message</h1>


<h3>Test</h3><br />
<hr />


@{ /* For the most part, your model lives inside the 'begin form.
         It's usually a good idea to keep it all inside one form.
         When returning data on a submit it will only know what the user changed inside it's scope.
          */
}
@using (Html.BeginForm())
{

    @Html.LabelFor(a => a.TextBoxTest)@Html.TextBoxFor(z => z.TextBoxTest)
    <br /><br />


    @Html.RadioButtonFor(a => a.TestRadioButtonselected, "Users")@Html.Label("Users")
    @Html.RadioButtonFor(a => a.TestRadioButtonselected, "Quiz")@Html.Label("Quiz")
    <Br />
    <hr />

    @Html.LabelFor(a => a.aNewGroup.GroupId) @Html.TextBoxFor(a => a.aNewGroup.GroupId)<br />
    @Html.LabelFor(a => a.aNewGroup.GroupName) @Html.TextBoxFor(a => a.aNewGroup.GroupName)<br />
    @Html.LabelFor(a => a.aNewGroup.AccessLevel) @Html.TextBoxFor(a => a.aNewGroup.AccessLevel)<br />

    @* the table is just display so it can be inside or outside the form.
        <Br />
        <hr />
        @if (Model.myGroups.Count() > 0)
        {
            <hr />
            <table cellpadding="5" cellspacing="0">
                <tr>
                    <th>ID</th>
                    <th>Group Name</th>
                    <th>Access Level</th>
                </tr>
                @foreach (GroupModel groups in Model.myGroups)
                {
                    <tr>
                        <td>@groups.GroupId</td>
                        <td>@groups.GroupName</td>
                        <td>@groups.AccessLevel</td>
                    </tr>
                }
            </table>
        }
        else
        {
            <span>No groups!</span>
        }*@

    <br />
    <input type="submit" value="DoStuff" />


}
@*the table is just display so it can be inside or outside the form.*@
<Br />
<hr />
@if (Model.myGroups.Count() > 0)
{
    <hr />
    <table cellpadding="5" cellspacing="0">
        <tr>
            <th>ID</th>
            <th>Group Name</th>
            <th>Access Level</th>
        </tr>
        @foreach (GroupModel groups in Model.myGroups)
        {
            <tr>
                <td>@groups.GroupId</td>
                <td>@groups.GroupName</td>
                <td>@groups.AccessLevel</td>
            </tr>
        }
    </table>
}
else
{
    <span>No groups!</span>
}




The controler sets up the bucket to be passed over the fence. In the initial index the server makes an instance of the model to pass over the fence, but fill it with some data for testing.

On the POST index the bucket is passed back from the client and the server can do what ever. In this case I simply have it take the user input and add it to the collection, and pass that all back to the client to show.

    public class HomeController : Controller
    {
 
        public ActionResult Index()
        {
            HomeViewModel foo = new HomeViewModel();
            TestGroups(foo); // comment out to see 'no groups'.
            //needed to push out model so ASP.NET has container to get data back in on the post.

            // For tesitng I want to add a few groups to the collection, so comment this out and make it an actual object first. 
            //return View(new HomeViewModel());
            return View(foo);
        }

        [HttpPost]
        public ActionResult Index(HomeViewModel model)
        {
            //the post heaves the "model" back over the fence... you can pull information from it, add to it, and then shove it back out the door to be viewed.
            model.Message = $"This was radiobutton: {model.TestRadioButtonselected}.  This was the textbox: {model.TextBoxTest}";

            //you would think about adding to a db or what ever here, but instead I am just adding it to the non persistent collection.
            model.myGroups.Add(model.aNewGroup);
            
            return View(model);
        }

        private void TestGroups(HomeViewModel temp)
        {
            temp.myGroups.Add(new GroupModel()
            {
                AccessLevel = "NONE",
                GroupId = 1,
                GroupName = "Unwelcomed"
            });

            temp.myGroups.Add(new GroupModel()
            {
                AccessLevel = "READ",
                GroupId = 2,
                GroupName = "Read Only"
            });

        }

    }


It's weirdly grained pattern to do, but once it clicks (yeah yeah.. stupid, but it's what happened for me) it makes sense.

Model is your bucket passed between neighbors over a fence. neither neighbor interacts with each other but what ever is in the bucket.

Model = bucket.
Controller = server
View (HTML) = client
Was This Post Helpful? 1
  • +
  • -

#5 BenignDesign   User is online

  • holy shitin shishkebobs
  • member icon




Reputation: 7951
  • View blog
  • Posts: 12,382
  • Joined: 28-September 07

Re: Help a desperate newbie out? Combining codes questions

Posted 22 April 2019 - 08:47 AM

Okay, this is awesome and something I can modify for another piece of this project later, but the initial question - how to merge the two forms I've built from different tutorial sources remains unanswered.

Instead of text boxes, I need a file upload (from my OP):
 <label>File to Upload: </label><input type="file" name="postedFile" />


Is there a way to change this notation to match the @Html.RadioButtonFor / @Html.TextBoxFor style? I see options for check boxes, radio buttons, text boxes, drop downs, text areas, hidden fields, password fields, but nothing that jumps out as a file upload field.

And how do I incorporate the file processing code into the radio button processing code?
[HttpPost]
public ActionResult Index(HttpPostedFileBase postedFile)
{
	List<GroupModel> groups = new List<GroupModel>();
	string filePath = string.Empty;
	if(postedFile!=null)
	{
	     string path = Server.MapPath("~/Uploads/");
	     if(!Directory.Exists(path))
	     {
	           Directory.CreateDirectory(path);
	     }

	     filePath = path + Path.GetFileName(postedFile.FileName);
	     string extension = Path.GetExtension(postedFile.FileName);
	     postedFile.SaveAs(filePath);

	     //Read the contents of CSV file
	     string csvData = System.IO.File.ReadAllText(filePath);

	     //Execute a loop over the rows
	     foreach (string row in csvData.Split('\n'))
	     {
	            if(!string.IsNullOrEmpty(row))
	            {
	                  groups.Add(new GroupModel
	                  {
	                       GroupId = Convert.ToInt32(row.Split(',')[0]),
	                       GroupName = row.Split(',')[1],
	                       AccessLevel = row.Split(',')[2]
	                  });
	             }
	      }
	}       
	    return View(groups);
	}
}


I've tried creating another public ActionResult Index(), but the code isn't happy with this solution. I'm building a system that reads GroupID, GroupName, and AccessLevel from a CSV file. It reads beautifully in a solution by itself, but I can't figure out how to integrate it with this solution.

I get this error:

Quote

[AmbiguousMatchException: The current request for action 'Index' on controller type 'HomeController' is ambiguous between the following action methods:
System.Web.Mvc.ActionResult Index(WebApplication10.Models.HomeViewModel) on type WebApplication10.Controllers.HomeController
System.Web.Mvc.ActionResult Index(System.Web.HttpPostedFileBase) on type WebApplication10.Controllers.HomeController]


So, public ActionResult Index() and public ActionResult Index(HomeViewModel model) cause no issues at all. But adding public ActionResult Index(HttpPostedFileBase postedFile) makes it freak out. Why will it take two public ActionResult Index() calls, but not the third? I don't understand why it does this or how to fix it. Do I pass two parameters to the second function? As in publicActionResult Index(HomeViewModel model, HttpPostedFileBase postedFile) and cram everything into one function? Do I need to rename the Index(HttpPostedFileBase postedFile) to something else? If so, what are the rules around naming things to actually get them to display?
Was This Post Helpful? 0
  • +
  • -

#6 modi123_1   User is offline

  • Suitor #2
  • member icon



Reputation: 15062
  • View blog
  • Posts: 60,147
  • Joined: 12-June 08

Re: Help a desperate newbie out? Combining codes questions

Posted 22 April 2019 - 08:50 AM

I thought I merged those two and showed how to roll everything into one big ol' model worked. Drat.

Is the issue about file uploads? I maaaaaaaaaay have over looked that.
Was This Post Helpful? 1
  • +
  • -

#7 modi123_1   User is offline

  • Suitor #2
  • member icon



Reputation: 15062
  • View blog
  • Posts: 60,147
  • Joined: 12-June 08

Re: Help a desperate newbie out? Combining codes questions

Posted 22 April 2019 - 09:39 AM

With your form post you can manipulate that about a half dozen ways as everyone wanted their way in so MS obliged.

Example.. in the index.cshml you can have a form post like this, where you tell it what action (aka function) you want it to go look for. If you didn't mess with the routing in the startup.cs it should look for the 'index.html' first then shared then fail.

<form >
    <input type="submit" value="Submit Form" [email protected]("MyFunction") formmethod="post" />
</form>



In the home controller you would have a function (the action) that looks for.
        [HttpPost]
        public ActionResult MyFunction(HomeViewModel model)
        {
            model.Message = "You Hit MyFunction";

            return View("Index", model);
        }



This routing is what happens when you don't specify an action so it defaults to the index or constructor.

The same code can be written like this:

@using (Html.BeginForm())
{
    <input type="submit" value="Submit Form" [email protected]("MyFunction") formmethod="post" />
}


Another way is telling it the action name and what controller to find it.

@using (Html.BeginForm("MyFunction", "Home", FormMethod.Post))
{
    <input type="submit" value="Submit Form 2" formmethod="post" />
}


It's honestly not a bad system once you get into it, but it suffers from a lot of web based anythings.. there are more ways to do something than there should be and lord knows if it isn't provided pitchforks are dolled out.


Reading on it.
https://www.complete...asp-net-mvc.php
https://www.aspsnipp...st-example.aspx



Let me poke at the file upload.. I believe that's a weird mesh of mashing in JS into ASP.NET so the syntax is jarring.

The important part is remembering with MVC it's all about the model swapping from each side of the fence and framing everything you do as such. So not regular programming where you jam in what ever parameters - f or the most part it should all be in the model.

The big win with that is you know what data to push back into the controls after post so the user doesn't have their screen wiped after hitting 'upload'.
Was This Post Helpful? 1
  • +
  • -

#8 BenignDesign   User is online

  • holy shitin shishkebobs
  • member icon




Reputation: 7951
  • View blog
  • Posts: 12,382
  • Joined: 28-September 07

Re: Help a desperate newbie out? Combining codes questions

Posted 22 April 2019 - 10:14 AM

View Postmodi123_1, on 22 April 2019 - 11:39 AM, said:



This page is beautiful. Like joyfully, almost brought me to tears, beautiful. I've done a metric shitton and a half of tutorials, I've seen and heard the terms weakly typed and strongly typed, but this is the first thing I've seen that shows what that means. And it breaks down each piece and tells me what the crap it's there for. I love this page. Thank you!
Was This Post Helpful? 1
  • +
  • -

#9 modi123_1   User is offline

  • Suitor #2
  • member icon



Reputation: 15062
  • View blog
  • Posts: 60,147
  • Joined: 12-June 08

Re: Help a desperate newbie out? Combining codes questions

Posted 22 April 2019 - 10:19 AM

So .. the file upload. FYI - working off my project I sent to you as a zip.

I have an idea where you got that, but here's a better link.

https://www.c-sharpc...p-net-core-2-0/

This is getting into the weeds, but it reasons fine.

Okay.. so your WWWROOT is like any old web page root folder. put a folder in there (right click on wwwroot -> add -> new folder) called 'test'.

In the home controller add a new function. A basic 'yoink' from the code above with some additions. First adding in the 'homeviewmodel' to the parameters. Again you find a time you don't need it, but that bucket is always passed back from the client to the server so it's a nice reminder it's there.

Also it helps us pass what the user entered (but not saved) back to the client so they don't gripe the form upload blows out there data. You can totes test this by filling out the textboxes, clicking the 'upload file submit' and seeing it all return.

You'll eventually want to have everything as an async (db interactions, file uploads, etc) to keep the machine well running.. so just fudged a bit here.

The method(action) changes a bit to be a task, but nothing tragic.

   [HttpPost]
        public async Task<IActionResult> MyFunction2(IFormFile file, HomeViewModel temp)
        {
            if (file == null || file.Length == 0)
                return Content("file not selected");

            var path = Path.Combine(
                        Directory.GetCurrentDirectory(), "wwwroot\\test",
                        file.FileName);

            using (var stream = new FileStream(path, FileMode.Create))
            {
                //  await file.CopyToAsync(stream);
                await Task.Run(() =>
                {
                    file.CopyTo(stream);
                    // or store the name to a db.
                    // or do what ever.
                });

            }
            // added to let us know what happened.
            temp.Message = $"{ file.FileName} - file uploaded";
            return View("Index", temp);
        } 


inside my index.cshtml I did a few things.

My main 'using / beginform' points to the myfunction from above... but since this will also need form data I chucked in that attribute.

@using (Html.BeginForm("MyFunction", "Home", FormMethod.Post, new { enctype = "multipart/form-data" }))


scrolling past all that existing crud, I have my section for the file upload. The key point is that the submit I want here is not the same submit for the form as a whole. I want it going to myfunction2 so I used the previous post about overriding input submits and said "do this!".

 <br />
    <hr />
    <div style="background-color:aliceblue;">
        <input type="file" name="file" /><br />
        <input type="submit" value="Upload" class="save" id="btnid" [email protected]("MyFunction2") formmethod="post" />
    </div>



In theory that all should work well.

Spoiler



If you want you can save the file name to a string in your homeviewmodel to be saved to a DB.. shown other places, etc.. but for the most part the model sticks out of it outside of carrying our message.
Was This Post Helpful? 0
  • +
  • -

Page 1 of 1