January 2018 : Instructor-led Online Course in ASP.NET Core 2.0. Conducted by Bipin Joshi. Read more...
Registration for January 2018 batch of ASP.NET Core 2.0 instructor-led online course has already started. Conducted by Bipin Joshi. Register today ! Click here for more details.

Accept / Reject File Upload Depending Upon XML Schema Validation

Uploading files from the client machine onto the server is a fairly common task in web applications. Recently I came across such an application where the end users are required to upload XML files from their machine onto the server. These XML files were produced as a result of some export operation of a desktop application installed on their machines. These XML files then used to get imported in some central database for further processing.  Uploading the file is a quite straightforward thing but in this case it was also required to validate these uploaded files against an XML schema (XSD). This was a safeguard against manual or accidental tampering of the files that might take place at the end user's side. If a file is found to be invalid the it shouldn't be accepted in the system for obvious reason. This article discusses a simple solution to accomplish this task.

Have a look at the following figure:

The above page allows you to upload one or more files on the server. Before the files are saved on the server you check whether the uploaded file(s) is a valid XML document. You do this by validating the incoming file against an XML schema. You accept only those files that are valid as per the schema rules. The success and error messages are displayed on the page to keep the user informed of the outcome.

To build this sample application, create a new ASP.NET MVC project in Visual Studio. Add HomeController and Index view as you normally do. Then open the Index.cshtml file and enter the following markup and code:

@model List<string>

@{
    Layout = null;
}

<!DOCTYPE html>
...
<body>
    <h1>Upload XML Files</h1>
    @using (Html.BeginForm("Upload", "Home", 
FormMethod.Post, new { enctype = "multipart/form-data" }))
    {
        <h3>Select file(s) to upload :</h3>
        @Html.TextBox("file", "", 
new { type = "file",multiple="multiple" }) 
        <br /><br />
        <input type="submit" value="Upload Selected Files" />
        <br />
        if (Model != null)
        {
            <ul>
            @foreach (string s in Model)
            {
                <li><strong>@s</strong></li>
            }
            </ul>
        }
    }
</body>
</html>

The Index view receives a List of string messages from the controller (if any) as its model. The Index view basically renders a <form> using the BeginForm() helper. Notice the parameters of the BeginForm(). The form will be posted to Upload() action of the HomeController. Also, the enctype of the <form> is set to multipart/form-data since we wish to upload files.

The file field is rendered using the TextBox() helper. The type attribute is changed from the default of text to file and its multiple attribute is also set to allow multiple file selection. A submit button submits the form. Below the submit button there is a foreach loop they outputs all the success or error messages.

So far so good. Now let's focus on the more important part - validating XML file against an XSD schema.

As an example we will use the following XML document :

<?xml version="1.0" encoding="utf-8" ?>
<employees>
  <employee employeeid="1">
    <firstname>Nancy</firstname>
    <lastname>Davolio</lastname>
  </employee>
  <employee employeeid="2">
    <firstname>Andrew</firstname>
    <lastname>Fuller</lastname>
  </employee>
  <employee employeeid="3">
    <firstname>Janet</firstname>
    <lastname>Leverling</lastname>
  </employee>
</employees>

This is a simple XML document with root element of <employees>. Each employee is wrapped in the <employee> element. The employeeid attribute holds an employee's ID. The <firstname> and <lastname> hold the employee's First Name and Last Name respectively. The end user is supposed to upload XML files matching this structure.

Now we want to ensure that the uploaded files are indeed match this structure. This validation is done with the help of the following XML Schema :

<?xml version="1.0" encoding="utf-8"?>
<xs:schema attributeFormDefault="unqualified" 
elementFormDefault="qualified" 
xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="employees">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="employee" 
type="EmployeeType" minOccurs="0" 
maxOccurs="unbounded" />
      </xs:sequence>
    </xs:complexType>
  </xs:element>
  <xs:complexType name="EmployeeType">
    <xs:all>
      <xs:element name="firstname" type="NameSimpleType" />
      <xs:element name="lastname" type="NameSimpleType" />
    </xs:all>
    <xs:attribute name="employeeid" 
type="xs:int" use="required" />
  </xs:complexType>
  <xs:simpleType name="NameSimpleType">
    <xs:restriction base="xs:string">
      <xs:minLength value="3" />
      <xs:maxLength value="255" />
    </xs:restriction>
  </xs:simpleType>
</xs:schema>

We won't go into the details of this schema. Just add this schema markup into Employees.xsd and store it somewhere in the project folder. Also create two sample XML files - Employees1.xml and Employees2.xml - by pasting the XML markup shown earlier. In one of the files remove the employeeid so that the validation will fail as per schema rules.

Ok. Now open the HomeController and add the following code to it.

