简体   繁体   中英

Populating Dropdown with a CSV File using MVC

I'm working with MVC for the first time, and I'm having a little trouble populating a dropdown list with values from a csv file. The csv file is two columns, the first being location names, the second being isOpen containing only 1s and 0s.

This was my first attempt in my View:

<div class="col-md-10">
    @{         
        List<SelectListItem> locations = new List<SelectListItem>();
        if (System.IO.File.Exists("~/Data/locations.csv"))
        {
            string[] lines = System.IO.File.ReadAllLines("~/Data/locations.csv");
            foreach (string line in lines)
            {
                string[] data = line.Split(',');
                if (data[1] == "1")
                {
                    locations.Add(new SelectListItem { Text = data[0], Value = data[0] });
                }
            }
        }
        Html.DropDownListFor(m => m.location, locations, new { htmlAttributes = new { @class = "form-control" } });
        Html.ValidationMessageFor(model => model.location, "", new { @class = "text-danger" });
    }
</div>

As of now, none of the code runs because the File.Exists function automatically defaults to to IIS Express folder and returns false. I couldn't find a simple answer when I researched, but is there a way to properly direct to my project folder instead? Or would it be better practice to have this code in my Controller?

public class Location
{
    //properties here that describe a location
}

public class CsvHelperLocationRepository : ILocationRepository
{
    private readonly string _dataFileLocation;

    public CsvHelperLocationRepository(string dataFileLocation)
    {
        _dataFileLocation = dataFileLocation;
    }

    public List<Location> GetLocations()
    {
        //use CsvHelper here to parse the CSV file and generate a list of Location objects to return
    }
}

public interface ILocationRepository
{
    List<Location> GetLocations();
}

public HomeController : Controller
{
    private readonly ILocationRepository _locationRepository;

    public HomeController()
    {
         //you really should use dependency injection instead of direct dependency like below
         _locationRepository = new CsvHelperLocationRepository(Server.MapPath("~/data/locations.csv");
    }

    public ActionResult SomeAction()
    {
        var model = new MyViewModel();
        model.Locations = _locationRepository.GetLocations();
        return View(model);
    }
}

You can use Server.MapPath to convert from an application root relative URL ( ~/ ) to an absolute path that System.IO can understand.

System.IO doesn't understand the ~/ syntax. You'll need to pre-process the file path before handing it to System.IO. That's where Server.MapPath comes in. Additionally, this sort of logic does not belong in a view. Instead, you should have a class responsible for reading data from the data source. Your controller should then utilize that class to retrieve the data it needs, add that data to the view model, and then the view should display that information from the view model. And lastly, don't parse CSV files manually yourself. That's a recipe for disaster. Use a library that handles the nuances for you, like CsvHelper .

Implementing the code as I have described will be cleaner (following the Single Responsibility Principle) and result in a more unit testable and easier to maintain application.

You should use a method like Server.MapPath to get the physical path of the file. If your Data directory is in your app root, you may use the ~ prefix with the file location, so that it gets the path from your app root.

var loc = Server.MapPath("~/Data/locations.csv");
if (System.IO.File.Exists(loc))
{
    string[] lines = System.IO.File.ReadAllLines(loc);
    foreach (string line in lines)
    {
        string[] data = line.Split(',');
        if (data[1] == "1")
        {
            locations.Add(new SelectListItem { Text = data[0], Value = data[0] });
        }
    }
}

While this will fix the problem, I strongly suggest you to contemplate on masons comment. You should abstract reading the CSV file to another class ( Single responsibility principle ) and simply use that class as needed. If you wrap it with an interface, you can easily switch your implementation with another robust/tested implementation later easily without changing a lot of code.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM