4

I have a partialview where it's possible to change a Connection String. When submitting an Edit action is called. From here I want to either return and re-open the partial view if I want the user to have a second go. If everything went well (or crashing) I want to call my JavaScript function Logout, that logs the user out and redirect to some startpage.

Both solutions works, just not together. I'm clearly missing some best practice, what should I do?

Partial View: EditSetting

@model WebConsole.ViewModels.Setting.SettingViewModel

@using (Ajax.BeginForm("Edit", "Setting", new AjaxOptions { UpdateTargetId = "div" }, new { id = "editform" }))
{
    <fieldset>
        @Html.AntiForgeryToken()

        <div class="form-horizontal">
            @Html.ValidationSummary(true, "", new {@class = "text-danger"})

            <div class="form-group">
                @Html.LabelFor(model => model.User, htmlAttributes: new {@class = "control-label col-md-2"})
                <div class="col-md-10">
                    @Html.EditorFor(model => model.User, new {htmlAttributes = new {@class = "form-control"}})
                    @Html.ValidationMessageFor(model => model.User, "", new {@class = "text-danger"})
                </div>
            </div>

            <div class="form-group">
                @Html.LabelFor(model => model.Password, htmlAttributes: new {@class = "control-label col-md-2"})
                <div class="col-md-10">
                    <input type="password" name="Password" id="Password" value=""/>
                    @Html.ValidationMessageFor(model => model.Password, "", new {@class = "text-danger"})
                </div>
            </div>

            <div class="form-group">
                @Html.LabelFor(model => model.DataSource, htmlAttributes: new {@class = "control-label col-md-2"})
                <div class="col-md-10">
                    @Html.EditorFor(model => model.DataSource, new {htmlAttributes = new {@class = "form-control"}})
                    @Html.ValidationMessageFor(model => model.DataSource, "", new {@class = "text-danger"})
                </div>
            </div>

            <div class="form-group">
                @Html.LabelFor(model => model.InitialCatalog, htmlAttributes: new {@class = "control-label col-md-2"})
                <div class="col-md-10">
                    @Html.EditorFor(model => model.InitialCatalog, new {htmlAttributes = new {@class = "form-control"}})
                    @Html.ValidationMessageFor(model => model.InitialCatalog, "", new {@class = "text-danger"})
                </div>
            </div>
        </div>
    </fieldset>
}

JavaScript: Submit

$('form').submit(function () {
    var $self = $(this);

    if ($(this).valid()) {

        // Change Connection String
        $.ajax({
            url: this.action,
            type: this.method,
            data: $(this).serialize(),
            success: function (message) {

                // Use Partial View
                //$('#myModal .modal-body').html(message);

                // Conn Str is now changed. Log out and redirect
                logOut($self, message);
            },
            error: function (message) {
                logOut($self, message);
            }
        });
    }
    return false;
});

Action: Edit

[HttpPost]
public ActionResult Edit(SettingViewModel model)
{
    // Validate inputs
    if (!ModelState.IsValid)
    {
        ModelState.AddModelError("", @"Not all inputs are valid.");
        return PartialView("EditSetting", model);
    }

    var sql = new DAL.SQL(DAL.SQL.GenerateConnectionString(model.DataSource, model.InitialCatalog, model.User, SecurePassword(model.Password)));

    // Validate Connection String
    if (!sql.Open())
    {
        ModelState.AddModelError("", @"Error. Unable to open connection to Database.");
        return PartialView("EditSetting", model);
    }

    // Validate a transaction
    if (!sql.IsRunningTransact())
    {
        ModelState.AddModelError("", @"Error. Unable to connect to Database Server.");
        return PartialView("EditSetting", model);
    }

    // Save Connection String
    BuildAndEncryptConnString(model);

    return Content("The Connection String is changed. Log in again to continue.");
}
radbyx
  • 9,352
  • 21
  • 84
  • 127
  • 1
    Take a look at this answer: http://stackoverflow.com/a/5359628/5071902 – Renan Araújo Dec 03 '15 at 13:09
  • @RenanAraújo I don't need to render the view as a string. I need to know client-side if my result(message) there is returned from the action, is a partial view i need to render again, or it's a message i need to prompt to the user, before I log the user out and redirect him to the startpage. :) – radbyx Dec 03 '15 at 14:47
  • @RenanAraújo Please tell me if i'm wrong, I just say it as I see it as for now. Maybe I don't understand it yet. – radbyx Dec 03 '15 at 14:48
  • 1
    Hi @radbyx I posted an answer, It was too long to say in a comment :) – Renan Araújo Dec 03 '15 at 16:38
  • The real hidden bug was here, I found it after 2½ days! "UpdateTargetId = "div"", for somehow it doesn't work with "$('#myModal .modal-body').html(message);" The partial view is rendered and it looks like everything is fine, but the SECOND ajax call don't return. It does now with using the "UpdateTargetId". :) – radbyx Dec 06 '15 at 14:15