public class HomeController : Controller
{
  private List<string> messages = new List<string>();
  private string currentFileName = "";
  private bool flag = false;

  public ActionResult Index()
  {
    return View();
 }

  [HttpPost]
  public ActionResult Upload()
  {
    var postedFiles = Request.Files;

    for(int i=0;i<postedFiles.Count;i++)
    {
        HttpPostedFileBase file = postedFiles[i];
        currentFileName = Path.GetFileName(file.FileName);
               
        string xmlPath = Server.MapPath
($"~/XmlData/{currentFileName}");
        string xsdPath = Server.MapPath
($"~/XmlData/Employees.xsd");

        XmlReaderSettings settings = new XmlReaderSettings();
        settings.ValidationType = ValidationType.Schema;
        settings.Schemas.Add("", xsdPath);
        settings.ValidationEventHandler += 
new ValidationEventHandler(OnValidationError);

        XmlReader reader = XmlReader.Create
(file.InputStream, settings);
        while (reader.Read())
        {
        }
        reader.Close();

        if (flag)
        {
            messages.Add($"Schema validation 
failed for {currentFileName}");
            //do not save file on the server
        }
        else
        {
            file.SaveAs(xmlPath);
            messages.Add($"Validation 
success for {currentFileName}");
        }

       flag = false;
    }

    return View("Index",messages);
}


void OnValidationError(object sender, 
ValidationEventArgs e)
{
    flag = true;
    messages.Add($"ERROR : {currentFileName}
 -- {e.Message}");
}

The code declares three private variables inside the HomeController class. The messages List is intended to store success as well as error messages. These messages will be shown to the user by outputting them onto the Index view. Why do we need a class level variable? That's because the validation event handler also needs access to this List. You will understand this when we discuss the code further. The currentFileName variable holds the name of the file that is being validated. This variable, too, is declared at class level for the reason mentioned earlier. The flag Boolean variable is just used to detect whether a file is done with the validation or not.

The Index() action is quite straightforward and simple displays the Index view in the browser.

The <form> is posted to the Upload() method. This is where the XML document validation and file upload takes place. Let's see how.

The code retrieves a list of files posted to the server using the Request.Files collection. A for loop iterates through these files one-by-one. Each file from the Files collection is HttpPostedFileBase object. The FileName (excluding the client side path) is stored in the currentFileName variable. This file name is obtained using the GetFileName() method of the Path class.

Then the server side path of the XML file is determined using Server.MapaPath() method. This is where the uploaded file will be saved if the schema validation succeeds. In the above example the XML files will be stored in the XmlData folder under the project root. On the same lines the physical path of Employees.xsd is determined. This path is needed in the later part of the code.

Then the code creates XmlReaderSettings object. This object contains the settings to be used while performing the validation. The ValidationType property is set to Schema. Since we wish to validate the incoming XML documents against Employees.xsd we add the schema file in the Schemas collection. The Schemas property is actually XmlSchemaSet collection.

Then the ValidationEventHandler of the XmlReaderSettings is wired to OnValidationError method. The ValidationEventHandler event is raised during the validation process whenever there is any validation error. The OnValidationError() method is discussed later.

Then an XmlReader object is created. Notice the two parameter of the XmlReader constructor. The first parameter is the InputStream of the incoming file. Since the file is not yet "accepted" by the system it's not physically saved onto the server. Hence, we pass the file's InputStream. The second parameter is the XmlReaderSettings object.

A while() loop runs the XmlReader's Read() method. The Read() method returns false when the XmlReader reaches the end of the stream. Once the validation is over the code closes the XmlReader by calling its Close() method.

If the OnValidationError() has set the flag variable to true, it indicates that the current file has validation errors. If so, we add a failure message in the List. If the flag is false it means there were no validation errors. We then save the incoming file using the SaveAs() method of HttpPostedFileBase object. We also add a success message in the messages List. Once the current file is over we reset the flag variable to false.

Finally, the Index view is displayed back to the user. This time messages List is passed to the Index view as its model.

The OnValidationError() event handler receives ValidationEventArgs parameter. The Message property of ValidationEventArgs tells us what went wrong during the validation. We add this message and the currentFileName to the messages List.

This completes the example. Run it and pick both the files - Employees1.xml and Employees2.xml. You should see success and error messages as shown in the figure earlier.

That's it for now! Keep coding !!


Bipin Joshi is a software consultant, an author and a yoga mentor having 22+ years of experience in software development. He also conducts online courses in ASP.NET MVC / Core and Design Patterns. He is a published author and has authored or co-authored books for Apress and Wrox press. Having embraced the Yoga way of life he also teaches Meditation and Mindfulness to interested individuals. To know more about him click here.

Get connected : Twitter  Facebook  Google+  LinkedIn

Posted On : 07 November 2017


Tags : ASP.NET MVC .NET Framework C#