This is a migrated thread and some comments may be shown as answers.

How to use MVC Upload control in MVC Grid?

7 Answers 340 Views
Upload
This is a migrated thread and some comments may be shown as answers.
This question is locked. New answers and comments are not allowed.
Troy
Top achievements
Rank 1
Troy asked on 24 Aug 2011, 05:51 AM
I am trying to figure out how to use an MVC Upload control in an Ajax-bound MVC Grid, which is using pop-up edit mode.

I've managed to get the upload control  to display successfully in the pop-up window, when I am in edit mode. The uploaded files themselves are being successfully uploaded and saved to my hard drive file structure great via the upload control. Now, however, I am simply trying to save the name of the file that is uploaded to my database, but can't figure out how to associate the uploaded file name with the column in the grid, called ""ProductImagePath," so that it can be returned via ModelBinding to my controller action. The rest of the values in the row being edited are changed and persisted great. It's just the Upload field that I am having trouble with.

Does anyone know how to accomplish this, or to get around this problem?

Here is what I think is the relevant code:

Grid Controller Edit Action:

//
// POST: /Product/Edit/5
[HttpPost]
[GridAction]
public ActionResult Edit(int id)
{
    if (ModelState.IsValid)
    {
        try
        {
            var product = _productService.GetProductByID(id);
            if (product != null)
            {
                TryUpdateModel(product); // "ProductImagePath" is always NULL
                _productService.Save();
            }
        }
        catch
        {
            throw new Exception("POST: /Product/Edit failed.");
            // TODO: log error
        }
    }
 
    return View(new GridModel<ProductPresentationModel>
    {
        Data = _mapper.MapAsList(_productService.GetProducts().ToList())
    });
}


Grid and Upload onSuccess JavaScript:

@(Html.Telerik().Grid<ProductPresentationModel>()
    .HtmlAttributes(new { style = "width: 100%;" })
    // Give the Grid an HTML id attribute value
    .Name("ProductGrid")
    // Establish the promiry key, to be used for Insert, Update, and Delete commands
    .DataKeys(dataKeys => dataKeys.Add(p => p.ProductID))
    // Add an Insert command to the Grid Toolbar
    .ToolBar(commands => commands.Insert().ButtonType(GridButtonType.Image))
    // Using Ajax Data Binding to bind data to the grid
    .DataBinding(dataBinding => dataBinding
        // Ajax Binding
        .Ajax()
        .Select("_Index", "Product")
        // Home.Insert inserts a new data record
        .Insert("Create", "Product")
        // Home.Update updates an existing data record
        .Update("Edit", "Product")
        // Home.Delete deletes an existing data record
        .Delete("Delete", "Product")
    )
    .Columns(columns =>
    {
        columns.Bound(p => p.ProductImagePath).Width(120) //Using ClientTemplate to display the image, once it is uploaded and saved to database
            .ClientTemplate(@"<img alt='Product Category Image' src='" + Url.Content("~/Content/Images/ProductImages/") + "<#= ProductImagePath #>' />");
        columns.Bound(p => p.ProductName).Width(150);
        columns.Bound(p => p.ProductDescription).Width(150);
        columns.Bound(p => p.PricePerMonth).Width(100);
        columns.Bound(p => p.ProductActive).Width(60)
            .ClientTemplate("<input type='checkbox' disabled='disabled' name='Active' <#= ProductActive ? checked='checked' : '' #> />");
        columns.Bound(p => p.ProductCategoryID).Width(150);
        columns.Command(commands =>
        {
            commands.Edit().ButtonType(GridButtonType.Image);
            commands.Delete().ButtonType(GridButtonType.Image);
        }).Width(70);
    })
    .Editable(editing => editing.Mode(GridEditMode.PopUp))
    .ClientEvents(events => events.OnEdit("onEdit")) // Controls are swapped out for EditorTemplates
    .Pageable()
    .Scrollable()
    .Sortable()
    .Filterable()
)
 