3 Answers3

9

Using the extension method RenderToString and basead on cacois answer, you can create your action like this:

public ActionResult Edit(SettingViewModel model)
{
    // "Ifs" to return only partials
    if (ModelState.IsValid)
    {
        return PartialView("EditSetting", model);
    }

    ...

    // Returning a Json with status (success, error, etc), message, and the content of 
    // your ajax, in your case will be a PartialView in string
    return Json(new { 
               Status = 1, 
               Message = "error message", 
               AjaxReturn = PartialView("EditSetting", model).RenderToString()});
}

Ps. I suggest you to create a model to define the Ajax return, with Status, Message and AjaxReturn. With that, your ajax requests will always return the same object type. For the Status property you can create a Enum.

Your ajax request, will be like this:

$.ajax({
    url: this.action,
    type: this.method,
    data: $(this).serialize(),
    success: function (data) {
        if(data.Message == undefined) {
            // Use data like a partial
        } else {
            // Use data.Message for the message and data.AjaxReturn for the partial
        }
    },
    error: function (message) {
        logOut($self, message);
    }
});
radbyx
  • 9,352
  • 21
  • 84
  • 127
Renan Araújo
  • 3,533
  • 11
  • 39
  • 49
  • 1
    Instead of "AjaxReturn = RenderViewToString(PartialView("EditSetting", model))});" did you mean: "AjaxReturn = RenderViewToString(PartialView("EditSetting", model).ViewName, model)});" – radbyx Dec 04 '15 at 09:55
  • Your code works with the edit I made except in the case where the user misstyped a input wrong in the first attempt. Maybe it's my edit, where I used the ".ViewName" or it might be the "$self" that isn't remembered anymore in the javascript Closure, I don't know? – radbyx Dec 04 '15 at 10:08
  • 1
    You are correct, to use that method you need to use it like you did. I changed the method to `RenderToString` created by Ted Nyberg, I think it's more cleaner now – Renan Araújo Dec 04 '15 at 11:41
  • 1
    So finially it all works. Your code was correct but I updated the DOM element wrong. I should have used the "TargetUpdateId" instead. Before did my second ajax call never called back to "success" for some weird reason I know about?! – radbyx Dec 06 '15 at 15:00
  • RenanAraújo It doesn't seems like the ModelState errors can be showned this way. Controller: ModelState.AddModelError(string.Empty, @"Not all inputs are valid."); View: @Html.ValidationSummary(true, "", new { @class = "text-danger" }). $('#updateTargetEditSetting').html(data.AjaxReturn); //$('#myModal .modal-body').html(data.AjaxReturn); (this will show the errors, but then if I do another ajax call it wont be returned to 'success' for some weird reason I can't figure out, any ideas? :) – radbyx Dec 08 '15 at 06:45
  • Ahh I was missing "form" here :$('#myModal .modal-body form').html(message); – radbyx Dec 08 '15 at 10:25
0

You can Return the Patial View, and Intialize ViewBag and assign the message to it, then in the view check if have value and show it if true.

Controller:

[HttpPost]
public ActionResult Edit(SettingViewModel model)
{
    // Put the ViewBag where ever you want
    ViewBag.ErrorMsg =" Error";
    return PartialView("EditSetting", model);
}

View:

@if(ViewBag.ErrorMsg !=null )
{
    <div>@ViewBag.ErrorMsg</div>
}

I hope that helped.

radbyx
  • 9,352
  • 21
  • 84
  • 127
user2120121
  • 665
  • 1
  • 6
  • 15
  • Yes i thought of that too just using the Model. I thought it was a hack, but might just be fine, I'll give it a try - thanks :) – radbyx Dec 03 '15 at 12:55
  • @Darin Dimitrov The MVC god, do NOT recommend ViewBag: http://stackoverflow.com/questions/6858723/lifetime-of-viewbag-elements-in-asp-net-mvc3 – radbyx Dec 03 '15 at 13:03
  • Yes do not use ViewBag, but the idea of setting "something" in the action, the JavaScript can use to either do one thing or the other can still be applied I think. I still open to other answers if there is a better one? :) – radbyx Dec 03 '15 at 13:11
0

Add error handling to your ViewModel:

bool hasErrors;
string errorMessage;

In your controller, if the data validation is ok, just return the PartialView or return RedirectToAction("Index");. If not, set hasErros = true; and a custom errorMessage.

In the view, place an error block someone where the user will see it:

@if (Model.hasErrors)
{
   <div>Model.errorMessage</div>
}

By the way, you can do the data validation inside the ViewModel constructor.

marcus
  • 333
  • 6
  • 14