@section HeadContent {
<script type="text/javascript">
 
    /*** ProductImagePath control ***/
    var lastUploadedFile;
    function onUploadSuccess(e) {
        var fileName = e.files[0].name; // This gets the filename perfectly, but now what?
        // e.dataItem['ProductImagePath'] = fileName; // Not valid with the upload control?
    }
</script>

Upload Editor Template:

@(Html.Telerik().Upload()
        .Name(ViewData["uploadTemplateName"].ToString())
    .Multiple((bool)ViewData["multiFileUpload"])
    .Async(async => async
        .Save("Save", "Upload", new { folder = ViewData["uploadFolder"] })
        .Remove("Remove", "Upload", new { folder = ViewData["uploadFolder"] })
        .AutoUpload(true)
    )
      // JavaScript "onUploadSuccess" is on the grid page, not in this editor template page
    .ClientEvents(e => e.OnUpload("onUpload").OnSuccess("onUploadSuccess"))
)

Upload Controller Save Action:

public ActionResult Save(IEnumerable<HttpPostedFileBase> files, string folder)
{
    // The Name of the Upload component is "files"
    foreach (var file in files)
    {
        // Some browsers send file names with full path. This needs to be stripped.
        var fileName = Path.GetFileName(file.FileName);
        if (fileName != null)
        {
            var physicalPath = Path.Combine(Server.MapPath("~/Content/Images/" + folder), fileName);
 
            // Save the file
            file.SaveAs(physicalPath);
 
            // Save the filename in the database. I can't get a hold of the correct row item here,
                // so this doesn't seem to work ...
            //SaveUploadedFileInDatabase(fileName, folder);
        }
    }
    // Return an empty string to signify success
    return Content("");
}

7 Answers, 1 is accepted

Sort by
0
T. Tsonev
Telerik team
answered on 24 Aug 2011, 04:42 PM
Hello Troy,

The Upload in Grid code library demonstrates how to associate files with grid records both on insert and on edit.

The relevant code is in the onSuccess and onGridSave handlers:

var lastUploadedFile;
function onSuccess(e) {
    var fileName = e.response.fileName;
     
    var grid = $(this).closest(".t-grid").data("tGrid");
    var tr = $(this).closest("tr");
    var dataItem = grid.dataItem(tr);
 
    if (dataItem) {
        // Store the file name in the grid data item (on Edit)
        grid.dataItem(tr).Picture = fileName;
    }
 
    // Store the file name in case no data item exists (on Insert)
    lastUploadedFile = fileName;
}
 
function onGridSave(e) {
    var values = e.values;
    if (!values.Picture) {
        // Retrieve the inserted picture name
        values.Picture = lastUploadedFile;
    }
}

I hope this helps. Kind regards,
Tsvetomir Tsonev
the Telerik team

Thank you for being the most amazing .NET community! Your unfailing support is what helps us charge forward! We'd appreciate your vote for Telerik in this year's DevProConnections Awards. We are competing in mind-blowing 20 categories and every vote counts! VOTE for Telerik NOW >>

0
Troy
Top achievements
Rank 1
answered on 24 Aug 2011, 08:27 PM
Tsvetomir,

Thank you for your reply. 

I tried this previously, and had a small problem. For some reason (perhaps because I am using pop-up editing?), the "grid" variable in the onSuccess method is always undefined when I test this code. Do you know if this is because I am using pop-up editing? Or, do you have any ideas as to why that might be happening?  

The code that I am using in my tests is exactly the same as I posted originally, except I changed my onSuccess method to match the one you provided, from the Upload in Grid code library.
0
T. Tsonev
Telerik team
answered on 26 Aug 2011, 08:31 AM
Hi Troy,

The pop-up edit form will indeed break the demo code. This line assumes the grid to be one of the parent elements which is not the case for the pop-up form:

var grid = $(this).closest(".t-grid").data("tGrid");

Locating the grid by it's ID should will work reliably:
var grid = $("#ProductGrid").data("tGrid");

I hope this helps.

Regards,
Tsvetomir Tsonev
the Telerik team

Thank you for being the most amazing .NET community! Your unfailing support is what helps us charge forward! We'd appreciate your vote for Telerik in this year's DevProConnections Awards. We are competing in mind-blowing 20 categories and every vote counts! VOTE for Telerik NOW >>

0
Troy
Top achievements
Rank 1
answered on 28 Aug 2011, 12:24 AM
Thank you, Tsvetomir. That was very helpful. I changed my JavaScript to find the grid as you suggested, and now I seem to be able to get a hold of the image value relaibly. This is also allowing me to update the value in my database. So, thank you!

However, I am now seeing an error, which was not occurring before this change. The error is a JavaScript error, which I am seeing in FireFox FireBug, and the error text is:

Security error"  code: "1000
http://aspnet-scripts.telerikstatic.com/mvcz/2011.2.712/jquery-1.5.1.min.js
Line 16


This error is causing some erratic behavior in my application, and so I need to resolve it. Note that this error is occurring inside a jquery file hosted by Telerik.

If I can get this resolved, I will have been able to get my project to a point in which I will be able to justify purchasing a commercial license of this MVC package, which I really want to do. So, working through this will lead to a win-win for everyone!

To reproduce this error, the following can be done in the attached sample project:

  1. Open the sample solution and play to start debugging. Make sure the test is conducted in FireFox, with FireBug enabled
  2. From the application Home page, click "Products & Pricing"
  3. In the Products Grid click the edit pencil for a row that has an image in the Image field.
  4. A pop-up edit window should appear. At this point, look in FireBug, under Console >> Errors, and note the script error.

I cannot tell which portion of the jquery script is causing a problem because the file is minimized. I also cannot make changes to the file since it is a jquery library and is hosted by Telerik. Because of this, troubleshooting this problem is very difficult, and so I am REALLY hoping you can help.

I have attached a sample project, which can be used to see this error, but the relevant JavaScript that has changed is:

var lastUploadedFile;
 
function onUpload(e) {
    var fileName = e.files[0].name;
  
    var grid = $("#ProductGrid").data("tGrid");
    var tr = $(this).closest("tr");
    var dataItem = grid.dataItem(tr);
  
    if (dataItem) {
        // Store the file name in the grid data item (on Edit)
        grid.dataItem(tr).ProductImagePath = fileName;
    }
  
    // Store the file name in case no data item exists (on Insert)
    lastUploadedFile = fileName;
}
  
function onGridSave(e) {
    var values = e.values;
    if (!values.ProductImagePath) {
        // Retrieve the inserted picture name
        values.ProductImagePath = lastUploadedFile;
     }
}
0
Troy
Top achievements
Rank 1
answered on 28 Aug 2011, 09:03 PM
I've tried to look into this a bit more, and have some new details for consideration. I don't know why yet, but it appears that the inclusion of an Editor Template for the Upload control, when in Pop-Up or InLine editing mode, causes the JQuery security error cited above. I know this because the error goes away when I comment out the reference to it in my ProductPresentationModel, like this ...

[DisplayName(@"Image")]
//[UIHint("UploadEditorTemplate")]
[DataType(DataType.Text),
    MaxLength(1024, ErrorMessage = @"Product Image URL cannot be more than 1024 characters in length")]
public string ProductImagePath
{
    get { return _model.ProductImagePath; }
 
    set { _model.ProductImagePath = value; }
}

The Upload Template itself looks fine to me. Here is the code for it:

@(Html.Telerik().Upload()
    .Name("ProductImagePath")
    .Multiple(true)
    .Async(async => async
        .Save("ProductImageSave", "Product", new { folder = "ProductImages" })
        .Remove("ProductImageRemove", "Product", new { folder = "ProductImages" })
        .AutoUpload(true)
    )
    .ClientEvents(e => e.OnUpload("onUpload"))
)


Here is the HTML used when the Editor Template is in place:

<div class="editor-field">
    <div class="t-widget t-upload">
        <div class="t-button t-upload-button">
            <span>Select...</span>
            <input id="ProductImagePath" type="file" name="ProductImagePath" multiple="" autocomplete="off">
        </div>
    </div>
    <span class="field-validation-valid" data-valmsg-replace="true" data-valmsg-for="ProductImagePath"></span>
</div>


Here is the HTML used without the Editor template, so just a text box is used:

<div class="editor-field">
    <input id="ProductImagePath" class="text-box single-line" type="text" value="" name="ProductImagePath">
    <span class="field-validation-valid" data-valmsg-replace="true" data-valmsg-for="ProductImagePath"></span>
</div>


At this point, I'm really at a loss with this. I could be wrong, but it appears that there is something happening in a "black box" when the Telerik MVC Upload control is rendered, which is causing the JQuery security error. This is, in-turn breaking the editing in the Telerik MVC Grid.

Can you help me with this, using the sample project that I included in my previous post as a research tool?
0
Accepted
T. Tsonev
Telerik team
answered on 29 Aug 2011, 12:13 PM
Hello Troy,

The problem turned out to be in the Grid editing code - it tries to set a value to the file input. This is not allowed by browsers for security reasons.

We've fixed the problem and I'm sending you an updated build, so you can give it a try. Note that you'll have to set useTelerikContentDeliveryNetwork to false in web.config.

As a token of gratitude for your involvement your Telerik points have been updated.

Greetings,
Tsvetomir Tsonev
the Telerik team

Thank you for being the most amazing .NET community! Your unfailing support is what helps us charge forward! We'd appreciate your vote for Telerik in this year's DevProConnections Awards. We are competing in mind-blowing 20 categories and every vote counts! VOTE for Telerik NOW >>

0
Troy
Top achievements
Rank 1
answered on 30 Aug 2011, 05:17 AM
This fix worked perfectly! Thank you!
Tags
Upload
Asked by
Troy
Top achievements
Rank 1
Answers by
T. Tsonev
Telerik team
Troy
Top achievements
Rank 1
Share this question
or